Contract
Use the contract
(opens in a new tab) macro to generate
contract messages
Use contract
(opens in a new tab) macro only on top of
struct impl blocks
Attributes
List of attributes supported by
contract
(opens in a new tab) macro:
Usage
pub struct Contract;
#[cw_serde]
pub struct SomeResponse;
#[contract]
impl Contract {
pub const fn new() -> Self {
Self
}
#[sv::msg(instantiate)]
fn instantiate(&self, ctx: InstantiateCtx) -> StdResult<Response> {
Ok(Response::new())
}
#[sv::msg(exec)]
fn some_exec(&self, ctx: ExecCtx) -> StdResult<Response> {
Ok(Response::new())
}
#[sv::msg(query)]
fn some_query(&self, ctx: QueryCtx) -> StdResult<SomeResponse> {
Ok(SomeResponse)
}
#[sv::msg(sudo)]
fn some_sudo(&self, ctx: SudoCtx) -> StdResult<Response> {
Ok(Response::new())
}
#[sv::msg(migrate)]
fn some_migrate(&self, ctx: MigrateCtx) -> StdResult<Response> {
Ok(Response::new())
}
#[sv::msg(reply)]
fn some_reply(&self, ctx: ReplyCtx, reply: Reply) -> StdResult<Response> {
Ok(Response::new())
}
}
We define our messages signatures by marking the appropriate methods with the
sv::msg
attribute.
The impl
block must contain the attributeless new
method for the generated dispatch
to work
properly.
Custom types
You can construct your contract to work with some specific custom types with the
sv::custom
.
Generic types
Sylvia contracts can be reused as a state by other contracts. This way you could expand functionality of one contract with another one. In such a case you might want to present a possibility for the user to use it with types that suits their purpose. You can do that by defining generics on your contract.
use cw_storage_plus::Item;
pub struct Contract<ExecParamT, FieldT> {
field: Item<FieldT>,
_phantom: PhantomData<ExecParamT>,
}
#[cw_serde]
pub struct SomeResponse;
#[contract]
impl<ExecParamT, FieldT> Contract<ExecParamT, FieldT>
where
ExecParamT: CustomMsg + 'static,
FieldT: 'static,
{
pub const fn new() -> Self {
Self {
field: Item::new("field"),
_phantom: PhantomData,
}
}
#[sv::msg(instantiate)]
fn instantiate(&self, ctx: InstantiateCtx) -> StdResult<Response> {
Ok(Response::new())
}
#[sv::msg(exec)]
fn some_exec(&self, ctx: ExecCtx, param: ExecParamT) -> StdResult<Response> {
Ok(Response::new())
}
}
This is a standard way to create generic structs in Rust. Two important things to mention are:
- Rust will complain that generics used in method signatures are unused. You have to create a
std::marker::PhantomData
field to silence this error. In case a contract uses multiple generic types, simply wrap them in a tuplePhantomData<(T1, T2)>
. - If generic types are used as part of the method signature, generated messages will require them to
fulfill their trait bounds. In most cases it's enough to add the
sylvia::types::CustomMsg + \'static
bounds.
Forwarding attributes to fields
The contract
(opens in a new tab) macro can forward
attributes to the fields of the messages.
#[sv::msg(instantiate)]
fn instantiate(
&self,
ctx: InstantiateCtx,
#[serde(default)] value: String,
) -> StdResult<Response> {
Ok(Response::new())
}
The output of the above code will be:
#[cw_serde]
pub struct InstantiateMsg {
#[serde(default)]
pub value: String,
}
Good practices
Prefer generic custom types
The sv::custom
attribute is not mandatory, as by default contract macro will
use Empty
(opens in a new tab) type in place of
custom types. It has a downside that the contract always expects the
Empty
(opens in a new tab) custom messages, and
it makes testing it in the MultiTest environment with different custom
messages tricky. We recommend that if your contract is meant to be chain agnostic, define it with a
generic custom message and custom query.
pub struct Contract<CMsgT, CQueryT> {
_phantom: PhantomData<(CMsgT, CQueryT)>,
}
#[contract]
#[sv::custom(msg=CMsgT, query=CQueryT)]
impl<CMsgT, CQueryT> Contract<CMsgT, CQueryT>
where
CMsgT: 'static + CustomMsg,
CQueryT: 'static + CustomQuery,
{
pub fn new() -> Self {
Self {
_phantom: PhantomData,
}
}
#[sv::msg(instantiate)]
fn instantiate(&self, ctx: InstantiateCtx<CQueryT>) -> StdResult<Response<CMsgT>> {
Ok(Response::new())
}
}