diff --git a/packages/multi-test/Cargo.toml b/packages/multi-test/Cargo.toml index 4735c49e0..812173f24 100644 --- a/packages/multi-test/Cargo.toml +++ b/packages/multi-test/Cargo.toml @@ -11,9 +11,10 @@ documentation = "https://docs.cosmwasm.com" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -default = ["iterator"] +default = ["iterator", "staking"] iterator = ["cosmwasm-std/iterator"] stargate = ["cosmwasm-std/stargate"] +staking = ["cosmwasm-std/staking"] backtrace = ["anyhow/backtrace"] [dependencies] diff --git a/packages/multi-test/src/app.rs b/packages/multi-test/src/app.rs index 93fb30ba2..c57a04bee 100644 --- a/packages/multi-test/src/app.rs +++ b/packages/multi-test/src/app.rs @@ -2,9 +2,8 @@ use std::fmt::{self, Debug}; use cosmwasm_std::testing::{mock_env, MockApi, MockStorage}; use cosmwasm_std::{ - from_slice, to_vec, Addr, Api, Binary, BlockInfo, Coin, ContractResult, CosmosMsg, CustomQuery, - Empty, Querier, QuerierResult, QuerierWrapper, QueryRequest, Storage, SystemError, - SystemResult, + from_slice, to_vec, Addr, Api, Binary, BlockInfo, Coin, ContractResult, CustomQuery, Empty, + Querier, QuerierResult, QuerierWrapper, QueryRequest, Storage, SystemError, SystemResult, }; use schemars::JsonSchema; use serde::de::DeserializeOwned; @@ -15,6 +14,7 @@ use crate::contracts::Contract; use crate::custom_handler::{CustomHandler, PanickingCustomHandler}; use crate::executor::{AppResponse, Executor}; use crate::transactions::transactional; +use crate::untyped_msg::CosmosMsg; use crate::wasm::{ContractData, Wasm, WasmKeeper}; use crate::BankKeeper; @@ -101,7 +101,11 @@ where StorageT: Storage, CustomT: CustomHandler, { - fn execute(&mut self, sender: Addr, msg: CosmosMsg) -> AnyResult { + fn execute( + &mut self, + sender: Addr, + msg: cosmwasm_std::CosmosMsg, + ) -> AnyResult { let mut all = self.execute_multi(sender, vec![msg])?; let res = all.pop().unwrap(); Ok(res) @@ -385,7 +389,7 @@ where pub fn execute_multi( &mut self, sender: Addr, - msgs: Vec>, + msgs: Vec>, ) -> AnyResult> { // we need to do some caching of storage here, once in the entry point: // meaning, wrap current state, all writes go to a cache, only when execute @@ -400,7 +404,7 @@ where transactional(&mut *storage, |write_cache, _| { msgs.into_iter() - .map(|msg| router.execute(&*api, write_cache, block, sender.clone(), msg)) + .map(|msg| router.execute(&*api, write_cache, block, sender.clone(), msg.into())) .collect() }) } @@ -449,13 +453,20 @@ pub struct Router { pub(crate) custom: Custom, } -impl Router { +impl Router +where + CustomT::ExecC: Clone + fmt::Debug + PartialEq + JsonSchema + DeserializeOwned + 'static, + CustomT::QueryC: CustomQuery + DeserializeOwned + 'static, + CustomT: CustomHandler, + WasmT: Wasm, + BankT: Bank, +{ pub fn querier<'a>( &'a self, api: &'a dyn Api, storage: &'a dyn Storage, block_info: &'a BlockInfo, - ) -> RouterQuerier<'a, BankT, CustomT, WasmT> { + ) -> RouterQuerier<'a, CustomT::ExecC, CustomT::QueryC> { RouterQuerier { router: self, api, @@ -463,25 +474,67 @@ impl Router { block_info, } } +} + +pub trait CosmosRouter { + type ExecC; + type QueryC: CustomQuery; + + fn execute( + &self, + api: &dyn Api, + storage: &mut dyn Storage, + block: &BlockInfo, + sender: Addr, + msg: CosmosMsg, + ) -> AnyResult; + + fn query( + &self, + api: &dyn Api, + storage: &dyn Storage, + block: &BlockInfo, + request: QueryRequest, + ) -> AnyResult; +} + +impl CosmosRouter for Router +where + CustomT::ExecC: std::fmt::Debug + Clone + PartialEq + JsonSchema + DeserializeOwned + 'static, + CustomT::QueryC: CustomQuery + DeserializeOwned + 'static, + CustomT: CustomHandler, + WasmT: Wasm, + BankT: Bank, +{ + type ExecC = CustomT::ExecC; + type QueryC = CustomT::QueryC; + + fn execute( + &self, + api: &dyn Api, + storage: &mut dyn Storage, + block: &BlockInfo, + sender: Addr, + msg: CosmosMsg, + ) -> AnyResult { + match msg { + CosmosMsg::Wasm(msg) => self.wasm.execute(api, storage, self, block, sender, msg), + CosmosMsg::Bank(msg) => self.bank.execute(storage, sender, msg), + CosmosMsg::Custom(msg) => self.custom.execute(api, storage, block, sender, msg), + _ => unimplemented!(), + } + } /// this is used by `RouterQuerier` to actual implement the `Querier` interface. /// you most likely want to use `router.querier(storage, block).wrap()` to get a /// QuerierWrapper to interact with - pub fn query( + fn query( &self, api: &dyn Api, storage: &dyn Storage, block: &BlockInfo, - request: QueryRequest, - ) -> AnyResult - where - CustomT::QueryC: CustomQuery + DeserializeOwned + 'static, - CustomT::ExecC: - std::fmt::Debug + PartialEq + Clone + JsonSchema + DeserializeOwned + 'static, - WasmT: Wasm, - BankT: Bank, - CustomT: CustomHandler, - { + request: QueryRequest, + ) -> AnyResult { match request { QueryRequest::Wasm(req) => { self.wasm @@ -492,40 +545,18 @@ impl Router { _ => unimplemented!(), } } - - pub fn execute( - &self, - api: &dyn Api, - storage: &mut dyn Storage, - block: &BlockInfo, - sender: Addr, - msg: CosmosMsg, - ) -> AnyResult - where - CustomT::ExecC: std::fmt::Debug + Clone + PartialEq + JsonSchema, - WasmT: Wasm, - BankT: Bank, - CustomT: CustomHandler, - { - match msg { - CosmosMsg::Wasm(msg) => self.wasm.execute(api, storage, &self, block, sender, msg), - CosmosMsg::Bank(msg) => self.bank.execute(storage, sender, msg), - CosmosMsg::Custom(msg) => self.custom.execute(api, storage, block, sender, msg), - _ => unimplemented!(), - } - } } -pub struct RouterQuerier<'a, Bank, Custom, Wasm> { - router: &'a Router, +pub struct RouterQuerier<'a, ExecC, QueryC> { + router: &'a dyn CosmosRouter, api: &'a dyn Api, storage: &'a dyn Storage, block_info: &'a BlockInfo, } -impl<'a, Bank, Custom, Wasm> RouterQuerier<'a, Bank, Custom, Wasm> { +impl<'a, ExecC, QueryC> RouterQuerier<'a, ExecC, QueryC> { pub fn new( - router: &'a Router, + router: &'a dyn CosmosRouter, api: &'a dyn Api, storage: &'a dyn Storage, block_info: &'a BlockInfo, @@ -539,16 +570,13 @@ impl<'a, Bank, Custom, Wasm> RouterQuerier<'a, Bank, Custom, Wasm> { } } -impl<'a, BankT, CustomT, WasmT> Querier for RouterQuerier<'a, BankT, CustomT, WasmT> +impl<'a, ExecC, QueryC> Querier for RouterQuerier<'a, ExecC, QueryC> where - CustomT::ExecC: Clone + fmt::Debug + PartialEq + JsonSchema + DeserializeOwned + 'static, - CustomT::QueryC: CustomQuery + DeserializeOwned + 'static, - WasmT: Wasm, - BankT: Bank, - CustomT: CustomHandler, + ExecC: Clone + fmt::Debug + PartialEq + JsonSchema + DeserializeOwned + 'static, + QueryC: CustomQuery + DeserializeOwned + 'static, { fn raw_query(&self, bin_request: &[u8]) -> QuerierResult { - let request: QueryRequest = match from_slice(bin_request) { + let request: QueryRequest = match from_slice(bin_request) { Ok(v) => v, Err(e) => { return SystemResult::Err(SystemError::InvalidRequest { @@ -622,7 +650,7 @@ mod test { // send both tokens let to_send = vec![coin(30, "eth"), coin(5, "btc")]; - let msg: CosmosMsg = BankMsg::Send { + let msg: cosmwasm_std::CosmosMsg = BankMsg::Send { to_address: rcpt.clone().into(), amount: to_send, } diff --git a/packages/multi-test/src/lib.rs b/packages/multi-test/src/lib.rs index 1c1e0fec4..c04ae504d 100644 --- a/packages/multi-test/src/lib.rs +++ b/packages/multi-test/src/lib.rs @@ -14,6 +14,7 @@ pub mod error; mod executor; mod test_helpers; mod transactions; +mod untyped_msg; mod wasm; pub use crate::app::{custom_app, next_block, App, AppBuilder, BasicApp, Router}; diff --git a/packages/multi-test/src/untyped_msg.rs b/packages/multi-test/src/untyped_msg.rs new file mode 100644 index 000000000..9e857fdcb --- /dev/null +++ b/packages/multi-test/src/untyped_msg.rs @@ -0,0 +1,95 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::fmt; + +use cosmwasm_std::{BankMsg, DistributionMsg, Empty, StakingMsg, WasmMsg}; + +/// This is needed so we can embed CosmosMsg as a trait bound. +/// See https://github.com/CosmWasm/cosmwasm/pull/1098 for a proper solution +/// (when we can deprecate this one) +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum CosmosMsg { + Bank(BankMsg), + // by default we use RawMsg, but a contract can override that + // to call into more app-specific code (whatever they define) + Custom(T), + #[cfg(feature = "staking")] + Distribution(DistributionMsg), + #[cfg(feature = "staking")] + Staking(StakingMsg), + Wasm(WasmMsg), +} + +impl From> for cosmwasm_std::CosmosMsg +where + T: Clone + fmt::Debug + PartialEq + JsonSchema, +{ + fn from(input: CosmosMsg) -> Self { + match input { + CosmosMsg::Bank(b) => cosmwasm_std::CosmosMsg::Bank(b), + CosmosMsg::Custom(c) => cosmwasm_std::CosmosMsg::Custom(c), + #[cfg(feature = "staking")] + CosmosMsg::Distribution(d) => cosmwasm_std::CosmosMsg::Distribution(d), + #[cfg(feature = "staking")] + CosmosMsg::Staking(s) => cosmwasm_std::CosmosMsg::Staking(s), + CosmosMsg::Wasm(w) => cosmwasm_std::CosmosMsg::Wasm(w), + } + } +} + +impl From> for CosmosMsg +where + T: Clone + fmt::Debug + PartialEq + JsonSchema, +{ + fn from(input: cosmwasm_std::CosmosMsg) -> CosmosMsg { + match input { + cosmwasm_std::CosmosMsg::Bank(b) => CosmosMsg::Bank(b), + cosmwasm_std::CosmosMsg::Custom(c) => CosmosMsg::Custom(c), + #[cfg(feature = "staking")] + cosmwasm_std::CosmosMsg::Distribution(d) => CosmosMsg::Distribution(d), + #[cfg(feature = "staking")] + cosmwasm_std::CosmosMsg::Staking(s) => CosmosMsg::Staking(s), + cosmwasm_std::CosmosMsg::Wasm(w) => CosmosMsg::Wasm(w), + _ => panic!("Unsupported type"), + } + } +} + +impl From for CosmosMsg +where + T: Clone + fmt::Debug + PartialEq + JsonSchema, +{ + fn from(msg: BankMsg) -> Self { + CosmosMsg::Bank(msg) + } +} + +#[cfg(feature = "staking")] +impl From for CosmosMsg +where + T: Clone + fmt::Debug + PartialEq + JsonSchema, +{ + fn from(msg: StakingMsg) -> Self { + CosmosMsg::Staking(msg) + } +} + +#[cfg(feature = "staking")] +impl From for CosmosMsg +where + T: Clone + fmt::Debug + PartialEq + JsonSchema, +{ + fn from(msg: DistributionMsg) -> Self { + CosmosMsg::Distribution(msg) + } +} + +impl From for CosmosMsg +where + T: Clone + fmt::Debug + PartialEq + JsonSchema, +{ + fn from(msg: WasmMsg) -> Self { + CosmosMsg::Wasm(msg) + } +} diff --git a/packages/multi-test/src/wasm.rs b/packages/multi-test/src/wasm.rs index a1c4f7cf3..7219e9357 100644 --- a/packages/multi-test/src/wasm.rs +++ b/packages/multi-test/src/wasm.rs @@ -15,12 +15,11 @@ use serde::{Deserialize, Serialize}; use cw_storage_plus::Map; -use crate::app::{Router, RouterQuerier}; +use crate::app::{CosmosRouter, RouterQuerier}; use crate::contracts::Contract; use crate::error::Error; use crate::executor::AppResponse; use crate::transactions::transactional; -use crate::{Bank, CustomHandler}; use cosmwasm_std::testing::mock_wasmd_attr; use anyhow::{anyhow, bail, Result as AnyResult}; @@ -59,19 +58,15 @@ pub trait Wasm { ) -> AnyResult; /// Handles all WasmMsg messages - fn execute( + fn execute( &self, api: &dyn Api, storage: &mut dyn Storage, - router: &Router, + router: &dyn CosmosRouter, block: &BlockInfo, sender: Addr, msg: WasmMsg, - ) -> AnyResult - where - Self: Sized, - BankT: Bank, - CustomT: CustomHandler; + ) -> AnyResult; // Add a new contract. Must be done on the base object, when no contracts running fn store_code(&mut self, code: Box>) -> usize; @@ -80,19 +75,15 @@ pub trait Wasm { fn contract_data(&self, storage: &dyn Storage, address: &Addr) -> AnyResult; /// Admin interface, cannot be called via CosmosMsg - fn sudo( + fn sudo( &self, api: &dyn Api, contract_addr: Addr, storage: &mut dyn Storage, - router: &Router, + router: &dyn CosmosRouter, block: &BlockInfo, msg: Vec, - ) -> AnyResult - where - Self: Sized, - BankT: Bank, - CustomT: CustomHandler; + ) -> AnyResult; } pub struct WasmKeeper { @@ -138,19 +129,15 @@ where } } - fn execute( + fn execute( &self, api: &dyn Api, storage: &mut dyn Storage, - router: &Router, + router: &dyn CosmosRouter, block: &BlockInfo, sender: Addr, msg: WasmMsg, - ) -> AnyResult - where - BankT: Bank, - CustomT: CustomHandler, - { + ) -> AnyResult { let (resender, res, custom_event) = self.execute_wasm(api, storage, router, block, sender, msg)?; @@ -168,19 +155,15 @@ where self.load_contract(storage, address) } - fn sudo( + fn sudo( &self, api: &dyn Api, contract: Addr, storage: &mut dyn Storage, - router: &Router, + router: &dyn CosmosRouter, block: &BlockInfo, msg: Vec, - ) -> AnyResult - where - BankT: Bank, - CustomT: CustomHandler, - { + ) -> AnyResult { let custom_event = Event::new("sudo").add_attribute(CONTRACT_ATTR, &contract); let res = self.call_sudo(contract.clone(), api, storage, router, block, msg)?; @@ -223,11 +206,11 @@ where data.into() } - fn send( + fn send( &self, api: &dyn Api, storage: &mut dyn Storage, - router: &Router, + router: &dyn CosmosRouter, block: &BlockInfo, sender: T, recipient: String, @@ -235,14 +218,13 @@ where ) -> AnyResult where T: Into, - BankT: Bank, - CustomT: CustomHandler, { if !amount.is_empty() { - let msg = BankMsg::Send { + let msg: cosmwasm_std::CosmosMsg = BankMsg::Send { to_address: recipient, amount: amount.to_vec(), - }; + } + .into(); let res = router.execute(api, storage, block, sender.into(), msg.into())?; Ok(res) } else { @@ -251,19 +233,15 @@ where } // this returns the contract address as well, so we can properly resend the data - fn execute_wasm( + fn execute_wasm( &self, api: &dyn Api, storage: &mut dyn Storage, - router: &Router, + router: &dyn CosmosRouter, block: &BlockInfo, sender: Addr, wasm_msg: WasmMsg, - ) -> AnyResult<(Addr, Response, Event)> - where - BankT: Bank, - CustomT: CustomHandler, - { + ) -> AnyResult<(Addr, Response, Event)> { match wasm_msg { WasmMsg::Execute { contract_addr, @@ -394,26 +372,22 @@ where /// /// The `data` on `AppResponse` is data returned from `reply` call, not from execution of /// submessage itself. In case if `reply` is not called, no `data` is set. - fn execute_submsg( + fn execute_submsg( &self, api: &dyn Api, - router: &Router, + router: &dyn CosmosRouter, storage: &mut dyn Storage, block: &BlockInfo, contract: Addr, msg: SubMsg, - ) -> AnyResult - where - BankT: Bank, - CustomT: CustomHandler, - { + ) -> AnyResult { let SubMsg { msg, id, reply_on, .. } = msg; // execute in cache let res = transactional(storage, |write_cache, _| { - router.execute(api, write_cache, block, contract.clone(), msg) + router.execute(api, write_cache, block, contract.clone(), msg.into()) }); // call reply if meaningful @@ -453,19 +427,15 @@ where } } - fn _reply( + fn _reply( &self, api: &dyn Api, - router: &Router, + router: &dyn CosmosRouter, storage: &mut dyn Storage, block: &BlockInfo, contract: Addr, reply: Reply, - ) -> AnyResult - where - BankT: Bank, - CustomT: CustomHandler, - { + ) -> AnyResult { let ok_attr = if reply.result.is_ok() { "handle_success" } else { @@ -526,20 +496,16 @@ where (app, messages) } - fn process_response( + fn process_response( &self, api: &dyn Api, - router: &Router, + router: &dyn CosmosRouter, storage: &mut dyn Storage, block: &BlockInfo, contract: Addr, response: AppResponse, messages: Vec>, - ) -> AnyResult - where - BankT: Bank, - CustomT: CustomHandler, - { + ) -> AnyResult { let AppResponse { mut events, data } = response; // recurse in all messages @@ -582,20 +548,16 @@ where Ok(addr) } - pub fn call_execute( + pub fn call_execute( &self, api: &dyn Api, storage: &mut dyn Storage, address: Addr, - router: &Router, + router: &dyn CosmosRouter, block: &BlockInfo, info: MessageInfo, msg: Vec, - ) -> AnyResult> - where - BankT: Bank, - CustomT: CustomHandler, - { + ) -> AnyResult> { Self::verify_response(self.with_storage( api, storage, @@ -606,20 +568,16 @@ where )?) } - pub fn call_instantiate( + pub fn call_instantiate( &self, address: Addr, api: &dyn Api, storage: &mut dyn Storage, - router: &Router, + router: &dyn CosmosRouter, block: &BlockInfo, info: MessageInfo, msg: Vec, - ) -> AnyResult> - where - BankT: Bank, - CustomT: CustomHandler, - { + ) -> AnyResult> { Self::verify_response(self.with_storage( api, storage, @@ -630,19 +588,15 @@ where )?) } - pub fn call_reply( + pub fn call_reply( &self, address: Addr, api: &dyn Api, storage: &mut dyn Storage, - router: &Router, + router: &dyn CosmosRouter, block: &BlockInfo, reply: Reply, - ) -> AnyResult> - where - BankT: Bank, - CustomT: CustomHandler, - { + ) -> AnyResult> { Self::verify_response(self.with_storage( api, storage, @@ -653,19 +607,15 @@ where )?) } - pub fn call_sudo( + pub fn call_sudo( &self, address: Addr, api: &dyn Api, storage: &mut dyn Storage, - router: &Router, + router: &dyn CosmosRouter, block: &BlockInfo, msg: Vec, - ) -> AnyResult> - where - BankT: Bank, - CustomT: CustomHandler, - { + ) -> AnyResult> { Self::verify_response(self.with_storage( api, storage, @@ -676,19 +626,15 @@ where )?) } - pub fn call_migrate( + pub fn call_migrate( &self, address: Addr, api: &dyn Api, storage: &mut dyn Storage, - router: &Router, + router: &dyn CosmosRouter, block: &BlockInfo, msg: Vec, - ) -> AnyResult> - where - BankT: Bank, - CustomT: CustomHandler, - { + ) -> AnyResult> { Self::verify_response(self.with_storage( api, storage, @@ -736,21 +682,18 @@ where action(handler, deps, env) } - fn with_storage( + fn with_storage( &self, api: &dyn Api, storage: &mut dyn Storage, - router: &Router, + router: &dyn CosmosRouter, block: &BlockInfo, address: Addr, action: F, ) -> AnyResult where F: FnOnce(&Box>, DepsMut, Env) -> AnyResult, - BankT: Bank, - CustomT: CustomHandler, ExecC: DeserializeOwned, - QueryC: 'static, { let contract = self.load_contract(storage, &address)?; let handler = self @@ -917,6 +860,7 @@ mod test { use cosmwasm_std::testing::{mock_env, mock_info, MockApi, MockQuerier, MockStorage}; use cosmwasm_std::{coin, from_slice, to_vec, BankMsg, Coin, CosmosMsg, Empty, StdError}; + use crate::app::Router; use crate::test_helpers::contracts::{error, payout}; use crate::transactions::StorageTransaction; use crate::BankKeeper;