专栏首页本体研究院本体技术视点 | 想用Wasm开发dApp?你不得不读的入门教程(3)

本体技术视点 | 想用Wasm开发dApp?你不得不读的入门教程(3)

在上期的技术视点中,我们简单介绍了如何在不依赖模板的情况下,完成一个简单的 Ontology Wasm 合约的开发,并介绍了 Ontology Wasm 工具库 API 文档的生成方式,方便开发者查询和调用已提供的功能。但我们发现在合约开发的过程中,通常需要进行以下操作:

  • 解析调用合约的参数;
  • 将自定义的结构体保存到链上;
  • 从链上读取已存在的数据并解析成原始的数据类型;
  • 跨合约调用的时候,传递目标合约需要的参数。

上面所列出来的情况,均涉及到参数的序列化和反序列化问题,本文将会详细介绍 Ontology Wasm 合约中参数序列化和反序列化的方法

图 | 网络

1

Encoder和Decoder接口

Encoder 接口定义了不同类型数据的序列化方法,具体实现逻辑请看sink.rs文件;Decoder 接口定义了不同类型数据的反序列化方法,具体实现逻辑请看source.rs文件。ontology-wasm-cdt-rust 工具库中已经为常用的数据类型实现 Encoder 和 Decoder 接口,有 u8、u16、u32、u64、u128、bool、Address、H256、Vec、&str、String 和 Tuple 多种类型。常用数据类型的序列化和反序列化示例如下:

let mut sink = Sink::new(16);
sink.write(1u128);
sink.write(true);
let addr = Address::repeat_byte(1);
sink.write(addr);
sink.write("test");
let vec_data = vec!["hello","world"];
sink.write(&vec_data);
let tuple_data = (1u8, "hello");
sink.write(tuple_data);
let mut source = Source::new(sink.bytes());
let res1:u128 = source.read().unwrap_or_default();
let res2:bool = source.read().unwrap_or_default();
let res3:Address = source.read().unwrap_or_default();
let res4:&str = source.read().unwrap_or_default();
let res5:Vec<&str> = source.read().unwrap_or_default();
let res6:(u8, &str) = source.read().unwrap_or_default();
assert_eq!(res1, 1u128);
assert_eq!(res2, true);
assert_eq!(res3, addr);
assert_eq!(res4, "test");
assert_eq!(res5, vec_data);
assert_eq!(res6, tuple_data);

sink.write()方法传入的参数支持所有已经实现 Encoder 接口的数据类型,序列化的时候需要声明其数据类型,比如1u128等。source.read()方法可以读取所有已经实现 Decoder 接口的数据类型,反序列化的时候要指明其结果的数据类型,如下面的代码:

let res1:u8 = source.read().unwrap_or_default();

在 res1后面要声明其数据类型是 u8。也可以写成:

let res1 = source.read_byte().unwrap_or_default();

在读取的时候,已经使用了read_byte,所以不需要在 res1后面声明数据类型了。

解析调用合约的参数

合约在获得调用参数时通过runtime::input()方法获得,但是该方法仅能拿到bytearray格式的参数,需要反序列化成对应的参数,第一个参数是合约方法名,后面的是方法参数,示例如下:

let input = runtime::input();
let mut source = Source::new(&input);
let method_name: &str = source.read().unwrap();
let mut sink = Sink::new();
match method_name {
    "transfer" => {
        let (from, to, amount) = source.read().unwrap();
        sink.write(ont::transfer(from, to, amount));
    }
    _ => panic!("unsupported action!"),
}

Rust 支持类型推导,大部分情况下可以省略类型声明,比如在ont::transfer()方法中已经声明了 from、to 和 amount 的数据类型,所以在前面解析 from、to 和 amount 的时候没有声明数据类型。

如果参数是 Vec<&str>类型,可以按照如下的方式进行反序列化:

let param:Vec<&str> = source.read().unwrap();

如果参数是 Vec<(&str,U128,Address)>类型,可以按照如下的方式进行反序列化:

let param:Vec<(&str,U128,Address)>= source.read().unwrap();

1

序列化自定义结构体

在合约开发中,我们经常需要对struct类型的数据进行序列化和反序列化,为了达到这个目的,我们仅需要在struct声明的地方加上#[derive(Encoder, Decoder)]即可,示例如下:

#[derive(Encoder, Decoder)]
struct ReceiveRecord {
    account: Address,
    amount: u64,
}

#[derive(Encoder, Decoder)]
struct EnvlopeStruct {
    token_addr: Address,
    total_amount: u64,
    total_package_count: u64,
    remain_amount: u64,
    remain_package_count: u64,
    records: Vec<ReceiveRecord>,
}

在使用该功能的时候,要注意struct的每个字段必须都已经实现EncoderDecoder接口,加上该注解后,就可以按照如下的方式序列化和反序列化了:

let addr = Address::repeat_byte(1);
let rr = ReceiveRecord{
    account: addr,
    amount: 1u64,
};
let es = EnvlopeStruct{
    token_addr: addr,
    total_amount: 1u64,
    total_package_count: 1u64,
    remain_amount: 1u64,
    remain_package_count: 1u64,
    records: vec![rr],
};
let mut sink = Sink::new(16);
sink.write(&es);

