MultiTestStorage

Storage

Default storage

By default, MultiTest relies on MockStorage, a storage implementation provided by the CosmWasm library. MockStorage operates entirely in memory, meaning that any data written during testing is discarded once the test completes. Since it does not persist any data beyond the test execution, each test is executed in isolation without retaining any previous state. To use a default storage in your tests, just create the chain with default settings, as shown below.

// initialize your chain this way:
let app = App::default();
 
// or this way:
let app = AppBuilder::default().build(no_init);

The App provides several methods to access and manipulate the storage, all of which are covered in the Accessing storage in tests section.

Custom storage

If the default storage does not fully meet your testing requirements, you can provide a custom storage by implementing the Storage trait. Only the get, set,remove and range methods are required, as the trait already provides a basic implementation for range_keys and range_values methods. The table below summarizes all these methods.

Methods of Storage traitDescription
getReturns a value associated with a specified key.
setSets a new value for a specified key.
removeRemoves an entry with a specified key.
rangeIterates over a set of key/value pairs, either forwards or backwards.
range_keysIterates over a set of keys, either forwards or backwards.
range_valuesIterates over a set of values, either forwards or backwards.

For inspiration on implementing custom storage, you can refer to MemoryStorage in the CosmWasm library. The following code stub could be a good starting point.

#[derive(Default)]
struct CustomStorage(/* use your persistent type here */);
 
impl Storage for CustomStorage {
    fn get(&self, key: &[u8]) -> Option<Vec<u8>> {
        // return a value associated with the specified key
    }
 
    fn set(&mut self, key: &[u8], value: &[u8]) {
        // associate value with specified key and persist them
    }
 
    fn remove(&mut self, key: &[u8]) {
        // remove an entry with specified key from storage
    }
 
    fn range<'a>(&'a self, start: Option<&[u8]>, end: Option<&[u8]>, order: Order) -> Box<dyn Iterator<Item = Record> + 'a> {
        // return an iterator over key/value pairs
    }
}

Initializing storage

To initialize the default storage or use a custom storage implementation in tests, use the AppBuilder::with_storage method when building the chain. For more details, see the AppBuilder/with_storage chapter.

Accessing storage from smart contracts

Smart contracts should access the storage used by the chain through libraries such as Storey and StoragePlus.

Accessing storage in tests

App provides several methods to access and manipulate storage in tests, and the table below summarizes them all.

Methods of AppAccessPurpose
storageRRead the value associated with any key.
storage_mutR/WRead or write the value associated with any key.
contract_storageRRead the value associated with any key but only for specified the contract address.
contract_storage_mutR/WRead or write the value associated with any key but only for specified the contract address.
prefixed_storageRRead the value associated with any key having a single prefix.
prefixed_storage_mutR/WRead or write the value associated with any key having a single prefix.
prefixed_multilevel_storageRRead the value associated with any key having multiple prefixes.
prefixed_multilevel_storage_mutR/WRead or write the value associated with any key having a multiple prefixes.
  • R - read-only
  • R/W - read/write

storage

Using methods storage and storage_mut you can access any value associated with a key, as long as you know the key (with or without a prefix). The following example firstly assigns value to a key in line 9, and then reads the value from storage in line 13. The key can be any binary.

use cosmwasm_std::Storage;
use cw_multi_test::App;
 
let mut app = App::default();
 
let key = b"key";
let value = b"value";
 
app.storage_mut().set(key, value);
 
assert_eq!(
    Some(value.to_vec()),
    app.storage().get(key)
);

contract_storage

If you know the address of a contract, you can access all keys and values stored by that contract using the contract_storage and contract_storage_mut methods. The following example assigns a value to a key in a similar way to how the contract would (line 11) and then reads this value on line 15. These methods are giving easy and simple access to the storage of any contract.

use cw_multi_test::App;
use cw_multi_test::IntoAddr;
 
let mut app = App::default();
 
let key = b"key";
let value = b"value";
 
let contract_addr = "contract".into_addr();
 
app.contract_storage_mut(&contract_addr).set(key, value);
 
assert_eq!(
    Some(value.to_vec()),
    app.contract_storage(&contract_addr).get(key)
);

prefixed_storage

Methods prefixed_storage and prefixed_storage_mut simplify access to keys having the same single prefix. In the example below, the common prefix (namespace) is the bank. Value is set in line 10 and then retrieved in line 14.

use cw_multi_test::App;
 
let mut app = App::default();
 
let key = b"key";
let value = b"value";
 
let namespace = b"bank";
 
app.prefixed_storage_mut(namespace).set(key, value);
 
assert_eq!(
    Some(value.to_vec()),
    app.prefixed_storage(namespace).get(key)
);

prefixed_multilevel_storage

If there is a need to access keys having multiple namespaces (multilevel prefixes), then methods prefixed_multilevel_storage and prefixed_multilevel_storage_mut should be used. The following example sets a value for a key having two levels of prefixes (line 10) and then the same value is read in line 14.

use cw_multi_test::App;
 
let mut app = App::default();
 
let key = b"key";
let value = b"value";
 
let namespaces = &[b"my-module".as_slice(), b"my-bank".as_slice()];
 
app.prefixed_multilevel_storage_mut(namespaces).set(key, value);
 
assert_eq!(
    Some(value.to_vec()),
    app.prefixed_multilevel_storage(namespaces).get(key)
);