bolt-cw-sdk 1.0.0

SDK for the BOLT protocol, providing utilities for interacting with the BOLT contracts.
Documentation
use crate::market::Allowance;
use crate::router::client::RouterAdminClient;
use crate::router::update_market_config::UpdateMarketConfig;
use crate::test_utils::test_scenario::TestScenario;
use crate::tx_builder::TxBuilder;
use cosmrs::tendermint::abci::Code;
use cosmwasm_std::{Addr, Decimal256, Uint128};
use serde::Serialize;
use tendermint_rpc::endpoint::broadcast::tx_commit::Response;

impl TestScenario {
    pub async fn instantiate_router_contract(
        &mut self,
        default_price_oracle_contract: String,
        default_protocol_fee_recipient: String,
        default_protocol_fee: Decimal256,
        default_lp_fee: Decimal256,
    ) -> String {
        let init_msg = InstantiateMsg {
            admin: Addr::unchecked(self.admin_address.clone()),
            default_price_oracle_contract: Addr::unchecked(default_price_oracle_contract),
            default_protocol_fee_recipient: Addr::unchecked(default_protocol_fee_recipient),
            default_protocol_fee,
            default_lp_fee,
            settlement_code_id: self.settlement_code_id,
        };
        let msg = serde_json::to_vec(&init_msg).expect("Serialize init msg");

        self.instantiate_contract(msg, self.router_code_id, "router")
            .await
    }

    pub async fn create_market(
        &self,
        client: &RouterAdminClient,
        base_asset_symbol: &str,
        quote_assets_symbols: &[&str],
        min_base_out: impl Into<Uint128>,
    ) -> String {
        let account = client
            .public_router_client
            .account(self.admin_address.clone())
            .await
            .expect("Failed to get account from public router client");
        let mut tx_builder = TxBuilder::new(
            self.admin_mnemonic.clone(),
            self.chain_prefix.clone(),
            self.chain_id.clone(),
            self.derivation_path,
            account.sequence,
            account.account_number,
        )
        .expect("Failed to create tx builder");

        client
            .append_create_market_msg(
                &mut tx_builder,
                None,
                None,
                None,
                None,
                base_asset_symbol.to_string(),
                quote_assets_symbols
                    .iter()
                    .map(ToString::to_string)
                    .collect(),
                Allowance::AllowedLps(vec![self.admin_address.clone()]),
                min_base_out.into(),
            )
            .expect("Failed to append post prices msg");

        tx_builder.set_memo("From test_create_market".to_string());
        let gas = 700_000u64;
        let amount = 80_000_000_000_000_000u128;
        tx_builder.set_fee(amount, &self.chain_denom, gas);
        let signed_bytes = tx_builder
            .get_signed_bytes()
            .expect("Failed to get signed bytes");

        let response = client
            .broadcast_tx(signed_bytes)
            .await
            .expect("Failed to broadcast tx");

        if let Code::Err(code) = response.tx_result.code {
            panic!(
                "Transaction failed with code: {:?} response: {:?}",
                code, response
            );
        }
        let instantiate_event = response
            .tx_result
            .events
            .iter()
            .find(|event| event.kind == "instantiate")
            .expect("Failed to find instantiate event");
        let contract_address_event_attr = instantiate_event
            .attributes
            .iter()
            .find(|attr| {
                attr.key_str().expect("Should stringify attribute key") == "_contract_address"
            })
            .expect("Should find the contract address attribute")
            .clone();

        contract_address_event_attr
            .value_str()
            .expect("Should stringify the contract address")
            .to_string()
    }

    pub async fn swap_exact_in(
        &self,
        client: &RouterAdminClient,
        want_out: &str,
        minimum_base_out: Option<u128>,
        receiver: Option<String>,
        swap_amount: u128,
        swap_asset: String,
    ) -> Response {
        let account = client
            .account(self.admin_address.clone())
            .await
            .expect("Failed to get account info");
        let mut tx_builder = TxBuilder::new(
            self.admin_mnemonic.clone(),
            self.chain_prefix.clone(),
            self.chain_id.clone(),
            self.derivation_path,
            account.sequence,
            account.account_number,
        )
        .expect("Failed to create tx builder");

        let minimum_base_out = minimum_base_out.map(Uint128::new);

        client
            .append_swap_exact_in_msg(
                &mut tx_builder,
                swap_amount,
                swap_asset,
                want_out.to_string(),
                minimum_base_out,
                receiver,
            )
            .expect("Failed to append swap exact in msg");
        tx_builder.set_memo("From swap_exact_in method".to_string());
        let gas = 500_000u64;
        let amount = 80_000_000_000_000_000u128;
        tx_builder.set_fee(amount, &self.chain_denom, gas);
        let signed_bytes = tx_builder
            .get_signed_bytes()
            .expect("Failed to get signed bytes");
        client
            .broadcast_tx(signed_bytes)
            .await
            .expect("Failed to broadcast tx")
    }

    pub async fn update_market_config(
        &self,
        client: &RouterAdminClient,
        config: UpdateMarketConfig,
    ) -> Response {
        let account = client
            .public_router_client
            .account(self.admin_address.clone())
            .await
            .expect("Failed to get account from public router client");
        let mut tx_builder = TxBuilder::new(
            self.admin_mnemonic.clone(),
            self.chain_prefix.clone(),
            self.chain_id.clone(),
            self.derivation_path,
            account.sequence,
            account.account_number,
        )
        .expect("Failed to create tx builder");

        client
            .append_update_market_config(&mut tx_builder, config)
            .unwrap();

        tx_builder.set_memo("From TestScenario::update_market_config".to_string());
        let gas = 660_000u64;
        let amount = 80_000_000_000_000_000u128;
        tx_builder.set_fee(amount, &self.chain_denom, gas);
        let signed_bytes = tx_builder
            .get_signed_bytes()
            .expect("Failed to get signed bytes");

        let response = client
            .broadcast_tx(signed_bytes)
            .await
            .expect("Failed to broadcast tx");

        if let Code::Err(code) = response.tx_result.code {
            panic!(
                "Transaction failed with code: {:?} response: {:?}",
                code, response
            );
        }
        response
    }
}

#[derive(Serialize)]
struct InstantiateMsg {
    /// The address which controls the config
    pub admin: Addr,
    // an already configured oracle contract which will be used as default
    pub default_price_oracle_contract: Addr,
    // the address who will receive the protocol fees which will be used as default
    pub default_protocol_fee_recipient: Addr,
    // the percentage of value taken as protocol fees which will be used as default
    pub default_protocol_fee: Decimal256,
    // the percentage of value taken to be given to liquidity providers which will be used as default
    pub default_lp_fee: Decimal256,
    pub settlement_code_id: u64,
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::test_utils::helpers::VALID_BECH32_ADDRESS;
    use crate::test_utils::test_scenario::TestScenario;
    use serial_test::serial;

    #[tokio::test]
    #[serial]
    #[ignore]
    async fn test_instantiate_router_contract() {
        let mut test_scenario = TestScenario::new_from_config("config.json".to_string()).await;

        let default_protocol_fee = Decimal256::percent(10);
        let default_lp_fee = Decimal256::percent(10);

        let contract_addr = test_scenario
            .instantiate_router_contract(
                VALID_BECH32_ADDRESS.to_string(), // For this test we don't have to use an exact address of the recipient.
                VALID_BECH32_ADDRESS.to_string(), // For this test we don't have to use an exact address of the recipient.
                default_protocol_fee,
                default_lp_fee,
            )
            .await;
        println!("Contract address: {:?}", contract_addr);
    }
}