Generated MultiTest helpers

đź’ˇ

The macros generate MultiTest helpers only if the mt feature flag is enabled. We recommend enabling it in the dev-dependencies.

The contract and interface macros generate QoL utilities to test your contract with the MultiTest.

đź’ˇ
The MultiTest helpers are generated in the sv::mt module.

Code generation

Most of the code is generated only by the contract macro. To see the code generated by the interface macro skip to the proxy trait section.

CodeId

The sole purpose of the CodeId is to act as a proxy for instantiating the contract instance.

pub struct CodeId<'app, MtApp> {
    code_id: u64,
    app: &'app sylvia::multitest::App<MtApp>,
    _phantom: std::marker::PhantomData<()>,
}
impl<'app, BankT, ApiT, StorageT, CustomT, StakingT, DistrT, IbcT, GovT>
    CodeId<
        'app,
        sylvia::cw_multi_test::App<
            BankT,
            ApiT,
            StorageT,
            CustomT,
            sylvia::cw_multi_test::WasmKeeper<sylvia::cw_std::Empty, sylvia::cw_std::Empty>,
            StakingT,
            DistrT,
            IbcT,
            GovT,
        >,
    >
where
    BankT: sylvia::cw_multi_test::Bank,
    ApiT: sylvia::cw_std::Api,
    StorageT: sylvia::cw_std::Storage,
    CustomT: sylvia::cw_multi_test::Module<
        ExecT = sylvia::cw_std::Empty,
        QueryT = sylvia::cw_std::Empty,
    >,
    StakingT: sylvia::cw_multi_test::Staking,
    DistrT: sylvia::cw_multi_test::Distribution,
    IbcT: sylvia::cw_multi_test::Ibc,
    GovT: sylvia::cw_multi_test::Gov,
{
    pub fn store_code(
        app: &'app sylvia::multitest::App<
            sylvia::cw_multi_test::App<
                BankT,
                ApiT,
                StorageT,
                CustomT,
                sylvia::cw_multi_test::WasmKeeper<
                    sylvia::cw_std::Empty,
                    sylvia::cw_std::Empty,
                >,
                StakingT,
                DistrT,
                IbcT,
                GovT,
            >,
        >,
    ) -> Self {
        let code_id = app.app_mut().store_code(Box::new(CounterContract::new()));
        Self {
            code_id,
            app,
            _phantom: std::marker::PhantomData::default(),
        }
    }
    pub fn code_id(&self) -> u64 {
        self.code_id
    }
    pub fn instantiate(
        &self,
        mutable: bool,
    ) -> InstantiateProxy<
        '_,
        'app,
        sylvia::cw_multi_test::App<
            BankT,
            ApiT,
            StorageT,
            CustomT,
            sylvia::cw_multi_test::WasmKeeper<sylvia::cw_std::Empty, sylvia::cw_std::Empty>,
            StakingT,
            DistrT,
            IbcT,
            GovT,
        >,
    > {
        let msg = InstantiateMsg { mutable };
        InstantiateProxy::<_> {
            code_id: self,
            funds: &[],
            label: "Contract",
            admin: None,
            salt: None,
            msg,
        }
    }
}

The CodeId structure imitates the id of a contracts binary uploaded to the actual blockchain. It links the code id with the App on which the contract is stored. Call the instantiate method and pass to it parameters you would pass to the InstantiateMsg to get the InstantiateProxy instance.

InstantiateProxy

This structure acts as an intermediate step in instantiating the contract.

pub struct InstantiateProxy<'proxy, 'app, MtApp> {
    code_id: &'proxy CodeId<'app, MtApp>,
    funds: &'proxy [sylvia::cw_std::Coin],
    label: &'proxy str,
    admin: Option<String>,
    salt: Option<&'proxy [u8]>,
    msg: InstantiateMsg,
}
impl<'proxy, 'app, MtApp> InstantiateProxy<'proxy, 'app, MtApp>
where
    MtApp: Executor<sylvia::cw_std::Empty>,
{
    pub fn with_funds(self, funds: &'proxy [sylvia::cw_std::Coin]) -> Self {
        Self { funds, ..self }
    }
    pub fn with_label(self, label: &'proxy str) -> Self {
        Self { label, ..self }
    }
    pub fn with_admin<'s>(self, admin: impl Into<Option<&'s str>>) -> Self {
        let admin = admin.into().map(str::to_owned);
        Self { admin, ..self }
    }
    pub fn with_salt(self, salt: impl Into<Option<&'proxy [u8]>>) -> Self {
        let salt = salt.into();
        Self { salt, ..self }
    }
    #[track_caller]
    pub fn call(
        self,
        sender: &sylvia::cw_std::Addr,
    ) -> Result<
        sylvia::multitest::Proxy<'app, MtApp, CounterContract>,
        sylvia::cw_std::StdError,
    > {
        let Self {
            code_id,
            funds,
            label,
            admin,
            salt,
            msg,
        } = self;
        match salt {
            Some(salt) => {
                let err = sylvia::cw_std::StdError::generic_err("`with_salt` was called, but it requires `cosmwasm_1_2` feature enabled. Consider removing `with_salt` or adding the `cosmwasm_1_2` feature.");
                Err(Into::into(err))
            }
            None => (*code_id.app)
                .app_mut()
                .instantiate_contract(
                    code_id.code_id,
                    sender.clone(),
                    &msg,
                    funds,
                    label,
                    admin,
                )
                .map_err(|err| err.downcast().unwrap())
                .map(|addr| sylvia::multitest::Proxy {
                    contract_addr: addr,
                    app: code_id.app,
                    _phantom: std::marker::PhantomData::default(),
                }),
        }
    }
}

