Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

想用Wasm开发dApp 入门教程

星火车品
89 0 0
新建合约
4 g+ s6 G) r! E" S8 X1 _         Created library `helloworld` package7 D* `% `- ]3 g" \, j3 ?0 u: j/ X
新建的合约目录结构如下:) ~) u. P0 G. H0 y
├── Cargo.toml4 Q! \% t) H1 a0 D3 ~' ?
└── src. D6 U3 X3 Y4 A! _# p& e1 g, m
        └── lib.rs4 C: j- P4 c) M; z
一个 Rust 版本的 Wasm 合约包含两部分组成,一部分是Cargo.toml配置文件,用于配置项目信息,一部分是src/lib.rs用于编写合约逻辑。
- H5 z, a9 @7 Z) I. F引入Ontology Wasm合约开发工具库
% j* K) T! T+ m在生成的Cargo.toml文件中引入 Ontology Wasm 合约开发工具库ontio-std,使用[dependencies]配置项来完成引入该工具库的动作。3 \  G( Y& K' O' I! Z5 H1 s9 W
name = "helloworld"$ {9 r6 g+ p1 o  m
version = "0.1.0"1 e3 l. |/ ]7 `. e  `$ v0 U( T
authors = ["Lucas "]5 m- A# [8 L( N. |  E: z  z
edition = "2018"1 `: O  b3 ]' y
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+ u* ?* R" B' G& e7 V8 I[dependencies]: V3 F# m1 O. ~& {9 u
ontio-std = {git="https://github.com/ontio/ontology-Wasm-cdt-rust.git"}
8 s. b7 x  J9 ]3 V- \由于我们合约要以库的形式进行编译,所以还需要在Cargo.toml文件里加上[lib]配置信息,一个完整的 Cargo.toml 配置文件如下:
! A7 e4 l1 u: \6 Rname = "helloworld", @  h+ ~, O" ^2 B, j& x
version = "0.1.0". I4 @  e" z+ {( u7 I4 a9 C
authors = ["Lucas "]8 @- ]( S$ S! k9 ?/ w3 _- L
edition = "2018"
+ g( R; b' p  E4 w7 K7 n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
) k4 r$ q% p9 d1 L- E6 ~+ ][lib]: \" u4 S' h5 U& b
crate-type = ["cdylib"]8 ~& z* i5 M1 c( ]+ t
path = "src/lib.rs"
7 W3 a0 b0 B/ A0 h[dependencies]
* {: ], L& m0 x1 zontio-std = {git="https://github.com/ontio/ontology-Wasm-cdt-rust.git"}/ @( h" T6 c/ g4 j4 E. l. E  s
[features]
% u& I. s3 x4 p. @) Vmock = ["ontio-std/mock"]
) l: D; \- R, T[features]用于开启一些不稳定特性,只可在 nightly 版的编译器中使用。此处我们引入了ontio-std/mock模块,该模块模拟了与链交互的接口,也就是可以通过该模块进行合约中与链交互的模拟测试,主要方便了合约开发者在本地测试合约中与链交互的功能是否正常,无需部署到链上,就可以实现测试的功能,在后面的章节中我们会详细介绍该模块的使用方法。+ S* N1 U& L( {4 p
生成ontio-std库API文件1 ^! L6 N7 a. n# g$ p2 t/ }
虽然我们引入了开发 Ontology Wasm 合约需要的工具库,但是我们还不知道该工具库中都有哪些 API 可以用,我们可以通过下面的命令生成该库的 API 文档。
* X& |8 o4 J3 _! Y$ R0 F& Acargo doc
+ ^7 u; |; k$ Z& Z9 Y3 h1 D# T  {+ `执行成功后的目录结构如下:1 \/ U+ X2 x& }( q0 C) P7 f7 p
├── Cargo.lock% F, I! e6 m; u' ~9 j$ p
├── Cargo.toml3 i4 T* {' m1 E
├── src
# o: r, W/ X  _7 ~│   └── lib.rs
& A, D, u) ^: k* q- [└── target- w: U4 I, m5 J4 u5 a' O/ O
        ├── debug
( |3 `! l; x8 @; V        └── doc# h4 N' ]- T/ o/ h9 s6 Q+ m5 i
生成的 API 接口文档在 doc 目录下。我们可以通过浏览器打开 settings.html 文件查看。如下图所示:
! B9 o% J/ d' A, e" v
' j% S: t/ R% j# h4 G( [请在左侧目录栏找到 ontio_std 库,点击该选项,如下图:
' L: l; L' s2 a. f) ^  s% e3 p' a- g9 A/ W
上面列出了ontio_std库中封装好的所有模块,在开发合约的过程中,可以使用这些模块中的功能。' |: ^/ j% T5 X* {
编写合约逻辑
% O6 \4 d: l9 ]2 [. W新建的 helloworld 合约lib.rs文件内容仅有一个测试代码,在项目根目录下,执行cargo test 来执行该测试代码。5 @0 J: @: @; |
#[cfg(test)]
) j: B2 S4 ~9 Q; H. [5 M- nmod tests {
+ x; C# ]  D6 b! A( I- G        #[test]
5 l" ?+ U+ [+ B) |* o        fn it_works() {6 d8 w. H' X6 a9 u! k
                assert_eq!(2 + 2, 4);
6 e) f9 b7 O: s* s" M        }
, F# Q$ o" S. s! A* F; _8 a3 k2 q}8 r% p5 j+ J$ B- y! l
下面开始编写合约逻辑:
- Q# d' M% K4 t9 ~- d& Z第一步:在lib.rs文件中引入刚才在Cargo.toml配置文件中添加的ontio-std依赖,为了屏蔽rust标准库中的方法,我们加上#![no_std]注解。
- u' ~1 F  z. ?5 m- i( _& j2 m( Z#![no_std]8 v5 m  G! \+ N  K+ J3 ]3 n0 U" H
extern crate ontio_std as ostd;
# x4 n1 T  m9 _8 t/ a+ U- p$ }, h$ F#[cfg(test)]! f2 `2 |4 \$ [
mod tests {
0 r0 Z' v- h7 j6 h4 W   ...
5 ^1 ?' v# O( ~+ r}3 I7 l# _. g/ S, t+ a+ ?  x
第二步:添加invoke函数,该函数是 Ontology Wasm 默认的入口函数,在这个合约中,我们实现一个方法获得调用的参数并将参数返回出去,代码如下:1 b2 e% Z* l+ E
#![no_std]4 Y/ K- p$ y) J+ t: O, G
extern crate ontio_std as ostd;, N% o/ t2 f. n/ `- [- K
use ostd::abi::{Sink, Source};( d, t% u9 I" f6 r' S
use ostd::prelude::*;
. O1 p3 C% E. K+ [- b& `use ostd::runtime;
7 y8 t: ]4 d6 gfn say_hello(msg: &str) -> String {- \1 m/ I( k4 o
        return msg.to_string();( s! Y( \; R: X& q) L
}
8 q( g5 i/ B$ H0 {& B  Q#[no_mangle]8 s! z0 Q! Q7 X0 Y; [* J
fn invoke() {; t+ \$ B5 e: P* \& _2 w
        let input = runtime::input();
' N9 c" h0 P1 Z( \1 B, _; r        let mut source = Source::new(&input);
- o& H6 s4 S* ?6 I9 U& b        let action: &[u8] = source.read().unwrap();. P" K+ \2 H- m3 J/ ]/ X1 c
        let mut sink = Sink::new(12);8 v& P7 o( v5 Y
        match action {" T6 o7 |& E& W/ s" ]/ r7 e4 ?
                b"hello" => {9 C5 x) C/ T6 r, u4 ?+ }7 V
                let msg = source.read().unwrap();0 P! c7 H; b; l" H9 b! D. z
                sink.write(say_hello(msg));
7 ?; j: W5 E0 z% T9 H                },* }7 s0 i% ]% c
                _ => panic!("unsupported action!"),
: ?0 n, R4 S& m+ o! G( ~: d( S& ~        }
9 Z" K! ]3 v2 i, e( F        runtime::ret(sink.bytes())) x* I4 F# E$ |" j% l
}
/ K6 Y' ~" R( F+ p; m9 B+ m! S4 ~#[test]
: `) z, L+ z8 a% L4 d6 {+ Sfn test_hello() {
: L( `9 z0 R6 S' s; r        let res = say_hello("hello world");9 \, F$ E9 ~( X9 ?  v
        assert_eq!(res, "hello world".to_string());
. r2 v) }; b# z+ S" w7 ?}2 J9 N% e5 Y9 ~4 N, |
在合约中,我们引入了ontio-std库里面abi模块的Sink和Source数据类型。Source用于读取外部调用合约中的方法时传进来的方法名和方法参数信息,Sink用于合约中不同类型的数据序列化成 bytearray。ontio-std库里面的prelude模块提供了一些常用的数据类型,比如Address、U128和String等。把合约执行的结果返回给调用合约的程序,需要使用runtime::ret()方法,runtime模块封装与链交互的接口。
' y3 W8 Y! O0 T3 t5 Z% ]至此,一个简单的返回传入参数的合约已经完成。9 Y' f! ]/ Z2 A' _
编译合约
. d- @1 p2 L# ], d6 ]用 Rust 编写的合约源代码需要编译成Wasm字节码,才能部署到链上,执行下面的命令编译合约:
) U" p8 h: W& a# d4 O3 l' D0 L  FRUSTFLAGS="-C link-arg=-zstack-size=32768" cargo build --release --target Wasm32-unknown-unknown4 s, ^! A- I% Y6 i
在上面的命令中,RUSTFLAGS="-C link-arg=-zstack-size=32768"表示设置 rustc 编译时使用的栈大小为32kb,rustc 编译默认设置的栈内存大小是1MB,对合约来说是巨大的浪费,因此在编译时设置下栈的大小,32kb 对于绝大多数合约来说是够用的。 Wasm32-unknown-unknown 表示在编译目标。2 w0 S5 c: S: U
该代码执行后,会生成target文件夹,目录结构如下:2 Y# O* Y/ z$ U9 o% T8 \- {% h& R
.
8 d$ K# K; R! A' [  `├── release4 f! S* q7 L% U5 j
│   ├── build4 {! F, l; O* S9 U8 F: a( V
│   ├── deps6 ^$ U; ~, N9 o2 w# [4 T4 V
│   ├── examples
5 E* r1 I& h+ }│   └── incremental8 N* K+ c/ L+ o) v! A0 U
└── Wasm32-unknown-unknown" {! [  ~  e6 |. j% ~- G& t
        └── release
: C) W" ]+ e  M( _) f) A5 a- k编译好的合约字节码文件为 target/Wasm32-unknown-unknown/release/ 目录下名为helloworld.Wasm的文件。
: C$ L- E" y8 o9 D0 o6 v优化合约字节码
5 K$ z3 ]* X6 M编译好的Wasm字节码文件会比较大,部署到链上需要的存储空间会比较多,费用也会比较高,但是我们可以使用ontio-Wasm-build工具将 Wasm 字节码减小。
) S1 R1 |" M2 f. v$ A0 D0 H8 q. }9 Y8 b执行下面的命令优化该合约字节码:
, c% I  i" r" B$ H6 k" N, montio-Wasm-build ./target/Wasm32-unknown-unknown/release/hellloworld.Wasm
- {3 \7 g( X+ c) q8 Q3 r* Z该命令执行完后,会在./target/Wasm32-unknown-unknown/release/生成的文件如下:! M9 |5 G8 z9 }6 [( S. c8 _9 t7 t3 D/ z8 l
helloworld_optimized.Wasm 优化后的Wasm合约字节码;9 D1 W: S5 e3 C, {5 V
helloworld_optimized.Wasm.str 优化后的Wasm合约字节码的hex编码格式。* p1 u7 b9 _# Z  `$ r" \) t
测试合约2 U( ^" T6 N6 q6 V9 j$ k
我们将在本地测试网络中测试刚刚编写的合约。4 k; X  g) z( h9 E7 O: A5 d9 E$ D
首先,生成钱包文件,本地测试网启动需要钱包文件,执行如下的命令:6 I! ]! ^: x2 d5 Y: O! U
./ontology account add
) o8 j; m( a8 z( ^  F# e) v( \, A其次,启动我们搭建好的本地测试网节点,执行下面的命令:
% c; l+ x/ B/ c0 H% n0 A' }9 y./ontology --testmode --loglevel 1& M0 G5 ^1 I, e' @) |, A1 @
–testmode表示以测试的模式启动。3 H, @$ G: _% b# ?3 ^. Y' e
–loglevel 1 表示将日志级别设置为 debug 模式。& S! w7 F% i4 w. q
然后,部署合约:  N" A  t3 C1 K9 ?$ V
$ ./ontology contract deploy --vmtype 3 --code ./helloworld.Wasm.str --name helloworld --author "author" --email "email" --desc "desc" --gaslimit 22200000
; O0 L( Z9 b- ]Password:
, R' S, ?6 ?  {! r) |" H5 ~* {Deploy contract:
- E0 t3 w) z% C8 d  Contract Address:913ea5298565123847ffe61ec93986a52e824a1b
$ v& ]  l1 g8 |( m. p" Z! `0 s  TxHash:8386410c2ccdc5127e5bd893072a152afaa5dcf20c9b736583f803cba4f461e6
9 k! ?/ ^: \* @5 zTip:
% r6 E* S- P# |' X  Using './ontology info status 8386410c2ccdc5127e5bd893072a152afaa5dcf20c9b736583f803cba4f461e6' to query transaction status.3 u* p- Q, g4 f# F% M- y/ D
–vmtype 3 表示部署的合约类型是Wasm合约,目前 Ontology 链除了支持Wasm合约还支持NeoVM合约,部署的时候要注明合约类型。 --name helloworld 表示部署合约名字是helloworld。 --author “author” 表示部署合约作者是author。 --email “email” 表示部署合约 email 是email。 --gaslimit 22200000表示部署合约需要的费用 gaslimit 上限是22200000。" I; u+ P/ D( q" @# B! z# X$ M
最后,调用合约中的方法。由于我们在 invoke 函数里仅定义了hello方法,并且该方法将输入的参数内容直接返回。所以,调用合约的时候,第一个参数是方法名,第二个参数是合约中的该方法需要的参数。6 _$ S( Z; Y" h$ u. U8 s+ m+ s
因为我们调用的方法没有更新链上的数据,仅仅是把输入的参数返回,我们在调用合约的时候,要加上预执行标签–prepare,否则,我们看不到合约返回的结果。1 t; H2 B5 p5 `# f
根据合约地址调用合约中的方法。该部分详细信息请参考命令行合约调用。
- E5 m4 P7 ~# G2 C$ ./ontology contract invoke --address 913ea5298565123847ffe61ec93986a52e824a1b --vmtype 3 --params 'string:hello,string:hello world' --version 0 --prepare
/ o% Z% h5 I" T% ?Invoke:1b4a822ea58639c91ee6ff473812658529a53e91 Params:["hello","hello world"]7 k5 D+ V/ Z1 N' h* G, w: J& ?
Contract invoke successfully
; ]1 ?- t# e6 e0 S  Gas limit:20000  z& w  S6 ^! ^) A/ u2 ^- C
  Return:0b68656c6c6f20776f726c64 (raw value)
* A# M& O" w2 N! g! o合约中我们的返回值是hello world,就像上一篇技术视点中提到的那样,执行结果返回了该值的hex编码68656c6c6f20776f726c64。
7 m1 B6 n  s) t6 J至此,我们在不依赖模板的情况下,完成了一个简单的 Ontology Wasm 合约,并进行了部署和调试。
$ A  `3 o( P9 `, r8 j结语
# u5 ]! g$ {+ A4 H在本期技术视点中,我们简单介绍了如何在不依赖模板的情况下,完成一个简单的 Ontology Wasm 合约的开发,并进行了测试。同时,我们也介绍了 Ontology Wasm 工具库 API 文档的生成方式,方便开发者查询和调用已提供的功能。相信你一定会有所收获。下期我们将会介绍 Ontology Wasm 合约获得调用参数以及合约中数据的序列化和反序列化。欢迎关注!
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

星火车品 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    12