1 个不稳定版本

0.8.0 2023 年 3 月 31 日

4#fadroma 中排名

Download history 1/week @ 2024-03-16 18/week @ 2024-03-30 6/week @ 2024-04-06 1/week @ 2024-05-18

78 每月下载量
用于 fadroma

AGPL-3.0

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 枚举,它表示您在合同及其实现的接口中使用的所有可能的错误。这用于生成的 executequery 函数,但这是一个将一切联系在一起的实现细节。另一方面,如果您想用它来做什么,它就在那里。

接口

除非您有多个相互通信的合同,否则您不需要此属性,可以直接使用 #[contract]。但如果是这种情况,此属性允许您单独定义合同的接口,并生成其 InstantiateMsg(如果存在)、ExecuteMsgQueryMsg。这意味着接口可以在单独的包中定义,并可以被多个其他实现合同的包所消费。这种方法与在单个包中定义所有合同消息的常见模式相得益彰,并且合同包可以使用该模式来实现和调用彼此。此外,在合同中实现接口特质意味着 Rust 从不会让接口和实现不同步。接口强制您声明关联类型 type Error: std::fmt::Display;,并且所有方法都必须返回该错误类型。这允许您有自定义的错误类型。否则,只需使用 cosmwasm_std::StdError

初始化

合约的实例化方法。每个合约只能有一个,但如果接口有定义,则每个实现的接口也必须有它。在 #[contract]#[interface] 上下文中都可以省略。在后者中使用时,将简单地生成一个 InstantiateMsg 结构体。在前者中,它仅作为任何实现接口中的标记,除非使用 entry 元参数。

元参数

  • entry
    • 用作 #[init(entry)] 并创建 InstantiateMsgExecuteMsgQueryMsg 结构体,以及 instantiateexecutequery 入口点函数。
    • 是可选的。
    • 只能用于 #[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/DepsMutEnvMessageInfo类型被作为参数插入到相关方法中,这样就不需要每次都指定它们。您的消息声明的任何参数都附加在方法签名中的那些参数之后。下表描述了哪些属性包含哪些参数

属性 参数名称 参数类型
初始化 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