Attribute Macro contract

Source
#[contract]
Expand description

Procedural macro generating messages from contract impl block. Generates instantiate, migrate, reply, sudo, exec and query enum messages to be later used in contract implementation.

§Example usage

pub struct SvContract {
    data: Item<ContractData>,
}

#[sylvia::contract]
#[sv::error(ContractError)]
#[sv::features(replies)]
impl SvContract {
    pub const fn new() -> Self {
        Self {
            data: Item::new("data"),
        }
    }

    #[sv::msg(instantiate)]
    fn instantiate(&self, ctx: InstantiateCtx, admin: Option<String>) -> Result<Response, ContractError> {
    }

    #[sv::msg(exec)]
    fn execute(&self, ctx: ExecCtx) -> Result<Response, ContractError> {
    }

    #[sv::msg(query)]
    fn query(&self, ctx: QueryCtx) -> Result<Response, ContractError> {
    }

    #[sv::msg(migrate)]
    fn migrate(&self, ctx: MigrateCtx) -> Result<Response, ContractError> {
    }

    #[sv::msg(reply)]
    fn reply(&self, ctx: ReplyCtx, result: SubMsgResult, #[sv::payload(raw)] payload: Binary) -> Result<Response, ContractError> {
    }

    #[sv::msg(sudo)]
    fn sudo(&self, ctx: SudoCtx) -> Result<Response, ContractError> {
    }
}

This would generate output like:

pub mod sv {
    #[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 admin: Option<String>,
    }
    impl InstantiateMsg {
        pub fn new(admin: Option<String>) -> Self {
            Self { admin }
        }
        pub fn dispatch(
            self,
            contract: &SvContract,
            ctx: (
                sylvia::cw_std::DepsMut<sylvia::cw_std::Empty>,
                sylvia::cw_std::Env,
                sylvia::cw_std::MessageInfo,
            ),
        ) -> Result<Response, ContractError> {
            let Self { admin } = self;
            contract
                .instantiate(Into::into(ctx), admin)
                .map_err(Into::into)
        }
    }
}

And appropriate messages for exec, query, migrate, reply and sudo variants.

§Attributes

Contract macro supports multiple attributes to customize the behavior of generated messages.

§sv::msg(...)

Message structures are generated based on specific implemented methods attributed with #[sv::msg(msg_type)]. Msg attribute takes as its first argument type of message it is supposed to handle:

  • instantiate - instantiation message handler. There should be always exactly one
  • exec - execute message variant
  • query - query message variant
  • migrate - migrate message variant
  • reply - reply message variant
  • sudo - sudo message variant

In the case of a query, it is possible to pass a second argument which is its ResponseType. This is required in case of aliased results wrapping their ResponseType to properly implement QueryResponses.

pub struct SvContract {
    data: Item<ContractData>,
}

#[sylvia::contract]
#[sv::error(ContractError)]
impl SvContract {
    pub const fn new() -> Self {
        Self {
            data: Item::new("data"),
        }
    }

    #[sv::msg(instantiate)]
    fn instantiate(&self, ctx: InstantiateCtx, admin: Option<String>) -> Result<Response, ContractError> {
        Ok(Response::new())
    }

    #[sv::msg(query, resp=QueryResponse)]
    fn query(&self, ctx: QueryCtx) -> Result<QueryResponse, ContractError> {
        // Some query implementation
    }
}

§sv::custom(msg=..., query=...)

Allows restricting interface to use specific custom message and query types. If used with ExecC and QueryC associated types sv::custom(...) attribute has priority in defining custom types.

§sv::error(error_type)

Allows specifing custom error type for the contract. Default is cosmwasm_std::StdError.

