Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

想用Wasm开发dApp 入门教程

星火车品
255 0 0
新建合约
) ]$ o) u1 K# W6 V6 f         Created library `helloworld` package/ o# i/ n9 a7 g
新建的合约目录结构如下:
+ [- [4 A) N1 T6 A├── Cargo.toml
' C, v# c6 {5 l* d4 V/ b) N└── src
, {- S0 n/ b: y" j        └── lib.rs3 i$ Z4 g4 O( k( E$ K. W/ A% A- ~- ?
一个 Rust 版本的 Wasm 合约包含两部分组成,一部分是Cargo.toml配置文件,用于配置项目信息,一部分是src/lib.rs用于编写合约逻辑。
' @! Y3 Z8 K1 f引入Ontology Wasm合约开发工具库: ^8 @" g5 q7 h3 O- Y
在生成的Cargo.toml文件中引入 Ontology Wasm 合约开发工具库ontio-std,使用[dependencies]配置项来完成引入该工具库的动作。
0 T* u8 h" ~9 g' V: e. L+ Dname = "helloworld"( M6 A4 F+ S& N. ~5 c( K
version = "0.1.0"
5 u. S1 H: i. c: i* }) zauthors = ["Lucas "]
- ^; M- F' P0 `! Fedition = "2018"
7 v4 n6 L. e- l* a2 u4 _! W# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
0 P( v7 b' ^1 u) N% d[dependencies]
5 i8 ^% V- C. Kontio-std = {git="https://github.com/ontio/ontology-Wasm-cdt-rust.git"}2 Z( g; D  [  I/ \
由于我们合约要以库的形式进行编译,所以还需要在Cargo.toml文件里加上[lib]配置信息,一个完整的 Cargo.toml 配置文件如下:+ B" }4 ]3 p6 D( [# V0 q. P
name = "helloworld", d. V* m* ?" K3 b: g' ^0 q
version = "0.1.0"
6 `& M; Y3 B. `( G2 mauthors = ["Lucas "]( H" T% b) i+ D$ M$ ]
edition = "2018"" x7 ?# r% V- g1 w9 e% O* L
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
- Z$ f" [+ L" @% w4 F- J9 w[lib]' B7 b2 l. W/ [0 s; t/ `
crate-type = ["cdylib"]) f/ o( t3 l  R$ h% E" j% c0 G
path = "src/lib.rs"
0 y* P/ S& ?( I) }" P0 A1 q6 X[dependencies]
% m) k2 \9 Z! r3 h% U' b0 _& `- G7 Pontio-std = {git="https://github.com/ontio/ontology-Wasm-cdt-rust.git"}) s, I6 t% s; Z, F% P6 ]# R
[features]& l, g9 e% }: ?* }9 P
mock = ["ontio-std/mock"]
( V. p9 H5 L2 [0 [0 s$ N[features]用于开启一些不稳定特性,只可在 nightly 版的编译器中使用。此处我们引入了ontio-std/mock模块,该模块模拟了与链交互的接口,也就是可以通过该模块进行合约中与链交互的模拟测试,主要方便了合约开发者在本地测试合约中与链交互的功能是否正常,无需部署到链上,就可以实现测试的功能,在后面的章节中我们会详细介绍该模块的使用方法。7 h) F& D' m% w1 P/ i( t" S
生成ontio-std库API文件
/ @9 Z' x4 b/ T0 [虽然我们引入了开发 Ontology Wasm 合约需要的工具库,但是我们还不知道该工具库中都有哪些 API 可以用,我们可以通过下面的命令生成该库的 API 文档。1 g  E: r, j4 G: R( ]
cargo doc" ?5 E) A# ]4 L# F9 l* q
执行成功后的目录结构如下:; t/ Q$ N: C# i' B8 N9 O8 f
├── Cargo.lock  f# o, U" i( x
├── Cargo.toml
) T! C5 _$ p* X; Y6 m! z* j├── src
  _; I8 _% H& o8 t& N│   └── lib.rs6 c. k0 _& L% G8 @9 K* Q
└── target
0 F$ i9 r9 Q8 N7 Z/ A% J& l        ├── debug
. r* ]* p" Q9 J& X        └── doc
0 \: S/ c& e. X6 ?生成的 API 接口文档在 doc 目录下。我们可以通过浏览器打开 settings.html 文件查看。如下图所示:
0 q  d# a, p# i# o. X
" a# y. j; \$ [6 N请在左侧目录栏找到 ontio_std 库,点击该选项,如下图:
  o/ f1 }7 q% H6 y  J  ]' f) ~( k; q- c. |# U* F
