diff --git a/contracts/cw20-escrow/src/integration_test.rs b/contracts/cw20-escrow/src/integration_test.rs index d2412d578..4733adf77 100644 --- a/contracts/cw20-escrow/src/integration_test.rs +++ b/contracts/cw20-escrow/src/integration_test.rs @@ -105,12 +105,16 @@ fn escrow_happy_path_cw20_tokens() { let res = router .execute_contract(owner.clone(), cash_addr.clone(), &send_msg, &[]) .unwrap(); - assert_eq!(2, res.events.len()); + assert_eq!(4, res.events.len()); println!("{:?}", res.events); - let cw20_attr = res.custom_attrs(0); + + assert_eq!(res.events[0].ty.as_str(), "execute"); + let cw20_attr = res.custom_attrs(1); println!("{:?}", cw20_attr); assert_eq!(4, cw20_attr.len()); - let escrow_attr = res.custom_attrs(1); + + assert_eq!(res.events[2].ty.as_str(), "execute"); + let escrow_attr = res.custom_attrs(3); println!("{:?}", escrow_attr); assert_eq!(2, escrow_attr.len()); diff --git a/contracts/cw3-flex-multisig/src/contract.rs b/contracts/cw3-flex-multisig/src/contract.rs index 53d26fee3..37e42ed88 100644 --- a/contracts/cw3-flex-multisig/src/contract.rs +++ b/contracts/cw3-flex-multisig/src/contract.rs @@ -731,7 +731,7 @@ mod tests { .execute_contract(Addr::unchecked(VOTER3), flex_addr.clone(), &proposal, &[]) .unwrap(); assert_eq!( - res.custom_attrs(0), + res.custom_attrs(1), [ ("action", "propose"), ("sender", VOTER3), @@ -745,7 +745,7 @@ mod tests { .execute_contract(Addr::unchecked(VOTER4), flex_addr, &proposal, &[]) .unwrap(); assert_eq!( - res.custom_attrs(0), + res.custom_attrs(1), [ ("action", "propose"), ("sender", VOTER4), @@ -812,7 +812,7 @@ mod tests { let res = app .execute_contract(Addr::unchecked(VOTER1), flex_addr.clone(), &proposal, &[]) .unwrap(); - let proposal_id1: u64 = res.custom_attrs(0)[2].value.parse().unwrap(); + let proposal_id1: u64 = res.custom_attrs(1)[2].value.parse().unwrap(); // another proposal immediately passes app.update_block(next_block); @@ -820,7 +820,7 @@ mod tests { let res = app .execute_contract(Addr::unchecked(VOTER3), flex_addr.clone(), &proposal, &[]) .unwrap(); - let proposal_id2: u64 = res.custom_attrs(0)[2].value.parse().unwrap(); + let proposal_id2: u64 = res.custom_attrs(1)[2].value.parse().unwrap(); // expire them both app.update_block(expire(voting_period)); @@ -830,7 +830,7 @@ mod tests { let res = app .execute_contract(Addr::unchecked(VOTER2), flex_addr.clone(), &proposal, &[]) .unwrap(); - let proposal_id3: u64 = res.custom_attrs(0)[2].value.parse().unwrap(); + let proposal_id3: u64 = res.custom_attrs(1)[2].value.parse().unwrap(); let proposed_at = app.block_info(); // next block, let's query them all... make sure status is properly updated (1 should be rejected in query) @@ -910,7 +910,7 @@ mod tests { .unwrap(); // Get the proposal id from the logs - let proposal_id: u64 = res.custom_attrs(0)[2].value.parse().unwrap(); + let proposal_id: u64 = res.custom_attrs(1)[2].value.parse().unwrap(); // Owner cannot vote (again) let yes_vote = ExecuteMsg::Vote { @@ -933,7 +933,7 @@ mod tests { .execute_contract(Addr::unchecked(VOTER1), flex_addr.clone(), &yes_vote, &[]) .unwrap(); assert_eq!( - res.custom_attrs(0), + res.custom_attrs(1), [ ("action", "vote"), ("sender", VOTER1), @@ -986,7 +986,7 @@ mod tests { .execute_contract(Addr::unchecked(VOTER4), flex_addr.clone(), &yes_vote, &[]) .unwrap(); assert_eq!( - res.custom_attrs(0), + res.custom_attrs(1), [ ("action", "vote"), ("sender", VOTER4), @@ -1066,7 +1066,7 @@ mod tests { .unwrap(); // Get the proposal id from the logs - let proposal_id: u64 = res.custom_attrs(0)[2].value.parse().unwrap(); + let proposal_id: u64 = res.custom_attrs(1)[2].value.parse().unwrap(); // Only Passed can be executed let execution = ExecuteMsg::Execute { proposal_id }; @@ -1084,7 +1084,7 @@ mod tests { .execute_contract(Addr::unchecked(VOTER3), flex_addr.clone(), &vote, &[]) .unwrap(); assert_eq!( - res.custom_attrs(0), + res.custom_attrs(1), [ ("action", "vote"), ("sender", VOTER3), @@ -1110,7 +1110,7 @@ mod tests { ) .unwrap(); assert_eq!( - res.custom_attrs(0), + res.custom_attrs(1), [ ("action", "execute"), ("sender", SOMEBODY), @@ -1152,7 +1152,7 @@ mod tests { .unwrap(); // Get the proposal id from the logs - let proposal_id: u64 = res.custom_attrs(0)[2].value.parse().unwrap(); + let proposal_id: u64 = res.custom_attrs(1)[2].value.parse().unwrap(); // Non-expired proposals cannot be closed let closing = ExecuteMsg::Close { proposal_id }; @@ -1167,7 +1167,7 @@ mod tests { .execute_contract(Addr::unchecked(SOMEBODY), flex_addr.clone(), &closing, &[]) .unwrap(); assert_eq!( - res.custom_attrs(0), + res.custom_attrs(1), [ ("action", "close"), ("sender", SOMEBODY), @@ -1204,7 +1204,7 @@ mod tests { .execute_contract(Addr::unchecked(VOTER1), flex_addr.clone(), &proposal, &[]) .unwrap(); // Get the proposal id from the logs - let proposal_id: u64 = res.custom_attrs(0)[2].value.parse().unwrap(); + let proposal_id: u64 = res.custom_attrs(1)[2].value.parse().unwrap(); let prop_status = |app: &App, proposal_id: u64| -> Status { let query_prop = QueryMsg::Proposal { proposal_id }; let prop: ProposalResponse = app @@ -1265,7 +1265,7 @@ mod tests { .execute_contract(Addr::unchecked(VOTER1), flex_addr.clone(), &proposal2, &[]) .unwrap(); // Get the proposal id from the logs - let proposal_id2: u64 = res.custom_attrs(0)[2].value.parse().unwrap(); + let proposal_id2: u64 = res.custom_attrs(1)[2].value.parse().unwrap(); // VOTER2 can pass this alone with the updated vote (newer height ignores snapshot) let yes_vote = ExecuteMsg::Vote { @@ -1346,7 +1346,7 @@ mod tests { ) .unwrap(); // Get the proposal id from the logs - let update_proposal_id: u64 = res.custom_attrs(0)[2].value.parse().unwrap(); + let update_proposal_id: u64 = res.custom_attrs(1)[2].value.parse().unwrap(); // next block... app.update_block(|b| b.height += 1); @@ -1362,7 +1362,7 @@ mod tests { ) .unwrap(); // Get the proposal id from the logs - let cash_proposal_id: u64 = res.custom_attrs(0)[2].value.parse().unwrap(); + let cash_proposal_id: u64 = res.custom_attrs(1)[2].value.parse().unwrap(); assert_ne!(cash_proposal_id, update_proposal_id); // query proposal state @@ -1455,7 +1455,7 @@ mod tests { .execute_contract(Addr::unchecked(VOTER3), flex_addr.clone(), &proposal, &[]) .unwrap(); // Get the proposal id from the logs - let proposal_id: u64 = res.custom_attrs(0)[2].value.parse().unwrap(); + let proposal_id: u64 = res.custom_attrs(1)[2].value.parse().unwrap(); let prop_status = |app: &App| -> Status { let query_prop = QueryMsg::Proposal { proposal_id }; let prop: ProposalResponse = app @@ -1499,7 +1499,7 @@ mod tests { .execute_contract(Addr::unchecked(newbie), flex_addr.clone(), &proposal, &[]) .unwrap(); // Get the proposal id from the logs - let proposal_id2: u64 = res.custom_attrs(0)[2].value.parse().unwrap(); + let proposal_id2: u64 = res.custom_attrs(1)[2].value.parse().unwrap(); // check proposal2 status let query_prop = QueryMsg::Proposal { @@ -1537,7 +1537,7 @@ mod tests { .execute_contract(Addr::unchecked(VOTER3), flex_addr.clone(), &proposal, &[]) .unwrap(); // Get the proposal id from the logs - let proposal_id: u64 = res.custom_attrs(0)[2].value.parse().unwrap(); + let proposal_id: u64 = res.custom_attrs(1)[2].value.parse().unwrap(); let prop_status = |app: &App| -> Status { let query_prop = QueryMsg::Proposal { proposal_id }; let prop: ProposalResponse = app @@ -1606,7 +1606,7 @@ mod tests { .execute_contract(Addr::unchecked(VOTER5), flex_addr.clone(), &proposal, &[]) .unwrap(); // Get the proposal id from the logs - let proposal_id: u64 = res.custom_attrs(0)[2].value.parse().unwrap(); + let proposal_id: u64 = res.custom_attrs(1)[2].value.parse().unwrap(); let prop_status = |app: &App| -> Status { let query_prop = QueryMsg::Proposal { proposal_id }; let prop: ProposalResponse = app diff --git a/packages/multi-test/README.md b/packages/multi-test/README.md index dc24087fb..0ecfff56b 100644 --- a/packages/multi-test/README.md +++ b/packages/multi-test/README.md @@ -1,8 +1,19 @@ # Multi Test: Test helpers for multi-contract interactions +Warning: **Alpha Software** Designed for internal use only. + +This is used for testing cosmwasm-plus contracts, we have no API +stability currently. We are working on refactoring it and will +expose a more refined version for use in other contracts. (Ideally +in cosmwasm-plus 0.9 or 0.10). + +**Use at your own risk** + Let us run unit tests with contracts calling contracts, and calling in and out of bank. This only works with contracts and bank currently. We are working on refactoring to make it more extensible for more handlers, including custom messages/queries as well as IBC. + + diff --git a/packages/multi-test/src/app.rs b/packages/multi-test/src/app.rs index ea8139ff3..73b7725f3 100644 --- a/packages/multi-test/src/app.rs +++ b/packages/multi-test/src/app.rs @@ -281,8 +281,8 @@ where mod test { use cosmwasm_std::testing::MockStorage; use cosmwasm_std::{ - attr, coin, coins, to_binary, AllBalanceResponse, Attribute, BankMsg, BankQuery, Event, - Reply, StdResult, SubMsg, WasmMsg, + coin, coins, to_binary, AllBalanceResponse, Attribute, BankMsg, BankQuery, Event, Reply, + StdResult, SubMsg, WasmMsg, }; use crate::test_helpers::contracts::{echo, hackatom, payout, reflect}; @@ -420,14 +420,23 @@ mod test { let res = app .execute_contract(random.clone(), contract_addr.clone(), &EmptyMsg {}, &[]) .unwrap(); - assert_eq!(2, res.events.len()); - let custom_attrs = res.custom_attrs(0); - assert_eq!(&[attr("action", "payout")], &custom_attrs); + assert_eq!(3, res.events.len()); + + // the call to payout does emit this as well as custom attributes + let payout_exec = &res.events[0]; + assert_eq!(payout_exec.ty.as_str(), "execute"); + assert_eq!(payout_exec.attributes, [("_contract_addr", &contract_addr)]); + + // next is a custom wasm event + let custom_attrs = res.custom_attrs(1); + assert_eq!(custom_attrs, [("action", "payout")]); + + // then the transfer event let expected_transfer = Event::new("transfer") .add_attribute("recipient", "random") .add_attribute("sender", &contract_addr) .add_attribute("amount", "5eth"); - assert_eq!(&expected_transfer, &res.events[1]); + assert_eq!(&expected_transfer, &res.events[2]); // random got cash let funds = get_balance(&app, &random); @@ -492,27 +501,35 @@ mod test { .unwrap(); // ensure the attributes were relayed from the sub-message - assert_eq!(3, res.events.len(), "{:?}", res.events); - // first event was the call to reflect - let first = &res.events[0]; - assert_eq!(first.ty.as_str(), "wasm"); - assert_eq!(1, first.attributes.len()); - assert_eq!( - &attr("contract_address", &reflect_addr), - &first.attributes[0] - ); - // second event was call to payout - let second = &res.events[1]; - assert_eq!(second.ty.as_str(), "wasm"); - assert_eq!(2, second.attributes.len()); + assert_eq!(4, res.events.len(), "{:?}", res.events); + + // reflect only returns standard wasm-execute event + let ref_exec = &res.events[0]; + assert_eq!(ref_exec.ty.as_str(), "execute"); + assert_eq!(ref_exec.attributes, [("_contract_addr", &reflect_addr)]); + + // the call to payout does emit this as well as custom attributes + let payout_exec = &res.events[1]; + assert_eq!(payout_exec.ty.as_str(), "execute"); + assert_eq!(payout_exec.attributes, [("_contract_addr", &payout_addr)]); + + let payout = &res.events[2]; + assert_eq!(payout.ty.as_str(), "wasm"); assert_eq!( - &attr("contract_address", &payout_addr), - &second.attributes[0] + payout.attributes, + [ + ("_contract_addr", payout_addr.as_str()), + ("action", "payout") + ] ); - assert_eq!(&attr("action", "payout"), &second.attributes[1]); - // third event is the transfer from bank - let third = &res.events[2]; - assert_eq!(third.ty.as_str(), "transfer"); + + // final event is the transfer from bank + let second = &res.events[3]; + assert_eq!(second.ty.as_str(), "transfer"); + assert_eq!(3, second.attributes.len()); + assert_eq!(second.attributes[0], ("recipient", &reflect_addr)); + assert_eq!(second.attributes[1], ("sender", &payout_addr)); + assert_eq!(second.attributes[2], ("amount", "5eth")); // ensure transfer was executed with reflect as sender let funds = get_balance(&app, &reflect_addr); @@ -564,12 +581,13 @@ mod test { let res = app .execute_contract(random.clone(), reflect_addr.clone(), &msgs, &[]) .unwrap(); - // only one wasm event with no custom attributes + // no wasm events as no attributes assert_eq!(2, res.events.len()); - assert_eq!(1, res.events[0].attributes.len()); - assert_eq!("wasm", res.events[0].ty.as_str()); - assert_eq!("contract_address", res.events[0].attributes[0].key.as_str()); - // second event is the transfer from bank + // standard wasm-execute event + let exec = &res.events[0]; + assert_eq!(exec.ty.as_str(), "execute"); + assert_eq!(exec.attributes, [("_contract_addr", &reflect_addr)]); + // only transfer event from bank let transfer = &res.events[1]; assert_eq!(transfer.ty.as_str(), "transfer"); @@ -689,25 +707,48 @@ mod test { let res = app .execute_contract(random.clone(), reflect_addr.clone(), &msgs, &[]) .unwrap(); - // we should get 2 events, the wasm one and the custom event - assert_eq!(3, res.events.len(), "{:?}", res.events); - // the first one is just the standard wasm message with custom_address (no more attrs) - let attrs = res.custom_attrs(0); - assert_eq!(0, attrs.len()); - // second event is the transfer from bank + + // expected events: execute, transfer, reply, custom wasm (set in reply) + assert_eq!(4, res.events.len(), "{:?}", res.events); + let first = &res.events[0]; + assert_eq!(first.ty.as_str(), "execute"); + assert_eq!(first.attributes, [("_contract_addr", &reflect_addr)]); + + // next event is the transfer from bank let transfer = &res.events[1]; assert_eq!(transfer.ty.as_str(), "transfer"); - // the third one is a custom event (from reply) - let custom = &res.events[2]; + + // then we get notification reply was called + let reply = &res.events[2]; + assert_eq!(reply.ty.as_str(), "reply"); + assert_eq!( + reply.attributes, + [ + ("_contract_addr", reflect_addr.as_str()), + ("mode", "handle_success") + ] + ); + + // the last one is a custom event (from reply) + let custom = &res.events[3]; assert_eq!("wasm-custom", custom.ty.as_str()); - assert_eq!(2, custom.attributes.len()); - assert_eq!(&attr("from", "reply"), &custom.attributes[0]); - assert_eq!(&attr("to", "test"), &custom.attributes[1]); + assert_eq!( + custom.attributes, + [ + // TODO + ("_contract_addr", reflect_addr.as_str()), + ("from", "reply"), + ("to", "test") + ] + ); // ensure success was written let res: Reply = app.wrap().query_wasm_smart(&reflect_addr, &query).unwrap(); assert_eq!(res.id, 123); - assert!(res.result.is_ok()); + // validate the events written in the reply blob...should just be bank transfer + let reply_events = res.result.unwrap().events; + assert_eq!(1, reply_events.len()); + assert_eq!("transfer", &reply_events[0].ty); // reflect sends 300 btc, failure, but error caught by submessage (so shows success) let msg = SubMsg::reply_always( diff --git a/packages/multi-test/src/executor.rs b/packages/multi-test/src/executor.rs index 380029f84..645535e03 100644 --- a/packages/multi-test/src/executor.rs +++ b/packages/multi-test/src/executor.rs @@ -14,6 +14,7 @@ pub struct AppResponse { impl AppResponse { // Return all custom attributes returned by the contract in the `idx` event. // We assert the type is wasm, and skip the contract_address attribute. + #[track_caller] pub fn custom_attrs(&self, idx: usize) -> &[Attribute] { assert_eq!(self.events[idx].ty.as_str(), "wasm"); &self.events[idx].attributes[1..] diff --git a/packages/multi-test/src/wasm.rs b/packages/multi-test/src/wasm.rs index c000e39a3..c8dff9ccb 100644 --- a/packages/multi-test/src/wasm.rs +++ b/packages/multi-test/src/wasm.rs @@ -18,11 +18,13 @@ use crate::app::{Router, RouterQuerier}; use crate::contracts::Contract; use crate::executor::AppResponse; use crate::transactions::transactional; +use cosmwasm_std::testing::mock_wasmd_attr; // Contract state is kept in Storage, separate from the contracts themselves const CONTRACTS: Map<&Addr, ContractData> = Map::new("contracts"); pub const NAMESPACE_WASM: &[u8] = b"wasm"; +const CONTRACT_ATTR: &str = "_contract_addr"; /// Contract Data includes information about contract, equivalent of `ContractInfo` in wasmd /// interface. @@ -135,8 +137,11 @@ where sender: Addr, msg: WasmMsg, ) -> Result { - let (resender, res) = self.execute_wasm(api, storage, router, block, sender, msg)?; - self.process_response(api, router, storage, block, resender, res, false) + let (resender, res, custom_event) = + self.execute_wasm(api, storage, router, block, sender, msg)?; + + let (res, msgs) = self.build_app_response(&resender, custom_event, res); + self.process_response(api, router, storage, block, resender, res, msgs) } fn store_code(&mut self, code: Box>) -> usize { @@ -152,14 +157,17 @@ where fn sudo( &self, api: &dyn Api, - contract_addr: Addr, + contract: Addr, storage: &mut dyn Storage, router: &Router, block: &BlockInfo, msg: Vec, ) -> Result { - let res = self.call_sudo(contract_addr.clone(), api, storage, router, block, msg)?; - self.process_response(api, router, storage, block, contract_addr, res, false) + let custom_event = Event::new("sudo").add_attribute(CONTRACT_ATTR, &contract); + + let res = self.call_sudo(contract.clone(), api, storage, router, block, msg)?; + let (res, msgs) = self.build_app_response(&contract, custom_event, res); + self.process_response(api, router, storage, block, contract, res, msgs) } } @@ -227,7 +235,7 @@ where block: &BlockInfo, sender: Addr, wasm_msg: WasmMsg, - ) -> Result<(Addr, Response), String> { + ) -> Result<(Addr, Response, Event), String> { match wasm_msg { WasmMsg::Execute { contract_addr, @@ -259,7 +267,10 @@ where info, msg.to_vec(), )?; - Ok((contract_addr, res)) + + let custom_event = + Event::new("execute").add_attribute(CONTRACT_ATTR, &contract_addr); + Ok((contract_addr, res, custom_event)) } WasmMsg::Instantiate { admin, @@ -304,7 +315,11 @@ where msg.to_vec(), )?; init_response(&mut res, &contract_addr); - Ok((contract_addr, res)) + + let custom_event = Event::new("instantiate") + .add_attribute(CONTRACT_ATTR, &contract_addr) + .add_attribute("code_id", code_id.to_string()); + Ok((contract_addr, res, custom_event)) } WasmMsg::Migrate { contract_addr, @@ -339,7 +354,11 @@ where block, msg.to_vec(), )?; - Ok((contract_addr, res)) + + let custom_event = Event::new("migrate") + .add_attribute(CONTRACT_ATTR, &contract_addr) + .add_attribute("code_id", new_code_id.to_string()); + Ok((contract_addr, res, custom_event)) } m => panic!("Unsupported wasm message: {:?}", m), } @@ -416,8 +435,64 @@ where contract: Addr, reply: Reply, ) -> Result { + let ok_attr = if reply.result.is_ok() { + "handle_success" + } else { + "handle_failure" + }; + let custom_event = Event::new("reply") + .add_attribute(CONTRACT_ATTR, &contract) + .add_attribute("mode", ok_attr); + let res = self.call_reply(contract.clone(), api, storage, router, block, reply)?; - self.process_response(api, router, storage, block, contract, res, true) + let (res, msgs) = self.build_app_response(&contract, custom_event, res); + self.process_response(api, router, storage, block, contract, res, msgs) + } + + // this captures all the events and data from the contract call. + // it does not handle the messages + fn build_app_response( + &self, + contract: &Addr, + custom_event: Event, // entry-point specific custom event added by x/wasm + response: Response, + ) -> (AppResponse, Vec>) { + let Response { + messages, + attributes, + events, + data, + .. + } = response; + + // always add custom event + let mut app_events = Vec::with_capacity(2 + events.len()); + app_events.push(custom_event); + + // we only emit the `wasm` event if some attributes are specified + if !attributes.is_empty() { + // turn attributes into event and place it first + let wasm_event = Event::new("wasm") + .add_attribute(CONTRACT_ATTR, contract) + .add_attributes(attributes); + app_events.push(wasm_event); + } + + // These need to get `wasm-` prefix to match the wasmd semantics (custom wasm messages cannot + // fake system level event types, like transfer from the bank module) + let wasm_events = events.into_iter().map(|mut ev| { + ev.ty = format!("wasm-{}", ev.ty); + ev.attributes + .insert(0, mock_wasmd_attr(CONTRACT_ATTR, contract)); + ev + }); + app_events.extend(wasm_events); + + let app = AppResponse { + events: app_events, + data, + }; + (app, messages) } fn process_response( @@ -427,39 +502,18 @@ where storage: &mut dyn Storage, block: &BlockInfo, contract: Addr, - response: Response, - ignore_attributes: bool, + response: AppResponse, + messages: Vec>, ) -> Result { - // These need to get `wasm-` prefix to match the wasmd semantics (custom wasm messages cannot - // fake system level event types, like transfer from the bank module) - let mut events: Vec<_> = response - .events - .into_iter() - .map(|mut ev| { - ev.ty = format!("wasm-{}", ev.ty); - ev - }) - .collect(); - // hmmm... we don't need this for reply, right? - if !ignore_attributes { - // turn attributes into event and place it first - let mut wasm_event = Event::new("wasm").add_attribute("contract_address", &contract); - wasm_event - .attributes - .extend_from_slice(&response.attributes); - events.insert(0, wasm_event); - } + let AppResponse { mut events, data } = response; // recurse in all messages - let data = response - .messages - .into_iter() - .try_fold(response.data, |data, resend| { - let subres = - self.execute_submsg(api, router, storage, block, contract.clone(), resend)?; - events.extend_from_slice(&subres.events); - Ok::<_, String>(subres.data.or(data)) - })?; + let data = messages.into_iter().try_fold(data, |data, resend| { + let subres = + self.execute_submsg(api, router, storage, block, contract.clone(), resend)?; + events.extend_from_slice(&subres.events); + Ok::<_, String>(subres.data.or(data)) + })?; Ok(AppResponse { events, data }) }