Repository: CosmWasm/cw-multi-test
Pull request #81 (2023-10-10 07:34:51)

Add tests for `MockApi` in `App`
Added unit tests for `cosmwasm_std::testing::MockApi` used by default in `App`.
In future versions of `cosmwasm-std`, the behavior of `cosmwasm_std::testing::MockApi` will be changed, so these tests should then guard these changes.
For planned changes in `cosmwasm_std::testing::MockApi` see:
- https://github.com/CosmWasm/cosmwasm/pull/1905
- https://github.com/CosmWasm/cosmwasm/issues/1873

Added integration test for `AppBuilder::with_api` function that takes as an argument a custom implementation of `Api` trait. This example test shows how to apply custom address encodings to `Api` interface.


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 090cadc..4173819 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -70,6 +70,12 @@ version = "1.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
 
+[[package]]
+name = "bech32"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445"
+
 [[package]]
 name = "block-buffer"
 version = "0.9.0"
@@ -129,9 +135,9 @@ checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f"
 
 [[package]]
 name = "cosmwasm-crypto"
-version = "1.4.0"
+version = "1.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ca101fbf2f76723711a30ea3771ef312ec3ec254ad021b237871ed802f9f175"
+checksum = "a6fb22494cf7d23d0c348740e06e5c742070b2991fd41db77bba0bcfbae1a723"
 dependencies = [
  "digest 0.10.7",
  "ed25519-zebra",
@@ -142,18 +148,18 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-derive"
-version = "1.4.0"
+version = "1.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c73d2dd292f60e42849d2b07c03d809cf31e128a4299a805abd6d24553bcaaf5"
+checksum = "6e199424486ea97d6b211db6387fd72e26b4a439d40cc23140b2d8305728055b"
 dependencies = [
  "syn 1.0.109",
 ]
 
 [[package]]
 name = "cosmwasm-schema"
-version = "1.4.0"
+version = "1.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ce34a08020433989af5cc470104f6bd22134320fe0221bd8aeb919fd5ec92d5"
+checksum = "fef683a9c1c4eabd6d31515719d0d2cc66952c4c87f7eb192bfc90384517dc34"
 dependencies = [
  "cosmwasm-schema-derive",
  "schemars",
@@ -164,9 +170,9 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-schema-derive"
-version = "1.4.0"
+version = "1.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96694ec781a7dd6dea1f968a2529ade009c21ad999c88b5f53d6cc495b3b96f7"
+checksum = "9567025acbb4c0c008178393eb53b3ac3c2e492c25949d3bf415b9cbe80772d8"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -175,9 +181,9 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-std"
-version = "1.4.0"
+version = "1.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a44d3f9c25b2f864737c6605a98f2e4675d53fd8bbc7cf4d7c02475661a793d"
+checksum = "7d89d680fb60439b7c5947b15f9c84b961b88d1f8a3b20c4bd178a3f87db8bae"
 dependencies = [
  "base64",
  "bnum",
@@ -242,6 +248,7 @@ name = "cw-multi-test"
 version = "0.17.0"
 dependencies = [
  "anyhow",
+ "bech32",
  "cosmwasm-std",
  "cw-storage-plus",
  "cw-utils",
@@ -508,9 +515,9 @@ dependencies = [
 
 [[package]]
 name = "libc"
-version = "0.2.148"
+version = "0.2.149"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
+checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
 
 [[package]]
 name = "memchr"
@@ -560,9 +567,9 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.68"
+version = "1.0.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b1106fec09662ec6dd98ccac0f81cef56984d0b49f75c92d8cbad76e20c005c"
+checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
 dependencies = [
  "unicode-ident",
 ]
diff --git a/Cargo.toml b/Cargo.toml
index a180c73..b60962d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -20,20 +20,23 @@ cosmwasm_1_3 = ["cosmwasm_1_2", "cosmwasm-std/cosmwasm_1_3"]
 cosmwasm_1_4 = ["cosmwasm_1_3", "cosmwasm-std/cosmwasm_1_4"]
 
 [dependencies]
-cw-utils = "1.0.2"
+anyhow = "1.0.75"
+cosmwasm-std = { version = "1.4.1", features = ["staking", "stargate"] }
 cw-storage-plus = "1.1.0"
-cosmwasm-std = { version = "1.4.0", features = ["staking", "stargate"] }
+cw-utils = "1.0.2"
+derivative = "2.2.0"
 itertools = "0.11.0"
+prost = "0.12.1"
 schemars = "0.8.15"
 serde = { version = "1.0.188", default-features = false, features = ["derive"] }
-prost = "0.12.1"
-anyhow = "1.0.75"
-thiserror = "1.0.49"
-derivative = "2.2.0"
 sha2 = "0.10.8"
+thiserror = "1.0.49"
 
 [dev-dependencies]
+bech32 = "0.9.1"
 once_cell = "1.18.0"
+sha2 = "0.10.8"
+
 # We don't use these dependencies directly,
 # we tighten versions that builds with `-Zminimal-versions` work.
 ecdsa = "0.16.8"


Test patch:
diff --git a/src/tests/app.rs b/src/tests/app.rs
index a22b941..4043bac 100644
--- a/src/tests/app.rs
+++ b/src/tests/app.rs
@@ -5,11 +5,12 @@ use crate::test_helpers::echo::EXECUTE_REPLY_BASE_ID;
 use crate::test_helpers::{caller, echo, error, hackatom, payout, reflect, CustomMsg};
 use crate::transactions::{transactional, StorageTransaction};
 use crate::wasm::ContractData;
+use crate::AppBuilder;
 use crate::{
     custom_app, next_block, App, AppResponse, Bank, CosmosRouter, Distribution, Executor, Module,
     Router, Staking, Wasm, WasmSudo,
 };
-use cosmwasm_std::testing::{mock_env, MockApi, MockQuerier};
+use cosmwasm_std::testing::{mock_env, MockQuerier};
 use cosmwasm_std::{
     coin, coins, from_slice, to_binary, Addr, AllBalanceResponse, Api, Attribute, BankMsg,
     BankQuery, Binary, BlockInfo, Coin, CosmosMsg, CustomQuery, Empty, Event, OverflowError,
@@ -1631,22 +1632,19 @@ mod wasm_queries {
 
 mod custom_messages {
     use super::*;
-    use crate::AppBuilder;
 
     #[test]
     fn triggering_custom_msg() {
-        let api = MockApi::default();
-        let sender = api.addr_validate("sender").unwrap();
-        let owner = api.addr_validate("owner").unwrap();
-
         let custom_handler = CachingCustomHandler::<CustomMsg, Empty>::new();
         let custom_handler_state = custom_handler.state();
 
         let mut app = AppBuilder::new_custom()
-            .with_api(api)
             .with_custom(custom_handler)
             .build(no_init);
 
+        let sender = app.api().addr_validate("sender").unwrap();
+        let owner = app.api().addr_validate("owner").unwrap();
+
         let contract_id = app.store_code(echo::custom_contract());
 
         let contract = app
@@ -1972,3 +1970,29 @@ mod errors {
         assert_eq!(err.chain().count(), 4);
     }
 }
+
+mod api {
+    use super::*;
+
+    #[test]
+    fn api_addr_validate_should_work() {
+        let app = App::default();
+        let addr = app.api().addr_validate("creator").unwrap();
+        assert_eq!(addr.to_string(), "creator");
+    }
+
+    #[test]
+    #[cfg(not(feature = "cosmwasm_1_5"))]
+    fn api_addr_canonicalize_should_work() {
+        let app = App::default();
+        let canonical = app.api().addr_canonicalize("creator").unwrap();
+        assert_eq!(canonical.to_string(), "0000000000000000000000000000726F0000000000000000000000000000000000000000006572000000000000000000000000000000000000000000610000000000000000000000000000000000000000006374000000000000");
+    }
+
+    #[test]
+    fn api_addr_humanize_should_work() {
+        let app = App::default();
+        let canonical = app.api().addr_canonicalize("creator").unwrap();
+        assert_eq!(app.api().addr_humanize(&canonical).unwrap(), "creator");
+    }
+}
diff --git a/tests/app_builder/mod.rs b/tests/app_builder/mod.rs
index 2aba8b8..74cc55a 100644
--- a/tests/app_builder/mod.rs
+++ b/tests/app_builder/mod.rs
@@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize};
 use std::fmt::Debug;
 use std::marker::PhantomData;
 
+mod with_api;
 mod with_bank;
 mod with_block;
 mod with_distribution;
diff --git a/tests/app_builder/with_api.rs b/tests/app_builder/with_api.rs
new file mode 100644
index 0000000..ed8a250
--- /dev/null
+++ b/tests/app_builder/with_api.rs
@@ -0,0 +1,139 @@
+use bech32::{decode, encode, FromBase32, ToBase32, Variant};
+use cosmwasm_std::{
+    Addr, Api, CanonicalAddr, HexBinary, RecoverPubkeyError, StdError, StdResult, VerificationError,
+};
+use cw_multi_test::AppBuilder;
+use sha2::{Digest, Sha256};
+
+struct MyApi {
+    prefix: &'static str,
+}
+
+impl MyApi {
+    fn new(prefix: &'static str) -> Self {
+        Self { prefix }
+    }
+}
+
+impl Api for MyApi {
+    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 MyApi {
+    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}"),
+        }
+    }
+}
+
+#[test]
+fn building_app_with_custom_api_should_work() {
+    // prepare test data
+    let human = "juno1h34lmpywh4upnjdg90cjf4j70aee6z8qqfspugamjp42e4q28kqsksmtyp";
+    let hex = "bc6bfd848ebd7819c9a82bf124d65e7f739d08e002601e23bb906aacd40a3d81";
+
+    // create application with custom api that implements
+    // Bech32 address encoding with 'juno' prefix
+    let app = AppBuilder::default()
+        .with_api(MyApi::new("juno"))
+        .build(|_, _, _| {});
+
+    // check address validation function
+    assert_eq!(
+        app.api().addr_validate(human).unwrap(),
+        Addr::unchecked(human)
+    );
+
+    // check if address can be canonicalized
+    assert_eq!(
+        app.api().addr_canonicalize(human).unwrap(),
+        CanonicalAddr::from(HexBinary::from_hex(hex).unwrap())
+    );
+
+    // check if address can be humanized
+    assert_eq!(
+        app.api()
+            .addr_humanize(&app.api().addr_canonicalize(human).unwrap())
+            .unwrap(),
+        Addr::unchecked(human)
+    );
+
+    // check extension function for creating Bech32 encoded addresses
+    assert_eq!(app.api().addr_make("creator"), Addr::unchecked(human));
+}