diff --git a/packages/multi-test/src/app.rs b/packages/multi-test/src/app.rs index 8d915350a..93fb30ba2 100644 --- a/packages/multi-test/src/app.rs +++ b/packages/multi-test/src/app.rs @@ -19,51 +19,89 @@ use crate::wasm::{ContractData, Wasm, WasmKeeper}; use crate::BankKeeper; use anyhow::Result as AnyResult; -use derivative::Derivative; pub fn next_block(block: &mut BlockInfo) { block.time = block.time.plus_seconds(5); block.height += 1; } +/// Type alias for default build `App` to make its storing simpler in typical scenario +pub type BasicApp = App< + BankKeeper, + MockApi, + MockStorage, + PanickingCustomHandler, + WasmKeeper, +>; + /// Router is a persisted state. You can query this. /// Execution generally happens on the RouterCache, which then can be atomically committed or rolled back. /// We offer .execute() as a wrapper around cache, execute, commit/rollback process. -/// -/// ExecC is the custom message returned init, handle, sudo (Response). -/// All contracts must return Response or Response. -/// -/// Also `ExecC` is the custom message which is handled by custom message handler. -/// -/// `QueryC` is custom query message handled by custom message handler. -pub struct App +pub struct App< + Bank = BankKeeper, + Api = MockApi, + Storage = MockStorage, + Custom = PanickingCustomHandler, + Wasm = WasmKeeper, +> { + router: Router, + api: Api, + storage: Storage, + block: BlockInfo, +} + +impl Default for BasicApp { + fn default() -> Self { + Self::new() + } +} + +impl BasicApp { + /// Creates new default `App` implementation working with Empty custom messages. + pub fn new() -> Self { + AppBuilder::new().build() + } +} + +/// Creates new default `App` implementation working with customized exec and query messages. +/// Outside of `App` implementation to make type elision better. +pub fn custom_app() -> BasicApp where - ExecC: Clone + fmt::Debug + PartialEq + JsonSchema + 'static, + ExecC: Debug + Clone + PartialEq + JsonSchema + DeserializeOwned + 'static, + QueryC: Debug + CustomQuery + DeserializeOwned + 'static, { - router: Router, - api: Box, - storage: Box, - block: BlockInfo, + AppBuilder::new_custom().build() } -impl Querier for App +impl Querier for App where - ExecC: Clone + fmt::Debug + PartialEq + JsonSchema + 'static, - QueryC: CustomQuery + DeserializeOwned, + CustomT::ExecC: Clone + fmt::Debug + PartialEq + JsonSchema + DeserializeOwned + 'static, + CustomT::QueryC: CustomQuery + DeserializeOwned + 'static, + WasmT: Wasm, + BankT: Bank, + ApiT: Api, + StorageT: Storage, + CustomT: CustomHandler, { fn raw_query(&self, bin_request: &[u8]) -> QuerierResult { self.router - .querier(&*self.api, &*self.storage, &self.block) + .querier(&self.api, &self.storage, &self.block) .raw_query(bin_request) } } -impl Executor for App +impl Executor + for App where - ExecC: Clone + fmt::Debug + PartialEq + JsonSchema + 'static, - QueryC: CustomQuery + DeserializeOwned, + CustomT::ExecC: Clone + fmt::Debug + PartialEq + JsonSchema + DeserializeOwned + 'static, + CustomT::QueryC: CustomQuery + DeserializeOwned + 'static, + WasmT: Wasm, + BankT: Bank, + ApiT: Api, + StorageT: Storage, + CustomT: CustomHandler, { - fn execute(&mut self, sender: Addr, msg: CosmosMsg) -> AnyResult { + fn execute(&mut self, sender: Addr, msg: CosmosMsg) -> AnyResult { let mut all = self.execute_multi(sender, vec![msg])?; let res = all.pop().unwrap(); Ok(res) @@ -71,101 +109,255 @@ where } /// Utility to build App in stages. If particular items wont be set, defaults would be used -#[derive(Derivative)] -#[derivative(Default(bound = "", new = "true"))] -pub struct AppBuilder { - wasm: Option>>, - bank: Option>, - api: Option>, - storage: Option>, - custom: Option>>, - block: Option, +pub struct AppBuilder { + wasm: Wasm, + bank: Bank, + api: Api, + storage: Storage, + custom: Custom, + block: BlockInfo, } -impl AppBuilder +impl Default + for AppBuilder< + BankKeeper, + MockApi, + MockStorage, + PanickingCustomHandler, + WasmKeeper, + > +{ + fn default() -> Self { + Self::new() + } +} + +impl + AppBuilder< + BankKeeper, + MockApi, + MockStorage, + PanickingCustomHandler, + WasmKeeper, + > +{ + /// Creates builder with default components working with empty exec and query messages. + pub fn new() -> Self { + AppBuilder { + wasm: WasmKeeper::new(), + bank: BankKeeper::new(), + api: MockApi::default(), + storage: MockStorage::new(), + custom: PanickingCustomHandler::new(), + block: mock_env().block, + } + } +} + +impl + AppBuilder< + BankKeeper, + MockApi, + MockStorage, + PanickingCustomHandler, + WasmKeeper, + > where - ExecC: Debug + PartialEq + Clone + JsonSchema + 'static, + ExecC: Debug + Clone + PartialEq + JsonSchema + DeserializeOwned + 'static, QueryC: Debug + CustomQuery + DeserializeOwned + 'static, { - /// Overwrites default wasm executor. Panic if already set. - #[track_caller] - pub fn with_wasm(mut self, wasm: impl Wasm + 'static) -> Self { - assert!(self.wasm.is_none(), "Wasm executor already overwritten"); - self.wasm = Some(Box::new(wasm)); - self + /// Creates builder with default components designed to work with custom exec and query + /// messages. + pub fn new_custom() -> Self { + AppBuilder { + wasm: WasmKeeper::new(), + bank: BankKeeper::new(), + api: MockApi::default(), + storage: MockStorage::new(), + custom: PanickingCustomHandler::new(), + block: mock_env().block, + } + } +} + +impl AppBuilder { + /// Overwrites default wasm executor. + /// + /// At this point it is needed that new wasm implements some `Wasm` trait, but it doesn't need + /// to be bound to Bank or Custom yet - as those may change. The cross-components validation is + /// done on final building. + /// + /// Also it is possible to completely abandon trait bounding here which would not be bad idea, + /// however it might make the message on build creepy in many cases, so as for properly build + /// `App` we always want `Wasm` to be `Wasm`, some checks are done early. + pub fn with_wasm>( + self, + wasm: NewWasm, + ) -> AppBuilder { + let AppBuilder { + bank, + api, + storage, + custom, + block, + .. + } = self; + + AppBuilder { + wasm, + bank, + api, + storage, + custom, + block, + } } /// Overwrites default bank interface - #[track_caller] - pub fn with_bank(mut self, bank: impl Bank + 'static) -> Self { - assert!(self.bank.is_none(), "Bank interface already overwritten"); - self.bank = Some(Box::new(bank)); - self + pub fn with_bank( + self, + bank: NewBank, + ) -> AppBuilder { + let AppBuilder { + wasm, + api, + storage, + custom, + block, + .. + } = self; + + AppBuilder { + wasm, + bank, + api, + storage, + custom, + block, + } } /// Overwrites default api interface - #[track_caller] - pub fn with_api(mut self, api: impl Api + 'static) -> Self { - assert!(self.api.is_none(), "API interface already overwritten"); - self.api = Some(Box::new(api)); - self + pub fn with_api( + self, + api: NewApi, + ) -> AppBuilder { + let AppBuilder { + wasm, + bank, + storage, + custom, + block, + .. + } = self; + + AppBuilder { + wasm, + bank, + api, + storage, + custom, + block, + } } /// Overwrites default storage interface - #[track_caller] - pub fn with_storage(mut self, storage: impl Storage + 'static) -> Self { - assert!( - self.storage.is_none(), - "Storage interface already overwritten" - ); - self.storage = Some(Box::new(storage)); - self + pub fn with_storage( + self, + storage: NewStorage, + ) -> AppBuilder { + let AppBuilder { + wasm, + api, + bank, + custom, + block, + .. + } = self; + + AppBuilder { + wasm, + bank, + api, + storage, + custom, + block, + } } /// Overwrites default custom messages handler - #[track_caller] - pub fn with_custom(mut self, custom: impl CustomHandler + 'static) -> Self { - assert!(self.custom.is_none(), "Custom handler already overwritten"); - self.custom = Some(Box::new(custom)); - self + /// + /// At this point it is needed that new custom implements some `CustomHandler` trait, but it doesn't need + /// to be bound to ExecC or QueryC yet - as those may change. The cross-components validation is + /// done on final building. + /// + /// Also it is possible to completely abandon trait bounding here which would not be bad idea, + /// however it might make the message on build creepy in many cases, so as for properly build + /// `App` we always want `Wasm` to be `Wasm`, some checks are done early. + pub fn with_custom( + self, + custom: NewCustom, + ) -> AppBuilder { + let AppBuilder { + wasm, + bank, + api, + storage, + block, + .. + } = self; + + AppBuilder { + wasm, + bank, + api, + storage, + custom, + block, + } } /// Overwrites default initial block - #[track_caller] pub fn with_block(mut self, block: BlockInfo) -> Self { - assert!( - self.block.is_none(), - "Initial block info already overwritten" - ); - self.block = Some(block); + self.block = block; self } - pub fn build(self) -> App { - let wasm = self.wasm.unwrap_or_else(|| Box::new(WasmKeeper::new())); - let bank = self.bank.unwrap_or_else(|| Box::new(BankKeeper::new())); - let api = self.api.unwrap_or_else(|| Box::new(MockApi::default())); - let storage = self.storage.unwrap_or_else(|| Box::new(MockStorage::new())); - let block = self.block.unwrap_or_else(|| mock_env().block); - let custom = self - .custom - .unwrap_or_else(|| Box::new(PanickingCustomHandler)); - - let router = Router { wasm, bank, custom }; + /// Builds final `App`. At this point all components type have to be properly related to each + /// other. If there are some generics related compilation error make sure, that all components + /// are properly relating to each other. + pub fn build(self) -> App + where + BankT: Bank, + ApiT: Api, + StorageT: Storage, + CustomT: CustomHandler, + WasmT: Wasm, + { + let router = Router { + wasm: self.wasm, + bank: self.bank, + custom: self.custom, + }; App { router, - api, - storage, - block, + api: self.api, + storage: self.storage, + block: self.block, } } } -impl App +impl App where - ExecC: Clone + fmt::Debug + PartialEq + JsonSchema + 'static, - QueryC: CustomQuery + DeserializeOwned, + CustomT::ExecC: std::fmt::Debug + PartialEq + Clone + JsonSchema + DeserializeOwned + 'static, + CustomT::QueryC: CustomQuery + DeserializeOwned + 'static, + WasmT: Wasm, + BankT: Bank, + ApiT: Api, + StorageT: Storage, + CustomT: CustomHandler, { pub fn set_block(&mut self, block: BlockInfo) { self.block = block; @@ -193,7 +385,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 @@ -206,9 +398,9 @@ where storage, } = self; - transactional(&mut **storage, |write_cache, _| { + 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)) .collect() }) } @@ -217,18 +409,18 @@ where pub fn init_bank_balance(&mut self, account: &Addr, amount: Vec) -> AnyResult<()> { self.router .bank - .init_balance(&mut *self.storage, account, amount) + .init_balance(&mut self.storage, account, amount) } /// This registers contract code (like uploading wasm bytecode on a chain), /// so it can later be used to instantiate a contract. - pub fn store_code(&mut self, code: Box>) -> u64 { + pub fn store_code(&mut self, code: Box>) -> u64 { self.router.wasm.store_code(code) as u64 } /// This allows to get `ContractData` for specific contract pub fn contract_data(&self, address: &Addr) -> AnyResult { - self.router.wasm.contract_data(&*self.storage, address) + self.router.wasm.contract_data(&self.storage, address) } /// Runs arbitrary CosmosMsg in "sudo" mode. @@ -241,9 +433,9 @@ where ) -> AnyResult { let msg = to_vec(msg)?; self.router.wasm.sudo( - &*self.api, + &self.api, contract_addr.into(), - &mut *self.storage, + &mut self.storage, &self.router, &self.block, msg, @@ -251,23 +443,19 @@ where } } -pub struct Router { - pub(crate) wasm: Box>, - pub(crate) bank: Box, - pub(crate) custom: Box>, +pub struct Router { + pub(crate) wasm: Wasm, + pub(crate) bank: Bank, + pub(crate) custom: Custom, } -impl Router -where - ExecC: Clone + fmt::Debug + PartialEq + JsonSchema + 'static, - QueryC: CustomQuery + DeserializeOwned, -{ +impl Router { pub fn querier<'a>( &'a self, api: &'a dyn Api, storage: &'a dyn Storage, block_info: &'a BlockInfo, - ) -> RouterQuerier<'a, ExecC, QueryC> { + ) -> RouterQuerier<'a, BankT, CustomT, WasmT> { RouterQuerier { router: self, api, @@ -284,8 +472,16 @@ where api: &dyn Api, storage: &dyn Storage, block: &BlockInfo, - request: QueryRequest, - ) -> AnyResult { + 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, + { match request { QueryRequest::Wasm(req) => { self.wasm @@ -303,8 +499,14 @@ where storage: &mut dyn Storage, block: &BlockInfo, sender: Addr, - msg: CosmosMsg, - ) -> AnyResult { + 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), @@ -314,22 +516,16 @@ where } } -pub struct RouterQuerier<'a, ExecC, QueryC> -where - ExecC: Clone + fmt::Debug + PartialEq + JsonSchema + 'static, -{ - router: &'a Router, +pub struct RouterQuerier<'a, Bank, Custom, Wasm> { + router: &'a Router, api: &'a dyn Api, storage: &'a dyn Storage, block_info: &'a BlockInfo, } -impl<'a, ExecC, QueryC> RouterQuerier<'a, ExecC, QueryC> -where - ExecC: Clone + fmt::Debug + PartialEq + JsonSchema + 'static, -{ +impl<'a, Bank, Custom, Wasm> RouterQuerier<'a, Bank, Custom, Wasm> { pub fn new( - router: &'a Router, + router: &'a Router, api: &'a dyn Api, storage: &'a dyn Storage, block_info: &'a BlockInfo, @@ -343,13 +539,16 @@ where } } -impl<'a, ExecC, QueryC> Querier for RouterQuerier<'a, ExecC, QueryC> +impl<'a, BankT, CustomT, WasmT> Querier for RouterQuerier<'a, BankT, CustomT, WasmT> where - ExecC: Clone + fmt::Debug + PartialEq + JsonSchema + 'static, - QueryC: CustomQuery + DeserializeOwned, + CustomT::ExecC: Clone + fmt::Debug + PartialEq + JsonSchema + DeserializeOwned + 'static, + CustomT::QueryC: CustomQuery + DeserializeOwned + 'static, + WasmT: Wasm, + BankT: Bank, + CustomT: CustomHandler, { 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 { @@ -381,24 +580,25 @@ mod test { use super::*; - fn mock_app() -> App { - AppBuilder::new().build() - } - - fn custom_app() -> App { - AppBuilder::new().build() - } - - fn get_balance(app: &App, addr: &Addr) -> Vec + fn get_balance( + app: &App, + addr: &Addr, + ) -> Vec where - C: Clone + fmt::Debug + PartialEq + JsonSchema, + CustomT::ExecC: Clone + fmt::Debug + PartialEq + JsonSchema + DeserializeOwned + 'static, + CustomT::QueryC: CustomQuery + DeserializeOwned + 'static, + WasmT: Wasm, + BankT: Bank, + ApiT: Api, + StorageT: Storage, + CustomT: CustomHandler, { app.wrap().query_all_balances(addr).unwrap() } #[test] fn update_block() { - let mut app = mock_app(); + let mut app = App::new(); let BlockInfo { time, height, .. } = app.block; app.update_block(next_block); @@ -409,7 +609,7 @@ mod test { #[test] fn send_tokens() { - let mut app = mock_app(); + let mut app = App::new(); let owner = Addr::unchecked("owner"); let rcpt = Addr::unchecked("receiver"); @@ -450,7 +650,7 @@ mod test { #[test] fn simple_contract() { - let mut app = mock_app(); + let mut app = App::new(); // set personal balance let owner = Addr::unchecked("owner"); @@ -529,7 +729,7 @@ mod test { #[test] fn reflect_success() { - let mut app = custom_app(); + let mut app = custom_app::(); // set personal balance let owner = Addr::unchecked("owner"); @@ -626,7 +826,7 @@ mod test { #[test] fn reflect_error() { - let mut app = custom_app(); + let mut app = custom_app::(); // set personal balance let owner = Addr::unchecked("owner"); @@ -717,7 +917,7 @@ mod test { #[test] fn sudo_works() { - let mut app = mock_app(); + let mut app = App::new(); let owner = Addr::unchecked("owner"); let init_funds = vec![coin(100, "eth")]; @@ -751,7 +951,7 @@ mod test { #[test] fn reflect_submessage_reply_works() { - let mut app = custom_app(); + let mut app = custom_app::(); // set personal balance let owner = Addr::unchecked("owner"); @@ -835,14 +1035,17 @@ mod test { // TODO: check error? } - fn query_router( - router: &Router, + fn query_router( + router: &Router, api: &dyn Api, storage: &dyn Storage, rcpt: &Addr, ) -> Vec where - ExecC: Clone + fmt::Debug + PartialEq + JsonSchema, + CustomT::ExecC: Clone + fmt::Debug + PartialEq + JsonSchema, + WasmT: Wasm, + BankT: Bank, + CustomT: CustomHandler, { let query = BankQuery::AllBalances { address: rcpt.into(), @@ -852,9 +1055,19 @@ mod test { val.amount } - fn query_app(app: &App, rcpt: &Addr) -> Vec + fn query_app( + app: &App, + rcpt: &Addr, + ) -> Vec where - C: Clone + fmt::Debug + PartialEq + JsonSchema, + CustomT::ExecC: + std::fmt::Debug + PartialEq + Clone + JsonSchema + DeserializeOwned + 'static, + CustomT::QueryC: CustomQuery + DeserializeOwned + 'static, + WasmT: Wasm, + BankT: Bank, + ApiT: Api, + StorageT: Storage, + CustomT: CustomHandler, { let query = BankQuery::AllBalances { address: rcpt.into(), @@ -866,7 +1079,7 @@ mod test { #[test] fn multi_level_bank_cache() { - let mut app = mock_app(); + let mut app = App::new(); // set personal balance let owner = Addr::unchecked("owner"); @@ -875,17 +1088,17 @@ mod test { app.init_bank_balance(&owner, init_funds).unwrap(); // cache 1 - send some tokens - let mut cache = StorageTransaction::new(&*app.storage); + let mut cache = StorageTransaction::new(&app.storage); let msg = BankMsg::Send { to_address: rcpt.clone().into(), amount: coins(25, "eth"), }; app.router - .execute(&*app.api, &mut cache, &app.block, owner.clone(), msg.into()) + .execute(&app.api, &mut cache, &app.block, owner.clone(), msg.into()) .unwrap(); // shows up in cache - let cached_rcpt = query_router(&app.router, &*app.api, &cache, &rcpt); + let cached_rcpt = query_router(&app.router, &app.api, &cache, &rcpt); assert_eq!(coins(25, "eth"), cached_rcpt); let router_rcpt = query_app(&app, &rcpt); assert_eq!(router_rcpt, vec![]); @@ -897,20 +1110,20 @@ mod test { amount: coins(12, "eth"), }; app.router - .execute(&*app.api, cache2, &app.block, owner, msg.into()) + .execute(&app.api, cache2, &app.block, owner, msg.into()) .unwrap(); // shows up in 2nd cache - let cached_rcpt = query_router(&app.router, &*app.api, read, &rcpt); + let cached_rcpt = query_router(&app.router, &app.api, read, &rcpt); assert_eq!(coins(25, "eth"), cached_rcpt); - let cached2_rcpt = query_router(&app.router, &*app.api, cache2, &rcpt); + let cached2_rcpt = query_router(&app.router, &app.api, cache2, &rcpt); assert_eq!(coins(37, "eth"), cached2_rcpt); Ok(()) }) .unwrap(); // apply first to router - cache.prepare().commit(&mut *app.storage); + cache.prepare().commit(&mut app.storage); let committed = query_app(&app, &rcpt); assert_eq!(coins(37, "eth"), committed); @@ -923,7 +1136,7 @@ mod test { // additional 20btc. Then beneficiary balance is checked - expeced value is 30btc. 10btc // would mean that sending tokens with message is not visible for this very message, and // 20btc means, that only such just send funds are visible. - let mut app = mock_app(); + let mut app = App::new(); let owner = Addr::unchecked("owner"); let beneficiary = Addr::unchecked("beneficiary"); @@ -967,7 +1180,7 @@ mod test { // migrate fails if not admin // migrate succeeds if admin // check beneficiary updated - let mut app = mock_app(); + let mut app = App::new(); let owner = Addr::unchecked("owner"); let beneficiary = Addr::unchecked("beneficiary"); @@ -1072,7 +1285,7 @@ mod test { #[test] fn no_submsg() { - let mut app = mock_app(); + let mut app = App::new(); let owner = Addr::unchecked("owner"); @@ -1098,7 +1311,7 @@ mod test { #[test] fn single_submsg() { - let mut app = mock_app(); + let mut app = App::new(); let owner = Addr::unchecked("owner"); @@ -1125,7 +1338,7 @@ mod test { #[test] fn single_submsg_no_reply() { - let mut app = mock_app(); + let mut app = App::new(); let owner = Addr::unchecked("owner"); @@ -1152,7 +1365,7 @@ mod test { #[test] fn single_no_submsg_data() { - let mut app = mock_app(); + let mut app = App::new(); let owner = Addr::unchecked("owner"); @@ -1179,7 +1392,7 @@ mod test { #[test] fn single_no_top_level_data() { - let mut app = mock_app(); + let mut app = App::new(); let owner = Addr::unchecked("owner"); @@ -1205,7 +1418,7 @@ mod test { #[test] fn single_submsg_reply_returns_none() { - let mut app = custom_app(); + let mut app = custom_app::(); // set personal balance let owner = Addr::unchecked("owner"); @@ -1261,7 +1474,7 @@ mod test { #[test] fn multiple_submsg() { - let mut app = mock_app(); + let mut app = App::new(); let owner = Addr::unchecked("owner"); @@ -1293,7 +1506,7 @@ mod test { #[test] fn multiple_submsg_no_reply() { - let mut app = mock_app(); + let mut app = App::new(); let owner = Addr::unchecked("owner"); @@ -1325,7 +1538,7 @@ mod test { #[test] fn multiple_submsg_mixed() { - let mut app = mock_app(); + let mut app = App::new(); let owner = Addr::unchecked("owner"); @@ -1357,7 +1570,7 @@ mod test { #[test] fn nested_submsg() { - let mut app = mock_app(); + let mut app = App::new(); let owner = Addr::unchecked("owner"); @@ -1403,7 +1616,7 @@ mod test { #[test] fn empty_attribute_key() { - let mut app = mock_app(); + let mut app = App::new(); let owner = Addr::unchecked("owner"); @@ -1433,7 +1646,7 @@ mod test { #[test] fn empty_attribute_value() { - let mut app = mock_app(); + let mut app = App::new(); let owner = Addr::unchecked("owner"); @@ -1463,7 +1676,7 @@ mod test { #[test] fn empty_event_attribute_key() { - let mut app = mock_app(); + let mut app = App::new(); let owner = Addr::unchecked("owner"); @@ -1492,7 +1705,7 @@ mod test { #[test] fn empty_event_attribute_value() { - let mut app = mock_app(); + let mut app = App::new(); let owner = Addr::unchecked("owner"); @@ -1521,7 +1734,7 @@ mod test { #[test] fn too_short_event_type() { - let mut app = mock_app(); + let mut app = App::new(); let owner = Addr::unchecked("owner"); @@ -1560,7 +1773,7 @@ mod test { let custom_handler = CachingCustomHandler::::new(); let custom_handler_state = custom_handler.state(); - let mut app = AppBuilder::new() + let mut app = AppBuilder::new_custom() .with_api(api) .with_custom(custom_handler) .build(); diff --git a/packages/multi-test/src/custom_handler.rs b/packages/multi-test/src/custom_handler.rs index f0eb399e3..f3420806f 100644 --- a/packages/multi-test/src/custom_handler.rs +++ b/packages/multi-test/src/custom_handler.rs @@ -1,8 +1,9 @@ -use cosmwasm_std::{Addr, Api, Binary, BlockInfo, Empty, Storage}; +use cosmwasm_std::{Addr, Api, Binary, BlockInfo, Storage}; use anyhow::Result as AnyResult; use derivative::Derivative; use std::cell::{Ref, RefCell}; +use std::marker::PhantomData; use std::ops::Deref; use std::rc::Rc; @@ -10,14 +11,20 @@ use crate::AppResponse; /// Custom message handler trait. Implementor of this trait is mocking environment behavior on /// given custom message. -pub trait CustomHandler { +pub trait CustomHandler { + /// Custom exec message for this handler + type ExecC; + + /// Custom query message for this handler + type QueryC; + fn execute( &self, api: &dyn Api, storage: &mut dyn Storage, block: &BlockInfo, sender: Addr, - msg: ExecC, + msg: Self::ExecC, ) -> AnyResult; fn query( @@ -25,26 +32,41 @@ pub trait CustomHandler { api: &dyn Api, storage: &dyn Storage, block: &BlockInfo, - msg: QueryC, + msg: Self::QueryC, ) -> AnyResult; } /// Custom handler implementation panicking on each call. Assuming, that unless specific behavior /// is implemented, custom messages should not be send. -pub(crate) struct PanickingCustomHandler; +pub struct PanickingCustomHandler(PhantomData, PhantomData); + +impl PanickingCustomHandler { + pub fn new() -> Self { + PanickingCustomHandler(PhantomData, PhantomData) + } +} -impl CustomHandler for PanickingCustomHandler +impl Default for PanickingCustomHandler { + fn default() -> Self { + Self::new() + } +} + +impl CustomHandler for PanickingCustomHandler where - ExecC: std::fmt::Debug, - QueryC: std::fmt::Debug, + Exec: std::fmt::Debug, + Query: std::fmt::Debug, { + type ExecC = Exec; + type QueryC = Query; + fn execute( &self, _api: &dyn Api, _storage: &mut dyn Storage, _block: &BlockInfo, sender: Addr, - msg: ExecC, + msg: Self::ExecC, ) -> AnyResult { panic!("Unexpected custom exec msg {:?} from {:?}", msg, sender) } @@ -54,7 +76,7 @@ where _api: &dyn Api, _storage: &dyn Storage, _block: &BlockInfo, - msg: QueryC, + msg: Self::QueryC, ) -> AnyResult { panic!("Unexpected custom query {:?}", msg) } @@ -99,14 +121,17 @@ impl CachingCustomHandler { } } -impl CustomHandler for CachingCustomHandler { +impl CustomHandler for CachingCustomHandler { + type ExecC = Exec; + type QueryC = Query; + fn execute( &self, _api: &dyn Api, _storage: &mut dyn Storage, _block: &BlockInfo, _sender: Addr, - msg: ExecC, + msg: Exec, ) -> AnyResult { self.state.execs.borrow_mut().push(msg); Ok(AppResponse::default()) @@ -117,7 +142,7 @@ impl CustomHandler for CachingCustomHandler AnyResult { self.state.queries.borrow_mut().push(msg); Ok(Binary::default()) diff --git a/packages/multi-test/src/lib.rs b/packages/multi-test/src/lib.rs index b3d83e37e..1c1e0fec4 100644 --- a/packages/multi-test/src/lib.rs +++ b/packages/multi-test/src/lib.rs @@ -16,7 +16,7 @@ mod test_helpers; mod transactions; mod wasm; -pub use crate::app::{next_block, App, AppBuilder, Router}; +pub use crate::app::{custom_app, next_block, App, AppBuilder, BasicApp, Router}; pub use crate::bank::{Bank, BankKeeper}; pub use crate::contracts::{Contract, ContractWrapper}; pub use crate::custom_handler::CustomHandler; diff --git a/packages/multi-test/src/wasm.rs b/packages/multi-test/src/wasm.rs index 66f3893ae..a1c4f7cf3 100644 --- a/packages/multi-test/src/wasm.rs +++ b/packages/multi-test/src/wasm.rs @@ -4,8 +4,8 @@ use std::ops::Deref; use cosmwasm_std::{ Addr, Api, Attribute, BankMsg, Binary, BlockInfo, Coin, ContractInfo, ContractResult, - CustomQuery, Deps, DepsMut, Empty, Env, Event, MessageInfo, Order, Querier, QuerierWrapper, - Reply, ReplyOn, Response, Storage, SubMsg, SubMsgExecutionResponse, WasmMsg, WasmQuery, + CustomQuery, 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; @@ -20,6 +20,7 @@ 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}; @@ -46,10 +47,7 @@ pub struct ContractData { pub created: u64, } -pub trait Wasm -where - ExecC: Clone + fmt::Debug + PartialEq + JsonSchema, -{ +pub trait Wasm { /// Handles all WasmQuery requests fn query( &self, @@ -61,15 +59,19 @@ where ) -> AnyResult; /// Handles all WasmMsg messages - fn execute( + fn execute( &self, api: &dyn Api, storage: &mut dyn Storage, - router: &Router, + router: &Router, block: &BlockInfo, sender: Addr, msg: WasmMsg, - ) -> AnyResult; + ) -> AnyResult + where + Self: Sized, + BankT: Bank, + CustomT: CustomHandler; // Add a new contract. Must be done on the base object, when no contracts running fn store_code(&mut self, code: Box>) -> usize; @@ -78,38 +80,42 @@ where 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: &Router, block: &BlockInfo, msg: Vec, - ) -> AnyResult; + ) -> AnyResult + where + Self: Sized, + BankT: Bank, + CustomT: CustomHandler; } -pub struct WasmKeeper { +pub struct WasmKeeper { /// code is in-memory lookup that stands in for wasm code /// this can only be edited on the WasmRouter, and just read in caches codes: HashMap>>, - /// Just marker to make type elision fork when using it as `Wasm` trait - _q: std::marker::PhantomData, + /// Just markers to make type elision fork when using it as `Wasm` trait + _p: std::marker::PhantomData, } impl Default for WasmKeeper { fn default() -> Self { Self { codes: HashMap::default(), - _q: std::marker::PhantomData, + _p: std::marker::PhantomData, } } } impl Wasm for WasmKeeper where - ExecC: Clone + fmt::Debug + PartialEq + JsonSchema + 'static, - QueryC: CustomQuery + DeserializeOwned, + ExecC: Clone + fmt::Debug + PartialEq + JsonSchema + DeserializeOwned + 'static, + QueryC: CustomQuery + DeserializeOwned + 'static, { fn query( &self, @@ -132,15 +138,19 @@ where } } - fn execute( + fn execute( &self, api: &dyn Api, storage: &mut dyn Storage, - router: &Router, + router: &Router, block: &BlockInfo, sender: Addr, msg: WasmMsg, - ) -> AnyResult { + ) -> AnyResult + where + BankT: Bank, + CustomT: CustomHandler, + { let (resender, res, custom_event) = self.execute_wasm(api, storage, router, block, sender, msg)?; @@ -158,15 +168,19 @@ where self.load_contract(storage, address) } - fn sudo( + fn sudo( &self, api: &dyn Api, contract: Addr, storage: &mut dyn Storage, - router: &Router, + router: &Router, block: &BlockInfo, msg: Vec, - ) -> AnyResult { + ) -> AnyResult + where + BankT: Bank, + CustomT: CustomHandler, + { let custom_event = Event::new("sudo").add_attribute(CONTRACT_ATTR, &contract); let res = self.call_sudo(contract.clone(), api, storage, router, block, msg)?; @@ -177,8 +191,8 @@ where impl WasmKeeper where - ExecC: Clone + fmt::Debug + PartialEq + JsonSchema + 'static, - QueryC: CustomQuery + DeserializeOwned, + ExecC: Clone + fmt::Debug + PartialEq + JsonSchema + DeserializeOwned + 'static, + QueryC: CustomQuery + DeserializeOwned + 'static, { pub fn new() -> Self { Self::default() @@ -209,16 +223,21 @@ where data.into() } - fn send>( + fn send( &self, api: &dyn Api, storage: &mut dyn Storage, - router: &Router, + router: &Router, block: &BlockInfo, sender: T, recipient: String, amount: &[Coin], - ) -> AnyResult { + ) -> AnyResult + where + T: Into, + BankT: Bank, + CustomT: CustomHandler, + { if !amount.is_empty() { let msg = BankMsg::Send { to_address: recipient, @@ -232,15 +251,19 @@ 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: &Router, block: &BlockInfo, sender: Addr, wasm_msg: WasmMsg, - ) -> AnyResult<(Addr, Response, Event)> { + ) -> AnyResult<(Addr, Response, Event)> + where + BankT: Bank, + CustomT: CustomHandler, + { match wasm_msg { WasmMsg::Execute { contract_addr, @@ -371,15 +394,19 @@ 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: &Router, storage: &mut dyn Storage, block: &BlockInfo, contract: Addr, msg: SubMsg, - ) -> AnyResult { + ) -> AnyResult + where + BankT: Bank, + CustomT: CustomHandler, + { let SubMsg { msg, id, reply_on, .. } = msg; @@ -426,15 +453,19 @@ where } } - fn _reply( + fn _reply( &self, api: &dyn Api, - router: &Router, + router: &Router, storage: &mut dyn Storage, block: &BlockInfo, contract: Addr, reply: Reply, - ) -> AnyResult { + ) -> AnyResult + where + BankT: Bank, + CustomT: CustomHandler, + { let ok_attr = if reply.result.is_ok() { "handle_success" } else { @@ -495,16 +526,20 @@ where (app, messages) } - fn process_response( + fn process_response( &self, api: &dyn Api, - router: &Router, + router: &Router, storage: &mut dyn Storage, block: &BlockInfo, contract: Addr, response: AppResponse, messages: Vec>, - ) -> AnyResult { + ) -> AnyResult + where + BankT: Bank, + CustomT: CustomHandler, + { let AppResponse { mut events, data } = response; // recurse in all messages @@ -547,16 +582,20 @@ where Ok(addr) } - pub fn call_execute( + pub fn call_execute( &self, api: &dyn Api, storage: &mut dyn Storage, address: Addr, - router: &Router, + router: &Router, block: &BlockInfo, info: MessageInfo, msg: Vec, - ) -> AnyResult> { + ) -> AnyResult> + where + BankT: Bank, + CustomT: CustomHandler, + { Self::verify_response(self.with_storage( api, storage, @@ -567,16 +606,20 @@ where )?) } - pub fn call_instantiate( + pub fn call_instantiate( &self, address: Addr, api: &dyn Api, storage: &mut dyn Storage, - router: &Router, + router: &Router, block: &BlockInfo, info: MessageInfo, msg: Vec, - ) -> AnyResult> { + ) -> AnyResult> + where + BankT: Bank, + CustomT: CustomHandler, + { Self::verify_response(self.with_storage( api, storage, @@ -587,15 +630,19 @@ where )?) } - pub fn call_reply( + pub fn call_reply( &self, address: Addr, api: &dyn Api, storage: &mut dyn Storage, - router: &Router, + router: &Router, block: &BlockInfo, reply: Reply, - ) -> AnyResult> { + ) -> AnyResult> + where + BankT: Bank, + CustomT: CustomHandler, + { Self::verify_response(self.with_storage( api, storage, @@ -606,15 +653,19 @@ where )?) } - pub fn call_sudo( + pub fn call_sudo( &self, address: Addr, api: &dyn Api, storage: &mut dyn Storage, - router: &Router, + router: &Router, block: &BlockInfo, msg: Vec, - ) -> AnyResult> { + ) -> AnyResult> + where + BankT: Bank, + CustomT: CustomHandler, + { Self::verify_response(self.with_storage( api, storage, @@ -625,15 +676,19 @@ where )?) } - pub fn call_migrate( + pub fn call_migrate( &self, address: Addr, api: &dyn Api, storage: &mut dyn Storage, - router: &Router, + router: &Router, block: &BlockInfo, msg: Vec, - ) -> AnyResult> { + ) -> AnyResult> + where + BankT: Bank, + CustomT: CustomHandler, + { Self::verify_response(self.with_storage( api, storage, @@ -681,17 +736,21 @@ where action(handler, deps, env) } - fn with_storage( + fn with_storage( &self, api: &dyn Api, storage: &mut dyn Storage, - router: &Router, + router: &Router, 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 @@ -864,15 +923,12 @@ mod test { use super::*; - fn mock_keeper() -> WasmKeeper { - WasmKeeper::new() - } - - fn mock_router() -> Router { + fn mock_router( + ) -> Router, WasmKeeper> { Router { - wasm: Box::new(WasmKeeper::new()), - bank: Box::new(BankKeeper::new()), - custom: Box::new(PanickingCustomHandler), + wasm: WasmKeeper::new(), + bank: BankKeeper::new(), + custom: PanickingCustomHandler::new(), } } @@ -880,7 +936,7 @@ mod test { fn register_contract() { let api = MockApi::default(); let mut wasm_storage = MockStorage::new(); - let mut keeper = mock_keeper(); + let mut keeper = WasmKeeper::new(); let block = mock_env().block; let code_id = keeper.store_code(error::contract()); @@ -970,7 +1026,7 @@ mod test { #[test] fn contract_send_coins() { let api = MockApi::default(); - let mut keeper = mock_keeper(); + let mut keeper = WasmKeeper::new(); let block = mock_env().block; let code_id = keeper.store_code(payout::contract()); @@ -1045,7 +1101,7 @@ mod test { } fn assert_payout( - router: &WasmKeeper, + router: &WasmKeeper, storage: &mut dyn Storage, contract_addr: &Addr, payout: &Coin, @@ -1081,7 +1137,7 @@ mod test { #[test] fn multi_level_wasm_cache() { let api = MockApi::default(); - let mut keeper = mock_keeper(); + let mut keeper = WasmKeeper::new(); let block = mock_env().block; let code_id = keeper.store_code(payout::contract());