Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

想用Wasm开发dApp 入门教程

星火车品
215 0 0
新建合约
- d9 R) E, ?! P* f( G         Created library `helloworld` package: h8 {' r' v5 A) Y, N
新建的合约目录结构如下:
# D% W* }; }2 _├── Cargo.toml  L8 R( `  P$ h* Y( j5 ~
└── src
6 z. @0 _0 `- c        └── lib.rs& y7 }7 P/ b2 I
一个 Rust 版本的 Wasm 合约包含两部分组成,一部分是Cargo.toml配置文件,用于配置项目信息,一部分是src/lib.rs用于编写合约逻辑。
! I9 v: d% m! l引入Ontology Wasm合约开发工具库
4 W, P3 ^% B& c  n% X; x# |在生成的Cargo.toml文件中引入 Ontology Wasm 合约开发工具库ontio-std,使用[dependencies]配置项来完成引入该工具库的动作。( Z" s) i; e0 a- B4 j0 E8 @$ \
name = "helloworld"6 g; x2 A3 f; ^# V# \$ y
version = "0.1.0"
3 V. K$ Y4 P- Z- }( vauthors = ["Lucas "]; c# h* A: U3 K
edition = "2018"
  z1 g3 G7 N' G) z- Z5 |# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
4 K: v2 v3 n3 T8 m* s3 R[dependencies]0 S4 @) w2 H% E7 b- {' c; m
ontio-std = {git="https://github.com/ontio/ontology-Wasm-cdt-rust.git"}
% J  i/ C, J  \& ~5 s由于我们合约要以库的形式进行编译,所以还需要在Cargo.toml文件里加上[lib]配置信息,一个完整的 Cargo.toml 配置文件如下:) D% P9 N: M" s- ~# X! y* `& r
name = "helloworld"& q! W5 o( E# Z2 P3 {+ p/ B% j
version = "0.1.0"
0 t" p1 m' n  @# Eauthors = ["Lucas "]+ S, F; t  B) U- Y4 _3 p
edition = "2018"
  ~- m; m3 j6 a! P+ V7 Y( I# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html: R+ J- S4 ?& V/ o# i% i- N
[lib]
# z! c# M- M8 D# o% |crate-type = ["cdylib"]
9 p4 H& z* y) l# I! n" B! Z& zpath = "src/lib.rs"  n: z) ?) Z4 Y8 |5 V$ U4 o: e& d2 \( f
[dependencies]
  @% X* s; A% R  E% s5 V7 rontio-std = {git="https://github.com/ontio/ontology-Wasm-cdt-rust.git"}2 B8 B  `1 s7 d2 ^8 a
[features]
! \  n; p3 U* @# U: Qmock = ["ontio-std/mock"]
" A0 d# M  Z# C2 r- j$ {[features]用于开启一些不稳定特性,只可在 nightly 版的编译器中使用。此处我们引入了ontio-std/mock模块,该模块模拟了与链交互的接口,也就是可以通过该模块进行合约中与链交互的模拟测试,主要方便了合约开发者在本地测试合约中与链交互的功能是否正常,无需部署到链上,就可以实现测试的功能,在后面的章节中我们会详细介绍该模块的使用方法。
  W; T" \: G$ T3 J/ m, E& D生成ontio-std库API文件- }7 T* X  C) ]# \
虽然我们引入了开发 Ontology Wasm 合约需要的工具库,但是我们还不知道该工具库中都有哪些 API 可以用,我们可以通过下面的命令生成该库的 API 文档。
0 k- l# c- |- n5 J7 Ycargo doc
% N, C( s# e# k6 _执行成功后的目录结构如下:
- P2 {* y. f) R1 O├── Cargo.lock$ Z( i' j: {5 i; a8 y
├── Cargo.toml) ^" \4 o" S4 t. a
├── src
: h  a, E/ K" C0 ^% o  q9 g│   └── lib.rs
- A7 M: z/ J6 g* P' y/ ~3 T4 d└── target1 B* J4 Q5 Z1 y& l- p/ v' d
        ├── debug
9 O* `3 N: V* q9 b        └── doc2 _2 `! C4 W  D1 x* H' h7 W8 e* ^
生成的 API 接口文档在 doc 目录下。我们可以通过浏览器打开 settings.html 文件查看。如下图所示:( a- U: c2 c3 o, u$ {

& ~- c& b. b! A7 Q3 a请在左侧目录栏找到 ontio_std 库,点击该选项,如下图:
0 E% z2 F& O; |! ~% r3 H+ c  C" K: B
上面列出了ontio_std库中封装好的所有模块,在开发合约的过程中,可以使用这些模块中的功能。! q: `( U2 \: L$ W0 I+ b
编写合约逻辑
! {! o3 u/ s; K5 A新建的 helloworld 合约lib.rs文件内容仅有一个测试代码,在项目根目录下,执行cargo test 来执行该测试代码。
, d. Y4 U  v/ ?: V#[cfg(test)]
/ L( h- \: C. q% k+ k* }0 z! @) b$ Fmod tests {
$ J: x6 F: y- X; a* W  }! u        #[test]
+ A' a& S3 g1 y        fn it_works() {
5 ?  J+ {6 B3 B+ U                assert_eq!(2 + 2, 4);
- w0 @! n/ n1 f) A1 v        }
- z* v( N) y3 j4 V! A}
7 Z# m' q- [" F8 v下面开始编写合约逻辑:
) A, G8 @9 N; `9 @2 V第一步:在lib.rs文件中引入刚才在Cargo.toml配置文件中添加的ontio-std依赖,为了屏蔽rust标准库中的方法,我们加上#![no_std]注解。
) {( c' B2 `7 W" I2 n#![no_std]
; v# G/ r% I8 ]extern crate ontio_std as ostd;
6 _6 x0 X6 a  g1 T4 L; ]#[cfg(test)]
, A2 R; `4 p$ E6 C) Qmod tests {9 r+ @; @) Z6 D; N
   ...0 x6 x& Z4 }. O# y/ B
}$ E4 \5 l0 k+ {. w; w2 A4 b: F3 {% q
第二步:添加invoke函数,该函数是 Ontology Wasm 默认的入口函数,在这个合约中,我们实现一个方法获得调用的参数并将参数返回出去,代码如下:% E+ |- ]. t% r8 \/ w
#![no_std]
1 A- C' H$ L! P0 ]5 s* Jextern crate ontio_std as ostd;
/ U: P! w( }! c4 j1 Buse ostd::abi::{Sink, Source};
- m8 A$ b# @: W8 q& Kuse ostd::prelude::*;
3 H9 z) ]! n+ ]% G3 @use ostd::runtime;
& C4 M) l+ A3 K$ ]. Afn say_hello(msg: &str) -> String {/ O) r: j" D. m  V4 z
        return msg.to_string();
/ v! o$ ~( k4 h7 J) T+ U$ ]6 D3 ?}9 @- ]( T6 F7 ~
#[no_mangle]3 m% q9 i6 P, H1 y+ r  A
fn invoke() {' I1 F1 X' m* ]) o
        let input = runtime::input();
$ {" u+ s8 t2 R4 r        let mut source = Source::new(&input);* m) }: ^1 R9 s$ t8 P
        let action: &[u8] = source.read().unwrap();
3 v% ?  \. f' k- ^- d        let mut sink = Sink::new(12);! X: E- U! i( y( S% w
        match action {2 ]! _! S5 R0 ?; y
                b"hello" => {9 x; `% o, q( R' s9 D/ ~
                let msg = source.read().unwrap();& ]$ c, K6 Z/ _! }& X. ]  j/ z$ }
                sink.write(say_hello(msg));
