CosmWasm MultiTest 0.18 released!

Dariusz Depta
CosmWasm
Published in
5 min readNov 15, 2023

--

Photo by Sigmund on Unsplash

This week, a new version of CosmWasm MultiTest has been released!
We are excited to share some details about the recent updates in version 0.18 and would like you to try them out for yourself.

Enhancements in version 0.18 of CosmWasm MultiTest include:

  • WasmMsg::Instantiate2 message handling
  • WasmQuery::CodeInfo message handling
  • Custom checksum generator support
  • Custom address generator extension
  • Wasm trait extension
  • Contract code duplication

WasmMsg::Instantiate2 message handling

Support for Instantiate2 message was introduced in version 1.2 of cosmwasm-std. Since then, it has been possible to deploy contracts with predictable addresses. To learn more about the pros and cons of using Instantiate2 message read Dev Note #3: Limitations of Instantiate2 and how to deal with them.
CosmWasm MultiTest 0.18 supports WasmMsg::Instantiate2 message, too. Now instantiating contracts with predictable addresses can be done also in tests, just as shown below:

// prepare instantiate message
let msg = WasmMsg::Instantiate2 {
admin: None,
code_id: ... // provide contract code id,
msg: ... // provide initialization message,
funds: vec![],
label: ... // provide a label,
salt: [1, 2, 3, 4, 5, 6].as_slice().into(),
};

// instantiate contract
let res = app.execute(sender, msg.into()).unwrap();

// parse the response
let parsed = parse_instantiate_response_data(res.data.unwrap()
.as_slice())
.unwrap();

// do something with the returned contract address
println!("{}", parsed.contract_address);

The default implementation in MultiTest generates predictable contract addresses that rely only on provided salt. So, changing the salt value will produce a different contract address. Instantiating multiple contract with the same salt will fail, while all contracts would have the same address. In tests, users should never make any assumptions about what the generated addresses look like. In some cases, however, it is desirable to have an influence on how the contract address is generated and in wah format. This case is described in details in section Custom address generator extension.

WasmQuery::CodeInfo message handling

WasmQuery::CodeInfo message was introduced in cosmwasm-std 1.2. CodeInfo message is used to query metadata of the contract’s code, which contains contract’s code identifier, address of the entity that stored the code, and the checksum of the original contract’s Wasm binary.
MultiTest supports this message since version 0.17, but in MultiTest, we are not dealing with the physical Wasm binary of the contract, so to calculate the checksum, we have used an internal checksum generator. Since version 0.18 of MultiTest, there is a way to provide a custom checksum generator described in details in the next section. The example below illustrates the common scenario in which WasmQuery::CodeInfo is used.

// example contract implementation
mod echo {

// entry points are not shown here

pub fn contract() -> Box<dyn Contract<Empty>> {
// this function should return an example contract
}
}

// create the application
let mut app = App::default();

// store an example contract
let code_id = app.store_code(echo::contract());

// query contract code info
let request = WasmQuery::CodeInfo { code_id };
let code_info_response = app.wrap()
.query(request.into())
.unwrap();

// do something with the resulting code info response

Custom checksum generator support

Although the default implementation of the checksum generator for contract code in WasmKeeper is sufficient for most use cases (see WasmQuery::CodeInfo message handling section), it is now possible to provide its custom implementation. Custom checksum generators need to implement ChecksumGenerator trait, which boils down to a single function definition named checksum:

pub trait ChecksumGenerator {
fn checksum(&self, creator: &Addr, code_id: u64) -> HexBinary;
}

During checksum generation, WasmKeeper passes two arguments to checksum function: the address of the creator of the contract’s code and the code identifier of the contract. This function in turn, returns HexBinary containing the calculated checksum. WasmKeeper makes no assumptions about the content of the checksum and the algorithm used to calculate it.
Having the implementation of ChecksumGenerator trait ready, the custom checksum generator should be provided to WasmKeeper, by calling with_checksum_generator function, as shown below:

// prepare the implementation of trait ChecksumGenerator
// for custom checksum generator, named for example MyChecksumGenerator

// prepare wasm keeper with custom checksum generator
let wasm_keeper: WasmKeeper<Empty, Empty> = WasmKeeper::default()
.with_checksum_generator(MyChecksumGenerator);

// prepare application with wasm keeper having custom checkum generator
let mut app = AppBuilder::default()
.with_wasm(wasm_keeper)
.build(|_, _, _| {});

// store some contract in application

// assuming there is a contract with code_id stored,
// to get its code info metadata, just call:
let code_info_response = app
.wrap()
.query_wasm_code_info(code_id)
.unwrap();

Custom address generator extension

In tests before CosmWasm Multitest 0.18, the instantiated contracts were assigned addresses generated based simply on the contract’s instance identifier, and this functionality could have been changed by providing custom address generator. Multitest 0.18 extends the AddressGenerator trait to support more ways the contract addresses can be generated in specific use cases. Current AddressGenerator trait looks like this:

pub trait AddressGenerator {
#[deprecated]
fn next_address(&self, storage: &mut dyn Storage) -> Addr {
// provided default implementation to support old tests
}

fn contract_address(
&self,
api: &dyn Api,
storage: &mut dyn Storage,
code_id: u64,
instance_id: u64,
) -> AnyResult<Addr> {
// provided default implementation
}

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> {
// provided default implementation
}
}

The old next_address function is deprecated since version 0.18.0 and will be removed in version 1.0.0. Two new functions are called by WasmKeeper while generating contract addresses. Function contract_address is called when instantiating contract with WasmMsg::Instantiate message, and the predictable_contract_address function is called when instantiating contract using WasmMsg::Instantiate2 message. Compared to next_address function, the new ones take a bunch of new arguments that allow to generate contract address in a more sophisticated way, like in Bech32 format.

Wasm trait extension

From the very beginning, CosmWasm MultiTest provides default implementation for Wasm trait named WasmKeeper. WasmKeeper takes care of all the complicated stuff like storing contract code, instantiating contracts, executing messages, processing queries, and many more. It’s not an easy task to replace the WasmKeeper, but to keep MultiTest modular and to let users to provide their own implementation of the Wasm trait, we have extended it by adding functions that were previously used only internally by App. Now it is possible to provide a custom implementation of Wasm trait, used later by App, like in the following example:

// prepare the implementation of Wasm trait 
// for custom wasm keeper, named for example MyWasmKeeper

// create the custom wasm keeper
let my_wasm_keeper = MyWasmKeeper::default();

// prepare the application builder
let app_builder = AppBuilder::default();

// build the application with custom wasm keeper
let mut app = app_builder.with_wasm(my_wasm_keeper).build(|_, _, _| {});

// use the application with custom wasm keeper

Contract code duplication

We have added the possibility to easily duplicate contract code during testing. Now, the App provides a function named duplicate_code that creates a copy of the contract’s code. Having the identifier of the copy of the contract code, new contracts may be instantiated and used exactly as the originals. The behaviour of instantiated contracts will be exactly the same.
An example of using duplicate_code function is shown below:

// example contract implementation
mod echo {

// entry points are not shown here

pub fn contract() -> Box<dyn Contract<Empty>> {
// this function should return an example contract
}
}

// create the application
let mut app = App::default();

// store an example contract that will be later duplicated
let code_id = app.store_code(echo::contract());

// duplicate contract code
let new_code_id = app.duplicate_code(code_id).unwrap();

// instantiate contract using new_code_id

--

--