# 使用 `gmeta` 为 Gear 合约生成 Metadata
---
上节回顾: 编译 Gear 模板合约并在 [idea](https://idea.gear-tech.io) 上部署,进行全量/部分状态查询
https://github.com/gear-dapps/app
1. 部署合约 code
2. 上传 metadata -> 合约交互 / 内部状态
3. 上传 metawasm -> 状态查询
---
# Gear 合约的 Metadata
Gear 使用 Scale 编解码器([SCALE codec](https://docs.substrate.io/reference/scale-codec/))作为其默认的二进制编解码格式
- 高效性:Scale编解码器在编码和解码时非常高效,因为它是一种紧凑的二进制格式,可以将数据转换为紧凑的字节流。这使得它在网络传输和存储数据时非常有效。
- 通用性:Scale编解码器是一种通用格式,可以用于多种类型的数据,例如数字、布尔、字符串、结构体等。这使得它非常适合用于区块链数据结构,因为区块链通常需要处理多种类型的数据。
- 可扩展性:Scale编解码器是可扩展的,这意味着它可以轻松地支持新的数据类型和协议变更。这使得Polkadot可以轻松地升级其协议,同时保持向后兼容性。
Typed Value: `u8`, `i8`, `u16`, `i16`, `u32`, `i32`, `Vec`, `Option`, `String`, `struct`, `enum` -> Binary `0xdeadbeef...`
编码省略了类型信息,不同数据可能有同样的编码:
`"\nX": String` => `0x0a58`
`2648: u32` => `0x0a58`
https://www.shawntabrizi.com/substrate-js-utilities/codec/
---
## Metadata: 包含合约相关数据的类型信息
> Metadata is a kind of interface map that helps to transform a set of bytes into an understandable structure and indicates the function this structure is intended for. Metadata determines how all incoming and outgoing data will be encoded/decoded.
https://wiki.gear-tech.io/docs/developing-contracts/metadata/
没有 Metadata 便无法
- 获得合约输入消息定义,编码调用参数,向合约发送消息
- 获得合约返回消息定义,解码合约返回的消息,获取返回值
- 获得合约状态类型定义,读取合约状态
Metadata 经编译脚本 SCALE 编码后保存为 *.meta.txt, 结构体定义:
```
#[derive(Encode, Debug, Decode, Eq, PartialEq)]
pub struct MetadataRepr {
pub init: TypesRepr,
pub handle: TypesRepr,
pub reply: TypesRepr,
pub others: TypesRepr,
pub signal: Option<u32>,
pub state: Option<u32>,
pub registry: Vec<u8>, // scale_info::PortableRegistry
}
#[derive(Encode, Debug, Decode, Eq, PartialEq)]
pub struct TypesRepr {
pub input: Option<u32>,
pub output: Option<u32>,
}
```
# 使用 `@gear-js/api` 解码 Metadata 内容
https://github.com/gear-tech/gear-js/tree/main/api#getting-metadata
使用 `getProgramMetadata` 转换为 JSON 表示:
<details>
<summary>Click to expand</summary>
```
{
"registry": {},
"regTypes": {},
"portableRegistry": {
"types": [
{
"id": 0,
"type": {
"path": [
"app_io",
"PingPong"
],
"params": [],
"def": {
"variant": {
"variants": [
{
"name": "Ping",
"fields": [],
"index": 0,
"docs": []
},
{
"name": "Pong",
"fields": [],
"index": 1,
"docs": []
}
]
}
},
"docs": []
}
},
{
"id": 1,
"type": {
"path": [],
"params": [],
"def": {
"sequence": {
"type": 2
}
},
"docs": []
}
},
{
"id": 2,
"type": {
"path": [],
"params": [],
"def": {
"tuple": [
3,
6
]
},
"docs": []
}
},
{
"id": 3,
"type": {
"path": [
"gstd",
"common",
"primitives",
"ActorId"
],
"params": [],
"def": {
"composite": {
"fields": [
{
"name": null,
"type": 4,
"typeName": "[u8; 32]",
"docs": []
}
]
}
},
"docs": []
}
},
{
"id": 4,
"type": {
"path": [],
"params": [],
"def": {
"array": {
"len": 32,
"type": 5
}
},
"docs": []
}
},
{
"id": 5,
"type": {
"path": [],
"params": [],
"def": {
"primitive": "U8"
},
"docs": []
}
},
{
"id": 6,
"type": {
"path": [],
"params": [],
"def": {
"primitive": "U128"
},
"docs": []
}
}
]
},
"types": {
"init": {
"input": null,
"output": null
},
"handle": {
"input": 0,
"output": 0
},
"reply": {
"input": null,
"output": null
},
"others": {
"input": null,
"output": null
},
"signal": null,
"state": 1
}
}
```
</details>
---
# 在项目中定义 Metadata
一般将 Metadata 的定义放在 ./io 中
下面以模板合约为例:https://github.com/gear-dapps/app
## 目录结构
不同的 Crate 及各自对应的编译产物:
- ./ : `app` 合约逻辑 (code)
- ./target/wasm32-unknown-unknown/release/app.opt.wasm
- ./io : `app-io` 类型定义 (metadata)
- ./app.meta.txt
- ./state : `app-state` 状态查询 (metawasm)
- ./target/wasm32-unknown-unknown/release/app_state.meta.wasm
---
## `gear-wasm-builder`
各种产物对应的编译方式
```
// 编译 code (./build.rs)
gear_wasm_builder::build();
// 编译 code + metadata (./build.rs)
gear_wasm_builder::build_with_metadata::<app_io::ContractMetadata>();
// 编译 metawasm (./state/build.rs)
gear_wasm_builder::build_metawasm();
```
```
pub fn build_with_metadata<T: Metadata>()
```
---
合约的 Metadata 应实现 `gmeta::Metadata` trait,定义如下
https://github.com/gear-tech/gear/blob/master/gmeta/src/lib.rs
```
pub trait Metadata {
type Init: Types;
type Handle: Types;
type Reply: Types;
type Others: Types;
type Signal: Type;
type State: Type;
fn repr() -> MetadataRepr {
let mut registry = Registry::new();
MetadataRepr {
init: Self::Init::register(&mut registry),
handle: Self::Handle::register(&mut registry),
reply: Self::Reply::register(&mut registry),
others: Self::Others::register(&mut registry),
signal: Self::Signal::register(&mut registry),
state: Self::State::register(&mut registry),
registry: PortableRegistry::from(registry).encode(),
}
}
}
pub trait Types {
type Input: Type;
type Output: Type;
fn register(registry: &mut Registry) -> TypesRepr {
let input = Self::Input::register(registry);
let output = Self::Output::register(registry);
TypesRepr { input, output }
}
}
pub type InOut<I, O> = (I, O);
pub type In<I> = InOut<I, ()>;
pub type Out<O> = InOut<(), O>;
impl<I: Type, O: Type> Types for InOut<I, O> {
type Input = I;
type Output = O;
}
impl Types for () {
type Input = ();
type Output = ();
}
```
---
# 示例
`app_io::ContractMetadata` 实现了 `gmeta`
```
use gmeta::{InOut, Metadata};
use gstd::{prelude::*, ActorId};
...
pub struct ContractMetadata;
impl Metadata for ContractMetadata {
type Init = ();
type Handle = InOut<PingPong, PingPong>;
type Others = ();
type Reply = ();
type Signal = ();
type State = Vec<(ActorId, u128)>;
}
```
https://wiki.gear-tech.io/docs/developing-contracts/metadata/
# 作业:
尝试修改 https://github.com/gear-dapps/app, 将 State 的类型变为 `BTreeMap<ActorId, u128>`, 并在 [idea](https://idea.gear-tech.io) 上部署