Contract structure
Sylvia contracts are designed using the actor model. An actor is a contract struct that can store a state and define a behavior.
use cw_storey::containers::Item;
pub struct CounterContract { pub count: Item<u64>, }
In Sylvia we keep the state accessors as part of the contract definition. The accessors are
storey
or cw_storage_plus
primitives.
Let's take a look at the behavior implementation.
use cosmwasm_schema::cw_serde;
use cw_storey::CwStorage;
use sylvia::contract;
use sylvia::cw_std::{Response, StdError, StdResult};
use sylvia::types::{ExecCtx, InstantiateCtx, QueryCtx};
#[cw_serde]
pub struct CountResponse {
pub count: u64,
}
#[cfg_attr(not(feature = "library"), sylvia::entry_points)]
#[contract]
impl CounterContract {
pub const fn new() -> Self {
Self {
count: Item::new(0),
}
}
#[sv::msg(instantiate)]
fn instantiate(&self, ctx: InstantiateCtx) -> StdResult<Response> {
self.count
.access(&mut CwStorage(ctx.deps.storage))
.set(&0)?;
Ok(Response::new())
}
#[sv::msg(exec)]
fn increment(&self, ctx: ExecCtx) -> StdResult<Response> {
let mut storage = CwStorage(ctx.deps.storage);
let mut accessor = self.count.access(&mut storage);
let count = accessor
.get()?
.ok_or_else(|| StdError::generic_err("Count not instantiated yet"))?;
accessor.set(&(count + 1))?;
Ok(Response::new())
}
#[sv::msg(query)]
fn count(&self, ctx: QueryCtx) -> StdResult<CountResponse> {
let count = self
.count
.access(&CwStorage(ctx.deps.storage))
.get()?
.ok_or_else(|| StdError::generic_err("Count not instantiated yet"))?;
Ok(CountResponse { count })
}
}
In the first two lines, we see the usage of two macros:
-
entry_points
- Generates entry points of the contract. By default it will generateinstantiate
,execute
andquery
entry points. The other ones,migrate
,reply
, andsudo
, are generated if a behavior related to them is defined in theimpl
block.This macro is wrapped in
cfg_attr
statement to be compiled only iflibrary
feature flag is not enabled. This way, other users who might want to use this contract in theirs won't get an entry point collision. -
contract
- Parses every method inside theimpl
block marked with the[sv::msg(...)]
attribute and create proper messages and utilities like helpers forMultiTest
.
This simple example also has the sv::msg(...)
attributes. Sylvia
macros distinguish the if message should be generated from the marked method and of what type.
CosmWasm contract requires the instantiate
message, and it is mandatory to specify it for the
contract
macro. We have to provide it with the proper context type:
InstantiateCtx
(opens in a new tab). Another
mandatory method is the new
, as contract fields are out of scope for the
contract
macro, and otherwise we wouldn't be able to create the contract
object in message dispatching.
Context gives us access to the blockchain state, information about our contract, and the sender of
the message. We return the
StdResult
(opens in a new tab) which uses
standard CosmWasm error
StdError
(opens in a new tab). It's generic over
Response
(opens in a new tab).
The template contract also contains a query and an exec messages. Each type of message in CosmWasm
supports different contexts. F.e. the QueryCtx
exposes to the user an
immutable Deps
(opens in a new tab) as by design,
queries should never mutate the state. This is not the case for the ExecCtx
and InstantiateCtx
which exposes the
DepsMut
(opens in a new tab).
Feel free expanding the macro now and seeing what Sylvia generates. It might be overwhelming, as
there will be a lot of things generated that seem not relevant to our code, so for the bare minimum,
check the InstantiateMsg
and its
impl
block.
Sylvia doesn't generate anything magical, but regular CosmWasm contract types customized based on the provided methods and attributes. This means that the Sylvia contract is fully interoperational with the standard CosmWasm contract.