Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

想用Wasm开发dApp 入门教程

星火车品
77 0 0
新建合约
% a/ \. j. T( ?+ N5 j         Created library `helloworld` package- u  K: I/ H8 w
新建的合约目录结构如下:
! n, {  g, I" F9 Q8 T$ j├── Cargo.toml
6 I1 B4 ]! M( _" ]+ q( c; K, e+ z└── src: r3 f4 T0 D$ L& V# [) `1 d
        └── lib.rs
8 W% x* f/ w/ |+ E6 y一个 Rust 版本的 Wasm 合约包含两部分组成,一部分是Cargo.toml配置文件,用于配置项目信息,一部分是src/lib.rs用于编写合约逻辑。
0 `; O6 t* p; H8 o6 V4 Q引入Ontology Wasm合约开发工具库- C$ G3 S4 y7 L  g- F8 n# L
在生成的Cargo.toml文件中引入 Ontology Wasm 合约开发工具库ontio-std,使用[dependencies]配置项来完成引入该工具库的动作。: v( B, t6 M+ x
name = "helloworld"
% J% y7 W8 t" I% E7 }# n  kversion = "0.1.0"/ v( z! ^/ Z% x* Q5 K0 f2 v
authors = ["Lucas "]# W+ A, T! e+ J
edition = "2018"6 g, T& W9 m5 `% p9 y6 u- v$ }& [5 _
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
9 T, {; O' \0 n+ j  i  m[dependencies]4 i8 g, N. {$ V( o( R
ontio-std = {git="https://github.com/ontio/ontology-Wasm-cdt-rust.git"}0 C0 `; q$ G, W, N! o* d
由于我们合约要以库的形式进行编译,所以还需要在Cargo.toml文件里加上[lib]配置信息,一个完整的 Cargo.toml 配置文件如下:
+ @$ @' R" Q. L; {/ zname = "helloworld"
* G/ w/ g- l% @2 ~7 N+ S6 ]version = "0.1.0"8 N+ Y, W+ e( h2 I
authors = ["Lucas "]
9 C8 Y9 Z) U1 vedition = "2018"
2 l& g' H4 P2 M4 N6 ?1 ]# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
& \8 u5 f9 [7 @7 s% i: |- C[lib]7 F' u: f0 U5 Z, f* f0 ]
crate-type = ["cdylib"]0 |/ h2 R; g- G, ~. [
path = "src/lib.rs"
0 k% P8 `  J3 l! x# Z$ p& ~[dependencies]
8 ~4 x) G0 a+ a" L& W: @ontio-std = {git="https://github.com/ontio/ontology-Wasm-cdt-rust.git"}
* Z- ?6 Y7 w! d: ~0 a; c8 F[features]
  B( I: P: D/ i3 C8 emock = ["ontio-std/mock"]  S/ `, I/ Z3 f, q2 h$ B7 b
[features]用于开启一些不稳定特性,只可在 nightly 版的编译器中使用。此处我们引入了ontio-std/mock模块,该模块模拟了与链交互的接口,也就是可以通过该模块进行合约中与链交互的模拟测试,主要方便了合约开发者在本地测试合约中与链交互的功能是否正常,无需部署到链上,就可以实现测试的功能,在后面的章节中我们会详细介绍该模块的使用方法。7 A; ^. v5 \3 C
生成ontio-std库API文件9 R' `8 f/ |9 P* [
虽然我们引入了开发 Ontology Wasm 合约需要的工具库,但是我们还不知道该工具库中都有哪些 API 可以用,我们可以通过下面的命令生成该库的 API 文档。
, W" j1 U% Y7 ?2 ecargo doc
6 J, r% v5 G6 o" i5 B: h, u执行成功后的目录结构如下:
0 e& [. U4 R% m6 c├── Cargo.lock+ y: i4 Z% ~2 A5 D  v
├── Cargo.toml/ k- }2 j) p# j
├── src
- w4 q! t. H; z/ o7 ~9 m│   └── lib.rs5 x: U! y' ~4 n" c/ j
└── target
6 {1 Q1 _9 m" P/ A4 `( B& a        ├── debug& b$ e" `. I& X* F
        └── doc! b+ O1 ]6 _# G; o+ t+ w. K
生成的 API 接口文档在 doc 目录下。我们可以通过浏览器打开 settings.html 文件查看。如下图所示:0 w6 M! l) w' q9 a" K! @8 ]
3 w4 ]0 E9 [8 a! P
请在左侧目录栏找到 ontio_std 库,点击该选项,如下图:
2 W* M- G3 d& w& A6 b( C) J0 R- f5 p0 h  e4 Y1 ^4 T
上面列出了ontio_std库中封装好的所有模块,在开发合约的过程中,可以使用这些模块中的功能。
% u' G, y: v& E编写合约逻辑2 N' |" J+ \# S" @! |, a
新建的 helloworld 合约lib.rs文件内容仅有一个测试代码,在项目根目录下,执行cargo test 来执行该测试代码。
" l/ a6 ?( U/ P: O: }* Z% P8 H#[cfg(test)]
0 C5 g4 c1 |; D. H# {7 Pmod tests {( n( A; i$ R! E+ ^6 \
        #[test]
! ?4 f1 h: U( ^; D8 k; R: R  z3 c" V        fn it_works() {; H0 A3 s+ A0 A
                assert_eq!(2 + 2, 4);
! i0 d  i" C" l1 o: ^        }+ P5 S- Y3 Q6 s% i- K- h
}
6 _; x9 s5 Z$ z7 ~! ?4 R) G下面开始编写合约逻辑:3 P3 q' ~1 W9 F% H5 h. h
第一步:在lib.rs文件中引入刚才在Cargo.toml配置文件中添加的ontio-std依赖,为了屏蔽rust标准库中的方法,我们加上#![no_std]注解。6 P' V- p/ M5 }. H$ s6 V
#![no_std]
$ \/ g! A/ i, s! Uextern crate ontio_std as ostd;" ]3 [5 `; H, y* Z4 D
#[cfg(test)]  V; ~. \- o; D; [
mod tests {
& {/ R% u4 m, d* E  X6 u   ...
6 z7 C$ \% C7 L4 m; e% N}, v9 L$ Y6 l6 v; h
第二步:添加invoke函数,该函数是 Ontology Wasm 默认的入口函数,在这个合约中,我们实现一个方法获得调用的参数并将参数返回出去,代码如下:
3 B6 i/ f, K7 O) V7 i2 c#![no_std]4 Z4 y/ c$ T1 {# H3 w9 S/ C
extern crate ontio_std as ostd;
! k0 ?* T" X" huse ostd::abi::{Sink, Source};# B8 z6 d; {8 R1 s6 g
use ostd::prelude::*;
9 c$ ^. S  h* tuse ostd::runtime;
% p: X! {( e/ A! @- k& q; g. [  c; ^fn say_hello(msg: &str) -> String {
5 k# J$ M4 f. }" U2 H. M' z( S        return msg.to_string();
3 y. ~* U; X7 U8 r}
3 z7 G+ c$ d& q#[no_mangle]
" h6 d4 \2 y6 |& S! G, Ofn invoke() {
' v; N2 Z: T3 U2 T0 X- s* G        let input = runtime::input();
6 B% e; w1 G! Z' E' i8 \        let mut source = Source::new(&input);
  d$ P4 C: ]% x& X# N9 Z9 |& z        let action: &[u8] = source.read().unwrap();  k/ t* K- v$ U6 W! S1 z
        let mut sink = Sink::new(12);& \& Z! }% Z7 H
        match action {( T- l/ W7 K% w+ j, k$ W
                b"hello" => {
) [9 P* P/ }" \: A8 _# c  l                let msg = source.read().unwrap();
+ h, E# G. o, C& ~( Z3 p' G& G5 B                sink.write(say_hello(msg));; `) C: b& @( `9 ]0 J% ]( \9 G! g
                },
8 j0 G8 C2 B3 V' }# v/ ~                _ => panic!("unsupported action!"),
8 v" Y. W8 K8 G: V        }* o: U2 o# ?$ _; U# {; [& j: o
        runtime::ret(sink.bytes())$ |7 F' S. x  W: r3 M& {
}
7 q/ Q7 L% @( E#[test]* q6 |2 |+ w# ?) c- U7 h6 n/ I
fn test_hello() {
* F9 [3 p0 W$ b5 k/ B8 {9 K2 o        let res = say_hello("hello world");
, Y* n) I" r0 K8 `0 u' E9 e        assert_eq!(res, "hello world".to_string());
! P& E* }8 A! Q}* p" @- ]' X  |
在合约中,我们引入了ontio-std库里面abi模块的Sink和Source数据类型。Source用于读取外部调用合约中的方法时传进来的方法名和方法参数信息,Sink用于合约中不同类型的数据序列化成 bytearray。ontio-std库里面的prelude模块提供了一些常用的数据类型,比如Address、U128和String等。把合约执行的结果返回给调用合约的程序,需要使用runtime::ret()方法,runtime模块封装与链交互的接口。+ V' U' G1 ^3 J# h$ g
至此,一个简单的返回传入参数的合约已经完成。
: Q# {7 S: M) N6 e* S0 h: _编译合约2 g+ v  O( U1 T+ m7 x& E/ z
用 Rust 编写的合约源代码需要编译成Wasm字节码,才能部署到链上,执行下面的命令编译合约:# _- j3 ?! z" S5 B0 i& g7 b4 A
RUSTFLAGS="-C link-arg=-zstack-size=32768" cargo build --release --target Wasm32-unknown-unknown0 p, A  M0 B, R, b+ f; r4 E4 i: D/ [
在上面的命令中,RUSTFLAGS="-C link-arg=-zstack-size=32768"表示设置 rustc 编译时使用的栈大小为32kb,rustc 编译默认设置的栈内存大小是1MB,对合约来说是巨大的浪费,因此在编译时设置下栈的大小,32kb 对于绝大多数合约来说是够用的。 Wasm32-unknown-unknown 表示在编译目标。
( O3 J& C  ^, z, N( y该代码执行后,会生成target文件夹,目录结构如下:
  J7 l) c, q, Z' U, o6 e; `.- D1 a6 p5 [0 K1 Z( H% z0 T7 J, [% v7 |
├── release
5 b# ~9 Y9 H2 p6 K! A! S* m! Z│   ├── build
) w* `1 V. F+ h: v. P8 K│   ├── deps9 t& H/ r8 S0 W; B# D
│   ├── examples
2 p2 f9 w7 v8 }$ x* f4 c( f│   └── incremental
: X6 O' P* v. y└── Wasm32-unknown-unknown9 r. Y4 b3 W+ Z& N( b7 m) F0 J! P  _
        └── release
* V1 `- o% m2 G, p2 B2 `+ F编译好的合约字节码文件为 target/Wasm32-unknown-unknown/release/ 目录下名为helloworld.Wasm的文件。+ v0 _2 r, l- a- L+ g
优化合约字节码
* S6 V* N9 D# ?8 W) F, _编译好的Wasm字节码文件会比较大,部署到链上需要的存储空间会比较多,费用也会比较高,但是我们可以使用ontio-Wasm-build工具将 Wasm 字节码减小。8 ?! k( f# N/ {0 D" W
执行下面的命令优化该合约字节码:1 a* L8 \, u1 g" x) z, S" e2 P$ C8 s
ontio-Wasm-build ./target/Wasm32-unknown-unknown/release/hellloworld.Wasm
% D! ]  ]/ s' O$ H该命令执行完后,会在./target/Wasm32-unknown-unknown/release/生成的文件如下:
* |  @% }; ^( X+ ?  O- nhelloworld_optimized.Wasm 优化后的Wasm合约字节码;
/ w, v! W2 ^) `5 qhelloworld_optimized.Wasm.str 优化后的Wasm合约字节码的hex编码格式。
1 `  T. L  R: ^" f. h0 g$ a测试合约
  p/ }0 m  V. R/ [9 h我们将在本地测试网络中测试刚刚编写的合约。
! y5 ^+ e$ Q. @$ J首先,生成钱包文件,本地测试网启动需要钱包文件,执行如下的命令:
8 S1 V( I! W) H5 A) A2 p./ontology account add
2 b$ q- e. b; H/ q/ W( d, S: A其次,启动我们搭建好的本地测试网节点,执行下面的命令:
) U$ N4 O. d& U./ontology --testmode --loglevel 1
4 C1 s3 Y$ f5 h% P- n2 }, L–testmode表示以测试的模式启动。
' M" `& V) ~) M  U2 U. D2 K8 r–loglevel 1 表示将日志级别设置为 debug 模式。( y. w5 u* ^, J8 X
然后,部署合约:
# Z0 b* N/ @, G9 I1 m$ ./ontology contract deploy --vmtype 3 --code ./helloworld.Wasm.str --name helloworld --author "author" --email "email" --desc "desc" --gaslimit 22200000: h7 S7 p) w! q4 W" e7 e* g
Password:* P! l4 M) z: T
Deploy contract:# Q* E! E! L# @8 _
  Contract Address:913ea5298565123847ffe61ec93986a52e824a1b9 {+ V, f- g" X' @9 O
  TxHash:8386410c2ccdc5127e5bd893072a152afaa5dcf20c9b736583f803cba4f461e6* s( Z6 X, q, m4 c% g
Tip:
3 Z! I* ?/ ~9 O  Using './ontology info status 8386410c2ccdc5127e5bd893072a152afaa5dcf20c9b736583f803cba4f461e6' to query transaction status.
- I; I! U6 F* [–vmtype 3 表示部署的合约类型是Wasm合约,目前 Ontology 链除了支持Wasm合约还支持NeoVM合约,部署的时候要注明合约类型。 --name helloworld 表示部署合约名字是helloworld。 --author “author” 表示部署合约作者是author。 --email “email” 表示部署合约 email 是email。 --gaslimit 22200000表示部署合约需要的费用 gaslimit 上限是22200000。, D* }# m. w0 u5 c
最后,调用合约中的方法。由于我们在 invoke 函数里仅定义了hello方法,并且该方法将输入的参数内容直接返回。所以,调用合约的时候,第一个参数是方法名,第二个参数是合约中的该方法需要的参数。
& G; C3 X# l- t8 P因为我们调用的方法没有更新链上的数据,仅仅是把输入的参数返回,我们在调用合约的时候,要加上预执行标签–prepare,否则,我们看不到合约返回的结果。: K/ P: ^3 [* S) M0 B- U
根据合约地址调用合约中的方法。该部分详细信息请参考命令行合约调用。9 b8 L, F7 E  Z5 V# C4 y1 P; t. G
$ ./ontology contract invoke --address 913ea5298565123847ffe61ec93986a52e824a1b --vmtype 3 --params 'string:hello,string:hello world' --version 0 --prepare
8 b& X3 ~4 h: @+ V$ E4 S* `Invoke:1b4a822ea58639c91ee6ff473812658529a53e91 Params:["hello","hello world"], r: p2 |( `# c% p
Contract invoke successfully
; X) s2 F& {1 V9 N, M: ]' R  Gas limit:20000
' q, D3 t) B( B- R7 R. U% `  Return:0b68656c6c6f20776f726c64 (raw value)
: S9 e0 m% _7 K$ W合约中我们的返回值是hello world,就像上一篇技术视点中提到的那样,执行结果返回了该值的hex编码68656c6c6f20776f726c64。1 K& m$ v! t( b' P5 y$ ?
至此,我们在不依赖模板的情况下,完成了一个简单的 Ontology Wasm 合约,并进行了部署和调试。% x* U' z" Q6 w" D2 t# `. m
结语
5 Y/ D" f7 a! ]/ [: ?0 ^6 [( h在本期技术视点中,我们简单介绍了如何在不依赖模板的情况下,完成一个简单的 Ontology Wasm 合约的开发,并进行了测试。同时,我们也介绍了 Ontology Wasm 工具库 API 文档的生成方式,方便开发者查询和调用已提供的功能。相信你一定会有所收获。下期我们将会介绍 Ontology Wasm 合约获得调用参数以及合约中数据的序列化和反序列化。欢迎关注!
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

星火车品 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    12