Repository: CosmWasm/cw-multi-test
Pull request #98 (2023-11-16 10:55:53)

Addons
Prepared module named `addons`, where additional components or extensions to **MultiTest** will be placed.
Add-ons are not required (or crucial) to use **MultiTest** in common testing scenarios, but provide additional functionalities, that may be used while testing CosmWasm smart contracts.

Add-ons provided in this PR:
- `crate::addons::MockAddressGenerator`
- `crate::addons::MockApiBech32`
- `crate::addons::MockApiBech32m`

Please consult the source code documentation to learn more about these add-ons.

Using provided add-ons, writing tests with **Bech32** addresses is now simpler, e.g.:
```rust
    // prepare the application with Bech32 Api and mock address generator
    let mut app = AppBuilder::default()
        .with_api(MockApiBech32::new("juno"))
        .with_wasm(WasmKeeper::default().with_address_generator(MockAddressGenerator))
        .build(|_, _, _| {});

    // test your contract
    // ...
```

DariuszDepta: @webmaster128 This is the proposal of a concept of providing add-ons instead separate crates. Provided add-ons in this PR address the needs reported e.g. here: #95 without the need to copy and paste the same code by test creators.

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 1fe94bb..83e98d2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -256,6 +256,8 @@ dependencies = [
  "cw-storage-plus",
  "cw-utils",
  "derivative",
+ "hex",
+ "hex-literal",
  "itertools 0.12.0",
  "once_cell",
  "prost",
@@ -477,6 +479,12 @@ version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
 
+[[package]]
+name = "hex-literal"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46"
+
 [[package]]
 name = "hmac"
 version = "0.12.1"
diff --git a/Cargo.toml b/Cargo.toml
index 40a3f4b..11b988a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,6 +18,7 @@ cosmwasm_1_4 = ["cosmwasm_1_3", "cosmwasm-std/cosmwasm_1_4"]
 
 [dependencies]
 anyhow = "1.0.75"
+bech32 = "0.9.1"
 cosmwasm-std = { version = "1.5.0", features = ["iterator", "staking", "stargate"] }
 cw-storage-plus = "1.2.0"
 cw-utils = "1.0.3"
@@ -30,5 +31,6 @@ sha2 = "0.10.8"
 thiserror = "1.0.50"
 
 [dev-dependencies]
-bech32 = "0.9.1"
+hex = "0.4.3"
+hex-literal = "0.4.1"
 once_cell = "1.18.0"
diff --git a/src/addons/addresses/mock.rs b/src/addons/addresses/mock.rs
new file mode 100644
index 0000000..081199c
--- /dev/null
+++ b/src/addons/addresses/mock.rs
@@ -0,0 +1,148 @@
+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 original `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<Addr> {
+        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<Addr> {
+        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::<u8>::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
new file mode 100644
index 0000000..9afc1d5
--- /dev/null
+++ b/src/addons/addresses/mod.rs
@@ -0,0 +1,1 @@
+pub mod mock;
diff --git a/src/addons/api/bech32.rs b/src/addons/api/bech32.rs
new file mode 100644
index 0000000..f2fd739
--- /dev/null
+++ b/src/addons/api/bech32.rs
@@ -0,0 +1,187 @@
+use bech32::{decode, encode, FromBase32, ToBase32, Variant};
+use cosmwasm_std::testing::MockApi;
+use cosmwasm_std::{
+    Addr, Api, CanonicalAddr, RecoverPubkeyError, StdError, StdResult, VerificationError,
+};
+use sha2::{Digest, Sha256};
+
+/// Implementation of the `Api` trait that uses [`Bech32`] format
+/// for humanizing canonical addresses.
+///
+/// [`Bech32`]:https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
+pub struct MockApiBech32 {
+    api: MockApi,
+    prefix: &'static str,
+    variant: Variant,
+}
+
+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::new_with_variant(prefix, Variant::Bech32)
+    }
+
+    /// Creates `Api` implementation that uses specified prefix
+    /// to generate addresses in format defined by provided Bech32 variant.
+    pub(crate) fn new_with_variant(prefix: &'static str, variant: Variant) -> Self {
+        Self {
+            api: MockApi::default(),
+            prefix,
+            variant,
+        }
+    }
+}
+
+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<Addr> {
+        self.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<CanonicalAddr> {
+        if let Ok((prefix, decoded, variant)) = decode(input) {
+            if prefix == self.prefix && variant == self.variant {
+                if let Ok(bytes) = Vec::<u8>::from_base32(&decoded) {
+                    return Ok(bytes.into());
+                }
+            }
+        }
+        Err(StdError::generic_err("Invalid 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<Addr> {
+        if let Ok(encoded) = encode(self.prefix, canonical.as_slice().to_base32(), self.variant) {
+            Ok(Addr::unchecked(encoded))
+        } else {
+            Err(StdError::generic_err("Invalid canonical address"))
+        }
+    }
+
+    fn secp256k1_verify(
+        &self,
+        message_hash: &[u8],
+        signature: &[u8],
+        public_key: &[u8],
+    ) -> Result<bool, VerificationError> {
+        self.api
+            .secp256k1_verify(message_hash, signature, public_key)
+    }
+
+    fn secp256k1_recover_pubkey(
+        &self,
+        message_hash: &[u8],
+        signature: &[u8],
+        recovery_param: u8,
+    ) -> Result<Vec<u8>, RecoverPubkeyError> {
+        self.api
+            .secp256k1_recover_pubkey(message_hash, signature, recovery_param)
+    }
+
+    fn ed25519_verify(
+        &self,
+        message: &[u8],
+        signature: &[u8],
+        public_key: &[u8],
+    ) -> Result<bool, VerificationError> {
+        self.api.ed25519_verify(message, signature, public_key)
+    }
+
+    fn ed25519_batch_verify(
+        &self,
+        messages: &[&[u8]],
+        signatures: &[&[u8]],
+        public_keys: &[&[u8]],
+    ) -> Result<bool, VerificationError> {
+        self.api
+            .ed25519_batch_verify(messages, signatures, public_keys)
+    }
+
+    fn debug(&self, message: &str) {
+        self.api.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 {
+        let digest = Sha256::digest(input).to_vec();
+        match encode(self.prefix, digest.to_base32(), self.variant) {
+            Ok(address) => Addr::unchecked(address),
+            Err(reason) => panic!("Generating address failed with reason: {}", reason),
+        }
+    }
+}
diff --git a/src/addons/api/bech32m.rs b/src/addons/api/bech32m.rs
new file mode 100644
index 0000000..d630f2b
--- /dev/null
+++ b/src/addons/api/bech32m.rs
@@ -0,0 +1,154 @@
+use crate::addons::MockApiBech32;
+use bech32::Variant;
+use cosmwasm_std::{Addr, Api, CanonicalAddr, RecoverPubkeyError, StdResult, VerificationError};
+
+/// Implementation of the `Api` trait that uses [`Bech32m`] format
+/// for humanizing canonical addresses.
+///
+/// [`Bech32m`]:https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki
+pub struct MockApiBech32m(MockApiBech32);
+
+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("osmosis");
+    /// let addr = api.addr_make("sender");
+    /// assert_eq!(addr.as_str(),
+    ///            "osmosis1pgm8hyk0pvphmlvfjc8wsvk4daluz5tgrw6pu5mfpemk74uxnx9qgv9940");
+    /// ```
+    pub fn new(prefix: &'static str) -> Self {
+        Self(MockApiBech32::new_with_variant(prefix, Variant::Bech32m))
+    }
+}
+
+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("osmosis");
+    /// 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<Addr> {
+        self.0.addr_validate(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("osmosis");
+    /// 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<CanonicalAddr> {
+        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("osmosis");
+    /// 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<Addr> {
+        self.0.addr_humanize(canonical)
+    }
+
+    fn secp256k1_verify(
+        &self,
+        message_hash: &[u8],
+        signature: &[u8],
+        public_key: &[u8],
+    ) -> Result<bool, VerificationError> {
+        self.0.secp256k1_verify(message_hash, signature, public_key)
+    }
+
+    fn secp256k1_recover_pubkey(
+        &self,
+        message_hash: &[u8],
+        signature: &[u8],
+        recovery_param: u8,
+    ) -> Result<Vec<u8>, RecoverPubkeyError> {
+        self.0
+            .secp256k1_recover_pubkey(message_hash, signature, recovery_param)
+    }
+
+    fn ed25519_verify(
+        &self,
+        message: &[u8],
+        signature: &[u8],
+        public_key: &[u8],
+    ) -> Result<bool, VerificationError> {
+        self.0.ed25519_verify(message, signature, public_key)
+    }
+
+    fn ed25519_batch_verify(
+        &self,
+        messages: &[&[u8]],
+        signatures: &[&[u8]],
+        public_keys: &[&[u8]],
+    ) -> Result<bool, VerificationError> {
+        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("osmosis");
+    /// let addr = api.addr_make("sender");
+    /// assert_eq!(addr.as_str(),
+    ///            "osmosis1pgm8hyk0pvphmlvfjc8wsvk4daluz5tgrw6pu5mfpemk74uxnx9qgv9940");
+    /// ```
+    ///
+    /// # 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/mod.rs b/src/addons/api/mod.rs
new file mode 100644
index 0000000..d59bbae
--- /dev/null
+++ b/src/addons/api/mod.rs
@@ -0,0 +1,2 @@
+pub mod bech32;
+pub mod bech32m;
diff --git a/src/addons/mod.rs b/src/addons/mod.rs
new file mode 100644
index 0000000..c31658c
--- /dev/null
+++ b/src/addons/mod.rs
@@ -0,0 +1,11 @@
+//! # MultiTest add-ons
+//!
+//! Additional components and functionalities used to enhance
+//! or customize tests of CosmWasm smart contracts.
+
+mod addresses;
+mod api;
+
+pub use addresses::mock::MockAddressGenerator;
+pub use api::bech32::MockApiBech32;
+pub use api::bech32m::MockApiBech32m;
diff --git a/src/lib.rs b/src/lib.rs
index dfcbbdf..91a9f40 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -6,6 +6,7 @@
 //!
 //! To understand the design of this module, please refer to `../DESIGN.md`
 
+pub mod addons;
 mod addresses;
 mod app;
 mod app_builder;


Test patch:
diff --git a/tests/mod.rs b/tests/mod.rs
index 8f2e70e..d14c147 100644
--- a/tests/mod.rs
+++ b/tests/mod.rs
@@ -1,17 +1,9 @@
 #![cfg(test)]
 
-use bech32::{decode, encode, FromBase32, ToBase32, Variant};
-use cosmwasm_std::{
-    instantiate2_address, Addr, Api, CanonicalAddr, RecoverPubkeyError, StdError, StdResult,
-    Storage, VerificationError,
-};
-use cw_multi_test::error::AnyResult;
-use cw_multi_test::AddressGenerator;
 use cw_storage_plus::Item;
 use serde::{Deserialize, Serialize};
-use sha2::digest::Update;
-use sha2::{Digest, Sha256};
 
+mod test_addons;
 mod test_app;
 mod test_app_builder;
 mod test_module;
@@ -77,156 +69,3 @@ mod test_contracts {
         }
     }
 }