At this stage of initializing the contract, we can set the funds, label, and admins of our contract, as well as the salt for the instantiate2 (opens in a new tab) call.

đź’ˇ

To use the instantiate2 (opens in a new tab) enable the cosmwasm_1_2 feature flag.

The call method instantiates the contract on the App and return the Proxy to communicate with it.

Proxy trait

By itself the Proxy stores the address of the contract as well as the reference to the App.

pub trait CounterContractProxy<'app, MtApp> {
    fn some_exec(
        &self,
        addr: String,
    ) -> sylvia::multitest::ExecProxy<
        sylvia::cw_std::StdError,
        <CounterContract as sylvia::types::ContractApi>::Exec,
        MtApp,
        sylvia::cw_std::Empty,
    >;
    #[track_caller]
    fn some_migrate(
        &self,
        addr: String,
    ) -> sylvia::multitest::MigrateProxy<
        sylvia::cw_std::StdError,
        <CounterContract as sylvia::types::ContractApi>::Migrate,
        MtApp,
        sylvia::cw_std::Empty,
    >;
    fn some_query(&self, addr: String) -> Result<SomeResponse, sylvia::cw_std::StdError>;
    fn some_sudo(
        &self,
        addr: String,
    ) -> Result<sylvia::cw_multi_test::AppResponse, sylvia::cw_std::StdError>;
}
impl<'app, BankT, ApiT, StorageT, CustomT, WasmT, StakingT, DistrT, IbcT, GovT>
    CounterContractProxy<
        'app,
        sylvia::cw_multi_test::App<
            BankT,
            ApiT,
            StorageT,
            CustomT,
            WasmT,
            StakingT,
            DistrT,
            IbcT,
            GovT,
        >,
    >
    for sylvia::multitest::Proxy<
        'app,
        sylvia::cw_multi_test::App<
            BankT,
            ApiT,
            StorageT,
            CustomT,
            WasmT,
            StakingT,
            DistrT,
            IbcT,
            GovT,
        >,
        CounterContract,
    >
where
    CustomT: sylvia::cw_multi_test::Module,
    CustomT::ExecT: sylvia::types::CustomMsg + 'static,
    CustomT::QueryT: sylvia::types::CustomQuery + 'static,
    WasmT: sylvia::cw_multi_test::Wasm<CustomT::ExecT, CustomT::QueryT>,
    BankT: sylvia::cw_multi_test::Bank,
    ApiT: sylvia::cw_std::Api,
    StorageT: sylvia::cw_std::Storage,
    StakingT: sylvia::cw_multi_test::Staking,
    DistrT: sylvia::cw_multi_test::Distribution,
    IbcT: sylvia::cw_multi_test::Ibc,
    GovT: sylvia::cw_multi_test::Gov,
    sylvia::cw_multi_test::App<
        BankT,
        ApiT,
        StorageT,
        CustomT,
        WasmT,
        StakingT,
        DistrT,
        IbcT,
        GovT,
    >: Executor<sylvia::cw_std::Empty>,
{
    #[track_caller]
    fn some_exec(
        &self,
        addr: String,
    ) -> sylvia::multitest::ExecProxy<
        sylvia::cw_std::StdError,
        <CounterContract as sylvia::types::ContractApi>::Exec,
        sylvia::cw_multi_test::App<
            BankT,
            ApiT,
            StorageT,
            CustomT,
            WasmT,
            StakingT,
            DistrT,
            IbcT,
            GovT,
        >,
        sylvia::cw_std::Empty,
    > {
        let msg = <CounterContract as sylvia::types::ContractApi>::Exec::some_exec(addr);
        sylvia::multitest::ExecProxy::new(&self.contract_addr, msg, &self.app)
    }
    #[track_caller]
    fn some_migrate(
        &self,
        addr: String,
    ) -> sylvia::multitest::MigrateProxy<
        sylvia::cw_std::StdError,
        <CounterContract as sylvia::types::ContractApi>::Migrate,
        sylvia::cw_multi_test::App<
            BankT,
            ApiT,
            StorageT,
            CustomT,
            WasmT,
            StakingT,
            DistrT,
            IbcT,
            GovT,
        >,
        sylvia::cw_std::Empty,
    > {
        let msg = <CounterContract as sylvia::types::ContractApi>::Migrate::new(addr);
        sylvia::multitest::MigrateProxy::new(&self.contract_addr, msg, &self.app)
    }
    fn some_query(&self, addr: String) -> Result<SomeResponse, sylvia::cw_std::StdError> {
        let msg = <CounterContract as sylvia::types::ContractApi>::Query::some_query(addr);
        (*self.app)
            .querier()
            .query_wasm_smart(self.contract_addr.clone(), &msg)
            .map_err(Into::into)
    }
    fn some_sudo(
        &self,
        addr: String,
    ) -> Result<sylvia::cw_multi_test::AppResponse, sylvia::cw_std::StdError> {
        let msg = <CounterContract as sylvia::types::ContractApi>::Sudo::some_sudo(addr);
        (*self.app)
            .app_mut()
            .wasm_sudo(self.contract_addr.clone(), &msg)
            .map_err(|err| err.downcast().unwrap())
    }
}

