28个版本 (稳定)

2.3.2 2024年7月4日
2.3.1 2024年6月14日
2.0.6 2024年5月30日
0.9.0 2024年5月24日
0.1.0 2024年3月7日

#955 in 神奇豆


用于 3 crate

MIT/Apache 和可能 GPL-2.0-or-later

1MB
19K SLoC

unc-sdk

Rust编写工具智能合约的库。

之前称为 unc-bindgen

Crates.io version Download Reference Documentation MSRV Join the community on Discord GitHub Actions Build

特性 | 先决条件 | 编写Rust合约 | 构建Rust合约 | 参考文档 | 贡献

发布说明

发布说明和未发布更改可在 CHANGELOG 中找到

示例

将一个结构体包裹在 #[unc] 中,并生成与Utility区块链兼容的智能合约

use unc_sdk::{unc, env};

#[unc(contract_state)]
#[derive(Default)]
pub struct StatusMessage {
    records: HashMap<AccountId, String>,
}

#[unc]
impl StatusMessage {
    pub fn set_status(&mut self, message: String) {
        let account_id = env::signer_account_id();
        self.records.insert(account_id, message);
    }

    pub fn get_status(&self, account_id: AccountId) -> Option<String> {
        self.records.get(&account_id).cloned()
    }
}

特性

可单元测试

使用 unc-sdk 编写单元测试非常简单

#[test]
fn set_get_message() {
    let mut contract = StatusMessage::default();
    contract.set_status("hello".to_string());
    assert_eq!("hello".to_string(), contract.get_status("bob".to_string()).unwrap());
}

以常规方式运行单元测试

cargo test --package status-message

异步跨合约调用

异步跨合约调用允许多个合约并行执行,并在另一个合约上执行回调后的聚合。 env 暴露以下方法

  • promise_create -- 在某个合约上安排函数执行;
  • promise_then -- 函数执行后,将回调附加到当前合约;
  • promise_and -- 组合器,允许在执行回调之前同时等待多个承诺;
  • promise_return -- 将承诺执行的返回结果视为当前函数的结果。

参考 examples/cross-contract-high-level 查看跨合约调用的各种用法,包括在合约内部执行的系统级操作(例如余额转账;其他系统级操作的例子包括:账户创建、访问密钥创建/删除、合约部署等)。

初始化方法

我们可以定义一个初始化方法,用于初始化合约的状态。 #[init] 验证合约尚未初始化(合约状态不存在),否则将引发 panic。

#[unc]
impl StatusMessage {
    #[init]
    pub fn new(user: String, status: String) -> Self {
        let mut res = Self::default();
        res.records.insert(user, status);
        res
    }
}

即使你有初始化方法,你的智能合约仍然需要推导 Default 特性。如果你不想禁用默认初始化,则可以像这样禁止它

impl Default for StatusMessage {
    fn default() -> Self {
        unc_sdk::env::panic_str("Contract should be initialized before the usage.")
    }
}

你还可以通过使用 unc_sdk::PanicOnDefault 辅助宏来禁止 Default 特性初始化。例如。

#[unc(contract_state)]
#[derive(PanicOnDefault)]
pub struct StatusMessage {
    records: HashMap<String, String>,
}

可支付方法

我们可以允许方法在函数调用时接受代币转账。这样做是为了让合约能够定义在使用时需要支付代币的费用。默认情况下,方法不是可支付的,如果在调用期间有人尝试向它们转账代币,则将引发 panic。这样做是为了安全起见,以防有人意外地在函数调用期间转账代币。

要声明一个可支付方法,只需使用 #[payable] 装饰器


#[payable]
pub fn my_method(&mut self) {
...
}

私有方法

通常,当一个合约需要远程跨合约调用的回调时,这个回调方法应该只由合约本身调用。这是为了避免其他人调用它并破坏状态。常见的模式是在回调方法中使用 assert 验证直接调用者(前驱账户 ID)与合约的账户(当前账户 ID)匹配。宏 #[private] 通过将其简化为单行宏来简化它,并提高可读性。

要声明一个私有方法,请使用 #[private] 装饰器


#[private]
pub fn my_method(&mut self) {
...
}
/// Which is equivalent to

pub fn my_method(&mut self ) {
    if unc_sdk::env::current_account_id() != unc_sdk::env::predecessor_account_id() {
        unc_sdk::env::panic_str("Method my_method is private");
    }
...
}

现在,只有合约本身的账户可以调用此方法,无论是直接调用还是通过承诺。

先决条件

要开发 Rust 合约,你需要

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
  • 将 wasm 目标添加到你的工具链
rustup target add wasm32-unknown-unknown

编写 Rust 合约

你可以参考 examples/status-message 框架,它展示了简单的 Rust 合约。

一般工作流程如下

  1. 创建一个框架并配置 Cargo.toml 类似于 examples/status-message/Cargo.toml 中的配置;

  2. 框架需要有一个 pub 结构,它将代表智能合约本身

    • 该结构需要实现 Default 特性,UNC 将使用它来创建合约的初始状态,当合约首次使用时
    • 结构体还需要实现BorshSerializeBorshDeserialize特征,UNC将使用这些特征来保存/加载合约的内部状态;

    以下是一个智能合约结构的示例

    use unc_sdk::{unc, env};
    
    #[unc(contract_state)]
    #[derive(Default)]
    pub struct MyContract {
        data: HashMap<u64, u64>
    }
    
  3. 定义UNC将公开作为智能合约方法的函数

    • 您可以为结构体定义任何方法,但只有公开方法才会作为智能合约方法公开;
    • 方法需要使用&self&mut selfself
    • 使用#[unc]宏装饰impl部分。所有M.A.G.I.C.(Macros-Auto-Generated Injected Code)都在这里发生;
    • 如果您需要使用区块链接口,例如获取当前账户ID,则可以使用env::*访问它;

    以下是一个智能合约方法的示例

    #[unc]
    impl MyContract {
        pub fn insert_data(&mut self, key: u64, value: u64) -> Option<u64> {
            self.data.insert(key)
        }
        pub fn get_data(&self, key: u64) -> Option<u64> {
            self.data.get(&key).cloned()
        }
    }
    

构建Rust合约

cargo-unc

这可以作为替代方案,允许在生成abi的同时构建;

# Install the unc extension if you haven't already
cargo install cargo-unc

# Builds the wasm contract and ABI into `target/unc`
cargo unc build --release

使用cargo build

RUSTFLAGS='-C link-arg=-s' cargo build --target wasm32-unknown-unknown --release

使用可重复构建构建

由于WebAssembly编译器将大量调试信息包含到二进制文件中,因此生成的二进制文件可能在不同的机器上有所不同。为了能够以可重复的方式编译二进制文件,我们添加了一个Dockerfile,允许编译二进制文件。

使用contract-builder

实用合约标准

unc-contract-standards crate提供了一套接口和实现,用于UNC的合约标准

  • 可升级性
  • 可替代代币(UIP-141)。请参阅示例用法
  • 不可替代代币(UIP-171)。请参阅示例用法

版本控制

语义版本控制

该crate遵循Cargo的semver指南

避免状态破坏性更改(任何数据类型的低级序列化格式)。如果发生此类更改,它将伴随一个大版本,并带有编译器错误。如果您遇到这种情况,请提交问题

MSRV

当前支持的最小Rust版本为1.78。如果需要发布需要增加Rust工具链的安全补丁版本,则无法保证会维持这一版本。

贡献

如果您有兴趣贡献,请参阅贡献指南

依赖关系

~3–19MB
~256K SLoC