diff --git a/Cargo.lock b/Cargo.lock index a055833f1..e980be02d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -201,9 +201,11 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", + "cw-multi-test", "cw0", "cw2", "cw20", + "cw20-base", "schemars", "serde", "thiserror", diff --git a/contracts/cw20-escrow/Cargo.toml b/contracts/cw20-escrow/Cargo.toml index e5cf24391..bdab20b05 100644 --- a/contracts/cw20-escrow/Cargo.toml +++ b/contracts/cw20-escrow/Cargo.toml @@ -29,3 +29,5 @@ thiserror = { version = "1.0.20" } [dev-dependencies] cosmwasm-schema = { version = "0.12.0-alpha2" } +cw-multi-test = { path = "../../packages/multi-test", version = "0.3.2" } +cw20-base = { path = "../cw20-base", version = "0.3.2", features = ["library"] } diff --git a/contracts/cw20-escrow/src/integration_test.rs b/contracts/cw20-escrow/src/integration_test.rs new file mode 100644 index 000000000..3d803b406 --- /dev/null +++ b/contracts/cw20-escrow/src/integration_test.rs @@ -0,0 +1,141 @@ +#![cfg(test)] + +use cosmwasm_std::testing::{mock_env, MockApi, MockStorage}; +use cosmwasm_std::{coins, to_binary, HumanAddr, Uint128}; +use cw20::{Cw20CoinHuman, Cw20Contract, Cw20HandleMsg}; +use cw_multi_test::{Contract, ContractWrapper, Router, SimpleBank}; + +use crate::msg::{CreateMsg, DetailsResponse, HandleMsg, InitMsg, QueryMsg, ReceiveMsg}; + +fn mock_router() -> Router { + let env = mock_env(); + let api = Box::new(MockApi::default()); + let bank = SimpleBank {}; + + Router::new(api, env.block, bank, || Box::new(MockStorage::new())) +} + +pub fn contract_escrow() -> Box { + let contract = ContractWrapper::new( + crate::contract::handle, + crate::contract::init, + crate::contract::query, + ); + Box::new(contract) +} + +pub fn contract_cw20() -> Box { + let contract = ContractWrapper::new( + cw20_base::contract::handle, + cw20_base::contract::init, + cw20_base::contract::query, + ); + Box::new(contract) +} + +#[test] +// receive cw20 tokens and release upon approval +fn escrow_happy_path_cw20_tokens() { + let mut router = mock_router(); + + // set personal balance + let owner = HumanAddr::from("owner"); + let init_funds = coins(2000, "btc"); + router + .set_bank_balance(owner.clone(), init_funds.clone()) + .unwrap(); + + // set up cw20 contract with some tokens + let cw20_id = router.store_code(contract_cw20()); + let msg = cw20_base::msg::InitMsg { + name: "Cash Money".to_string(), + symbol: "CASH".to_string(), + decimals: 2, + initial_balances: vec![Cw20CoinHuman { + address: owner.clone(), + amount: Uint128(5000), + }], + mint: None, + }; + let cash_addr = router + .instantiate_contract(cw20_id, &owner, &msg, &[], "CASH") + .unwrap(); + + // set up reflect contract + let escrow_id = router.store_code(contract_escrow()); + let escrow_addr = router + .instantiate_contract(escrow_id, &owner, &InitMsg {}, &[], "Escrow") + .unwrap(); + + // they are different + assert_ne!(cash_addr, escrow_addr); + + // set up cw20 helpers + let cash = Cw20Contract(cash_addr.clone()); + + // ensure our balances + let owner_balance = cash.balance(&router, owner.clone()).unwrap(); + assert_eq!(owner_balance, Uint128(5000)); + let escrow_balance = cash.balance(&router, escrow_addr.clone()).unwrap(); + assert_eq!(escrow_balance, Uint128(0)); + + // send some tokens to create an escrow + let arb = HumanAddr::from("arbiter"); + let ben = HumanAddr::from("beneficiary"); + let id = "demo".to_string(); + let create_msg = ReceiveMsg::Create(CreateMsg { + id: id.clone(), + arbiter: arb.clone(), + recipient: ben.clone(), + end_height: None, + end_time: None, + cw20_whitelist: None, + }); + let create_bin = to_binary(&create_msg).unwrap(); + let send_msg = Cw20HandleMsg::Send { + contract: escrow_addr.clone(), + amount: Uint128(1200), + msg: Some(create_bin), + }; + let res = router + .execute_contract(&owner, &cash_addr, &send_msg, &[]) + .unwrap(); + println!("{:?}", res.attributes); + assert_eq!(6, res.attributes.len()); + + // ensure balances updated + let owner_balance = cash.balance(&router, owner.clone()).unwrap(); + assert_eq!(owner_balance, Uint128(3800)); + let escrow_balance = cash.balance(&router, escrow_addr.clone()).unwrap(); + assert_eq!(escrow_balance, Uint128(1200)); + + // ensure escrow properly created + let details: DetailsResponse = router + .wrap() + .query_wasm_smart(&escrow_addr, &QueryMsg::Details { id: id.clone() }) + .unwrap(); + assert_eq!(id, details.id); + assert_eq!(arb, details.arbiter); + assert_eq!(ben, details.recipient); + assert_eq!( + vec![Cw20CoinHuman { + address: cash_addr.clone(), + amount: Uint128(1200) + }], + details.cw20_balance + ); + + // release escrow + let approve_msg = HandleMsg::Approve { id: id.clone() }; + let _ = router + .execute_contract(&arb, &escrow_addr, &approve_msg, &[]) + .unwrap(); + + // ensure balances updated - release to ben + let owner_balance = cash.balance(&router, owner.clone()).unwrap(); + assert_eq!(owner_balance, Uint128(3800)); + let escrow_balance = cash.balance(&router, escrow_addr.clone()).unwrap(); + assert_eq!(escrow_balance, Uint128(0)); + let ben_balance = cash.balance(&router, ben.clone()).unwrap(); + assert_eq!(ben_balance, Uint128(1200)); +} diff --git a/contracts/cw20-escrow/src/lib.rs b/contracts/cw20-escrow/src/lib.rs index 33ecec4df..6d596e1ca 100644 --- a/contracts/cw20-escrow/src/lib.rs +++ b/contracts/cw20-escrow/src/lib.rs @@ -1,5 +1,6 @@ pub mod contract; mod error; +mod integration_test; pub mod msg; pub mod state; diff --git a/packages/multi-test/src/handlers.rs b/packages/multi-test/src/handlers.rs index 98a820965..4341575fc 100644 --- a/packages/multi-test/src/handlers.rs +++ b/packages/multi-test/src/handlers.rs @@ -155,8 +155,8 @@ impl Router { /// This is just a helper around execute() pub fn execute_contract>( &mut self, - contract_addr: U, sender: U, + contract_addr: U, msg: &T, send_funds: &[Coin], ) -> Result { @@ -465,7 +465,7 @@ mod test { // do one payout and see money coming in let res = router - .execute_contract(&contract_addr, &random, &EmptyMsg {}, &[]) + .execute_contract(&random, &contract_addr, &EmptyMsg {}, &[]) .unwrap(); assert_eq!(1, res.attributes.len()); assert_eq!(&attr("action", "payout"), &res.attributes[0]); @@ -525,7 +525,7 @@ mod test { messages: vec![msg], }; let res = router - .execute_contract(&reflect_addr, &HumanAddr::from("random"), &msgs, &[]) + .execute_contract(&HumanAddr::from("random"), &reflect_addr, &msgs, &[]) .unwrap(); // ensure the attributes were relayed from the sub-message @@ -583,7 +583,7 @@ mod test { messages: vec![msg], }; let res = router - .execute_contract(&reflect_addr, &random, &msgs, &[]) + .execute_contract(&random, &reflect_addr, &msgs, &[]) .unwrap(); assert_eq!(0, res.attributes.len()); // ensure random got paid @@ -614,7 +614,7 @@ mod test { messages: vec![msg, msg2], }; let err = router - .execute_contract(&reflect_addr, &random, &msgs, &[]) + .execute_contract(&random, &reflect_addr, &msgs, &[]) .unwrap_err(); assert_eq!("Cannot subtract 3 from 0", err.as_str()); diff --git a/packages/multi-test/src/wasm.rs b/packages/multi-test/src/wasm.rs index b5eaefbdf..9b86f86cc 100644 --- a/packages/multi-test/src/wasm.rs +++ b/packages/multi-test/src/wasm.rs @@ -35,30 +35,33 @@ type QueryFn = fn(deps: Deps, env: Env, msg: T) -> Result; /// Wraps the exported functions from a contract and provides the normalized format /// TODO: Allow to customize return values (CustomMsg beyond Empty) -/// TODO: Allow different error types? -pub struct ContractWrapper +pub struct ContractWrapper where T1: DeserializeOwned, T2: DeserializeOwned, T3: DeserializeOwned, - E: std::fmt::Display, + E1: std::fmt::Display, + E2: std::fmt::Display, + E3: std::fmt::Display, { - handle_fn: ContractFn, - init_fn: ContractFn, - query_fn: QueryFn, + handle_fn: ContractFn, + init_fn: ContractFn, + query_fn: QueryFn, } -impl ContractWrapper +impl ContractWrapper where T1: DeserializeOwned, T2: DeserializeOwned, T3: DeserializeOwned, - E: std::fmt::Display, + E1: std::fmt::Display, + E2: std::fmt::Display, + E3: std::fmt::Display, { pub fn new( - handle_fn: ContractFn, - init_fn: ContractFn, - query_fn: QueryFn, + handle_fn: ContractFn, + init_fn: ContractFn, + query_fn: QueryFn, ) -> Self { ContractWrapper { handle_fn, @@ -68,12 +71,14 @@ where } } -impl Contract for ContractWrapper +impl Contract for ContractWrapper where T1: DeserializeOwned, T2: DeserializeOwned, T3: DeserializeOwned, - E: std::fmt::Display, + E1: std::fmt::Display, + E2: std::fmt::Display, + E3: std::fmt::Display, { fn handle( &self, @@ -311,7 +316,8 @@ impl<'a> WasmCache<'a> { // TODO: better addr generation fn next_address(&self) -> HumanAddr { let count = self.router.contracts.len() + self.state.contracts.len(); - HumanAddr::from(count.to_string()) + // we make this longer so it is not rejected by tests + HumanAddr::from("Contract #".to_string() + &count.to_string()) } pub fn handle(