use cosmwasm_std::testing::mock_env; use cosmwasm_std::{to_json_binary, Addr, Coin, Decimal, Timestamp, Uint128}; use cw20::{BalanceResponse, Cw20ExecuteMsg, Cw20QueryMsg, MinterResponse}; use cw_multi_test::{App, ContractWrapper, Executor}; use dexter::asset::{Asset, AssetInfo}; use dexter::lp_token::InstantiateMsg as TokenInstantiateMsg; use dexter::router::{ ConfigResponse, ExecuteMsg, HopSwapRequest, InstantiateMsg, QueryMsg, SimulateMultiHopResponse, SimulatedTrade, }; use dexter::vault::{ ConfigResponse as VaultConfigResponse, ExecuteMsg as VaultExecuteMsg, FeeInfo, InstantiateMsg as VaultInstantiateMsg, PoolTypeConfig, PoolInfoResponse, PoolType, QueryMsg as VaultQueryMsg, SwapType, PoolCreationFee, AutoStakeImpl, PauseInfo, NativeAssetPrecisionInfo, }; const EPOCH_START: u64 = 1_000_000; fn mock_app(owner: Addr, coins: Vec<Coin>) -> App { let mut env = mock_env(); env.block.time = Timestamp::from_seconds(EPOCH_START); let mut app = App::new(|router, _, storage| { // initialization moved to App construction router.bank.init_balance(storage, &owner, coins).unwrap(); }); app.set_block(env.block); app } fn store_router_code(app: &mut App) -> u64 { let router_contract = Box::new(ContractWrapper::new_with_empty( dexter_router::contract::execute, dexter_router::contract::instantiate, dexter_router::contract::query, )); app.store_code(router_contract) } fn store_vault_code(app: &mut App) -> u64 { let factory_contract = Box::new( ContractWrapper::new_with_empty( dexter_vault::contract::execute, dexter_vault::contract::instantiate, dexter_vault::contract::query, ) .with_reply_empty(dexter_vault::contract::reply), ); app.store_code(factory_contract) } fn store_token_code(app: &mut App) -> u64 { let token_contract = Box::new(ContractWrapper::new_with_empty( dexter_lp_token::contract::execute, dexter_lp_token::contract::instantiate, dexter_lp_token::contract::query, )); app.store_code(token_contract) } fn store_stable5_pool_code(app: &mut App) -> u64 { let pool_contract = Box::new(ContractWrapper::new_with_empty( dexter_stable_pool::contract::execute, dexter_stable_pool::contract::instantiate, dexter_stable_pool::contract::query, )); app.store_code(pool_contract) } fn store_weighted_pool_code(app: &mut App) -> u64 { let pool_contract = Box::new(ContractWrapper::new_with_empty( dexter_weighted_pool::contract::execute, dexter_weighted_pool::contract::instantiate, dexter_weighted_pool::contract::query, )); app.store_code(pool_contract) } // Initialize a vault with StableSwap, Weighted pools fn instantiate_contract(app: &mut App, owner: &Addr) -> Addr { let weighted_pool_code_id = store_weighted_pool_code(app); let stable5_pool_code_id = store_stable5_pool_code(app); let vault_code_id = store_vault_code(app); let token_code_id = store_token_code(app); let pool_configs = vec![ PoolTypeConfig { code_id: weighted_pool_code_id, pool_type: PoolType::Weighted {}, default_fee_info: FeeInfo { total_fee_bps: 300u16, protocol_fee_percent: 64u16, }, allow_instantiation: dexter::vault::AllowPoolInstantiation::Everyone, paused: PauseInfo::default(), }, PoolTypeConfig { code_id: stable5_pool_code_id, pool_type: PoolType::StableSwap {}, default_fee_info: FeeInfo { total_fee_bps: 300u16, protocol_fee_percent: 64u16, }, allow_instantiation: dexter::vault::AllowPoolInstantiation::Everyone, paused: PauseInfo::default(), }, ]; let vault_init_msg = VaultInstantiateMsg { pool_configs: pool_configs.clone(), lp_token_code_id: Some(token_code_id), fee_collector: Some("fee_collector".to_string()), owner: owner.to_string(), pool_creation_fee: PoolCreationFee::default(), auto_stake_impl: dexter::vault::AutoStakeImpl::None, }; let vault_instance = app .instantiate_contract( vault_code_id, owner.to_owned(), &vault_init_msg, &[], "vault", None, ) .unwrap(); return vault_instance; } fn initialize_3_tokens(app: &mut App, owner: &Addr) -> (Addr, Addr, Addr) { let token_code_id = store_token_code(app); // Initialize 3 tokens let token_instance0 = app .instantiate_contract( token_code_id, Addr::unchecked(owner.clone()), &TokenInstantiateMsg { name: "x_token".to_string(), symbol: "X-Tok".to_string(), decimals: 6, initial_balances: vec![], mint: Some(MinterResponse { minter: owner.to_string(), cap: None, }), marketing: None, }, &[], "x_token", None, ) .unwrap(); let token_instance2 = app .instantiate_contract( token_code_id, Addr::unchecked(owner.clone()), &TokenInstantiateMsg { name: "y_token".to_string(), symbol: "y-Tok".to_string(), decimals: 6, initial_balances: vec![], mint: Some(MinterResponse { minter: owner.to_string(), cap: None, }), marketing: None, }, &[], "y_token", None, ) .unwrap(); let token_instance3 = app .instantiate_contract( token_code_id, Addr::unchecked(owner.clone()), &TokenInstantiateMsg { name: "z_token".to_string(), symbol: "z-Tok".to_string(), decimals: 6, initial_balances: vec![], mint: Some(MinterResponse { minter: owner.to_string(), cap: None, }), marketing: None, }, &[], "x_token", None, ) .unwrap(); (token_instance0, token_instance2, token_instance3) } // Mints some Tokens to "to" recipient fn mint_some_tokens(app: &mut App, owner: Addr, token_instance: Addr, amount: Uint128, to: String) { let msg = cw20::Cw20ExecuteMsg::Mint { recipient: to.clone(), amount: amount, }; app.execute_contract(owner.clone(), token_instance.clone(), &msg, &[]) .unwrap(); } /// Initialize a STABLE-5-POOL /// -------------------------- fn initialize_stable_5_pool( app: &mut App, owner: &Addr, vault_instance: Addr, token_instance0: Addr, token_instance1: Addr, token_instance2: Addr, denom0: String, denom1: String, ) -> (Addr, Addr, Uint128) { let asset_infos = vec![ AssetInfo::NativeToken { denom: denom0.clone() }, AssetInfo::NativeToken { denom: denom1.clone() }, AssetInfo::Token { contract_addr: token_instance1.clone(), }, AssetInfo::Token { contract_addr: token_instance0.clone(), }, AssetInfo::Token { contract_addr: token_instance2.clone(), }, ]; // Initialize Stable-5-Pool contract instance // ------------------------------------------ let vault_config_res: VaultConfigResponse = app .wrap() .query_wasm_smart(vault_instance.clone(), &VaultQueryMsg::Config {}) .unwrap(); let next_pool_id = vault_config_res.next_pool_id; let msg = VaultExecuteMsg::CreatePoolInstance { pool_type: PoolType::StableSwap {}, asset_infos: asset_infos.to_vec(), native_asset_precisions: vec![NativeAssetPrecisionInfo { denom: denom0.clone(), precision: 6u8, }, NativeAssetPrecisionInfo { denom: denom1.clone(), precision: 6u8, }], init_params: Some(to_json_binary(&dexter_stable_pool::state::StablePoolParams { amp: 10u64, scaling_factors: vec![], supports_scaling_factors_update: false, scaling_factor_manager: None, }).unwrap()), fee_info: None, }; app.execute_contract(Addr::unchecked(owner), vault_instance.clone(), &msg, &[]) .unwrap(); let pool_info_res: PoolInfoResponse = app .wrap() .query_wasm_smart( vault_instance.clone(), &VaultQueryMsg::GetPoolById { pool_id: next_pool_id, }, ) .unwrap(); let pool_addr = pool_info_res.pool_addr; let lp_token_addr = pool_info_res.lp_token_addr; let pool_id = pool_info_res.pool_id; return (pool_addr, lp_token_addr, pool_id); } /// Initialize a WEIGHTED POOL /// -------------------------- fn initialize_weighted_pool( app: &mut App, owner: &Addr, vault_instance: Addr, token_instance0: Addr, token_instance1: Addr, token_instance2: Addr, denom0: String, denom1: String, ) -> (Addr, Addr, Uint128) { let asset_infos = vec![ AssetInfo::NativeToken { denom: denom0.clone(), }, AssetInfo::NativeToken { denom: denom1.clone(), }, AssetInfo::Token { contract_addr: token_instance1.clone(), }, AssetInfo::Token { contract_addr: token_instance0.clone(), }, AssetInfo::Token { contract_addr: token_instance2.clone(), }, ]; let asset_infos_with_weights = vec![ Asset { info: AssetInfo::NativeToken { denom: denom0.clone() }, amount: Uint128::from(20u128), }, Asset { info: AssetInfo::NativeToken { denom: denom1.clone() }, amount: Uint128::from(20u128), }, Asset { info: AssetInfo::Token { contract_addr: token_instance0.clone(), }, amount: Uint128::from(20u128), }, Asset { info: AssetInfo::Token { contract_addr: token_instance1.clone(), }, amount: Uint128::from(20u128), }, Asset { info: AssetInfo::Token { contract_addr: token_instance2.clone(), }, amount: Uint128::from(20u128), }, ]; // Initialize Stable-5-Pool contract instance // ------------------------------------------ let vault_config_res: VaultConfigResponse = app .wrap() .query_wasm_smart(vault_instance.clone(), &VaultQueryMsg::Config {}) .unwrap(); let next_pool_id = vault_config_res.next_pool_id; let msg = VaultExecuteMsg::CreatePoolInstance { pool_type: PoolType::Weighted {}, asset_infos: asset_infos.to_vec(), native_asset_precisions: vec![ NativeAssetPrecisionInfo { denom: denom0.clone(), precision: 6u8, }, NativeAssetPrecisionInfo { denom: denom1.clone(), precision: 6u8, } ], init_params: Some( to_json_binary(&dexter_weighted_pool::state::WeightedParams { weights: asset_infos_with_weights, exit_fee: Some(Decimal::from_ratio(1u128, 100u128)), }) .unwrap(), ), fee_info: None, }; app.execute_contract(Addr::unchecked(owner), vault_instance.clone(), &msg, &[]) .unwrap(); let pool_info_res: PoolInfoResponse = app .wrap() .query_wasm_smart( vault_instance.clone(), &VaultQueryMsg::GetPoolById { pool_id: next_pool_id, }, ) .unwrap(); let pool_addr = pool_info_res.pool_addr; let lp_token_addr = pool_info_res.lp_token_addr; let pool_id = pool_info_res.pool_id; return (pool_addr, lp_token_addr, pool_id); } /// Initialize the Router contract /// -------------------------- fn initialize_router(app: &mut App, owner: &Addr, vault_instance: Addr) -> Addr { let router_code_id = store_router_code(app); let router_instance = app .instantiate_contract( router_code_id, Addr::unchecked(owner), &InstantiateMsg { dexter_vault: vault_instance.clone().to_string(), }, &[], "Router", None, ) .unwrap(); return router_instance; } #[test] fn proper_initialization() { let owner = Addr::unchecked("owner"); let mut app = mock_app(Addr::unchecked(owner.clone()), vec![]); let vault_code_id = store_vault_code(&mut app); let weighted_pool_code_id = store_weighted_pool_code(&mut app); let stable5_pool_code_id = store_stable5_pool_code(&mut app); let token_code_id = store_token_code(&mut app); let router_code_id = store_router_code(&mut app); let pool_configs = vec![ PoolTypeConfig { code_id: stable5_pool_code_id, pool_type: PoolType::StableSwap {}, default_fee_info: FeeInfo { total_fee_bps: 300u16, protocol_fee_percent: 49u16, }, allow_instantiation: dexter::vault::AllowPoolInstantiation::Everyone, paused: PauseInfo::default(), }, PoolTypeConfig { code_id: weighted_pool_code_id, pool_type: PoolType::Weighted {}, default_fee_info: FeeInfo { total_fee_bps: 300u16, protocol_fee_percent: 49u16, }, allow_instantiation: dexter::vault::AllowPoolInstantiation::Everyone, paused: PauseInfo::default(), }, ]; //// -----x----- Success :: Initialize Vault & Router Contracts -----x----- //// // Vault contract instance let vault_init_msg = VaultInstantiateMsg { pool_configs: pool_configs.clone(), lp_token_code_id: Some(token_code_id), fee_collector: Some("fee_collector".to_string()), owner: owner.to_string(), pool_creation_fee: PoolCreationFee::default(), auto_stake_impl: dexter::vault::AutoStakeImpl::None, }; let vault_instance = app .instantiate_contract( vault_code_id, Addr::unchecked(owner.clone()), &vault_init_msg, &[], "vault", None, ) .unwrap(); let msg = VaultQueryMsg::Config {}; let config_res: VaultConfigResponse = app.wrap().query_wasm_smart(&vault_instance, &msg).unwrap(); assert_eq!(owner, config_res.owner); assert_eq!(token_code_id, config_res.lp_token_code_id.unwrap()); assert_eq!( Some(Addr::unchecked("fee_collector".to_string())), config_res.fee_collector ); assert_eq!(AutoStakeImpl::None, config_res.auto_stake_impl); // Router contract instance let router_init_msg = InstantiateMsg { dexter_vault: vault_instance.clone().to_string(), }; let router_instance = app .instantiate_contract( router_code_id, Addr::unchecked(owner.clone()), &router_init_msg, &[], "vault", None, ) .unwrap(); let msg = QueryMsg::Config {}; let r_config_res: ConfigResponse = app.wrap().query_wasm_smart(&router_instance, &msg).unwrap(); assert_eq!( vault_instance.clone().to_string(), r_config_res.dexter_vault ); } #[test] fn test_router_functionality() { let owner = Addr::unchecked("owner".to_string()); let denom0 = "token0".to_string(); let denom1 = "token1".to_string(); let mut app = mock_app( owner.clone(), vec![ Coin { denom: denom0.clone(), amount: Uint128::new(100000000_000_000_000u128), }, Coin { denom: denom1.clone(), amount: Uint128::new(100000000_000_000_000u128), }, ], ); let vault_instance = instantiate_contract(&mut app, &owner.clone()); let router_instance = initialize_router(&mut app, &owner.clone(), vault_instance.clone()); let (token_instance1, token_instance2, token_instance3) = initialize_3_tokens(&mut app, &owner.clone()); // Mint Tokens mint_some_tokens( &mut app, owner.clone(), token_instance1.clone(), Uint128::new(100000000_000000u128), owner.clone().to_string(), ); mint_some_tokens( &mut app, owner.clone(), token_instance2.clone(), Uint128::new(100000000_000000u128), owner.clone().to_string(), ); mint_some_tokens( &mut app, owner.clone(), token_instance3.clone(), Uint128::new(100000000_000000u128), owner.clone().to_string(), ); // Increase Allowances app.execute_contract( owner.clone(), token_instance1.clone(), &Cw20ExecuteMsg::IncreaseAllowance { spender: vault_instance.clone().to_string(), amount: Uint128::from(100000000_000000u128), expires: None, }, &[], ) .unwrap(); app.execute_contract( owner.clone(), token_instance2.clone(), &Cw20ExecuteMsg::IncreaseAllowance { spender: vault_instance.clone().to_string(), amount: Uint128::from(100000000_000000u128), expires: None, }, &[], ) .unwrap(); app.execute_contract( owner.clone(), token_instance3.clone(), &Cw20ExecuteMsg::IncreaseAllowance { spender: vault_instance.clone().to_string(), amount: Uint128::from(100000000_000000u128), expires: None, }, &[], ) .unwrap(); // Increase Allowances app.execute_contract( owner.clone(), token_instance1.clone(), &Cw20ExecuteMsg::IncreaseAllowance { spender: router_instance.clone().to_string(), amount: Uint128::from(100000000_000000u128), expires: None, }, &[], ) .unwrap(); app.execute_contract( owner.clone(), token_instance2.clone(), &Cw20ExecuteMsg::IncreaseAllowance { spender: router_instance.clone().to_string(), amount: Uint128::from(100000000_000000u128), expires: None, }, &[], ) .unwrap(); app.execute_contract( owner.clone(), token_instance3.clone(), &Cw20ExecuteMsg::IncreaseAllowance { spender: router_instance.clone().to_string(), amount: Uint128::from(100000000_000000u128), expires: None, }, &[], ) .unwrap(); // Create STABLE-5-POOL pool let (_, _, stable5_pool_id) = initialize_stable_5_pool( &mut app, &Addr::unchecked(owner.clone()), vault_instance.clone(), token_instance1.clone(), token_instance2.clone(), token_instance3.clone(), denom0.clone(), denom1.clone(), ); // Create WEIGHTED pool let (_, _, weighted_pool_id) = initialize_weighted_pool( &mut app, &Addr::unchecked(owner.clone()), vault_instance.clone(), token_instance1.clone(), token_instance2.clone(), token_instance3.clone(), denom0.clone(), denom1.clone(), ); // -------x---------- STABLE-5-POOL -::- PROVIDE LIQUIDITY -------x---------- // -------x---------- -------x---------- -------x---------- -------x---------- let assets_msg = vec![ Asset { info: AssetInfo::NativeToken { denom: denom0.clone(), }, amount: Uint128::from(100000_000000u128), }, Asset { info: AssetInfo::NativeToken { denom: denom1.clone(), }, amount: Uint128::from(100000_000000u128), }, Asset { info: AssetInfo::Token { contract_addr: token_instance1.clone(), }, amount: Uint128::from(100000_000000u128), }, Asset { info: AssetInfo::Token { contract_addr: token_instance2.clone(), }, amount: Uint128::from(100000_000000u128), }, Asset { info: AssetInfo::Token { contract_addr: token_instance3.clone(), }, amount: Uint128::from(100000_000000u128), }, ]; // Provide liquidity to empty stable 5 pool. No fee is charged app.execute_contract( owner.clone(), vault_instance.clone(), &VaultExecuteMsg::JoinPool { pool_id: Uint128::from(stable5_pool_id), recipient: None, min_lp_to_receive: None, auto_stake: None, assets: Some(assets_msg.clone()), }, &[ Coin { denom: denom0.clone(), amount: Uint128::new(100000_000000u128), }, Coin { denom: denom1.clone(), amount: Uint128::new(100000_000000u128), }, ], ) .unwrap(); // -------x---------- WEIGHTED-POOL -::- PROVIDE LIQUIDITY -------x---------- // -------x---------- -------x---------- -------x---------- -------x---------- let assets_msg = vec![ Asset { info: AssetInfo::NativeToken { denom: denom0.clone(), }, amount: Uint128::from(100000_000000u128), }, Asset { info: AssetInfo::NativeToken { denom: denom1.clone(), }, amount: Uint128::from(100000_000000u128), }, Asset { info: AssetInfo::Token { contract_addr: token_instance1.clone(), }, amount: Uint128::from(100000_000000u128), }, Asset { info: AssetInfo::Token { contract_addr: token_instance2.clone(), }, amount: Uint128::from(100000_000000u128), }, Asset { info: AssetInfo::Token { contract_addr: token_instance3.clone(), }, amount: Uint128::from(100000_000000u128), }, ]; app.execute_contract( owner.clone(), vault_instance.clone(), &VaultExecuteMsg::JoinPool { pool_id: Uint128::from(weighted_pool_id), recipient: None, min_lp_to_receive: None, auto_stake: None, assets: Some(assets_msg.clone()), }, &[ Coin { denom: denom0.clone(), amount: Uint128::new(100000_000000u128), }, Coin { denom: denom1.clone(), amount: Uint128::new(100000_000000u128), }, ], ) .unwrap(); // -------x---------- DEXTER ROUTER -::- Test swap simulations -------x------- // -------x---------- -------x---------- -------x---------- -------x---------- // SWAP TYPE -::- GIVE IN {} // Pool ID: Uint128(4) | asset_in: "token0" | asset_out: "contract2" | amount_provided "1000000" // Number of "contract2" tokens returned and are to be used for next swap: Uint128(969991) // Pool ID: Uint128(2) | asset_in: "contract2" | asset_out: "contract3" | amount_provided "969991" // Number of "contract3" tokens returned and are to be used for next swap: Uint128(940882) // Pool ID: Uint128(1) | asset_in: "contract3" | asset_out: "token0" | amount_provided "940882" // Number of "token0" tokens returned and are to be used for next swap: Uint128(912656) // Pool ID: Uint128(3) | asset_in: "token0" | asset_out: "contract2" | amount_provided "912656" // Number of "contract2" tokens returned and are to be used for next swap: Uint128(885277) // multihop_sim_response: SimulateMultiHopResponse { // swap_operations: [SimulatedTrade { pool_id: Uint128(4), asset_in: NativeToken { denom: "token0" }, offered_amount: Uint128(1000000), asset_out: Token { contract_addr: Addr("contract2") }, received_amount: Uint128(969991) }, // SimulatedTrade { pool_id: Uint128(2), asset_in: Token { contract_addr: Addr("contract2") }, offered_amount: Uint128(969991), asset_out: Token { contract_addr: Addr("contract3") }, received_amount: Uint128(940882) }, // SimulatedTrade { pool_id: Uint128(1), asset_in: Token { contract_addr: Addr("contract3") }, offered_amount: Uint128(940882), asset_out: NativeToken { denom: "token0" }, received_amount: Uint128(912656) }, // SimulatedTrade { pool_id: Uint128(3), asset_in: NativeToken { denom: "token0" }, offered_amount: Uint128(912656), asset_out: Token { contract_addr: Addr("contract2") }, received_amount: Uint128(885277) }] // , response: Success } let multiswap_request_msg: Vec<HopSwapRequest> = [ HopSwapRequest { pool_id: Uint128::from(stable5_pool_id), asset_in: AssetInfo::NativeToken { denom: denom0.clone(), }, asset_out: AssetInfo::Token { contract_addr: token_instance1.clone(), } }, HopSwapRequest { pool_id: Uint128::from(weighted_pool_id), asset_in: AssetInfo::Token { contract_addr: token_instance1.clone(), }, asset_out: AssetInfo::Token { contract_addr: token_instance2.clone(), } }, HopSwapRequest { pool_id: Uint128::from(stable5_pool_id), asset_in: AssetInfo::Token { contract_addr: token_instance2.clone(), }, asset_out: AssetInfo::NativeToken { denom: denom0.clone(), } }, HopSwapRequest { pool_id: Uint128::from(weighted_pool_id), asset_in: AssetInfo::NativeToken { denom: denom0.clone(), }, asset_out: AssetInfo::Token { contract_addr: token_instance1.clone(), } }, ] .to_vec(); let multihop_sim_response: SimulateMultiHopResponse = app .wrap() .query_wasm_smart( &router_instance.clone(), &dexter::router::QueryMsg::SimulateMultihopSwap { multiswap_request: multiswap_request_msg.clone(), swap_type: SwapType::GiveIn {}, amount: Uint128::from(1000000u128), }, ) .unwrap(); assert_eq!( dexter::pool::ResponseType::Success {}, multihop_sim_response.response ); assert_eq!( vec![ SimulatedTrade { pool_id: Uint128::from(1u128), asset_in: AssetInfo::NativeToken { denom: denom0.clone() }, offered_amount: Uint128::from(1000000u128), asset_out: AssetInfo::Token { contract_addr: token_instance1.clone() }, received_amount: Uint128::from(970000u128) }, SimulatedTrade { pool_id: Uint128::from(2u128), asset_in: AssetInfo::Token { contract_addr: token_instance1.clone() }, offered_amount: Uint128::from(970000u128), asset_out: AssetInfo::Token { contract_addr: token_instance2.clone() }, received_amount: Uint128::from(940891u128) }, SimulatedTrade { pool_id: Uint128::from(1u128), asset_in: AssetInfo::Token { contract_addr: token_instance2.clone() }, offered_amount: Uint128::from(940891u128), asset_out: AssetInfo::NativeToken { denom: denom0.clone() }, received_amount: Uint128::from(912665u128) }, SimulatedTrade { pool_id: Uint128::from(2u128), asset_in: AssetInfo::NativeToken { denom: denom0.clone() }, offered_amount: Uint128::from(912665u128), asset_out: AssetInfo::Token { contract_addr: token_instance1.clone() }, received_amount: Uint128::from(885278u128) } ], multihop_sim_response.swap_operations ); assert_eq!( vec![ Asset { info: AssetInfo::NativeToken { denom: denom0.clone() }, amount: Uint128::from(30000u128) }, Asset { info: AssetInfo::Token { contract_addr: token_instance1.clone() }, amount: Uint128::from(29100u128) }, Asset { info: AssetInfo::Token { contract_addr: token_instance2.clone() }, amount: Uint128::from(28226u128) }, Asset { info: AssetInfo::NativeToken { denom: denom0.clone() }, amount: Uint128::from(27379u128) } ], multihop_sim_response.fee ); // SWAP TYPE -::- GIVE OUT {} // Pool ID: Uint128(3) | asset_in: "token0" | asset_out: "contract2" | amount_to_receive "885277" // asset_in: "token0" offered_amount: Uint128(912656) || asset_out: "contract2" received_amount: Uint128(885277) // Number of "token0" tokens to be provided for this swap and should be returned by previous swap: Uint128(912656) // Pool ID: Uint128(1) | asset_in: "contract3" | asset_out: "token0" | amount_to_receive "912656" // asset_in: "contract3" offered_amount: Uint128(940882) || asset_out: "token0" received_amount: Uint128(912656) // Number of "contract3" tokens to be provided for this swap and should be returned by previous swap: Uint128(940882) // Pool ID: Uint128(2) | asset_in: "contract2" | asset_out: "contract3" | amount_to_receive "940882" // asset_in: "contract2" offered_amount: Uint128(969990) || asset_out: "contract3" received_amount: Uint128(940882) // Number of "contract2" tokens to be provided for this swap and should be returned by previous swap: Uint128(969990) // Pool ID: Uint128(4) | asset_in: "token0" | asset_out: "contract2" | amount_to_receive "969990" // asset_in: "token0" offered_amount: Uint128(999998) || asset_out: "contract2" received_amount: Uint128(969990) // Number of "token0" tokens to be provided for this swap and should be returned by previous swap: Uint128(999998) // multihop_sim_response: SimulateMultiHopResponse { swap_operations: [ SimulatedTrade { pool_id: Uint128(4), asset_in: NativeToken { denom: "token0" }, offered_amount: Uint128(999998), asset_out: Token { contract_addr: Addr("contract2") }, received_amount: Uint128(969990) }, // SimulatedTrade { pool_id: Uint128(2), asset_in: Token { contract_addr: Addr("contract2") }, offered_amount: Uint128(969990), asset_out: Token { contract_addr: Addr("contract3") }, received_amount: Uint128(940882) }, // SimulatedTrade { pool_id: Uint128(1), asset_in: Token { contract_addr: Addr("contract3") }, offered_amount: Uint128(940882), asset_out: NativeToken { denom: "token0" }, received_amount: Uint128(912656) }, // SimulatedTrade { pool_id: Uint128(3), asset_in: NativeToken { denom: "token0" }, offered_amount: Uint128(912656), asset_out: Token { contract_addr: Addr("contract2") }, received_amount: Uint128(885277) }], // response: Success } let multihop_sim_response: SimulateMultiHopResponse = app .wrap() .query_wasm_smart( &router_instance.clone(), &dexter::router::QueryMsg::SimulateMultihopSwap { multiswap_request: multiswap_request_msg.clone(), swap_type: SwapType::GiveOut {}, amount: Uint128::from(885277u128), }, ) .unwrap(); assert_eq!( dexter::pool::ResponseType::Success {}, multihop_sim_response.response ); assert_eq!( vec![ SimulatedTrade { pool_id: Uint128::from(1u128), asset_in: AssetInfo::NativeToken { denom: denom0.clone() }, offered_amount: Uint128::from(999997u128), asset_out: AssetInfo::Token { contract_addr: token_instance1.clone() }, received_amount: Uint128::from(969997u128) }, SimulatedTrade { pool_id: Uint128::from(2u128), asset_in: AssetInfo::Token { contract_addr: token_instance1.clone() }, offered_amount: Uint128::from(969997u128), asset_out: AssetInfo::Token { contract_addr: token_instance2.clone() }, received_amount: Uint128::from(940890u128) }, SimulatedTrade { pool_id: Uint128::from(1u128), asset_in: AssetInfo::Token { contract_addr: token_instance2.clone() }, offered_amount: Uint128::from(940890u128), asset_out: AssetInfo::NativeToken { denom: denom0.clone() }, received_amount: Uint128::from(912663u128) }, SimulatedTrade { pool_id: Uint128::from(2u128), asset_in: AssetInfo::NativeToken { denom: denom0.clone() }, offered_amount: Uint128::from(912663u128), asset_out: AssetInfo::Token { contract_addr: token_instance1.clone() }, received_amount: Uint128::from(885277u128) } ], multihop_sim_response.swap_operations ); assert_eq!( vec![ Asset { info: AssetInfo::NativeToken { denom: denom0.clone() }, amount: Uint128::from(29999u128) }, Asset { info: AssetInfo::Token { contract_addr: token_instance1.clone() }, amount: Uint128::from(29099u128) }, Asset { info: AssetInfo::Token { contract_addr: token_instance2.clone() }, amount: Uint128::from(28226u128) }, Asset { info: AssetInfo::NativeToken { denom: denom0.clone() }, amount: Uint128::from(27379u128) } ], multihop_sim_response.fee ); // -------x---------- DEXTER ROUTER -::- Test Multi-Hop Function ----------x---------- // -------x---------- ---------x---------- -------------x---------- -------x---------- let current_block = app.block_info(); app.update_block(|b| { b.height += 10; b.time = Timestamp::from_seconds(current_block.time.seconds() + 90) }); let multiswap_request_msg: Vec<HopSwapRequest> = [ HopSwapRequest { pool_id: Uint128::from(stable5_pool_id), asset_in: AssetInfo::Token { contract_addr: token_instance1.clone(), }, asset_out: AssetInfo::NativeToken { denom: denom0.clone(), } }, HopSwapRequest { pool_id: Uint128::from(weighted_pool_id), asset_in: AssetInfo::NativeToken { denom: denom0.clone(), }, asset_out: AssetInfo::Token { contract_addr: token_instance2.clone(), } }, HopSwapRequest { pool_id: Uint128::from(stable5_pool_id), asset_in: AssetInfo::Token { contract_addr: token_instance2.clone(), }, asset_out: AssetInfo::Token { contract_addr: token_instance3.clone(), } }, ] .to_vec(); let cur_sender_offer_asset_balance: BalanceResponse = app .wrap() .query_wasm_smart( &token_instance1.clone(), &Cw20QueryMsg::Balance { address: owner.clone().to_string(), }, ) .unwrap(); let cur_sender_ask_asset_balance: BalanceResponse = app .wrap() .query_wasm_smart( &token_instance3.clone(), &Cw20QueryMsg::Balance { address: owner.clone().to_string(), }, ) .unwrap(); let cur_vault_offer_asset_balance: BalanceResponse = app .wrap() .query_wasm_smart( &token_instance1.clone(), &Cw20QueryMsg::Balance { address: vault_instance.clone().to_string(), }, ) .unwrap(); let cur_vault_ask_asset_balance: BalanceResponse = app .wrap() .query_wasm_smart( &token_instance3.clone(), &Cw20QueryMsg::Balance { address: vault_instance.clone().to_string(), }, ) .unwrap(); // Multi-Hop Swap // Offer token is not native. Transferring tokens to router from the user and providing allowance // First hop swap request: SingleSwapRequest { pool_id: Uint128(4), asset_in: Token { contract_addr: Addr("contract2") }, asset_out: NativeToken { denom: "token0" }, swap_type: GiveIn, amount: Uint128(885277), max_spread: None, belief_price: None } // Current ask balance (before swap): Uint128(0) // Current offer asset ("token0") balance (after swap): Uint128(858711) // Amount returned from the last hop swap: Uint128(858711) // Next hop swap request: SingleSwapRequest { pool_id: Uint128(2), asset_in: NativeToken { denom: "token0" }, asset_out: Token { contract_addr: Addr("contract3") }, swap_type: GiveIn, amount: Uint128(858711), max_spread: None, belief_price: None } // Current ask asset ("contract3") balance: Uint128(0) // Current offer asset ("contract3") balance (after swap): Uint128(832942) // Amount returned from the last hop swap: Uint128(832942) // Next hop swap request: SingleSwapRequest { pool_id: Uint128(1), asset_in: Token { contract_addr: Addr("contract3") }, asset_out: Token { contract_addr: Addr("contract4") }, swap_type: GiveIn, amount: Uint128(832942), max_spread: None, belief_price: None } // Current ask asset ("contract4") balance: Uint128(0) // Current offer asset ("contract4") balance (after swap): Uint128(807954) // Amount returned from the last hop swap: Uint128(807954) // Hop is over. Checking if minimum receive amount is met. Minimum receive amount: "0" Amount returned from the last hop swap: "807954" let multihop_swap_msg = ExecuteMsg::ExecuteMultihopSwap { requests: multiswap_request_msg, recipient: None, offer_amount: Uint128::from(885277u128), minimum_receive: None, }; app.execute_contract( owner.clone(), router_instance.clone(), &multihop_swap_msg, &[], ) .unwrap(); let new_sender_offer_asset_balance: BalanceResponse = app .wrap() .query_wasm_smart( &token_instance1.clone(), &Cw20QueryMsg::Balance { address: owner.clone().to_string(), }, ) .unwrap(); let new_sender_ask_asset_balance: BalanceResponse = app .wrap() .query_wasm_smart( &token_instance3.clone(), &Cw20QueryMsg::Balance { address: owner.clone().to_string(), }, ) .unwrap(); let new_vault_offer_asset_balance: BalanceResponse = app .wrap() .query_wasm_smart( &token_instance1.clone(), &Cw20QueryMsg::Balance { address: vault_instance.clone().to_string(), }, ) .unwrap(); let new_vault_ask_asset_balance: BalanceResponse = app .wrap() .query_wasm_smart( &token_instance3.clone(), &Cw20QueryMsg::Balance { address: vault_instance.clone().to_string(), }, ) .unwrap(); assert_eq!( Uint128::from(807963u128), new_sender_ask_asset_balance.balance - cur_sender_ask_asset_balance.balance ); // Fees are not deducted from ask asset balance assert_eq!( Uint128::from(807963u128), cur_vault_ask_asset_balance.balance - new_vault_ask_asset_balance.balance ); assert_eq!( Uint128::from(885277u128), cur_sender_offer_asset_balance.balance - new_sender_offer_asset_balance.balance ); assert_eq!( Uint128::from(868280u128), new_vault_offer_asset_balance.balance - cur_vault_offer_asset_balance.balance ); }