Generated message types

Sylvia macros generate CosmWasm messages from methods marked with sv::msg attributes.

Messages are generated either as struct, for instantiate and migrate, or enum in case of the rest of the types.

Each message's implementation contains a dispatch method. Calling the msg.dispatch(...) method on a struct message forwards the message's fields to the single method as arguments, i.e. structure's fields become the method's arguments. For the enum messages, the respective method will be called depending on the enum variant. This is described in the example below

Contract messages

The following code:

use cosmwasm_schema::cw_serde;
use sylvia::contract;
use sylvia::cw_std::{Response, StdResult};
use sylvia::types::{ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, SudoCtx};
 
#[cw_serde]
pub struct SomeResponse;
 
pub struct CounterContract;
 
#[contract]
impl CounterContract {
    pub const fn new() -> Self {
        Self
    }
 
    #[sv::msg(instantiate)]
    fn instantiate(&self, ctx: InstantiateCtx, mutable: bool) -> StdResult<Response> {
        Ok(Response::new())
    }
 
    #[sv::msg(exec)]
    fn some_exec(&self, ctx: ExecCtx, addr: String) -> StdResult<Response> {
        Ok(Response::new())
    }
 
    #[sv::msg(query)]
    fn some_query(&self, ctx: QueryCtx, addr: String) -> StdResult<SomeResponse> {
        Ok(SomeResponse)
    }
 
    #[sv::msg(sudo)]
    fn some_sudo(&self, ctx: SudoCtx, addr: String) -> StdResult<Response> {
        Ok(Response::new())
    }
 
    #[sv::msg(migrate)]
    fn some_migrate(&self, ctx: MigrateCtx, addr: String) -> StdResult<Response> {
        Ok(Response::new())
    }
}

generates the following messages for every respective entrypoint:

#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(
    sylvia::serde::Serialize,
    sylvia::serde::Deserialize,
    Clone,
    Debug,
    PartialEq,
    sylvia::schemars::JsonSchema,
)]
#[serde(rename_all = "snake_case")]
pub struct InstantiateMsg {
    pub mutable: bool,
}
impl InstantiateMsg {
    pub fn new(mutable: bool) -> Self {
        Self { mutable }
    }
    pub fn dispatch(
        self,
        contract: &CounterContract,
        ctx: (
            sylvia::cw_std::DepsMut<sylvia::cw_std::Empty>,
            sylvia::cw_std::Env,
            sylvia::cw_std::MessageInfo,
        ),
    ) -> StdResult<Response> {
        let Self { mutable } = self;
        contract
            .instantiate(Into::into(ctx), mutable)
            .map_err(Into::into)
    }
}
💡

Notice that the method parameters are used as fields of appropriate messages and their variants.

The contract macro also generates wrapper messages for exec, query and sudo. Their goal is to wrap respective messages, like ExecMsg, from both the contract and interfaces implemented on it, which are used as the main messages of the contract.

💡

Use ContractExecMsg/ContractQueryMsg/ContractSudoMsg in hand made entry points and in write_api! (opens in a new tab) macro.

#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(sylvia::serde::Serialize, Clone, Debug, PartialEq, sylvia::schemars::JsonSchema)]
#[serde(rename_all = "snake_case", untagged)]
pub enum ContractExecMsg {
    Interface(<interface::sv::Api as sylvia::types::InterfaceApi>::Exec),
    CounterContract(ExecMsg),
}
impl ContractExecMsg {
    pub fn dispatch(
        self,
        contract: &CounterContract,
        ctx: (
            sylvia::cw_std::DepsMut<sylvia::cw_std::Empty>,
            sylvia::cw_std::Env,
            sylvia::cw_std::MessageInfo,
        ),
    ) -> std::result::Result<
        sylvia::cw_std::Response<sylvia::cw_std::Empty>,
        sylvia::cw_std::StdError,
    > {
        const _: () = {
            let msgs: [&[&str]; 2usize] =
                [&interface::sv::execute_messages(), &execute_messages()];
            sylvia::utils::assert_no_intersection(msgs);
        };
        match self {
            ContractExecMsg::Interface(msg) => msg.dispatch(contract, Into::into(ctx)),
            ContractExecMsg::CounterContract(msg) => msg.dispatch(contract, ctx),
        }
    }
}

We mark the wrapper message with the serde(untagged) (opens in a new tab) attribute to drop the wrapping variant names so that serialization matches one done for the regular CosmWasm messages.

The interface variants use <interface::sv::Api as sylvia::types::InterfaceApi>::Sudo, which is an accessor meant to simplify the API while using generics in the contract/interface.

Lastly, in dispatch, there is a const block. Its goal is to provide compile time validation that none of the messages overlap between a contract and interfaces.

Interface messages

The generation done by interface macro is much simpler, as Sylvia generates just three types of messages.

We will use the below example interface to generate the messages:

use cosmwasm_std::{Response, StdError};
use sylvia::types::{ExecCtx, QueryCtx, SudoCtx};
 
use sylvia::interface;
 
use super::SomeResponse;
 
#[interface]
pub trait Interface {
    type Error: From<StdError>;
 
    #[sv::msg(exec)]
    fn interface_exec(&self, ctx: ExecCtx, addr: String) -> Result<Response, Self::Error>;
 
    #[sv::msg(query)]
    fn interface_query(&self, ctx: QueryCtx, addr: String)
        -> Result<SomeResponse, Self::Error>;
 
    #[sv::msg(sudo)]
    fn interface_sudo(&self, ctx: SudoCtx, addr: String) -> Result<Response, Self::Error>;
}

And the three generated messages:

#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(
    sylvia::serde::Serialize,
    sylvia::serde::Deserialize,
    Clone,
    Debug,
    PartialEq,
    sylvia::schemars::JsonSchema,
)]
#[serde(rename_all = "snake_case")]
pub enum InterfaceExecMsg {
    InterfaceExec { addr: String },
}
pub type ExecMsg = InterfaceExecMsg;
impl InterfaceExecMsg {
    pub fn dispatch<ContractT>(
        self,
        contract: &ContractT,
        ctx: (
            sylvia::cw_std::DepsMut<sylvia::cw_std::Empty>,
            sylvia::cw_std::Env,
            sylvia::cw_std::MessageInfo,
        ),
    ) -> std::result::Result<sylvia::cw_std::Response<sylvia::cw_std::Empty>, ContractT::Error>
    where
        ContractT: Interface,
    {
        use InterfaceExecMsg::*;
        match self {
            InterfaceExec { addr: field1 } => contract
                .interface_exec(Into::into(ctx), field1)
                .map_err(Into::into),
        }
    }
    pub fn interface_exec(addr: String) -> Self {
        Self::InterfaceExec { addr }
    }
}

Although interface messages are almost the same as contract ones, some slight differences exist.

Notice that the messages are prefixed with the interfaces name: InterfaceSudoMsg, while in case of the contract the name was just SudoMsg. This prefix is required. Otherwise, ContractSudoMsg variants names would overlap during the schema generation, and all but one would be lost. However, a simplified alias is generated for them, so it's possible to access them like, e.g., SudoMsg. The same rule applies to the exec and query messages.

Since multiple contracts might use these messages, the dispatch is generic over the ContractT type implementing the interface. The error type returned in the dispatch is the associated type defined on the interface.