bolt-cw-sdk 1.0.0

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

use super::account::TestAccount;

impl TestScenario {
    #[allow(clippy::too_many_arguments)]
    pub async fn instantiate_settlement_contract(
        &mut self,
        price_oracle_contract: impl Into<String>,
        protocol_fee_recipient: impl Into<String>,
        protocol_fee: Decimal256,
        lp_fee: Decimal256,
        base_asset_symbol: String,
        quote_asset_symbols: Vec<String>,
        min_base_out: Uint128,
    ) -> String {
        let init_msg = InstantiateMsg {
            admin: Addr::unchecked(self.admin_address.clone()),
            price_oracle_contract: Addr::unchecked(price_oracle_contract),
            protocol_fee_recipient: Addr::unchecked(protocol_fee_recipient),
            protocol_fee,
            lp_fee,
            base_asset_symbol,
            quote_asset_symbols,
            allowance: Allowance::BannedLps(vec![]),
            min_base_out,
        };
        let msg = serde_json::to_vec(&init_msg).expect("Serialize init msg");

        self.instantiate_contract(msg, self.settlement_code_id, "settlement")
            .await
    }

    pub async fn deposit_base(
        &self,
        client: &MarketAdminClient,
        market_address: AccountId,
        sender_account: &TestAccount,
        deposit_amount: Coin,
    ) -> Response {
        let account = client
            .public_market_client
            .account(sender_account.account_id.to_string())
            .await
            .expect("Failed to get account from public market client");
        let mut tx_builder = TxBuilder::new(
            sender_account.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_deposit_base_msg(
                &mut tx_builder,
                market_address,
                deposit_amount.amount,
                deposit_amount.denom.to_string(),
            )
            .expect("Failed to append deposit base msg");

        tx_builder.set_memo("From deposit_base".to_string());
        let gas = 550_000u64;
        let amount = 77_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
    }

    pub async fn swap(
        &self,
        client: &MarketAdminClient,
        market_address: AccountId,
        sender_account: &TestAccount,
        swap_amount: Coin,
        minimum_out: Option<Uint128>,
        receiver: Option<Addr>,
    ) -> Response {
        let account = client
            .public_market_client
            .account(sender_account.account_id.to_string())
            .await
            .unwrap();
        let mut tx_builder = TxBuilder::new(
            sender_account.mnemonic.clone(),
            self.chain_prefix.clone(),
            self.chain_id.clone(),
            self.derivation_path,
            account.sequence,
            account.account_number,
        )
        .unwrap();

        client
            .append_swap_msg(
                &mut tx_builder,
                market_address,
                swap_amount,
                minimum_out,
                receiver,
            )
            .unwrap();

        tx_builder.set_memo("From TestScenario::swap".to_string());
        let gas = 410_000u64;
        let amount = 70_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
    }

    pub async fn update_config(
        &self,
        client: &MarketAdminClient,
        market_address: String,
        sender_account: &TestAccount,
        config: UpdateConfig,
    ) -> Response {
        let account = client
            .public_market_client
            .account(sender_account.account_id.to_string())
            .await
            .unwrap();
        let mut tx_builder = TxBuilder::new(
            sender_account.mnemonic.clone(),
            self.chain_prefix.clone(),
            self.chain_id.clone(),
            self.derivation_path,
            account.sequence,
            account.account_number,
        )
        .unwrap();

        let market_contract_account_id = market_address.to_string().parse().unwrap();
        client
            .append_update_config_msg(&mut tx_builder, market_contract_account_id, config)
            .expect("Failed to append update config msg");

        tx_builder.set_memo("From TestScenario::update_config".to_string());
        let gas = 400_000u64;
        let amount = 70_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
    pub price_oracle_contract: Addr,
    /// The address who will receive the protocol fees
    pub protocol_fee_recipient: Addr,
    /// The percentage of value taken as protocol fees. e.g 0.1
    pub protocol_fee: Decimal256,
    /// The percentage of value taken to be given to liquidity providers e.g 0.1
    pub lp_fee: Decimal256,
    /// Native token symbol of base asset e.g aarch, ibc/xxxx
    pub base_asset_symbol: String,
    /// Native token symbols for quote assets e.g aarch, ibc/xxxx
    pub quote_asset_symbols: Vec<String>,
    /// Specifies addresses to be allowed/banned from depositing tokens
    pub allowance: Allowance,
    /// The minimum amount of base asset that can be withdrawn from the pool e.g 1aconst
    pub min_base_out: Uint128,
}

#[cfg(test)]
mod tests {
    use std::ops::Add;
    use std::str::FromStr;
    use std::time::{Duration, SystemTime, UNIX_EPOCH};

    use serial_test::serial;

    use super::*;
    use crate::oracle::client::OracleAdminClient;
    use crate::test_utils::helpers::{TEST_ASSET_ARCH_SYMBOL, TEST_ASSET_USDT_SYMBOL};
    use crate::test_utils::test_scenario::TestScenario;

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

        // Settlement contract queries the oracle on instantiate so we have to instantiate it here.
        let price_threshold_ratio = Decimal256::from_str("0.1").unwrap();
        let price_expire_millis = Some(1000);
        let price_oracle_contract = test_scenario
            .instantiate_oracle_contract(price_threshold_ratio, price_expire_millis)
            .await;

        let client = OracleAdminClient::from_scenario(&test_scenario, &price_oracle_contract)
            .expect("Failed to create oracle admin client");

        // Set default assets
        test_scenario.set_default_assets(&client).await;

        // Set default prices
        let price_expiry_time = SystemTime::now().add(Duration::from_secs(7200)); // 2h from now
        let price_expiry_timestamp = price_expiry_time
            .duration_since(UNIX_EPOCH)
            .expect("Converting to timestamp failed");
        test_scenario
            .set_default_prices(&client, price_expiry_timestamp, "50000")
            .await;

        // For this test we don't have to use an exact address of the recipient.
        let protocol_fee_recipient =
            "archway1706lywddqxu24ff6426puftkfaw20tyhdkun30pljux7e9alv2fql0q0ca".to_string();
        let protocol_fee = Decimal256::percent(10);
        let lp_fee = Decimal256::percent(10);

        let contract_addr = test_scenario
            .instantiate_settlement_contract(
                price_oracle_contract,
                protocol_fee_recipient,
                protocol_fee,
                lp_fee,
                TEST_ASSET_ARCH_SYMBOL.to_owned(),
                vec![TEST_ASSET_USDT_SYMBOL.to_owned()],
                Uint128::zero(),
            )
            .await;
        println!("Contract address: {:?}", contract_addr);
    }
}