CW Multi Test

Cw Multi Test is a rust-based test framework that allows developers to test for multi-contract interactions without having to dispatch messages, storage and other variables themselves. With cw-orchestrator, most of the cw-multi-test logic is abstracted away, but you can still learn more about the framework here.

⚠️ Custom-typed contracts: cw-orch’s integration with cw-multi-test is not compatible with custom-typed contracts.

Custom-typed contracts are contracts where the endpoints return Response<M> where Mis not cosmwasm_std::Empty OR where the Deps and DepsMut endpoint arguments have a non Empty generic parameter specified.

You can still test and deploy your contracts on actual nodes (testnets, mainnets). To achieve that, don’t specify the wrapper function in the Uploadable trait implementation on your interface.

Quick Start

The cw-multi-test integration comes at no extra cost for the developer. Creating a test environement in cw-orchestrator that leverages cw-multi-test goes along the lines of:

    use cw_orch::prelude::*;
    use cosmwasm_std::Addr;
    let sender = Addr::unchecked("juno16g2rahf5846rxzp3fwlswy08fz8ccuwk03k57y");

    let mock = Mock::new(&sender);

NOTE: When using cw-multi-test, the addresses ARE NOT validated like on a live chain. Therefore, you can use any string format for designating addresses. For instance,Addr::unchecked("my-first-sender") is a valid cw-multi-test address.

NOTE: When using cw-multi-test, NO gas fees are charged to the sender address.

Interacting with contracts

You can then use the resulting Mock variable to interact with your contracts:

    let contract_counter = CounterContract::new(mock);

    let upload_res = contract_counter.upload();
    upload_res.unwrap();

When executing contracts in a cw-multi-test environment, the messages and sub-messages sent along the Response of an endpoint, will be executed as well. This environment mocks the actual on-chain execution exactly.

  • This environment uses the actual functions of your contract without having to compile them into WASM. When you are calling upload with this environment, no wasm files are included in the test environment. This allows for better debugging of your contract code.

  • You will need to have implemented the wrapper function for interacting the the Mock environment. This function will allow you to “connect” your contract endpoints to your Contract struct. See the dedicated page for more details.

  • NOTE: Keep in mind that cw-multi-test is based solely in rust and that a lot of actual blockchain modules are not mocked in the environment. The main cosmos modules are there (Bank, Staking), but some very useful ones (tokenfactory, ibc) as well as Stargate messages are not supported by the environment.

Cloning

When cloning a cw_multi_test environment, you are not cloning the entire environment, but instead you are creating a new Mock typed variable with the same underlying cw_multi_test::App object reference. This is useful for objects that require to pass the chain as an object rather than by reference. The underlying cw_multi_test::App object is however not clonable.

Bech32

When testing your smart contracts, you may need to use valid bech32 addresses instead of arbitrary strings. Notably Bech32 addresses are a requirment whenever instantiate2 is used in your codebase. Therefore cw-orch allows this behavior using the MockBech32 struct. This structure has the same features as the above described Mock with the following differences:

  • The constructor now takes the bech32 prefix instead of the sender. Under the hood, the environment creates an address that will be used as the default sender for actions executed on that environment:

    let mock = MockBech32::new("<chain-prefix>");
    // For instance: 
    let mock = MockBech32::new("juno");
    // With chain id: 
    let mock = MockBech32::new_with_chain_id("juno", "juno-1");
    // Default sender address for this env
    let sender = mock.sender();
  • To facilitate the creation of additional valid bech32 addresses, we have added an addr_make function. This function uses its string argument to create a new address. That way you can create a new address without having to care about bech32 encoding/decoding yourself:

     let named_sender = mock.addr_make("sender");
     /// This creates a new address with some funds inside of it
     let rich_sender = mock.addr_make_with_balance("sender_with_funds", coins(100_000_000_000_000, "ujuno"));

Snapshot testing

cw-orch provides snapshot testing capabilities to assist you catching breaking changes to your contracts. The Mock::take_storage_snapshot function allows you to dump all the deployed contracts’ storage values into insta.rs that executes snapshot testing. An example application of this feature is to make sure that the storage of your contracts don’t change when migrating a contract. Using this tool, you should have a test that looks something like this:


#[test]
fn storage_stays_the_same(){
    let mock = Mock::new(Addr::unchecked("sender"));

    ... // Upload, instantiate, execute contracts

    // Make sure that the operations have a fixed result
    mock.take_storage_snapshot("mock_snapshot")?;
}

At any point of development, if the storage variables are modified, this test will fail and alert you that you are doing breaking changes to your storage variables. Learn more about the underlying tool in the official documentation.

Additional tools

The Mock test environment allows you to change application variables (such as the balance of an account) using wrappers around the underlying cw_multi_test::App object. Here are some examples of those wrappers in context:

    let new_sender = Addr::unchecked("entirely-new-sender");
    mock.set_balance(&new_sender, coins(100_000, "ujunox"))
        .unwrap();

    // Reuploads as the new sender
    contract_counter.call_as(&new_sender).upload().unwrap();

    // Here the contract_counter sender is again `sender`

    // Sets the new_sender as the definite sender
    contract_counter.set_sender(&new_sender);

    // From now on the contract_counter sender is `new_sender`

Additional customization

As we don’t provide wrappers around each and every functionality that cw-multi-test provides, you can also customize the underlying cw_multi_test::Appobject to your specific needs. In the following example, we create a new validator in the test environment:

    mock.app
        .borrow_mut()
        .init_modules(|router, api, storage| {
            router.staking.add_validator(
                api,
                storage,
                &BlockInfo {
                    height: 16736,
                    time: Timestamp::from_seconds(13345762376),
                    chain_id: "juno-1".to_string(),
                },
                cosmwasm_std::Validator {
                    address: "new-validator-address".to_string(),
                    commission: Decimal::from_str("0.5").unwrap(), // Greedy validator
                    max_commission: Decimal::from_str("1").unwrap(), // Dangerous validator
                    max_change_rate: Decimal::from_str("1").unwrap(), // Very dangerous validator
                },
            )
        })
        .unwrap();