From 4ed55c62387b1c2c9a29e799f670e92796680b46 Mon Sep 17 00:00:00 2001 From: Art3mix Date: Tue, 7 Mar 2023 15:07:59 +0200 Subject: [PATCH 01/11] add send msgs --- .../external/cw-token-swap/src/contract.rs | 9 +- contracts/external/cw-token-swap/src/lib.rs | 1 + contracts/external/cw-token-swap/src/msg.rs | 25 +-- contracts/external/cw-token-swap/src/state.rs | 140 +------------- contracts/external/cw-token-swap/src/tests.rs | 121 +++++++++++- contracts/external/cw-token-swap/src/types.rs | 175 ++++++++++++++++++ 6 files changed, 300 insertions(+), 171 deletions(-) create mode 100644 contracts/external/cw-token-swap/src/types.rs diff --git a/contracts/external/cw-token-swap/src/contract.rs b/contracts/external/cw-token-swap/src/contract.rs index 283ea5ac0..5c80d7ed9 100644 --- a/contracts/external/cw-token-swap/src/contract.rs +++ b/contracts/external/cw-token-swap/src/contract.rs @@ -10,7 +10,8 @@ use cw_utils::must_pay; use crate::{ error::ContractError, msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, StatusResponse}, - state::{CheckedCounterparty, CheckedTokenInfo, COUNTERPARTY_ONE, COUNTERPARTY_TWO}, + state::{COUNTERPARTY_ONE, COUNTERPARTY_TWO}, + types::{CheckedCounterparty, CheckedTokenInfo}, }; pub(crate) const CONTRACT_NAME: &str = "crates.io:cw-token-swap"; @@ -115,10 +116,10 @@ fn do_fund( vec![ counterparty .promise - .into_send_message(&other_counterparty.address)?, + .into_send_message(&other_counterparty.address, other_counterparty.send_msg)?, other_counterparty .promise - .into_send_message(&counterparty.address)?, + .into_send_message(&counterparty.address, counterparty.send_msg)?, ] } else { vec![] @@ -217,7 +218,7 @@ pub fn execute_withdraw(deps: DepsMut, info: MessageInfo) -> Result = Item::new("counterparty_one"); pub const COUNTERPARTY_TWO: Item = Item::new("counterparty_two"); - -impl Counterparty { - pub fn into_checked(self, deps: Deps) -> Result { - Ok(CheckedCounterparty { - address: deps.api.addr_validate(&self.address)?, - provided: false, - promise: self.promise.into_checked(deps)?, - }) - } -} - -impl TokenInfo { - pub fn into_checked(self, deps: Deps) -> Result { - match self { - TokenInfo::Native { denom, amount } => { - if amount.is_zero() { - Err(ContractError::ZeroTokens {}) - } else { - Ok(CheckedTokenInfo::Native { denom, amount }) - } - } - TokenInfo::Cw20 { - contract_addr, - amount, - } => { - if amount.is_zero() { - Err(ContractError::ZeroTokens {}) - } else { - let contract_addr = deps.api.addr_validate(&contract_addr)?; - // Make sure we are dealing with a cw20. - let _: cw20::TokenInfoResponse = deps.querier.query_wasm_smart( - contract_addr.clone(), - &cw20::Cw20QueryMsg::TokenInfo {}, - )?; - Ok(CheckedTokenInfo::Cw20 { - contract_addr, - amount, - }) - } - } - } - } -} - -impl CheckedTokenInfo { - pub fn into_send_message(self, recipient: &Addr) -> Result { - Ok(match self { - Self::Native { denom, amount } => BankMsg::Send { - to_address: recipient.to_string(), - amount: vec![Coin { denom, amount }], - } - .into(), - Self::Cw20 { - contract_addr, - amount, - } => WasmMsg::Execute { - contract_addr: contract_addr.into_string(), - msg: to_binary(&cw20::Cw20ExecuteMsg::Transfer { - recipient: recipient.to_string(), - amount, - })?, - funds: vec![], - } - .into(), - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_into_spend_message_native() { - let info = CheckedTokenInfo::Native { - amount: Uint128::new(100), - denom: "uekez".to_string(), - }; - let message = info.into_send_message(&Addr::unchecked("ekez")).unwrap(); - - assert_eq!( - message, - CosmosMsg::Bank(BankMsg::Send { - to_address: "ekez".to_string(), - amount: vec![Coin { - amount: Uint128::new(100), - denom: "uekez".to_string() - }] - }) - ); - } - - #[test] - fn test_into_spend_message_cw20() { - let info = CheckedTokenInfo::Cw20 { - amount: Uint128::new(100), - contract_addr: Addr::unchecked("ekez_token"), - }; - let message = info.into_send_message(&Addr::unchecked("ekez")).unwrap(); - - assert_eq!( - message, - CosmosMsg::Wasm(WasmMsg::Execute { - funds: vec![], - contract_addr: "ekez_token".to_string(), - msg: to_binary(&cw20::Cw20ExecuteMsg::Transfer { - recipient: "ekez".to_string(), - amount: Uint128::new(100) - }) - .unwrap() - }) - ); - } -} diff --git a/contracts/external/cw-token-swap/src/tests.rs b/contracts/external/cw-token-swap/src/tests.rs index 7e6073f20..48eb7b895 100644 --- a/contracts/external/cw-token-swap/src/tests.rs +++ b/contracts/external/cw-token-swap/src/tests.rs @@ -7,10 +7,8 @@ use cw_multi_test::{App, BankSudo, Contract, ContractWrapper, Executor, SudoMsg} use crate::{ contract::{migrate, CONTRACT_NAME, CONTRACT_VERSION}, - msg::{ - Counterparty, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, StatusResponse, TokenInfo, - }, - state::{CheckedCounterparty, CheckedTokenInfo}, + msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, StatusResponse}, + types::{CheckedCounterparty, CheckedTokenInfo, Counterparty, TokenInfo}, ContractError, }; @@ -74,6 +72,7 @@ fn test_simple_escrow() { denom: "ujuno".to_string(), amount: Uint128::new(100), }, + send_msg: None, }, counterparty_two: Counterparty { address: DAO2.to_string(), @@ -81,6 +80,7 @@ fn test_simple_escrow() { contract_addr: cw20.to_string(), amount: Uint128::new(100), }, + send_msg: None, }, }, &[], @@ -136,6 +136,95 @@ fn test_simple_escrow() { assert_eq!(dao2_balance.amount, Uint128::new(100)) } +#[test] +fn test_simple_with_send_messages() { + let mut app = App::default(); + + let cw20_code = app.store_code(cw20_contract()); + let escrow_code = app.store_code(escrow_contract()); + + let cw20 = app + .instantiate_contract( + cw20_code, + Addr::unchecked(DAO2), + &cw20_base::msg::InstantiateMsg { + name: "coin coin".to_string(), + symbol: "coin".to_string(), + decimals: 6, + initial_balances: vec![Cw20Coin { + address: DAO2.to_string(), + amount: Uint128::new(100), + }], + mint: None, + marketing: None, + }, + &[], + "coin", + None, + ) + .unwrap(); + + let escrow = app + .instantiate_contract( + escrow_code, + Addr::unchecked(DAO1), + &InstantiateMsg { + counterparty_one: Counterparty { + address: DAO1.to_string(), + promise: TokenInfo::Native { + denom: "ujuno".to_string(), + amount: Uint128::new(100), + }, + send_msg: None, + }, + counterparty_two: Counterparty { + address: DAO2.to_string(), + promise: TokenInfo::Cw20 { + contract_addr: cw20.to_string(), + amount: Uint128::new(100), + }, + send_msg: None, + }, + }, + &[], + "escrow", + None, + ) + .unwrap(); + + app.execute_contract( + Addr::unchecked(DAO2), + cw20.clone(), + &cw20::Cw20ExecuteMsg::Send { + contract: escrow.to_string(), + amount: Uint128::new(100), + msg: to_binary("").unwrap(), + }, + &[], + ) + .unwrap(); + + app.sudo(SudoMsg::Bank(BankSudo::Mint { + to_address: DAO1.to_string(), + amount: vec![Coin { + amount: Uint128::new(100), + denom: "ujuno".to_string(), + }], + })) + .unwrap(); + + app.execute_contract( + Addr::unchecked(DAO1), + escrow, + &ExecuteMsg::Fund {}, + &[Coin { + amount: Uint128::new(100), + denom: "ujuno".to_string(), + }], + ) + .unwrap(); +} + #[test] fn test_withdraw() { let mut app = App::default(); @@ -175,6 +264,7 @@ fn test_withdraw() { denom: "ujuno".to_string(), amount: Uint128::new(100), }, + send_msg: None, }, counterparty_two: Counterparty { address: DAO2.to_string(), @@ -182,6 +272,7 @@ fn test_withdraw() { contract_addr: cw20.to_string(), amount: Uint128::new(100), }, + send_msg: None, }, }, &[], @@ -269,6 +360,7 @@ fn test_withdraw() { amount: Uint128::new(100) }, provided: true, + send_msg: None, }, counterparty_two: CheckedCounterparty { address: Addr::unchecked(DAO2), @@ -277,6 +369,7 @@ fn test_withdraw() { amount: Uint128::new(100) }, provided: false, + send_msg: None, } } ); @@ -307,6 +400,7 @@ fn test_withdraw() { amount: Uint128::new(100) }, provided: false, + send_msg: None, }, counterparty_two: CheckedCounterparty { address: Addr::unchecked(DAO2), @@ -315,6 +409,7 @@ fn test_withdraw() { amount: Uint128::new(100) }, provided: false, + send_msg: None, } } ) @@ -359,6 +454,7 @@ fn test_withdraw_post_completion() { denom: "ujuno".to_string(), amount: Uint128::new(100), }, + send_msg: None, }, counterparty_two: Counterparty { address: DAO2.to_string(), @@ -366,6 +462,7 @@ fn test_withdraw_post_completion() { contract_addr: cw20.to_string(), amount: Uint128::new(100), }, + send_msg: None, }, }, &[], @@ -468,6 +565,7 @@ fn test_invalid_instantiate() { denom: "ujuno".to_string(), amount: Uint128::new(0), }, + send_msg: None, }, counterparty_two: Counterparty { address: DAO2.to_string(), @@ -475,6 +573,7 @@ fn test_invalid_instantiate() { contract_addr: cw20.to_string(), amount: Uint128::new(100), }, + send_msg: None, }, }, &[], @@ -499,6 +598,7 @@ fn test_invalid_instantiate() { denom: "ujuno".to_string(), amount: Uint128::new(100), }, + send_msg: None, }, counterparty_two: Counterparty { address: DAO2.to_string(), @@ -506,6 +606,7 @@ fn test_invalid_instantiate() { contract_addr: cw20.to_string(), amount: Uint128::new(0), }, + send_msg: None, }, }, &[], @@ -537,6 +638,7 @@ fn test_non_distincy_counterparties() { denom: "ujuno".to_string(), amount: Uint128::new(110), }, + send_msg: None, }, counterparty_two: Counterparty { address: DAO1.to_string(), @@ -544,6 +646,7 @@ fn test_non_distincy_counterparties() { denom: "ujuno".to_string(), amount: Uint128::new(10), }, + send_msg: None, }, }, &[], @@ -596,6 +699,7 @@ fn test_fund_non_counterparty() { denom: "ujuno".to_string(), amount: Uint128::new(100), }, + send_msg: None, }, counterparty_two: Counterparty { address: DAO2.to_string(), @@ -603,6 +707,7 @@ fn test_fund_non_counterparty() { contract_addr: cw20.to_string(), amount: Uint128::new(100), }, + send_msg: None, }, }, &[], @@ -693,6 +798,7 @@ fn test_fund_twice() { denom: "ujuno".to_string(), amount: Uint128::new(100), }, + send_msg: None, }, counterparty_two: Counterparty { address: DAO2.to_string(), @@ -700,6 +806,7 @@ fn test_fund_twice() { contract_addr: cw20.to_string(), amount: Uint128::new(100), }, + send_msg: None, }, }, &[], @@ -813,6 +920,7 @@ fn test_fund_invalid_amount() { denom: "ujuno".to_string(), amount: Uint128::new(100), }, + send_msg: None, }, counterparty_two: Counterparty { address: DAO2.to_string(), @@ -820,6 +928,7 @@ fn test_fund_invalid_amount() { contract_addr: cw20.to_string(), amount: Uint128::new(100), }, + send_msg: None, }, }, &[], @@ -896,6 +1005,7 @@ fn test_fund_invalid_denom() { denom: "ujuno".to_string(), amount: Uint128::new(100), }, + send_msg: None, }, counterparty_two: Counterparty { address: DAO2.to_string(), @@ -903,6 +1013,7 @@ fn test_fund_invalid_denom() { denom: "uekez".to_string(), amount: Uint128::new(100), }, + send_msg: None, }, }, &[], @@ -999,6 +1110,7 @@ fn test_fund_invalid_cw20() { denom: "ujuno".to_string(), amount: Uint128::new(100), }, + send_msg: None, }, counterparty_two: Counterparty { address: DAO2.to_string(), @@ -1006,6 +1118,7 @@ fn test_fund_invalid_cw20() { contract_addr: cw20.to_string(), amount: Uint128::new(100), }, + send_msg: None, }, }, &[], diff --git a/contracts/external/cw-token-swap/src/types.rs b/contracts/external/cw-token-swap/src/types.rs new file mode 100644 index 000000000..49581d741 --- /dev/null +++ b/contracts/external/cw-token-swap/src/types.rs @@ -0,0 +1,175 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{ + to_binary, Addr, BankMsg, Binary, Coin, CosmosMsg, Deps, StdError, Uint128, WasmMsg, +}; + +use crate::ContractError; + +#[cw_serde] +pub struct SendMessage { + pub address: String, + pub message: Binary, +} + +impl SendMessage { + pub fn into_checked(self, deps: Deps) -> Result { + Ok(CheckedSendMessage { + address: deps.api.addr_validate(&self.address)?, + message: self.message, + }) + } +} + +#[cw_serde] +pub struct CheckedSendMessage { + pub address: Addr, + pub message: Binary, +} + +/// Information about the token being used on one side of the escrow. +#[cw_serde] +pub enum TokenInfo { + /// A native token. + Native { denom: String, amount: Uint128 }, + /// A cw20 token. + Cw20 { + contract_addr: String, + amount: Uint128, + }, +} + +impl TokenInfo { + pub fn into_checked(self, deps: Deps) -> Result { + match self { + TokenInfo::Native { denom, amount } => { + if amount.is_zero() { + Err(ContractError::ZeroTokens {}) + } else { + Ok(CheckedTokenInfo::Native { denom, amount }) + } + } + TokenInfo::Cw20 { + contract_addr, + amount, + } => { + if amount.is_zero() { + Err(ContractError::ZeroTokens {}) + } else { + let contract_addr = deps.api.addr_validate(&contract_addr)?; + // Make sure we are dealing with a cw20. + let _: cw20::TokenInfoResponse = deps.querier.query_wasm_smart( + contract_addr.clone(), + &cw20::Cw20QueryMsg::TokenInfo {}, + )?; + Ok(CheckedTokenInfo::Cw20 { + contract_addr, + amount, + }) + } + } + } + } +} + +/// Information about a counterparty in this escrow transaction and +/// their promised funds. +#[cw_serde] +pub struct Counterparty { + /// The address of the counterparty. + pub address: String, + /// The funds they have promised to provide. + pub promise: TokenInfo, + /// The message to send once the escrow is complete. + pub send_msg: Option, +} + +impl Counterparty { + pub fn into_checked(self, deps: Deps) -> Result { + let send_msg = if let Some(send_msg) = self.send_msg { + Some(send_msg.into_checked(deps)?) + } else { + None + }; + + Ok(CheckedCounterparty { + address: deps.api.addr_validate(&self.address)?, + provided: false, + promise: self.promise.into_checked(deps)?, + send_msg, + }) + } +} + +#[cw_serde] +pub enum CheckedTokenInfo { + Native { + denom: String, + amount: Uint128, + }, + Cw20 { + contract_addr: Addr, + amount: Uint128, + }, +} + +#[cw_serde] +pub struct CheckedCounterparty { + pub address: Addr, + pub promise: CheckedTokenInfo, + pub provided: bool, + pub send_msg: Option, +} + +impl CheckedTokenInfo { + pub fn into_send_message( + self, + recipient: &Addr, + send_msg: Option, + ) -> Result { + Ok(match self { + Self::Native { denom, amount } => match send_msg { + Some(CheckedSendMessage { + address, + message: msg, + }) => WasmMsg::Execute { + contract_addr: address.to_string(), + msg, + funds: vec![Coin { denom, amount }], + } + .into(), + None => BankMsg::Send { + to_address: recipient.to_string(), + amount: vec![Coin { denom, amount }], + } + .into(), + }, + Self::Cw20 { + contract_addr, + amount, + } => match send_msg { + Some(CheckedSendMessage { + address, + message: msg, + }) => WasmMsg::Execute { + contract_addr: contract_addr.to_string(), + msg: to_binary(&cw20::Cw20ExecuteMsg::Send { + contract: address.to_string(), + amount, + msg, + })?, + funds: vec![], + } + .into(), + None => WasmMsg::Execute { + contract_addr: contract_addr.into_string(), + msg: to_binary(&cw20::Cw20ExecuteMsg::Transfer { + recipient: recipient.to_string(), + amount, + })?, + funds: vec![], + } + .into(), + }, + }) + } +} From c2b4f338314369e8ed40dbf8688fa21ac4048fc5 Mon Sep 17 00:00:00 2001 From: Art3mix Date: Wed, 8 Mar 2023 15:03:18 +0200 Subject: [PATCH 02/11] start better design --- Cargo.lock | 2 + contracts/external/cw-token-swap/Cargo.toml | 2 + .../external/cw-token-swap/src/contract.rs | 48 +-- contracts/external/cw-token-swap/src/error.rs | 7 + contracts/external/cw-token-swap/src/msg.rs | 3 +- contracts/external/cw-token-swap/src/tests.rs | 123 ++++++-- contracts/external/cw-token-swap/src/types.rs | 293 ++++++++++++++---- 7 files changed, 370 insertions(+), 108 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aed9346f8..eeed69d59 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -792,9 +792,11 @@ version = "2.0.3" dependencies = [ "cosmwasm-schema", "cosmwasm-std", + "cw-denom", "cw-multi-test", "cw-storage-plus 1.0.1 (git+https://github.com/DA0-DA0/cw-storage-plus.git)", "cw-utils 0.16.0", + "cw-vesting", "cw2 0.16.0", "cw20 0.16.0", "cw20-base 0.16.0", diff --git a/contracts/external/cw-token-swap/Cargo.toml b/contracts/external/cw-token-swap/Cargo.toml index 1e6903504..563dd4cc7 100644 --- a/contracts/external/cw-token-swap/Cargo.toml +++ b/contracts/external/cw-token-swap/Cargo.toml @@ -29,3 +29,5 @@ thiserror = { workspace = true } cosmwasm-schema = { workspace = true } cw-multi-test = { workspace = true } cw20-base = { workspace = true } +cw-vesting = { workspace = true } +cw-denom = { workspace = true } \ No newline at end of file diff --git a/contracts/external/cw-token-swap/src/contract.rs b/contracts/external/cw-token-swap/src/contract.rs index 5c80d7ed9..089e04f20 100644 --- a/contracts/external/cw-token-swap/src/contract.rs +++ b/contracts/external/cw-token-swap/src/contract.rs @@ -1,7 +1,8 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, Uint128, + to_binary, Addr, Binary, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Response, StdResult, + Uint128, }; use cw2::set_contract_version; use cw_storage_plus::Item; @@ -26,8 +27,8 @@ pub fn instantiate( ) -> Result { set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - let counterparty_one = msg.counterparty_one.into_checked(deps.as_ref())?; - let counterparty_two = msg.counterparty_two.into_checked(deps.as_ref())?; + let counterparty_one = msg.counterparty_one.into_checked(deps.as_ref(), None)?; + let counterparty_two = msg.counterparty_two.into_checked(deps.as_ref(), None)?; if counterparty_one.address == counterparty_two.address { return Err(ContractError::NonDistinctCounterparties {}); @@ -51,7 +52,7 @@ pub fn execute( ) -> Result { match msg { ExecuteMsg::Receive(msg) => execute_receive(deps, info.sender, msg), - ExecuteMsg::Fund {} => execute_fund(deps, info), + ExecuteMsg::Fund { send_message } => execute_fund(deps, info, send_message), ExecuteMsg::Withdraw {} => execute_withdraw(deps, info), } } @@ -113,14 +114,18 @@ fn do_fund( storage.save(deps.storage, &counterparty)?; let messages = if counterparty.provided && other_counterparty.provided { - vec![ - counterparty - .promise - .into_send_message(&other_counterparty.address, other_counterparty.send_msg)?, - other_counterparty - .promise - .into_send_message(&counterparty.address, counterparty.send_msg)?, - ] + let mut msgs = counterparty.promise.clone().into_send_message( + deps.as_ref(), + &other_counterparty, + counterparty.send_msg.clone(), + )?; + + msgs.append(&mut other_counterparty.promise.into_send_message( + deps.as_ref(), + &counterparty, + other_counterparty.send_msg, + )?); + msgs } else { vec![] }; @@ -169,13 +174,16 @@ pub fn execute_receive( ) } -pub fn execute_fund(deps: DepsMut, info: MessageInfo) -> Result { +pub fn execute_fund( + deps: DepsMut, + info: MessageInfo, + send_message: Option, +) -> Result { let CounterpartyResponse { - counterparty, + mut counterparty, other_counterparty, storage, } = get_counterparty(deps.as_ref(), &info.sender)?; - let (expected_payment, paid) = if let CheckedTokenInfo::Native { amount, denom } = &counterparty.promise { let paid = must_pay(&info, denom).map_err(|_| ContractError::InvalidFunds {})?; @@ -215,10 +223,12 @@ pub fn execute_withdraw(deps: DepsMut, info: MessageInfo) -> Result> }, /// Withdraws provided funds. Only allowed if the other /// counterparty has yet to provide their promised funds. Withdraw {}, diff --git a/contracts/external/cw-token-swap/src/tests.rs b/contracts/external/cw-token-swap/src/tests.rs index 48eb7b895..74d6e5409 100644 --- a/contracts/external/cw-token-swap/src/tests.rs +++ b/contracts/external/cw-token-swap/src/tests.rs @@ -1,6 +1,7 @@ use cosmwasm_std::{ + coins, testing::{mock_dependencies, mock_env}, - to_binary, Addr, Coin, Empty, Uint128, + to_binary, Addr, Coin, Decimal, Empty, Uint128, Validator, }; use cw20::Cw20Coin; use cw_multi_test::{App, BankSudo, Contract, ContractWrapper, Executor, SudoMsg}; @@ -33,6 +34,15 @@ fn cw20_contract() -> Box> { Box::new(contract) } +fn cw_vesting() -> Box> { + let contract = ContractWrapper::new( + cw_vesting::contract::execute, + cw_vesting::contract::instantiate, + cw_vesting::contract::query, + ); + Box::new(contract) +} + #[test] fn test_simple_escrow() { let mut app = App::default(); @@ -138,10 +148,35 @@ fn test_simple_escrow() { #[test] fn test_simple_with_send_messages() { - let mut app = App::default(); + let mut app = App::new(|router, api, storage| { + router + .bank + .init_balance( + storage, + &Addr::unchecked("owner"), + coins(100_000_000_000, "ujuno"), + ) + .unwrap(); + + router + .staking + .add_validator( + api, + storage, + &mock_env().block, + Validator { + address: "validator".to_string(), + commission: Decimal::zero(), // zero percent comission to keep math simple. + max_commission: Decimal::percent(10), + max_change_rate: Decimal::percent(2), + }, + ) + .unwrap(); + }); let cw20_code = app.store_code(cw20_contract()); let escrow_code = app.store_code(escrow_contract()); + let vesting_code = app.store_code(cw_vesting()); let cw20 = app .instantiate_contract( @@ -164,6 +199,28 @@ fn test_simple_with_send_messages() { ) .unwrap(); + let vetsing = app + .instantiate_contract( + vesting_code, + Addr::unchecked("owner"), + &cw_vesting::msg::InstantiateMsg { + owner: Some("owner".to_string()), + recipient: DAO2.to_string(), + title: "title".to_string(), + description: Some("description".to_string()), + total: Uint128::new(100_000_000), + denom: cw_denom::UncheckedDenom::Native("ujuno".to_string()), + schedule: cw_vesting::vesting::Schedule::SaturatingLinear, + start_time: None, + vesting_duration_seconds: 60 * 60 * 24 * 7, // one week + unbonding_duration_seconds: 60, + }, + &coins(100_000_000, "ujuno"), + "coin", + None, + ) + .unwrap(); + let escrow = app .instantiate_contract( escrow_code, @@ -192,37 +249,37 @@ fn test_simple_with_send_messages() { ) .unwrap(); - app.execute_contract( - Addr::unchecked(DAO2), - cw20.clone(), - &cw20::Cw20ExecuteMsg::Send { - contract: escrow.to_string(), - amount: Uint128::new(100), - msg: to_binary("").unwrap(), - }, - &[], - ) - .unwrap(); - - app.sudo(SudoMsg::Bank(BankSudo::Mint { - to_address: DAO1.to_string(), - amount: vec![Coin { - amount: Uint128::new(100), - denom: "ujuno".to_string(), - }], - })) - .unwrap(); - - app.execute_contract( - Addr::unchecked(DAO1), - escrow, - &ExecuteMsg::Fund {}, - &[Coin { - amount: Uint128::new(100), - denom: "ujuno".to_string(), - }], - ) - .unwrap(); + app.execute_contract( + Addr::unchecked(DAO2), + cw20.clone(), + &cw20::Cw20ExecuteMsg::Send { + contract: escrow.to_string(), + amount: Uint128::new(100), + msg: to_binary("").unwrap(), + }, + &[], + ) + .unwrap(); + + app.sudo(SudoMsg::Bank(BankSudo::Mint { + to_address: DAO1.to_string(), + amount: vec![Coin { + amount: Uint128::new(100), + denom: "ujuno".to_string(), + }], + })) + .unwrap(); + + app.execute_contract( + Addr::unchecked(DAO1), + escrow, + &ExecuteMsg::Fund {}, + &[Coin { + amount: Uint128::new(100), + denom: "ujuno".to_string(), + }], + ) + .unwrap(); } #[test] diff --git a/contracts/external/cw-token-swap/src/types.rs b/contracts/external/cw-token-swap/src/types.rs index 49581d741..e8d0ce57c 100644 --- a/contracts/external/cw-token-swap/src/types.rs +++ b/contracts/external/cw-token-swap/src/types.rs @@ -1,29 +1,192 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{ - to_binary, Addr, BankMsg, Binary, Coin, CosmosMsg, Deps, StdError, Uint128, WasmMsg, + to_binary, Addr, BankMsg, Binary, Coin, CosmosMsg, Deps, MessageInfo, StdError, Uint128, + WasmMsg, }; +use cw_utils::must_pay; use crate::ContractError; #[cw_serde] -pub struct SendMessage { - pub address: String, - pub message: Binary, +pub enum AcceptedMessages { + BankSend { + to_address: String, + amount: Vec, + }, + BankBurn { + amount: Vec, + }, + WasmExecute { + contract_addr: String, + msg: Binary, + funds: Vec, + }, + WasmInstantiate { + admin: Option, + code_id: u64, + msg: Binary, + funds: Vec, + label: String, + }, +} + +/// Enum to accept either a cosmos msg (for recieved native tokens) +/// or address and binary message (for recieved cw20 tokens) +#[cw_serde] +pub enum SendMessage { + SendCw20 { + /// Contract address to execute the msg on + contract_address: String, + /// The message in binary format + message: Binary, + }, + SendNative { + /// Vector of accepted messages to send + messages: Vec, + }, } impl SendMessage { - pub fn into_checked(self, deps: Deps) -> Result { - Ok(CheckedSendMessage { - address: deps.api.addr_validate(&self.address)?, - message: self.message, - }) + pub fn into_checked_cosmos_msgs( + self, + deps: Deps, + other_token_info: CheckedTokenInfo, + ) -> Result, ContractError> { + match self { + SendMessage::SendCw20 { + contract_address, + message, + } => { + // We check if the other party token type is cw20 + if let CheckedTokenInfo::Cw20 { + contract_addr: cw20_address, + amount, + } = other_token_info + { + Ok(vec![WasmMsg::Execute { + contract_addr: cw20_address.to_string(), + msg: to_binary(&cw20::Cw20ExecuteMsg::Send { + contract: deps.api.addr_validate(&contract_address)?.to_string(), + amount, + msg: message, + })?, + funds: vec![], + } + .into()]) + } else { + return Err(ContractError::InvalidSendMsg {}); + } + } + SendMessage::SendNative { messages } => { + if let CheckedTokenInfo::Native { + amount: total_amount, + denom, + } = other_token_info + { + if messages.is_empty() { + return Err(ContractError::InvalidSendMsg {}); + } + + let mut total_send_funds = Uint128::zero(); + let cosmos_msgs = messages + .into_iter() + .map(|msg| match msg { + AcceptedMessages::BankSend { + to_address, + amount: amount_to_pay, + } => { + total_send_funds = + self.add_to_total(total_send_funds, amount_to_pay, denom)?; + + Ok(BankMsg::Send { + to_address: deps.api.addr_validate(&to_address)?.to_string(), + amount: amount_to_pay, + } + .into()) + } + AcceptedMessages::BankBurn { + amount: amount_to_pay, + } => { + total_send_funds = + self.add_to_total(total_send_funds, amount_to_pay, denom)?; + + Ok(BankMsg::Burn { + amount: amount_to_pay, + } + .into()) + } + AcceptedMessages::WasmExecute { + contract_addr, + msg, + funds: amount_to_pay, + } => { + total_send_funds = + self.add_to_total(total_send_funds, amount_to_pay, denom)?; + + Ok(WasmMsg::Execute { + contract_addr: deps + .api + .addr_validate(&contract_addr)? + .to_string(), + msg, + funds: amount_to_pay, + } + .into()) + } + AcceptedMessages::WasmInstantiate { + admin, + code_id, + msg, + funds: amount_to_pay, + label, + } => { + total_send_funds = + self.add_to_total(total_send_funds, amount_to_pay, denom)?; + + Ok(WasmMsg::Instantiate { + admin: admin + .map(|a| deps.api.addr_validate(&a).unwrap().to_string()), + code_id, + msg, + funds: amount_to_pay, + label, + } + .into()) + } + }) + .collect::, ContractError>>()?; + + // Make sure that the funds we try to send, matches exactly to the total amount swapped. + if total_send_funds != total_amount { + return Err(ContractError::InvalidFunds {}); + } + + Ok(cosmos_msgs) + } else { + return Err(ContractError::InvalidSendMsg {}); + } + } + } } -} + /// This function check the given funds to make sure they are valid. + /// and add the amount to the total funds. + /// returns the new total. + pub fn add_to_total( + self, + total_funds: Uint128, + funds: Vec, + denom: String, + ) -> Result { + let amount = must_pay( + &MessageInfo { + sender: Addr::unchecked(""), + funds, + }, + &denom, + )?; -#[cw_serde] -pub struct CheckedSendMessage { - pub address: Addr, - pub message: Binary, + Ok(total_funds + amount) + } } /// Information about the token being used on one side of the escrow. @@ -79,18 +242,10 @@ pub struct Counterparty { pub address: String, /// The funds they have promised to provide. pub promise: TokenInfo, - /// The message to send once the escrow is complete. - pub send_msg: Option, } impl Counterparty { - pub fn into_checked(self, deps: Deps) -> Result { - let send_msg = if let Some(send_msg) = self.send_msg { - Some(send_msg.into_checked(deps)?) - } else { - None - }; - + pub fn into_checked(self, deps: Deps, send_msg: Option>) -> Result { Ok(CheckedCounterparty { address: deps.api.addr_validate(&self.address)?, provided: false, @@ -117,59 +272,87 @@ pub struct CheckedCounterparty { pub address: Addr, pub promise: CheckedTokenInfo, pub provided: bool, - pub send_msg: Option, + pub send_msg: Option> } impl CheckedTokenInfo { pub fn into_send_message( self, - recipient: &Addr, - send_msg: Option, - ) -> Result { + deps: Deps, + other_counterparty: &CheckedCounterparty, + send_msg: Option>, + ) -> Result, StdError> { Ok(match self { Self::Native { denom, amount } => match send_msg { - Some(CheckedSendMessage { - address, - message: msg, - }) => WasmMsg::Execute { - contract_addr: address.to_string(), - msg, - funds: vec![Coin { denom, amount }], - } - .into(), - None => BankMsg::Send { - to_address: recipient.to_string(), + Some(msgs) => msgs, + None => vec![BankMsg::Send { + to_address: other_counterparty.address.to_string(), amount: vec![Coin { denom, amount }], } - .into(), + .into()], }, Self::Cw20 { contract_addr, amount, } => match send_msg { - Some(CheckedSendMessage { - address, - message: msg, - }) => WasmMsg::Execute { - contract_addr: contract_addr.to_string(), - msg: to_binary(&cw20::Cw20ExecuteMsg::Send { - contract: address.to_string(), - amount, - msg, - })?, - funds: vec![], - } - .into(), - None => WasmMsg::Execute { + Some(msgs) => msgs, + None => vec![WasmMsg::Execute { contract_addr: contract_addr.into_string(), msg: to_binary(&cw20::Cw20ExecuteMsg::Transfer { - recipient: recipient.to_string(), + recipient: other_counterparty.address.to_string(), amount, })?, funds: vec![], } - .into(), + .into()], }, }) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_into_spend_message_native() { + let info = CheckedTokenInfo::Native { + amount: Uint128::new(100), + denom: "uekez".to_string(), + }; + let message = info.into_send_message(&Addr::unchecked("ekez")).unwrap(); + + assert_eq!( + message, + CosmosMsg::Bank(BankMsg::Send { + to_address: "ekez".to_string(), + amount: vec![Coin { + amount: Uint128::new(100), + denom: "uekez".to_string() + }] + }) + ); + } + + #[test] + fn test_into_spend_message_cw20() { + let info = CheckedTokenInfo::Cw20 { + amount: Uint128::new(100), + contract_addr: Addr::unchecked("ekez_token"), + }; + let message = info.into_send_message(&Addr::unchecked("ekez")).unwrap(); + + assert_eq!( + message, + CosmosMsg::Wasm(WasmMsg::Execute { + funds: vec![], + contract_addr: "ekez_token".to_string(), + msg: to_binary(&cw20::Cw20ExecuteMsg::Transfer { + recipient: "ekez".to_string(), + amount: Uint128::new(100) + }) + .unwrap() + }) + ); + } +} From 31713fd3e44ee7bcd601e238345d40a38d7bbc32 Mon Sep 17 00:00:00 2001 From: Art3mix Date: Thu, 9 Mar 2023 13:28:40 +0200 Subject: [PATCH 03/11] finished token swap --- .../cw-token-swap/schema/cw-token-swap.json | 947 +++++++++++++++++- .../external/cw-token-swap/src/contract.rs | 113 ++- contracts/external/cw-token-swap/src/error.rs | 5 +- contracts/external/cw-token-swap/src/msg.rs | 14 +- contracts/external/cw-token-swap/src/tests.rs | 404 ++++++-- contracts/external/cw-token-swap/src/types.rs | 171 ++-- .../cw-token-swap/CwTokenSwap.client.ts | 20 +- .../cw-token-swap/CwTokenSwap.types.ts | 187 +++- 8 files changed, 1673 insertions(+), 188 deletions(-) diff --git a/contracts/external/cw-token-swap/schema/cw-token-swap.json b/contracts/external/cw-token-swap/schema/cw-token-swap.json index 80f134832..56a99f6c5 100644 --- a/contracts/external/cw-token-swap/schema/cw-token-swap.json +++ b/contracts/external/cw-token-swap/schema/cw-token-swap.json @@ -132,6 +132,18 @@ "properties": { "fund": { "type": "object", + "properties": { + "send_message": { + "anyOf": [ + { + "$ref": "#/definitions/SendMessage" + }, + { + "type": "null" + } + ] + } + }, "additionalProperties": false } }, @@ -153,10 +165,157 @@ } ], "definitions": { + "AcceptedMessages": { + "oneOf": [ + { + "type": "object", + "required": [ + "bank_send" + ], + "properties": { + "bank_send": { + "type": "object", + "required": [ + "amount", + "to_address" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "to_address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "bank_burn" + ], + "properties": { + "bank_burn": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "wasm_execute" + ], + "properties": { + "wasm_execute": { + "type": "object", + "required": [ + "contract_addr", + "funds", + "msg" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "msg": { + "$ref": "#/definitions/Binary" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "wasm_instantiate" + ], + "properties": { + "wasm_instantiate": { + "type": "object", + "required": [ + "code_id", + "funds", + "label", + "msg" + ], + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "label": { + "type": "string" + }, + "msg": { + "$ref": "#/definitions/Binary" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, "Binary": { "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", "type": "string" }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, "Cw20ReceiveMsg": { "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", "type": "object", @@ -178,6 +337,67 @@ }, "additionalProperties": false }, + "SendMessage": { + "description": "Enum to accept either a cosmos msg (for recieved native tokens) or address and binary message (for recieved cw20 tokens)", + "oneOf": [ + { + "type": "object", + "required": [ + "send_cw20" + ], + "properties": { + "send_cw20": { + "type": "object", + "required": [ + "contract_address", + "message" + ], + "properties": { + "contract_address": { + "description": "Contract address to execute the msg on", + "type": "string" + }, + "message": { + "description": "The message in binary format", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "send_native" + ], + "properties": { + "send_native": { + "type": "object", + "required": [ + "messages" + ], + "properties": { + "messages": { + "description": "Vector of accepted messages to send", + "type": "array", + "items": { + "$ref": "#/definitions/AcceptedMessages" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" @@ -233,6 +453,67 @@ "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", "type": "string" }, + "BankMsg": { + "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", + "oneOf": [ + { + "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "send" + ], + "properties": { + "send": { + "type": "object", + "required": [ + "amount", + "to_address" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "to_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, "CheckedCounterparty": { "type": "object", "required": [ @@ -249,6 +530,15 @@ }, "provided": { "type": "boolean" + }, + "send_msg": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/CosmosMsg_for_Empty" + } } }, "additionalProperties": false @@ -307,9 +597,660 @@ } ] }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "CosmosMsg_for_Empty": { + "oneOf": [ + { + "type": "object", + "required": [ + "bank" + ], + "properties": { + "bank": { + "$ref": "#/definitions/BankMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "staking" + ], + "properties": { + "staking": { + "$ref": "#/definitions/StakingMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "distribution" + ], + "properties": { + "distribution": { + "$ref": "#/definitions/DistributionMsg" + } + }, + "additionalProperties": false + }, + { + "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", + "type": "object", + "required": [ + "stargate" + ], + "properties": { + "stargate": { + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Binary" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/IbcMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "wasm" + ], + "properties": { + "wasm": { + "$ref": "#/definitions/WasmMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "gov" + ], + "properties": { + "gov": { + "$ref": "#/definitions/GovMsg" + } + }, + "additionalProperties": false + } + ] + }, + "DistributionMsg": { + "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "set_withdraw_address" + ], + "properties": { + "set_withdraw_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "The `withdraw_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "withdraw_delegator_reward" + ], + "properties": { + "withdraw_delegator_reward": { + "type": "object", + "required": [ + "validator" + ], + "properties": { + "validator": { + "description": "The `validator_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, + "GovMsg": { + "description": "This message type allows the contract interact with the [x/gov] module in order to cast votes.\n\n[x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov\n\n## Examples\n\nCast a simple vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); use cosmwasm_std::{GovMsg, VoteOption};\n\n#[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::Vote { proposal_id: 4, vote: VoteOption::Yes, })) } ```\n\nCast a weighted vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); # #[cfg(feature = \"cosmwasm_1_2\")] use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};\n\n# #[cfg(feature = \"cosmwasm_1_2\")] #[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::VoteWeighted { proposal_id: 4, options: vec![ WeightedVoteOption { option: VoteOption::Yes, weight: Decimal::percent(65), }, WeightedVoteOption { option: VoteOption::Abstain, weight: Decimal::percent(35), }, ], })) } ```", + "oneOf": [ + { + "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "description": "The vote option.\n\nThis should be called \"option\" for consistency with Cosmos SDK. Sorry for that. See .", + "allOf": [ + { + "$ref": "#/definitions/VoteOption" + } + ] + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcMsg": { + "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", + "oneOf": [ + { + "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "amount", + "channel_id", + "timeout", + "to_address" + ], + "properties": { + "amount": { + "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "channel_id": { + "description": "exisiting channel to send the tokens over", + "type": "string" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + }, + "to_address": { + "description": "address on the remote chain to receive these tokens", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", + "type": "object", + "required": [ + "send_packet" + ], + "properties": { + "send_packet": { + "type": "object", + "required": [ + "channel_id", + "data", + "timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "data": { + "$ref": "#/definitions/Binary" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", + "type": "object", + "required": [ + "close_channel" + ], + "properties": { + "close_channel": { + "type": "object", + "required": [ + "channel_id" + ], + "properties": { + "channel_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcTimeout": { + "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", + "type": "object", + "properties": { + "block": { + "anyOf": [ + { + "$ref": "#/definitions/IbcTimeoutBlock" + }, + { + "type": "null" + } + ] + }, + "timestamp": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + } + } + }, + "IbcTimeoutBlock": { + "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", + "type": "object", + "required": [ + "height", + "revision" + ], + "properties": { + "height": { + "description": "block height after which the packet times out. the height within the given revision", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "revision": { + "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "StakingMsg": { + "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "delegate" + ], + "properties": { + "delegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "undelegate" + ], + "properties": { + "undelegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "redelegate" + ], + "properties": { + "redelegate": { + "type": "object", + "required": [ + "amount", + "dst_validator", + "src_validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "dst_validator": { + "type": "string" + }, + "src_validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + }, + "VoteOption": { + "type": "string", + "enum": [ + "yes", + "no", + "abstain", + "no_with_veto" + ] + }, + "WasmMsg": { + "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", + "oneOf": [ + { + "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "contract_addr", + "funds", + "msg" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "msg": { + "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThe contract address is non-predictable. But it is guaranteed that when emitting the same Instantiate message multiple times, multiple instances on different addresses will be generated. See also Instantiate2.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L53-L71). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "instantiate" + ], + "properties": { + "instantiate": { + "type": "object", + "required": [ + "code_id", + "funds", + "label", + "msg" + ], + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "label": { + "description": "A human-readbale label for the contract", + "type": "string" + }, + "msg": { + "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "migrate" + ], + "properties": { + "migrate": { + "type": "object", + "required": [ + "contract_addr", + "msg", + "new_code_id" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "msg": { + "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "new_code_id": { + "description": "the code_id of the new logic to place in the given contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "required": [ + "admin", + "contract_addr" + ], + "properties": { + "admin": { + "type": "string" + }, + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "clear_admin" + ], + "properties": { + "clear_admin": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] } } } diff --git a/contracts/external/cw-token-swap/src/contract.rs b/contracts/external/cw-token-swap/src/contract.rs index 089e04f20..a4bc390df 100644 --- a/contracts/external/cw-token-swap/src/contract.rs +++ b/contracts/external/cw-token-swap/src/contract.rs @@ -1,7 +1,7 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_binary, Addr, Binary, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Response, StdResult, + from_binary, to_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, Uint128, }; use cw2::set_contract_version; @@ -10,9 +10,9 @@ use cw_utils::must_pay; use crate::{ error::ContractError, - msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, StatusResponse}, + msg::{Cw20RecieveMsg, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, StatusResponse}, state::{COUNTERPARTY_ONE, COUNTERPARTY_TWO}, - types::{CheckedCounterparty, CheckedTokenInfo}, + types::{CheckedCounterparty, CheckedTokenInfo, SendMessage}, }; pub(crate) const CONTRACT_NAME: &str = "crates.io:cw-token-swap"; @@ -61,6 +61,7 @@ struct CounterpartyResponse<'a> { pub counterparty: CheckedCounterparty, pub other_counterparty: CheckedCounterparty, pub storage: Item<'a, CheckedCounterparty>, + pub other_storage: Item<'a, CheckedCounterparty>, } fn get_counterparty<'a>( @@ -70,19 +71,31 @@ fn get_counterparty<'a>( let counterparty_one = COUNTERPARTY_ONE.load(deps.storage)?; let counterparty_two = COUNTERPARTY_TWO.load(deps.storage)?; - let (counterparty, other_counterparty, storage) = if *sender == counterparty_one.address { - (counterparty_one, counterparty_two, COUNTERPARTY_ONE) - } else if *sender == counterparty_two.address { - (counterparty_two, counterparty_one, COUNTERPARTY_TWO) - } else { - // Contract may only be funded by a counterparty. - return Err(ContractError::Unauthorized {}); - }; + let (counterparty, other_counterparty, storage, other_storage) = + if *sender == counterparty_one.address { + ( + counterparty_one, + counterparty_two, + COUNTERPARTY_ONE, + COUNTERPARTY_TWO, + ) + } else if *sender == counterparty_two.address { + ( + counterparty_two, + counterparty_one, + COUNTERPARTY_TWO, + COUNTERPARTY_ONE, + ) + } else { + // Contract may only be funded by a counterparty. + return Err(ContractError::Unauthorized {}); + }; Ok(CounterpartyResponse { counterparty, other_counterparty, storage, + other_storage, }) } @@ -90,13 +103,16 @@ fn get_counterparty<'a>( /// escrow funds if both counterparties have funded the contract. /// /// NOTE: The caller must verify that the denom of PAID is correct. +#[allow(clippy::too_many_arguments)] fn do_fund( deps: DepsMut, counterparty: CheckedCounterparty, paid: Uint128, expected: Uint128, other_counterparty: CheckedCounterparty, + send_message: Option, storage: Item, + other_storage: Item, ) -> Result { if counterparty.provided { return Err(ContractError::AlreadyProvided {}); @@ -113,18 +129,24 @@ fn do_fund( counterparty.provided = true; storage.save(deps.storage, &counterparty)?; + // We add the send message to the other counterparty + // Because the send message is based on the promised token + // so if I promise cw20 token, it will send cw20 msg + let mut other_counterparty = other_counterparty; + other_counterparty.add_send_msg(deps.as_ref(), send_message)?; + other_storage.save(deps.storage, &other_counterparty)?; + let messages = if counterparty.provided && other_counterparty.provided { - let mut msgs = counterparty.promise.clone().into_send_message( - deps.as_ref(), - &other_counterparty, - counterparty.send_msg.clone(), - )?; - - msgs.append(&mut other_counterparty.promise.into_send_message( - deps.as_ref(), - &counterparty, - other_counterparty.send_msg, - )?); + let mut msgs = counterparty + .promise + .clone() + .into_send_message(&other_counterparty, counterparty.send_msg.clone())?; + + msgs.append( + &mut other_counterparty + .promise + .into_send_message(&counterparty, other_counterparty.send_msg)?, + ); msgs } else { vec![] @@ -147,6 +169,7 @@ pub fn execute_receive( counterparty, other_counterparty, storage, + other_storage, } = get_counterparty(deps.as_ref(), &sender)?; let (expected_payment, paid) = if let CheckedTokenInfo::Cw20 { @@ -164,25 +187,49 @@ pub fn execute_receive( return Err(ContractError::InvalidFunds {}); }; + let mut send_msg = None; + if let Ok(msg) = from_binary::(&msg.msg) { + match msg { + Cw20RecieveMsg::FundWithMsgs { + amount, + send_message, + } => { + if let CheckedTokenInfo::Cw20 { + amount: promised_amount, + .. + } = other_counterparty.promise + { + if promised_amount != amount { + return Err(ContractError::WrongFundsCalculation {}); + } + }; + send_msg = Some(send_message); + } + } + }; + do_fund( deps, counterparty, paid, expected_payment, other_counterparty, + send_msg, storage, + other_storage, ) } pub fn execute_fund( deps: DepsMut, info: MessageInfo, - send_message: Option, + send_msg: Option, ) -> Result { let CounterpartyResponse { - mut counterparty, + counterparty, other_counterparty, storage, + other_storage, } = get_counterparty(deps.as_ref(), &info.sender)?; let (expected_payment, paid) = if let CheckedTokenInfo::Native { amount, denom } = &counterparty.promise { @@ -199,7 +246,9 @@ pub fn execute_fund( paid, expected_payment, other_counterparty, + send_msg, storage, + other_storage, ) } @@ -208,6 +257,7 @@ pub fn execute_withdraw(deps: DepsMut, info: MessageInfo) -> Result Result> }, + Fund { send_message: Option }, /// Withdraws provided funds. Only allowed if the other /// counterparty has yet to provide their promised funds. Withdraw {}, @@ -36,3 +36,11 @@ pub struct StatusResponse { #[cw_serde] pub struct MigrateMsg {} + +#[cw_serde] +pub enum Cw20RecieveMsg { + FundWithMsgs { + amount: Uint128, + send_message: SendMessage, + }, +} diff --git a/contracts/external/cw-token-swap/src/tests.rs b/contracts/external/cw-token-swap/src/tests.rs index 74d6e5409..29f847cd9 100644 --- a/contracts/external/cw-token-swap/src/tests.rs +++ b/contracts/external/cw-token-swap/src/tests.rs @@ -1,15 +1,18 @@ use cosmwasm_std::{ coins, testing::{mock_dependencies, mock_env}, - to_binary, Addr, Coin, Decimal, Empty, Uint128, Validator, + to_binary, Addr, Coin, Empty, Uint128, }; use cw20::Cw20Coin; use cw_multi_test::{App, BankSudo, Contract, ContractWrapper, Executor, SudoMsg}; use crate::{ contract::{migrate, CONTRACT_NAME, CONTRACT_VERSION}, - msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, StatusResponse}, - types::{CheckedCounterparty, CheckedTokenInfo, Counterparty, TokenInfo}, + msg::{Cw20RecieveMsg, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, StatusResponse}, + types::{ + AcceptedMessages, CheckedCounterparty, CheckedTokenInfo, Counterparty, SendMessage, + TokenInfo, + }, ContractError, }; @@ -82,7 +85,6 @@ fn test_simple_escrow() { denom: "ujuno".to_string(), amount: Uint128::new(100), }, - send_msg: None, }, counterparty_two: Counterparty { address: DAO2.to_string(), @@ -90,7 +92,6 @@ fn test_simple_escrow() { contract_addr: cw20.to_string(), amount: Uint128::new(100), }, - send_msg: None, }, }, &[], @@ -123,7 +124,7 @@ fn test_simple_escrow() { app.execute_contract( Addr::unchecked(DAO1), escrow, - &ExecuteMsg::Fund {}, + &ExecuteMsg::Fund { send_message: None }, &[Coin { amount: Uint128::new(100), denom: "ujuno".to_string(), @@ -148,31 +149,7 @@ fn test_simple_escrow() { #[test] fn test_simple_with_send_messages() { - let mut app = App::new(|router, api, storage| { - router - .bank - .init_balance( - storage, - &Addr::unchecked("owner"), - coins(100_000_000_000, "ujuno"), - ) - .unwrap(); - - router - .staking - .add_validator( - api, - storage, - &mock_env().block, - Validator { - address: "validator".to_string(), - commission: Decimal::zero(), // zero percent comission to keep math simple. - max_commission: Decimal::percent(10), - max_change_rate: Decimal::percent(2), - }, - ) - .unwrap(); - }); + let mut app = App::default(); let cw20_code = app.store_code(cw20_contract()); let escrow_code = app.store_code(escrow_contract()); @@ -199,48 +176,63 @@ fn test_simple_with_send_messages() { ) .unwrap(); - let vetsing = app + let vetsing_init_msg = cw_vesting::msg::InstantiateMsg { + owner: Some("owner".to_string()), + recipient: DAO2.to_string(), + title: "title".to_string(), + description: Some("description".to_string()), + total: Uint128::new(200), + denom: cw_denom::UncheckedDenom::Native("ujuno".to_string()), + schedule: cw_vesting::vesting::Schedule::SaturatingLinear, + start_time: None, + vesting_duration_seconds: 60 * 60 * 24 * 7, // one week + unbonding_duration_seconds: 60, + }; + + let escrow = app .instantiate_contract( - vesting_code, - Addr::unchecked("owner"), - &cw_vesting::msg::InstantiateMsg { - owner: Some("owner".to_string()), - recipient: DAO2.to_string(), - title: "title".to_string(), - description: Some("description".to_string()), - total: Uint128::new(100_000_000), - denom: cw_denom::UncheckedDenom::Native("ujuno".to_string()), - schedule: cw_vesting::vesting::Schedule::SaturatingLinear, - start_time: None, - vesting_duration_seconds: 60 * 60 * 24 * 7, // one week - unbonding_duration_seconds: 60, + escrow_code, + Addr::unchecked(DAO1), + &InstantiateMsg { + counterparty_one: Counterparty { + address: DAO1.to_string(), + promise: TokenInfo::Native { + denom: "ujuno".to_string(), + amount: Uint128::new(200), + }, + }, + counterparty_two: Counterparty { + address: DAO2.to_string(), + promise: TokenInfo::Cw20 { + contract_addr: cw20.to_string(), + amount: Uint128::new(100), + }, + }, }, - &coins(100_000_000, "ujuno"), - "coin", + &[], + "escrow", None, ) .unwrap(); - let escrow = app + let some_other_escrow = app .instantiate_contract( escrow_code, Addr::unchecked(DAO1), &InstantiateMsg { counterparty_one: Counterparty { - address: DAO1.to_string(), + address: DAO2.to_string(), promise: TokenInfo::Native { denom: "ujuno".to_string(), amount: Uint128::new(100), }, - send_msg: None, }, counterparty_two: Counterparty { - address: DAO2.to_string(), + address: escrow.to_string(), promise: TokenInfo::Cw20 { contract_addr: cw20.to_string(), amount: Uint128::new(100), }, - send_msg: None, }, }, &[], @@ -249,13 +241,27 @@ fn test_simple_with_send_messages() { ) .unwrap(); + // In this case we are sending cw20 tokens, but expecting to get native token + // So we can send any set of messages we want here. app.execute_contract( Addr::unchecked(DAO2), cw20.clone(), &cw20::Cw20ExecuteMsg::Send { contract: escrow.to_string(), amount: Uint128::new(100), - msg: to_binary("").unwrap(), + msg: to_binary(&Cw20RecieveMsg::FundWithMsgs { + amount: Uint128::new(200), + send_message: SendMessage::SendNative { + messages: vec![AcceptedMessages::WasmInstantiate { + admin: None, + code_id: vesting_code, + msg: to_binary(&vetsing_init_msg).unwrap(), + funds: coins(200, "ujuno"), + label: "some vesting".to_string(), + }], + }, + }) + .unwrap(), }, &[], ) @@ -264,22 +270,276 @@ fn test_simple_with_send_messages() { app.sudo(SudoMsg::Bank(BankSudo::Mint { to_address: DAO1.to_string(), amount: vec![Coin { - amount: Uint128::new(100), + amount: Uint128::new(200), denom: "ujuno".to_string(), }], })) .unwrap(); + // We recieve 100 cw20 token, just for fun, im trying to fund a differetn swap with this swap + // So once this swap is done, I can fund the other swap with the 50 cw20 tokens app.execute_contract( Addr::unchecked(DAO1), escrow, - &ExecuteMsg::Fund {}, + &ExecuteMsg::Fund { + send_message: Some(SendMessage::SendCw20 { + contract_address: some_other_escrow.to_string(), + message: to_binary("").unwrap(), + }), + }, &[Coin { + amount: Uint128::new(200), + denom: "ujuno".to_string(), + }], + ) + .unwrap(); + + // --- Cool everything passed, lets make sure everything is sent correctly --- + + // dao1 cw20 balance should be 0 because we sent it into the other escrow + let dao1_cw20_balance: cw20::BalanceResponse = app + .wrap() + .query_wasm_smart( + cw20.clone(), + &cw20::Cw20QueryMsg::Balance { + address: DAO2.to_string(), + }, + ) + .unwrap(); + assert_eq!(dao1_cw20_balance.balance, Uint128::new(0)); + + // Lets make sure the other escrow was funded correctly + // provided is true and the cw20 balance is 100 + let other_escrow_status: StatusResponse = app + .wrap() + .query_wasm_smart(some_other_escrow.clone(), &QueryMsg::Status {}) + .unwrap(); + let other_escrow_cw20_balance: cw20::BalanceResponse = app + .wrap() + .query_wasm_smart( + cw20, + &cw20::Cw20QueryMsg::Balance { + address: some_other_escrow.to_string(), + }, + ) + .unwrap(); + assert!(other_escrow_status.counterparty_two.provided); + assert_eq!(other_escrow_cw20_balance.balance, Uint128::new(100)); + + // Make sure that DAO1 native balance is 0 (sent to the vesting contract) + let dao1_balance = app.wrap().query_balance(DAO1.to_string(), "ujuno").unwrap(); + assert_eq!(dao1_balance.amount, Uint128::new(0)); +} + +#[test] +fn test_multiple_send_messages() { + let mut app = App::default(); + + let cw20_code = app.store_code(cw20_contract()); + let escrow_code = app.store_code(escrow_contract()); + + let cw20 = app + .instantiate_contract( + cw20_code, + Addr::unchecked(DAO2), + &cw20_base::msg::InstantiateMsg { + name: "coin coin".to_string(), + symbol: "coin".to_string(), + decimals: 6, + initial_balances: vec![Cw20Coin { + address: DAO2.to_string(), + amount: Uint128::new(100), + }], + mint: None, + marketing: None, + }, + &[], + "coin", + None, + ) + .unwrap(); + + let escrow = app + .instantiate_contract( + escrow_code, + Addr::unchecked(DAO1), + &InstantiateMsg { + counterparty_one: Counterparty { + address: DAO1.to_string(), + promise: TokenInfo::Native { + denom: "ujuno".to_string(), + amount: Uint128::new(200), + }, + }, + counterparty_two: Counterparty { + address: DAO2.to_string(), + promise: TokenInfo::Cw20 { + contract_addr: cw20.to_string(), + amount: Uint128::new(100), + }, + }, + }, + &[], + "escrow", + None, + ) + .unwrap(); + + app.sudo(SudoMsg::Bank(BankSudo::Mint { + to_address: DAO1.to_string(), + amount: vec![Coin { + amount: Uint128::new(200), + denom: "ujuno".to_string(), + }], + })) + .unwrap(); + + // We recieve 100 cw20 token, just for fun, im trying to fund a differetn swap with this swap + // So once this swap is done, I can fund the other swap with the 50 cw20 tokens + app.execute_contract( + Addr::unchecked(DAO1), + escrow.clone(), + &ExecuteMsg::Fund { send_message: None }, + &[Coin { + amount: Uint128::new(200), + denom: "ujuno".to_string(), + }], + ) + .unwrap(); + + app.execute_contract( + Addr::unchecked(DAO2), + cw20, + &cw20::Cw20ExecuteMsg::Send { + contract: escrow.to_string(), amount: Uint128::new(100), + msg: to_binary(&Cw20RecieveMsg::FundWithMsgs { + amount: Uint128::new(200), + send_message: SendMessage::SendNative { + messages: vec![ + AcceptedMessages::BankBurn { + amount: coins(100, "ujuno"), + }, + AcceptedMessages::BankSend { + to_address: "some_random".to_string(), + amount: coins(100, "ujuno"), + }, + ], + }, + }) + .unwrap(), + }, + &[], + ) + .unwrap(); + + let some_random_balance = app + .wrap() + .query_balance("some_random".to_string(), "ujuno") + .unwrap(); + assert_eq!(some_random_balance.amount, Uint128::new(100)); +} + +#[test] +fn test_send_messages_incomplete_funds() { + let mut app = App::default(); + + let cw20_code = app.store_code(cw20_contract()); + let escrow_code = app.store_code(escrow_contract()); + + let cw20 = app + .instantiate_contract( + cw20_code, + Addr::unchecked(DAO2), + &cw20_base::msg::InstantiateMsg { + name: "coin coin".to_string(), + symbol: "coin".to_string(), + decimals: 6, + initial_balances: vec![Cw20Coin { + address: DAO2.to_string(), + amount: Uint128::new(100), + }], + mint: None, + marketing: None, + }, + &[], + "coin", + None, + ) + .unwrap(); + + let escrow = app + .instantiate_contract( + escrow_code, + Addr::unchecked(DAO1), + &InstantiateMsg { + counterparty_one: Counterparty { + address: DAO1.to_string(), + promise: TokenInfo::Native { + denom: "ujuno".to_string(), + amount: Uint128::new(200), + }, + }, + counterparty_two: Counterparty { + address: DAO2.to_string(), + promise: TokenInfo::Cw20 { + contract_addr: cw20.to_string(), + amount: Uint128::new(100), + }, + }, + }, + &[], + "escrow", + None, + ) + .unwrap(); + + app.sudo(SudoMsg::Bank(BankSudo::Mint { + to_address: DAO1.to_string(), + amount: vec![Coin { + amount: Uint128::new(200), + denom: "ujuno".to_string(), + }], + })) + .unwrap(); + + // We recieve 100 cw20 token, just for fun, im trying to fund a differetn swap with this swap + // So once this swap is done, I can fund the other swap with the 50 cw20 tokens + app.execute_contract( + Addr::unchecked(DAO1), + escrow.clone(), + &ExecuteMsg::Fund { send_message: None }, + &[Coin { + amount: Uint128::new(200), denom: "ujuno".to_string(), }], ) .unwrap(); + + let err = app + .execute_contract( + Addr::unchecked(DAO2), + cw20, + &cw20::Cw20ExecuteMsg::Send { + contract: escrow.to_string(), + amount: Uint128::new(100), + msg: to_binary(&Cw20RecieveMsg::FundWithMsgs { + amount: Uint128::new(200), + send_message: SendMessage::SendNative { + messages: vec![AcceptedMessages::BankBurn { + amount: coins(100, "ujuno"), + }], + }, + }) + .unwrap(), + }, + &[], + ) + .unwrap_err() + .downcast::() + .unwrap(); + + assert_eq!(err, ContractError::WrongFundsCalculation {}); } #[test] @@ -321,7 +581,6 @@ fn test_withdraw() { denom: "ujuno".to_string(), amount: Uint128::new(100), }, - send_msg: None, }, counterparty_two: Counterparty { address: DAO2.to_string(), @@ -329,7 +588,6 @@ fn test_withdraw() { contract_addr: cw20.to_string(), amount: Uint128::new(100), }, - send_msg: None, }, }, &[], @@ -395,7 +653,7 @@ fn test_withdraw() { app.execute_contract( Addr::unchecked(DAO1), escrow.clone(), - &ExecuteMsg::Fund {}, + &ExecuteMsg::Fund { send_message: None }, &[Coin { amount: Uint128::new(100), denom: "ujuno".to_string(), @@ -511,7 +769,6 @@ fn test_withdraw_post_completion() { denom: "ujuno".to_string(), amount: Uint128::new(100), }, - send_msg: None, }, counterparty_two: Counterparty { address: DAO2.to_string(), @@ -519,7 +776,6 @@ fn test_withdraw_post_completion() { contract_addr: cw20.to_string(), amount: Uint128::new(100), }, - send_msg: None, }, }, &[], @@ -552,7 +808,7 @@ fn test_withdraw_post_completion() { app.execute_contract( Addr::unchecked(DAO1), escrow.clone(), - &ExecuteMsg::Fund {}, + &ExecuteMsg::Fund { send_message: None }, &[Coin { amount: Uint128::new(100), denom: "ujuno".to_string(), @@ -622,7 +878,6 @@ fn test_invalid_instantiate() { denom: "ujuno".to_string(), amount: Uint128::new(0), }, - send_msg: None, }, counterparty_two: Counterparty { address: DAO2.to_string(), @@ -630,7 +885,6 @@ fn test_invalid_instantiate() { contract_addr: cw20.to_string(), amount: Uint128::new(100), }, - send_msg: None, }, }, &[], @@ -655,7 +909,6 @@ fn test_invalid_instantiate() { denom: "ujuno".to_string(), amount: Uint128::new(100), }, - send_msg: None, }, counterparty_two: Counterparty { address: DAO2.to_string(), @@ -663,7 +916,6 @@ fn test_invalid_instantiate() { contract_addr: cw20.to_string(), amount: Uint128::new(0), }, - send_msg: None, }, }, &[], @@ -695,7 +947,6 @@ fn test_non_distincy_counterparties() { denom: "ujuno".to_string(), amount: Uint128::new(110), }, - send_msg: None, }, counterparty_two: Counterparty { address: DAO1.to_string(), @@ -703,7 +954,6 @@ fn test_non_distincy_counterparties() { denom: "ujuno".to_string(), amount: Uint128::new(10), }, - send_msg: None, }, }, &[], @@ -756,7 +1006,6 @@ fn test_fund_non_counterparty() { denom: "ujuno".to_string(), amount: Uint128::new(100), }, - send_msg: None, }, counterparty_two: Counterparty { address: DAO2.to_string(), @@ -764,7 +1013,6 @@ fn test_fund_non_counterparty() { contract_addr: cw20.to_string(), amount: Uint128::new(100), }, - send_msg: None, }, }, &[], @@ -803,7 +1051,7 @@ fn test_fund_non_counterparty() { .execute_contract( Addr::unchecked("noah"), escrow, - &ExecuteMsg::Fund {}, + &ExecuteMsg::Fund { send_message: None }, &[Coin { amount: Uint128::new(100), denom: "ujuno".to_string(), @@ -855,7 +1103,6 @@ fn test_fund_twice() { denom: "ujuno".to_string(), amount: Uint128::new(100), }, - send_msg: None, }, counterparty_two: Counterparty { address: DAO2.to_string(), @@ -863,7 +1110,6 @@ fn test_fund_twice() { contract_addr: cw20.to_string(), amount: Uint128::new(100), }, - send_msg: None, }, }, &[], @@ -896,7 +1142,7 @@ fn test_fund_twice() { app.execute_contract( Addr::unchecked(DAO1), escrow.clone(), - &ExecuteMsg::Fund {}, + &ExecuteMsg::Fund { send_message: None }, &[Coin { amount: Uint128::new(100), denom: "ujuno".to_string(), @@ -908,7 +1154,7 @@ fn test_fund_twice() { .execute_contract( Addr::unchecked(DAO1), escrow.clone(), - &ExecuteMsg::Fund {}, + &ExecuteMsg::Fund { send_message: None }, &[Coin { amount: Uint128::new(100), denom: "ujuno".to_string(), @@ -977,7 +1223,6 @@ fn test_fund_invalid_amount() { denom: "ujuno".to_string(), amount: Uint128::new(100), }, - send_msg: None, }, counterparty_two: Counterparty { address: DAO2.to_string(), @@ -985,7 +1230,6 @@ fn test_fund_invalid_amount() { contract_addr: cw20.to_string(), amount: Uint128::new(100), }, - send_msg: None, }, }, &[], @@ -1028,7 +1272,7 @@ fn test_fund_invalid_amount() { .execute_contract( Addr::unchecked(DAO1), escrow, - &ExecuteMsg::Fund {}, + &ExecuteMsg::Fund { send_message: None }, &[Coin { amount: Uint128::new(200), denom: "ujuno".to_string(), @@ -1062,7 +1306,6 @@ fn test_fund_invalid_denom() { denom: "ujuno".to_string(), amount: Uint128::new(100), }, - send_msg: None, }, counterparty_two: Counterparty { address: DAO2.to_string(), @@ -1070,7 +1313,6 @@ fn test_fund_invalid_denom() { denom: "uekez".to_string(), amount: Uint128::new(100), }, - send_msg: None, }, }, &[], @@ -1094,7 +1336,7 @@ fn test_fund_invalid_denom() { .execute_contract( Addr::unchecked(DAO1), escrow, - &ExecuteMsg::Fund {}, + &ExecuteMsg::Fund { send_message: None }, &[Coin { amount: Uint128::new(100), denom: "uekez".to_string(), @@ -1167,7 +1409,6 @@ fn test_fund_invalid_cw20() { denom: "ujuno".to_string(), amount: Uint128::new(100), }, - send_msg: None, }, counterparty_two: Counterparty { address: DAO2.to_string(), @@ -1175,7 +1416,6 @@ fn test_fund_invalid_cw20() { contract_addr: cw20.to_string(), amount: Uint128::new(100), }, - send_msg: None, }, }, &[], diff --git a/contracts/external/cw-token-swap/src/types.rs b/contracts/external/cw-token-swap/src/types.rs index e8d0ce57c..1a304cc91 100644 --- a/contracts/external/cw-token-swap/src/types.rs +++ b/contracts/external/cw-token-swap/src/types.rs @@ -50,7 +50,7 @@ impl SendMessage { pub fn into_checked_cosmos_msgs( self, deps: Deps, - other_token_info: CheckedTokenInfo, + token_info: CheckedTokenInfo, ) -> Result, ContractError> { match self { SendMessage::SendCw20 { @@ -61,7 +61,7 @@ impl SendMessage { if let CheckedTokenInfo::Cw20 { contract_addr: cw20_address, amount, - } = other_token_info + } = token_info { Ok(vec![WasmMsg::Execute { contract_addr: cw20_address.to_string(), @@ -74,14 +74,14 @@ impl SendMessage { } .into()]) } else { - return Err(ContractError::InvalidSendMsg {}); + Err(ContractError::InvalidSendMsg {}) } } SendMessage::SendNative { messages } => { if let CheckedTokenInfo::Native { amount: total_amount, denom, - } = other_token_info + } = token_info { if messages.is_empty() { return Err(ContractError::InvalidSendMsg {}); @@ -96,7 +96,7 @@ impl SendMessage { amount: amount_to_pay, } => { total_send_funds = - self.add_to_total(total_send_funds, amount_to_pay, denom)?; + add_to_total(total_send_funds, &amount_to_pay, &denom)?; Ok(BankMsg::Send { to_address: deps.api.addr_validate(&to_address)?.to_string(), @@ -108,7 +108,7 @@ impl SendMessage { amount: amount_to_pay, } => { total_send_funds = - self.add_to_total(total_send_funds, amount_to_pay, denom)?; + add_to_total(total_send_funds, &amount_to_pay, &denom)?; Ok(BankMsg::Burn { amount: amount_to_pay, @@ -121,7 +121,7 @@ impl SendMessage { funds: amount_to_pay, } => { total_send_funds = - self.add_to_total(total_send_funds, amount_to_pay, denom)?; + add_to_total(total_send_funds, &amount_to_pay, &denom)?; Ok(WasmMsg::Execute { contract_addr: deps @@ -141,7 +141,7 @@ impl SendMessage { label, } => { total_send_funds = - self.add_to_total(total_send_funds, amount_to_pay, denom)?; + add_to_total(total_send_funds, &amount_to_pay, &denom)?; Ok(WasmMsg::Instantiate { admin: admin @@ -158,34 +158,83 @@ impl SendMessage { // Make sure that the funds we try to send, matches exactly to the total amount swapped. if total_send_funds != total_amount { - return Err(ContractError::InvalidFunds {}); + return Err(ContractError::WrongFundsCalculation {}); } Ok(cosmos_msgs) } else { - return Err(ContractError::InvalidSendMsg {}); + Err(ContractError::InvalidSendMsg {}) } } } } - /// This function check the given funds to make sure they are valid. - /// and add the amount to the total funds. - /// returns the new total. - pub fn add_to_total( +} + +/// This function check the given funds are valid and adds the amount to the total funds. +/// returns the new total. +pub fn add_to_total( + total_funds: Uint128, + funds: &[Coin], + denom: &str, +) -> Result { + let amount = must_pay( + &MessageInfo { + sender: Addr::unchecked(""), + funds: funds.to_owned(), + }, + denom, + )?; + + Ok(total_funds + amount) +} + +/// Information about a counterparty in this escrow transaction and +/// their promised funds. +#[cw_serde] +pub struct Counterparty { + /// The address of the counterparty. + pub address: String, + /// The funds they have promised to provide. + pub promise: TokenInfo, +} + +impl Counterparty { + pub fn into_checked( self, - total_funds: Uint128, - funds: Vec, - denom: String, - ) -> Result { - let amount = must_pay( - &MessageInfo { - sender: Addr::unchecked(""), - funds, - }, - &denom, - )?; + deps: Deps, + send_msg: Option>, + ) -> Result { + Ok(CheckedCounterparty { + address: deps.api.addr_validate(&self.address)?, + provided: false, + promise: self.promise.into_checked(deps)?, + send_msg, + }) + } +} - Ok(total_funds + amount) +#[cw_serde] +pub struct CheckedCounterparty { + pub address: Addr, + pub promise: CheckedTokenInfo, + pub provided: bool, + pub send_msg: Option>, +} + +impl CheckedCounterparty { + pub fn add_send_msg( + &mut self, + deps: Deps, + send_msg: Option, + ) -> Result<(), ContractError> { + self.send_msg = match send_msg { + Some(msg) => Some(msg.into_checked_cosmos_msgs(deps, self.promise.clone())?), + None => None, + }; + Ok(()) + } + pub fn remove_send_msg(&mut self) { + self.send_msg = None; } } @@ -234,27 +283,6 @@ impl TokenInfo { } } -/// Information about a counterparty in this escrow transaction and -/// their promised funds. -#[cw_serde] -pub struct Counterparty { - /// The address of the counterparty. - pub address: String, - /// The funds they have promised to provide. - pub promise: TokenInfo, -} - -impl Counterparty { - pub fn into_checked(self, deps: Deps, send_msg: Option>) -> Result { - Ok(CheckedCounterparty { - address: deps.api.addr_validate(&self.address)?, - provided: false, - promise: self.promise.into_checked(deps)?, - send_msg, - }) - } -} - #[cw_serde] pub enum CheckedTokenInfo { Native { @@ -267,26 +295,17 @@ pub enum CheckedTokenInfo { }, } -#[cw_serde] -pub struct CheckedCounterparty { - pub address: Addr, - pub promise: CheckedTokenInfo, - pub provided: bool, - pub send_msg: Option> -} - impl CheckedTokenInfo { pub fn into_send_message( self, - deps: Deps, - other_counterparty: &CheckedCounterparty, + recipient_counterparty: &CheckedCounterparty, send_msg: Option>, ) -> Result, StdError> { Ok(match self { Self::Native { denom, amount } => match send_msg { Some(msgs) => msgs, None => vec![BankMsg::Send { - to_address: other_counterparty.address.to_string(), + to_address: recipient_counterparty.address.to_string(), amount: vec![Coin { denom, amount }], } .into()], @@ -299,7 +318,7 @@ impl CheckedTokenInfo { None => vec![WasmMsg::Execute { contract_addr: contract_addr.into_string(), msg: to_binary(&cw20::Cw20ExecuteMsg::Transfer { - recipient: other_counterparty.address.to_string(), + recipient: recipient_counterparty.address.to_string(), amount, })?, funds: vec![], @@ -320,10 +339,23 @@ mod tests { amount: Uint128::new(100), denom: "uekez".to_string(), }; - let message = info.into_send_message(&Addr::unchecked("ekez")).unwrap(); + let message = info + .into_send_message( + &CheckedCounterparty { + address: Addr::unchecked("ekez"), + promise: CheckedTokenInfo::Native { + amount: Uint128::new(100), + denom: "uekez".to_string(), + }, + provided: false, + send_msg: None, + }, + None, + ) + .unwrap(); assert_eq!( - message, + message[0], CosmosMsg::Bank(BankMsg::Send { to_address: "ekez".to_string(), amount: vec![Coin { @@ -340,10 +372,23 @@ mod tests { amount: Uint128::new(100), contract_addr: Addr::unchecked("ekez_token"), }; - let message = info.into_send_message(&Addr::unchecked("ekez")).unwrap(); + let message = info + .into_send_message( + &CheckedCounterparty { + address: Addr::unchecked("ekez"), + promise: CheckedTokenInfo::Native { + amount: Uint128::new(100), + denom: "uekez".to_string(), + }, + provided: false, + send_msg: None, + }, + None, + ) + .unwrap(); assert_eq!( - message, + message[0], CosmosMsg::Wasm(WasmMsg::Execute { funds: vec![], contract_addr: "ekez_token".to_string(), diff --git a/typescript/contracts/cw-token-swap/CwTokenSwap.client.ts b/typescript/contracts/cw-token-swap/CwTokenSwap.client.ts index 65dbb3aae..ed803b663 100644 --- a/typescript/contracts/cw-token-swap/CwTokenSwap.client.ts +++ b/typescript/contracts/cw-token-swap/CwTokenSwap.client.ts @@ -5,8 +5,8 @@ */ import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from "@cosmjs/cosmwasm-stargate"; -import { Coin, StdFee } from "@cosmjs/amino"; -import { TokenInfo, Uint128, InstantiateMsg, Counterparty, ExecuteMsg, Binary, Cw20ReceiveMsg, QueryMsg, MigrateMsg, Addr, CheckedTokenInfo, StatusResponse, CheckedCounterparty } from "./CwTokenSwap.types"; +import { StdFee } from "@cosmjs/amino"; +import { TokenInfo, Uint128, InstantiateMsg, Counterparty, ExecuteMsg, Binary, SendMessage, AcceptedMessages, Cw20ReceiveMsg, Coin, QueryMsg, MigrateMsg, Addr, CheckedTokenInfo, CosmosMsgForEmpty, BankMsg, StakingMsg, DistributionMsg, IbcMsg, Timestamp, Uint64, WasmMsg, GovMsg, VoteOption, StatusResponse, CheckedCounterparty, Empty, IbcTimeout, IbcTimeoutBlock } from "./CwTokenSwap.types"; export interface CwTokenSwapReadOnlyInterface { contractAddress: string; status: () => Promise; @@ -39,7 +39,11 @@ export interface CwTokenSwapInterface extends CwTokenSwapReadOnlyInterface { msg: Binary; sender: string; }, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]) => Promise; - fund: (fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]) => Promise; + fund: ({ + sendMessage + }: { + sendMessage?: SendMessage; + }, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]) => Promise; withdraw: (fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]) => Promise; } export class CwTokenSwapClient extends CwTokenSwapQueryClient implements CwTokenSwapInterface { @@ -74,9 +78,15 @@ export class CwTokenSwapClient extends CwTokenSwapQueryClient implements CwToken } }, fee, memo, funds); }; - fund = async (fee: number | StdFee | "auto" = "auto", memo?: string, funds?: Coin[]): Promise => { + fund = async ({ + sendMessage + }: { + sendMessage?: SendMessage; + }, fee: number | StdFee | "auto" = "auto", memo?: string, funds?: Coin[]): Promise => { return await this.client.execute(this.sender, this.contractAddress, { - fund: {} + fund: { + send_message: sendMessage + } }, fee, memo, funds); }; withdraw = async (fee: number | StdFee | "auto" = "auto", memo?: string, funds?: Coin[]): Promise => { diff --git a/typescript/contracts/cw-token-swap/CwTokenSwap.types.ts b/typescript/contracts/cw-token-swap/CwTokenSwap.types.ts index 68256e008..1c6d11492 100644 --- a/typescript/contracts/cw-token-swap/CwTokenSwap.types.ts +++ b/typescript/contracts/cw-token-swap/CwTokenSwap.types.ts @@ -27,16 +27,57 @@ export interface Counterparty { export type ExecuteMsg = { receive: Cw20ReceiveMsg; } | { - fund: {}; + fund: { + send_message?: SendMessage | null; + }; } | { withdraw: {}; }; export type Binary = string; +export type SendMessage = { + send_cw20: { + contract_address: string; + message: Binary; + }; +} | { + send_native: { + messages: AcceptedMessages[]; + }; +}; +export type AcceptedMessages = { + bank_send: { + amount: Coin[]; + to_address: string; + }; +} | { + bank_burn: { + amount: Coin[]; + }; +} | { + wasm_execute: { + contract_addr: string; + funds: Coin[]; + msg: Binary; + }; +} | { + wasm_instantiate: { + admin?: string | null; + code_id: number; + funds: Coin[]; + label: string; + msg: Binary; + }; +}; export interface Cw20ReceiveMsg { amount: Uint128; msg: Binary; sender: string; } +export interface Coin { + amount: Uint128; + denom: string; + [k: string]: unknown; +} export type QueryMsg = { status: {}; }; @@ -53,6 +94,136 @@ export type CheckedTokenInfo = { contract_addr: Addr; }; }; +export type CosmosMsgForEmpty = { + bank: BankMsg; +} | { + custom: Empty; +} | { + staking: StakingMsg; +} | { + distribution: DistributionMsg; +} | { + stargate: { + type_url: string; + value: Binary; + [k: string]: unknown; + }; +} | { + ibc: IbcMsg; +} | { + wasm: WasmMsg; +} | { + gov: GovMsg; +}; +export type BankMsg = { + send: { + amount: Coin[]; + to_address: string; + [k: string]: unknown; + }; +} | { + burn: { + amount: Coin[]; + [k: string]: unknown; + }; +}; +export type StakingMsg = { + delegate: { + amount: Coin; + validator: string; + [k: string]: unknown; + }; +} | { + undelegate: { + amount: Coin; + validator: string; + [k: string]: unknown; + }; +} | { + redelegate: { + amount: Coin; + dst_validator: string; + src_validator: string; + [k: string]: unknown; + }; +}; +export type DistributionMsg = { + set_withdraw_address: { + address: string; + [k: string]: unknown; + }; +} | { + withdraw_delegator_reward: { + validator: string; + [k: string]: unknown; + }; +}; +export type IbcMsg = { + transfer: { + amount: Coin; + channel_id: string; + timeout: IbcTimeout; + to_address: string; + [k: string]: unknown; + }; +} | { + send_packet: { + channel_id: string; + data: Binary; + timeout: IbcTimeout; + [k: string]: unknown; + }; +} | { + close_channel: { + channel_id: string; + [k: string]: unknown; + }; +}; +export type Timestamp = Uint64; +export type Uint64 = string; +export type WasmMsg = { + execute: { + contract_addr: string; + funds: Coin[]; + msg: Binary; + [k: string]: unknown; + }; +} | { + instantiate: { + admin?: string | null; + code_id: number; + funds: Coin[]; + label: string; + msg: Binary; + [k: string]: unknown; + }; +} | { + migrate: { + contract_addr: string; + msg: Binary; + new_code_id: number; + [k: string]: unknown; + }; +} | { + update_admin: { + admin: string; + contract_addr: string; + [k: string]: unknown; + }; +} | { + clear_admin: { + contract_addr: string; + [k: string]: unknown; + }; +}; +export type GovMsg = { + vote: { + proposal_id: number; + vote: VoteOption; + [k: string]: unknown; + }; +}; +export type VoteOption = "yes" | "no" | "abstain" | "no_with_veto"; export interface StatusResponse { counterparty_one: CheckedCounterparty; counterparty_two: CheckedCounterparty; @@ -61,4 +232,18 @@ export interface CheckedCounterparty { address: Addr; promise: CheckedTokenInfo; provided: boolean; + send_msg?: CosmosMsgForEmpty[] | null; +} +export interface Empty { + [k: string]: unknown; +} +export interface IbcTimeout { + block?: IbcTimeoutBlock | null; + timestamp?: Timestamp | null; + [k: string]: unknown; +} +export interface IbcTimeoutBlock { + height: number; + revision: number; + [k: string]: unknown; } \ No newline at end of file From 2b79538318b687fd146eb10d118400de12fd5b45 Mon Sep 17 00:00:00 2001 From: Art3mix Date: Thu, 9 Mar 2023 13:53:21 +0200 Subject: [PATCH 04/11] fixes --- contracts/external/cw-token-swap/src/contract.rs | 14 ++------------ contracts/external/cw-token-swap/src/error.rs | 2 +- contracts/external/cw-token-swap/src/msg.rs | 2 -- contracts/external/cw-token-swap/src/tests.rs | 3 --- contracts/external/cw-token-swap/src/types.rs | 3 +-- 5 files changed, 4 insertions(+), 20 deletions(-) diff --git a/contracts/external/cw-token-swap/src/contract.rs b/contracts/external/cw-token-swap/src/contract.rs index a4bc390df..8cb2a9608 100644 --- a/contracts/external/cw-token-swap/src/contract.rs +++ b/contracts/external/cw-token-swap/src/contract.rs @@ -27,8 +27,8 @@ pub fn instantiate( ) -> Result { set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - let counterparty_one = msg.counterparty_one.into_checked(deps.as_ref(), None)?; - let counterparty_two = msg.counterparty_two.into_checked(deps.as_ref(), None)?; + let counterparty_one = msg.counterparty_one.into_checked(deps.as_ref())?; + let counterparty_two = msg.counterparty_two.into_checked(deps.as_ref())?; if counterparty_one.address == counterparty_two.address { return Err(ContractError::NonDistinctCounterparties {}); @@ -191,18 +191,8 @@ pub fn execute_receive( if let Ok(msg) = from_binary::(&msg.msg) { match msg { Cw20RecieveMsg::FundWithMsgs { - amount, send_message, } => { - if let CheckedTokenInfo::Cw20 { - amount: promised_amount, - .. - } = other_counterparty.promise - { - if promised_amount != amount { - return Err(ContractError::WrongFundsCalculation {}); - } - }; send_msg = Some(send_message); } } diff --git a/contracts/external/cw-token-swap/src/error.rs b/contracts/external/cw-token-swap/src/error.rs index a8f50612d..0eee9888b 100644 --- a/contracts/external/cw-token-swap/src/error.rs +++ b/contracts/external/cw-token-swap/src/error.rs @@ -32,7 +32,7 @@ pub enum ContractError { #[error("Provided funds do not match promised funds")] InvalidFunds {}, - #[error("You are trying to send more funds then you have")] + #[error("You are trying to send more funds then promised")] WrongFundsCalculation {}, #[error("Send message doesn't match the other party token type")] diff --git a/contracts/external/cw-token-swap/src/msg.rs b/contracts/external/cw-token-swap/src/msg.rs index dbdc191a6..a2714eb64 100644 --- a/contracts/external/cw-token-swap/src/msg.rs +++ b/contracts/external/cw-token-swap/src/msg.rs @@ -1,5 +1,4 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::Uint128; use crate::types::{CheckedCounterparty, Counterparty, SendMessage}; @@ -40,7 +39,6 @@ pub struct MigrateMsg {} #[cw_serde] pub enum Cw20RecieveMsg { FundWithMsgs { - amount: Uint128, send_message: SendMessage, }, } diff --git a/contracts/external/cw-token-swap/src/tests.rs b/contracts/external/cw-token-swap/src/tests.rs index 29f847cd9..b68c0cf47 100644 --- a/contracts/external/cw-token-swap/src/tests.rs +++ b/contracts/external/cw-token-swap/src/tests.rs @@ -250,7 +250,6 @@ fn test_simple_with_send_messages() { contract: escrow.to_string(), amount: Uint128::new(100), msg: to_binary(&Cw20RecieveMsg::FundWithMsgs { - amount: Uint128::new(200), send_message: SendMessage::SendNative { messages: vec![AcceptedMessages::WasmInstantiate { admin: None, @@ -414,7 +413,6 @@ fn test_multiple_send_messages() { contract: escrow.to_string(), amount: Uint128::new(100), msg: to_binary(&Cw20RecieveMsg::FundWithMsgs { - amount: Uint128::new(200), send_message: SendMessage::SendNative { messages: vec![ AcceptedMessages::BankBurn { @@ -524,7 +522,6 @@ fn test_send_messages_incomplete_funds() { contract: escrow.to_string(), amount: Uint128::new(100), msg: to_binary(&Cw20RecieveMsg::FundWithMsgs { - amount: Uint128::new(200), send_message: SendMessage::SendNative { messages: vec![AcceptedMessages::BankBurn { amount: coins(100, "ujuno"), diff --git a/contracts/external/cw-token-swap/src/types.rs b/contracts/external/cw-token-swap/src/types.rs index 1a304cc91..57870ceb1 100644 --- a/contracts/external/cw-token-swap/src/types.rs +++ b/contracts/external/cw-token-swap/src/types.rs @@ -202,13 +202,12 @@ impl Counterparty { pub fn into_checked( self, deps: Deps, - send_msg: Option>, ) -> Result { Ok(CheckedCounterparty { address: deps.api.addr_validate(&self.address)?, provided: false, promise: self.promise.into_checked(deps)?, - send_msg, + send_msg: None, }) } } From c9d66a6d56fa14c55184cad1c210b4c041dce0b0 Mon Sep 17 00:00:00 2001 From: Art3mix Date: Thu, 9 Mar 2023 14:26:15 +0200 Subject: [PATCH 05/11] lint --- contracts/external/cw-token-swap/src/contract.rs | 4 +--- contracts/external/cw-token-swap/src/msg.rs | 4 +--- contracts/external/cw-token-swap/src/types.rs | 5 +---- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/contracts/external/cw-token-swap/src/contract.rs b/contracts/external/cw-token-swap/src/contract.rs index 8cb2a9608..134b0203a 100644 --- a/contracts/external/cw-token-swap/src/contract.rs +++ b/contracts/external/cw-token-swap/src/contract.rs @@ -190,9 +190,7 @@ pub fn execute_receive( let mut send_msg = None; if let Ok(msg) = from_binary::(&msg.msg) { match msg { - Cw20RecieveMsg::FundWithMsgs { - send_message, - } => { + Cw20RecieveMsg::FundWithMsgs { send_message } => { send_msg = Some(send_message); } } diff --git a/contracts/external/cw-token-swap/src/msg.rs b/contracts/external/cw-token-swap/src/msg.rs index a2714eb64..df01ff021 100644 --- a/contracts/external/cw-token-swap/src/msg.rs +++ b/contracts/external/cw-token-swap/src/msg.rs @@ -38,7 +38,5 @@ pub struct MigrateMsg {} #[cw_serde] pub enum Cw20RecieveMsg { - FundWithMsgs { - send_message: SendMessage, - }, + FundWithMsgs { send_message: SendMessage }, } diff --git a/contracts/external/cw-token-swap/src/types.rs b/contracts/external/cw-token-swap/src/types.rs index 57870ceb1..4d9f0fee5 100644 --- a/contracts/external/cw-token-swap/src/types.rs +++ b/contracts/external/cw-token-swap/src/types.rs @@ -199,10 +199,7 @@ pub struct Counterparty { } impl Counterparty { - pub fn into_checked( - self, - deps: Deps, - ) -> Result { + pub fn into_checked(self, deps: Deps) -> Result { Ok(CheckedCounterparty { address: deps.api.addr_validate(&self.address)?, provided: false, From 229b2b2b7e72e897a7946548d0c79b8402ba6c75 Mon Sep 17 00:00:00 2001 From: Art3mix Date: Fri, 10 Mar 2023 22:39:12 +0200 Subject: [PATCH 06/11] new strategy --- .../cw-token-swap/schema/cw-token-swap.json | 329 ++++++------ .../external/cw-token-swap/src/contract.rs | 84 +-- contracts/external/cw-token-swap/src/msg.rs | 9 +- contracts/external/cw-token-swap/src/tests.rs | 309 ++++++----- contracts/external/cw-token-swap/src/types.rs | 479 +++++++++--------- 5 files changed, 595 insertions(+), 615 deletions(-) diff --git a/contracts/external/cw-token-swap/schema/cw-token-swap.json b/contracts/external/cw-token-swap/schema/cw-token-swap.json index 56a99f6c5..7693ac5ff 100644 --- a/contracts/external/cw-token-swap/schema/cw-token-swap.json +++ b/contracts/external/cw-token-swap/schema/cw-token-swap.json @@ -20,6 +20,25 @@ }, "additionalProperties": false, "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, "Counterparty": { "description": "Information about a counterparty in this escrow transaction and their promised funds.", "type": "object", @@ -36,35 +55,37 @@ "description": "The funds they have promised to provide.", "allOf": [ { - "$ref": "#/definitions/TokenInfo" + "$ref": "#/definitions/SwapInfo" } ] } }, "additionalProperties": false }, - "TokenInfo": { - "description": "Information about the token being used on one side of the escrow.", + "Cw20SendMsgs": { "oneOf": [ { - "description": "A native token.", "type": "object", "required": [ - "native" + "cw20_send" ], "properties": { - "native": { + "cw20_send": { "type": "object", "required": [ "amount", - "denom" + "contract", + "msg" ], "properties": { "amount": { "$ref": "#/definitions/Uint128" }, - "denom": { + "contract": { "type": "string" + }, + "msg": { + "$ref": "#/definitions/Binary" } }, "additionalProperties": false @@ -73,99 +94,54 @@ "additionalProperties": false }, { - "description": "A cw20 token.", "type": "object", "required": [ - "cw20" + "cw20_burn" ], "properties": { - "cw20": { + "cw20_burn": { "type": "object", "required": [ - "amount", - "contract_addr" + "amount" ], "properties": { "amount": { "$ref": "#/definitions/Uint128" - }, - "contract_addr": { - "type": "string" } }, "additionalProperties": false } }, "additionalProperties": false - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, - "execute": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "oneOf": [ - { - "description": "Used to provide cw20 tokens to satisfy a funds promise.", - "type": "object", - "required": [ - "receive" - ], - "properties": { - "receive": { - "$ref": "#/definitions/Cw20ReceiveMsg" - } - }, - "additionalProperties": false - }, - { - "description": "Provides native tokens to satisfy a funds promise.", - "type": "object", - "required": [ - "fund" - ], - "properties": { - "fund": { + }, + { "type": "object", + "required": [ + "cw20_transfer" + ], "properties": { - "send_message": { - "anyOf": [ - { - "$ref": "#/definitions/SendMessage" + "cw20_transfer": { + "type": "object", + "required": [ + "amount", + "recipient" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" }, - { - "type": "null" + "recipient": { + "type": "string" } - ] + }, + "additionalProperties": false } }, "additionalProperties": false } - }, - "additionalProperties": false + ] }, - { - "description": "Withdraws provided funds. Only allowed if the other counterparty has yet to provide their promised funds.", - "type": "object", - "required": [ - "withdraw" - ], - "properties": { - "withdraw": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ], - "definitions": { - "AcceptedMessages": { + "NativeSendMsg": { "oneOf": [ { "type": "object", @@ -297,73 +273,35 @@ } ] }, - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", - "type": "string" - }, - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "Cw20ReceiveMsg": { - "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", - "type": "object", - "required": [ - "amount", - "msg", - "sender" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "msg": { - "$ref": "#/definitions/Binary" - }, - "sender": { - "type": "string" - } - }, - "additionalProperties": false - }, - "SendMessage": { - "description": "Enum to accept either a cosmos msg (for recieved native tokens) or address and binary message (for recieved cw20 tokens)", + "SwapInfo": { + "description": "Information about the token being used on one side of the escrow.", "oneOf": [ { + "description": "A native token.", "type": "object", "required": [ - "send_cw20" + "native" ], "properties": { - "send_cw20": { + "native": { "type": "object", "required": [ - "contract_address", - "message" + "amount", + "denom", + "on_completion" ], "properties": { - "contract_address": { - "description": "Contract address to execute the msg on", + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { "type": "string" }, - "message": { - "description": "The message in binary format", - "allOf": [ - { - "$ref": "#/definitions/Binary" - } - ] + "on_completion": { + "type": "array", + "items": { + "$ref": "#/definitions/NativeSendMsg" + } } }, "additionalProperties": false @@ -372,22 +310,30 @@ "additionalProperties": false }, { + "description": "A cw20 token.", "type": "object", "required": [ - "send_native" + "cw20" ], "properties": { - "send_native": { + "cw20": { "type": "object", "required": [ - "messages" + "amount", + "contract_addr", + "on_completion" ], "properties": { - "messages": { - "description": "Vector of accepted messages to send", + "amount": { + "$ref": "#/definitions/Uint128" + }, + "contract_addr": { + "type": "string" + }, + "on_completion": { "type": "array", "items": { - "$ref": "#/definitions/AcceptedMessages" + "$ref": "#/definitions/Cw20SendMsgs" } } }, @@ -404,6 +350,84 @@ } } }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "Used to provide cw20 tokens to satisfy a funds promise.", + "type": "object", + "required": [ + "receive" + ], + "properties": { + "receive": { + "$ref": "#/definitions/Cw20ReceiveMsg" + } + }, + "additionalProperties": false + }, + { + "description": "Provides native tokens to satisfy a funds promise.", + "type": "object", + "required": [ + "fund" + ], + "properties": { + "fund": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Withdraws provided funds. Only allowed if the other counterparty has yet to provide their promised funds.", + "type": "object", + "required": [ + "withdraw" + ], + "properties": { + "withdraw": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Cw20ReceiveMsg": { + "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", + "type": "object", + "required": [ + "amount", + "msg", + "sender" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "msg": { + "$ref": "#/definitions/Binary" + }, + "sender": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, "query": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "QueryMsg", @@ -526,24 +550,15 @@ "$ref": "#/definitions/Addr" }, "promise": { - "$ref": "#/definitions/CheckedTokenInfo" + "$ref": "#/definitions/CheckedSwapInfo" }, "provided": { "type": "boolean" - }, - "send_msg": { - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/CosmosMsg_for_Empty" - } } }, "additionalProperties": false }, - "CheckedTokenInfo": { + "CheckedSwapInfo": { "oneOf": [ { "type": "object", @@ -555,7 +570,8 @@ "type": "object", "required": [ "amount", - "denom" + "denom", + "on_completion" ], "properties": { "amount": { @@ -563,6 +579,12 @@ }, "denom": { "type": "string" + }, + "on_completion": { + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_Empty" + } } }, "additionalProperties": false @@ -580,7 +602,8 @@ "type": "object", "required": [ "amount", - "contract_addr" + "contract_addr", + "on_completion" ], "properties": { "amount": { @@ -588,6 +611,12 @@ }, "contract_addr": { "$ref": "#/definitions/Addr" + }, + "on_completion": { + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_Empty" + } } }, "additionalProperties": false diff --git a/contracts/external/cw-token-swap/src/contract.rs b/contracts/external/cw-token-swap/src/contract.rs index 134b0203a..eab4a3d0d 100644 --- a/contracts/external/cw-token-swap/src/contract.rs +++ b/contracts/external/cw-token-swap/src/contract.rs @@ -1,8 +1,7 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - from_binary, to_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, - Uint128, + to_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, Uint128, }; use cw2::set_contract_version; use cw_storage_plus::Item; @@ -10,9 +9,9 @@ use cw_utils::must_pay; use crate::{ error::ContractError, - msg::{Cw20RecieveMsg, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, StatusResponse}, + msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, StatusResponse}, state::{COUNTERPARTY_ONE, COUNTERPARTY_TWO}, - types::{CheckedCounterparty, CheckedTokenInfo, SendMessage}, + types::{CheckedCounterparty, CheckedSwapInfo}, }; pub(crate) const CONTRACT_NAME: &str = "crates.io:cw-token-swap"; @@ -52,7 +51,7 @@ pub fn execute( ) -> Result { match msg { ExecuteMsg::Receive(msg) => execute_receive(deps, info.sender, msg), - ExecuteMsg::Fund { send_message } => execute_fund(deps, info, send_message), + ExecuteMsg::Fund {} => execute_fund(deps, info), ExecuteMsg::Withdraw {} => execute_withdraw(deps, info), } } @@ -61,7 +60,6 @@ struct CounterpartyResponse<'a> { pub counterparty: CheckedCounterparty, pub other_counterparty: CheckedCounterparty, pub storage: Item<'a, CheckedCounterparty>, - pub other_storage: Item<'a, CheckedCounterparty>, } fn get_counterparty<'a>( @@ -71,31 +69,19 @@ fn get_counterparty<'a>( let counterparty_one = COUNTERPARTY_ONE.load(deps.storage)?; let counterparty_two = COUNTERPARTY_TWO.load(deps.storage)?; - let (counterparty, other_counterparty, storage, other_storage) = - if *sender == counterparty_one.address { - ( - counterparty_one, - counterparty_two, - COUNTERPARTY_ONE, - COUNTERPARTY_TWO, - ) - } else if *sender == counterparty_two.address { - ( - counterparty_two, - counterparty_one, - COUNTERPARTY_TWO, - COUNTERPARTY_ONE, - ) - } else { - // Contract may only be funded by a counterparty. - return Err(ContractError::Unauthorized {}); - }; + let (counterparty, other_counterparty, storage) = if *sender == counterparty_one.address { + (counterparty_one, counterparty_two, COUNTERPARTY_ONE) + } else if *sender == counterparty_two.address { + (counterparty_two, counterparty_one, COUNTERPARTY_TWO) + } else { + // Contract may only be funded by a counterparty. + return Err(ContractError::Unauthorized {}); + }; Ok(CounterpartyResponse { counterparty, other_counterparty, storage, - other_storage, }) } @@ -110,9 +96,7 @@ fn do_fund( paid: Uint128, expected: Uint128, other_counterparty: CheckedCounterparty, - send_message: Option, storage: Item, - other_storage: Item, ) -> Result { if counterparty.provided { return Err(ContractError::AlreadyProvided {}); @@ -129,23 +113,16 @@ fn do_fund( counterparty.provided = true; storage.save(deps.storage, &counterparty)?; - // We add the send message to the other counterparty - // Because the send message is based on the promised token - // so if I promise cw20 token, it will send cw20 msg - let mut other_counterparty = other_counterparty; - other_counterparty.add_send_msg(deps.as_ref(), send_message)?; - other_storage.save(deps.storage, &other_counterparty)?; - let messages = if counterparty.provided && other_counterparty.provided { let mut msgs = counterparty .promise .clone() - .into_send_message(&other_counterparty, counterparty.send_msg.clone())?; + .into_send_message(other_counterparty.address.to_string())?; msgs.append( &mut other_counterparty .promise - .into_send_message(&counterparty, other_counterparty.send_msg)?, + .into_send_message(counterparty.address.to_string())?, ); msgs } else { @@ -169,12 +146,12 @@ pub fn execute_receive( counterparty, other_counterparty, storage, - other_storage, } = get_counterparty(deps.as_ref(), &sender)?; - let (expected_payment, paid) = if let CheckedTokenInfo::Cw20 { + let (expected_payment, paid) = if let CheckedSwapInfo::Cw20 { contract_addr, amount, + .. } = &counterparty.promise { if *contract_addr != token_contract { @@ -187,40 +164,24 @@ pub fn execute_receive( return Err(ContractError::InvalidFunds {}); }; - let mut send_msg = None; - if let Ok(msg) = from_binary::(&msg.msg) { - match msg { - Cw20RecieveMsg::FundWithMsgs { send_message } => { - send_msg = Some(send_message); - } - } - }; - do_fund( deps, counterparty, paid, expected_payment, other_counterparty, - send_msg, storage, - other_storage, ) } -pub fn execute_fund( - deps: DepsMut, - info: MessageInfo, - send_msg: Option, -) -> Result { +pub fn execute_fund(deps: DepsMut, info: MessageInfo) -> Result { let CounterpartyResponse { counterparty, other_counterparty, storage, - other_storage, } = get_counterparty(deps.as_ref(), &info.sender)?; let (expected_payment, paid) = - if let CheckedTokenInfo::Native { amount, denom } = &counterparty.promise { + if let CheckedSwapInfo::Native { amount, denom, .. } = &counterparty.promise { let paid = must_pay(&info, denom).map_err(|_| ContractError::InvalidFunds {})?; (*amount, paid) @@ -234,9 +195,7 @@ pub fn execute_fund( paid, expected_payment, other_counterparty, - send_msg, storage, - other_storage, ) } @@ -245,7 +204,6 @@ pub fn execute_withdraw(deps: DepsMut, info: MessageInfo) -> Result Result }, + Fund {}, /// Withdraws provided funds. Only allowed if the other /// counterparty has yet to provide their promised funds. Withdraw {}, @@ -35,8 +35,3 @@ pub struct StatusResponse { #[cw_serde] pub struct MigrateMsg {} - -#[cw_serde] -pub enum Cw20RecieveMsg { - FundWithMsgs { send_message: SendMessage }, -} diff --git a/contracts/external/cw-token-swap/src/tests.rs b/contracts/external/cw-token-swap/src/tests.rs index b68c0cf47..95ea36535 100644 --- a/contracts/external/cw-token-swap/src/tests.rs +++ b/contracts/external/cw-token-swap/src/tests.rs @@ -8,10 +8,9 @@ use cw_multi_test::{App, BankSudo, Contract, ContractWrapper, Executor, SudoMsg} use crate::{ contract::{migrate, CONTRACT_NAME, CONTRACT_VERSION}, - msg::{Cw20RecieveMsg, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, StatusResponse}, + msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, StatusResponse}, types::{ - AcceptedMessages, CheckedCounterparty, CheckedTokenInfo, Counterparty, SendMessage, - TokenInfo, + CheckedCounterparty, CheckedSwapInfo, Counterparty, Cw20SendMsgs, NativeSendMsg, SwapInfo, }, ContractError, }; @@ -81,16 +80,18 @@ fn test_simple_escrow() { &InstantiateMsg { counterparty_one: Counterparty { address: DAO1.to_string(), - promise: TokenInfo::Native { + promise: SwapInfo::Native { denom: "ujuno".to_string(), amount: Uint128::new(100), + on_completion: vec![], }, }, counterparty_two: Counterparty { address: DAO2.to_string(), - promise: TokenInfo::Cw20 { + promise: SwapInfo::Cw20 { contract_addr: cw20.to_string(), amount: Uint128::new(100), + on_completion: vec![], }, }, }, @@ -124,7 +125,7 @@ fn test_simple_escrow() { app.execute_contract( Addr::unchecked(DAO1), escrow, - &ExecuteMsg::Fund { send_message: None }, + &ExecuteMsg::Fund {}, &[Coin { amount: Uint128::new(100), denom: "ujuno".to_string(), @@ -196,42 +197,27 @@ fn test_simple_with_send_messages() { &InstantiateMsg { counterparty_one: Counterparty { address: DAO1.to_string(), - promise: TokenInfo::Native { + promise: SwapInfo::Native { denom: "ujuno".to_string(), amount: Uint128::new(200), + on_completion: vec![NativeSendMsg::WasmInstantiate { + admin: None, + code_id: vesting_code, + msg: to_binary(&vetsing_init_msg).unwrap(), + funds: coins(200, "ujuno"), + label: "some vesting".to_string(), + }], }, }, counterparty_two: Counterparty { address: DAO2.to_string(), - promise: TokenInfo::Cw20 { - contract_addr: cw20.to_string(), - amount: Uint128::new(100), - }, - }, - }, - &[], - "escrow", - None, - ) - .unwrap(); - - let some_other_escrow = app - .instantiate_contract( - escrow_code, - Addr::unchecked(DAO1), - &InstantiateMsg { - counterparty_one: Counterparty { - address: DAO2.to_string(), - promise: TokenInfo::Native { - denom: "ujuno".to_string(), - amount: Uint128::new(100), - }, - }, - counterparty_two: Counterparty { - address: escrow.to_string(), - promise: TokenInfo::Cw20 { + promise: SwapInfo::Cw20 { contract_addr: cw20.to_string(), amount: Uint128::new(100), + on_completion: vec![Cw20SendMsgs::Cw20Transfer { + recipient: "some_random".to_string(), + amount: Uint128::new(100), + }], }, }, }, @@ -249,18 +235,7 @@ fn test_simple_with_send_messages() { &cw20::Cw20ExecuteMsg::Send { contract: escrow.to_string(), amount: Uint128::new(100), - msg: to_binary(&Cw20RecieveMsg::FundWithMsgs { - send_message: SendMessage::SendNative { - messages: vec![AcceptedMessages::WasmInstantiate { - admin: None, - code_id: vesting_code, - msg: to_binary(&vetsing_init_msg).unwrap(), - funds: coins(200, "ujuno"), - label: "some vesting".to_string(), - }], - }, - }) - .unwrap(), + msg: to_binary("").unwrap(), }, &[], ) @@ -275,17 +250,12 @@ fn test_simple_with_send_messages() { })) .unwrap(); - // We recieve 100 cw20 token, just for fun, im trying to fund a differetn swap with this swap + // We recieve 100 cw20 token, just for fun, im trying to fund a different swap with this swap // So once this swap is done, I can fund the other swap with the 50 cw20 tokens app.execute_contract( Addr::unchecked(DAO1), escrow, - &ExecuteMsg::Fund { - send_message: Some(SendMessage::SendCw20 { - contract_address: some_other_escrow.to_string(), - message: to_binary("").unwrap(), - }), - }, + &ExecuteMsg::Fund {}, &[Coin { amount: Uint128::new(200), denom: "ujuno".to_string(), @@ -309,21 +279,16 @@ fn test_simple_with_send_messages() { // Lets make sure the other escrow was funded correctly // provided is true and the cw20 balance is 100 - let other_escrow_status: StatusResponse = app - .wrap() - .query_wasm_smart(some_other_escrow.clone(), &QueryMsg::Status {}) - .unwrap(); - let other_escrow_cw20_balance: cw20::BalanceResponse = app + let random_cw20_balance: cw20::BalanceResponse = app .wrap() .query_wasm_smart( cw20, &cw20::Cw20QueryMsg::Balance { - address: some_other_escrow.to_string(), + address: "some_random".to_string(), }, ) .unwrap(); - assert!(other_escrow_status.counterparty_two.provided); - assert_eq!(other_escrow_cw20_balance.balance, Uint128::new(100)); + assert_eq!(random_cw20_balance.balance, Uint128::new(100)); // Make sure that DAO1 native balance is 0 (sent to the vesting contract) let dao1_balance = app.wrap().query_balance(DAO1.to_string(), "ujuno").unwrap(); @@ -347,7 +312,7 @@ fn test_multiple_send_messages() { decimals: 6, initial_balances: vec![Cw20Coin { address: DAO2.to_string(), - amount: Uint128::new(100), + amount: Uint128::new(200), }], mint: None, marketing: None, @@ -365,16 +330,26 @@ fn test_multiple_send_messages() { &InstantiateMsg { counterparty_one: Counterparty { address: DAO1.to_string(), - promise: TokenInfo::Native { + promise: SwapInfo::Native { denom: "ujuno".to_string(), amount: Uint128::new(200), + on_completion: vec![], }, }, counterparty_two: Counterparty { address: DAO2.to_string(), - promise: TokenInfo::Cw20 { + promise: SwapInfo::Cw20 { contract_addr: cw20.to_string(), - amount: Uint128::new(100), + amount: Uint128::new(200), + on_completion: vec![ + Cw20SendMsgs::Cw20Transfer { + recipient: "some_random".to_string(), + amount: Uint128::new(100), + }, + Cw20SendMsgs::Cw20Burn { + amount: Uint128::new(100), + }, + ], }, }, }, @@ -393,12 +368,10 @@ fn test_multiple_send_messages() { })) .unwrap(); - // We recieve 100 cw20 token, just for fun, im trying to fund a differetn swap with this swap - // So once this swap is done, I can fund the other swap with the 50 cw20 tokens app.execute_contract( Addr::unchecked(DAO1), escrow.clone(), - &ExecuteMsg::Fund { send_message: None }, + &ExecuteMsg::Fund {}, &[Coin { amount: Uint128::new(200), denom: "ujuno".to_string(), @@ -408,34 +381,26 @@ fn test_multiple_send_messages() { app.execute_contract( Addr::unchecked(DAO2), - cw20, + cw20.clone(), &cw20::Cw20ExecuteMsg::Send { contract: escrow.to_string(), - amount: Uint128::new(100), - msg: to_binary(&Cw20RecieveMsg::FundWithMsgs { - send_message: SendMessage::SendNative { - messages: vec![ - AcceptedMessages::BankBurn { - amount: coins(100, "ujuno"), - }, - AcceptedMessages::BankSend { - to_address: "some_random".to_string(), - amount: coins(100, "ujuno"), - }, - ], - }, - }) - .unwrap(), + amount: Uint128::new(200), + msg: to_binary("").unwrap(), }, &[], ) .unwrap(); - let some_random_balance = app + let some_random_balance: cw20::BalanceResponse = app .wrap() - .query_balance("some_random".to_string(), "ujuno") + .query_wasm_smart( + cw20, + &cw20::Cw20QueryMsg::Balance { + address: "some_random".to_string(), + }, + ) .unwrap(); - assert_eq!(some_random_balance.amount, Uint128::new(100)); + assert_eq!(some_random_balance.balance, Uint128::new(100)); } #[test] @@ -466,23 +431,29 @@ fn test_send_messages_incomplete_funds() { ) .unwrap(); - let escrow = app + let err = app .instantiate_contract( escrow_code, Addr::unchecked(DAO1), &InstantiateMsg { counterparty_one: Counterparty { address: DAO1.to_string(), - promise: TokenInfo::Native { + promise: SwapInfo::Native { denom: "ujuno".to_string(), amount: Uint128::new(200), + on_completion: vec![NativeSendMsg::BankBurn { + amount: coins(100, "ujuno"), + }], }, }, counterparty_two: Counterparty { address: DAO2.to_string(), - promise: TokenInfo::Cw20 { + promise: SwapInfo::Cw20 { contract_addr: cw20.to_string(), amount: Uint128::new(100), + on_completion: vec![Cw20SendMsgs::Cw20Burn { + amount: Uint128::new(100), + }], }, }, }, @@ -490,47 +461,41 @@ fn test_send_messages_incomplete_funds() { "escrow", None, ) + .unwrap_err() + .downcast::() .unwrap(); - app.sudo(SudoMsg::Bank(BankSudo::Mint { - to_address: DAO1.to_string(), - amount: vec![Coin { - amount: Uint128::new(200), - denom: "ujuno".to_string(), - }], - })) - .unwrap(); - - // We recieve 100 cw20 token, just for fun, im trying to fund a differetn swap with this swap - // So once this swap is done, I can fund the other swap with the 50 cw20 tokens - app.execute_contract( - Addr::unchecked(DAO1), - escrow.clone(), - &ExecuteMsg::Fund { send_message: None }, - &[Coin { - amount: Uint128::new(200), - denom: "ujuno".to_string(), - }], - ) - .unwrap(); + assert_eq!(err, ContractError::WrongFundsCalculation {}); let err = app - .execute_contract( - Addr::unchecked(DAO2), - cw20, - &cw20::Cw20ExecuteMsg::Send { - contract: escrow.to_string(), - amount: Uint128::new(100), - msg: to_binary(&Cw20RecieveMsg::FundWithMsgs { - send_message: SendMessage::SendNative { - messages: vec![AcceptedMessages::BankBurn { - amount: coins(100, "ujuno"), + .instantiate_contract( + escrow_code, + Addr::unchecked(DAO1), + &InstantiateMsg { + counterparty_one: Counterparty { + address: DAO1.to_string(), + promise: SwapInfo::Native { + denom: "ujuno".to_string(), + amount: Uint128::new(200), + on_completion: vec![NativeSendMsg::BankBurn { + amount: coins(200, "ujuno"), }], }, - }) - .unwrap(), + }, + counterparty_two: Counterparty { + address: DAO2.to_string(), + promise: SwapInfo::Cw20 { + contract_addr: cw20.to_string(), + amount: Uint128::new(100), + on_completion: vec![Cw20SendMsgs::Cw20Burn { + amount: Uint128::new(50), + }], + }, + }, }, &[], + "escrow", + None, ) .unwrap_err() .downcast::() @@ -574,16 +539,18 @@ fn test_withdraw() { &InstantiateMsg { counterparty_one: Counterparty { address: DAO1.to_string(), - promise: TokenInfo::Native { + promise: SwapInfo::Native { denom: "ujuno".to_string(), amount: Uint128::new(100), + on_completion: vec![], }, }, counterparty_two: Counterparty { address: DAO2.to_string(), - promise: TokenInfo::Cw20 { + promise: SwapInfo::Cw20 { contract_addr: cw20.to_string(), amount: Uint128::new(100), + on_completion: vec![], }, }, }, @@ -650,7 +617,7 @@ fn test_withdraw() { app.execute_contract( Addr::unchecked(DAO1), escrow.clone(), - &ExecuteMsg::Fund { send_message: None }, + &ExecuteMsg::Fund {}, &[Coin { amount: Uint128::new(100), denom: "ujuno".to_string(), @@ -667,21 +634,21 @@ fn test_withdraw() { StatusResponse { counterparty_one: CheckedCounterparty { address: Addr::unchecked(DAO1), - promise: CheckedTokenInfo::Native { + promise: CheckedSwapInfo::Native { denom: "ujuno".to_string(), - amount: Uint128::new(100) + amount: Uint128::new(100), + on_completion: vec![] }, provided: true, - send_msg: None, }, counterparty_two: CheckedCounterparty { address: Addr::unchecked(DAO2), - promise: CheckedTokenInfo::Cw20 { + promise: CheckedSwapInfo::Cw20 { contract_addr: cw20.clone(), - amount: Uint128::new(100) + amount: Uint128::new(100), + on_completion: vec![] }, provided: false, - send_msg: None, } } ); @@ -707,21 +674,21 @@ fn test_withdraw() { StatusResponse { counterparty_one: CheckedCounterparty { address: Addr::unchecked(DAO1), - promise: CheckedTokenInfo::Native { + promise: CheckedSwapInfo::Native { denom: "ujuno".to_string(), - amount: Uint128::new(100) + amount: Uint128::new(100), + on_completion: vec![] }, provided: false, - send_msg: None, }, counterparty_two: CheckedCounterparty { address: Addr::unchecked(DAO2), - promise: CheckedTokenInfo::Cw20 { + promise: CheckedSwapInfo::Cw20 { contract_addr: cw20, - amount: Uint128::new(100) + amount: Uint128::new(100), + on_completion: vec![] }, provided: false, - send_msg: None, } } ) @@ -762,16 +729,18 @@ fn test_withdraw_post_completion() { &InstantiateMsg { counterparty_one: Counterparty { address: DAO1.to_string(), - promise: TokenInfo::Native { + promise: SwapInfo::Native { denom: "ujuno".to_string(), amount: Uint128::new(100), + on_completion: vec![], }, }, counterparty_two: Counterparty { address: DAO2.to_string(), - promise: TokenInfo::Cw20 { + promise: SwapInfo::Cw20 { contract_addr: cw20.to_string(), amount: Uint128::new(100), + on_completion: vec![], }, }, }, @@ -805,7 +774,7 @@ fn test_withdraw_post_completion() { app.execute_contract( Addr::unchecked(DAO1), escrow.clone(), - &ExecuteMsg::Fund { send_message: None }, + &ExecuteMsg::Fund {}, &[Coin { amount: Uint128::new(100), denom: "ujuno".to_string(), @@ -871,16 +840,18 @@ fn test_invalid_instantiate() { &InstantiateMsg { counterparty_one: Counterparty { address: DAO1.to_string(), - promise: TokenInfo::Native { + promise: SwapInfo::Native { denom: "ujuno".to_string(), amount: Uint128::new(0), + on_completion: vec![], }, }, counterparty_two: Counterparty { address: DAO2.to_string(), - promise: TokenInfo::Cw20 { + promise: SwapInfo::Cw20 { contract_addr: cw20.to_string(), amount: Uint128::new(100), + on_completion: vec![], }, }, }, @@ -902,16 +873,18 @@ fn test_invalid_instantiate() { &InstantiateMsg { counterparty_one: Counterparty { address: DAO1.to_string(), - promise: TokenInfo::Native { + promise: SwapInfo::Native { denom: "ujuno".to_string(), amount: Uint128::new(100), + on_completion: vec![], }, }, counterparty_two: Counterparty { address: DAO2.to_string(), - promise: TokenInfo::Cw20 { + promise: SwapInfo::Cw20 { contract_addr: cw20.to_string(), amount: Uint128::new(0), + on_completion: vec![], }, }, }, @@ -940,16 +913,18 @@ fn test_non_distincy_counterparties() { &InstantiateMsg { counterparty_one: Counterparty { address: DAO1.to_string(), - promise: TokenInfo::Native { + promise: SwapInfo::Native { denom: "ujuno".to_string(), amount: Uint128::new(110), + on_completion: vec![], }, }, counterparty_two: Counterparty { address: DAO1.to_string(), - promise: TokenInfo::Native { + promise: SwapInfo::Native { denom: "ujuno".to_string(), amount: Uint128::new(10), + on_completion: vec![], }, }, }, @@ -999,16 +974,18 @@ fn test_fund_non_counterparty() { &InstantiateMsg { counterparty_one: Counterparty { address: DAO1.to_string(), - promise: TokenInfo::Native { + promise: SwapInfo::Native { denom: "ujuno".to_string(), amount: Uint128::new(100), + on_completion: vec![], }, }, counterparty_two: Counterparty { address: DAO2.to_string(), - promise: TokenInfo::Cw20 { + promise: SwapInfo::Cw20 { contract_addr: cw20.to_string(), amount: Uint128::new(100), + on_completion: vec![], }, }, }, @@ -1048,7 +1025,7 @@ fn test_fund_non_counterparty() { .execute_contract( Addr::unchecked("noah"), escrow, - &ExecuteMsg::Fund { send_message: None }, + &ExecuteMsg::Fund {}, &[Coin { amount: Uint128::new(100), denom: "ujuno".to_string(), @@ -1096,16 +1073,18 @@ fn test_fund_twice() { &InstantiateMsg { counterparty_one: Counterparty { address: DAO1.to_string(), - promise: TokenInfo::Native { + promise: SwapInfo::Native { denom: "ujuno".to_string(), amount: Uint128::new(100), + on_completion: vec![], }, }, counterparty_two: Counterparty { address: DAO2.to_string(), - promise: TokenInfo::Cw20 { + promise: SwapInfo::Cw20 { contract_addr: cw20.to_string(), amount: Uint128::new(100), + on_completion: vec![], }, }, }, @@ -1139,7 +1118,7 @@ fn test_fund_twice() { app.execute_contract( Addr::unchecked(DAO1), escrow.clone(), - &ExecuteMsg::Fund { send_message: None }, + &ExecuteMsg::Fund {}, &[Coin { amount: Uint128::new(100), denom: "ujuno".to_string(), @@ -1151,7 +1130,7 @@ fn test_fund_twice() { .execute_contract( Addr::unchecked(DAO1), escrow.clone(), - &ExecuteMsg::Fund { send_message: None }, + &ExecuteMsg::Fund {}, &[Coin { amount: Uint128::new(100), denom: "ujuno".to_string(), @@ -1216,16 +1195,18 @@ fn test_fund_invalid_amount() { &InstantiateMsg { counterparty_one: Counterparty { address: DAO1.to_string(), - promise: TokenInfo::Native { + promise: SwapInfo::Native { denom: "ujuno".to_string(), amount: Uint128::new(100), + on_completion: vec![], }, }, counterparty_two: Counterparty { address: DAO2.to_string(), - promise: TokenInfo::Cw20 { + promise: SwapInfo::Cw20 { contract_addr: cw20.to_string(), amount: Uint128::new(100), + on_completion: vec![], }, }, }, @@ -1269,7 +1250,7 @@ fn test_fund_invalid_amount() { .execute_contract( Addr::unchecked(DAO1), escrow, - &ExecuteMsg::Fund { send_message: None }, + &ExecuteMsg::Fund {}, &[Coin { amount: Uint128::new(200), denom: "ujuno".to_string(), @@ -1299,16 +1280,18 @@ fn test_fund_invalid_denom() { &InstantiateMsg { counterparty_one: Counterparty { address: DAO1.to_string(), - promise: TokenInfo::Native { + promise: SwapInfo::Native { denom: "ujuno".to_string(), amount: Uint128::new(100), + on_completion: vec![], }, }, counterparty_two: Counterparty { address: DAO2.to_string(), - promise: TokenInfo::Native { + promise: SwapInfo::Native { denom: "uekez".to_string(), amount: Uint128::new(100), + on_completion: vec![], }, }, }, @@ -1333,7 +1316,7 @@ fn test_fund_invalid_denom() { .execute_contract( Addr::unchecked(DAO1), escrow, - &ExecuteMsg::Fund { send_message: None }, + &ExecuteMsg::Fund {}, &[Coin { amount: Uint128::new(100), denom: "uekez".to_string(), @@ -1402,16 +1385,18 @@ fn test_fund_invalid_cw20() { &InstantiateMsg { counterparty_one: Counterparty { address: DAO1.to_string(), - promise: TokenInfo::Native { + promise: SwapInfo::Native { denom: "ujuno".to_string(), amount: Uint128::new(100), + on_completion: vec![], }, }, counterparty_two: Counterparty { address: DAO2.to_string(), - promise: TokenInfo::Cw20 { + promise: SwapInfo::Cw20 { contract_addr: cw20.to_string(), amount: Uint128::new(100), + on_completion: vec![], }, }, }, diff --git a/contracts/external/cw-token-swap/src/types.rs b/contracts/external/cw-token-swap/src/types.rs index 4d9f0fee5..7c307a992 100644 --- a/contracts/external/cw-token-swap/src/types.rs +++ b/contracts/external/cw-token-swap/src/types.rs @@ -1,14 +1,50 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::{ - to_binary, Addr, BankMsg, Binary, Coin, CosmosMsg, Deps, MessageInfo, StdError, Uint128, - WasmMsg, -}; -use cw_utils::must_pay; +use cosmwasm_std::{to_binary, Addr, BankMsg, Binary, Coin, CosmosMsg, Deps, Uint128, WasmMsg}; use crate::ContractError; +// We create empty trait and implement it for the types we want to use +// so we can create a generic function that only accepts a vec of those types +trait CompletionMsgsToCosmosMsg { + fn into_checked_cosmos_msg( + self, + deps: Deps, + denom: &str, + ) -> Result<(Uint128, CosmosMsg), ContractError>; +} + +/// This function returns a vector of CosmosMsgs from the given vector of CompletionMsgs +/// It verifies the msgs is not empty (else return empty vec) +/// It verifies the total amount of funds matches the funds sent in all messages +fn completion_to_cosmos_msgs( + deps: Deps, + msgs: Vec, + amount: Uint128, + string: &str, // extra data we need (denom or contract addr) +) -> Result, ContractError> { + if msgs.is_empty() { + return Ok(vec![]); + } + + let mut total_amount = Uint128::zero(); + let cosmos_msgs = msgs + .into_iter() + .map(|msg| { + let (amount, cosmos_msg) = msg.into_checked_cosmos_msg(deps, string)?; + total_amount += amount; + Ok(cosmos_msg) + }) + .collect::, ContractError>>()?; + + // Verify that total amount of funds matches funds sent in all messages + if total_amount != amount { + return Err(ContractError::WrongFundsCalculation {}); + } + Ok(cosmos_msgs) +} + #[cw_serde] -pub enum AcceptedMessages { +pub enum NativeSendMsg { BankSend { to_address: String, amount: Vec, @@ -30,162 +66,136 @@ pub enum AcceptedMessages { }, } -/// Enum to accept either a cosmos msg (for recieved native tokens) -/// or address and binary message (for recieved cw20 tokens) -#[cw_serde] -pub enum SendMessage { - SendCw20 { - /// Contract address to execute the msg on - contract_address: String, - /// The message in binary format - message: Binary, - }, - SendNative { - /// Vector of accepted messages to send - messages: Vec, - }, -} - -impl SendMessage { - pub fn into_checked_cosmos_msgs( +impl CompletionMsgsToCosmosMsg for NativeSendMsg { + fn into_checked_cosmos_msg( self, deps: Deps, - token_info: CheckedTokenInfo, - ) -> Result, ContractError> { + denom: &str, + ) -> Result<(Uint128, CosmosMsg), ContractError> { + let verify_coin = |coins: &Vec| { + if coins.len() != 1 { + return Err(ContractError::InvalidSendMsg {}); + } + if coins[0].amount.is_zero() { + return Err(ContractError::InvalidSendMsg {}); + } + if denom != coins[0].denom { + return Err(ContractError::InvalidSendMsg {}); + } + Ok(coins[0].amount) + }; + match self { - SendMessage::SendCw20 { - contract_address, - message, - } => { - // We check if the other party token type is cw20 - if let CheckedTokenInfo::Cw20 { - contract_addr: cw20_address, + NativeSendMsg::BankSend { to_address, amount } => Ok(( + verify_coin(&amount)?, + BankMsg::Send { + to_address: deps.api.addr_validate(&to_address)?.to_string(), amount, - } = token_info - { - Ok(vec![WasmMsg::Execute { - contract_addr: cw20_address.to_string(), - msg: to_binary(&cw20::Cw20ExecuteMsg::Send { - contract: deps.api.addr_validate(&contract_address)?.to_string(), - amount, - msg: message, - })?, - funds: vec![], - } - .into()]) - } else { - Err(ContractError::InvalidSendMsg {}) } + .into(), + )), + NativeSendMsg::BankBurn { amount } => { + Ok((verify_coin(&amount)?, BankMsg::Burn { amount }.into())) } - SendMessage::SendNative { messages } => { - if let CheckedTokenInfo::Native { - amount: total_amount, - denom, - } = token_info - { - if messages.is_empty() { - return Err(ContractError::InvalidSendMsg {}); - } - - let mut total_send_funds = Uint128::zero(); - let cosmos_msgs = messages - .into_iter() - .map(|msg| match msg { - AcceptedMessages::BankSend { - to_address, - amount: amount_to_pay, - } => { - total_send_funds = - add_to_total(total_send_funds, &amount_to_pay, &denom)?; - - Ok(BankMsg::Send { - to_address: deps.api.addr_validate(&to_address)?.to_string(), - amount: amount_to_pay, - } - .into()) - } - AcceptedMessages::BankBurn { - amount: amount_to_pay, - } => { - total_send_funds = - add_to_total(total_send_funds, &amount_to_pay, &denom)?; - - Ok(BankMsg::Burn { - amount: amount_to_pay, - } - .into()) - } - AcceptedMessages::WasmExecute { - contract_addr, - msg, - funds: amount_to_pay, - } => { - total_send_funds = - add_to_total(total_send_funds, &amount_to_pay, &denom)?; - - Ok(WasmMsg::Execute { - contract_addr: deps - .api - .addr_validate(&contract_addr)? - .to_string(), - msg, - funds: amount_to_pay, - } - .into()) - } - AcceptedMessages::WasmInstantiate { - admin, - code_id, - msg, - funds: amount_to_pay, - label, - } => { - total_send_funds = - add_to_total(total_send_funds, &amount_to_pay, &denom)?; - - Ok(WasmMsg::Instantiate { - admin: admin - .map(|a| deps.api.addr_validate(&a).unwrap().to_string()), - code_id, - msg, - funds: amount_to_pay, - label, - } - .into()) - } - }) - .collect::, ContractError>>()?; - - // Make sure that the funds we try to send, matches exactly to the total amount swapped. - if total_send_funds != total_amount { - return Err(ContractError::WrongFundsCalculation {}); - } - - Ok(cosmos_msgs) - } else { - Err(ContractError::InvalidSendMsg {}) + NativeSendMsg::WasmExecute { + contract_addr, + msg, + funds, + } => Ok(( + verify_coin(&funds)?, + WasmMsg::Execute { + contract_addr: deps.api.addr_validate(&contract_addr)?.to_string(), + msg, + funds, } - } + .into(), + )), + NativeSendMsg::WasmInstantiate { + admin, + code_id, + msg, + funds, + label, + } => Ok(( + verify_coin(&funds)?, + WasmMsg::Instantiate { + admin: admin.map(|a| deps.api.addr_validate(&a).unwrap().to_string()), + code_id, + msg, + funds, + label, + } + .into(), + )), } } } -/// This function check the given funds are valid and adds the amount to the total funds. -/// returns the new total. -pub fn add_to_total( - total_funds: Uint128, - funds: &[Coin], - denom: &str, -) -> Result { - let amount = must_pay( - &MessageInfo { - sender: Addr::unchecked(""), - funds: funds.to_owned(), - }, - denom, - )?; +#[cw_serde] +pub enum Cw20SendMsgs { + Cw20Send { + contract: String, + amount: Uint128, + msg: Binary, + }, + Cw20Burn { + amount: Uint128, + }, + Cw20Transfer { + recipient: String, + amount: Uint128, + }, +} - Ok(total_funds + amount) +impl CompletionMsgsToCosmosMsg for Cw20SendMsgs { + /// This is a helper function to convert the Cw20SendMsgs into a CosmosMsg + /// It will return the amount of tokens and the cosmosMsg to be sent + fn into_checked_cosmos_msg( + self, + deps: Deps, + cw20_addr: &str, + ) -> Result<(Uint128, CosmosMsg), ContractError> { + match self { + Cw20SendMsgs::Cw20Send { + contract, + amount, + msg, + } => Ok(( + amount, + WasmMsg::Execute { + contract_addr: cw20_addr.to_string(), + msg: to_binary(&cw20::Cw20ExecuteMsg::Send { + contract: deps.api.addr_validate(&contract)?.to_string(), + amount, + msg, + })?, + funds: vec![], + } + .into(), + )), + Cw20SendMsgs::Cw20Burn { amount } => Ok(( + amount, + WasmMsg::Execute { + contract_addr: cw20_addr.to_string(), + msg: to_binary(&cw20::Cw20ExecuteMsg::Burn { amount })?, + funds: vec![], + } + .into(), + )), + Cw20SendMsgs::Cw20Transfer { recipient, amount } => Ok(( + amount, + WasmMsg::Execute { + contract_addr: cw20_addr.to_string(), + msg: to_binary(&cw20::Cw20ExecuteMsg::Transfer { + recipient: deps.api.addr_validate(&recipient)?.to_string(), + amount, + })?, + funds: vec![], + } + .into(), + )), + } + } } /// Information about a counterparty in this escrow transaction and @@ -195,7 +205,7 @@ pub struct Counterparty { /// The address of the counterparty. pub address: String, /// The funds they have promised to provide. - pub promise: TokenInfo, + pub promise: SwapInfo, } impl Counterparty { @@ -204,7 +214,6 @@ impl Counterparty { address: deps.api.addr_validate(&self.address)?, provided: false, promise: self.promise.into_checked(deps)?, - send_msg: None, }) } } @@ -212,53 +221,52 @@ impl Counterparty { #[cw_serde] pub struct CheckedCounterparty { pub address: Addr, - pub promise: CheckedTokenInfo, + pub promise: CheckedSwapInfo, pub provided: bool, - pub send_msg: Option>, -} - -impl CheckedCounterparty { - pub fn add_send_msg( - &mut self, - deps: Deps, - send_msg: Option, - ) -> Result<(), ContractError> { - self.send_msg = match send_msg { - Some(msg) => Some(msg.into_checked_cosmos_msgs(deps, self.promise.clone())?), - None => None, - }; - Ok(()) - } - pub fn remove_send_msg(&mut self) { - self.send_msg = None; - } } /// Information about the token being used on one side of the escrow. #[cw_serde] -pub enum TokenInfo { +pub enum SwapInfo { /// A native token. - Native { denom: String, amount: Uint128 }, + Native { + denom: String, + amount: Uint128, + on_completion: Vec, + }, /// A cw20 token. Cw20 { contract_addr: String, amount: Uint128, + on_completion: Vec, }, } -impl TokenInfo { - pub fn into_checked(self, deps: Deps) -> Result { +impl SwapInfo { + pub fn into_checked(self, deps: Deps) -> Result { match self { - TokenInfo::Native { denom, amount } => { + SwapInfo::Native { + denom, + amount, + on_completion, + } => { if amount.is_zero() { Err(ContractError::ZeroTokens {}) } else { - Ok(CheckedTokenInfo::Native { denom, amount }) + let on_completion = + completion_to_cosmos_msgs(deps, on_completion, amount, &denom)?; + + Ok(CheckedSwapInfo::Native { + denom, + amount, + on_completion, + }) } } - TokenInfo::Cw20 { + SwapInfo::Cw20 { contract_addr, amount, + on_completion, } => { if amount.is_zero() { Err(ContractError::ZeroTokens {}) @@ -269,9 +277,18 @@ impl TokenInfo { contract_addr.clone(), &cw20::Cw20QueryMsg::TokenInfo {}, )?; - Ok(CheckedTokenInfo::Cw20 { + + let on_completion = completion_to_cosmos_msgs( + deps, + on_completion, + amount, + contract_addr.as_str(), + )?; + + Ok(CheckedSwapInfo::Cw20 { contract_addr, amount, + on_completion, }) } } @@ -280,47 +297,57 @@ impl TokenInfo { } #[cw_serde] -pub enum CheckedTokenInfo { +pub enum CheckedSwapInfo { Native { denom: String, amount: Uint128, + on_completion: Vec, }, Cw20 { contract_addr: Addr, amount: Uint128, + on_completion: Vec, }, } -impl CheckedTokenInfo { - pub fn into_send_message( - self, - recipient_counterparty: &CheckedCounterparty, - send_msg: Option>, - ) -> Result, StdError> { +impl CheckedSwapInfo { + pub fn into_send_message(self, recipient: String) -> Result, ContractError> { Ok(match self { - Self::Native { denom, amount } => match send_msg { - Some(msgs) => msgs, - None => vec![BankMsg::Send { - to_address: recipient_counterparty.address.to_string(), + Self::Native { + denom, + amount, + on_completion, + } => { + // If completion msgs was specified we send them + if !on_completion.is_empty() { + return Ok(on_completion); + } + + // If completion msgs was not specified we send funds to the other party + vec![BankMsg::Send { + to_address: recipient, amount: vec![Coin { denom, amount }], } - .into()], - }, + .into()] + } Self::Cw20 { contract_addr, amount, - } => match send_msg { - Some(msgs) => msgs, - None => vec![WasmMsg::Execute { + on_completion, + } => { + // If completion msgs was specified we send them + if !on_completion.is_empty() { + return Ok(on_completion); + } + + // If completion msgs was not specified we transfer funds to the other party + vec![WasmMsg::Execute { contract_addr: contract_addr.into_string(), - msg: to_binary(&cw20::Cw20ExecuteMsg::Transfer { - recipient: recipient_counterparty.address.to_string(), - amount, - })?, + msg: to_binary(&cw20::Cw20ExecuteMsg::Transfer { recipient, amount })?, funds: vec![], } - .into()], - }, + .into()] + } }) } } @@ -331,24 +358,18 @@ mod tests { #[test] fn test_into_spend_message_native() { - let info = CheckedTokenInfo::Native { + let info = CheckedSwapInfo::Native { amount: Uint128::new(100), denom: "uekez".to_string(), + on_completion: vec![CosmosMsg::Bank(BankMsg::Send { + to_address: "ekez".to_string(), + amount: vec![Coin { + amount: Uint128::new(100), + denom: "uekez".to_string(), + }], + })], }; - let message = info - .into_send_message( - &CheckedCounterparty { - address: Addr::unchecked("ekez"), - promise: CheckedTokenInfo::Native { - amount: Uint128::new(100), - denom: "uekez".to_string(), - }, - provided: false, - send_msg: None, - }, - None, - ) - .unwrap(); + let message = info.into_send_message("ekez".to_string()).unwrap(); assert_eq!( message[0], @@ -364,24 +385,20 @@ mod tests { #[test] fn test_into_spend_message_cw20() { - let info = CheckedTokenInfo::Cw20 { + let info = CheckedSwapInfo::Cw20 { amount: Uint128::new(100), contract_addr: Addr::unchecked("ekez_token"), + on_completion: vec![CosmosMsg::Wasm(WasmMsg::Execute { + funds: vec![], + contract_addr: "ekez_token".to_string(), + msg: to_binary(&cw20::Cw20ExecuteMsg::Transfer { + recipient: "ekez".to_string(), + amount: Uint128::new(100), + }) + .unwrap(), + })], }; - let message = info - .into_send_message( - &CheckedCounterparty { - address: Addr::unchecked("ekez"), - promise: CheckedTokenInfo::Native { - amount: Uint128::new(100), - denom: "uekez".to_string(), - }, - provided: false, - send_msg: None, - }, - None, - ) - .unwrap(); + let message = info.into_send_message("ekez".to_string()).unwrap(); assert_eq!( message[0], From d12812cd117a3a9169a2fe866d77578dc1e122a5 Mon Sep 17 00:00:00 2001 From: Art3mix Date: Sat, 11 Mar 2023 00:06:56 +0200 Subject: [PATCH 07/11] fix wrong msgs on withdraw --- .../external/cw-token-swap/src/contract.rs | 6 +- contracts/external/cw-token-swap/src/tests.rs | 133 +++++++++++++++++- contracts/external/cw-token-swap/src/types.rs | 10 +- 3 files changed, 140 insertions(+), 9 deletions(-) diff --git a/contracts/external/cw-token-swap/src/contract.rs b/contracts/external/cw-token-swap/src/contract.rs index eab4a3d0d..5098a66f6 100644 --- a/contracts/external/cw-token-swap/src/contract.rs +++ b/contracts/external/cw-token-swap/src/contract.rs @@ -117,12 +117,12 @@ fn do_fund( let mut msgs = counterparty .promise .clone() - .into_send_message(other_counterparty.address.to_string())?; + .into_send_message(other_counterparty.address.to_string(), false)?; msgs.append( &mut other_counterparty .promise - .into_send_message(counterparty.address.to_string())?, + .into_send_message(counterparty.address.to_string(), false)?, ); msgs } else { @@ -222,7 +222,7 @@ pub fn execute_withdraw(deps: DepsMut, info: MessageInfo) -> Result Result, ContractError> { + pub fn into_send_message(self, recipient: String, is_withdraw: bool) -> Result, ContractError> { Ok(match self { Self::Native { denom, @@ -319,7 +319,7 @@ impl CheckedSwapInfo { on_completion, } => { // If completion msgs was specified we send them - if !on_completion.is_empty() { + if !is_withdraw && !on_completion.is_empty() { return Ok(on_completion); } @@ -336,7 +336,7 @@ impl CheckedSwapInfo { on_completion, } => { // If completion msgs was specified we send them - if !on_completion.is_empty() { + if !is_withdraw && !on_completion.is_empty() { return Ok(on_completion); } @@ -369,7 +369,7 @@ mod tests { }], })], }; - let message = info.into_send_message("ekez".to_string()).unwrap(); + let message = info.into_send_message("ekez".to_string(), false).unwrap(); assert_eq!( message[0], @@ -398,7 +398,7 @@ mod tests { .unwrap(), })], }; - let message = info.into_send_message("ekez".to_string()).unwrap(); + let message = info.into_send_message("ekez".to_string(), false).unwrap(); assert_eq!( message[0], From 1e8c55d6d5cdc3d0a7899bf29388fca25cdfeb7a Mon Sep 17 00:00:00 2001 From: Art3mix Date: Sat, 11 Mar 2023 00:09:31 +0200 Subject: [PATCH 08/11] lint ... --- contracts/external/cw-token-swap/src/tests.rs | 4 ++-- contracts/external/cw-token-swap/src/types.rs | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/contracts/external/cw-token-swap/src/tests.rs b/contracts/external/cw-token-swap/src/tests.rs index fdc0d4a26..945c5eb0b 100644 --- a/contracts/external/cw-token-swap/src/tests.rs +++ b/contracts/external/cw-token-swap/src/tests.rs @@ -503,7 +503,7 @@ fn test_withdraw_ignores_msgs() { app.execute_contract( Addr::unchecked(DAO2), - cw20.clone(), + cw20, &cw20::Cw20ExecuteMsg::Send { contract: escrow.to_string(), amount: Uint128::new(200), @@ -524,7 +524,7 @@ fn test_withdraw_ignores_msgs() { app.execute_contract( Addr::unchecked(DAO1), - escrow.clone(), + escrow, &ExecuteMsg::Fund {}, &[Coin { amount: Uint128::new(200), diff --git a/contracts/external/cw-token-swap/src/types.rs b/contracts/external/cw-token-swap/src/types.rs index 3a920618e..92b11a9c5 100644 --- a/contracts/external/cw-token-swap/src/types.rs +++ b/contracts/external/cw-token-swap/src/types.rs @@ -311,7 +311,11 @@ pub enum CheckedSwapInfo { } impl CheckedSwapInfo { - pub fn into_send_message(self, recipient: String, is_withdraw: bool) -> Result, ContractError> { + pub fn into_send_message( + self, + recipient: String, + is_withdraw: bool, + ) -> Result, ContractError> { Ok(match self { Self::Native { denom, From b14672f6ef0a5c13e5855eb4b1ed27ca993967ea Mon Sep 17 00:00:00 2001 From: Art3mix Date: Sat, 18 Mar 2023 22:54:43 +0200 Subject: [PATCH 09/11] fix zeke review --- .../cw-token-swap/schema/cw-token-swap.json | 4 +- .../external/cw-token-swap/src/contract.rs | 11 +- contracts/external/cw-token-swap/src/error.rs | 4 +- contracts/external/cw-token-swap/src/tests.rs | 8 +- contracts/external/cw-token-swap/src/types.rs | 120 +++++++++--------- 5 files changed, 71 insertions(+), 76 deletions(-) diff --git a/contracts/external/cw-token-swap/schema/cw-token-swap.json b/contracts/external/cw-token-swap/schema/cw-token-swap.json index 7693ac5ff..bfa4eecef 100644 --- a/contracts/external/cw-token-swap/schema/cw-token-swap.json +++ b/contracts/external/cw-token-swap/schema/cw-token-swap.json @@ -141,7 +141,7 @@ } ] }, - "NativeSendMsg": { + "NativeSendMsgs": { "oneOf": [ { "type": "object", @@ -300,7 +300,7 @@ "on_completion": { "type": "array", "items": { - "$ref": "#/definitions/NativeSendMsg" + "$ref": "#/definitions/NativeSendMsgs" } } }, diff --git a/contracts/external/cw-token-swap/src/contract.rs b/contracts/external/cw-token-swap/src/contract.rs index 5098a66f6..55a4c880e 100644 --- a/contracts/external/cw-token-swap/src/contract.rs +++ b/contracts/external/cw-token-swap/src/contract.rs @@ -89,7 +89,6 @@ fn get_counterparty<'a>( /// escrow funds if both counterparties have funded the contract. /// /// NOTE: The caller must verify that the denom of PAID is correct. -#[allow(clippy::too_many_arguments)] fn do_fund( deps: DepsMut, counterparty: CheckedCounterparty, @@ -116,11 +115,10 @@ fn do_fund( let messages = if counterparty.provided && other_counterparty.provided { let mut msgs = counterparty .promise - .clone() .into_send_message(other_counterparty.address.to_string(), false)?; - msgs.append( - &mut other_counterparty + msgs.extend( + other_counterparty .promise .into_send_message(counterparty.address.to_string(), false)?, ); @@ -222,8 +220,7 @@ pub fn execute_withdraw(deps: DepsMut, info: MessageInfo) -> Result Result Result<(Uint128, CosmosMsg), ContractError>; -} - -/// This function returns a vector of CosmosMsgs from the given vector of CompletionMsgs -/// It verifies the msgs is not empty (else return empty vec) -/// It verifies the total amount of funds matches the funds sent in all messages -fn completion_to_cosmos_msgs( - deps: Deps, - msgs: Vec, - amount: Uint128, - string: &str, // extra data we need (denom or contract addr) -) -> Result, ContractError> { - if msgs.is_empty() { - return Ok(vec![]); - } - - let mut total_amount = Uint128::zero(); - let cosmos_msgs = msgs - .into_iter() - .map(|msg| { - let (amount, cosmos_msg) = msg.into_checked_cosmos_msg(deps, string)?; - total_amount += amount; - Ok(cosmos_msg) - }) - .collect::, ContractError>>()?; - - // Verify that total amount of funds matches funds sent in all messages - if total_amount != amount { - return Err(ContractError::WrongFundsCalculation {}); - } - Ok(cosmos_msgs) -} - #[cw_serde] -pub enum NativeSendMsg { +pub enum NativeSendMsgs { BankSend { to_address: String, amount: Vec, @@ -66,27 +26,31 @@ pub enum NativeSendMsg { }, } -impl CompletionMsgsToCosmosMsg for NativeSendMsg { +impl NativeSendMsgs { + /// This is a helper function to convert the Cw20SendMsgs into a CosmosMsg + /// + /// Returns (amount_funds_to_send, CosmosMsg), we need `amount_funds_to_send` because later + /// we make sure total amount of funds sent is equal to the amount of funds promised fn into_checked_cosmos_msg( self, deps: Deps, - denom: &str, + denom: &str, // Promised denom. ) -> Result<(Uint128, CosmosMsg), ContractError> { let verify_coin = |coins: &Vec| { if coins.len() != 1 { - return Err(ContractError::InvalidSendMsg {}); + return Err(ContractError::InvalidSendMsgFunds {}); } if coins[0].amount.is_zero() { - return Err(ContractError::InvalidSendMsg {}); + return Err(ContractError::InvalidSendMsgFunds {}); } if denom != coins[0].denom { - return Err(ContractError::InvalidSendMsg {}); + return Err(ContractError::InvalidSendMsgFunds {}); } Ok(coins[0].amount) }; match self { - NativeSendMsg::BankSend { to_address, amount } => Ok(( + NativeSendMsgs::BankSend { to_address, amount } => Ok(( verify_coin(&amount)?, BankMsg::Send { to_address: deps.api.addr_validate(&to_address)?.to_string(), @@ -94,10 +58,10 @@ impl CompletionMsgsToCosmosMsg for NativeSendMsg { } .into(), )), - NativeSendMsg::BankBurn { amount } => { + NativeSendMsgs::BankBurn { amount } => { Ok((verify_coin(&amount)?, BankMsg::Burn { amount }.into())) } - NativeSendMsg::WasmExecute { + NativeSendMsgs::WasmExecute { contract_addr, msg, funds, @@ -110,7 +74,7 @@ impl CompletionMsgsToCosmosMsg for NativeSendMsg { } .into(), )), - NativeSendMsg::WasmInstantiate { + NativeSendMsgs::WasmInstantiate { admin, code_id, msg, @@ -147,9 +111,11 @@ pub enum Cw20SendMsgs { }, } -impl CompletionMsgsToCosmosMsg for Cw20SendMsgs { +impl Cw20SendMsgs { /// This is a helper function to convert the Cw20SendMsgs into a CosmosMsg - /// It will return the amount of tokens and the cosmosMsg to be sent + /// + /// Returns (amount_funds_to_send, CosmosMsg), we need `amount_funds_to_send` because later + /// we make sure total amount of funds sent is equal to the amount of funds promised fn into_checked_cosmos_msg( self, deps: Deps, @@ -232,7 +198,7 @@ pub enum SwapInfo { Native { denom: String, amount: Uint128, - on_completion: Vec, + on_completion: Vec, }, /// A cw20 token. Cw20 { @@ -253,8 +219,26 @@ impl SwapInfo { if amount.is_zero() { Err(ContractError::ZeroTokens {}) } else { - let on_completion = - completion_to_cosmos_msgs(deps, on_completion, amount, &denom)?; + let on_completion = if on_completion.is_empty() { + vec![] + } else { + let mut total_amount = Uint128::zero(); + let cosmos_msgs = on_completion + .into_iter() + .map(|msg| { + let (amount, cosmos_msg) = + msg.into_checked_cosmos_msg(deps, &denom)?; + total_amount += amount; + Ok(cosmos_msg) + }) + .collect::, ContractError>>()?; + + // Verify that total amount of funds matches funds sent in all messages + if total_amount != amount { + return Err(ContractError::WrongFundsCalculation {}); + } + cosmos_msgs + }; Ok(CheckedSwapInfo::Native { denom, @@ -278,12 +262,26 @@ impl SwapInfo { &cw20::Cw20QueryMsg::TokenInfo {}, )?; - let on_completion = completion_to_cosmos_msgs( - deps, - on_completion, - amount, - contract_addr.as_str(), - )?; + let on_completion = if on_completion.is_empty() { + vec![] + } else { + let mut total_amount = Uint128::zero(); + let cosmos_msgs = on_completion + .into_iter() + .map(|msg| { + let (amount, cosmos_msg) = + msg.into_checked_cosmos_msg(deps, contract_addr.as_str())?; + total_amount += amount; + Ok(cosmos_msg) + }) + .collect::, ContractError>>()?; + + // Verify that total amount of funds matches funds sent in all messages + if total_amount != amount { + return Err(ContractError::WrongFundsCalculation {}); + } + cosmos_msgs + }; Ok(CheckedSwapInfo::Cw20 { contract_addr, From f4cdfcdab7218b5f2d4037decc0202769f7e4c2d Mon Sep 17 00:00:00 2001 From: Art3mix Date: Sat, 18 Mar 2023 23:02:57 +0200 Subject: [PATCH 10/11] added comment --- contracts/external/cw-token-swap/src/types.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/external/cw-token-swap/src/types.rs b/contracts/external/cw-token-swap/src/types.rs index 24b24f9f1..8b5c4cb0f 100644 --- a/contracts/external/cw-token-swap/src/types.rs +++ b/contracts/external/cw-token-swap/src/types.rs @@ -36,6 +36,8 @@ impl NativeSendMsgs { deps: Deps, denom: &str, // Promised denom. ) -> Result<(Uint128, CosmosMsg), ContractError> { + // Function to verify the coins sent is valid + // it MUST be used in every match arm let verify_coin = |coins: &Vec| { if coins.len() != 1 { return Err(ContractError::InvalidSendMsgFunds {}); From 0750631ebb8b942627ded615bcf393f5cca9e76f Mon Sep 17 00:00:00 2001 From: Art3mix Date: Wed, 22 Mar 2023 22:39:58 +0200 Subject: [PATCH 11/11] fuction fix --- contracts/external/cw-token-swap/src/types.rs | 43 ++++++++++--------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/contracts/external/cw-token-swap/src/types.rs b/contracts/external/cw-token-swap/src/types.rs index 8b5c4cb0f..366274410 100644 --- a/contracts/external/cw-token-swap/src/types.rs +++ b/contracts/external/cw-token-swap/src/types.rs @@ -211,6 +211,7 @@ pub enum SwapInfo { } impl SwapInfo { + /// pub fn into_checked(self, deps: Deps) -> Result { match self { SwapInfo::Native { @@ -224,19 +225,17 @@ impl SwapInfo { let on_completion = if on_completion.is_empty() { vec![] } else { - let mut total_amount = Uint128::zero(); - let cosmos_msgs = on_completion - .into_iter() - .map(|msg| { - let (amount, cosmos_msg) = - msg.into_checked_cosmos_msg(deps, &denom)?; - total_amount += amount; - Ok(cosmos_msg) - }) - .collect::, ContractError>>()?; + let (cosmos_msgs, tokens_sent) = on_completion.into_iter().try_fold( + (vec![], Uint128::zero()), + |(mut messages, total_sent), msg| -> Result<_, ContractError> { + let (sent, msg) = msg.into_checked_cosmos_msg(deps, &denom)?; + messages.push(msg); + Ok((messages, sent + total_sent)) + }, + )?; // Verify that total amount of funds matches funds sent in all messages - if total_amount != amount { + if tokens_sent != amount { return Err(ContractError::WrongFundsCalculation {}); } cosmos_msgs @@ -267,19 +266,18 @@ impl SwapInfo { let on_completion = if on_completion.is_empty() { vec![] } else { - let mut total_amount = Uint128::zero(); - let cosmos_msgs = on_completion - .into_iter() - .map(|msg| { - let (amount, cosmos_msg) = + let (cosmos_msgs, tokens_sent) = on_completion.into_iter().try_fold( + (vec![], Uint128::zero()), + |(mut messages, total_sent), msg| -> Result<_, ContractError> { + let (sent, msg) = msg.into_checked_cosmos_msg(deps, contract_addr.as_str())?; - total_amount += amount; - Ok(cosmos_msg) - }) - .collect::, ContractError>>()?; + messages.push(msg); + Ok((messages, sent + total_sent)) + }, + )?; // Verify that total amount of funds matches funds sent in all messages - if total_amount != amount { + if tokens_sent != amount { return Err(ContractError::WrongFundsCalculation {}); } cosmos_msgs @@ -311,6 +309,9 @@ pub enum CheckedSwapInfo { } impl CheckedSwapInfo { + /// Returns the msgs we need to send based on what we do/have + /// If we do withdraw, we ignore on completion msgs and set recipient as the withdrawer address + /// If swap successful, we either return on completion msgs if we have them or send funds to the other party pub fn into_send_message( self, recipient: String,