Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

想用Wasm开发dApp 入门教程

星火车品
98 0 0
新建合约
8 d3 E! _+ W' N, p+ k         Created library `helloworld` package5 Y2 q9 ]. @/ q% h
新建的合约目录结构如下:
' z1 o+ e- E! z8 P├── Cargo.toml. m6 [# P8 u+ r, {0 }" X$ ~. i
└── src/ ?0 Y' R6 l' N  {# F
        └── lib.rs: e3 i2 _/ t2 V# U
一个 Rust 版本的 Wasm 合约包含两部分组成,一部分是Cargo.toml配置文件,用于配置项目信息,一部分是src/lib.rs用于编写合约逻辑。9 j5 f7 |) W( T
引入Ontology Wasm合约开发工具库
$ a* L) h' E3 \. e" S. n在生成的Cargo.toml文件中引入 Ontology Wasm 合约开发工具库ontio-std,使用[dependencies]配置项来完成引入该工具库的动作。
& x. w. C7 v4 cname = "helloworld"/ c; b$ k' d7 N3 D# i3 P% o
version = "0.1.0"7 x% k* b* A3 H' S: ~! D+ P/ x
authors = ["Lucas "]
& k: d( i0 W5 w9 ~1 j" ^) oedition = "2018"
/ E5 o4 U+ Q1 G; \# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html, Y# k% T, D" X% J
[dependencies]2 S7 X: _! w( o  d+ l5 ?
ontio-std = {git="https://github.com/ontio/ontology-Wasm-cdt-rust.git"}
5 k5 u4 _* |4 J由于我们合约要以库的形式进行编译,所以还需要在Cargo.toml文件里加上[lib]配置信息,一个完整的 Cargo.toml 配置文件如下:% D+ p4 M) Z9 L% r* V
name = "helloworld"
! t5 u5 C# N5 F; }4 b( W/ ]2 J; lversion = "0.1.0"
* H7 {: |) n. j0 R4 v& V5 q' A: zauthors = ["Lucas "]$ A' H+ K. a( q8 R9 D7 A, Z' X
edition = "2018"
1 a/ K6 Q4 d9 I; e# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
4 @9 `# J8 R$ `# k[lib]
" q9 p$ o3 Y& _- J7 K- n% Mcrate-type = ["cdylib"]
2 a0 K  s4 r4 J5 d: apath = "src/lib.rs"
. T. K5 \3 {; l% d[dependencies]
, o  \4 k4 \& e8 Yontio-std = {git="https://github.com/ontio/ontology-Wasm-cdt-rust.git"}
- D0 g2 ?. E/ b) o1 V, D* b[features]
0 K% D. b4 {9 R/ rmock = ["ontio-std/mock"]
6 M6 Z. n4 v, G. W2 e[features]用于开启一些不稳定特性,只可在 nightly 版的编译器中使用。此处我们引入了ontio-std/mock模块,该模块模拟了与链交互的接口,也就是可以通过该模块进行合约中与链交互的模拟测试,主要方便了合约开发者在本地测试合约中与链交互的功能是否正常,无需部署到链上,就可以实现测试的功能,在后面的章节中我们会详细介绍该模块的使用方法。- X* ~% R! v  D, I
生成ontio-std库API文件
% `5 c" Y( v# n4 b9 s虽然我们引入了开发 Ontology Wasm 合约需要的工具库,但是我们还不知道该工具库中都有哪些 API 可以用,我们可以通过下面的命令生成该库的 API 文档。, Z" z4 e1 @) ^$ t1 \
cargo doc
% E& F) T$ p. m) b7 }6 H& I6 _2 [, ?执行成功后的目录结构如下:4 f3 _' F4 O2 |( ?$ `6 ]# q1 I# K
├── Cargo.lock
. m: q  o6 _6 j  J3 e  @2 j! R# a* n├── Cargo.toml
2 e4 U5 M( H1 s& x8 a4 @/ n1 _├── src
3 [5 h. h/ M$ k' {8 {' _; ?│   └── lib.rs
5 C: a2 O, c  `; x3 `4 n2 K' g2 \- c└── target
& V' u& G1 r3 O5 u        ├── debug
  u! F' J3 ]/ A8 {: y9 T        └── doc
7 Y4 e: H& H# `5 i" P6 K$ x生成的 API 接口文档在 doc 目录下。我们可以通过浏览器打开 settings.html 文件查看。如下图所示:
8 j; \9 Z/ K7 ]) C; Q/ K2 b# o
$ O; Z# I5 k7 H请在左侧目录栏找到 ontio_std 库,点击该选项,如下图:& P5 E- ^( s3 ]% K; s
1 ^- v8 |# s+ r. C- n! `5 T
上面列出了ontio_std库中封装好的所有模块,在开发合约的过程中,可以使用这些模块中的功能。2 _8 v( Y4 R  c4 j/ j7 ~
编写合约逻辑
) g1 _: H2 m  ~# x9 S+ w( K新建的 helloworld 合约lib.rs文件内容仅有一个测试代码,在项目根目录下,执行cargo test 来执行该测试代码。1 E4 w% v8 Q1 i! H  r/ ^: b6 [# X
#[cfg(test)]
5 v) k3 `# M& \" C; w8 H; Zmod tests {, Y( o, u+ O/ U0 s* }
        #[test]
2 I( L" F$ w5 R) g2 L' I        fn it_works() {8 n2 g7 Q- }9 _; }0 H- @
                assert_eq!(2 + 2, 4);
( k+ h/ Z' L* v        }$ V3 R. F0 l/ ^5 D
}5 _# A3 f4 Z& t5 D9 y
下面开始编写合约逻辑:
$ f: i9 n7 x7 E) u第一步:在lib.rs文件中引入刚才在Cargo.toml配置文件中添加的ontio-std依赖,为了屏蔽rust标准库中的方法,我们加上#![no_std]注解。  ^3 \: @/ @' q# b
#![no_std]
8 C, m7 j/ T# W8 textern crate ontio_std as ostd;
$ a5 A' h6 R1 U, t#[cfg(test)]
" U( {8 T6 M& Z9 G0 I* q6 dmod tests {0 d8 _% Q9 ]0 W# w
   ...
9 v9 q) O7 t4 n6 _}
5 ~$ a! l1 T5 l( N7 b4 S第二步:添加invoke函数,该函数是 Ontology Wasm 默认的入口函数,在这个合约中,我们实现一个方法获得调用的参数并将参数返回出去,代码如下:" G8 b; D" K/ b1 H
#![no_std]
- q5 p* _0 ~; U8 Mextern crate ontio_std as ostd;) u9 m' ~% M2 G1 D! `8 `
use ostd::abi::{Sink, Source};! X% c! F* P- r. C9 [
use ostd::prelude::*;/ j5 B8 H$ X8 Y5 o0 |  v2 {1 j
use ostd::runtime;' _# i# a( A7 N% Y( i2 y6 }' d" d( }
fn say_hello(msg: &str) -> String {5 ~, H) l8 x! n+ W3 w2 R0 `8 T
        return msg.to_string();& a7 u( L% z0 _1 I6 U! K
}
1 Y. X4 k7 v9 N! S9 a4 P#[no_mangle]
4 j5 g# N! a# B. lfn invoke() {
8 @9 V" w0 R- C, J# @) y        let input = runtime::input();
$ ]! u! X9 T. H- A* S- F        let mut source = Source::new(&input);
/ f9 y6 ]( M4 d; X7 Q4 R        let action: &[u8] = source.read().unwrap();
) ]0 ?  C& s3 y$ O* D& G; N/ j        let mut sink = Sink::new(12);
2 b5 A6 `7 ]( E        match action {( `( }; ^' h4 s* u! O/ n! S* U
                b"hello" => {
% i. @7 m7 V9 f: L& g                let msg = source.read().unwrap();* M" c4 N. n' B) W5 i5 T
                sink.write(say_hello(msg));0 K3 A/ _/ W% A( l
                },
+ n) F6 e) h% l+ S5 L  o                _ => panic!("unsupported action!"),
0 K% G, q! W9 o8 K$ Q. i        }
$ b) B5 y/ W& X0 x        runtime::ret(sink.bytes())& o( T8 y% b) t
}' G  s6 ]$ S8 r* j& n
#[test]
" T# f0 D* ~0 T& w, A; kfn test_hello() {
& d2 p# g. h$ {& q$ N; ~* p  d# M        let res = say_hello("hello world");
5 \; Y9 D8 n6 _4 ?) r+ Y. Y- L( W( `3 D* _        assert_eq!(res, "hello world".to_string());
+ j' U% N2 a: d* C3 \}
% N% b' s% g1 ?9 t在合约中,我们引入了ontio-std库里面abi模块的Sink和Source数据类型。Source用于读取外部调用合约中的方法时传进来的方法名和方法参数信息,Sink用于合约中不同类型的数据序列化成 bytearray。ontio-std库里面的prelude模块提供了一些常用的数据类型,比如Address、U128和String等。把合约执行的结果返回给调用合约的程序,需要使用runtime::ret()方法,runtime模块封装与链交互的接口。/ w0 r* _" X: Z1 H+ o% S
至此,一个简单的返回传入参数的合约已经完成。
7 Q0 y) T/ E8 u( @  Z# @" s1 E( y7 G编译合约
& a1 q" j) a, w: _7 [* W) y! n用 Rust 编写的合约源代码需要编译成Wasm字节码,才能部署到链上,执行下面的命令编译合约:
- \. H" X/ z9 E1 o* a* h. D1 ?RUSTFLAGS="-C link-arg=-zstack-size=32768" cargo build --release --target Wasm32-unknown-unknown
2 R. ]0 e# ~; Q9 J8 W在上面的命令中,RUSTFLAGS="-C link-arg=-zstack-size=32768"表示设置 rustc 编译时使用的栈大小为32kb,rustc 编译默认设置的栈内存大小是1MB,对合约来说是巨大的浪费,因此在编译时设置下栈的大小,32kb 对于绝大多数合约来说是够用的。 Wasm32-unknown-unknown 表示在编译目标。: z& q/ X7 r: Z2 y8 G. l3 l) z
该代码执行后,会生成target文件夹,目录结构如下:" @! M. W6 n; d
.
: \) ]# o+ R& g9 S├── release
" ?' e# P2 ~3 S; B5 U/ Y│   ├── build
. C. V: H4 T2 S│   ├── deps3 `* Y. f/ _6 m+ \8 Y/ i
│   ├── examples
9 B8 B# l7 }$ E0 I8 N│   └── incremental
! p# `9 H. e& B% e└── Wasm32-unknown-unknown) P4 _" c# y5 ]
        └── release& }6 w6 [7 c6 a6 c5 m. [
编译好的合约字节码文件为 target/Wasm32-unknown-unknown/release/ 目录下名为helloworld.Wasm的文件。2 i4 k: {! H& W
优化合约字节码
' M# g' ^! Q  C- D! U& b8 E/ E编译好的Wasm字节码文件会比较大,部署到链上需要的存储空间会比较多,费用也会比较高,但是我们可以使用ontio-Wasm-build工具将 Wasm 字节码减小。3 |) c' e. N  t
执行下面的命令优化该合约字节码:
4 c  }. Y0 m, [/ ~  Pontio-Wasm-build ./target/Wasm32-unknown-unknown/release/hellloworld.Wasm
: b7 O' F! g" [, G+ ]2 U该命令执行完后,会在./target/Wasm32-unknown-unknown/release/生成的文件如下:
( c3 R* o" _* M2 A9 c+ B' u4 Z9 `6 hhelloworld_optimized.Wasm 优化后的Wasm合约字节码;
8 A3 q" a1 K: i) ^  _, rhelloworld_optimized.Wasm.str 优化后的Wasm合约字节码的hex编码格式。# ], C# _, T0 ?2 i& @& ~
测试合约- p, T1 s5 p% i! R, m% m+ Y
我们将在本地测试网络中测试刚刚编写的合约。
4 L: ]1 |0 `% }8 o6 V6 _首先,生成钱包文件,本地测试网启动需要钱包文件,执行如下的命令:9 x( M; V3 Z, [3 H) B
./ontology account add9 ?$ U3 f* S6 G' K5 m
其次,启动我们搭建好的本地测试网节点,执行下面的命令:" W+ O) @8 O9 k( A. M  R. b
./ontology --testmode --loglevel 1
* B: r9 H/ _# n/ @% W( f–testmode表示以测试的模式启动。8 T# ~* H2 j& t* p6 U0 Z  c
–loglevel 1 表示将日志级别设置为 debug 模式。% Q& d7 ]6 q3 C) ?8 d
然后,部署合约:
. \. j$ R9 b5 s& Q3 ?) O$ ./ontology contract deploy --vmtype 3 --code ./helloworld.Wasm.str --name helloworld --author "author" --email "email" --desc "desc" --gaslimit 22200000. v+ W+ I; d2 y2 F
Password:
( v5 }/ D$ e' ^' M4 }& m- ], @& BDeploy contract:
# O' m3 x4 Z1 U9 U$ X- s  W  Contract Address:913ea5298565123847ffe61ec93986a52e824a1b
7 ^7 e4 L# I  D4 x7 @  TxHash:8386410c2ccdc5127e5bd893072a152afaa5dcf20c9b736583f803cba4f461e60 ]" h0 x1 Y0 z8 N9 ~
Tip:
% E" N; K) F) l! e; r9 R9 ^  Using './ontology info status 8386410c2ccdc5127e5bd893072a152afaa5dcf20c9b736583f803cba4f461e6' to query transaction status.
8 R  k; ]0 b9 a3 }! G( B8 ]–vmtype 3 表示部署的合约类型是Wasm合约,目前 Ontology 链除了支持Wasm合约还支持NeoVM合约,部署的时候要注明合约类型。 --name helloworld 表示部署合约名字是helloworld。 --author “author” 表示部署合约作者是author。 --email “email” 表示部署合约 email 是email。 --gaslimit 22200000表示部署合约需要的费用 gaslimit 上限是22200000。3 C7 m4 v% v' ?: R, x" f  n
最后,调用合约中的方法。由于我们在 invoke 函数里仅定义了hello方法,并且该方法将输入的参数内容直接返回。所以,调用合约的时候,第一个参数是方法名,第二个参数是合约中的该方法需要的参数。- k- W6 J  `4 f# }
因为我们调用的方法没有更新链上的数据,仅仅是把输入的参数返回,我们在调用合约的时候,要加上预执行标签–prepare,否则,我们看不到合约返回的结果。9 f  A  i5 E/ `  q2 h
根据合约地址调用合约中的方法。该部分详细信息请参考命令行合约调用。* i' o; J& i8 G0 }6 y: @! |' _
$ ./ontology contract invoke --address 913ea5298565123847ffe61ec93986a52e824a1b --vmtype 3 --params 'string:hello,string:hello world' --version 0 --prepare; h. W" I4 m* O( R4 u
Invoke:1b4a822ea58639c91ee6ff473812658529a53e91 Params:["hello","hello world"]
5 M2 F) @3 V$ P# o5 x7 u+ b0 zContract invoke successfully
3 h8 j" W1 Q9 E+ Q/ Y  Gas limit:20000! s" l* E! ]8 f& f, z
  Return:0b68656c6c6f20776f726c64 (raw value)! Q1 w) P7 }6 R$ o1 ?4 g2 K/ r
合约中我们的返回值是hello world,就像上一篇技术视点中提到的那样,执行结果返回了该值的hex编码68656c6c6f20776f726c64。
0 ^$ ~& z: ?7 n至此,我们在不依赖模板的情况下,完成了一个简单的 Ontology Wasm 合约,并进行了部署和调试。
- M5 y% a& @9 G* `结语
5 h* h9 v2 @. ^1 Z  \1 O( |+ d在本期技术视点中,我们简单介绍了如何在不依赖模板的情况下,完成一个简单的 Ontology Wasm 合约的开发,并进行了测试。同时,我们也介绍了 Ontology Wasm 工具库 API 文档的生成方式,方便开发者查询和调用已提供的功能。相信你一定会有所收获。下期我们将会介绍 Ontology Wasm 合约获得调用参数以及合约中数据的序列化和反序列化。欢迎关注!
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

星火车品 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    12