The proxy trait declares methods marked with the sv::msg attribute, and found in the contract impl block

The trait is then implemented on the Proxy generic over the contract type. Implementation of the methods instantiates appropriate messages and sends them to the App so that we don't have to do it manually.

Contract implementation

The store_code (opens in a new tab) expects a type to implement the Contract (opens in a new tab) trait. The contract macro covers that by implementing this trait on our contract type.

impl sylvia::cw_multi_test::Contract<sylvia::cw_std::Empty, sylvia::cw_std::Empty>
    for CounterContract
{
    fn execute(
        &self,
        deps: sylvia::cw_std::DepsMut<sylvia::cw_std::Empty>,
        env: sylvia::cw_std::Env,
        info: sylvia::cw_std::MessageInfo,
        msg: Vec<u8>,
    ) -> sylvia::anyhow::Result<sylvia::cw_std::Response<sylvia::cw_std::Empty>>
    {
        sylvia::cw_std::from_json::<
            <CounterContract as sylvia::types::ContractApi>::ContractExec,
        >(&msg)?
        .dispatch(self, (deps, env, info))
        .map_err(Into::into)
    }
    fn instantiate(
        &self,
        deps: sylvia::cw_std::DepsMut<sylvia::cw_std::Empty>,
        env: sylvia::cw_std::Env,
        info: sylvia::cw_std::MessageInfo,
        msg: Vec<u8>,
    ) -> sylvia::anyhow::Result<sylvia::cw_std::Response<sylvia::cw_std::Empty>>
    {
        sylvia::cw_std::from_json::<
            <CounterContract as sylvia::types::ContractApi>::Instantiate,
        >(&msg)?
        .dispatch(self, (deps, env, info))
        .map_err(Into::into)
    }
    fn query(
        &self,
        deps: sylvia::cw_std::Deps<sylvia::cw_std::Empty>,
        env: sylvia::cw_std::Env,
        msg: Vec<u8>,
    ) -> sylvia::anyhow::Result<sylvia::cw_std::Binary> {
        sylvia::cw_std::from_json::<
            <CounterContract as sylvia::types::ContractApi>::ContractQuery,
        >(&msg)?
        .dispatch(self, (deps, env))
        .map_err(Into::into)
    }
    fn sudo(
        &self,
        deps: sylvia::cw_std::DepsMut<sylvia::cw_std::Empty>,
        env: sylvia::cw_std::Env,
        msg: Vec<u8>,
    ) -> sylvia::anyhow::Result<sylvia::cw_std::Response<sylvia::cw_std::Empty>>
    {
        sylvia::cw_std::from_json::<
            <CounterContract as sylvia::types::ContractApi>::ContractSudo,
        >(&msg)?
        .dispatch(self, (deps, env))
        .map_err(Into::into)
    }
    fn reply(
        &self,
        deps: sylvia::cw_std::DepsMut<sylvia::cw_std::Empty>,
        env: sylvia::cw_std::Env,
        msg: sylvia::cw_std::Reply,
    ) -> sylvia::anyhow::Result<sylvia::cw_std::Response<sylvia::cw_std::Empty>>
    {
        sylvia::anyhow::bail!("reply not implemented for contract")
    }
    fn migrate(
        &self,
        deps: sylvia::cw_std::DepsMut<sylvia::cw_std::Empty>,
        env: sylvia::cw_std::Env,
        msg: Vec<u8>,
    ) -> sylvia::anyhow::Result<sylvia::cw_std::Response<sylvia::cw_std::Empty>>
    {
        sylvia::cw_std::from_json:: < <CounterContract as sylvia::types::ContractApi> ::Migrate>(&msg)? .dispatch(self,(deps,env)).map_err(Into::into)
    }
}
đź’ˇ

By default, the instantiate, exec and query methods will dispatch the message to the proper methods in our contract. The sudo, reply, and migrate by default calls bail! (opens in a new tab) unless the respective method is specified in the contract impl block.