use serde::{Deserialize, Serialize}; use crate::{ bin_serde::adapter::SerdeAdapter, storage::{SingleItem, ItemSpace, TypedKey}, ensemble::{ ContractEnsemble, ContractHarness, MockEnv, AnyResult, anyhow::{bail, anyhow}, response::ResponseVariants, EnsembleResult } }; use crate::prelude::*; const SENDER: &str = "sender"; const A_ADDR: &str = "a"; const B_ADDR: &str = "b"; const C_ADDR: &str = "c"; const EVENT_TYPE: &str = "MSG_ORDER"; struct Contract; #[derive(Serialize, Deserialize)] struct InstantiateMsg { reply_fail_id: Option } #[derive(Serialize, Deserialize)] enum ExecuteMsg { RunMsgs(Vec), IncrNumber(u32), IncrAndSend { amount: u32, recipient: String }, Fail, ReplyResponse(SubMsg), ReplyData { id: u64, data: Binary } } #[derive(Serialize, Deserialize)] enum QueryMsg { State, Reply(u64) } #[derive(Serialize, Deserialize)] struct StateResponse { num: u32, balance: Coin } crate::namespace!(ReplyDataNs, b"reply_data_"); const REPLY_DATA: ItemSpace> = ItemSpace::new(); crate::namespace!(ReplyNs, b"reply"); const REPLY_RESP: SingleItem, ReplyNs> = SingleItem::new(); const REPLIES: ItemSpace, ReplyNs, TypedKey> = ItemSpace::new(); impl ContractHarness for Contract { fn instantiate(&self, deps: DepsMut, _env: Env, _info: MessageInfo, msg: Binary) -> AnyResult { let msg: InstantiateMsg = from_binary(&msg)?; if let Some(id) = msg.reply_fail_id { storage::save(deps.storage, b"fail", &id)?; } Ok(Response::default().add_attribute("INSTANTIATE", "test")) } fn execute(&self, deps: DepsMut, env: Env, info: MessageInfo, msg: Binary) -> AnyResult { let msg: ExecuteMsg = from_binary(&msg)?; let mut resp = Response::default() .add_attribute("TEST_ATTR", "test") .add_attribute("ANOTHER_TEST_ATTR", "test") .add_event( Event::new(EVENT_TYPE).add_attribute( "submsg_execute", format!("sender: {}\ncontract: {}", info.sender, env.contract.address) ) ); match msg { ExecuteMsg::RunMsgs(msgs) => { resp = resp.add_submessages(msgs); } ExecuteMsg::IncrNumber(amount) => { resp.data = Some(increment_data(amount)); increment(deps.storage, amount)?; } ExecuteMsg::IncrAndSend { amount, recipient } => { increment(deps.storage, amount)?; resp = resp.add_message(BankMsg::Send { to_address: recipient, amount: vec![coin(100, "uscrt")] }) } ExecuteMsg::ReplyResponse(msg) => { REPLY_RESP.save(deps.storage, &msg.into())?; } ExecuteMsg::ReplyData { id, data } => { REPLY_DATA.save(deps.storage, &id, &data)?; } ExecuteMsg::Fail => bail!(StdError::generic_err("Fail")) } Ok(resp) } fn query(&self, deps: Deps, env: Env, msg: Binary) -> AnyResult { let msg: QueryMsg = from_binary(&msg)?; let result = match msg { QueryMsg::State => { let num: u32 = storage::load(deps.storage, b"num")?.unwrap_or_default(); let balance = deps.querier.query_balance(env.contract.address, "uscrt")?; let resp = StateResponse { num, balance }; to_binary(&resp) }, QueryMsg::Reply(id) => { let resp = REPLIES.load(deps.storage, &id)?; if let Some(resp) = resp { to_binary(&resp.0) } else { bail!(no_reply_stored_err(id)) } } }; result.map_err(|x| anyhow!(x)) } fn reply(&self, deps: DepsMut, env: Env, reply: Reply) -> AnyResult { let fail_id: Option = storage::load(deps.storage, b"fail")?; if let Some(id) = fail_id { if id == reply.id { bail!(StdError::generic_err("Failed in reply.")) } } let result_ok = reply.result.is_ok(); if let SubMsgResult::Ok(resp) = reply.result { REPLIES.save(deps.storage, &reply.id, &resp.into())?; } let mut response = Response::default().add_event( Event::new(EVENT_TYPE) .add_attribute( "submsg_reply", format!( "address: {}, id: {}, success: {}", env.contract.address, reply.id, result_ok ) ) ); if let Some(msg) = REPLY_RESP.load(deps.storage)? { response.messages.push(msg.0); REPLY_RESP.remove(deps.storage); } if let Some(data) = REPLY_DATA.load(deps.storage, &reply.id)? { response.data = Some(data); } Ok(response) } } fn increment(storage: &mut dyn Storage, amount: u32) -> StdResult<()> { let mut num: u32 = storage::load(storage, b"num")?.unwrap_or_default(); num += amount; storage::save(storage, b"num", &num)?; if num > 10 { Err(StdError::generic_err("Number is bigger than 10.")) } else { Ok(()) } } fn no_reply_stored_err(id: u64) -> StdError { StdError::generic_err(format!("No reply response for id {}", id)) } fn increment_data(amount: u32) -> Binary { Binary::from(format!("Increment amount: {}", amount).as_bytes()) } fn reply_data(id: u64) -> Binary { Binary::from(format!("Overwrite data in reply for id: {}", id).as_bytes()) } struct TestContracts { ensemble: ContractEnsemble, a: ContractLink, b: ContractLink, c: ContractLink } impl TestContracts { fn a_state(&self) -> StateResponse { self.ensemble.query(&self.a.address, &QueryMsg::State).unwrap() } fn b_state(&self) -> StateResponse { self.ensemble.query(&self.b.address, &QueryMsg::State).unwrap() } fn c_state(&self) -> StateResponse { self.ensemble.query(&self.c.address, &QueryMsg::State).unwrap() } fn a_events(&self, id: u64) -> EnsembleResult { self.ensemble.query(&self.a.address, &QueryMsg::Reply(id)) } fn b_events(&self, id: u64) -> EnsembleResult { self.ensemble.query(&self.b.address, &QueryMsg::Reply(id)) } fn c_events(&self, id: u64) -> EnsembleResult { self.ensemble.query(&self.c.address, &QueryMsg::Reply(id)) } } #[test] fn correct_message_order() { let mut c = init([None, None, None]); let msg = ExecuteMsg::RunMsgs(vec![ SubMsg::reply_always( b_msg( &ExecuteMsg::RunMsgs(vec![ SubMsg::new(a_msg(&ExecuteMsg::IncrNumber(1))) ]) ), 0 ), SubMsg::reply_always(b_msg(&ExecuteMsg::IncrNumber(2)), 1), SubMsg::new(c_msg(&ExecuteMsg::IncrNumber(3))) ]); // https://github.com/CosmWasm/cosmwasm/blob/main/SEMANTICS.md#order-and-rollback // Contract A returns submessages S1 and S2, and message M1. // Submessage S1 returns message N1. // The order will be: S1, N1, reply(S1), S2, reply(S2), M1 let resp = c.ensemble.execute(&msg, MockEnv::new(SENDER, c.a.address.clone())).unwrap(); let mut resp = resp.iter(); let next = resp.next().unwrap(); assert!(next.is_execute()); // S1 if let ResponseVariants::Execute(resp) = next { assert_eq!(resp.address, B_ADDR); assert_eq!(resp.sender, A_ADDR); } let next = resp.next().unwrap(); assert!(next.is_execute()); // N1 if let ResponseVariants::Execute(resp) = next { assert_eq!(resp.address, A_ADDR); assert_eq!(resp.sender, B_ADDR); } let next = resp.next().unwrap(); assert!(next.is_reply()); // reply(S1) if let ResponseVariants::Reply(resp) = next { assert_eq!(resp.address, A_ADDR); assert_eq!(resp.reply.id, 0); } let next = resp.next().unwrap(); assert!(next.is_execute()); // S2 if let ResponseVariants::Execute(resp) = next { assert_eq!(resp.address, B_ADDR); assert_eq!(resp.sender, A_ADDR); } let next = resp.next().unwrap(); assert!(next.is_reply()); // reply(S2) if let ResponseVariants::Reply(resp) = next { assert_eq!(resp.address, A_ADDR); assert_eq!(resp.reply.id, 1); } let next = resp.next().unwrap(); assert!(next.is_execute()); //M1 if let ResponseVariants::Execute(resp) = next { assert_eq!(resp.address, C_ADDR); assert_eq!(resp.sender, A_ADDR); } assert_eq!(resp.next(), None); let state = c.a_state(); assert_eq!(state.num, 1); let state = c.b_state(); assert_eq!(state.num, 2); let state = c.c_state(); assert_eq!(state.num, 3); } #[test] fn replies_chain_correctly() { let mut c = init([None, None, None]); c.ensemble.execute( &ExecuteMsg::ReplyData { id: 1, data: reply_data(1) }, MockEnv::new(SENDER, c.b.address.clone()) ).unwrap(); let msg = ExecuteMsg::RunMsgs(vec![ SubMsg::reply_always( b_msg( &ExecuteMsg::RunMsgs(vec![ SubMsg::reply_always(a_msg(&ExecuteMsg::IncrNumber(1)), 0) ]) ), 1 ), SubMsg::reply_always(b_msg(&ExecuteMsg::IncrNumber(2)), 2), SubMsg::new(c_msg(&ExecuteMsg::IncrNumber(3))) ]); let resp = c.ensemble.execute(&msg, MockEnv::new(SENDER, c.a.address.clone())).unwrap(); let mut resp = resp.iter(); let next = resp.next().unwrap(); assert!(next.is_execute()); if let ResponseVariants::Execute(resp) = next { assert_eq!(resp.address, B_ADDR); assert_eq!(resp.sender, A_ADDR); } let next = resp.next().unwrap(); assert!(next.is_execute()); if let ResponseVariants::Execute(resp) = next { assert_eq!(resp.address, A_ADDR); assert_eq!(resp.sender, B_ADDR); } let next = resp.next().unwrap(); assert!(next.is_reply()); if let ResponseVariants::Reply(resp) = next { assert_eq!(resp.address, B_ADDR); assert_eq!(resp.reply.id, 0); } let next = resp.next().unwrap(); assert!(next.is_reply()); if let ResponseVariants::Reply(resp) = next { assert_eq!(resp.address, A_ADDR); assert_eq!(resp.reply.id, 1); } let next = resp.next().unwrap(); assert!(next.is_execute()); if let ResponseVariants::Execute(resp) = next { assert_eq!(resp.address, B_ADDR); assert_eq!(resp.sender, A_ADDR); } let next = resp.next().unwrap(); assert!(next.is_reply()); if let ResponseVariants::Reply(resp) = next { assert_eq!(resp.address, A_ADDR); assert_eq!(resp.reply.id, 2); } let next = resp.next().unwrap(); assert!(next.is_execute()); if let ResponseVariants::Execute(resp) = next { assert_eq!(resp.address, C_ADDR); assert_eq!(resp.sender, A_ADDR); } assert_eq!(resp.next(), None); let state = c.a_state(); assert_eq!(state.num, 1); let state = c.b_state(); assert_eq!(state.num, 2); let state = c.c_state(); assert_eq!(state.num, 3); cmp_response_json( &c.b_events(0).unwrap(), "{\"events\":[{\"type\":\"execute\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"a\",\"encrypted\":true}]},{\"type\":\"wasm\",\"attributes\":[{\"key\":\"ANOTHER_TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"contract_address\",\"value\":\"a\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"a\",\"encrypted\":true},{\"key\":\"submsg_execute\",\"value\":\"sender: b\\ncontract: a\",\"encrypted\":true}]}],\"data\":\"SW5jcmVtZW50IGFtb3VudDogMQ==\"}" ); cmp_response_json( &c.a_events(1).unwrap(), "{\"events\":[{\"type\":\"execute\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true}]},{\"type\":\"wasm\",\"attributes\":[{\"key\":\"ANOTHER_TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true},{\"key\":\"submsg_execute\",\"value\":\"sender: a\\ncontract: b\",\"encrypted\":true}]},{\"type\":\"execute\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"a\",\"encrypted\":true}]},{\"type\":\"wasm\",\"attributes\":[{\"key\":\"ANOTHER_TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"contract_address\",\"value\":\"a\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"a\",\"encrypted\":true},{\"key\":\"submsg_execute\",\"value\":\"sender: b\\ncontract: a\",\"encrypted\":true}]},{\"type\":\"reply\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true},{\"key\":\"submsg_reply\",\"value\":\"address: b, id: 0, success: true\",\"encrypted\":true}]}],\"data\":null}" ); cmp_response_json( &c.a_events(2).unwrap(), "{\"events\":[{\"type\":\"execute\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true}]},{\"type\":\"wasm\",\"attributes\":[{\"key\":\"ANOTHER_TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true},{\"key\":\"submsg_execute\",\"value\":\"sender: a\\ncontract: b\",\"encrypted\":true}]}],\"data\":\"SW5jcmVtZW50IGFtb3VudDogMg==\"}" ); assert_eq!( c.a_events(3).unwrap_err().to_string(), no_reply_stored_err(3).to_string() ) } #[test] fn last_data_from_reply_is_passed_to_caller() { let mut c = init([None, None, None]); c.ensemble.execute( &ExecuteMsg::ReplyData { id: 0, data: reply_data(0) }, MockEnv::new(SENDER, c.b.address.clone()) ).unwrap(); c.ensemble.execute( &ExecuteMsg::ReplyData { id: 1, data: reply_data(1) }, MockEnv::new(SENDER, c.b.address.clone()) ).unwrap(); let msg = ExecuteMsg::RunMsgs(vec![ SubMsg::reply_always( b_msg( &ExecuteMsg::RunMsgs(vec![ SubMsg::reply_always(a_msg(&ExecuteMsg::IncrNumber(1)), 0), SubMsg::reply_always(c_msg(&ExecuteMsg::IncrNumber(2)), 1) ]) ), 2 ) ]); c.ensemble.execute(&msg, MockEnv::new(SENDER, c.a.address.clone())).unwrap(); cmp_response_json( &c.b_events(0).unwrap(), "{\"events\":[{\"type\":\"execute\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"a\",\"encrypted\":true}]},{\"type\":\"wasm\",\"attributes\":[{\"key\":\"ANOTHER_TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"contract_address\",\"value\":\"a\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"a\",\"encrypted\":true},{\"key\":\"submsg_execute\",\"value\":\"sender: b\\ncontract: a\",\"encrypted\":true}]}],\"data\":\"SW5jcmVtZW50IGFtb3VudDogMQ==\"}" ); cmp_response_json( &c.b_events(1).unwrap(), "{\"events\":[{\"type\":\"execute\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true}]},{\"type\":\"wasm\",\"attributes\":[{\"key\":\"ANOTHER_TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true},{\"key\":\"submsg_execute\",\"value\":\"sender: b\\ncontract: c\",\"encrypted\":true}]}],\"data\":\"SW5jcmVtZW50IGFtb3VudDogMg==\"}" ); cmp_response_json( &c.a_events(2).unwrap(), "{\"events\":[{\"type\":\"execute\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true}]},{\"type\":\"wasm\",\"attributes\":[{\"key\":\"ANOTHER_TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true},{\"key\":\"submsg_execute\",\"value\":\"sender: a\\ncontract: b\",\"encrypted\":true}]},{\"type\":\"execute\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"a\",\"encrypted\":true}]},{\"type\":\"wasm\",\"attributes\":[{\"key\":\"ANOTHER_TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"contract_address\",\"value\":\"a\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"a\",\"encrypted\":true},{\"key\":\"submsg_execute\",\"value\":\"sender: b\\ncontract: a\",\"encrypted\":true}]},{\"type\":\"reply\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true},{\"key\":\"submsg_reply\",\"value\":\"address: b, id: 0, success: true\",\"encrypted\":true}]},{\"type\":\"execute\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true}]},{\"type\":\"wasm\",\"attributes\":[{\"key\":\"ANOTHER_TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true},{\"key\":\"submsg_execute\",\"value\":\"sender: b\\ncontract: c\",\"encrypted\":true}]},{\"type\":\"reply\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true},{\"key\":\"submsg_reply\",\"value\":\"address: b, id: 1, success: true\",\"encrypted\":true}]}],\"data\":\"T3ZlcndyaXRlIGRhdGEgaW4gcmVwbHkgZm9yIGlkOiAx\"}" ); } #[test] fn reverts_state_when_a_single_message_in_a_submsg_chain_fails() { let mut c = init([None, None, None]); let msg = ExecuteMsg::RunMsgs(vec![ SubMsg::reply_always( b_msg( &ExecuteMsg::RunMsgs(vec![ SubMsg::new(c_msg(&ExecuteMsg::IncrNumber(1))), SubMsg::new(c_msg(&ExecuteMsg::Fail)) ]) ), 0 ), SubMsg::reply_always(b_msg(&ExecuteMsg::IncrNumber(2)), 1) ]); let resp = c.ensemble.execute(&msg, MockEnv::new(SENDER, c.a.address.clone())).unwrap(); let mut resp = resp.iter(); let next = resp.next().unwrap(); // reply(A) - ID: 0 - notice that even though it was successful, // the first sub-message is not included because all state was reverted assert!(next.is_reply()); if let ResponseVariants::Reply(resp) = next { assert_eq!(resp.address, A_ADDR); assert_eq!(resp.reply.id, 0); } let next = resp.next().unwrap(); assert!(next.is_execute()); if let ResponseVariants::Execute(resp) = next { assert_eq!(resp.address, B_ADDR); assert_eq!(resp.sender, A_ADDR); } let next = resp.next().unwrap(); assert!(next.is_reply()); if let ResponseVariants::Reply(resp) = next { assert_eq!(resp.address, A_ADDR); assert_eq!(resp.reply.id, 1); } assert_eq!(resp.next(), None); let state = c.a_state(); assert_eq!(state.num, 0); let state = c.b_state(); assert_eq!(state.num, 2); let state = c.c_state(); assert_eq!(state.num, 0); } #[test] fn only_successful_submsg_state_is_committed() { let mut c = init([None, None, None]); let msg = ExecuteMsg::RunMsgs(vec![ SubMsg::reply_always( b_msg( &ExecuteMsg::RunMsgs(vec![ SubMsg::reply_always(c_msg(&ExecuteMsg::IncrNumber(1)), 0), SubMsg::reply_always(c_msg(&ExecuteMsg::IncrNumber(12)), 1) // This will fail ]) ), 2 ), SubMsg::new(b_msg(&ExecuteMsg::IncrNumber(2))) ]); let resp = c.ensemble.execute(&msg, MockEnv::new(SENDER, c.a.address.clone())).unwrap(); let mut resp = resp.iter(); let next = resp.next().unwrap(); assert!(next.is_execute()); if let ResponseVariants::Execute(resp) = next { assert_eq!(resp.address, B_ADDR); assert_eq!(resp.sender, A_ADDR); } let next = resp.next().unwrap(); assert!(next.is_execute()); if let ResponseVariants::Execute(resp) = next { assert_eq!(resp.address, C_ADDR); assert_eq!(resp.sender, B_ADDR); } let next = resp.next().unwrap(); assert!(next.is_reply()); if let ResponseVariants::Reply(resp) = next { assert_eq!(resp.address, B_ADDR); assert_eq!(resp.reply.id, 0); } let next = resp.next().unwrap(); assert!(next.is_reply()); if let ResponseVariants::Reply(resp) = next { assert_eq!(resp.address, B_ADDR); assert_eq!(resp.reply.id, 1); } let next = resp.next().unwrap(); assert!(next.is_reply()); if let ResponseVariants::Reply(resp) = next { assert_eq!(resp.address, A_ADDR); assert_eq!(resp.reply.id, 2); } let next = resp.next().unwrap(); assert!(next.is_execute()); if let ResponseVariants::Execute(resp) = next { assert_eq!(resp.address, B_ADDR); assert_eq!(resp.sender, A_ADDR); } assert_eq!(resp.next(), None); let state = c.a_state(); assert_eq!(state.num, 0); let state = c.b_state(); assert_eq!(state.num, 2); let state = c.c_state(); assert_eq!(state.num, 1); cmp_response_json( &c.b_events(0).unwrap(), "{\"events\":[{\"type\":\"execute\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true}]},{\"type\":\"wasm\",\"attributes\":[{\"key\":\"ANOTHER_TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true},{\"key\":\"submsg_execute\",\"value\":\"sender: b\\ncontract: c\",\"encrypted\":true}]}],\"data\":\"SW5jcmVtZW50IGFtb3VudDogMQ==\"}" ); assert_eq!( c.b_events(1).unwrap_err().to_string(), no_reply_stored_err(1).to_string() ); cmp_response_json( &c.a_events(2).unwrap(), "{\"events\":[{\"type\":\"execute\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true}]},{\"type\":\"wasm\",\"attributes\":[{\"key\":\"ANOTHER_TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true},{\"key\":\"submsg_execute\",\"value\":\"sender: a\\ncontract: b\",\"encrypted\":true}]},{\"type\":\"execute\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true}]},{\"type\":\"wasm\",\"attributes\":[{\"key\":\"ANOTHER_TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true},{\"key\":\"submsg_execute\",\"value\":\"sender: b\\ncontract: c\",\"encrypted\":true}]},{\"type\":\"reply\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true},{\"key\":\"submsg_reply\",\"value\":\"address: b, id: 0, success: true\",\"encrypted\":true}]},{\"type\":\"reply\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true},{\"key\":\"submsg_reply\",\"value\":\"address: b, id: 1, success: false\",\"encrypted\":true}]}],\"data\":null}" ); } #[test] fn reverts_state_when_reply_in_submsg_fails() { let mut c = init([None, Some(1), None]); let msg = ExecuteMsg::RunMsgs(vec![ SubMsg::reply_always( b_msg( &ExecuteMsg::RunMsgs(vec![ SubMsg::reply_always(c_msg(&ExecuteMsg::IncrNumber(1)), 0), SubMsg::reply_always(c_msg(&ExecuteMsg::IncrNumber(1)), 1), SubMsg::reply_always(c_msg(&ExecuteMsg::IncrNumber(1)), 2), ]) ), 3 ), SubMsg::reply_always(b_msg(&ExecuteMsg::IncrNumber(2)), 4) ]); let resp = c.ensemble.execute(&msg, MockEnv::new(SENDER, c.a.address.clone())).unwrap(); let mut resp = resp.iter(); let next = resp.next().unwrap(); assert!(next.is_reply()); if let ResponseVariants::Reply(resp) = next { assert_eq!(resp.address, A_ADDR); assert_eq!(resp.reply.id, 3); } let next = resp.next().unwrap(); assert!(next.is_execute()); if let ResponseVariants::Execute(resp) = next { assert_eq!(resp.address, B_ADDR); assert_eq!(resp.sender, A_ADDR); } let next = resp.next().unwrap(); assert!(next.is_reply()); if let ResponseVariants::Reply(resp) = next { assert_eq!(resp.address, A_ADDR); assert_eq!(resp.reply.id, 4); } assert_eq!(resp.next(), None); let state = c.a_state(); assert_eq!(state.num, 0); let state = c.b_state(); assert_eq!(state.num, 2); let state = c.c_state(); assert_eq!(state.num, 0); } #[test] fn reply_err_in_root_level_fails_tx() { let mut c = init([Some(2), None, None]); c.ensemble.add_funds(C_ADDR, vec![coin(200, "uscrt")]); let msg = ExecuteMsg::RunMsgs(vec![ SubMsg::reply_always( b_msg( &ExecuteMsg::RunMsgs(vec![ SubMsg::reply_always( c_msg(&ExecuteMsg::IncrAndSend { amount: 1, recipient: A_ADDR.into() }), 0 ), SubMsg::reply_always(c_msg(&ExecuteMsg::Fail), 1), ]) ), 2 ), SubMsg::reply_always(b_msg(&ExecuteMsg::IncrNumber(2)), 3), SubMsg::new(c_msg(&ExecuteMsg::IncrNumber(5))) ]); let err = c.ensemble.execute(&msg, MockEnv::new(SENDER, c.a.address.clone())).unwrap_err(); assert_eq!(err.to_string(), "Generic error: Failed in reply."); let state = c.a_state(); assert_eq!(state.num, 0); assert_eq!(state.balance.amount.u128(), 0); let state = c.b_state(); assert_eq!(state.num, 0); assert_eq!(state.balance.amount.u128(), 0); let state = c.c_state(); assert_eq!(state.num, 0); assert_eq!(state.balance.amount.u128(), 200); } #[test] fn errors_are_handled_in_submsg_reply_state_is_committed() { let mut c = init([None, None, None]); let msg = ExecuteMsg::RunMsgs(vec![ SubMsg::new( b_msg( &ExecuteMsg::RunMsgs(vec![ SubMsg::reply_always(c_msg(&ExecuteMsg::IncrNumber(1)), 0), SubMsg::reply_always(c_msg(&ExecuteMsg::Fail), 1), ]) ) ), SubMsg::new(b_msg(&ExecuteMsg::IncrNumber(3))) ]); let resp = c.ensemble.execute(&msg, MockEnv::new(SENDER, c.a.address.clone())).unwrap(); let mut resp = resp.iter(); let next = resp.next().unwrap(); assert!(next.is_execute()); if let ResponseVariants::Execute(resp) = next { assert_eq!(resp.address, B_ADDR); assert_eq!(resp.sender, A_ADDR); } let next = resp.next().unwrap(); assert!(next.is_execute()); if let ResponseVariants::Execute(resp) = next { assert_eq!(resp.address, C_ADDR); assert_eq!(resp.sender, B_ADDR); } let next = resp.next().unwrap(); assert!(next.is_reply()); if let ResponseVariants::Reply(resp) = next { assert_eq!(resp.address, B_ADDR); assert_eq!(resp.reply.id, 0); } let next = resp.next().unwrap(); assert!(next.is_reply()); if let ResponseVariants::Reply(resp) = next { assert_eq!(resp.address, B_ADDR); assert_eq!(resp.reply.id, 1); } let next = resp.next().unwrap(); assert!(next.is_execute()); if let ResponseVariants::Execute(resp) = next { assert_eq!(resp.address, B_ADDR); assert_eq!(resp.sender, A_ADDR); } assert_eq!(resp.next(), None); let state = c.a_state(); assert_eq!(state.num, 0); let state = c.b_state(); assert_eq!(state.num, 3); let state = c.c_state(); assert_eq!(state.num, 1); } #[test] fn errors_in_middle_of_submsg_scope_are_handled_and_execution_continues() { let mut c = init([None, None, None]); let msg = ExecuteMsg::RunMsgs(vec![ SubMsg::new( b_msg( &ExecuteMsg::RunMsgs(vec![ SubMsg::reply_always(c_msg(&ExecuteMsg::Fail), 0), SubMsg::reply_always(c_msg(&ExecuteMsg::IncrNumber(1)), 1) ]) ) ), SubMsg::new(b_msg(&ExecuteMsg::IncrNumber(3))) ]); let resp = c.ensemble.execute(&msg, MockEnv::new(SENDER, c.a.address.clone())).unwrap(); let mut resp = resp.iter(); let next = resp.next().unwrap(); assert!(next.is_execute()); if let ResponseVariants::Execute(resp) = next { assert_eq!(resp.address, B_ADDR); assert_eq!(resp.sender, A_ADDR); } let next = resp.next().unwrap(); assert!(next.is_reply()); if let ResponseVariants::Reply(resp) = next { assert_eq!(resp.address, B_ADDR); assert_eq!(resp.reply.id, 0); } let next = resp.next().unwrap(); assert!(next.is_execute()); if let ResponseVariants::Execute(resp) = next { assert_eq!(resp.address, C_ADDR); assert_eq!(resp.sender, B_ADDR); } let next = resp.next().unwrap(); assert!(next.is_reply()); if let ResponseVariants::Reply(resp) = next { assert_eq!(resp.address, B_ADDR); assert_eq!(resp.reply.id, 1); } let next = resp.next().unwrap(); assert!(next.is_execute()); if let ResponseVariants::Execute(resp) = next { assert_eq!(resp.address, B_ADDR); assert_eq!(resp.sender, A_ADDR); } assert_eq!(resp.next(), None); let state = c.a_state(); assert_eq!(state.num, 0); let state = c.b_state(); assert_eq!(state.num, 3); let state = c.c_state(); assert_eq!(state.num, 1); } #[test] fn unhandled_error_in_submsg_is_bubbled_up_to_the_caller() { let mut c = init([None, None, None]); c.ensemble.add_funds(C_ADDR, vec![coin(200, "uscrt")]); let msg = ExecuteMsg::RunMsgs(vec![ SubMsg::reply_always( b_msg( &ExecuteMsg::RunMsgs(vec![ SubMsg::reply_always( c_msg(&ExecuteMsg::IncrAndSend { amount: 1, recipient: A_ADDR.into() }), 0 ), SubMsg::new(c_msg(&ExecuteMsg::Fail)), SubMsg::reply_always(c_msg(&ExecuteMsg::IncrNumber(1)), 1), ]) ), 2 ), SubMsg::new( c_msg(&ExecuteMsg::IncrAndSend { amount: 3, recipient: B_ADDR.into() }) ), ]); let resp = c.ensemble.execute(&msg, MockEnv::new(SENDER, c.a.address.clone())).unwrap(); let mut resp = resp.iter(); let next = resp.next().unwrap(); assert!(next.is_reply()); if let ResponseVariants::Reply(resp) = next { assert_eq!(resp.address, A_ADDR); assert_eq!(resp.reply.id, 2); } let next = resp.next().unwrap(); assert!(next.is_execute()); if let ResponseVariants::Execute(resp) = next { assert_eq!(resp.address, C_ADDR); assert_eq!(resp.sender, A_ADDR); } let next = resp.next().unwrap(); assert!(next.is_bank()); if let ResponseVariants::Bank(resp) = next { assert_eq!(resp.receiver, B_ADDR); assert_eq!(resp.sender, C_ADDR); assert_eq!(resp.coins, vec![coin(100, "uscrt")]); } assert_eq!(resp.next(), None); let state = c.a_state(); assert_eq!(state.num, 0); assert_eq!(state.balance.amount.u128(), 0); let state = c.b_state(); assert_eq!(state.num, 0); assert_eq!(state.balance.amount.u128(), 100); let state = c.c_state(); assert_eq!(state.num, 3); assert_eq!(state.balance.amount.u128(), 100); } #[test] fn unhandled_error_in_nested_message_fails_tx() { let mut c = init([None, None, None]); let msg = ExecuteMsg::RunMsgs(vec![ SubMsg::new( b_msg( &ExecuteMsg::RunMsgs(vec![ SubMsg::reply_always(c_msg(&ExecuteMsg::IncrNumber(1)), 0), SubMsg::new(c_msg(&ExecuteMsg::Fail)) ]) ) ), SubMsg::new(b_msg(&ExecuteMsg::IncrNumber(3))) ]); let err = c.ensemble.execute(&msg, MockEnv::new(SENDER, c.a.address.clone())).unwrap_err(); assert_eq!(err.to_string(), "Generic error: Fail"); let state = c.a_state(); assert_eq!(state.num, 0); let state = c.b_state(); assert_eq!(state.num, 0); let state = c.c_state(); assert_eq!(state.num, 0); } #[test] fn error_bubbles_multiple_levels_up_the_stack() { let mut c = init([None, None, None]); let msg = ExecuteMsg::RunMsgs(vec![ SubMsg::reply_on_error( b_msg( &ExecuteMsg::RunMsgs(vec![ SubMsg::reply_always(c_msg(&ExecuteMsg::IncrNumber(1)), 0), SubMsg::new(c_msg(&ExecuteMsg::RunMsgs(vec![ SubMsg::new(b_msg(&ExecuteMsg::IncrNumber(1))), SubMsg::reply_on_success(a_msg( &ExecuteMsg::RunMsgs(vec![ SubMsg::new(b_msg(&ExecuteMsg::IncrNumber(15))) // This will fail ]) ), 1), ]))), SubMsg::reply_always(c_msg(&ExecuteMsg::IncrNumber(1)), 2), ]) ), 3 ), SubMsg::new(b_msg(&ExecuteMsg::IncrNumber(3))) ]); let resp = c.ensemble.execute(&msg, MockEnv::new(SENDER, c.a.address.clone())).unwrap(); let mut resp = resp.iter(); let next = resp.next().unwrap(); assert!(next.is_reply()); if let ResponseVariants::Reply(resp) = next { assert_eq!(resp.address, A_ADDR); assert_eq!(resp.reply.id, 3); } let next = resp.next().unwrap(); assert!(next.is_execute()); if let ResponseVariants::Execute(resp) = next { assert_eq!(resp.address, B_ADDR); assert_eq!(resp.sender, A_ADDR); } assert_eq!(resp.next(), None); let state = c.a_state(); assert_eq!(state.num, 0); let state = c.b_state(); assert_eq!(state.num, 3); let state = c.c_state(); assert_eq!(state.num, 0); assert_eq!( c.b_events(0).unwrap_err().to_string(), no_reply_stored_err(0).to_string() ); assert_eq!( c.c_events(1).unwrap_err().to_string(), no_reply_stored_err(1).to_string() ); assert_eq!( c.b_events(2).unwrap_err().to_string(), no_reply_stored_err(2).to_string() ); assert_eq!( c.a_events(3).unwrap_err().to_string(), no_reply_stored_err(3).to_string() ); } #[test] fn unhandled_error_jumps_to_the_first_reply() { let mut c = init([None, None, None]); let msg = ExecuteMsg::RunMsgs(vec![ SubMsg::reply_always( b_msg( &ExecuteMsg::RunMsgs(vec![ SubMsg::reply_on_success( c_msg( &ExecuteMsg::RunMsgs(vec![ SubMsg::reply_on_success(a_msg(&ExecuteMsg::IncrNumber(1)), 0), SubMsg::new(b_msg(&ExecuteMsg::Fail)) ]) ), 1 ), SubMsg::new(c_msg(&ExecuteMsg::IncrNumber(1))) ]) ), 2 ), SubMsg::new(b_msg(&ExecuteMsg::IncrNumber(2))) ]); let resp = c.ensemble.execute(&msg, MockEnv::new(SENDER, c.a.address.clone())).unwrap(); let mut resp = resp.iter(); let next = resp.next().unwrap(); assert!(next.is_reply()); if let ResponseVariants::Reply(resp) = next { assert_eq!(resp.address, A_ADDR); assert_eq!(resp.reply.id, 2); } let next = resp.next().unwrap(); assert!(next.is_execute()); if let ResponseVariants::Execute(resp) = next { assert_eq!(resp.address, B_ADDR); assert_eq!(resp.sender, A_ADDR); } assert_eq!(resp.next(), None); let state = c.a_state(); assert_eq!(state.num, 0); let state = c.b_state(); assert_eq!(state.num, 2); let state = c.c_state(); assert_eq!(state.num, 0); assert_eq!( c.c_events(0).unwrap_err().to_string(), no_reply_stored_err(0).to_string() ); assert_eq!( c.b_events(1).unwrap_err().to_string(), no_reply_stored_err(1).to_string() ); assert_eq!( c.a_events(2).unwrap_err().to_string(), no_reply_stored_err(2).to_string() ); } #[test] fn reply_responses_are_handled_correctly() { let mut c = init([None, None, None]); let msg = ExecuteMsg::ReplyResponse( SubMsg::reply_always( a_msg(&ExecuteMsg::IncrNumber(1)), 2 ) ); let resp = c.ensemble.execute(&msg, MockEnv::new(SENDER, c.b.address.clone())).unwrap(); assert!(resp.sent.is_empty()); assert_eq!(resp.address, B_ADDR); assert_eq!(resp.sender, SENDER); let msg = ExecuteMsg::RunMsgs(vec![ SubMsg::reply_always( b_msg( &ExecuteMsg::RunMsgs(vec![ SubMsg::reply_on_success(c_msg(&ExecuteMsg::IncrNumber(1)), 0), SubMsg::reply_on_success(c_msg(&ExecuteMsg::IncrNumber(1)), 1), ]) ), 3 ), SubMsg::new(c_msg(&ExecuteMsg::IncrNumber(3))), SubMsg::reply_always(b_msg(&ExecuteMsg::IncrNumber(2)), 4) ]); let resp = c.ensemble.execute(&msg, MockEnv::new(SENDER, c.a.address.clone())).unwrap(); let mut resp = resp.iter(); let next = resp.next().unwrap(); assert!(next.is_execute()); if let ResponseVariants::Execute(resp) = next { assert_eq!(resp.address, B_ADDR); assert_eq!(resp.sender, A_ADDR); } let next = resp.next().unwrap(); assert!(next.is_execute()); if let ResponseVariants::Execute(resp) = next { assert_eq!(resp.address, C_ADDR); assert_eq!(resp.sender, B_ADDR); } let next = resp.next().unwrap(); assert!(next.is_reply()); if let ResponseVariants::Reply(resp) = next { assert_eq!(resp.address, B_ADDR); assert_eq!(resp.reply.id, 0); } let next = resp.next().unwrap(); assert!(next.is_execute()); if let ResponseVariants::Execute(resp) = next { assert_eq!(resp.address, A_ADDR); assert_eq!(resp.sender, B_ADDR); } let next = resp.next().unwrap(); assert!(next.is_reply()); if let ResponseVariants::Reply(resp) = next { assert_eq!(resp.address, B_ADDR); assert_eq!(resp.reply.id, 2); } let next = resp.next().unwrap(); assert!(next.is_execute()); if let ResponseVariants::Execute(resp) = next { assert_eq!(resp.address, C_ADDR); assert_eq!(resp.sender, B_ADDR); } let next = resp.next().unwrap(); assert!(next.is_reply()); if let ResponseVariants::Reply(resp) = next { assert_eq!(resp.address, B_ADDR); assert_eq!(resp.reply.id, 1); } let next = resp.next().unwrap(); assert!(next.is_reply()); if let ResponseVariants::Reply(resp) = next { assert_eq!(resp.address, A_ADDR); assert_eq!(resp.reply.id, 3); } let next = resp.next().unwrap(); assert!(next.is_execute()); if let ResponseVariants::Execute(resp) = next { assert_eq!(resp.address, C_ADDR); assert_eq!(resp.sender, A_ADDR); } let next = resp.next().unwrap(); assert!(next.is_execute()); if let ResponseVariants::Execute(resp) = next { assert_eq!(resp.address, B_ADDR); assert_eq!(resp.sender, A_ADDR); } let next = resp.next().unwrap(); assert!(next.is_reply()); if let ResponseVariants::Reply(resp) = next { assert_eq!(resp.address, A_ADDR); assert_eq!(resp.reply.id, 4); } assert_eq!(resp.next(), None); let state = c.a_state(); assert_eq!(state.num, 1); let state = c.b_state(); assert_eq!(state.num, 2); let state = c.c_state(); assert_eq!(state.num, 5); cmp_response_json( &c.b_events(0).unwrap(), "{\"events\":[{\"type\":\"execute\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true}]},{\"type\":\"wasm\",\"attributes\":[{\"key\":\"ANOTHER_TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true},{\"key\":\"submsg_execute\",\"value\":\"sender: b\\ncontract: c\",\"encrypted\":true}]}],\"data\":\"SW5jcmVtZW50IGFtb3VudDogMQ==\"}" ); cmp_response_json( &c.b_events(1).unwrap(), "{\"events\":[{\"type\":\"execute\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true}]},{\"type\":\"wasm\",\"attributes\":[{\"key\":\"ANOTHER_TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true},{\"key\":\"submsg_execute\",\"value\":\"sender: b\\ncontract: c\",\"encrypted\":true}]}],\"data\":\"SW5jcmVtZW50IGFtb3VudDogMQ==\"}" ); cmp_response_json( &c.b_events(2).unwrap(), "{\"events\":[{\"type\":\"execute\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"a\",\"encrypted\":true}]},{\"type\":\"wasm\",\"attributes\":[{\"key\":\"ANOTHER_TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"contract_address\",\"value\":\"a\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"a\",\"encrypted\":true},{\"key\":\"submsg_execute\",\"value\":\"sender: b\\ncontract: a\",\"encrypted\":true}]}],\"data\":\"SW5jcmVtZW50IGFtb3VudDogMQ==\"}" ); cmp_response_json( &c.a_events(3).unwrap(), "{\"events\":[{\"type\":\"execute\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true}]},{\"type\":\"wasm\",\"attributes\":[{\"key\":\"ANOTHER_TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true},{\"key\":\"submsg_execute\",\"value\":\"sender: a\\ncontract: b\",\"encrypted\":true}]},{\"type\":\"execute\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true}]},{\"type\":\"wasm\",\"attributes\":[{\"key\":\"ANOTHER_TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true},{\"key\":\"submsg_execute\",\"value\":\"sender: b\\ncontract: c\",\"encrypted\":true}]},{\"type\":\"reply\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true},{\"key\":\"submsg_reply\",\"value\":\"address: b, id: 0, success: true\",\"encrypted\":true}]},{\"type\":\"execute\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"a\",\"encrypted\":true}]},{\"type\":\"wasm\",\"attributes\":[{\"key\":\"ANOTHER_TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"contract_address\",\"value\":\"a\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"a\",\"encrypted\":true},{\"key\":\"submsg_execute\",\"value\":\"sender: b\\ncontract: a\",\"encrypted\":true}]},{\"type\":\"reply\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true},{\"key\":\"submsg_reply\",\"value\":\"address: b, id: 2, success: true\",\"encrypted\":true}]},{\"type\":\"execute\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true}]},{\"type\":\"wasm\",\"attributes\":[{\"key\":\"ANOTHER_TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true},{\"key\":\"submsg_execute\",\"value\":\"sender: b\\ncontract: c\",\"encrypted\":true}]},{\"type\":\"reply\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true},{\"key\":\"submsg_reply\",\"value\":\"address: b, id: 1, success: true\",\"encrypted\":true}]}],\"data\":null}" ); cmp_response_json( &c.a_events(4).unwrap(), "{\"events\":[{\"type\":\"execute\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true}]},{\"type\":\"wasm\",\"attributes\":[{\"key\":\"ANOTHER_TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true},{\"key\":\"submsg_execute\",\"value\":\"sender: a\\ncontract: b\",\"encrypted\":true}]}],\"data\":\"SW5jcmVtZW50IGFtb3VudDogMg==\"}" ); } #[test] fn reply_response_error_is_handled_properly() { let mut c = init([None, Some(2), None]); let msg = ExecuteMsg::ReplyResponse( SubMsg::reply_always( a_msg(&ExecuteMsg::RunMsgs(vec![ SubMsg::new(c_msg(&ExecuteMsg::IncrNumber(1))), SubMsg::reply_on_success(b_msg(&ExecuteMsg::IncrNumber(20)), 1) // This will fail ])), 2 ) ); let resp = c.ensemble.execute(&msg, MockEnv::new(SENDER, c.b.address.clone())).unwrap(); assert!(resp.sent.is_empty()); assert_eq!(resp.address, B_ADDR); assert_eq!(resp.sender, SENDER); let msg = ExecuteMsg::RunMsgs(vec![ SubMsg::reply_always( b_msg( &ExecuteMsg::RunMsgs(vec![ SubMsg::reply_on_success(c_msg(&ExecuteMsg::IncrNumber(1)), 0), SubMsg::new(c_msg(&ExecuteMsg::IncrNumber(1))) ]) ), 3 ), SubMsg::reply_always(b_msg(&ExecuteMsg::IncrNumber(2)), 4) ]); let resp = c.ensemble.execute(&msg, MockEnv::new(SENDER, c.a.address.clone())).unwrap(); let mut resp = resp.iter(); let next = resp.next().unwrap(); assert!(next.is_reply()); if let ResponseVariants::Reply(resp) = next { assert_eq!(resp.address, A_ADDR); assert_eq!(resp.reply.id, 3); } let next = resp.next().unwrap(); assert!(next.is_execute()); if let ResponseVariants::Execute(resp) = next { assert_eq!(resp.address, B_ADDR); assert_eq!(resp.sender, A_ADDR); } let next = resp.next().unwrap(); assert!(next.is_reply()); if let ResponseVariants::Reply(resp) = next { assert_eq!(resp.address, A_ADDR); assert_eq!(resp.reply.id, 4); } assert_eq!(resp.next(), None); let state = c.a_state(); assert_eq!(state.num, 0); let state = c.b_state(); assert_eq!(state.num, 2); let state = c.c_state(); assert_eq!(state.num, 0); assert_eq!( c.b_events(0).unwrap_err().to_string(), no_reply_stored_err(0).to_string() ); assert_eq!( c.b_events(2).unwrap_err().to_string(), no_reply_stored_err(2).to_string() ); assert_eq!( c.a_events(1).unwrap_err().to_string(), no_reply_stored_err(1).to_string() ); assert_eq!( c.a_events(3).unwrap_err().to_string(), no_reply_stored_err(3).to_string() ); cmp_response_json( &c.a_events(4).unwrap(), "{\"events\":[{\"type\":\"execute\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true}]},{\"type\":\"wasm\",\"attributes\":[{\"key\":\"ANOTHER_TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true},{\"key\":\"submsg_execute\",\"value\":\"sender: a\\ncontract: b\",\"encrypted\":true}]}],\"data\":\"SW5jcmVtZW50IGFtb3VudDogMg==\"}" ); } #[test] fn correct_events_passed_to_reply() { let mut c = init([None, None, None]); c.ensemble.add_funds(C_ADDR, vec![coin(200, "uscrt")]); let msg = ExecuteMsg::RunMsgs(vec![ SubMsg::reply_always( b_msg( &ExecuteMsg::RunMsgs(vec![ SubMsg::reply_always( c_msg(&ExecuteMsg::IncrAndSend { amount: 1, recipient: A_ADDR.into() }), 0 ), SubMsg::reply_on_error(c_msg(&ExecuteMsg::Fail), 1), SubMsg::reply_always(c_msg(&ExecuteMsg::IncrNumber(1)), 2), ]) ), 3 ), SubMsg::reply_always( c_msg(&ExecuteMsg::IncrAndSend { amount: 3, recipient: B_ADDR.into() }), 4 ), ]); c.ensemble.execute(&msg, MockEnv::new(SENDER, c.a.address.clone())).unwrap(); let state = c.a_state(); assert_eq!(state.num, 0); assert_eq!(state.balance.amount.u128(), 100); let state = c.b_state(); assert_eq!(state.num, 0); assert_eq!(state.balance.amount.u128(), 100); let state = c.c_state(); assert_eq!(state.num, 5); assert_eq!(state.balance.amount.u128(), 0); cmp_response_json( &c.b_events(0).unwrap(), "{\"events\":[{\"type\":\"execute\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true}]},{\"type\":\"wasm\",\"attributes\":[{\"key\":\"ANOTHER_TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true},{\"key\":\"submsg_execute\",\"value\":\"sender: b\\ncontract: c\",\"encrypted\":true}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"amount\",\"value\":\"100uscrt\",\"encrypted\":true},{\"key\":\"spender\",\"value\":\"c\",\"encrypted\":true}]},{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"amount\",\"value\":\"100uscrt\",\"encrypted\":true},{\"key\":\"receiver\",\"value\":\"a\",\"encrypted\":true}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"amount\",\"value\":\"100uscrt\",\"encrypted\":true},{\"key\":\"recipient\",\"value\":\"a\",\"encrypted\":true},{\"key\":\"sender\",\"value\":\"c\",\"encrypted\":true}]}],\"data\":null}" ); assert_eq!( c.b_events(1).unwrap_err().to_string(), no_reply_stored_err(1).to_string() ); cmp_response_json( &c.b_events(2).unwrap(), "{\"events\":[{\"type\":\"execute\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true}]},{\"type\":\"wasm\",\"attributes\":[{\"key\":\"ANOTHER_TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true},{\"key\":\"submsg_execute\",\"value\":\"sender: b\\ncontract: c\",\"encrypted\":true}]}],\"data\":\"SW5jcmVtZW50IGFtb3VudDogMQ==\"}" ); cmp_response_json( &c.a_events(3).unwrap(), "{\"events\":[{\"type\":\"execute\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true}]},{\"type\":\"wasm\",\"attributes\":[{\"key\":\"ANOTHER_TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true},{\"key\":\"submsg_execute\",\"value\":\"sender: a\\ncontract: b\",\"encrypted\":true}]},{\"type\":\"execute\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true}]},{\"type\":\"wasm\",\"attributes\":[{\"key\":\"ANOTHER_TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true},{\"key\":\"submsg_execute\",\"value\":\"sender: b\\ncontract: c\",\"encrypted\":true}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"amount\",\"value\":\"100uscrt\",\"encrypted\":true},{\"key\":\"spender\",\"value\":\"c\",\"encrypted\":true}]},{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"amount\",\"value\":\"100uscrt\",\"encrypted\":true},{\"key\":\"receiver\",\"value\":\"a\",\"encrypted\":true}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"amount\",\"value\":\"100uscrt\",\"encrypted\":true},{\"key\":\"recipient\",\"value\":\"a\",\"encrypted\":true},{\"key\":\"sender\",\"value\":\"c\",\"encrypted\":true}]},{\"type\":\"reply\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true},{\"key\":\"submsg_reply\",\"value\":\"address: b, id: 0, success: true\",\"encrypted\":true}]},{\"type\":\"reply\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true},{\"key\":\"submsg_reply\",\"value\":\"address: b, id: 1, success: false\",\"encrypted\":true}]},{\"type\":\"execute\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true}]},{\"type\":\"wasm\",\"attributes\":[{\"key\":\"ANOTHER_TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true},{\"key\":\"submsg_execute\",\"value\":\"sender: b\\ncontract: c\",\"encrypted\":true}]},{\"type\":\"reply\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"b\",\"encrypted\":true},{\"key\":\"submsg_reply\",\"value\":\"address: b, id: 2, success: true\",\"encrypted\":true}]}],\"data\":null}" ); cmp_response_json( &c.a_events(4).unwrap(), "{\"events\":[{\"type\":\"execute\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true}]},{\"type\":\"wasm\",\"attributes\":[{\"key\":\"ANOTHER_TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"TEST_ATTR\",\"value\":\"test\",\"encrypted\":true},{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true}]},{\"type\":\"wasm-MSG_ORDER\",\"attributes\":[{\"key\":\"contract_address\",\"value\":\"c\",\"encrypted\":true},{\"key\":\"submsg_execute\",\"value\":\"sender: a\\ncontract: c\",\"encrypted\":true}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"amount\",\"value\":\"100uscrt\",\"encrypted\":true},{\"key\":\"spender\",\"value\":\"c\",\"encrypted\":true}]},{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"amount\",\"value\":\"100uscrt\",\"encrypted\":true},{\"key\":\"receiver\",\"value\":\"b\",\"encrypted\":true}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"amount\",\"value\":\"100uscrt\",\"encrypted\":true},{\"key\":\"recipient\",\"value\":\"b\",\"encrypted\":true},{\"key\":\"sender\",\"value\":\"c\",\"encrypted\":true}]}],\"data\":null}" ); } fn init(msgs: [Option; 3]) -> TestContracts { let mut ensemble = ContractEnsemble::new(); let contract = ensemble.register(Box::new(Contract)); let a = ensemble.instantiate( contract.id, &InstantiateMsg { reply_fail_id: msgs[0] }, MockEnv::new(SENDER, A_ADDR) ).unwrap(); let b = ensemble.instantiate( contract.id, &InstantiateMsg { reply_fail_id: msgs[1] }, MockEnv::new(SENDER, B_ADDR) ).unwrap(); let c = ensemble.instantiate( contract.id, &InstantiateMsg { reply_fail_id: msgs[2] }, MockEnv::new(SENDER, C_ADDR) ).unwrap(); TestContracts { ensemble, a: a.instance, b: b.instance, c: c.instance } } fn a_msg(msg: &ExecuteMsg) -> WasmMsg { WasmMsg::Execute { contract_addr: A_ADDR.into(), code_hash: "test_contract_0".into(), msg: to_binary(msg).unwrap(), funds: vec![] } } fn b_msg(msg: &ExecuteMsg) -> WasmMsg { WasmMsg::Execute { contract_addr: B_ADDR.into(), code_hash: "test_contract_0".into(), msg: to_binary(msg).unwrap(), funds: vec![] } } fn c_msg(msg: &ExecuteMsg) -> WasmMsg { WasmMsg::Execute { contract_addr: C_ADDR.into(), code_hash: "test_contract_0".into(), msg: to_binary(msg).unwrap(), funds: vec![] } } fn cmp_response_json(resp: &SubMsgResponse, json: &'static str) { assert_eq!( String::from_utf8(to_vec(&resp).unwrap()).unwrap(), json ) }