Skip to content

Commit

Permalink
Merge pull request #137 from CosmWasm/multi-test-example
Browse files Browse the repository at this point in the history
Multi test example
  • Loading branch information
ethanfrey committed Nov 16, 2020
2 parents 87c9b8d + 04cf9fb commit 564817d
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 19 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions contracts/cw20-escrow/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
141 changes: 141 additions & 0 deletions contracts/cw20-escrow/src/integration_test.rs
Original file line number Diff line number Diff line change
@@ -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<dyn Contract> {
let contract = ContractWrapper::new(
crate::contract::handle,
crate::contract::init,
crate::contract::query,
);
Box::new(contract)
}

pub fn contract_cw20() -> Box<dyn Contract> {
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));
}
1 change: 1 addition & 0 deletions contracts/cw20-escrow/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod contract;
mod error;
mod integration_test;
pub mod msg;
pub mod state;

Expand Down
10 changes: 5 additions & 5 deletions packages/multi-test/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,8 @@ impl Router {
/// This is just a helper around execute()
pub fn execute_contract<T: Serialize, U: Into<HumanAddr>>(
&mut self,
contract_addr: U,
sender: U,
contract_addr: U,
msg: &T,
send_funds: &[Coin],
) -> Result<RouterResponse, String> {
Expand Down Expand Up @@ -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]);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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());

Expand Down
34 changes: 20 additions & 14 deletions packages/multi-test/src/wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,30 +35,33 @@ type QueryFn<T, E> = fn(deps: Deps, env: Env, msg: T) -> Result<Binary, E>;

/// 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<T1, T2, T3, E>
pub struct ContractWrapper<T1, T2, T3, E1, E2, E3>
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<T1, HandleResponse, E>,
init_fn: ContractFn<T2, InitResponse, E>,
query_fn: QueryFn<T3, E>,
handle_fn: ContractFn<T1, HandleResponse, E1>,
init_fn: ContractFn<T2, InitResponse, E2>,
query_fn: QueryFn<T3, E3>,
}

impl<T1, T2, T3, E> ContractWrapper<T1, T2, T3, E>
impl<T1, T2, T3, E1, E2, E3> ContractWrapper<T1, T2, T3, E1, E2, E3>
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<T1, HandleResponse, E>,
init_fn: ContractFn<T2, InitResponse, E>,
query_fn: QueryFn<T3, E>,
handle_fn: ContractFn<T1, HandleResponse, E1>,
init_fn: ContractFn<T2, InitResponse, E2>,
query_fn: QueryFn<T3, E3>,
) -> Self {
ContractWrapper {
handle_fn,
Expand All @@ -68,12 +71,14 @@ where
}
}

impl<T1, T2, T3, E> Contract for ContractWrapper<T1, T2, T3, E>
impl<T1, T2, T3, E1, E2, E3> Contract for ContractWrapper<T1, T2, T3, E1, E2, E3>
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,
Expand Down Expand Up @@ -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(
Expand Down

0 comments on commit 564817d

Please sign in to comment.