上面列出了ontio_std库中封装好的所有模块,在开发合约的过程中,可以使用这些模块中的功能。
+ P+ y! K$ z. O/ p  V1 Q! [1 n6 X编写合约逻辑  m% h# r6 n) X; c: q0 }6 J! ]7 L
新建的 helloworld 合约lib.rs文件内容仅有一个测试代码,在项目根目录下,执行cargo test 来执行该测试代码。
# |6 `. W" R1 B/ I8 u9 d# c; f#[cfg(test)]
, M8 [. h, f- ^$ ]% qmod tests {1 e  a; U( r" f# w- c% f
        #[test]
: p2 l' p. T& D: F$ U0 _% l" |        fn it_works() {0 Y4 V) |3 R4 J* _. v
                assert_eq!(2 + 2, 4);
/ ^+ K) C- X. o" s+ Y& n5 [  W        }9 L! ~) v4 k, V% t2 f) R( N1 Z; S
}
& D3 G* n3 ^  D9 o下面开始编写合约逻辑:$ `/ f# i$ m) v  n
第一步:在lib.rs文件中引入刚才在Cargo.toml配置文件中添加的ontio-std依赖,为了屏蔽rust标准库中的方法,我们加上#![no_std]注解。8 i1 h& M% _) \/ t+ J
#![no_std]3 C4 |' P9 N. g& s. U
extern crate ontio_std as ostd;
/ P0 |4 H- I8 Z+ i7 M- ?#[cfg(test)]
$ R8 {! A  }# S9 e: xmod tests {' ?+ |+ t( o4 v4 k" R# y, f
   ...2 J; s. ~- A1 n
}
6 t; `0 S" j. a9 r第二步:添加invoke函数,该函数是 Ontology Wasm 默认的入口函数,在这个合约中,我们实现一个方法获得调用的参数并将参数返回出去,代码如下:
) `* U; U" H! R& D#![no_std]# A) ^/ D+ {4 B9 K
extern crate ontio_std as ostd;" m( p  ^5 n; M4 x
use ostd::abi::{Sink, Source};
' m7 h+ T! v& o1 T! ?, ^8 Ause ostd::prelude::*;
3 ]2 d9 R9 ^+ |( ?/ _$ |use ostd::runtime;# Z  a% i/ D' @5 u" s, C
fn say_hello(msg: &str) -> String {0 w' V" y! m4 f3 p% D3 c) X. [) d1 f
        return msg.to_string();
6 U5 {3 Y; q$ w2 {* S! c' [}
" }0 v  p8 X9 }5 y2 L/ w( E#[no_mangle]
. {: V8 T4 i- a3 ^fn invoke() {
/ {" b9 ]; Q, [& Y& L- z8 M        let input = runtime::input();
. S4 V, l: n1 D' n( W        let mut source = Source::new(&input);# u2 a. U9 d- }8 F( j7 w+ _: F3 m5 L; s
        let action: &[u8] = source.read().unwrap();
8 O8 x) ?2 X, P$ s; Y- m        let mut sink = Sink::new(12);
3 n4 k3 ?" b% L        match action {
2 p2 r# x$ C2 M" z, Y# T1 E                b"hello" => {! E5 r2 g/ L7 O9 j# _/ N
                let msg = source.read().unwrap();
/ Q0 A; F' s! b" L9 X3 a& T  U                sink.write(say_hello(msg));9 E" l* I+ p3 K# T: s
                },  T- X4 P% r  w% p4 [1 q4 j( Y
                _ => panic!("unsupported action!"),. ^2 G3 u- H" u5 F
        }: e0 g- {. m" t2 ?$ ~2 W
        runtime::ret(sink.bytes())7 [  ?7 ?% C% ~
}, o# j$ G) R- N! M4 |4 G
#[test]7 ^1 t' u5 k4 j8 i
fn test_hello() {6 D! u- x# S! k1 A2 ~
        let res = say_hello("hello world");
" q8 n- \, M3 v. T: Y        assert_eq!(res, "hello world".to_string());
, w. W6 E! [' S. e4 B7 o3 A& S9 j}& [0 w) q# a5 \- t9 h& L
在合约中,我们引入了ontio-std库里面abi模块的Sink和Source数据类型。Source用于读取外部调用合约中的方法时传进来的方法名和方法参数信息,Sink用于合约中不同类型的数据序列化成 bytearray。ontio-std库里面的prelude模块提供了一些常用的数据类型,比如Address、U128和String等。把合约执行的结果返回给调用合约的程序,需要使用runtime::ret()方法,runtime模块封装与链交互的接口。
, E3 p) U- z& ]" ]6 E' X至此,一个简单的返回传入参数的合约已经完成。
, _8 T; P5 ?4 C: F: v# w8 R4 q编译合约
$ c7 e1 z$ _6 ?& g1 @, V  ]用 Rust 编写的合约源代码需要编译成Wasm字节码,才能部署到链上,执行下面的命令编译合约:) |, ]( e* b( a* S
RUSTFLAGS="-C link-arg=-zstack-size=32768" cargo build --release --target Wasm32-unknown-unknown
/ X! C  \: [, I6 l" z在上面的命令中,RUSTFLAGS="-C link-arg=-zstack-size=32768"表示设置 rustc 编译时使用的栈大小为32kb,rustc 编译默认设置的栈内存大小是1MB,对合约来说是巨大的浪费,因此在编译时设置下栈的大小,32kb 对于绝大多数合约来说是够用的。 Wasm32-unknown-unknown 表示在编译目标。: D, W9 v  v  `3 z: R! h
该代码执行后,会生成target文件夹,目录结构如下:* n4 h! v: J' W, W
.
: [4 I' D) T( X7 m; Q├── release# `1 m$ g0 ^  j) I7 S
│   ├── build
" U; `/ ]' b6 ?9 J7 h3 X│   ├── deps
) i$ I0 F% w/ e! G+ U│   ├── examples1 j! f6 D- S5 w+ Y$ F
│   └── incremental- v, n2 r; m$ Z! r. V8 c
└── Wasm32-unknown-unknown+ S" ^+ ^: C5 Y) B
        └── release. c, }( v( B" c- }
编译好的合约字节码文件为 target/Wasm32-unknown-unknown/release/ 目录下名为helloworld.Wasm的文件。
# T& g" R: k; V优化合约字节码" V  B" z, ~3 [- y& K
编译好的Wasm字节码文件会比较大,部署到链上需要的存储空间会比较多,费用也会比较高,但是我们可以使用ontio-Wasm-build工具将 Wasm 字节码减小。
* k7 ?1 r1 ^( `; n' [执行下面的命令优化该合约字节码:
( Z$ O0 Y; L0 h. qontio-Wasm-build ./target/Wasm32-unknown-unknown/release/hellloworld.Wasm
0 m2 _2 I8 _% D! S% }该命令执行完后,会在./target/Wasm32-unknown-unknown/release/生成的文件如下:! Q1 _2 U6 `% a! F
helloworld_optimized.Wasm 优化后的Wasm合约字节码;" h' ]5 R/ X; w
helloworld_optimized.Wasm.str 优化后的Wasm合约字节码的hex编码格式。9 z4 P" N* \9 Q4 u( l1 E  C0 R
测试合约# \% }% b3 C, q. _$ B' ~' Y
我们将在本地测试网络中测试刚刚编写的合约。9 n  c5 L( U4 c$ w! w
首先,生成钱包文件,本地测试网启动需要钱包文件,执行如下的命令:
6 i3 u" U, P1 x3 U0 E1 |./ontology account add2 `( m2 m$ `2 {
其次,启动我们搭建好的本地测试网节点,执行下面的命令:+ R, Y9 i( U: A$ ^
./ontology --testmode --loglevel 1  k8 h$ R% b( B- i  D1 _# F+ j
–testmode表示以测试的模式启动。/ }0 j& l; h3 o+ R
–loglevel 1 表示将日志级别设置为 debug 模式。# u4 s" t3 }" e. L3 ^; s* k
然后,部署合约:
4 S) |% _/ {' P  P$ ./ontology contract deploy --vmtype 3 --code ./helloworld.Wasm.str --name helloworld --author "author" --email "email" --desc "desc" --gaslimit 222000003 m# Y  ?- q( O
Password:5 M0 ~: E! D" ]" p4 T
Deploy contract:) T% b) N; Z. O' `! S7 a
  Contract Address:913ea5298565123847ffe61ec93986a52e824a1b
6 \! B! z8 I. M* R+ u$ Z& m  TxHash:8386410c2ccdc5127e5bd893072a152afaa5dcf20c9b736583f803cba4f461e6
' @% h2 H: i. J6 u6 N5 O/ ?/ [Tip:
6 t  N& ^, E- V6 ?( F  g  Using './ontology info status 8386410c2ccdc5127e5bd893072a152afaa5dcf20c9b736583f803cba4f461e6' to query transaction status.: Q6 ^) c; D% ~" L* R8 I# R
–vmtype 3 表示部署的合约类型是Wasm合约,目前 Ontology 链除了支持Wasm合约还支持NeoVM合约,部署的时候要注明合约类型。 --name helloworld 表示部署合约名字是helloworld。 --author “author” 表示部署合约作者是author。 --email “email” 表示部署合约 email 是email。 --gaslimit 22200000表示部署合约需要的费用 gaslimit 上限是22200000。+ o( u- n+ z, S1 W
最后,调用合约中的方法。由于我们在 invoke 函数里仅定义了hello方法,并且该方法将输入的参数内容直接返回。所以,调用合约的时候,第一个参数是方法名,第二个参数是合约中的该方法需要的参数。/ B( y3 b8 i9 b! `! c
因为我们调用的方法没有更新链上的数据,仅仅是把输入的参数返回,我们在调用合约的时候,要加上预执行标签–prepare,否则,我们看不到合约返回的结果。
8 Z) E9 c" N( Z5 Z根据合约地址调用合约中的方法。该部分详细信息请参考命令行合约调用。
3 e# \/ R; n- g4 a- K$ ./ontology contract invoke --address 913ea5298565123847ffe61ec93986a52e824a1b --vmtype 3 --params 'string:hello,string:hello world' --version 0 --prepare- L% p8 m) o0 ^- X5 b; r" U; d5 _
Invoke:1b4a822ea58639c91ee6ff473812658529a53e91 Params:["hello","hello world"]
- d; _/ C& c; z5 T6 ?Contract invoke successfully
% e* X+ h* o+ A8 Q  Gas limit:20000
# S0 `8 Z$ M( Q5 `  Return:0b68656c6c6f20776f726c64 (raw value)! O( }9 R7 L! R# i: F2 X5 P
合约中我们的返回值是hello world,就像上一篇技术视点中提到的那样,执行结果返回了该值的hex编码68656c6c6f20776f726c64。
* R6 S, G, G+ I: O8 P) n: x至此,我们在不依赖模板的情况下,完成了一个简单的 Ontology Wasm 合约,并进行了部署和调试。
, z  x# H4 ]( H3 g$ z2 s3 {6 w结语
6 V8 y& m# b( Y0 ?6 p. M  A$ ]; `在本期技术视点中,我们简单介绍了如何在不依赖模板的情况下,完成一个简单的 Ontology Wasm 合约的开发,并进行了测试。同时,我们也介绍了 Ontology Wasm 工具库 API 文档的生成方式,方便开发者查询和调用已提供的功能。相信你一定会有所收获。下期我们将会介绍 Ontology Wasm 合约获得调用参数以及合约中数据的序列化和反序列化。欢迎关注!
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

星火车品 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    12