Repository: CosmWasm/cw-multi-test
Pull request #154 (2024-03-26 08:51:00)

Exposed prefixed storage
Exposed functions for accessing prefixed storage (like bank) to be used mainly in testing custom modules with storage.


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 401c81e..1493190 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -39,9 +39,9 @@ dependencies = [
 
 [[package]]
 name = "backtrace"
-version = "0.3.69"
+version = "0.3.71"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
+checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d"
 dependencies = [
  "addr2line",
  "cc",
@@ -114,9 +114,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
 
 [[package]]
 name = "bytes"
-version = "1.5.0"
+version = "1.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
+checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
 
 [[package]]
 name = "cc"
@@ -500,9 +500,9 @@ dependencies = [
 
 [[package]]
 name = "itoa"
-version = "1.0.10"
+version = "1.0.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
+checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
 
 [[package]]
 name = "k256"
@@ -599,7 +599,7 @@ dependencies = [
  "itertools 0.11.0",
  "proc-macro2",
  "quote",
- "syn 2.0.53",
+ "syn 2.0.55",
 ]
 
 [[package]]
@@ -712,7 +712,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.53",
+ "syn 2.0.55",
 ]
 
 [[package]]
@@ -728,9 +728,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.114"
+version = "1.0.115"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
+checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd"
 dependencies = [
  "itoa",
  "ryu",
@@ -806,9 +806,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.53"
+version = "2.0.55"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032"
+checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -832,7 +832,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.53",
+ "syn 2.0.55",
 ]
 
 [[package]]
diff --git a/src/app.rs b/src/app.rs
index a400882..b3d8db8 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -5,6 +5,9 @@ use crate::executor::{AppResponse, Executor};
 use crate::gov::Gov;
 use crate::ibc::Ibc;
 use crate::module::{FailingModule, Module};
+use crate::prefixed_storage::{
+    prefixed, prefixed_multilevel, prefixed_multilevel_read, prefixed_read,
+};
 use crate::staking::{Distribution, DistributionKeeper, StakeKeeper, Staking, StakingSudo};
 use crate::stargate::{Stargate, StargateFailingModule};
 use crate::transactions::transactional;
@@ -345,6 +348,32 @@ where
     pub fn dump_wasm_raw(&self, address: &Addr) -> Vec<Record> {
         self.read_module(|router, _, storage| router.wasm.dump_wasm_raw(storage, address))
     }
+
+    /// Returns **read-only** prefixed storage with specified namespace.
+    pub fn prefixed_storage<'a>(&'a self, namespace: &[u8]) -> Box<dyn Storage + 'a> {
+        Box::new(prefixed_read(&self.storage, namespace))
+    }
+
+    /// Returns **mutable** prefixed storage with specified namespace.
+    pub fn prefixed_storage_mut<'a>(&'a mut self, namespace: &[u8]) -> Box<dyn Storage + 'a> {
+        Box::new(prefixed(&mut self.storage, namespace))
+    }
+
+    /// Returns **read-only** prefixed, multilevel storage with specified namespaces.
+    pub fn prefixed_multilevel_storage<'a>(
+        &'a self,
+        namespaces: &[&[u8]],
+    ) -> Box<dyn Storage + 'a> {
+        Box::new(prefixed_multilevel_read(&self.storage, namespaces))
+    }
+
+    /// Returns **mutable** prefixed, multilevel storage with specified namespaces.
+    pub fn prefixed_multilevel_storage_mut<'a>(
+        &'a mut self,
+        namespaces: &[&[u8]],
+    ) -> Box<dyn Storage + 'a> {
+        Box::new(prefixed_multilevel(&mut self.storage, namespaces))
+    }
 }
 
 impl<BankT, ApiT, StorageT, CustomT, WasmT, StakingT, DistrT, IbcT, GovT, StargateT>
diff --git a/src/bank.rs b/src/bank.rs
index 1843c7e..4586e93 100644
--- a/src/bank.rs
+++ b/src/bank.rs
@@ -20,7 +20,7 @@ const BALANCES: Map<&Addr, NativeBalance> = Map::new("balances");
 const DENOM_METADATA: Map<String, DenomMetadata> = Map::new("metadata");
 
 /// Default storage namespace for bank module.
-pub const NAMESPACE_BANK: &[u8] = b"bank";
+const NAMESPACE_BANK: &[u8] = b"bank";
 
 /// A message representing privileged actions in bank module.
 #[derive(Clone, Debug, PartialEq, Eq, JsonSchema)]