let mut source = Source::new(sink.bytes());
let es2:EnvlopeStruct = source.read().unwrap();

assert_eq!(&es.token_addr,&es2.token_addr);
assert_eq!(&es.total_amount,&es2.total_amount);
assert_eq!(&es.total_package_count,&es2.total_package_count);
assert_eq!(&es.remain_amount,&es2.remain_amount);
assert_eq!(&es.remain_package_count,&es2.remain_package_count);

从链上读取指定类型的数据

合约中不同类型的数据在保存到链上之前,需要先序列化成bytearray类型的数据,从链上读取数据时,读到的也都是bytearray类型的数据,需要反序列化成指定的数据类型。database模块提供了更加简便的接口供开发者使用。

  • fn put<K: AsRef<[u8]>, T: Encoder>(key: K, val: T) 根据 key 保存 T 类型的数据,要求 T 类型实现Encoder接口示例:
let es = EnvlopeStruct{
    token_addr: addr,
    total_amount: 1u64,
    total_package_count: 1u64,
    remain_amount: 1u64,
    remain_package_count: 1u64,
    records: vec![rr],
};
database::put("key", es);

我们从database::put的源码可以看到,该方法在执行的时候,会先序列化 es 参数,然后将序列化结果保存到链上。

  • fn get<K: AsRef<[u8]>, T>(key: K) -> Option<T> where for<'a> T: Decoder<'a> + 'static,根据 key 获得指定类型 T 的数据,其中,T 类型要求实现了 Decoder 接口。示例:
let res:EnvlopeStruct = database::get("key").unwrap();

我们从database::get的源码可以看到,该方法在执行的时候,会先从链上拿到 bytearray 类型的数据,然后再反序列化得到 EnvlopeStruct 类型的数据。

1

跨合约参数传递

在跨合约调用的时候,参数是以 bytearray 的格式进行传递的,所以需要将不同类型的数据进行序列化,下面以跨合约调用 Wasm 合约为例。

let (contract_address,method,a, b): (&Address,&str,u128, u128) = source.read().unwrap();
let mut sink = Sink::new(16);
sink.write(method);
sink.write(a);
sink.write(b);
let resv = runtime::call_contract(contract_address, sink.bytes()).expect("get no return");

该段代码实现了一个合约调用另一个 Wasm 合约的功能,调用的合约先解析出来参数,然后根据解析的参数去调用另外一个合约时,需要将方法名和方法参数依次序列化,通过 sink.bytes()方法拿到序列化的结果,最后通过runtime模块中的 call_contract方法实现跨合约调用的功能。

1

结语

本文详细介绍了 Ontology Wasm 合约中参数序列化和反序列化的方法。主要包括在合约执行的过程中如何解析外部传进来的参数,在合约中自定义的结构体如何进行序列化和反序列化,链上读取的 bytearray 类型的数据如何反序列化成原始的数据类型,以及原始的数据类型如何序列化成 bytearray 类型的数据保存到链上,最后介绍了跨合约调用中如何进行参数的传递。

Wasm 技术具有十分庞大活跃的社区,且 Wasm 可以支持更加复杂的合约,并拥有丰富的第三库支持,生态十分完善,开发门槛比较低,在 Ontology 链上开发和部署 Wasm 合约对于想要入门的开发者来说十分友好。欢迎全球各地的开发者加入我们的技术社区,共建繁荣的 Ontology 生态。

本文分享自微信公众号 - 本体研究院(ontologyresearch),作者:Lucas

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-10-22

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 本体技术视点 | 手把手教你Wasm合约开发

    Ontology Wasm 自从上线测试网以来,得到了社区开发人员的极大关注。因为这项技术使得业务逻辑复杂的 dApp 合约上链成本降低,极大丰富 dApp 生...

    本体Ontology
  • 本体技术视点 | 虚拟机中引用性动态语言对象模型思考

    Ontology 的 NeoVM 虚拟机新增加了 DCALL、HAS_KEY、KEYS 以及 VALUES 等几条新的指令。因此,基于 NeoVM 的引用性动态...

    本体Ontology
  • 本体技术视点 | 关于Merkle Proof问题的一点思考

    Merkle Patricia Tree(又称为 Merkle Patricia Trie)是一种经过改良、融合了 Merkle tree 和前缀树两种树结构优...

    本体Ontology
  • Swift基础---Integers

    用户3004328
  • swift基础1

    用户2554571
  • ES6

    ES的全称是ECMAScript,它是由ECMA国际标准化组织制定的一项脚本语言的标准化规范。

    eadela
  • 变量的解构赋值

    上面代码中,函数move的参数是一个对象,通过对这个对象进行解构,得到变量x和y的值。如果解构失败,x和y等于默认值。

    达达前端
  • Swift3.0 - 数据类型

    // 插入操作 shoppingList.insert("Maple Syrup", at: 0)

    酷走天涯
  • ES2018

    但如果数据源是异步的,for...of循环就只能拿到一堆Promise,而不是想要的值:

    ayqy贾杰
  • ES6语法基础之let用法

    简单讲解一些ES6语法基础!了解一些es6新特性!当然下一步需要学习的vue框架也是基于es6的,因此很有必要学习下es6语法,接下来几次简单讲解es6语法!

    十月梦想

扫码关注云+社区

领取腾讯云代金券