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.
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.