bolt-cw-sdk 1.0.0

SDK for the BOLT protocol, providing utilities for interacting with the BOLT contracts.
Documentation
use crate::router::client::RouterClient;
use crate::router::error::RouterError;
use cosmwasm_std::Addr;
use serde::{Deserialize, Serialize};

impl RouterClient {
    pub async fn market_for_base(
        &self,
        base_asset_symbol: &str,
    ) -> Result<MarketForBaseResponse, RouterError> {
        let query = Query {
            market_for_base: MarketForBaseRequest {
                base_asset: base_asset_symbol.to_string(),
            },
        };
        let response: MarketForBaseResponse = self
            .query_contract(self.router_contract_address.to_string(), &query, None)
            .await
            .map_err(|err| {
                if let RouterError::CosmosClientError(cosmos_err) = &err {
                    if cosmos_err.no_data_returned_from_contract() {
                        return RouterError::NoMarketFound(base_asset_symbol.to_string());
                    }
                }

                err
            })?
            .data;

        Ok(response)
    }
}

#[derive(Serialize)]
struct Query {
    pub market_for_base: MarketForBaseRequest,
}

#[derive(Serialize)]
pub struct MarketForBaseRequest {
    pub base_asset: String,
}

#[derive(Debug, Deserialize)]
pub struct MarketForBaseResponse {
    pub market_address: Addr,
    pub base_asset_symbol: String,
    pub quote_assets_symbols: Vec<String>,
}

#[cfg(test)]
mod tests {
    use crate::oracle::client::OracleAdminClient;
    use crate::router::client::RouterAdminClient;
    use crate::router::error::RouterError;
    use crate::test_utils::helpers::{
        TEST_ASSET_ARCH_SYMBOL, TEST_ASSET_ETH_SYMBOL, TEST_ASSET_USDT_SYMBOL,
    };
    use crate::test_utils::test_scenario::TestScenario;
    use cosmwasm_std::{Decimal256, Uint128};
    use serial_test::serial;
    use std::ops::Add;
    use std::str::FromStr;
    use std::time::{Duration, SystemTime, UNIX_EPOCH};

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

        // Initialize oracle contract
        let price_threshold_ratio = Decimal256::from_str("0.5").unwrap();
        let price_expire_millis = Some(1000);
        let oracle_contract_address = test_scenario
            .instantiate_oracle_contract(price_threshold_ratio, price_expire_millis)
            .await;

        // Initialize router contract
        let default_protocol_fee = Decimal256::from_str("0.1").unwrap();
        let default_lp_fee = Decimal256::from_str("0.1").unwrap();
        let router_contract_address = test_scenario
            .instantiate_router_contract(
                oracle_contract_address.clone(),
                oracle_contract_address.clone(),
                default_protocol_fee,
                default_lp_fee,
            )
            .await;

        // Set default assets and prices
        let oracle_client =
            OracleAdminClient::from_scenario(&test_scenario, &oracle_contract_address)
                .expect("Failed to create oracle admin client");
        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_assets(&oracle_client).await;
        test_scenario
            .set_default_prices(&oracle_client, price_expiry_timestamp, "50000")
            .await;

        // Create market
        let router_admin_client = RouterAdminClient::from_scenario(
            &test_scenario,
            router_contract_address.parse().unwrap(),
        )
        .expect("Failed to create router admin client");

        test_scenario
            .create_market(
                &router_admin_client,
                TEST_ASSET_ARCH_SYMBOL,
                &[TEST_ASSET_USDT_SYMBOL],
                Uint128::new(10),
            )
            .await;
        test_scenario
            .create_market(
                &router_admin_client,
                TEST_ASSET_USDT_SYMBOL,
                &[TEST_ASSET_ARCH_SYMBOL],
                Uint128::new(10),
            )
            .await;

        let router_client = router_admin_client.public_router_client;
        let arch_market = router_client
            .market_for_base(TEST_ASSET_ARCH_SYMBOL)
            .await
            .expect("Failed to get market for base asset");
        assert_eq!(arch_market.base_asset_symbol, TEST_ASSET_ARCH_SYMBOL);
        assert_eq!(arch_market.quote_assets_symbols.len(), 1);
        assert_eq!(arch_market.quote_assets_symbols[0], TEST_ASSET_USDT_SYMBOL);

        let usdt_market = router_client
            .market_for_base(TEST_ASSET_USDT_SYMBOL)
            .await
            .expect("Failed to get market for base asset");
        assert_eq!(usdt_market.base_asset_symbol, TEST_ASSET_USDT_SYMBOL);
        assert_eq!(usdt_market.quote_assets_symbols.len(), 1);
        assert_eq!(usdt_market.quote_assets_symbols[0], TEST_ASSET_ARCH_SYMBOL);

        let eth_market_err = router_client
            .market_for_base(TEST_ASSET_ETH_SYMBOL)
            .await
            .expect_err("Failed to get market for base asset");
        assert!(matches!(
            eth_market_err,
            RouterError::NoMarketFound(ref s) if s == TEST_ASSET_ETH_SYMBOL
        ));
    }

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

        // Initialize oracle contract
        let price_threshold_ratio = Decimal256::from_str("0.5").unwrap();
        let price_expire_millis = Some(1000);
        let oracle_contract_address = test_scenario
            .instantiate_oracle_contract(price_threshold_ratio, price_expire_millis)
            .await;

        // Initialize router contract
        let default_protocol_fee = Decimal256::from_str("0.1").unwrap();
        let default_lp_fee = Decimal256::from_str("0.1").unwrap();
        let router_contract_address = test_scenario
            .instantiate_router_contract(
                oracle_contract_address.clone(),
                oracle_contract_address.clone(),
                default_protocol_fee,
                default_lp_fee,
            )
            .await;

        // Set default assets and prices
        let oracle_client =
            OracleAdminClient::from_scenario(&test_scenario, &oracle_contract_address)
                .expect("Failed to create oracle admin client");
        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_assets(&oracle_client).await;
        test_scenario
            .set_default_prices(&oracle_client, price_expiry_timestamp, "50000")
            .await;

        // Create market
        let router_admin_client = RouterAdminClient::from_scenario(
            &test_scenario,
            router_contract_address.parse().unwrap(),
        )
        .expect("Failed to create router admin client");

        test_scenario
            .create_market(
                &router_admin_client,
                TEST_ASSET_ARCH_SYMBOL,
                &[TEST_ASSET_USDT_SYMBOL],
                Uint128::new(10),
            )
            .await;
        test_scenario
            .create_market(
                &router_admin_client,
                TEST_ASSET_USDT_SYMBOL,
                &[TEST_ASSET_ARCH_SYMBOL],
                Uint128::new(10),
            )
            .await;

        let incorrect_token = "incorrect_token";
        let router_client = router_admin_client.public_router_client;
        let err = router_client
            .market_for_base(incorrect_token)
            .await
            .expect_err("should not be found");

        assert!(matches!(err, RouterError::NoMarketFound(token) if token == incorrect_token));
    }
}