-
-mod test_api {
-    use super::*;
-
-    pub struct MockApiBech32 {
-        prefix: &'static str,
-    }
-
-    impl MockApiBech32 {
-        pub fn new(prefix: &'static str) -> Self {
-            Self { prefix }
-        }
-    }
-
-    impl Api for MockApiBech32 {
-        fn addr_validate(&self, input: &str) -> StdResult<Addr> {
-            let canonical = self.addr_canonicalize(input)?;
-            let normalized = self.addr_humanize(&canonical)?;
-            if input != normalized {
-                Err(StdError::generic_err(
-                    "Invalid input: address not normalized",
-                ))
-            } else {
-                Ok(Addr::unchecked(input))
-            }
-        }
-
-        fn addr_canonicalize(&self, input: &str) -> StdResult<CanonicalAddr> {
-            if let Ok((prefix, decoded, Variant::Bech32)) = decode(input) {
-                if prefix == self.prefix {
-                    if let Ok(bytes) = Vec::<u8>::from_base32(&decoded) {
-                        return Ok(bytes.into());
-                    }
-                }
-            }
-            Err(StdError::generic_err("Invalid input"))
-        }
-
-        fn addr_humanize(&self, canonical: &CanonicalAddr) -> StdResult<Addr> {
-            if let Ok(encoded) = encode(
-                self.prefix,
-                canonical.as_slice().to_base32(),
-                Variant::Bech32,
-            ) {
-                Ok(Addr::unchecked(encoded))
-            } else {
-                Err(StdError::generic_err("Invalid canonical address"))
-            }
-        }
-
-        fn secp256k1_verify(
-            &self,
-            _message_hash: &[u8],
-            _signature: &[u8],
-            _public_key: &[u8],
-        ) -> Result<bool, VerificationError> {
-            unimplemented!()
-        }
-
-        fn secp256k1_recover_pubkey(
-            &self,
-            _message_hash: &[u8],
-            _signature: &[u8],
-            _recovery_param: u8,
-        ) -> Result<Vec<u8>, RecoverPubkeyError> {
-            unimplemented!()
-        }
-
-        fn ed25519_verify(
-            &self,
-            _message: &[u8],
-            _signature: &[u8],
-            _public_key: &[u8],
-        ) -> Result<bool, VerificationError> {
-            unimplemented!()
-        }
-
-        fn ed25519_batch_verify(
-            &self,
-            _messages: &[&[u8]],
-            _signatures: &[&[u8]],
-            _public_keys: &[&[u8]],
-        ) -> Result<bool, VerificationError> {
-            unimplemented!()
-        }
-
-        fn debug(&self, _message: &str) {
-            unimplemented!()
-        }
-    }
-
-    impl MockApiBech32 {
-        pub fn addr_make(&self, input: &str) -> Addr {
-            let digest = Sha256::digest(input).to_vec();
-            match encode(self.prefix, digest.to_base32(), Variant::Bech32) {
-                Ok(address) => Addr::unchecked(address),
-                Err(reason) => panic!("Generating address failed with reason: {reason}"),
-            }
-        }
-    }
-}
-
-mod test_addresses {
-    use super::*;
-
-    #[derive(Default)]
-    pub struct MockAddressGenerator;
-
-    impl AddressGenerator for MockAddressGenerator {
-        fn contract_address(
-            &self,
-            api: &dyn Api,
-            _storage: &mut dyn Storage,
-            code_id: u64,
-            instance_id: u64,
-        ) -> AnyResult<Addr> {
-            let canonical_addr = Self::instantiate_address(code_id, instance_id);
-            Ok(Addr::unchecked(api.addr_humanize(&canonical_addr)?))
-        }
-
-        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<Addr> {
-            let canonical_addr = instantiate2_address(checksum, creator, salt)?;
-            Ok(Addr::unchecked(api.addr_humanize(&canonical_addr)?))
-        }
-    }
-
-    impl MockAddressGenerator {
-        // non-predictable contract address generator, see `BuildContractAddressClassic`
-        // implementation in wasmd: https://github.com/CosmWasm/wasmd/blob/main/x/wasm/keeper/addresses.go#L35-L42
-        fn instantiate_address(code_id: u64, instance_id: u64) -> CanonicalAddr {
-            let mut key = Vec::<u8>::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/tests/test_addons/mod.rs b/tests/test_addons/mod.rs
new file mode 100644
index 0000000..0ce9c88
--- /dev/null
+++ b/tests/test_addons/mod.rs
@@ -0,0 +1,1 @@
+mod test_api;
diff --git a/tests/test_addons/test_api/mod.rs b/tests/test_addons/test_api/mod.rs
new file mode 100644
index 0000000..d049acc
--- /dev/null
+++ b/tests/test_addons/test_api/mod.rs
@@ -0,0 +1,46 @@
+use cosmwasm_std::Api;
+use hex_literal::hex;
+
+mod test_bech32;
+mod test_bech32m;
+
+const SECP256K1_MSG_HASH: [u8; 32] =
+    hex!("5ae8317d34d1e595e3fa7247db80c0af4320cce1116de187f8f7e2e099c0d8d0");
+const SECP256K1_SIG: [u8; 64] = hex!("207082eb2c3dfa0b454e0906051270ba4074ac93760ba9e7110cd9471475111151eb0dbbc9920e72146fb564f99d039802bf6ef2561446eb126ef364d21ee9c4");
+const SECP256K1_PUBKEY: [u8;65] = hex!("04051c1ee2190ecfb174bfe4f90763f2b4ff7517b70a2aec1876ebcfd644c4633fb03f3cfbd94b1f376e34592d9d41ccaf640bb751b00a1fadeb0c01157769eb73");
+const SECP256K1_SIG_RECOVER: [u8; 64] = hex!("45c0b7f8c09a9e1f1cea0c25785594427b6bf8f9f878a8af0b1abbb48e16d0920d8becd0c220f67c51217eecfd7184ef0732481c843857e6bc7fc095c4f6b788");
+const SECP256K1_PUBKEY_RECOVER: [u8;65] = hex!("044a071e8a6e10aada2b8cf39fa3b5fb3400b04e99ea8ae64ceea1a977dbeaf5d5f8c8fbd10b71ab14cd561f7df8eb6da50f8a8d81ba564342244d26d1d4211595");
+const ED25519_MSG: [u8; 1] = hex!("72");
+const ED25519_SIG: [u8;64] = hex!("92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00");
+const ED25519_PUBKEY: [u8; 32] =
+    hex!("3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c");
+
+fn assert_secp256k1_verify_works(api: &dyn Api) {
+    assert!(api
+        .secp256k1_verify(&SECP256K1_MSG_HASH, &SECP256K1_SIG, &SECP256K1_PUBKEY)
+        .unwrap());
+}
+
+fn assert_secp256k1_recover_pubkey_works(api: &dyn Api) {
+    assert_eq!(
+        api.secp256k1_recover_pubkey(&SECP256K1_MSG_HASH, &SECP256K1_SIG_RECOVER, 1)
+            .unwrap(),
+        SECP256K1_PUBKEY_RECOVER
+    );
+}
+
+fn assert_ed25519_verify_works(api: &dyn Api) {
+    assert!(api
+        .ed25519_verify(&ED25519_MSG, &ED25519_SIG, &ED25519_PUBKEY)
+        .unwrap());
+}
+
+fn assert_ed25519_batch_verify_works(api: &dyn Api) {
+    assert!(api
+        .ed25519_batch_verify(&[&ED25519_MSG], &[&ED25519_SIG], &[&ED25519_PUBKEY])
+        .unwrap());
+}
+
+fn assert_debug_does_not_panic(api: &dyn Api) {
+    api.debug("debug should not panic");
+}
diff --git a/tests/test_addons/test_api/test_bech32.rs b/tests/test_addons/test_api/test_bech32.rs
new file mode 100644
index 0000000..4b5101e
--- /dev/null
+++ b/tests/test_addons/test_api/test_bech32.rs
@@ -0,0 +1,107 @@
+use super::*;
+use cosmwasm_std::{Api, CanonicalAddr};
+use cw_multi_test::addons::{MockApiBech32, MockApiBech32m};
+
+const HUMAN_ADDRESS: &str = "juno1h34lmpywh4upnjdg90cjf4j70aee6z8qqfspugamjp42e4q28kqsksmtyp";
+
+#[test]
+fn new_api_bech32_should_work() {
+    assert_eq!(
+        MockApiBech32::new("juno").addr_make("creator").as_str(),
+        HUMAN_ADDRESS
+    );
+}
+
+#[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(),
+    );
+}
+
+#[test]
+fn address_validate_should_work() {
+    assert_eq!(
+        MockApiBech32::new("juno")
+            .addr_validate(HUMAN_ADDRESS)
+            .unwrap()
+            .as_str(),
+        HUMAN_ADDRESS
+    )
+}
+
+#[test]
+fn address_validate_invalid_address() {
+    MockApiBech32::new("juno")
+        .addr_validate("creator")
+        .unwrap_err();
+}
+
+#[test]
+fn addr_validate_invalid_prefix() {
+    MockApiBech32::new("juno")
+        .addr_validate(MockApiBech32m::new("osmosis").addr_make("creator").as_str())
+        .unwrap_err();
+}
+
+#[test]
+fn address_validate_invalid_variant() {
+    MockApiBech32::new("juno")
+        .addr_validate(MockApiBech32m::new("juno").addr_make("creator").as_str())
+        .unwrap_err();
+}
+
+#[test]
+fn address_canonicalize_humanize_should_work() {
+    let api = MockApiBech32::new("juno");
+    assert_eq!(
+        api.addr_humanize(&api.addr_canonicalize(HUMAN_ADDRESS).unwrap())
+            .unwrap()
+            .as_str(),
+        HUMAN_ADDRESS
+    );
+}
+
+#[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();
+}
+
+#[test]
+fn debug_should_not_panic() {
+    assert_debug_does_not_panic(&MockApiBech32::new("juno"));
+}
+
+#[test]
+#[should_panic(expected = "Generating address failed with reason: invalid length")]
+fn address_make_prefix_too_long() {
+    MockApiBech32::new(
+        "juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_",
+    )
+    .addr_make("creator");
+}
+
+#[test]
+fn secp256k1_verify_works() {
+    assert_secp256k1_verify_works(&MockApiBech32::new("juno"));
+}
+
+#[test]
+fn secp256k1_recover_pubkey_works() {
+    assert_secp256k1_recover_pubkey_works(&MockApiBech32::new("juno"));
+}
+
+#[test]
+fn ed25519_verify_works() {
+    assert_ed25519_verify_works(&MockApiBech32::new("juno"));
+}
+
+#[test]
+fn ed25519_batch_verify_works() {
+    assert_ed25519_batch_verify_works(&MockApiBech32::new("juno"));
+}
diff --git a/tests/test_addons/test_api/test_bech32m.rs b/tests/test_addons/test_api/test_bech32m.rs
new file mode 100644
index 0000000..9cdc70c
--- /dev/null
+++ b/tests/test_addons/test_api/test_bech32m.rs
@@ -0,0 +1,107 @@
+use super::*;
+use cosmwasm_std::CanonicalAddr;
+use cw_multi_test::addons::{MockApiBech32, MockApiBech32m};
+
+const HUMAN_ADDRESS: &str = "juno1h34lmpywh4upnjdg90cjf4j70aee6z8qqfspugamjp42e4q28kqsrvt8pr";
+
+#[test]
+fn new_api_bech32m_should_work() {
+    assert_eq!(
+        MockApiBech32m::new("juno").addr_make("creator").as_str(),
+        HUMAN_ADDRESS
+    );
+}
+
+#[test]
+fn api_bech32m_should_differ_from_bech32() {
+    assert_ne!(
+        MockApiBech32m::new("juno").addr_make("sender").as_str(),
+        MockApiBech32::new("juno").addr_make("sender").as_str()
+    );
+}
+
+#[test]
+fn address_validate_should_work() {
+    assert_eq!(
+        MockApiBech32m::new("juno")
+            .addr_validate(HUMAN_ADDRESS)
+            .unwrap()
+            .as_str(),
+        HUMAN_ADDRESS
+    )
+}
+
+#[test]
+fn address_validate_invalid_address() {
+    MockApiBech32m::new("juno")
+        .addr_validate("creator")
+        .unwrap_err();
+}
+
+#[test]
+fn addr_validate_invalid_prefix() {
+    MockApiBech32m::new("juno")
+        .addr_validate(MockApiBech32m::new("osmosis").addr_make("creator").as_str())
+        .unwrap_err();
+}
+
+#[test]
+fn address_validate_invalid_variant() {
+    MockApiBech32m::new("juno")
+        .addr_validate(MockApiBech32::new("juno").addr_make("creator").as_str())
+        .unwrap_err();
+}
+
+#[test]
+fn address_canonicalize_humanize_should_work() {
+    let api = MockApiBech32m::new("juno");
+    assert_eq!(
+        api.addr_humanize(&api.addr_canonicalize(HUMAN_ADDRESS).unwrap())
+            .unwrap()
+            .as_str(),
+        HUMAN_ADDRESS
+    );
+}
+
+#[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();
+}
+
+#[test]
+fn debug_should_not_panic() {
+    assert_debug_does_not_panic(&MockApiBech32m::new("juno"));
+}
+
+#[test]
+#[should_panic(expected = "Generating address failed with reason: invalid length")]
+fn address_make_prefix_too_long() {
+    MockApiBech32m::new(
+        "juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_juno_",
+    )
+    .addr_make("creator");
+}
+
+#[test]
+fn secp256k1_verify_works() {
+    assert_secp256k1_verify_works(&MockApiBech32m::new("juno"));
+}
+
+#[test]
+fn secp256k1_recover_pubkey_works() {
+    assert_secp256k1_recover_pubkey_works(&MockApiBech32m::new("juno"));
+}
+
+#[test]
+fn ed25519_verify_works() {
+    assert_ed25519_verify_works(&MockApiBech32m::new("juno"));
+}
+
+#[test]
+fn ed25519_batch_verify_works() {
+    assert_ed25519_batch_verify_works(&MockApiBech32m::new("juno"));
+}
diff --git a/tests/test_app/test_instantiate2.rs b/tests/test_app/test_instantiate2.rs
index 767f5cf..4e2a810 100644
--- a/tests/test_app/test_instantiate2.rs
+++ b/tests/test_app/test_instantiate2.rs
@@ -1,9 +1,8 @@
 #![cfg(feature = "cosmwasm_1_2")]
 
