
GearFans
2023/04/03阅读:25主题:默认主题
Gear 智能合约开发常用技术 1|Gear Wiki
提示
本篇文章介绍与 Gear 智能合约相关技术,帮助你更好的了解 Gear。

可执行函数
程序是 Gear 组件的主要单元。程序代码存储为不可变的 Wasm 二进制文件(blob)。 每个程序都有一个固定的内存,在消息处理之间持续存在(所谓的静态区域)。
基础结构
任何程序最多可以包含 3 个入口点,它们在程序生命周期中执行各种功能:init()
、handle()
、handle_reply()
。 它们都是可选的,但任何程序都需要至少有一个方法:init()
或 handle()
。
Gear 协议引入的另一个特殊系统入口点是 handle_signal()
。如果有必要的通知(信号)与程序消息相关的某些事件已经发生,它允许系统与程序通信。
init()
init()
方法是可选的,在程序初始化时只被调用一次。并处理传入的 init payload
,如果有的话。
#[no_mangle]
extern "C" fn init() {
// execute this code during contract initialization
}
handle()
handle()
方法(同样可选)将在每次程序收到传入的消息时被调用。在这种情况下,payload
将被处理。
#[no_mangle]
extern "C" fn handle() {
// execute this code during explicitly incoming message
}
handle_reply()
在 Gear 程序中对信息的回复,使用handle_reply
函数进行处理。
#[no_mangle]
extern "C" fn handle_reply() {
// execute this code during handling reply on the previously sent message
}
Gear 库
Gear Protocol 的库 gstd
为开发智能合约提供了所有必要和完善的功能和方法。
通过 prelude 导入相似的类型
gstd
的默认 prelude
模块列出了 Rust 自动导入每个程序中的东西。它重新导入了默认的 std
模块和 traits。在 Rust 的 Gear 程序中,std
可以安全地替换为 gstd
。
更多详细内容请看 https://docs.gear.rs/gstd/prelude/index.html
消息处理
Gear Protocol 允许用户和程序通过消息与其他用户和程序进行交互。消息可以包含在 payload
,它能够在消息执行期间处理。通过模块 msg
,我们可以与消息交互:
use gstd::msg;
消息处理只能在定义的函数 init()
、handle()
和 hadle_reply()
内进行。它们还定义了处理此类消息的上下文。
-
获取当前正在处理的消息的 payload
并对其进行解码:
#![no_std]
use gstd::{msg, prelude::*};
#[no_mangle]
extern "C" fn handle() {
let payload_string: String = msg::load().expect("Unable to decode `String`");
}
-
使用 payload
回复消息:
#![no_std]
use gstd::msg;
#[no_mangle]
extern "C" fn handle() {
msg::reply("PONG", 0).expect("Unable to reply");
}
-
向用户发送消息:
#![no_std]
use gstd::{msg, prelude::*};
#[no_mangle]
extern "C" fn handle() {
// ...
let id = msg::source();
let message_string = "Hello there".to_string();
msg::send(id, message_string, 0).expect("Unable to send message");
}
关于 msg
的更多用法,请看 https://docs.gear.rs/gstd/msg/index.html
执行信息
程序可以通过使用 exec
模块获取有关当前执行上下文的一些有用信息:
use gstd::exec;
-
在区块时间戳到达指定日期后发送回复消息:
#![no_std]
use gstd::{exec, msg};
#[no_mangle]
extern "C" fn handle() {
// Timestamp is in milliseconds since the Unix epoch
if exec::block_timestamp() >= 1672531200000 {
msg::reply(b"Current block has been generated after January 01, 2023", 0)
.expect("Unable to reply");
}
}
-
获得一个程序的余额:
#![no_std]
use gstd::exec;
#[no_mangle]
extern "C" fn handle() {
// Get self value balance in program
let my_balance = exec::value_available();
}
关于 syscalls
的更多用法,请看 https://docs.gear.rs/gstd/exec/index.html
在合约内部进行调试
宏 gstd::debug
提供在程序执行期间调试合约的能力:
#![no_std]
use gstd::{debug, msg, prelude::*};
#[no_mangle]
extern "C" fn handle() {
let payload_string: String = msg::load().expect("Unable to decode `String`");
debug!("{:?} received message: ", payload_string);
}
Mailbox
当程序向用户发送消息时,这条消息会被放在用户的 mailbox 里。实际上,mailbox 是一个专门的存储器,用于保存从程序中收到的信息。
用户可以通过订阅 UserMessageSent
事件来检测收到的信息
在使用 IDEA 网站时,应该进入 https://idea.gear-tech.io/mailbox。
备注
一条信息在 mailbox 中是要收费的。因此,信息在 mailbox 中的存在时间是有限的。 我们来探讨一下用户对 mailbox 信息的可能反应。
用户对信息进行回复
程序可以向用户发送一条信息,并等待回复。用户可以使用 send_reply
extrinsic 来回复。与消息相关的余额被转移到用户的账户,信息被从 mailbox 中删除,而新的回复信息被添加到信息队列中。
用户从 mailbox 的信息中获取余额
如果 mailbox 中的消息具有关联的余额,则用户可以使用 claim_value
extrinsic 声明它。余额被转移到用户的帐户,消息从 mailbox 中删除。
用户忽略 mailbox 中的消息
一条消息在其 gas 限制内对 mailbox 中的每一个区块都要收费。如果消息没有明确的 gas 限制,则从源头的限制中借用 gas(例如,启动执行的行为者)。
当信息的 gas 耗尽时,信息将从 mailbox 中删除,并将相关转账金额转回给信息发送者。
State 函数
存储数据
Gear 智能合约的持久化数据的存储方式与传统程序相同,不需要初始化外部存储。
// ...
// describe state structure
#[derive(TypeInfo, Decode, Encode, Clone)]
pub struct Wallet {
pub id: ActorId,
pub person: String,
}
// declare and initialize the state
static mut WALLETS: Vec<Wallet> = Vec::new();
如果用 Rust 或其他面向对象的语言编程,你应该对大多数类型都很熟悉。然而,在 Gear 上开发合约时,ActorId
类型是一个新内容。
一级引用如下:
信息
ActorId
是一个特殊的类型,代表一个 32 字节的数组,并定义了 Gear Protocol 中所有的ID
。
State 函数
为了显示合约状态信息(类似于view
函数),使用 state()
函数。它可以立即读取合约状态(例如,余额)。读取状态是一个免费函数,不需要消耗任何 gas。
要返回 State 使用:
#[no_mangle]
extern "C" fn state() {
msg::reply(unsafe { WALLETS.clone() }, 0).expect("Failed to share state");
}
默认情况下,state()
函数返合约的整体状态。
自定义程序读取状态
此外,你可以创建自己的程序来读取状态。这个包装器将允许你为客户端实现自定义函数,而不依赖于主程序。
这有很多优点,例如,即使程序改变了,也始终能够读取状态(只要输入或输出的类型没有改变)。或者你正在基于一个已经存在的程序创建服务,你需要一些自己的函数来从状态中获得你自己的数据块。
为此,我们需要创建一个独立的程序并在 metawasm
特征中描述必要的功能。例如:
// ...
use gmeta::metawasm;
#[metawasm]
pub trait Metawasm {
type State = Vec<Wallet>;
fn all_wallets(state: Self::State) -> Vec<Wallet> {
state
}
fn first_wallet(state: Self::State) -> Option<Wallet> {
state.first().cloned()
}
fn last_wallet(state: Self::State) -> Option<Wallet> {
state.last().cloned()
}
}
更多复杂的例子:
// ...
use gmeta::metawasm;
#[metawasm]
pub trait Metawasm {
type State = Vec<Wallet>;
fn wallet_by_id(id: Id, state: Self::State) -> Option<Wallet> {
state.into_iter().find(|w| w.id == id)
}
fn wallet_by_person(person: String, state: Self::State) -> Option<Wallet> {
state.into_iter().find(|w| w.person == person)
}
}
要构建 meta.wasm
,需要在项目根目录下设置 build.rs
文件:
fn main() {
gear_wasm_builder::build_metawasm();
}
元数据
元数据是一种接口映射,有助于将一组字节转换为可理解的结构,并展示出该结构的用途。元数据决定了所有传入和传出数据的编码/解码方式。
元数据允许 dApp 的各个部分--智能合约和客户端(JavaScript)相互理解并交互数据。
要描述元数据接口,请使用 gmeta
:
use gmeta::{InOut, Metadata};
pub struct ProgramMetadata;
// 一定要描述所有的类型。
// 但如果程序中缺少任一入口,请使用 () 代替;
// 如 `type Signal` 所示
impl Metadata for ProgramMetadata {
type Init = InOut<MessageInitIn, MessageInitOut>;
type Handle = InOut<MessageIn, MessageOut>;
type Others = InOut<MessageAsyncIn, Option<u8>>;
type Reply = InOut<String, Vec<u16>>;
type Signal = ();
type State = Vec<Wallet>;
}
正如我们所看到的,元数据使你能够确定合约在每个端点的输入/输出的预期数据格式:
-
Init - init() 函数的输入/输出类型。 -
Handle -handle()函数的输入/输出类型。 -
Others- 在异步交互的情况下,main()函数的输入/输出类型。 -
Reply - 使用 handle_reply 函数执行的消息的输入/输出类型。 -
Signal - 只描述在处理系统信号时从程序中输出的类型。 -
State - 查询的状态的类型
生成元数据
要生成元数据,需要在项目文件夹的根目录下添加 build.rs
文件:
// build.rs
// Where ProgramMetadata is your metadata structure
use meta_io::ProgramMetadata;
fn main() {
gear_wasm_builder::build_with_metadata::<ProgramMetadata>();
}
作为智能合约编译的结果,将生成一个 meta.txt
文件。这个元数据文件可用于与该智能合约互动的用户界面应用程序。文件的内容看起来像一个十六进制字符串:
01000000000103000000010500000001090000000102000000010d000000010f0000000111000000000112000000a9094c00083064656d6f5f6d6574615f696f344d657373616765496e6974496e0000080118616d6f756e74040108753800012063757272656e6379080118537472696e6700000400000503000800000502000c083064656d6f5f6d6574615f696f384d657373616765496e69744f7574000008013465786368616e67655f72617465100138526573756c743c75382c2075383e00010c73756d04010875380000100418526573756c740804540104044501040108084f6b040004000000000c457272040004000001000014083064656d6f5f6d6574615f696f244d657373616765496e000004010869641801084964000018083064656d6f5f6d6574615f696f084964000008011c646563696d616c1c010c75363400010c68657820011c5665633c75383e00001c000005060020000002040024083064656d6f5f6d6574615f696f284d6573736167654f7574000004010c7265732801384f7074696f6e3c57616c6c65743e00002804184f7074696f6e040454012c0108104e6f6e6500000010536f6d6504002c00000100002c083064656d6f5f6d6574615f696f1857616c6c6574000008010869641801084964000118706572736f6e300118506572736f6e000030083064656d6f5f6d6574615f696f18506572736f6e000008011c7375726e616d65080118537472696e670001106e616d65080118537472696e6700003400000238003800000504003c083064656d6f5f6d6574615f696f384d6573736167654173796e63496e0000040114656d707479400108282900004000000400004404184f7074696f6e04045401040108104e6f6e6500000010536f6d650400040000010000480000022c00
验证元数据
为了验证程序的元数据,可以使用metahash()函数。它允许在链上验证元数据。
#[no_mangle]
// It returns the Hash of metadata.
// .metahash is generating automatically while you are using build.rs
extern "C" fn metahash() {
let metahash: [u8; 32] = include!("../.metahash");
msg::reply(metahash, 0).expect("Failed to share metahash");
}
数据序列化/反序列化
为了优化数据在网络上的发送和接收方式,Gear 使用 parity-scale-codec
,这是 SCALE 编解码器的 Rust 实现。这个编解码器被 Substrate 节点的内部 runtime 所使用。SCALE 是一种轻量级的编码格式,能够实现数据的序列化和反序列化。使用 SCALE 对数据进行编码(和解码),它非常适用于资源受限的执行环境,如区块链运行时间和低功耗、低内存设备。
在程序中使用 SCALE codec,Cargo.toml
需要添加以下内容:
[dependencies]
// ...
codec = { package = "parity-scale-codec", version = "3.1.2", default-features = false }
use codec::{Decode, Encode};
#[derive(Encode, Decode)]
enum MyType {
MyStruct { field: ... },
...
}
信息
我们只需要在使用
gstd
的包装方法时使用 Encode 和 Decode 特性,例如msg::send
,msg::reply
,send_for_reply
等。像send_byte
或reply_bytes
这样的方法,我们操作的是字节数组,所以不需要进行解码/编码。
更多的内容请看 SCALE Codec。
https://github.com/paritytech/parity-scale-codec
scale-info
scale-info
是一个描述 Rust 类型的库,提供有关可编码 SCALE 类型结构的信息。
这些第三方工具 (例如 UI 客户端) 提供了关于它们如何能够解码不受语言影响的类型的信息。Gear 程序使用 scale-info
的接口称为 metadata
。它为所有必要的入口点定义了输入和输出类型,并允许合约和客户端相互理解。
信息
如何在合约中使用
metadata
,请看链接
在项目中使用 scale-info
:
[dependencies]
// ...
scale-info = { version = "2.1.1", default-features = false, features = ["derive"] }
更多的内容请看 scale-info。 https://github.com/paritytech/scale-info
关于 GearFans
Gear 是波卡生态的计算组件,GearFans 是 Gear 爱好者社区。
-
官网:https://gear-tech.io/ -
Twitter:https://twitter.com/gear_techs -
中文 Twitter:https://twitter.com/gear_fans -
Vara Twitter:https://twitter.com/VaraNetwork -
Vara Twitter CN:https://twitter.com/VaraNetwork_CN -
GitHub:https://github.com/gear-tech -
Discord:https://discord.com/invite/7BQznC9uD9 -
Medium:https://medium.com/@gear_techs -
Telegram 群:https://t.me/gear_tech -
Telegram 中文群:https://t.me/Gear_CN -
Telegram 中文开发群:https://t.me/gear_dev_cn -
Vara Telegram CN:https://t.me/varanetwork_cn
作者介绍

GearFans
Gear 是波卡生态的计算组件。