# 使用 `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) 上部署