§sv::override_entry_point(entry_point_type=<path_to_entry_point(msg_path)>

Allows overriding default entry point for specific message type. Used in case there is a need to provide some custom functionality inside the entry point. Specified entry point will be used in generated multitest helpers

pub struct SvContract;

#[cw_serde]
pub enum CustomExecMsg {}

#[entry_point]
pub fn execute(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    msg: CustomExecMsg,
) -> StdResult<Response> {
}


#[sylvia::contract]
#[sv::override_entry_point(exec=execute(CustomExecMsg))]
impl SvContract {
}

§sv::messages(path_to_interface)

Used to declare interfaces implemented on the contract. Required for the contract to be able to handle an interface message.

pub mod sv_interface {
    #[sylvia::interface]
    pub trait SvInterface {
        type Error: From<StdError>;
    }
}

impl sv_interface::SvInterface for SvContract {
    type Error = StdError;
}

pub struct SvContract;

#[sylvia::contract]
#[sv::messages(sv_interface)]
impl SvContract {
}

In case an interface has different name than a module in which its defined you have to pass the name of an interface as below:

pub mod interface {
    #[sylvia::interface]
    pub trait SvInterface {
        type Error: From<StdError>;
    }
}

impl interface::SvInterface for SvContract {
    type Error = StdError;
}

pub struct SvContract;

#[sylvia::contract]
#[sv::messages(interface as SvInterface)]
impl SvContract {
}

§sv::msg_attr(msg_type, {...})

This attribute can be used for the whole impl Contract {} block and for every message type: exec, query, sudo, instantiate, migrate and reply. The {...} part will be forwarded as an attribute #[{...}] to the given message type (enum or struct).

§sv::attr({...})

Forwards variant’s attribute to the specific enum’s field in the generated message type. It can be used along with sv::msg(...) and only for message types variants that resolves in an enum field, i.e. exec, query and sudo.

§sv::features(...)

Enables additional features for the contract. Allows user to use features that are considered breaking before the major release.

Currently supported features are:

  • replies - enables better dispatching of reply as well as its auto deserialization. With this feature enabled, user can use additional parameters in the sv::msg attribute like so: #[sv::msg(reply, handlers=[scenario1, scenario2], reply_on=Success)].

    Based on this parameters reply ids will be generated and associated with proper scenario specified by the reply_on parameter.

    User can also specify custom data and payload types that will be auto deserialized from the cosmwasm_std::Binary type.

§sv::payload(raw)

Requires contract to be marked with the sv::features(replies).

Used next to the reply method argument. It disables auto deserialization of the payload argument.

§sv::data(...)

Requires contract to be marked with the sv::features(replies).

Used next to the reply method argument. Based on the passed parameters it enables different behavior:

  • #[sv::data(raw)] - Returns error if the data is None, extracts and forwards Binary if it’s Some.

  • #[sv::data(raw, opt)] - Forwards data as Option<Binary>.

  • #[sv::data(opt)] - Expects type of data in the method signature to be data: Option<T> where T: Deserialize. Tries to deserialize data to type defined in the method signature and forwards it wrapped in the Option. Requires data to be JSON serialized.

If data is:

Some(valid)Some(invalid)None
forwards Some(valid)early returns an error specifying what went wrong with serde error attachedNone - forwards None
  • #[sv::data] - Expects data in the method signature to be data: T where T: Deserialize. Tries to deserialize data to type defined in the method signature and forwards it or returns early an error. Requires data to be JSON serialized.

If data is:

Some(valid)Some(invalid)None
forwards validearly returns error specifying what went wrong with serde error attachedearly returns error specifying the data is missing
  • #[sv::data(instantiate)] - special case for reply to WasmMsg::instantiate and WasmMsg::instantiate2. Tries to deserialize data using parse_instantiate_response_data.

If data is:

Some(valid)Some(invalid)None
extracts and forwards validearly returns error specifying what went wrong with serde error attachedearly returns error specifying the data is missing
  • #[sv::data(instantiate, opt)] - special case for reply to WasmMsg::instantiate and WasmMsg::instantiate2. tries to deserialize data using parse_instantiate_response_data.

if data is:

Some(valid)Some(invalid)None
forwards Some(valid)early returns error specifying what went wrong with serde error attachedForwards None
  • Missing #[sv::data(...)] - In case sv::data is not found Sylvia won’t forward the data argument so the data should be omited in the method signature.