-use crate::test_addresses::MockAddressGenerator;
-use crate::test_api::MockApiBech32;
 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::{AppBuilder, Executor, WasmKeeper};
 use cw_utils::parse_instantiate_response_data;
 
diff --git a/tests/test_app_builder/test_with_api.rs b/tests/test_app_builder/test_with_api.rs
index bf883a1..46a92b7 100644
--- a/tests/test_app_builder/test_with_api.rs
+++ b/tests/test_app_builder/test_with_api.rs
@@ -1,5 +1,5 @@
-use crate::test_api::MockApiBech32;
 use cosmwasm_std::{Addr, Api, CanonicalAddr, HexBinary};
+use cw_multi_test::addons::MockApiBech32;
 use cw_multi_test::AppBuilder;
 
 #[test]
diff --git a/tests/test_wasm/test_with_addr_gen.rs b/tests/test_wasm/test_with_addr_gen.rs
index 2b8bd5e..ef7bdaa 100644
--- a/tests/test_wasm/test_with_addr_gen.rs
+++ b/tests/test_wasm/test_with_addr_gen.rs
@@ -1,7 +1,6 @@
-use crate::test_addresses::MockAddressGenerator;
-use crate::test_api::MockApiBech32;
 use crate::test_contracts;
 use cosmwasm_std::{Addr, Empty};
+use cw_multi_test::addons::{MockAddressGenerator, MockApiBech32};
 use cw_multi_test::{AppBuilder, Executor, WasmKeeper};
 
 #[test]