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 trait | Description |
---|---|
get | Returns a value associated with a specified key. |
set | Sets a new value for a specified key. |
remove | Removes an entry with a specified key. |
range | Iterates over a set of key/value pairs, either forwards or backwards. |
range_keys | Iterates over a set of keys, either forwards or backwards. |
range_values | Iterates 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 App | Access | Purpose |
---|---|---|
storage | R | Read the value associated with any key. |
storage_mut | R/W | Read or write the value associated with any key. |
contract_storage | R | Read the value associated with any key but only for specified the contract address. |
contract_storage_mut | R/W | Read or write the value associated with any key but only for specified the contract address. |
prefixed_storage | R | Read the value associated with any key having a single prefix. |
prefixed_storage_mut | R/W | Read or write the value associated with any key having a single prefix. |
prefixed_multilevel_storage | R | Read the value associated with any key having multiple prefixes. |
prefixed_multilevel_storage_mut | R/W | Read or write the value associated with any key having a multiple prefixes. |
R
- read-onlyR/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)
);