bolt-cw-sdk 1.0.0

SDK for the BOLT protocol, providing utilities for interacting with the BOLT contracts.
Documentation
use crate::oracle::client::OracleAdminClient;
use crate::oracle::error::OracleError;
use crate::tx_builder::TxBuilder;
use cosmrs::cosmwasm::MsgExecuteContract;
use cosmrs::tx::Msg;
use cosmwasm_std::Timestamp;
use serde::Serialize;

impl OracleAdminClient {
    pub fn append_post_prices_msg(
        &self,
        tx_builder: &mut TxBuilder,
        price_updates: Vec<PriceUpdate>,
    ) -> Result<(), OracleError> {
        let msgs = price_updates.into_iter().map(|price_update| {
            let contract_msg = ExecuteMsg {
                update_price: price_update,
            };
            let contract_msg =
                serde_json::to_vec(&contract_msg).map_err(OracleError::SerdeJsonError)?;
            let msg = MsgExecuteContract {
                sender: tx_builder.account_id.clone(),
                contract: self.chain_config.oracle_contract_address.clone(),
                msg: contract_msg,
                funds: vec![],
            };
            msg.to_any().map_err(OracleError::EyreError)
        });
        let msgs = msgs.collect::<Result<Vec<_>, _>>()?;
        tx_builder.add_msgs(msgs);
        Ok(())
    }
}

#[derive(Serialize)]
pub struct PriceUpdate {
    pub base_asset_symbol: String,
    pub quote_asset_symbol: String,
    pub price: String,
    pub price_expiry_time: Option<Timestamp>,
}

#[derive(Serialize)]
struct ExecuteMsg {
    pub update_price: PriceUpdate,
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::events::BOLT_UPDATE_PRICE_EVENT_NAME;
    use crate::test_utils::helpers::{
        assert_event_attribute, get_test_arch_usdt_pair, TEST_ASSET_ARCH_SYMBOL,
        TEST_ASSET_USDT_SYMBOL,
    };
    use crate::test_utils::test_scenario::TestScenario;
    use cosmrs::tendermint::abci::Code;
    use cosmwasm_std::Decimal256;
    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_post_prices() {
        let test_scenario = TestScenario::new_from_config("config.json".to_string()).await;

        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;

        let client = OracleAdminClient::from_scenario(&test_scenario, &oracle_contract_address)
            .expect("Failed to create oracle admin client");
        let account = client
            .public_oracle_client
            .account(test_scenario.admin_address)
            .await
            .expect("Failed to get account from public oracle client");
        let mut tx_builder = TxBuilder::new(
            test_scenario.admin_mnemonic,
            test_scenario.chain_prefix,
            test_scenario.chain_id,
            test_scenario.derivation_path,
            account.sequence,
            account.account_number,
        )
        .expect("Failed to create tx builder");
        let asset_pairs = vec![get_test_arch_usdt_pair()];
        client
            .append_add_asset_pairs_msg(&mut tx_builder, asset_pairs)
            .expect("Failed to append add asset pairs msg");

        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");
        let arch_usdt_price = Decimal256::from_str("50000").unwrap();
        let price_expiry_time = Timestamp::from_seconds(price_expiry_timestamp.as_secs());
        let price_updates = vec![PriceUpdate {
            base_asset_symbol: TEST_ASSET_ARCH_SYMBOL.to_string(),
            quote_asset_symbol: TEST_ASSET_USDT_SYMBOL.to_string(),
            price: arch_usdt_price.to_string(),
            price_expiry_time: Some(price_expiry_time),
        }];
        client
            .append_post_prices_msg(&mut tx_builder, price_updates)
            .expect("Append PostPrices message");

        tx_builder.set_memo("From test_post_prices".to_string());

        let response = client
            .execute_tx(tx_builder)
            .await
            .expect("Failed to broadcast tx");

        match response.tx_result.code {
            Code::Ok => {
                println!("Transaction successful: {:?}", response.hash);
                dbg!(&response.tx_result.events);
                let update_price_event = response
                    .tx_result
                    .events
                    .iter()
                    .find(|ev| ev.kind == BOLT_UPDATE_PRICE_EVENT_NAME)
                    .expect("Failed to find update price event");
                assert_event_attribute(
                    update_price_event,
                    "trading_pair",
                    &format!("{}:{}", TEST_ASSET_ARCH_SYMBOL, TEST_ASSET_USDT_SYMBOL),
                );
                assert_event_attribute(update_price_event, "new_price", "50000");
                assert_event_attribute(update_price_event, "forced_update", "true");
            }
            Code::Err(code) => {
                panic!(
                    "Transaction failed with code: {:?} response: {:?}",
                    code, response
                );
            }
        }
    }
}