diff --git a/packages/multi-test/src/app.rs b/packages/multi-test/src/app.rs index 4069370cb..3991e6a6d 100644 --- a/packages/multi-test/src/app.rs +++ b/packages/multi-test/src/app.rs @@ -281,7 +281,8 @@ where mod test { use cosmwasm_std::testing::MockStorage; use cosmwasm_std::{ - attr, coin, coins, AllBalanceResponse, BankMsg, BankQuery, Event, Reply, SubMsg, WasmMsg, + attr, coin, coins, to_binary, AllBalanceResponse, Attribute, BankMsg, BankQuery, Event, + Reply, SubMsg, WasmMsg, }; use crate::test_helpers::contracts::{echo, hackatom, payout, reflect}; @@ -925,10 +926,6 @@ mod test { } mod replay_data_overwrite { - use cosmwasm_std::to_binary; - - use crate::test_helpers::EmptyMsg; - use super::*; fn make_echo_submsg( @@ -939,7 +936,12 @@ mod test { let data = data.into().map(|s| s.to_owned()); SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: contract.into(), - msg: to_binary(&echo::Message { data, sub_msg }).unwrap(), + msg: to_binary(&echo::Message { + data, + sub_msg, + ..echo::Message::default() + }) + .unwrap(), funds: vec![], })) } @@ -961,7 +963,7 @@ mod test { contract, &echo::Message { data: Some("Data".to_owned()), - sub_msg: vec![], + ..echo::Message::default() }, &[], ) @@ -988,6 +990,7 @@ mod test { &echo::Message { data: Some("First".to_owned()), sub_msg: vec![make_echo_submsg(contract, "Second", vec![])], + ..echo::Message::default() }, &[], ) @@ -1014,6 +1017,7 @@ mod test { &echo::Message { data: Some("First".to_owned()), sub_msg: vec![make_echo_submsg(contract, None, vec![])], + ..echo::Message::default() }, &[], ) @@ -1045,6 +1049,7 @@ mod test { make_echo_submsg(contract.clone(), "Second", vec![]), make_echo_submsg(contract, None, vec![]), ], + ..echo::Message::default() }, &[], ) @@ -1083,6 +1088,7 @@ mod test { )], )], )], + ..echo::Message::default() }, &[], ) @@ -1091,4 +1097,153 @@ mod test { assert_eq!(response.data, Some("Second".as_bytes().into())); } } + + mod response_validation { + use super::*; + + #[test] + fn empty_attribute_key() { + let mut app = mock_app(); + + let owner = Addr::unchecked("owner"); + + let contract_id = app.store_code(echo::contract()); + let contract = app + .instantiate_contract(contract_id, owner.clone(), &EmptyMsg {}, &[], "Echo", None) + .unwrap(); + + let err = app + .execute_contract( + owner, + contract, + &echo::Message { + data: None, + attributes: vec![ + Attribute::new(" ", "value"), + Attribute::new("proper", "proper_val"), + ], + ..echo::Message::default() + }, + &[], + ) + .unwrap_err(); + + assert_eq!(err, "Empty attribute key. Value: value"); + } + + #[test] + fn empty_attribute_value() { + let mut app = mock_app(); + + let owner = Addr::unchecked("owner"); + + let contract_id = app.store_code(echo::contract()); + let contract = app + .instantiate_contract(contract_id, owner.clone(), &EmptyMsg {}, &[], "Echo", None) + .unwrap(); + + let err = app + .execute_contract( + owner, + contract, + &echo::Message { + data: None, + attributes: vec![ + Attribute::new("key", " "), + Attribute::new("proper", "proper_val"), + ], + ..echo::Message::default() + }, + &[], + ) + .unwrap_err(); + + assert_eq!(err, "Empty attribute value. Key: key"); + } + + #[test] + fn empty_event_attribute_key() { + let mut app = mock_app(); + + let owner = Addr::unchecked("owner"); + + let contract_id = app.store_code(echo::contract()); + let contract = app + .instantiate_contract(contract_id, owner.clone(), &EmptyMsg {}, &[], "Echo", None) + .unwrap(); + + let err = app + .execute_contract( + owner, + contract, + &echo::Message { + data: None, + events: vec![Event::new("event") + .add_attribute(" ", "value") + .add_attribute("proper", "proper_val")], + ..echo::Message::default() + }, + &[], + ) + .unwrap_err(); + + assert_eq!(err, "Empty attribute key. Value: value"); + } + + #[test] + fn empty_event_attribute_value() { + let mut app = mock_app(); + + let owner = Addr::unchecked("owner"); + + let contract_id = app.store_code(echo::contract()); + let contract = app + .instantiate_contract(contract_id, owner.clone(), &EmptyMsg {}, &[], "Echo", None) + .unwrap(); + + let err = app + .execute_contract( + owner, + contract, + &echo::Message { + data: None, + events: vec![Event::new("event") + .add_attribute("key", " ") + .add_attribute("proper", "proper_val")], + ..echo::Message::default() + }, + &[], + ) + .unwrap_err(); + + assert_eq!(err, "Empty attribute value. Key: key"); + } + + #[test] + fn too_short_event_type() { + let mut app = mock_app(); + + let owner = Addr::unchecked("owner"); + + let contract_id = app.store_code(echo::contract()); + let contract = app + .instantiate_contract(contract_id, owner.clone(), &EmptyMsg {}, &[], "Echo", None) + .unwrap(); + + let err = app + .execute_contract( + owner, + contract, + &echo::Message { + data: None, + events: vec![Event::new(" e "), Event::new("event")], + ..echo::Message::default() + }, + &[], + ) + .unwrap_err(); + + assert_eq!(err, "Event type too short: e"); + } + } } diff --git a/packages/multi-test/src/test_helpers/contracts/echo.rs b/packages/multi-test/src/test_helpers/contracts/echo.rs index f8e301628..582d8d651 100644 --- a/packages/multi-test/src/test_helpers/contracts/echo.rs +++ b/packages/multi-test/src/test_helpers/contracts/echo.rs @@ -1,9 +1,11 @@ //! Very simple echoing contract which just returns incomming string if any, but performming subcall of -//! given message to test response +//! given message to test response. +//! +//! Additionally it bypass all events and attributes send to it use cosmwasm_std::{ - to_binary, Binary, ContractResult, Deps, DepsMut, Empty, Env, MessageInfo, Reply, Response, - StdError, SubMsg, SubMsgExecutionResponse, + to_binary, Attribute, Binary, ContractResult, Deps, DepsMut, Empty, Env, Event, MessageInfo, + Reply, Response, StdError, SubMsg, SubMsgExecutionResponse, }; use serde::{Deserialize, Serialize}; @@ -13,6 +15,8 @@ use crate::{test_helpers::EmptyMsg, Contract, ContractWrapper}; pub struct Message { pub data: Option, pub sub_msg: Vec, + pub attributes: Vec, + pub events: Vec, } #[allow(clippy::unnecessary_wraps)] @@ -33,10 +37,15 @@ fn execute( msg: Message, ) -> Result { let mut resp = Response::new(); + if let Some(data) = msg.data { resp = resp.set_data(data.into_bytes()); } - Ok(resp.add_submessages(msg.sub_msg)) + + Ok(resp + .add_submessages(msg.sub_msg) + .add_attributes(msg.attributes) + .add_events(msg.events)) } fn query(_deps: Deps, _env: Env, msg: EmptyMsg) -> Result { diff --git a/packages/multi-test/src/wasm.rs b/packages/multi-test/src/wasm.rs index bb4fb6ccb..c000e39a3 100644 --- a/packages/multi-test/src/wasm.rs +++ b/packages/multi-test/src/wasm.rs @@ -3,9 +3,9 @@ use std::fmt; use std::ops::Deref; use cosmwasm_std::{ - Addr, Api, BankMsg, Binary, BlockInfo, Coin, ContractInfo, ContractResult, Deps, DepsMut, Env, - Event, MessageInfo, Order, Querier, QuerierWrapper, Reply, ReplyOn, Response, Storage, SubMsg, - SubMsgExecutionResponse, WasmMsg, WasmQuery, + Addr, Api, Attribute, BankMsg, Binary, BlockInfo, Coin, ContractInfo, ContractResult, Deps, + DepsMut, Env, Event, MessageInfo, Order, Querier, QuerierWrapper, Reply, ReplyOn, Response, + Storage, SubMsg, SubMsgExecutionResponse, WasmMsg, WasmQuery, }; use cosmwasm_storage::{prefixed, prefixed_read, PrefixedStorage, ReadonlyPrefixedStorage}; use prost::Message; @@ -268,6 +268,10 @@ where funds, label, } => { + if label.is_empty() { + return Err("Label is required on all contracts".to_owned()); + } + let contract_addr = self.register_contract( storage, code_id as usize, @@ -276,6 +280,7 @@ where label, block.height, )?; + // move the cash self.send( api, @@ -412,7 +417,6 @@ where reply: Reply, ) -> Result { let res = self.call_reply(contract.clone(), api, storage, router, block, reply)?; - // TODO: process result better, combine events / data from parent self.process_response(api, router, storage, block, contract, res, true) } @@ -499,14 +503,14 @@ where info: MessageInfo, msg: Vec, ) -> Result, String> { - self.with_storage( + Self::verify_response(self.with_storage( api, storage, router, block, address, |contract, deps, env| contract.execute(deps, env, info, msg), - ) + )?) } pub fn call_instantiate( @@ -519,14 +523,14 @@ where info: MessageInfo, msg: Vec, ) -> Result, String> { - self.with_storage( + Self::verify_response(self.with_storage( api, storage, router, block, address, |contract, deps, env| contract.instantiate(deps, env, info, msg), - ) + )?) } pub fn call_reply( @@ -538,14 +542,14 @@ where block: &BlockInfo, reply: Reply, ) -> Result, String> { - self.with_storage( + Self::verify_response(self.with_storage( api, storage, router, block, address, |contract, deps, env| contract.reply(deps, env, reply), - ) + )?) } pub fn call_sudo( @@ -557,14 +561,14 @@ where block: &BlockInfo, msg: Vec, ) -> Result, String> { - self.with_storage( + Self::verify_response(self.with_storage( api, storage, router, block, address, |contract, deps, env| contract.sudo(deps, env, msg), - ) + )?) } pub fn call_migrate( @@ -576,14 +580,14 @@ where block: &BlockInfo, msg: Vec, ) -> Result, String> { - self.with_storage( + Self::verify_response(self.with_storage( api, storage, router, block, address, |contract, deps, env| contract.migrate(deps, env, msg), - ) + )?) } fn get_env>(&self, address: T, block: &BlockInfo) -> Env { @@ -720,6 +724,47 @@ where let storage = ReadonlyPrefixedStorage::multilevel(storage, &[NAMESPACE_WASM, &namespace]); Box::new(storage) } + + fn verify_attributes(attributes: &[Attribute]) -> Result<(), String> { + for attr in attributes { + let key = attr.key.trim(); + let val = attr.value.trim(); + + if key.is_empty() { + return Err(format!("Empty attribute key. Value: {}", val)); + } + + if val.is_empty() { + return Err(format!("Empty attribute value. Key: {}", key)); + } + + if key.starts_with('_') { + return Err(format!( + "Attribute key starts with reserved prefix _: {}", + attr.key + )); + } + } + + Ok(()) + } + + fn verify_response(response: Response) -> Result, String> + where + T: Clone + fmt::Debug + PartialEq + JsonSchema, + { + Self::verify_attributes(&response.attributes)?; + + for event in &response.events { + Self::verify_attributes(&event.attributes)?; + let ty = event.ty.trim(); + if ty.len() < 2 { + return Err(format!("Event type too short: {}", ty)); + } + } + + Ok(response) + } } #[derive(Clone, PartialEq, Message)]