1 个不稳定版本
0.8.0 | 2023 年 3 月 31 日 |
---|
4 在 #fadroma 中排名
78 每月下载量
用于 fadroma
74KB
1.5K SLoC
Fadroma DSL
用于可组合且无样板代码的 CosmWasm 智能合同的进程宏。
简介
Fadroma DSL 负责处理 CosmWasm 智能合同中所有必要的样板代码,并提供更高效的开发体验。它有两个主要目标。
第一个目标是生成你无论如何都会编写的重复代码,但以结构化的方式,以便你在看到使用 DSL 编写的代码片段时知道期望什么。
其次,它旨在提供一种可靠且灵活的系统,用于组合常见的智能合同功能,甚至扩展整个合同。也就是说,如果你已经实现了例如管理功能,你可以轻松地将它添加到你的合同中,并像这样轻松地扩展/更改你需要的内容。这种粒度扩展到单个函数。此外,你的模块可能需要其他模块才能正常工作。你可以使用 DSL 来强制执行这一点,这样,如果你没有包含所需的内容,就会产生编译错误。
任何对宏的误用都将导致 编译错误,而不是一些隐藏或意外的运行时行为。此外,宏的错误消息应确切地告诉你需要添加或更改什么才能进行编译。
用法
如果你已经熟悉 CosmWasm 合同,那么理解 Fadroma DSL 以及为什么它以这种方式行事应该更容易。否则,可能最好先熟悉它。还可以查看 Fadroma 仓库中的 示例,这些示例展示了如何使用 DSL,以及如何使用 Fadroma 中定义的模块(也使用 DSL)使用或无需使用它。
DSL 定义了以下属性
请注意,你不需要记住所有属性及其规则,因为编译器会引导你。
合同
仅对 mod
项目有效。该 mod
将包含您合同的完整实现。它生成一个零大小的 Contract
结构体,您 必须 使用它来实现合同方法以及您希望实现的任何接口。所有标记为 #[init]
、#[execute]
或 #[query]
的方法都将作为合同公开的功能的一部分被包含。尽管如此,您仍然可以在模块内部编写任何其他 Contract
方法或函数。接口使用标准的 Rust 语法实现合同,即 impl MyTrait for Contract { ... }
,其中 MyTrait
是一个带有 #[interface]
属性声明的特质。技术上,您可以使用任何满足 #[interface]
要求的特质,而不会破坏任何东西,因为宏强制执行这一点。该宏还生成一个 Error
枚举,它表示您在合同及其实现的接口中使用的所有可能的错误。这用于生成的 execute
和 query
函数,但这是一个将一切联系在一起的实现细节。另一方面,如果您想用它来做什么,它就在那里。
接口
除非您有多个相互通信的合同,否则您不需要此属性,可以直接使用 #[contract]
。但如果是这种情况,此属性允许您单独定义合同的接口,并生成其 InstantiateMsg
(如果存在)、ExecuteMsg
和 QueryMsg
。这意味着接口可以在单独的包中定义,并可以被多个其他实现合同的包所消费。这种方法与在单个包中定义所有合同消息的常见模式相得益彰,并且合同包可以使用该模式来实现和调用彼此。此外,在合同中实现接口特质意味着 Rust 从不会让接口和实现不同步。接口强制您声明关联类型 type Error: std::fmt::Display;
,并且所有方法都必须返回该错误类型。这允许您有自定义的错误类型。否则,只需使用 cosmwasm_std::StdError
。
初始化
合约的实例化方法。每个合约只能有一个,但如果接口有定义,则每个实现的接口也必须有它。在 #[contract]
和 #[interface]
上下文中都可以省略。在后者中使用时,将简单地生成一个 InstantiateMsg
结构体。在前者中,它仅作为任何实现接口中的标记,除非使用 entry
元参数。
元参数
entry
- 用作
#[init(entry)]
并创建InstantiateMsg
、ExecuteMsg
和QueryMsg
结构体,以及instantiate
、execute
和query
入口点函数。 - 是可选的。
- 只能用于
#[contract]
。 - 只能有一个
#[init]
可以标记,这包括合约实现的任何接口。 - 如果你在你的合约方法中有一个
#[init]
属性(在impl Contract
块内),你必须添加这个属性。这样做的理由是,由于这是生成消息枚举和入口函数的东西,如果在合约方法(但不是接口方法)中有一个#[init]
属性而没有它,基本上就不会有什么作用。
- 用作
entry_wasm
- 生成相同的样板代码,并遵循相同的规则,如
entry
元,但还会生成 WASM 样板代码 FFI 模块。
- 生成相同的样板代码,并遵循相同的规则,如
execute
是合约可执行方法集的一部分。要成为该集合的一部分的每个方法都必须用该属性进行注释。生成的 ExecuteMsg
枚举包括所有这些方法的名称。通过生成的 execute
函数自动进行调度。这些都是您自己编写的代码。
query
与 #[execute]
属性的工作方式相同,但生成 QueryMsg
枚举和 query
函数。
reply
将方法标记为 CosmWasm 回复处理程序。每个合约只能存在一个这样的函数,并且它必须有一个类型为 cosmwasm_std::Reply
的单一参数。
execute_guard
执行守卫函数是一个特殊函数,在调用宏生成的execute
函数中的ExecuteMsg
枚举之前被调用。这两个函数都是由宏生成的。每个合约中只能存在一个这样的函数,并且它必须有一个类型为&ExecuteMsg
的单个参数。
在执行传入的消息之前,我们想要断言一些状态并在必要时失败的情况下,它非常有用。例如,对于这个例子,应该与Fadroma的killswitch组件一起使用。在执行守卫内部,我们检查合约是否正在暂停或迁移,如果是这样,就返回一个Err(())
。
auto_impl
仅适用于impl
块。它接受一个路径,指向实现给定接口特质的结构体。对于特质的每个方法,它将实现委托给给定的结构体。我们通过使用Rust的完全限定语法(<MyStruct as Trait>::method_name()
)来确保提供的结构体确实实现了特质。它还将填充接口必须具有的具体Error
类型。通过将方法体完全留空,您可以将实现委托给结构体。否则,编写方法体将使用您的代码。这提供了极大的灵活性,因为您可以通过使用现有实现来实现接口,同时允许您直接覆盖您希望覆盖的任何方法。例如
#[auto_impl(ImplementingStuct)]
impl MyInterface for Contract {
// Here we leave the body empty and it will delegate the implementation to ImplementingStuct
#[execute]
fn first_method(some_arg: u32) -> Result<Response, Self::Error> {
// The macro inserts the following code here
// <ImplementingStuct as MyInterface>::first_method(deps, env, info, some_arg)
}
// Here we provide our own implementation so ImplementingStuct is not used at all.
#[execute]
fn second_method() -> Result<Response, Self::Error> {
Ok(Response::default())
}
}
元参数
- 实现特质的结构的路径
- 不是可选的。
用法
由于在CosmWasm合约中它们的用法始终是一个事实,因此Deps/DepsMut
、Env
和MessageInfo
类型被作为参数插入到相关方法中,这样就不需要每次都指定它们。您的消息声明的任何参数都附加在方法签名中的那些参数之后。下表描述了哪些属性包含哪些参数
属性 | 参数名称 | 参数类型 |
---|---|---|
初始化 | deps | DepsMut |
初始化 | env | Env |
初始化 | info | MessageInfo |
execute | deps | DepsMut |
execute | env | Env |
execute | info | MessageInfo |
query | deps | Deps |
query | env | Env |
reply | deps | DepsMut |
reply | env | Env |
execute_guard | deps | DepsMut |
execute_guard | env | &Env |
execute_guard | info | &MessageInfo |
比较
为了更好地理解宏在这里生成的内容,下面是一个简单的合约以及生成的代码最终看起来像什么
#[fadroma::dsl::contract]
pub mod counter_contract {
use fadroma::{
dsl::*,
admin::{self, Admin, Mode},
schemars,
cosmwasm_std::{self, Response, Addr, StdError}
};
impl Contract {
#[init(entry_wasm)]
pub fn new(initial_value: u64) -> Result<Response, StdError> {
Ok(Response::default())
}
#[execute]
pub fn add(value: u64) -> Result<Response, StdError> {
Ok(Response::default())
}
#[query]
pub fn value() -> Result<u64, StdError> {
Ok(0)
}
}
#[auto_impl(admin::DefaultImpl)]
impl Admin for Contract {
#[execute]
fn change_admin(mode: Option<Mode>) -> Result<Response, Self::Error> { }
#[query]
fn admin() -> Result<Option<Addr>, Self::Error> { }
}
}
将展开为
pub mod counter_contract {
use fadroma::{
dsl::*,
admin::{self, Admin, Mode},
schemars,
cosmwasm_std::{self, Response, Addr, StdError}
};
#[derive(Clone, Copy)]
pub struct Contract;
impl Contract {
pub fn new(
mut deps: cosmwasm_std::DepsMut,
env: cosmwasm_std::Env,
info: cosmwasm_std::MessageInfo,
initial_value: u64,
) -> Result<Response, StdError> {
Ok(Response::default())
}
pub fn add(
mut deps: cosmwasm_std::DepsMut,
env: cosmwasm_std::Env,
info: cosmwasm_std::MessageInfo,
value: u64,
) -> Result<Response, StdError> {
Ok(Response::default())
}
pub fn value(
deps: cosmwasm_std::Deps,
env: cosmwasm_std::Env
) -> Result<u64, StdError> {
Ok(0)
}
}
impl Admin for Contract {
type Error = <admin::DefaultImpl as Admin>::Error;
fn change_admin(
mut deps: cosmwasm_std::DepsMut,
env: cosmwasm_std::Env,
info: cosmwasm_std::MessageInfo,
mode: Option<Mode>,
) -> Result<Response, Self::Error> {
<admin::DefaultImpl as Admin>::change_admin(deps, env, info, mode)
}
fn admin(
deps: cosmwasm_std::Deps,
env: cosmwasm_std::Env,
) -> Result<Option<Addr>, Self::Error> {
<admin::DefaultImpl as Admin>::admin(deps, env)
}
}
#[derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema, Debug)]
pub struct InstantiateMsg {
pub initial_value: u64,
}
#[derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema, Debug)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
Add { value: u64 },
ChangeAdmin { mode: Option<Mode> },
}
#[derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema, Debug)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
Value {},
Admin {},
}
#[derive(Debug)]
pub enum Error {
// The macro needs this to signal errors when calling cosmwasm_std::to_binary
// in the query function that it generates when the call fails.
#[doc(hidden)]
QueryResponseSerialize(String),
// We call this for every method inside the impl Contract block.
Base(StdError),
// One for each interface implemented.
Admin(<Contract as Admin>::Error),
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::QueryResponseSerialize(msg) => f.write_fmt(
format_args!("Error serializing query response: {}", msg)
),
Self::Base(x) => std::fmt::Display::fmt(x, f),
Self::Admin(x) => std::fmt::Display::fmt(x, f),
}
}
}
impl std::error::Error for Error {}
pub fn instantiate(
mut deps: cosmwasm_std::DepsMut,
env: cosmwasm_std::Env,
info: cosmwasm_std::MessageInfo,
msg: InstantiateMsg,
) -> Result<Response, StdError> {
Contract::new(deps, env, info, msg.initial_value)
}
pub fn execute(
mut deps: cosmwasm_std::DepsMut,
env: cosmwasm_std::Env,
info: cosmwasm_std::MessageInfo,
msg: ExecuteMsg,
) -> std::result::Result<cosmwasm_std::Response, Error> {
match msg {
ExecuteMsg::Add { value } => {
Contract::add(deps, env, info, value).map_err(|x| Error::Base(x))
}
ExecuteMsg::ChangeAdmin { mode } => {
Contract::change_admin(deps, env, info, mode).map_err(|x| Error::Admin(x))
}
}
}
pub fn query(
deps: cosmwasm_std::Deps,
env: cosmwasm_std::Env,
msg: QueryMsg,
) -> std::result::Result<cosmwasm_std::Binary, Error> {
match msg {
QueryMsg::Value {} => {
let result = Contract::value(deps, env).map_err(|x| Error::Base(x))?;
cosmwasm_std::to_binary(&result)
.map_err(|x| Error::QueryResponseSerialize(x.to_string()))
}
QueryMsg::Admin {} => {
let result = Contract::admin(deps, env).map_err(|x| Error::Admin(x))?;
cosmwasm_std::to_binary(&result)
.map_err(|x| Error::QueryResponseSerialize(x.to_string()))
}
}
}
#[cfg(target_arch = "wasm32")]
mod wasm_entry {
use super::cosmwasm_std::{do_instantiate, do_execute, do_query};
#[no_mangle]
extern "C" fn instantiate(env_ptr: u32, info_ptr: u32, msg_ptr: u32) -> u32 {
do_instantiate(&super::instantiate, env_ptr, info_ptr, msg_ptr)
}
#[no_mangle]
extern "C" fn execute(env_ptr: u32, info_ptr: u32, msg_ptr: u32) -> u32 {
do_execute(&super::execute, env_ptr, info_ptr, msg_ptr)
}
#[no_mangle]
extern "C" fn query(env_ptr: u32, msg_ptr: u32) -> u32 {
do_query(&super::query, env_ptr, msg_ptr)
}
}
}
依赖项
~1.5MB
~35K SLoC