: V9 T! L. H8 `4 H( o" Q* L* x! B                },; J: A& m& p. a6 L
                _ => panic!("unsupported action!"),) m% @( v& M% @) Q7 L5 B
        }& g# m* N8 J% j% |
        runtime::ret(sink.bytes())
) X  h& a6 i7 @, _+ |- l9 `- b}
" r6 W3 P) Z$ p# i( j#[test]: t" I- A, H' z+ O# S
fn test_hello() {3 }; \& j8 X% w8 m! F
        let res = say_hello("hello world");+ n. m) m) @- e- j5 _9 @! S9 Q3 H
        assert_eq!(res, "hello world".to_string());1 u" D; `( e& |- x
}+ F0 o* l& {- j4 R$ F
在合约中,我们引入了ontio-std库里面abi模块的Sink和Source数据类型。Source用于读取外部调用合约中的方法时传进来的方法名和方法参数信息,Sink用于合约中不同类型的数据序列化成 bytearray。ontio-std库里面的prelude模块提供了一些常用的数据类型,比如Address、U128和String等。把合约执行的结果返回给调用合约的程序,需要使用runtime::ret()方法,runtime模块封装与链交互的接口。
# Z. \& P/ I( s9 j5 w9 u2 e! i% o1 {至此,一个简单的返回传入参数的合约已经完成。
% {6 x/ k4 Z( G* {0 X" Z编译合约5 Y9 F1 a+ Q7 T) O* y
用 Rust 编写的合约源代码需要编译成Wasm字节码,才能部署到链上,执行下面的命令编译合约:
& C' `6 P, ^7 fRUSTFLAGS="-C link-arg=-zstack-size=32768" cargo build --release --target Wasm32-unknown-unknown0 @5 o( J* I3 c, ^/ P9 |
在上面的命令中,RUSTFLAGS="-C link-arg=-zstack-size=32768"表示设置 rustc 编译时使用的栈大小为32kb,rustc 编译默认设置的栈内存大小是1MB,对合约来说是巨大的浪费,因此在编译时设置下栈的大小,32kb 对于绝大多数合约来说是够用的。 Wasm32-unknown-unknown 表示在编译目标。
7 q9 d# Y, C1 S$ M; C4 X该代码执行后,会生成target文件夹,目录结构如下:
, I. `6 f( n0 v/ q9 C.1 k# O6 Q3 |) N, \& q
├── release* W* `# D$ K4 b0 w& l, P- N# z) j9 F: H
│   ├── build
5 U! {) _( U8 A9 f0 |% a! b' H8 I5 ?# ?/ w│   ├── deps" I7 Z8 s8 H( ]" E" T8 W
│   ├── examples1 W' P7 |5 o1 ?2 o9 ]9 w& C
│   └── incremental9 o& [/ F3 t# ~; U1 v5 `
└── Wasm32-unknown-unknown
7 _$ v) }9 Y- p5 y% m# q3 ~        └── release
  F* F9 {8 H& b0 ]- o% \编译好的合约字节码文件为 target/Wasm32-unknown-unknown/release/ 目录下名为helloworld.Wasm的文件。
1 n9 X0 W  D( Q- S7 G7 V; b  N优化合约字节码
1 f1 ?) R, X. Y( j! Q! {' t编译好的Wasm字节码文件会比较大,部署到链上需要的存储空间会比较多,费用也会比较高,但是我们可以使用ontio-Wasm-build工具将 Wasm 字节码减小。
* {7 b# t# ]3 a$ e7 x执行下面的命令优化该合约字节码:$ @1 E+ p2 c6 D+ [0 b
ontio-Wasm-build ./target/Wasm32-unknown-unknown/release/hellloworld.Wasm) R: `. {& Z" t- M7 [1 O
该命令执行完后,会在./target/Wasm32-unknown-unknown/release/生成的文件如下:
. T3 s, V8 n  {: r: j* O+ yhelloworld_optimized.Wasm 优化后的Wasm合约字节码;
/ E, T8 E/ Z7 W4 m: a7 xhelloworld_optimized.Wasm.str 优化后的Wasm合约字节码的hex编码格式。- v4 R/ j9 D5 a. P
测试合约
3 r# \3 r0 b3 x3 x7 R* J, w我们将在本地测试网络中测试刚刚编写的合约。$ p3 s- n9 J1 B4 t, e* Q+ |0 |
首先,生成钱包文件,本地测试网启动需要钱包文件,执行如下的命令:
: j+ t+ i; K3 g% M./ontology account add) r. ?$ ~1 k2 u' P6 z
其次,启动我们搭建好的本地测试网节点,执行下面的命令:1 B% f2 L3 R5 u/ v7 [9 g# `  c
./ontology --testmode --loglevel 1
, j" B9 m% X3 }4 z; @( ]–testmode表示以测试的模式启动。4 w7 _/ }/ h' A0 _
–loglevel 1 表示将日志级别设置为 debug 模式。
: w8 A6 g9 f( D3 A8 T然后,部署合约:
5 p( b& J4 I7 ^4 F, u$ ./ontology contract deploy --vmtype 3 --code ./helloworld.Wasm.str --name helloworld --author "author" --email "email" --desc "desc" --gaslimit 222000003 P( I. ]6 h0 r) O6 Y3 u0 x+ U& _
Password:# L) A% @9 r, t( Y
Deploy contract:
% Z' o% H. p: s! P- }5 Y0 W  Contract Address:913ea5298565123847ffe61ec93986a52e824a1b
8 d- |, l" g2 }7 X  TxHash:8386410c2ccdc5127e5bd893072a152afaa5dcf20c9b736583f803cba4f461e6+ u8 N' `' H0 E. P; z7 R
Tip:. X, m0 h9 |3 q# C9 L3 Y5 c( j
  Using './ontology info status 8386410c2ccdc5127e5bd893072a152afaa5dcf20c9b736583f803cba4f461e6' to query transaction status.8 H  V! S5 R0 L9 p
–vmtype 3 表示部署的合约类型是Wasm合约,目前 Ontology 链除了支持Wasm合约还支持NeoVM合约,部署的时候要注明合约类型。 --name helloworld 表示部署合约名字是helloworld。 --author “author” 表示部署合约作者是author。 --email “email” 表示部署合约 email 是email。 --gaslimit 22200000表示部署合约需要的费用 gaslimit 上限是22200000。
8 a9 `4 g- K. C最后,调用合约中的方法。由于我们在 invoke 函数里仅定义了hello方法,并且该方法将输入的参数内容直接返回。所以,调用合约的时候,第一个参数是方法名,第二个参数是合约中的该方法需要的参数。
- Q+ B. a# \& X  H9 D/ ?因为我们调用的方法没有更新链上的数据,仅仅是把输入的参数返回,我们在调用合约的时候,要加上预执行标签–prepare,否则,我们看不到合约返回的结果。6 |: m' D9 g- d4 G0 ~+ ]
根据合约地址调用合约中的方法。该部分详细信息请参考命令行合约调用。
# o& G0 R) ?5 p& G7 Q7 y$ b4 e% }3 l$ ./ontology contract invoke --address 913ea5298565123847ffe61ec93986a52e824a1b --vmtype 3 --params 'string:hello,string:hello world' --version 0 --prepare
* I. k* \" B' r5 G7 B9 b) m( P. NInvoke:1b4a822ea58639c91ee6ff473812658529a53e91 Params:["hello","hello world"]
4 S- |4 x8 j9 P  WContract invoke successfully5 r5 m' W. j" A$ f" J
  Gas limit:200004 n3 w2 L! L  X! z
  Return:0b68656c6c6f20776f726c64 (raw value)
" R/ }9 Q/ _9 S& }2 ^8 Y合约中我们的返回值是hello world,就像上一篇技术视点中提到的那样,执行结果返回了该值的hex编码68656c6c6f20776f726c64。
% W: s3 o+ S4 n3 R至此,我们在不依赖模板的情况下,完成了一个简单的 Ontology Wasm 合约,并进行了部署和调试。
3 \' b1 a3 G* r  Y结语+ X4 @  P( ^: T/ M
在本期技术视点中,我们简单介绍了如何在不依赖模板的情况下,完成一个简单的 Ontology Wasm 合约的开发,并进行了测试。同时,我们也介绍了 Ontology Wasm 工具库 API 文档的生成方式,方便开发者查询和调用已提供的功能。相信你一定会有所收获。下期我们将会介绍 Ontology Wasm 合约获得调用参数以及合约中数据的序列化和反序列化。欢迎关注!
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

星火车品 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    12