From 845f8de176181a8282b0f5eed233457c726ee2b5 Mon Sep 17 00:00:00 2001 From: Jeremy Liu <31809888+NotJeremyLiu@users.noreply.github.com> Date: Wed, 8 Nov 2023 10:13:32 -0700 Subject: [PATCH] fix bug in swap adapter contract and add tests - must switch info.sender after the sent_asset is created --- .../adapters/swap/astroport/src/contract.rs | 8 +- .../astroport/tests/test_execute_receive.rs | 271 ++++++++++++++++++ 2 files changed, 275 insertions(+), 4 deletions(-) create mode 100644 contracts/adapters/swap/astroport/tests/test_execute_receive.rs diff --git a/contracts/adapters/swap/astroport/src/contract.rs b/contracts/adapters/swap/astroport/src/contract.rs index 2383fc41..4d84d292 100644 --- a/contracts/adapters/swap/astroport/src/contract.rs +++ b/contracts/adapters/swap/astroport/src/contract.rs @@ -70,16 +70,16 @@ pub fn receive_cw20( mut info: MessageInfo, cw20_msg: Cw20ReceiveMsg, ) -> ContractResult { - // Set the sender to the originating address that triggered the cw20 send call - // This is later validated / enforced to be the entry point contract address - info.sender = deps.api.addr_validate(&cw20_msg.sender)?; - let sent_asset = Asset::Cw20(Cw20Coin { address: info.sender.to_string(), amount: cw20_msg.amount, }); sent_asset.validate(&deps, &env, &info)?; + // Set the sender to the originating address that triggered the cw20 send call + // This is later validated / enforced to be the entry point contract address + info.sender = deps.api.addr_validate(&cw20_msg.sender)?; + match from_binary(&cw20_msg.msg)? { Cw20HookMsg::Swap { operations } => execute_swap(deps, env, info, sent_asset, operations), } diff --git a/contracts/adapters/swap/astroport/tests/test_execute_receive.rs b/contracts/adapters/swap/astroport/tests/test_execute_receive.rs new file mode 100644 index 00000000..1e514a49 --- /dev/null +++ b/contracts/adapters/swap/astroport/tests/test_execute_receive.rs @@ -0,0 +1,271 @@ +use astroport::{ + asset::AssetInfo, + router::{ExecuteMsg as RouterExecuteMsg, SwapOperation as AstroportSwapOperation}, +}; +use core::panic; +use cosmwasm_std::{ + testing::{mock_dependencies, mock_env, mock_info}, + to_binary, Addr, Coin, ContractResult as SystemContractResult, QuerierResult, + ReplyOn::Never, + SubMsg, SystemResult, Uint128, WasmMsg, WasmQuery, +}; +use cw20::{BalanceResponse, Cw20Coin, Cw20ExecuteMsg, Cw20ReceiveMsg}; +use cw_utils::PaymentError::NonPayable; +use skip::{ + asset::Asset, + error::SkipError::Payment, + swap::{ExecuteMsg, SwapOperation}, +}; +use skip_api_swap_adapter_astroport::{ + error::{ContractError, ContractResult}, + state::{ENTRY_POINT_CONTRACT_ADDRESS, ROUTER_CONTRACT_ADDRESS}, +}; +use test_case::test_case; + +/* +Test Cases: + +Expect Success + - One Swap Operation - Cw20 In + - One Swap Operation - Cw20 In And Out + +Expect Error + - Coin sent with cw20 + + */ + +// Define test parameters +struct Params { + caller: String, + info_funds: Vec, + sent_asset: Asset, + swap_operations: Vec, + expected_messages: Vec, + expected_error: Option, +} + +// Test execute_swap +#[test_case( + Params { + caller: "entry_point".to_string(), + info_funds: vec![], + sent_asset: Asset::Cw20(Cw20Coin { + address: "neutron123".to_string(), + amount: Uint128::from(100u128) + }), + swap_operations: vec![ + SwapOperation { + pool: "pool_1".to_string(), + denom_in: "neutron123".to_string(), + denom_out: "ua".to_string(), + } + ], + expected_messages: vec![ + SubMsg { + id: 0, + msg: WasmMsg::Execute { + contract_addr: "neutron123".to_string(), + msg: to_binary(&Cw20ExecuteMsg::Send { + contract: "router_contract".to_string(), + amount: Uint128::from(100u128), + msg: to_binary(&RouterExecuteMsg::ExecuteSwapOperations { + operations: vec![ + AstroportSwapOperation::AstroSwap { + offer_asset_info: AssetInfo::Token { + contract_addr: Addr::unchecked("neutron123"), + }, + ask_asset_info: AssetInfo::NativeToken { + denom: "ua".to_string(), + }, + } + ], + minimum_receive: None, + to: None, + max_spread: None, + })?, + })?, + funds: vec![], + }.into(), + gas_limit: None, + reply_on: Never, + }, + SubMsg { + id: 0, + msg: WasmMsg::Execute { + contract_addr: "swap_contract_address".to_string(), + msg: to_binary(&ExecuteMsg::TransferFundsBack { + return_denom: "ua".to_string(), + swapper: Addr::unchecked("entry_point"), + })?, + funds: vec![], + } + .into(), + gas_limit: None, + reply_on: Never, + }, + ], + expected_error: None, + }; + "One Swap Operation - Cw20 In")] +#[test_case( + Params { + caller: "entry_point".to_string(), + info_funds: vec![], + sent_asset: Asset::Cw20(Cw20Coin { + address: "neutron123".to_string(), + amount: Uint128::from(100u128) + }), + swap_operations: vec![ + SwapOperation { + pool: "pool_1".to_string(), + denom_in: "neutron123".to_string(), + denom_out: "neutron987".to_string(), + } + ], + expected_messages: vec![ + SubMsg { + id: 0, + msg: WasmMsg::Execute { + contract_addr: "neutron123".to_string(), + msg: to_binary(&Cw20ExecuteMsg::Send { + contract: "router_contract".to_string(), + amount: Uint128::from(100u128), + msg: to_binary(&RouterExecuteMsg::ExecuteSwapOperations { + operations: vec![ + AstroportSwapOperation::AstroSwap { + offer_asset_info: AssetInfo::Token { + contract_addr: Addr::unchecked("neutron123"), + }, + ask_asset_info: AssetInfo::Token { + contract_addr: Addr::unchecked("neutron987"), + }, + } + ], + minimum_receive: None, + to: None, + max_spread: None, + })?, + })?, + funds: vec![], + }.into(), + gas_limit: None, + reply_on: Never, + }, + SubMsg { + id: 0, + msg: WasmMsg::Execute { + contract_addr: "swap_contract_address".to_string(), + msg: to_binary(&ExecuteMsg::TransferFundsBack { + return_denom: "neutron987".to_string(), + swapper: Addr::unchecked("entry_point"), + })?, + funds: vec![], + } + .into(), + gas_limit: None, + reply_on: Never, + }, + ], + expected_error: None, + }; + "One Swap Operation - Cw20 In And Out")] +#[test_case( + Params { + caller: "entry_point".to_string(), + info_funds: vec![ + Coin::new(100, "un"), + ], + sent_asset: Asset::Cw20(Cw20Coin { + address: "neutron123".to_string(), + amount: Uint128::from(100u128) + }), + swap_operations: vec![], + expected_messages: vec![], + expected_error: Some(ContractError::Skip(Payment(NonPayable{}))), + }; + "Coin sent with cw20 - Expect Error")] +fn test_execute_swap(params: Params) -> ContractResult<()> { + // Create mock dependencies + let mut deps = mock_dependencies(); + + // Create mock wasm handler to handle the swap adapter contract query + let wasm_handler = |query: &WasmQuery| -> QuerierResult { + match query { + WasmQuery::Smart { contract_addr, .. } => { + if contract_addr == "neutron123" { + SystemResult::Ok(SystemContractResult::Ok( + to_binary(&BalanceResponse { + balance: Uint128::from(100u128), + }) + .unwrap(), + )) + } else { + panic!("Unsupported contract: {:?}", query); + } + } + _ => panic!("Unsupported query: {:?}", query), + } + }; + + // Update querier with mock wasm handler + deps.querier.update_wasm(wasm_handler); + + // Create mock env + let mut env = mock_env(); + env.contract.address = Addr::unchecked("swap_contract_address"); + + // Convert info funds vector into a slice of Coin objects + let info_funds: &[Coin] = ¶ms.info_funds; + + // Create mock info with entry point contract address + let info = mock_info(params.sent_asset.denom(), info_funds); + + // Store the entry point contract address + ENTRY_POINT_CONTRACT_ADDRESS.save(deps.as_mut().storage, &Addr::unchecked("entry_point"))?; + + // Store the router contract address + ROUTER_CONTRACT_ADDRESS.save(deps.as_mut().storage, &Addr::unchecked("router_contract"))?; + + // Call execute_swap with the given test parameters + let res = skip_api_swap_adapter_astroport::contract::execute( + deps.as_mut(), + env, + info, + ExecuteMsg::Receive(Cw20ReceiveMsg { + sender: params.caller, + amount: params.sent_asset.amount(), + msg: to_binary(&ExecuteMsg::Swap { + operations: params.swap_operations, + }) + .unwrap(), + }), + ); + + // Assert the behavior is correct + match res { + Ok(res) => { + // Assert the test did not expect an error + assert!( + params.expected_error.is_none(), + "expected test to error with {:?}, but it succeeded", + params.expected_error + ); + + // Assert the messages are correct + assert_eq!(res.messages, params.expected_messages); + } + Err(err) => { + // Assert the test expected an error + assert!( + params.expected_error.is_some(), + "expected test to succeed, but it errored with {:?}", + err + ); + + // Assert the error is correct + assert_eq!(err, params.expected_error.unwrap()); + } + } + + Ok(()) +}