Procedural macros for composable boilerplate-free CosmWasm smart contracts.
Derive contract macro takes care of all necessary boilerplate in CosmWasm smart contracts and provides a more productive development experience. The goal is to generate the repetitive code that you'd write anyway and nothing more, while providing as much flexibility as possible. Any misuse of the macro will result in compile errors and not some hidden or unexpected runtime behavior.
Derive macro can be used for direct contract implementations with #[contract(entry)]
or for contracts that implement interfaces with #[contract_impl(entry, path="some_interface")]
.
An interface implementation should be used over direct implementations if there's any intercontract communication, this allows to include messages of other contracts without any cyclical dependencies as those messages are exported by the interface and as such can be declared in a shared crate which is then included by individual contract crates. This is an extremely common pattern when writing CosmWasm based contracts.
The derive-contract macro supports the following attributes:
| Attribute | Description |
|-------------------|------------------------------------------------------------------------------------------------------------------|
| init | The init
method of the contract. Only one per contract. Can be omitted if not using entry
(in components). |
| execute | The execute
method of the contract. One per execute method. |
| query | The query
method of the contract. One per query method. |
| executeguard | A function marked with this will be called before any execute method execution. Only one per contract (optional). |
| component | Used to include a component. |
| entry | Signals that WASM entry points should be generated for the current contract. |
| path | Specifies a path to a type or a namespace. |
| skip | Used to not include a execute/query of the component. |
| customimpl | Used to provide a custom implementation of a component instead of using the auto generated default trait impl. |
Since their usage is always a fact, we decided to implicitly include the Env
and Extern
types from cosmwasm_std
as parameters to the relevant methods so that they don't need to be specified all the time. The following table describes which attribute includes what parameters:
|Attribute |Parameter name|Parameter type | |-------------|--------------|------------------| |init |deps |DepsMut | |init |env |Env | |init |info |MessageInfo | |execute |deps |DepsMut | |execute |env |Env | |execute |info |MessageInfo | |query |deps |Deps | |query |env |Env | |executeguard |deps |DepsMut | |executeguard |env |&Env | |execute_guard |info |&MessageInfo |
The names of the methods annotated with execute
and query
are used as the enum messsage variants (converted to pascal case) in their respective definitions.
```rust // contract.rs
pub trait Contract {
#[init]
fn new(config: Config) -> StdResult
#[execute]
fn set_config(config: Config) -> StdResult<Response> {
Ok(Response::default())
}
#[query]
fn get_config() -> StdResult<Config> {
Ok(Config)
}
}
pub struct Config;
To get a better idea of what the macro actually does, the above code is equivalent to the following (without including WASM boilerplate):
rust
pub trait Contract {
fn new(
&self,
config: Config,
mut deps: cosmwasmstd::DepsMut,
env: cosmwasmstd::Env,
info: cosmwasm_std::MessageInfo,
) -> StdResult
fn set_config(
&self,
config: Config,
mut deps: cosmwasm_std::DepsMut,
env: cosmwasm_std::Env,
info: cosmwasm_std::MessageInfo,
) -> StdResult<Response> {
Ok(Response::default())
}
fn get_config(&self, deps: cosmwasm_std::Deps, env: cosmwasm_std::Env) -> StdResult<Config> {
Ok(Config)
}
}
pub struct DefaultImpl;
impl Contract for DefaultImpl {}
pub struct InstantiateMsg { pub config: Config, }
pub enum ExecuteMsg { SetConfig { config: Config }, }
pub enum QueryMsg { GetConfig {}, }
pub fn instantiate(
deps: cosmwasmstd::DepsMut,
env: cosmwasmstd::Env,
info: cosmwasmstd::MessageInfo,
msg: InstantiateMsg,
contract: impl Contract,
) -> cosmwasmstd::StdResult
pub fn execute(
mut deps: cosmwasmstd::DepsMut,
env: cosmwasmstd::Env,
info: cosmwasmstd::MessageInfo,
msg: ExecuteMsg,
contract: impl Contract,
) -> cosmwasmstd::StdResult
pub fn query(
deps: cosmwasmstd::Deps,
env: cosmwasmstd::Env,
msg: QueryMsg,
contract: impl Contract,
) -> cosmwasmstd::StdResult
mod wasm { use super::cosmwasmstd::{ doexecute, doinstantiate, doquery, to_binary, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, StdResult, };
fn entry_init(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: super::InstantiateMsg,
) -> StdResult<Response> {
super::instantiate(deps, env, info, msg, super::DefaultImpl)
}
pub fn entry_execute(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: super::ExecuteMsg,
) -> StdResult<Response> {
super::execute(deps, env, info, msg, super::DefaultImpl)
}
fn entry_query(deps: Deps, env: Env, msg: super::QueryMsg) -> StdResult<QueryResponse> {
let result = super::query(deps, env, msg, super::DefaultImpl)?;
to_binary(&result)
}
#[no_mangle]
extern "C" fn instantiate(env_ptr: u32, info_ptr: u32, msg_ptr: u32) -> u32 {
do_instantiate(&entry_init, env_ptr, info_ptr, msg_ptr)
}
#[no_mangle]
extern "C" fn execute(env_ptr: u32, info_ptr: u32, msg_ptr: u32) -> u32 {
do_execute(&entry_execute, env_ptr, info_ptr, msg_ptr)
}
#[no_mangle]
extern "C" fn query(env_ptr: u32, msg_ptr: u32) -> u32 {
do_query(&entry_query, env_ptr, msg_ptr)
}
} ```
```rust // shared/interfaces/contract.rs
pub trait Contract {
#[init]
pub fn new(config: Config) -> StdResult
#[execute]
pub fn set_config(config: Config) -> StdResult<Response>;
#[query]
pub fn get_config() -> StdResult<Config>;
}
pub struct Config;
// contracts/contract.rs
pub trait Contract {
#[init]
fn new(config: Config) -> StdResult
#[execute]
fn set_config(config: Config) -> StdResult<Response> {
Ok(Response::default())
}
#[query]
fn get_config() -> StdResult<Config> {
Ok(Config)
}
}
// some other contract
// contracts/other_contract.rs
use shared::interfaces::contract::{ExecuteMsg, QueryMsg};
-- snip --
``
This code will generate the necessary entry points and dispatch functions using the messages exported by the interface module. The interface definition only generates the
InstantiateMsg,
ExecuteMsgand
QueryMsg` types. In addition, its methods cannot have a default implementation. Note that the interface definition and the implementing contract cannot go out of sync since any deviation between the two will result in compile errors.
A execute guard function is a special function that is called before matching the ExecuteMsg
enum inside the execute
function both of which are generated by the macro. It must take no arguments and be annotated with the execute_guard
attribute. Only one such function can exist per contract definition. It is useful in cases where we want to assert some state before proceeding with executing the incoming message and fail before that if necessary. For example, it should be used with the Fadroma killswitch component. Inside the execute guard we check whether the contract is pausing or migrated and return an Err(())
if so.
A component is simply a contract declared somewhere else using the contract
macro. We can reuse its functionality by including it via the component
attribute in our current contract.
One or many components can be used in a single contract like so:
```rust
component(path = "fadroma::admin"),
component(path = "fadroma::killswitch")
)]
or when implementing an interface:
rust
path = "shared::interfaces::contract",
component(path = "fadroma::admin"),
component(path = "fadroma::killswitch")
)]
``
The macro will include their execute and query message enums in the current message enums as tuple variants. The name of the variant is derived from the last segment in the
path` argument of the component. For example, the above code will generate the following execute message:
rust
pub enum ExecuteMsg {
Admin(fadroma::admin::ExecuteMsg),
Killswitch(fadroma::killswitch::ExecuteMsg)
// .. other variants
}
A component may not implement any query or execute methods (like the Fadroma auth component). In that case those should be skipped in the importing contract messages by specifying the skip
attribute like so:
```rust
component(path = "fadroma::auth", skip(query)),
)]
``
Valid tokens are
queryand
execute`. Both can be used at the same time as well.
Sometimes we may want to use a component but change the implementation of one (or many) of its methods. In that case all we need to do is implement the component trait on a new empty struct (like we'd normally do in Rust) and specify its name in the component definition using the custom_impl
attribute. By default the macro will use the DefaultImpl
struct which it generates for every contract
and contract_impl
. If we wanted to use our custom implementation it would look like this:
```rust
component(path = "fadroma::admin", custom_impl = "MyCustomAdminImplStruct"),
)] ```