diff --git a/src/prefixed_storage/mod.rs b/src/prefixed_storage/mod.rs
index 2666a85..77416fc 100644
--- a/src/prefixed_storage/mod.rs
+++ b/src/prefixed_storage/mod.rs
@@ -6,12 +6,12 @@ use namespace_helpers::{get_with_prefix, range_with_prefix, remove_with_prefix,
 mod length_prefixed;
 mod namespace_helpers;
 
-/// An alias of PrefixedStorage::new for less verbose usage
+/// An alias of [PrefixedStorage::new] for less verbose usage.
 pub fn prefixed<'a>(storage: &'a mut dyn Storage, namespace: &[u8]) -> PrefixedStorage<'a> {
     PrefixedStorage::new(storage, namespace)
 }
 
-/// An alias of ReadonlyPrefixedStorage::new for less verbose usage
+/// An alias of [ReadonlyPrefixedStorage::new] for less verbose usage.
 pub fn prefixed_read<'a>(
     storage: &'a dyn Storage,
     namespace: &[u8],
@@ -19,12 +19,30 @@ pub fn prefixed_read<'a>(
     ReadonlyPrefixedStorage::new(storage, namespace)
 }
 
+/// An alias of [PrefixedStorage::multilevel] for less verbose usage.
+pub fn prefixed_multilevel<'a>(
+    storage: &'a mut dyn Storage,
+    namespaces: &[&[u8]],
+) -> PrefixedStorage<'a> {
+    PrefixedStorage::multilevel(storage, namespaces)
+}
+
+/// An alias of [ReadonlyPrefixedStorage::multilevel] for less verbose usage.
+pub fn prefixed_multilevel_read<'a>(
+    storage: &'a dyn Storage,
+    namespaces: &[&[u8]],
+) -> ReadonlyPrefixedStorage<'a> {
+    ReadonlyPrefixedStorage::multilevel(storage, namespaces)
+}
+
+/// Prefixed, mutable storage.
 pub struct PrefixedStorage<'a> {
     storage: &'a mut dyn Storage,
     prefix: Vec<u8>,
 }
 
 impl<'a> PrefixedStorage<'a> {
+    /// Returns a mutable prefixed storage with specified namespace.
     pub fn new(storage: &'a mut dyn Storage, namespace: &[u8]) -> Self {
         PrefixedStorage {
             storage,
@@ -32,8 +50,9 @@ impl<'a> PrefixedStorage<'a> {
         }
     }
 
-    // Nested namespaces as documented in
-    // https://github.com/webmaster128/key-namespacing#nesting
+    /// Returns a mutable prefixed storage with [nested namespaces].
+    ///
+    /// [nested namespaces]: https://github.com/webmaster128/key-namespacing#nesting
     pub fn multilevel(storage: &'a mut dyn Storage, namespaces: &[&[u8]]) -> Self {
         PrefixedStorage {
             storage,
@@ -67,12 +86,14 @@ impl<'a> Storage for PrefixedStorage<'a> {
     }
 }
 
+/// Prefixed, read-only storage.
 pub struct ReadonlyPrefixedStorage<'a> {
     storage: &'a dyn Storage,
     prefix: Vec<u8>,
 }
 
 impl<'a> ReadonlyPrefixedStorage<'a> {
+    /// Returns a read-only prefixed storage with specified namespace.
     pub fn new(storage: &'a dyn Storage, namespace: &[u8]) -> Self {
         ReadonlyPrefixedStorage {
             storage,
@@ -80,8 +101,9 @@ impl<'a> ReadonlyPrefixedStorage<'a> {
         }
     }
 
-    // Nested namespaces as documented in
-    // https://github.com/webmaster128/key-namespacing#nesting
+    /// Returns a read-only prefixed storage with [nested namespaces].
+    ///
+    /// [nested namespaces]: https://github.com/webmaster128/key-namespacing#nesting
     pub fn multilevel(storage: &'a dyn Storage, namespaces: &[&[u8]]) -> Self {
         ReadonlyPrefixedStorage {
             storage,


Test patch:
diff --git a/tests/mod.rs b/tests/mod.rs
index f537060..ddba5c2 100644
--- a/tests/mod.rs
+++ b/tests/mod.rs
@@ -7,6 +7,7 @@ mod test_api;
 mod test_app;
 mod test_app_builder;
 mod test_module;
+mod test_prefixed_storage;
 mod test_wasm;
 
 const COUNTER: Item<u64> = Item::new("count");
diff --git a/tests/test_prefixed_storage/mod.rs b/tests/test_prefixed_storage/mod.rs
new file mode 100644
index 0000000..aeda3e9
--- /dev/null
+++ b/tests/test_prefixed_storage/mod.rs
@@ -0,0 +1,2 @@
+mod test_prefixed_multilevel_storage;
+mod test_prefixed_storage_bank;
diff --git a/tests/test_prefixed_storage/test_prefixed_multilevel_storage.rs b/tests/test_prefixed_storage/test_prefixed_multilevel_storage.rs
new file mode 100644
index 0000000..03dd64c
--- /dev/null
+++ b/tests/test_prefixed_storage/test_prefixed_multilevel_storage.rs
@@ -0,0 +1,40 @@
+use cosmwasm_std::{coin, Addr};
+use cw_multi_test::{App, IntoAddr};
+use cw_storage_plus::Map;
+use cw_utils::NativeBalance;
+use std::ops::{Deref, DerefMut};
+
+const NAMESPACE_CENTRAL_BANK: &[u8] = b"central-bank";
+const NAMESPACE_NATIONAL_BANK: &[u8] = b"national-bank";
+const NAMESPACE_LOCAL_BANK: &[u8] = b"local-bank";
+const NAMESPACES: &[&[u8]] = &[
+    NAMESPACE_NATIONAL_BANK,
+    NAMESPACE_CENTRAL_BANK,
+    NAMESPACE_LOCAL_BANK,
+];
+const BALANCES: Map<&Addr, NativeBalance> = Map::new("balances");
+
+#[test]
+fn multilevel_storage_should_work() {
+    // prepare balance owner
+    let owner_addr = "owner".into_addr();
+    // create the blockchain
+    let mut app = App::default();
+    {
+        // get the mutable prefixed, multilevel storage for banks
+        let mut storage_mut = app.prefixed_multilevel_storage_mut(NAMESPACES);
+        // set balances manually
+        let mut balance = NativeBalance(vec![coin(111, "BTC"), coin(222, "ETH")]);
+        balance.normalize();
+        BALANCES
+            .save(storage_mut.deref_mut(), &owner_addr, &balance)
+            .unwrap();
+    }
+    {
+        // get the read-only prefixed, multilevel storage for banks
+        let storage = app.prefixed_multilevel_storage(NAMESPACES);
+        // read balances manually
+        let balances = BALANCES.load(storage.deref(), &owner_addr).unwrap();
+        assert_eq!("BTC111ETH222", balances.to_string());
+    }
+}
diff --git a/tests/test_prefixed_storage/test_prefixed_storage_bank.rs b/tests/test_prefixed_storage/test_prefixed_storage_bank.rs
new file mode 100644
index 0000000..653ddcf
--- /dev/null
+++ b/tests/test_prefixed_storage/test_prefixed_storage_bank.rs
@@ -0,0 +1,49 @@
+use cosmwasm_std::{coin, Addr};
+use cw_multi_test::{App, IntoAddr};
+use cw_storage_plus::Map;
+use cw_utils::NativeBalance;
+use std::ops::{Deref, DerefMut};
+
+const NAMESPACE_BANK: &[u8] = b"bank";
+const BALANCES: Map<&Addr, NativeBalance> = Map::new("balances");
+
+#[test]
+fn reading_bank_storage_should_work() {
+    // prepare balance owner
+    let owner_addr = "owner".into_addr();
+
+    // set balances
+    let init_funds = vec![coin(1, "BTC"), coin(2, "ETH")];
+    let app = App::new(|router, _, storage| {
+        router
+            .bank
+            .init_balance(storage, &owner_addr, init_funds)
+            .unwrap();
+    });
+
+    // get the read-only prefixed storage for bank
+    let storage = app.prefixed_storage(NAMESPACE_BANK);
+    let balances = BALANCES.load(storage.deref(), &owner_addr).unwrap();
+    assert_eq!("BTC1ETH2", balances.to_string());
+}
+
+#[test]
+fn writing_bank_storage_should_work() {
+    // prepare balance owner
+    let owner_addr = "owner".into_addr();
+
+    let mut app = App::default();
+    // get the mutable prefixed storage for bank
+    let mut storage = app.prefixed_storage_mut(NAMESPACE_BANK);
+
+    // set balances manually
+    let mut balance = NativeBalance(vec![coin(3, "BTC"), coin(4, "ETH")]);
+    balance.normalize();
+    BALANCES
+        .save(storage.deref_mut(), &owner_addr, &balance)
+        .unwrap();
+
+    // check balances
+    let balances = BALANCES.load(storage.deref(), &owner_addr).unwrap();
+    assert_eq!("BTC3ETH4", balances.to_string());
+}