#![cfg(not(tarpaulin_include))] #![allow(dead_code)] use std::collections::HashMap; use anyhow::Result as AnyResult; use cosmwasm_schema::cw_serde; use cosmwasm_std::Decimal; use cosmwasm_std::{coin, to_json_binary, Addr, Coin, Empty, StdResult, Uint128}; use cw20::{BalanceResponse, Cw20Coin, Cw20ExecuteMsg, Cw20QueryMsg}; use derivative::Derivative; use itertools::Itertools; use astroport::asset::{native_asset_info, token_asset_info, Asset, AssetInfo, PairInfo}; use astroport::factory::{PairConfig, PairType}; use astroport::pair::{ ConfigResponse, CumulativePricesResponse, Cw20HookMsg, ExecuteMsg, PoolResponse, QueryMsg, ReverseSimulationResponse, SimulationResponse, }; use astroport_pair_transmuter::contract::{execute, instantiate, reply}; use astroport_pair_transmuter::queries::query; use astroport_test::coins::TestCoin; use astroport_test::cw_multi_test::{ App, AppBuilder, AppResponse, Contract, ContractWrapper, Executor, }; use astroport_test::modules::stargate::{MockStargate, StargateApp as TestApp}; const INIT_BALANCE: u128 = u128::MAX; #[cw_serde] pub struct AmpGammaResponse { pub amp: Decimal, pub gamma: Decimal, pub future_time: u64, } pub fn init_native_coins(test_coins: &[TestCoin]) -> Vec { let test_coins: Vec = test_coins .iter() .unique() .filter_map(|test_coin| match test_coin { TestCoin::Native(name) => { let init_balance = INIT_BALANCE; Some(coin(init_balance, name)) } _ => None, }) .collect(); test_coins } fn token_contract() -> Box> { Box::new(ContractWrapper::new_with_empty( cw20_base::contract::execute, cw20_base::contract::instantiate, cw20_base::contract::query, )) } fn pair_contract() -> Box> { Box::new(ContractWrapper::new_with_empty(execute, instantiate, query).with_reply_empty(reply)) } fn factory_contract() -> Box> { Box::new( ContractWrapper::new_with_empty( astroport_factory::contract::execute, astroport_factory::contract::instantiate, astroport_factory::contract::query, ) .with_reply_empty(astroport_factory::contract::reply), ) } fn coin_registry_contract() -> Box> { Box::new(ContractWrapper::new_with_empty( astroport_native_coin_registry::contract::execute, astroport_native_coin_registry::contract::instantiate, astroport_native_coin_registry::contract::query, )) } #[derive(Derivative)] #[derivative(Debug)] pub struct Helper { #[derivative(Debug = "ignore")] pub app: TestApp, pub owner: Addr, pub assets: HashMap, pub factory: Addr, pub pair_addr: Addr, pub lp_token: String, pub fake_maker: Addr, } impl Helper { pub fn new( owner: &Addr, test_coins: Vec, native_coins: Vec<(String, u8)>, // decimals for native coins ) -> AnyResult { let mut app = AppBuilder::new_custom() .with_stargate(MockStargate::default()) .build(|router, _, storage| { router .bank .init_balance(storage, owner, init_native_coins(&test_coins)) .unwrap() }); let token_code_id = app.store_code(token_contract()); let asset_infos_vec = test_coins .iter() .cloned() .map(|coin| { let asset_info = match &coin { TestCoin::Native(denom) => native_asset_info(denom.clone()), TestCoin::Cw20(..) | TestCoin::Cw20Precise(..) => { let (name, precision) = coin.cw20_init_data().unwrap(); token_asset_info(Self::init_token( &mut app, token_code_id, name, precision, owner, )) } }; (coin, asset_info) }) .collect::>(); let pair_code_id = app.store_code(pair_contract()); let factory_code_id = app.store_code(factory_contract()); let pair_type = PairType::Custom("transmuter".to_string()); let fake_maker = Addr::unchecked("fake_maker"); let coin_registry_id = app.store_code(coin_registry_contract()); let coin_registry_address = app .instantiate_contract( coin_registry_id, owner.clone(), &astroport::native_coin_registry::InstantiateMsg { owner: owner.to_string(), }, &[], "Coin registry", None, ) .unwrap(); app.execute_contract( owner.clone(), coin_registry_address.clone(), &astroport::native_coin_registry::ExecuteMsg::Add { native_coins }, &[], ) .unwrap(); let init_msg = astroport::factory::InstantiateMsg { fee_address: Some(fake_maker.to_string()), pair_configs: vec![PairConfig { code_id: pair_code_id, maker_fee_bps: 0, total_fee_bps: 0, pair_type: pair_type.clone(), is_disabled: false, is_generator_disabled: false, permissioned: true, }], token_code_id, generator_address: None, owner: owner.to_string(), whitelist_code_id: 0, coin_registry_address: coin_registry_address.to_string(), tracker_config: None, }; let factory = app.instantiate_contract( factory_code_id, owner.clone(), &init_msg, &[], "factory label", None, )?; let asset_infos = asset_infos_vec .clone() .into_iter() .map(|(_, asset_info)| asset_info) .collect_vec(); let init_pair_msg = astroport::factory::ExecuteMsg::CreatePair { pair_type, asset_infos: asset_infos.clone(), init_params: None, }; app.execute_contract(owner.clone(), factory.clone(), &init_pair_msg, &[])?; let resp: PairInfo = app.wrap().query_wasm_smart( &factory, &astroport::factory::QueryMsg::Pair { asset_infos }, )?; Ok(Self { app, owner: owner.clone(), assets: asset_infos_vec.into_iter().collect(), factory, pair_addr: resp.contract_addr, lp_token: resp.liquidity_token, fake_maker, }) } pub fn provide_liquidity(&mut self, sender: &Addr, assets: &[Asset]) -> AnyResult { let funds = assets.mock_coins_sent(&mut self.app, sender, &self.pair_addr, SendType::Allowance); let msg = ExecuteMsg::ProvideLiquidity { assets: assets.to_vec(), slippage_tolerance: None, auto_stake: None, receiver: None, min_lp_to_receive: None, }; self.app .execute_contract(sender.clone(), self.pair_addr.clone(), &msg, &funds) } pub fn withdraw_liquidity( &mut self, sender: &Addr, amount: u128, assets: Vec, ) -> AnyResult { self.app.execute_contract( sender.clone(), self.pair_addr.clone(), &ExecuteMsg::WithdrawLiquidity { assets, min_assets_to_receive: None, }, &[coin(amount, self.lp_token.clone())], ) } pub fn swap( &mut self, sender: &Addr, offer_asset: &Asset, ask_asset_info: Option, to: Option, ) -> AnyResult { match &offer_asset.info { AssetInfo::Token { contract_addr } => { let msg = Cw20ExecuteMsg::Send { contract: self.pair_addr.to_string(), amount: offer_asset.amount, msg: to_json_binary(&Cw20HookMsg::Swap { ask_asset_info, belief_price: None, max_spread: None, to, }) .unwrap(), }; self.app .execute_contract(sender.clone(), contract_addr.clone(), &msg, &[]) } AssetInfo::NativeToken { .. } => { let funds = offer_asset.mock_coin_sent( &mut self.app, sender, &self.pair_addr, SendType::None, ); let msg = ExecuteMsg::Swap { offer_asset: offer_asset.clone(), ask_asset_info, belief_price: None, max_spread: None, to, }; self.app .execute_contract(sender.clone(), self.pair_addr.clone(), &msg, &funds) } } } pub fn simulate_swap( &self, offer_asset: &Asset, ask_asset_info: Option, ) -> StdResult { self.app.wrap().query_wasm_smart( &self.pair_addr, &QueryMsg::Simulation { offer_asset: offer_asset.clone(), ask_asset_info, }, ) } pub fn simulate_reverse_swap( &self, ask_asset: &Asset, offer_asset_info: Option, ) -> StdResult { self.app.wrap().query_wasm_smart( &self.pair_addr, &QueryMsg::ReverseSimulation { ask_asset: ask_asset.clone(), offer_asset_info, }, ) } pub fn query_prices(&self) -> StdResult { self.app .wrap() .query_wasm_smart(&self.pair_addr, &QueryMsg::CumulativePrices {}) } fn init_token( app: &mut TestApp, token_code: u64, name: String, decimals: u8, owner: &Addr, ) -> Addr { let init_balance = INIT_BALANCE; app.instantiate_contract( token_code, owner.clone(), &astroport::token::InstantiateMsg { symbol: name.to_string(), name, decimals, initial_balances: vec![Cw20Coin { address: owner.to_string(), amount: Uint128::from(init_balance), }], mint: None, marketing: None, }, &[], "{name}_token", None, ) .unwrap() } pub fn token_balance(&self, token_addr: &Addr, user: &Addr) -> u128 { let resp: BalanceResponse = self .app .wrap() .query_wasm_smart( token_addr, &Cw20QueryMsg::Balance { address: user.to_string(), }, ) .unwrap(); resp.balance.u128() } pub fn native_balance(&self, denom: impl Into, user: &Addr) -> u128 { self.app .wrap() .query_balance(user, denom) .unwrap() .amount .u128() } pub fn coin_balance(&self, coin: &TestCoin, user: &Addr) -> u128 { match &self.assets[coin] { AssetInfo::Token { contract_addr } => self.token_balance(contract_addr, user), AssetInfo::NativeToken { denom } => self.native_balance(denom, user), } } pub fn give_me_money(&mut self, assets: &[Asset], recipient: &Addr) { let funds = assets.mock_coins_sent(&mut self.app, &self.owner, recipient, SendType::Transfer); if !funds.is_empty() { self.app .send_tokens(self.owner.clone(), recipient.clone(), &funds) .unwrap(); } } pub fn query_config(&self) -> StdResult { self.app .wrap() .query_wasm_smart(&self.pair_addr, &QueryMsg::Config {}) } pub fn query_pool(&self) -> StdResult { self.app .wrap() .query_wasm_smart(&self.pair_addr, &QueryMsg::Pool {}) } pub fn query_share(&self, amount: impl Into) -> StdResult> { self.app.wrap().query_wasm_smart::>( &self.pair_addr, &QueryMsg::Share { amount: amount.into(), }, ) } } #[derive(Clone, Copy)] pub enum SendType { Allowance, Transfer, None, } pub trait AssetExt { fn mock_coin_sent( &self, app: &mut TestApp, user: &Addr, spender: &Addr, typ: SendType, ) -> Vec; } impl AssetExt for Asset { fn mock_coin_sent( &self, app: &mut TestApp, user: &Addr, spender: &Addr, typ: SendType, ) -> Vec { let mut funds = vec![]; match &self.info { AssetInfo::Token { contract_addr } if !self.amount.is_zero() => { let msg = match typ { SendType::Allowance => Cw20ExecuteMsg::IncreaseAllowance { spender: spender.to_string(), amount: self.amount, expires: None, }, SendType::Transfer => Cw20ExecuteMsg::Transfer { recipient: spender.to_string(), amount: self.amount, }, _ => unimplemented!(), }; app.execute_contract(user.clone(), contract_addr.clone(), &msg, &[]) .unwrap(); } AssetInfo::NativeToken { denom } if !self.amount.is_zero() => { funds = vec![coin(self.amount.u128(), denom)]; } _ => {} } funds } } pub trait AssetsExt { fn mock_coins_sent( &self, app: &mut TestApp, user: &Addr, spender: &Addr, typ: SendType, ) -> Vec; } impl AssetsExt for &[Asset] { fn mock_coins_sent( &self, app: &mut TestApp, user: &Addr, spender: &Addr, typ: SendType, ) -> Vec { let mut funds = vec![]; for asset in self.iter() { funds.extend(asset.mock_coin_sent(app, user, spender, typ)); } funds } } pub trait AppExtension { fn next_block(&mut self, time: u64); } impl AppExtension for App { fn next_block(&mut self, time: u64) { self.update_block(|block| { block.time = block.time.plus_seconds(time); block.height += 1 }); } }