From 8cccfd81f4c0a03ded5d1905d02ae50a3fb7addb Mon Sep 17 00:00:00 2001 From: Dariusz Depta Date: Fri, 8 Mar 2024 15:29:42 +0100 Subject: [PATCH 1/2] Added IntoAddr trait, refactoring. --- src/addons/addresses/mock.rs | 148 ------------- src/addons/addresses/mod.rs | 1 - src/addons/api/b32.rs | 154 ------------- src/addons/api/b32m.rs | 154 ------------- src/addons/mod.rs | 12 - src/addresses.rs | 206 +++++++++++++++++- src/{addons/api/mod.rs => api.rs} | 26 ++- src/lib.rs | 8 +- tests/mod.rs | 2 +- tests/test_addons/mod.rs | 1 - tests/{test_addons => }/test_api/mod.rs | 1 + tests/test_api/test_addr.rs | 21 ++ .../{test_addons => }/test_api/test_bech32.rs | 39 +++- .../test_api/test_bech32m.rs | 35 ++- tests/test_app/test_instantiate2.rs | 2 +- .../test_app/test_store_code_with_creator.rs | 2 +- tests/test_app_builder/test_with_api.rs | 2 +- tests/test_wasm/test_with_addr_gen.rs | 2 +- 18 files changed, 312 insertions(+), 504 deletions(-) delete mode 100644 src/addons/addresses/mock.rs delete mode 100644 src/addons/addresses/mod.rs delete mode 100644 src/addons/api/b32.rs delete mode 100644 src/addons/api/b32m.rs delete mode 100644 src/addons/mod.rs rename src/{addons/api/mod.rs => api.rs} (80%) delete mode 100644 tests/test_addons/mod.rs rename tests/{test_addons => }/test_api/mod.rs (99%) create mode 100644 tests/test_api/test_addr.rs rename tests/{test_addons => }/test_api/test_bech32.rs (66%) rename tests/{test_addons => }/test_api/test_bech32m.rs (70%) diff --git a/src/addons/addresses/mock.rs b/src/addons/addresses/mock.rs deleted file mode 100644 index ab166f95..00000000 --- a/src/addons/addresses/mock.rs +++ /dev/null @@ -1,148 +0,0 @@ -use crate::error::AnyResult; -use crate::AddressGenerator; -use cosmwasm_std::{instantiate2_address, Addr, Api, CanonicalAddr, Storage}; -use sha2::digest::Update; -use sha2::{Digest, Sha256}; - -/// Address generator that mimics the [wasmd](https://github.com/CosmWasm/wasmd) behavior. -/// -/// [MockAddressGenerator] implements [AddressGenerator] trait in terms of -/// [`contract_address`](AddressGenerator::contract_address) and -/// [`predictable_contract_address`](AddressGenerator::predictable_contract_address) functions: -/// - `contract_address` generates non-predictable addresses for contracts, -/// using the same algorithm as `wasmd`, see: [`BuildContractAddressClassic`] for details. -/// - `predictable_contract_address` generates predictable addresses for contracts using -/// [`instantiate2_address`] function defined in `cosmwasm-std`. -/// -/// [`BuildContractAddressClassic`]:https://github.com/CosmWasm/wasmd/blob/3b6512c9f154995188ead84ab3bd9e034b49a0f3/x/wasm/keeper/addresses.go#L35-L41 -/// [`instantiate2_address`]:https://github.com/CosmWasm/cosmwasm/blob/8a652d7cd8071f71139deca6be8194ed4a278b2c/packages/std/src/addresses.rs#L309-L318 -#[derive(Default)] -pub struct MockAddressGenerator; - -impl AddressGenerator for MockAddressGenerator { - /// Generates a _non-predictable_ contract address, like `wasmd` does it in real-life chain. - /// - /// Note that addresses generated by `wasmd` may change and users **should not** - /// rely on this value in any extend. - /// - /// Returns the contract address after its instantiation. - /// Address generated by this function is returned as a result - /// of processing `WasmMsg::Instantiate` message. - /// - /// **NOTES** - /// > 👉 The canonical address generated by this function is humanized using the - /// > `Api::addr_humanize` function, so the resulting value depends on used `Api` implementation. - /// > The following example uses Bech32 format for humanizing canonical addresses. - /// - /// > 👉 Do NOT use this function **directly** to generate a contract address, - /// > pass this address generator to `WasmKeeper`: - /// > `WasmKeeper::new().with_address_generator(MockAddressGenerator::default());` - /// - /// # Example - /// - /// ``` - /// # use cosmwasm_std::testing::MockStorage; - /// # use cw_multi_test::AddressGenerator; - /// # use cw_multi_test::addons::{MockAddressGenerator, MockApiBech32}; - /// // use `Api` that implements Bech32 format - /// let api = MockApiBech32::new("juno"); - /// // prepare mock storage - /// let mut storage = MockStorage::default(); - /// // initialize the address generator - /// let address_generator = MockAddressGenerator::default(); - /// // generate the address - /// let addr = address_generator.contract_address(&api, &mut storage, 1, 1).unwrap(); - /// - /// assert_eq!(addr.to_string(), - /// "juno14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9skjuwg8"); - /// ``` - fn contract_address( - &self, - api: &dyn Api, - _storage: &mut dyn Storage, - code_id: u64, - instance_id: u64, - ) -> AnyResult { - let canonical_addr = instantiate_address(code_id, instance_id); - Ok(Addr::unchecked(api.addr_humanize(&canonical_addr)?)) - } - - /// Generates a _predictable_ contract address, like `wasmd` does it in real-life chain. - /// - /// Returns a contract address after its instantiation. - /// Address generated by this function is returned as a result - /// of processing `WasmMsg::Instantiate2` message. - /// - /// **NOTES** - /// > 👉 The canonical address generated by this function is humanized using the - /// > `Api::addr_humanize` function, so the resulting value depends on used `Api` implementation. - /// > The following example uses Bech32 format for humanizing canonical addresses. - /// - /// > 👉 Do NOT use this function **directly** to generate a contract address, - /// > pass this address generator to WasmKeeper: - /// > `WasmKeeper::new().with_address_generator(MockAddressGenerator::default());` - /// - /// # Example - /// - /// ``` - /// # use cosmwasm_std::Api; - /// # use cosmwasm_std::testing::MockStorage; - /// # use cw_multi_test::AddressGenerator; - /// # use cw_multi_test::addons::{MockAddressGenerator, MockApiBech32}; - /// // use `Api` that implements Bech32 format - /// let api = MockApiBech32::new("juno"); - /// // prepare mock storage - /// let mut storage = MockStorage::default(); - /// // initialize the address generator - /// let address_generator = MockAddressGenerator::default(); - /// // checksum of the contract code base - /// let checksum = [0, 1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10, - /// 11,12,13,14,15,16,17,18,19,20,21, - /// 22,23,24,25,26,27,28,29,30,31]; - /// // creator address - /// let creator = api.addr_canonicalize(api.addr_make("creator").as_str()).unwrap(); - /// // salt - /// let salt = [10,11,12]; - /// // generate the address - /// let addr = address_generator - /// .predictable_contract_address(&api, &mut storage, 1, 1, &checksum, &creator, &salt) - /// .unwrap(); - /// - /// assert_eq!(addr.to_string(), - /// "juno1sv3gjp85m3xxluxreruards8ruxk5ykys8qfljwrdj5tv8kqxuhsmlfyud"); - /// ``` - fn predictable_contract_address( - &self, - api: &dyn Api, - _storage: &mut dyn Storage, - _code_id: u64, - _instance_id: u64, - checksum: &[u8], - - creator: &CanonicalAddr, - salt: &[u8], - ) -> AnyResult { - let canonical_addr = instantiate2_address(checksum, creator, salt)?; - Ok(Addr::unchecked(api.addr_humanize(&canonical_addr)?)) - } -} - -/// Returns non-predictable contract address. -/// -/// Address is generated using the same algorithm as [`BuildContractAddressClassic`] -/// implementation in `wasmd`. -/// -/// [`BuildContractAddressClassic`]:https://github.com/CosmWasm/wasmd/blob/3b6512c9f154995188ead84ab3bd9e034b49a0f3/x/wasm/keeper/addresses.go#L35-L41 -fn instantiate_address(code_id: u64, instance_id: u64) -> CanonicalAddr { - let mut key = Vec::::new(); - key.extend_from_slice(b"wasm\0"); - key.extend_from_slice(&code_id.to_be_bytes()); - key.extend_from_slice(&instance_id.to_be_bytes()); - let module = Sha256::digest("module".as_bytes()); - Sha256::new() - .chain(module) - .chain(key) - .finalize() - .to_vec() - .into() -} diff --git a/src/addons/addresses/mod.rs b/src/addons/addresses/mod.rs deleted file mode 100644 index 9afc1d5e..00000000 --- a/src/addons/addresses/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod mock; diff --git a/src/addons/api/b32.rs b/src/addons/api/b32.rs deleted file mode 100644 index 0837567d..00000000 --- a/src/addons/api/b32.rs +++ /dev/null @@ -1,154 +0,0 @@ -use crate::addons::api::MockApiBech; -use bech32::Bech32; -use cosmwasm_std::{Addr, Api, CanonicalAddr, RecoverPubkeyError, StdResult, VerificationError}; - -/// Implementation of the `cosmwasm_std::Api` trait that uses [Bech32] format -/// for humanizing canonical addresses. -/// -/// [Bech32]: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki -pub struct MockApiBech32(MockApiBech); - -impl MockApiBech32 { - /// Returns `Api` implementation that uses specified prefix - /// to generate addresses in `Bech32` format. - /// - /// # Example - /// - /// ``` - /// use cw_multi_test::addons::MockApiBech32; - /// - /// let api = MockApiBech32::new("juno"); - /// let addr = api.addr_make("creator"); - /// assert_eq!(addr.as_str(), - /// "juno1h34lmpywh4upnjdg90cjf4j70aee6z8qqfspugamjp42e4q28kqsksmtyp"); - /// ``` - pub fn new(prefix: &'static str) -> Self { - Self(MockApiBech::new(prefix)) - } -} - -impl Api for MockApiBech32 { - /// Takes a human-readable address in `Bech32` format and checks if it is valid. - /// - /// If the validation succeeds, an `Addr` containing the same string as the input is returned. - /// - /// # Example - /// - /// ``` - /// use cosmwasm_std::Api; - /// use cw_multi_test::addons::MockApiBech32; - /// - /// let api = MockApiBech32::new("juno"); - /// let addr = api.addr_make("creator"); - /// assert_eq!(api.addr_validate(addr.as_str()).unwrap().as_str(), - /// addr.as_str()); - /// ``` - fn addr_validate(&self, input: &str) -> StdResult { - self.0.addr_humanize(&self.addr_canonicalize(input)?) - } - - /// Takes a human-readable address in `Bech32` format and returns - /// a canonical binary representation of it. - /// - /// # Example - /// - /// ``` - /// use cosmwasm_std::Api; - /// use cw_multi_test::addons::MockApiBech32; - /// - /// let api = MockApiBech32::new("juno"); - /// let addr = api.addr_make("creator"); - /// assert_eq!(api.addr_canonicalize(addr.as_str()).unwrap().to_string(), - /// "BC6BFD848EBD7819C9A82BF124D65E7F739D08E002601E23BB906AACD40A3D81"); - /// ``` - fn addr_canonicalize(&self, input: &str) -> StdResult { - self.0.addr_canonicalize(input) - } - - /// Takes a canonical address and returns a human-readable address in `Bech32` format. - /// - /// This is the inverse operation of [`addr_canonicalize`]. - /// - /// [`addr_canonicalize`]: MockApiBech32::addr_canonicalize - /// - /// # Example - /// - /// ``` - /// use cosmwasm_std::Api; - /// use cw_multi_test::addons::MockApiBech32; - /// - /// let api = MockApiBech32::new("juno"); - /// let addr = api.addr_make("creator"); - /// let canonical_addr = api.addr_canonicalize(addr.as_str()).unwrap(); - /// assert_eq!(api.addr_humanize(&canonical_addr).unwrap().as_str(), - /// addr.as_str()); - /// ``` - fn addr_humanize(&self, canonical: &CanonicalAddr) -> StdResult { - self.0.addr_humanize(canonical) - } - - fn secp256k1_verify( - &self, - message_hash: &[u8], - signature: &[u8], - public_key: &[u8], - ) -> Result { - self.0.secp256k1_verify(message_hash, signature, public_key) - } - - fn secp256k1_recover_pubkey( - &self, - message_hash: &[u8], - signature: &[u8], - recovery_param: u8, - ) -> Result, RecoverPubkeyError> { - self.0 - .secp256k1_recover_pubkey(message_hash, signature, recovery_param) - } - - fn ed25519_verify( - &self, - message: &[u8], - signature: &[u8], - public_key: &[u8], - ) -> Result { - self.0.ed25519_verify(message, signature, public_key) - } - - fn ed25519_batch_verify( - &self, - messages: &[&[u8]], - signatures: &[&[u8]], - public_keys: &[&[u8]], - ) -> Result { - self.0 - .ed25519_batch_verify(messages, signatures, public_keys) - } - - fn debug(&self, message: &str) { - self.0.debug(message) - } -} - -impl MockApiBech32 { - /// Returns an address in `Bech32` format, built from provided input string. - /// - /// # Example - /// - /// ``` - /// use cw_multi_test::addons::MockApiBech32; - /// - /// let api = MockApiBech32::new("juno"); - /// let addr = api.addr_make("creator"); - /// assert_eq!(addr.as_str(), - /// "juno1h34lmpywh4upnjdg90cjf4j70aee6z8qqfspugamjp42e4q28kqsksmtyp"); - /// ``` - /// - /// # Panics - /// - /// This function panics when generating a valid address in **Bech32** - /// format is not possible, especially when prefix is too long or empty. - pub fn addr_make(&self, input: &str) -> Addr { - self.0.addr_make(input) - } -} diff --git a/src/addons/api/b32m.rs b/src/addons/api/b32m.rs deleted file mode 100644 index 2c91477f..00000000 --- a/src/addons/api/b32m.rs +++ /dev/null @@ -1,154 +0,0 @@ -use crate::addons::api::MockApiBech; -use bech32::Bech32m; -use cosmwasm_std::{Addr, Api, CanonicalAddr, RecoverPubkeyError, StdResult, VerificationError}; - -/// Implementation of the `cosmwasm_std::Api` trait that uses [Bech32m] format -/// for humanizing canonical addresses. -/// -/// [Bech32m]: https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki -pub struct MockApiBech32m(MockApiBech); - -impl MockApiBech32m { - /// Returns `Api` implementation that uses specified prefix - /// to generate addresses in `Bech32m` format. - /// - /// # Example - /// - /// ``` - /// use cw_multi_test::addons::MockApiBech32m; - /// - /// let api = MockApiBech32m::new("osmo"); - /// let addr = api.addr_make("sender"); - /// assert_eq!(addr.as_str(), - /// "osmo1pgm8hyk0pvphmlvfjc8wsvk4daluz5tgrw6pu5mfpemk74uxnx9qdlmaeg"); - /// ``` - pub fn new(prefix: &'static str) -> Self { - Self(MockApiBech::new(prefix)) - } -} - -impl Api for MockApiBech32m { - /// Takes a human-readable address in `Bech32m` format and checks if it is valid. - /// - /// If the validation succeeds, an `Addr` containing the same string as the input is returned. - /// - /// # Example - /// - /// ``` - /// use cosmwasm_std::Api; - /// use cw_multi_test::addons::MockApiBech32m; - /// - /// let api = MockApiBech32m::new("osmo"); - /// let addr = api.addr_make("sender"); - /// assert_eq!(api.addr_validate(addr.as_str()).unwrap().as_str(), - /// addr.as_str()); - /// ``` - fn addr_validate(&self, input: &str) -> StdResult { - self.0.addr_humanize(&self.addr_canonicalize(input)?) - } - - /// Takes a human-readable address in `Bech32m` format and returns - /// a canonical binary representation of it. - /// - /// # Example - /// - /// ``` - /// use cosmwasm_std::Api; - /// use cw_multi_test::addons::MockApiBech32; - /// - /// let api = MockApiBech32::new("osmo"); - /// let addr = api.addr_make("sender"); - /// assert_eq!(api.addr_canonicalize(addr.as_str()).unwrap().to_string(), - /// "0A367B92CF0B037DFD89960EE832D56F7FC151681BB41E53690E776F5786998A"); - /// ``` - fn addr_canonicalize(&self, input: &str) -> StdResult { - self.0.addr_canonicalize(input) - } - - /// Takes a canonical address and returns a human-readable address in `Bech32m` format. - /// - /// This is the inverse operation of [`addr_canonicalize`]. - /// - /// [`addr_canonicalize`]: MockApiBech32m::addr_canonicalize - /// - /// # Example - /// - /// ``` - /// use cosmwasm_std::Api; - /// use cw_multi_test::addons::MockApiBech32m; - /// - /// let api = MockApiBech32m::new("osmo"); - /// let addr = api.addr_make("sender"); - /// let canonical_addr = api.addr_canonicalize(addr.as_str()).unwrap(); - /// assert_eq!(api.addr_humanize(&canonical_addr).unwrap().as_str(), - /// addr.as_str()); - /// ``` - fn addr_humanize(&self, canonical: &CanonicalAddr) -> StdResult { - self.0.addr_humanize(canonical) - } - - fn secp256k1_verify( - &self, - message_hash: &[u8], - signature: &[u8], - public_key: &[u8], - ) -> Result { - self.0.secp256k1_verify(message_hash, signature, public_key) - } - - fn secp256k1_recover_pubkey( - &self, - message_hash: &[u8], - signature: &[u8], - recovery_param: u8, - ) -> Result, RecoverPubkeyError> { - self.0 - .secp256k1_recover_pubkey(message_hash, signature, recovery_param) - } - - fn ed25519_verify( - &self, - message: &[u8], - signature: &[u8], - public_key: &[u8], - ) -> Result { - self.0.ed25519_verify(message, signature, public_key) - } - - fn ed25519_batch_verify( - &self, - messages: &[&[u8]], - signatures: &[&[u8]], - public_keys: &[&[u8]], - ) -> Result { - self.0 - .ed25519_batch_verify(messages, signatures, public_keys) - } - - fn debug(&self, message: &str) { - self.0.debug(message) - } -} - -impl MockApiBech32m { - /// Returns an address in `Bech32m` format, built from provided input string. - /// - /// # Example - /// - /// ``` - /// use cw_multi_test::addons::MockApiBech32m; - /// - /// let api = MockApiBech32m::new("osmo"); - /// let addr = api.addr_make("sender"); - /// assert_eq!(addr.as_str(), - /// "osmo1pgm8hyk0pvphmlvfjc8wsvk4daluz5tgrw6pu5mfpemk74uxnx9qdlmaeg"); - /// ``` - /// - /// # Panics - /// - /// This function panics when generating a valid address in **Bech32** - /// format is not possible, especially when prefix is too long or empty. - pub fn addr_make(&self, input: &str) -> Addr { - self.0.addr_make(input) - } -} diff --git a/src/addons/mod.rs b/src/addons/mod.rs deleted file mode 100644 index 648c9b5f..00000000 --- a/src/addons/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! # Additional testing tools (extensions) -//! -//! **CosmWasm MultiTest** addons provide additional tools for testing smart contracts, -//! simulating complex blockchain scenarios that developers might encounter. -//! They enhance the CosmWasm environment, enabling more advanced and nuanced testing. - -mod addresses; -mod api; - -pub use addresses::mock::MockAddressGenerator; -pub use api::b32::MockApiBech32; -pub use api::b32m::MockApiBech32m; diff --git a/src/addresses.rs b/src/addresses.rs index 4b6c4f93..04263b3d 100644 --- a/src/addresses.rs +++ b/src/addresses.rs @@ -1,7 +1,68 @@ //! # Implementation of address generators use crate::error::AnyResult; -use cosmwasm_std::{Addr, Api, CanonicalAddr, HexBinary, Storage}; +use crate::{MockApiBech32, MockApiBech32m}; +use cosmwasm_std::{instantiate2_address, Addr, Api, CanonicalAddr, HexBinary, Storage}; +use sha2::digest::Update; +use sha2::{Digest, Sha256}; + +const DEFAULT_PREFIX: &str = "cosmwasm"; + +/// Defines conversions to [Addr]. +pub trait IntoAddr { + /// Converts into [Addr]. + fn into_addr(self) -> Addr; + + /// Converts into [Addr] with custom prefix. + fn into_addr_with_prefix(self, prefix: &'static str) -> Addr; +} + +impl IntoAddr for &str { + fn into_addr(self) -> Addr { + MockApiBech32::new(DEFAULT_PREFIX).addr_make(self) + } + + fn into_addr_with_prefix(self, prefix: &'static str) -> Addr { + MockApiBech32::new(prefix).addr_make(self) + } +} + +/// Defines conversions to `Bech32` compatible addresses. +pub trait IntoBech32 { + /// Converts into [Addr] containing a string compatible with `Bech32` format with default prefix. + fn into_bech32(self) -> Addr; + + /// Converts into [Addr] containing a string compatible with `Bech32` format with custom prefix. + fn into_bech32_with_prefix(self, prefix: &'static str) -> Addr; +} + +impl IntoBech32 for &str { + fn into_bech32(self) -> Addr { + MockApiBech32::new(DEFAULT_PREFIX).addr_make(self) + } + + fn into_bech32_with_prefix(self, prefix: &'static str) -> Addr { + MockApiBech32::new(prefix).addr_make(self) + } +} + +/// Defines conversions to `Bech32m` compatible addresses. +pub trait IntoBech32m { + /// Converts into [Addr] containing a string compatible with `Bech32m` format with default prefix. + fn into_bech32m(self) -> Addr; + /// Converts into [Addr] containing a string compatible with `Bech32m` format with custom prefix. + fn into_bech32m_with_prefix(self, prefix: &'static str) -> Addr; +} + +impl IntoBech32m for &str { + fn into_bech32m(self) -> Addr { + MockApiBech32m::new(DEFAULT_PREFIX).addr_make(self) + } + + fn into_bech32m_with_prefix(self, prefix: &'static str) -> Addr { + MockApiBech32m::new(prefix).addr_make(self) + } +} /// Common address generator interface. /// @@ -113,3 +174,146 @@ pub trait AddressGenerator { pub struct SimpleAddressGenerator; impl AddressGenerator for SimpleAddressGenerator {} + +/// Address generator that mimics the [wasmd](https://github.com/CosmWasm/wasmd) behavior. +/// +/// [crate::addons::MockAddressGenerator] implements [AddressGenerator] trait in terms of +/// [`contract_address`](AddressGenerator::contract_address) and +/// [`predictable_contract_address`](AddressGenerator::predictable_contract_address) functions: +/// - `contract_address` generates non-predictable addresses for contracts, +/// using the same algorithm as `wasmd`, see: [`BuildContractAddressClassic`] for details. +/// - `predictable_contract_address` generates predictable addresses for contracts using +/// [`instantiate2_address`] function defined in `cosmwasm-std`. +/// +/// [`BuildContractAddressClassic`]:https://github.com/CosmWasm/wasmd/blob/3b6512c9f154995188ead84ab3bd9e034b49a0f3/x/wasm/keeper/addresses.go#L35-L41 +/// [`instantiate2_address`]:https://github.com/CosmWasm/cosmwasm/blob/8a652d7cd8071f71139deca6be8194ed4a278b2c/packages/std/src/addresses.rs#L309-L318 +#[derive(Default)] +pub struct MockAddressGenerator; + +impl AddressGenerator for MockAddressGenerator { + /// Generates a _non-predictable_ contract address, like `wasmd` does it in real-life chain. + /// + /// Note that addresses generated by `wasmd` may change and users **should not** + /// rely on this value in any extend. + /// + /// Returns the contract address after its instantiation. + /// Address generated by this function is returned as a result + /// of processing `WasmMsg::Instantiate` message. + /// + /// **NOTES** + /// > 👉 The canonical address generated by this function is humanized using the + /// > `Api::addr_humanize` function, so the resulting value depends on used `Api` implementation. + /// > The following example uses Bech32 format for humanizing canonical addresses. + /// + /// > 👉 Do NOT use this function **directly** to generate a contract address, + /// > pass this address generator to `WasmKeeper`: + /// > `WasmKeeper::new().with_address_generator(MockAddressGenerator::default());` + /// + /// # Example + /// + /// ``` + /// # use cosmwasm_std::testing::MockStorage; + /// # use cw_multi_test::AddressGenerator; + /// # use cw_multi_test::{MockAddressGenerator, MockApiBech32}; + /// // use `Api` that implements Bech32 format + /// let api = MockApiBech32::new("juno"); + /// // prepare mock storage + /// let mut storage = MockStorage::default(); + /// // initialize the address generator + /// let address_generator = MockAddressGenerator::default(); + /// // generate the address + /// let addr = address_generator.contract_address(&api, &mut storage, 1, 1).unwrap(); + /// + /// assert_eq!(addr.to_string(), + /// "juno14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9skjuwg8"); + /// ``` + fn contract_address( + &self, + api: &dyn Api, + _storage: &mut dyn Storage, + code_id: u64, + instance_id: u64, + ) -> AnyResult { + let canonical_addr = instantiate_address(code_id, instance_id); + Ok(Addr::unchecked(api.addr_humanize(&canonical_addr)?)) + } + + /// Generates a _predictable_ contract address, like `wasmd` does it in real-life chain. + /// + /// Returns a contract address after its instantiation. + /// Address generated by this function is returned as a result + /// of processing `WasmMsg::Instantiate2` message. + /// + /// **NOTES** + /// > 👉 The canonical address generated by this function is humanized using the + /// > `Api::addr_humanize` function, so the resulting value depends on used `Api` implementation. + /// > The following example uses Bech32 format for humanizing canonical addresses. + /// + /// > 👉 Do NOT use this function **directly** to generate a contract address, + /// > pass this address generator to WasmKeeper: + /// > `WasmKeeper::new().with_address_generator(MockAddressGenerator::default());` + /// + /// # Example + /// + /// ``` + /// # use cosmwasm_std::Api; + /// # use cosmwasm_std::testing::MockStorage; + /// # use cw_multi_test::AddressGenerator; + /// # use cw_multi_test::{MockAddressGenerator, MockApiBech32}; + /// // use `Api` that implements Bech32 format + /// let api = MockApiBech32::new("juno"); + /// // prepare mock storage + /// let mut storage = MockStorage::default(); + /// // initialize the address generator + /// let address_generator = MockAddressGenerator::default(); + /// // checksum of the contract code base + /// let checksum = [0, 1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10, + /// 11,12,13,14,15,16,17,18,19,20,21, + /// 22,23,24,25,26,27,28,29,30,31]; + /// // creator address + /// let creator = api.addr_canonicalize(api.addr_make("creator").as_str()).unwrap(); + /// // salt + /// let salt = [10,11,12]; + /// // generate the address + /// let addr = address_generator + /// .predictable_contract_address(&api, &mut storage, 1, 1, &checksum, &creator, &salt) + /// .unwrap(); + /// + /// assert_eq!(addr.to_string(), + /// "juno1sv3gjp85m3xxluxreruards8ruxk5ykys8qfljwrdj5tv8kqxuhsmlfyud"); + /// ``` + fn predictable_contract_address( + &self, + api: &dyn Api, + _storage: &mut dyn Storage, + _code_id: u64, + _instance_id: u64, + checksum: &[u8], + + creator: &CanonicalAddr, + salt: &[u8], + ) -> AnyResult { + let canonical_addr = instantiate2_address(checksum, creator, salt)?; + Ok(Addr::unchecked(api.addr_humanize(&canonical_addr)?)) + } +} + +/// Returns non-predictable contract address. +/// +/// Address is generated using the same algorithm as [`BuildContractAddressClassic`] +/// implementation in `wasmd`. +/// +/// [`BuildContractAddressClassic`]:https://github.com/CosmWasm/wasmd/blob/3b6512c9f154995188ead84ab3bd9e034b49a0f3/x/wasm/keeper/addresses.go#L35-L41 +fn instantiate_address(code_id: u64, instance_id: u64) -> CanonicalAddr { + let mut key = Vec::::new(); + key.extend_from_slice(b"wasm\0"); + key.extend_from_slice(&code_id.to_be_bytes()); + key.extend_from_slice(&instance_id.to_be_bytes()); + let module = Sha256::digest("module".as_bytes()); + Sha256::new() + .chain(module) + .chain(key) + .finalize() + .to_vec() + .into() +} diff --git a/src/addons/api/mod.rs b/src/api.rs similarity index 80% rename from src/addons/api/mod.rs rename to src/api.rs index 1e16c5ab..c09d0079 100644 --- a/src/addons/api/mod.rs +++ b/src/api.rs @@ -1,15 +1,12 @@ use bech32::primitives::decode::CheckedHrpstring; -use bech32::{encode, Hrp}; +use bech32::{encode, Bech32, Bech32m, Hrp}; use cosmwasm_std::testing::MockApi; use cosmwasm_std::{ Addr, Api, CanonicalAddr, RecoverPubkeyError, StdError, StdResult, VerificationError, }; use sha2::{Digest, Sha256}; -pub mod b32; -pub mod b32m; - -struct MockApiBech { +pub struct MockApiBech { api: MockApi, prefix: &'static str, _phantom_data: std::marker::PhantomData, @@ -100,14 +97,23 @@ impl MockApiBech { /// # Panics /// /// This function panics when generating a valid address in `Bech32` or `Bech32m` - /// format is not possible, especially when prefix is too long or empty. + /// format is not possible, especially when the prefix is too long or empty. pub fn addr_make(&self, input: &str) -> Addr { match Hrp::parse(self.prefix) { - Ok(hrp) => match encode::(hrp, Sha256::digest(input).as_slice()) { - Ok(address) => Addr::unchecked(address), - Err(reason) => panic!("Generating address failed with reason: {}", reason), - }, + Ok(hrp) => Addr::unchecked(encode::(hrp, Sha256::digest(input).as_slice()).unwrap()), Err(reason) => panic!("Generating address failed with reason: {}", reason), } } } + +/// Implementation of the `cosmwasm_std::Api` trait that uses [Bech32] format +/// for humanizing canonical addresses. +/// +/// [Bech32]: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki +pub type MockApiBech32 = MockApiBech; + +/// Implementation of the `cosmwasm_std::Api` trait that uses [Bech32m] format +/// for humanizing canonical addresses. +/// +/// [Bech32m]: https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki +pub type MockApiBech32m = MockApiBech; diff --git a/src/lib.rs b/src/lib.rs index 9a6becda..c88382ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -124,8 +124,8 @@ #![deny(rustdoc::broken_intra_doc_links)] #![deny(rustdoc::missing_crate_level_docs)] -pub mod addons; mod addresses; +mod api; mod app; mod app_builder; mod bank; @@ -145,7 +145,11 @@ mod tests; mod transactions; mod wasm; -pub use crate::addresses::{AddressGenerator, SimpleAddressGenerator}; +pub use crate::addresses::{ + AddressGenerator, IntoAddr, IntoBech32, IntoBech32m, MockAddressGenerator, + SimpleAddressGenerator, +}; +pub use crate::api::{MockApiBech32, MockApiBech32m}; pub use crate::app::{ custom_app, next_block, no_init, App, BasicApp, CosmosRouter, Router, SudoMsg, }; diff --git a/tests/mod.rs b/tests/mod.rs index d14c1479..f5370608 100644 --- a/tests/mod.rs +++ b/tests/mod.rs @@ -3,7 +3,7 @@ use cw_storage_plus::Item; use serde::{Deserialize, Serialize}; -mod test_addons; +mod test_api; mod test_app; mod test_app_builder; mod test_module; diff --git a/tests/test_addons/mod.rs b/tests/test_addons/mod.rs deleted file mode 100644 index 0ce9c88e..00000000 --- a/tests/test_addons/mod.rs +++ /dev/null @@ -1 +0,0 @@ -mod test_api; diff --git a/tests/test_addons/test_api/mod.rs b/tests/test_api/mod.rs similarity index 99% rename from tests/test_addons/test_api/mod.rs rename to tests/test_api/mod.rs index d049acc5..e50ddd4e 100644 --- a/tests/test_addons/test_api/mod.rs +++ b/tests/test_api/mod.rs @@ -1,6 +1,7 @@ use cosmwasm_std::Api; use hex_literal::hex; +mod test_addr; mod test_bech32; mod test_bech32m; diff --git a/tests/test_api/test_addr.rs b/tests/test_api/test_addr.rs new file mode 100644 index 00000000..015f338e --- /dev/null +++ b/tests/test_api/test_addr.rs @@ -0,0 +1,21 @@ +use cosmwasm_std::testing::MockApi; +use cw_multi_test::IntoAddr; + +#[test] +fn conversion_with_default_prefix_should_work() { + assert_eq!( + MockApi::default().addr_make("creator").as_str(), + "creator".into_addr().as_str(), + ); +} + +#[test] +fn conversion_with_custom_prefix_should_work() { + assert_eq!( + MockApi::default() + .with_prefix("juno") + .addr_make("sender") + .as_str(), + "sender".into_addr_with_prefix("juno").as_str(), + ); +} diff --git a/tests/test_addons/test_api/test_bech32.rs b/tests/test_api/test_bech32.rs similarity index 66% rename from tests/test_addons/test_api/test_bech32.rs rename to tests/test_api/test_bech32.rs index 68448488..8bb74499 100644 --- a/tests/test_addons/test_api/test_bech32.rs +++ b/tests/test_api/test_bech32.rs @@ -1,22 +1,36 @@ use super::*; use cosmwasm_std::CanonicalAddr; -use cw_multi_test::addons::{MockApiBech32, MockApiBech32m}; +use cw_multi_test::{IntoBech32, IntoBech32m, MockApiBech32, MockApiBech32m}; -const HUMAN_ADDRESS: &str = "juno1h34lmpywh4upnjdg90cjf4j70aee6z8qqfspugamjp42e4q28kqsksmtyp"; +const ADDR_JUNO: &str = "juno1h34lmpywh4upnjdg90cjf4j70aee6z8qqfspugamjp42e4q28kqsksmtyp"; +const ADDR_DEFAULT: &str = "cosmwasm1h34lmpywh4upnjdg90cjf4j70aee6z8qqfspugamjp42e4q28kqs8s7vcp"; #[test] fn new_api_bech32_should_work() { assert_eq!( MockApiBech32::new("juno").addr_make("creator").as_str(), - HUMAN_ADDRESS + ADDR_JUNO ); + assert_eq!( + "creator".into_bech32_with_prefix("juno").as_str(), + ADDR_JUNO + ); + assert_eq!("creator".into_bech32().as_str(), ADDR_DEFAULT); } #[test] fn api_bech32_should_differ_from_bech32m() { assert_ne!( - MockApiBech32::new("juno").addr_make("creator").as_str(), - MockApiBech32m::new("juno").addr_make("creator").as_str(), + MockApiBech32::new("juno").addr_make("sender").as_str(), + MockApiBech32m::new("juno").addr_make("sender").as_str(), + ); + assert_ne!( + "sender".into_bech32_with_prefix("juno").as_str(), + "sender".into_bech32m_with_prefix("juno").as_str() + ); + assert_ne!( + "sender".into_bech32().as_str(), + "sender".into_bech32m().as_str() ); } @@ -24,10 +38,10 @@ fn api_bech32_should_differ_from_bech32m() { fn address_validate_should_work() { assert_eq!( MockApiBech32::new("juno") - .addr_validate(HUMAN_ADDRESS) + .addr_validate(ADDR_JUNO) .unwrap() .as_str(), - HUMAN_ADDRESS + ADDR_JUNO ) } @@ -56,10 +70,10 @@ fn address_validate_invalid_variant() { fn address_canonicalize_humanize_should_work() { let api = MockApiBech32::new("juno"); assert_eq!( - api.addr_humanize(&api.addr_canonicalize(HUMAN_ADDRESS).unwrap()) + api.addr_humanize(&api.addr_canonicalize(ADDR_JUNO).unwrap()) .unwrap() .as_str(), - HUMAN_ADDRESS + ADDR_JUNO ); } @@ -72,6 +86,13 @@ fn address_humanize_prefix_too_long() { .unwrap_err(); } +#[test] +fn address_humanize_canonical_too_long() { + MockApiBech32::new("juno") + .addr_humanize(&CanonicalAddr::from([1; 1024])) + .unwrap_err(); +} + #[test] fn debug_should_not_panic() { assert_debug_does_not_panic(&MockApiBech32::new("juno")); diff --git a/tests/test_addons/test_api/test_bech32m.rs b/tests/test_api/test_bech32m.rs similarity index 70% rename from tests/test_addons/test_api/test_bech32m.rs rename to tests/test_api/test_bech32m.rs index ffb1d48e..6a649e53 100644 --- a/tests/test_addons/test_api/test_bech32m.rs +++ b/tests/test_api/test_bech32m.rs @@ -1,15 +1,21 @@ use super::*; use cosmwasm_std::CanonicalAddr; -use cw_multi_test::addons::{MockApiBech32, MockApiBech32m}; +use cw_multi_test::{IntoBech32, IntoBech32m, MockApiBech32, MockApiBech32m}; -const HUMAN_ADDRESS: &str = "juno1h34lmpywh4upnjdg90cjf4j70aee6z8qqfspugamjp42e4q28kqsrvt8pr"; +const ADDR_JUNO: &str = "juno1h34lmpywh4upnjdg90cjf4j70aee6z8qqfspugamjp42e4q28kqsrvt8pr"; +const ADDR_DEFAULT: &str = "cosmwasm1h34lmpywh4upnjdg90cjf4j70aee6z8qqfspugamjp42e4q28kqsjvwqar"; #[test] fn new_api_bech32m_should_work() { assert_eq!( MockApiBech32m::new("juno").addr_make("creator").as_str(), - HUMAN_ADDRESS + ADDR_JUNO ); + assert_eq!( + "creator".into_bech32m_with_prefix("juno").as_str(), + ADDR_JUNO + ); + assert_eq!("creator".into_bech32m().as_str(), ADDR_DEFAULT); } #[test] @@ -18,16 +24,24 @@ fn api_bech32m_should_differ_from_bech32() { MockApiBech32m::new("juno").addr_make("sender").as_str(), MockApiBech32::new("juno").addr_make("sender").as_str() ); + assert_ne!( + "sender".into_bech32m_with_prefix("juno").as_str(), + "sender".into_bech32_with_prefix("juno").as_str() + ); + assert_ne!( + "sender".into_bech32m().as_str(), + "sender".into_bech32().as_str() + ); } #[test] fn address_validate_should_work() { assert_eq!( MockApiBech32m::new("juno") - .addr_validate(HUMAN_ADDRESS) + .addr_validate(ADDR_JUNO) .unwrap() .as_str(), - HUMAN_ADDRESS + ADDR_JUNO ) } @@ -56,10 +70,10 @@ fn address_validate_invalid_variant() { fn address_canonicalize_humanize_should_work() { let api = MockApiBech32m::new("juno"); assert_eq!( - api.addr_humanize(&api.addr_canonicalize(HUMAN_ADDRESS).unwrap()) + api.addr_humanize(&api.addr_canonicalize(ADDR_JUNO).unwrap()) .unwrap() .as_str(), - HUMAN_ADDRESS + ADDR_JUNO ); } @@ -72,6 +86,13 @@ fn address_humanize_prefix_too_long() { .unwrap_err(); } +#[test] +fn address_humanize_canonical_too_long() { + MockApiBech32m::new("juno") + .addr_humanize(&CanonicalAddr::from([1; 1024])) + .unwrap_err(); +} + #[test] fn debug_should_not_panic() { assert_debug_does_not_panic(&MockApiBech32m::new("juno")); diff --git a/tests/test_app/test_instantiate2.rs b/tests/test_app/test_instantiate2.rs index cec92ad6..8f82667b 100644 --- a/tests/test_app/test_instantiate2.rs +++ b/tests/test_app/test_instantiate2.rs @@ -2,8 +2,8 @@ use crate::test_contracts::counter; use cosmwasm_std::{instantiate2_address, to_json_binary, Api, Empty, WasmMsg}; -use cw_multi_test::addons::{MockAddressGenerator, MockApiBech32}; use cw_multi_test::{no_init, AppBuilder, Executor, WasmKeeper}; +use cw_multi_test::{MockAddressGenerator, MockApiBech32}; use cw_utils::parse_instantiate_response_data; #[test] diff --git a/tests/test_app/test_store_code_with_creator.rs b/tests/test_app/test_store_code_with_creator.rs index 9cdc590c..5f70a1e4 100644 --- a/tests/test_app/test_store_code_with_creator.rs +++ b/tests/test_app/test_store_code_with_creator.rs @@ -1,8 +1,8 @@ #![cfg(feature = "cosmwasm_1_2")] use crate::test_contracts::counter; -use cw_multi_test::addons::{MockApiBech32, MockApiBech32m}; use cw_multi_test::{no_init, AppBuilder}; +use cw_multi_test::{MockApiBech32, MockApiBech32m}; #[test] fn store_code_with_custom_creator_address_should_work() { diff --git a/tests/test_app_builder/test_with_api.rs b/tests/test_app_builder/test_with_api.rs index c96184d4..a61861c3 100644 --- a/tests/test_app_builder/test_with_api.rs +++ b/tests/test_app_builder/test_with_api.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{Addr, Api, CanonicalAddr, HexBinary}; -use cw_multi_test::addons::MockApiBech32; +use cw_multi_test::MockApiBech32; use cw_multi_test::{no_init, AppBuilder}; #[test] diff --git a/tests/test_wasm/test_with_addr_gen.rs b/tests/test_wasm/test_with_addr_gen.rs index 619f2e56..ddabf3d9 100644 --- a/tests/test_wasm/test_with_addr_gen.rs +++ b/tests/test_wasm/test_with_addr_gen.rs @@ -1,8 +1,8 @@ use cosmwasm_std::{Addr, Api, Empty, Storage}; -use cw_multi_test::addons::{MockAddressGenerator, MockApiBech32}; use cw_multi_test::error::AnyResult; use cw_multi_test::{no_init, AddressGenerator, AppBuilder, Executor, WasmKeeper}; +use cw_multi_test::{MockAddressGenerator, MockApiBech32}; use crate::test_contracts; From f312483475516242d86e3cef81f89246aa27fe3b Mon Sep 17 00:00:00 2001 From: Dariusz Depta Date: Fri, 8 Mar 2024 18:48:39 +0100 Subject: [PATCH 2/2] Applied changes after review. --- src/addresses.rs | 7 +++++-- tests/test_api/test_bech32.rs | 24 ++++++++++++++++-------- tests/test_api/test_bech32m.rs | 24 ++++++++++++++++-------- 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/src/addresses.rs b/src/addresses.rs index 04263b3d..b934abf2 100644 --- a/src/addresses.rs +++ b/src/addresses.rs @@ -8,7 +8,10 @@ use sha2::{Digest, Sha256}; const DEFAULT_PREFIX: &str = "cosmwasm"; -/// Defines conversions to [Addr]. +/// Defines conversions to [Addr], this conversion is format agnostic +/// and should be aligned with the format generated by [MockApi]. +/// +/// [MockApi]: https://github.com/CosmWasm/cosmwasm/blob/9a239838baba50f4f47230da306f39a8bb4ea697/packages/std/src/testing/mock.rs#L251-L257 pub trait IntoAddr { /// Converts into [Addr]. fn into_addr(self) -> Addr; @@ -177,7 +180,7 @@ impl AddressGenerator for SimpleAddressGenerator {} /// Address generator that mimics the [wasmd](https://github.com/CosmWasm/wasmd) behavior. /// -/// [crate::addons::MockAddressGenerator] implements [AddressGenerator] trait in terms of +/// [MockAddressGenerator] implements [AddressGenerator] trait in terms of /// [`contract_address`](AddressGenerator::contract_address) and /// [`predictable_contract_address`](AddressGenerator::predictable_contract_address) functions: /// - `contract_address` generates non-predictable addresses for contracts, diff --git a/tests/test_api/test_bech32.rs b/tests/test_api/test_bech32.rs index 8bb74499..35e86234 100644 --- a/tests/test_api/test_bech32.rs +++ b/tests/test_api/test_bech32.rs @@ -79,18 +79,26 @@ fn address_canonicalize_humanize_should_work() { #[test] fn address_humanize_prefix_too_long() { - MockApiBech32::new( - "juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_", - ) - .addr_humanize(&CanonicalAddr::from([1, 2, 3, 4, 5])) - .unwrap_err(); + assert_eq!( + "Generic error: hrp is too long, found 85 characters, must be <= 126", + MockApiBech32::new( + "juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_", + ) + .addr_humanize(&CanonicalAddr::from([1, 2, 3, 4, 5])) + .unwrap_err() + .to_string() + ); } #[test] fn address_humanize_canonical_too_long() { - MockApiBech32::new("juno") - .addr_humanize(&CanonicalAddr::from([1; 1024])) - .unwrap_err(); + assert_eq!( + "Generic error: Invalid canonical address", + MockApiBech32::new("juno") + .addr_humanize(&CanonicalAddr::from([1; 1024])) + .unwrap_err() + .to_string() + ); } #[test] diff --git a/tests/test_api/test_bech32m.rs b/tests/test_api/test_bech32m.rs index 6a649e53..61af0177 100644 --- a/tests/test_api/test_bech32m.rs +++ b/tests/test_api/test_bech32m.rs @@ -79,18 +79,26 @@ fn address_canonicalize_humanize_should_work() { #[test] fn address_humanize_prefix_too_long() { - MockApiBech32m::new( - "juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_", - ) - .addr_humanize(&CanonicalAddr::from([1, 2, 3, 4, 5])) - .unwrap_err(); + assert_eq!( + "Generic error: hrp is too long, found 85 characters, must be <= 126", + MockApiBech32m::new( + "juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_", + ) + .addr_humanize(&CanonicalAddr::from([1, 2, 3, 4, 5])) + .unwrap_err() + .to_string() + ); } #[test] fn address_humanize_canonical_too_long() { - MockApiBech32m::new("juno") - .addr_humanize(&CanonicalAddr::from([1; 1024])) - .unwrap_err(); + assert_eq!( + "Generic error: Invalid canonical address", + MockApiBech32m::new("juno") + .addr_humanize(&CanonicalAddr::from([1; 1024])) + .unwrap_err() + .to_string() + ); } #[test]