Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

想用Wasm开发dApp 入门教程

星火车品
248 0 0
新建合约2 O2 Z1 c( ^8 e( j
         Created library `helloworld` package. N- z( k7 M! `
新建的合约目录结构如下:5 |( w) ^* u  B, [% B# k
├── Cargo.toml
3 G2 l! D$ x6 ]3 G) s└── src
5 I1 I0 y$ N* K; A. @9 A3 y+ F        └── lib.rs
, [+ ]+ i1 k3 G; p一个 Rust 版本的 Wasm 合约包含两部分组成,一部分是Cargo.toml配置文件,用于配置项目信息,一部分是src/lib.rs用于编写合约逻辑。+ d0 H3 g9 w# V3 d$ i9 M9 F$ D
引入Ontology Wasm合约开发工具库
/ f; f5 M, I- r& V( }5 j2 ?在生成的Cargo.toml文件中引入 Ontology Wasm 合约开发工具库ontio-std,使用[dependencies]配置项来完成引入该工具库的动作。* p* H% X0 I* T- W8 y' Q: D
name = "helloworld"
; j8 p" e8 X3 S' g. [; [. b' {version = "0.1.0"
, T3 C/ x5 Z4 m+ u" cauthors = ["Lucas "]
9 X4 x, l% t/ f9 s+ Iedition = "2018": r: E7 x( t$ d7 |
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
6 p5 p8 \( `& E  F# `[dependencies]. }  h; R) \& \5 [/ D1 q5 ?
ontio-std = {git="https://github.com/ontio/ontology-Wasm-cdt-rust.git"}7 l- s1 M7 l  z* t& K
由于我们合约要以库的形式进行编译,所以还需要在Cargo.toml文件里加上[lib]配置信息,一个完整的 Cargo.toml 配置文件如下:
8 u: t( q6 h! \6 w0 Xname = "helloworld"
* z. d! p4 u6 I8 c4 uversion = "0.1.0"; q) i! o. P0 T6 m# Q
authors = ["Lucas "]( `7 n. e9 S# u$ r+ c
edition = "2018"
& b+ ^+ I8 c( ?" H' f2 g* v# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
2 G% f7 A' w# n1 k4 i% d, ^4 I2 p[lib]2 x/ f; O  a8 |8 Z
crate-type = ["cdylib"]. {( C  t0 P% C, x- k, _
path = "src/lib.rs"$ K9 u7 J0 N) t4 c& d- o
[dependencies], b5 X9 Q. p3 R% v
ontio-std = {git="https://github.com/ontio/ontology-Wasm-cdt-rust.git"}
' f6 w3 g$ H# |[features]  Q6 v. S" F% H* Q, }
mock = ["ontio-std/mock"]1 T- i: C; {" ?9 W# i0 I5 @
[features]用于开启一些不稳定特性,只可在 nightly 版的编译器中使用。此处我们引入了ontio-std/mock模块,该模块模拟了与链交互的接口,也就是可以通过该模块进行合约中与链交互的模拟测试,主要方便了合约开发者在本地测试合约中与链交互的功能是否正常,无需部署到链上,就可以实现测试的功能,在后面的章节中我们会详细介绍该模块的使用方法。
4 R; X) Y  I0 A! E: V生成ontio-std库API文件
* b- Y% L& I7 B% |0 ]4 L虽然我们引入了开发 Ontology Wasm 合约需要的工具库,但是我们还不知道该工具库中都有哪些 API 可以用,我们可以通过下面的命令生成该库的 API 文档。5 R% M% ^; Z0 B7 A! M4 w$ s0 Z/ g
cargo doc9 O( n  U' T3 m1 q6 |
执行成功后的目录结构如下:& H, h7 x5 l+ i/ F+ p
├── Cargo.lock
. e1 j: H! U+ m6 F├── Cargo.toml8 x# V$ N3 U; ]0 u# c
├── src9 O$ K, e0 }* U; h4 y
│   └── lib.rs
0 x: \& W( K7 W" o) t$ ^2 D$ V└── target
( b# j+ ^" ^0 S' a        ├── debug- p% j+ a1 W/ ^6 b% M* {0 V
        └── doc( V" }2 [/ L) C
生成的 API 接口文档在 doc 目录下。我们可以通过浏览器打开 settings.html 文件查看。如下图所示:: |' L- B6 _, @: g/ X% x8 ]7 e

; s  A$ W  C* C6 j) `请在左侧目录栏找到 ontio_std 库,点击该选项,如下图:' i" [# s8 S; W# p4 Z+ K0 h9 D1 D3 l
7 ?/ T4 Z' l. I" u
上面列出了ontio_std库中封装好的所有模块,在开发合约的过程中,可以使用这些模块中的功能。
, K6 Q) {7 T6 ]% z编写合约逻辑% i( F0 t2 j$ N2 \* z
新建的 helloworld 合约lib.rs文件内容仅有一个测试代码,在项目根目录下,执行cargo test 来执行该测试代码。/ D# U! f$ x$ X8 @( u6 T
#[cfg(test)]
9 {' W" f# ~! kmod tests {
$ h( L5 t3 w. {+ V( k3 H5 c$ o        #[test]4 Y3 ^5 p6 s* W/ M$ N
        fn it_works() {
3 i% O2 U, ?+ y                assert_eq!(2 + 2, 4);7 D- r/ D; H9 ~! m7 ^
        }
# ]3 s9 Y6 O  _. a# [6 d$ D}
* r- b! b, \: I  G& y1 ?4 d! \下面开始编写合约逻辑:
3 J0 E: Z2 \* e7 Z; \. l) G8 f第一步:在lib.rs文件中引入刚才在Cargo.toml配置文件中添加的ontio-std依赖,为了屏蔽rust标准库中的方法,我们加上#![no_std]注解。& `& N2 M* m/ S& f' q9 P( D
#![no_std]
% F. ^1 [" T8 V# Z$ b$ Aextern crate ontio_std as ostd;
( Q) G) T; U6 @1 q( z1 |" Z#[cfg(test)]
' E/ M& a0 V3 K+ \6 w, i3 Jmod tests {* n9 J( y& f0 t0 d( \: o5 Z
   ...' b4 u" m- a& `( j7 i5 R
}" H* h% `) Z$ _0 I2 L- L& p% k
第二步:添加invoke函数,该函数是 Ontology Wasm 默认的入口函数,在这个合约中,我们实现一个方法获得调用的参数并将参数返回出去,代码如下:
! x; ^& t$ m  t#![no_std]) j) a$ {4 a% b# N7 O- ^& h
extern crate ontio_std as ostd;0 n7 y) A/ |. {$ i$ c% K8 j
use ostd::abi::{Sink, Source};
: G' Z- x$ A, U3 G7 w: Q* K- Z# {use ostd::prelude::*;+ o, W' e" a" [( j6 T3 i
use ostd::runtime;
- z; y& v+ E$ b5 `5 [7 P2 ^fn say_hello(msg: &str) -> String {% [& {' ?. T: }5 A5 i+ V
        return msg.to_string();2 Y' p  N" q/ o
}
% w' S5 D& Y* w6 K1 q3 e% m#[no_mangle]
$ \. M. h. z; c8 {5 pfn invoke() {% z( b$ B$ ?+ N2 x6 o- {/ \$ |
        let input = runtime::input();
3 @/ w+ [# u4 \+ S        let mut source = Source::new(&input);
! p# W1 W6 M3 d% C+ {6 a        let action: &[u8] = source.read().unwrap();
. `3 d* I0 K1 X6 p        let mut sink = Sink::new(12);
1 U9 k$ k3 n5 ?6 T5 q$ ]        match action {& v! c( G5 ]) D0 X% r( y
                b"hello" => {# Q# ]' j. \# z0 a! E5 P0 E
                let msg = source.read().unwrap();
+ X' ~- }6 }0 v) f                sink.write(say_hello(msg));
7 a+ w. V. G6 r% A7 X* k/ H/ P                },2 {6 g1 @9 k3 ~8 l  E
                _ => panic!("unsupported action!"),
+ I+ H0 m; R2 R1 \" k) l        }, z4 `' I& O- E+ y# C
        runtime::ret(sink.bytes())
. Y, ]( V5 Q# z% O, e}: p! y& {! n3 Y: C- ~+ ^, g
#[test]0 S' D7 a- ~5 D' c9 t, O) }
fn test_hello() {
1 `" L; D& z1 j. J        let res = say_hello("hello world");. \4 Y+ i  z; O1 d6 e1 I8 h) N
        assert_eq!(res, "hello world".to_string());
) Z4 k; p8 l0 h/ @% v  [}+ Y$ {5 f9 `7 a* J7 j8 O- g
在合约中,我们引入了ontio-std库里面abi模块的Sink和Source数据类型。Source用于读取外部调用合约中的方法时传进来的方法名和方法参数信息,Sink用于合约中不同类型的数据序列化成 bytearray。ontio-std库里面的prelude模块提供了一些常用的数据类型,比如Address、U128和String等。把合约执行的结果返回给调用合约的程序,需要使用runtime::ret()方法,runtime模块封装与链交互的接口。2 j: x8 N/ }/ p# P# ]7 L8 W
至此,一个简单的返回传入参数的合约已经完成。
& e! M% x( P$ S0 x  Q1 K; C2 A编译合约7 Z; `1 k. y/ L9 u: _3 u
用 Rust 编写的合约源代码需要编译成Wasm字节码,才能部署到链上,执行下面的命令编译合约:
5 C5 N( x: F3 q- ~RUSTFLAGS="-C link-arg=-zstack-size=32768" cargo build --release --target Wasm32-unknown-unknown7 I* X8 P/ U- N+ O
在上面的命令中,RUSTFLAGS="-C link-arg=-zstack-size=32768"表示设置 rustc 编译时使用的栈大小为32kb,rustc 编译默认设置的栈内存大小是1MB,对合约来说是巨大的浪费,因此在编译时设置下栈的大小,32kb 对于绝大多数合约来说是够用的。 Wasm32-unknown-unknown 表示在编译目标。
) Q# y$ u( j( S; j2 f该代码执行后,会生成target文件夹,目录结构如下:  f  V6 c. y+ J# ]  x' J
.6 v/ z" f" V/ v. Q1 a; T
├── release! `4 D/ J$ C" R1 s
│   ├── build
( q9 h2 X6 B- N- ^2 H: o│   ├── deps
6 w; X% W7 \( }: G) O7 P+ r│   ├── examples) L  l2 ]6 J- R4 K$ M' L( q& E- d; y
│   └── incremental
5 Z" M. Z# u9 L: C/ ~/ T- D└── Wasm32-unknown-unknown
4 N6 R& Q* m  Y) `+ b0 H) p        └── release
! j8 q& D2 D: F' |4 k# c编译好的合约字节码文件为 target/Wasm32-unknown-unknown/release/ 目录下名为helloworld.Wasm的文件。
( R% c& H% [  q' I优化合约字节码, M8 W: o- a* Q! q9 o' L
编译好的Wasm字节码文件会比较大,部署到链上需要的存储空间会比较多,费用也会比较高,但是我们可以使用ontio-Wasm-build工具将 Wasm 字节码减小。% K- |/ {) r2 o" h
执行下面的命令优化该合约字节码:
: \6 K; z( \7 Y& D' C, ?5 sontio-Wasm-build ./target/Wasm32-unknown-unknown/release/hellloworld.Wasm
+ l0 F0 h5 j2 ?0 L' u( {% J9 A. D该命令执行完后,会在./target/Wasm32-unknown-unknown/release/生成的文件如下:
, n3 E, y; _- `" D/ \  \helloworld_optimized.Wasm 优化后的Wasm合约字节码;
( |3 U' P2 w$ _$ U; d- Q/ ~6 ^helloworld_optimized.Wasm.str 优化后的Wasm合约字节码的hex编码格式。1 Y2 D) J* x" Q' v7 V9 a
测试合约
, p2 r: l2 b3 B% g+ Z8 r我们将在本地测试网络中测试刚刚编写的合约。
( P8 m0 z1 c# V1 w8 }* i首先,生成钱包文件,本地测试网启动需要钱包文件,执行如下的命令:7 e  f: }: J+ `& N% _9 h
./ontology account add" F) y4 b7 J4 s+ }$ H
其次,启动我们搭建好的本地测试网节点,执行下面的命令:! E: U* B# G) ~
./ontology --testmode --loglevel 12 d7 C" {6 B- m6 x0 j2 w
–testmode表示以测试的模式启动。
. o; i! O' F1 b  T2 O–loglevel 1 表示将日志级别设置为 debug 模式。
1 O! @  ~. l% h9 X然后,部署合约:0 R  j0 Y5 K  B$ g
$ ./ontology contract deploy --vmtype 3 --code ./helloworld.Wasm.str --name helloworld --author "author" --email "email" --desc "desc" --gaslimit 22200000
+ W; Q9 p: G' f) y/ p* w/ FPassword:
7 H* W! c. t* W$ iDeploy contract:
. t" J" q6 k; e# ^9 d+ w+ P  Contract Address:913ea5298565123847ffe61ec93986a52e824a1b
" p  O/ x" G! I) b6 i4 u  TxHash:8386410c2ccdc5127e5bd893072a152afaa5dcf20c9b736583f803cba4f461e6
: Q/ K1 T5 h- E$ T, J$ D+ m- w" j0 mTip:
2 D. R/ b. N+ D& i4 o  Using './ontology info status 8386410c2ccdc5127e5bd893072a152afaa5dcf20c9b736583f803cba4f461e6' to query transaction status.
1 |7 }3 \- M  Y, ~6 ~3 K6 ]+ L–vmtype 3 表示部署的合约类型是Wasm合约,目前 Ontology 链除了支持Wasm合约还支持NeoVM合约,部署的时候要注明合约类型。 --name helloworld 表示部署合约名字是helloworld。 --author “author” 表示部署合约作者是author。 --email “email” 表示部署合约 email 是email。 --gaslimit 22200000表示部署合约需要的费用 gaslimit 上限是22200000。
6 I( e4 \8 F1 X0 ~1 C# q最后,调用合约中的方法。由于我们在 invoke 函数里仅定义了hello方法,并且该方法将输入的参数内容直接返回。所以,调用合约的时候,第一个参数是方法名,第二个参数是合约中的该方法需要的参数。+ d$ `2 k  p5 }
因为我们调用的方法没有更新链上的数据,仅仅是把输入的参数返回,我们在调用合约的时候,要加上预执行标签–prepare,否则,我们看不到合约返回的结果。
# d  w1 L7 A  T; k5 ?" K4 J2 ?根据合约地址调用合约中的方法。该部分详细信息请参考命令行合约调用。' t4 _' w7 I3 ]5 D8 T: j2 i
$ ./ontology contract invoke --address 913ea5298565123847ffe61ec93986a52e824a1b --vmtype 3 --params 'string:hello,string:hello world' --version 0 --prepare. ^+ b! c8 g3 J- Z8 {. ^7 l4 E
Invoke:1b4a822ea58639c91ee6ff473812658529a53e91 Params:["hello","hello world"]& ~& W; f6 D) x1 p) N
Contract invoke successfully- l  J+ A* [' t% N) M
  Gas limit:20000
4 r0 ~) Y& l1 |# Z  l  Return:0b68656c6c6f20776f726c64 (raw value)
9 W" H: R, U% N/ N* n. W# K# Z合约中我们的返回值是hello world,就像上一篇技术视点中提到的那样,执行结果返回了该值的hex编码68656c6c6f20776f726c64。! l/ F+ R' o. m' P: z* A
至此,我们在不依赖模板的情况下,完成了一个简单的 Ontology Wasm 合约,并进行了部署和调试。
" V7 N/ k8 X( X9 [$ Q2 h结语
. `* K  N0 ^( z' ?. [" G6 S0 M在本期技术视点中,我们简单介绍了如何在不依赖模板的情况下,完成一个简单的 Ontology Wasm 合约的开发,并进行了测试。同时,我们也介绍了 Ontology Wasm 工具库 API 文档的生成方式,方便开发者查询和调用已提供的功能。相信你一定会有所收获。下期我们将会介绍 Ontology Wasm 合约获得调用参数以及合约中数据的序列化和反序列化。欢迎关注!
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

星火车品 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    12