Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

想用Wasm开发dApp 入门教程

星火车品
246 0 0
新建合约3 ]( t* A6 [" t8 X) @8 {& ?. X
         Created library `helloworld` package! B5 Q& F# Q3 ^6 P
新建的合约目录结构如下:
. U# F: w, I  Y: G4 q, v1 f( [├── Cargo.toml
: a. I. U* {* v8 a) B9 M└── src9 @1 z) z- X8 @! r
        └── lib.rs
5 D9 n0 B1 [5 S& m! F+ Y4 F& H' O一个 Rust 版本的 Wasm 合约包含两部分组成,一部分是Cargo.toml配置文件,用于配置项目信息,一部分是src/lib.rs用于编写合约逻辑。
0 E& q- ?6 h. L引入Ontology Wasm合约开发工具库, P( Q4 e8 \/ o0 G- u: B
在生成的Cargo.toml文件中引入 Ontology Wasm 合约开发工具库ontio-std,使用[dependencies]配置项来完成引入该工具库的动作。
+ U5 U3 k& W( j3 r. V' _9 {% j3 Q! cname = "helloworld"
! M) |& b; `7 U/ Eversion = "0.1.0") b6 x3 T7 X+ j$ y) X1 J
authors = ["Lucas "]
7 @0 J+ ]! m' X0 b! Bedition = "2018"
0 Q. D6 ?, {% ~5 t8 Q# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
" U/ P5 x  W4 y* F; \( k[dependencies]
1 E( Y/ y# C5 h# }) Lontio-std = {git="https://github.com/ontio/ontology-Wasm-cdt-rust.git"}4 Q( d- J& }- U
由于我们合约要以库的形式进行编译,所以还需要在Cargo.toml文件里加上[lib]配置信息,一个完整的 Cargo.toml 配置文件如下:7 [% l9 a+ T5 `2 x
name = "helloworld"
6 K9 m4 \6 G) m2 bversion = "0.1.0"
/ E  O3 @: Z; g6 m% oauthors = ["Lucas "]
* c3 [' t$ ^# B  V+ i% G) h, wedition = "2018"
% e& H/ x9 G/ R; s/ p2 t4 F3 X4 W# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
3 k; g8 A$ ]$ j7 m* X+ \; e[lib]
8 y5 k- X  e6 U7 V# n  r% |& Tcrate-type = ["cdylib"]
$ A6 I& k. U: u. p& G( n1 F) upath = "src/lib.rs"
4 {" f* F* ~$ h8 y9 f" @1 V[dependencies]3 V7 M* F& a4 t2 R5 Q- J0 d
ontio-std = {git="https://github.com/ontio/ontology-Wasm-cdt-rust.git"}
$ x4 \. Y6 z. Y5 J7 w0 i[features]
2 T4 x0 \, l) G" L, f# ^mock = ["ontio-std/mock"]
1 B+ A2 _$ R* P2 M: P) ^1 I4 I[features]用于开启一些不稳定特性,只可在 nightly 版的编译器中使用。此处我们引入了ontio-std/mock模块,该模块模拟了与链交互的接口,也就是可以通过该模块进行合约中与链交互的模拟测试,主要方便了合约开发者在本地测试合约中与链交互的功能是否正常,无需部署到链上,就可以实现测试的功能,在后面的章节中我们会详细介绍该模块的使用方法。
6 B3 i. l' j! J/ A! ^- {& w生成ontio-std库API文件/ n8 ^& N8 Z& w6 h
虽然我们引入了开发 Ontology Wasm 合约需要的工具库,但是我们还不知道该工具库中都有哪些 API 可以用,我们可以通过下面的命令生成该库的 API 文档。. L9 W$ ?4 g' e* g& u
cargo doc
( h9 O6 G3 _; \/ d' y5 I% u执行成功后的目录结构如下:
+ S# q! A* B: Y' {/ i2 W├── Cargo.lock
/ h. A( i- _% i/ z1 J3 o8 K: s├── Cargo.toml
8 ^! v6 J" Q8 l) o├── src6 N) B7 y8 P# _: W
│   └── lib.rs" q4 \( I% A0 ^3 M& g1 L+ Z" q3 B, a
└── target+ r9 K& l' t& D$ b* @' Z6 s' X
        ├── debug0 _* o& p, a8 X! P( \& U1 b
        └── doc
4 u! e2 s6 g7 l8 \0 ], [, _生成的 API 接口文档在 doc 目录下。我们可以通过浏览器打开 settings.html 文件查看。如下图所示:: d2 z! d- @) u! I, r; `1 x
6 s: Q( k* K) r2 H+ m
请在左侧目录栏找到 ontio_std 库,点击该选项,如下图:
7 ?9 U4 B+ w  T, t  i* d6 ^) Z, f4 O& K  C5 T4 K# e/ `
上面列出了ontio_std库中封装好的所有模块,在开发合约的过程中,可以使用这些模块中的功能。9 u% c, e6 X" m$ M6 Y: F; Y8 p) B" W
编写合约逻辑5 T3 O: E7 Z% u, X% ^. U
新建的 helloworld 合约lib.rs文件内容仅有一个测试代码,在项目根目录下,执行cargo test 来执行该测试代码。
. v( ~, Y, E& c5 r$ x& k2 T1 l! @) @#[cfg(test)]
# e6 n7 R& r# E6 I- Nmod tests {
7 C7 _7 l9 W# `* k        #[test]( \+ T6 d* J3 f& Q. f$ s5 a
        fn it_works() {* ~( y! P( Q! C+ x! m- P$ X8 w
                assert_eq!(2 + 2, 4);
9 l! O5 B  U$ {$ y5 l5 K7 r        }
/ ?! K+ t6 e( R. ?}
- }% G% X: {0 u* p! k6 [# A下面开始编写合约逻辑:
8 t7 r$ g8 L$ j0 _第一步:在lib.rs文件中引入刚才在Cargo.toml配置文件中添加的ontio-std依赖,为了屏蔽rust标准库中的方法,我们加上#![no_std]注解。9 t4 ^/ d% {# n- n" ]5 A- Q
#![no_std]
& _2 R& }+ `; i% {. t: Oextern crate ontio_std as ostd;
* ~- i. ~) n0 H5 T* P8 `5 w' @" ~#[cfg(test)]
' A+ L) L0 \0 y* dmod tests {$ H: A. t; T& U; S3 J% `* p+ A  [
   ...
7 h( d, s$ [" D0 i" \}, {. a# x9 C2 P% \( I
第二步:添加invoke函数,该函数是 Ontology Wasm 默认的入口函数,在这个合约中,我们实现一个方法获得调用的参数并将参数返回出去,代码如下:1 k4 Y! q* S% g  Q. y& ?
#![no_std]# @$ Z  `- }9 r$ j7 J: S3 J; s7 w
extern crate ontio_std as ostd;
  a6 z6 r' f. ^$ b  A' c! Suse ostd::abi::{Sink, Source};
( s3 c' X5 g+ k1 B% T% m0 x/ \& `# Luse ostd::prelude::*;) s' B9 @, A; l
use ostd::runtime;8 Q4 T4 B6 g8 f5 y# _
fn say_hello(msg: &str) -> String {( y, x' F  W2 [  P$ m3 ^( Y  }
        return msg.to_string();# ~0 @( T4 c! M0 j" J
}' V  Q  o- t& B$ m  a
#[no_mangle]/ m  [6 P  _( v6 ~
fn invoke() {
- ^0 D6 b/ d1 B# n        let input = runtime::input();( i7 i# |+ t: r( F! [* o3 ~
        let mut source = Source::new(&input);! T: q) M  G4 Y: h; G/ k  g7 q0 |
        let action: &[u8] = source.read().unwrap();
" b& a* W1 E; d/ ?* r! W. K        let mut sink = Sink::new(12);
/ s, \0 a! F' x% ?! I0 r) |3 E        match action {& x- U, [( n; ?; g9 A7 w+ @5 L
                b"hello" => {
0 A! q* M; Q& A6 W% {                let msg = source.read().unwrap();
; I2 a+ F/ Z  Y! m, K, m" |$ A3 d                sink.write(say_hello(msg));
: M. s& G( O$ |2 m9 K- b7 S  A                },+ D' F3 `1 \( S! y& A, @
                _ => panic!("unsupported action!"),
1 q6 t) i" v) H1 x1 ~2 ?) B        }/ I, _: j/ j& [$ T* n) o" q/ P+ g
        runtime::ret(sink.bytes())
# w8 I' {: U, k/ I2 r) z  Z}' P9 u2 g2 o) i; Z" f& N
#[test]
! K2 Y8 n9 q$ Pfn test_hello() {
/ V9 ^' W4 P3 K9 t& I3 J        let res = say_hello("hello world");" O( u6 ]6 N6 c- R8 {3 {) C$ H
        assert_eq!(res, "hello world".to_string());
# D' y7 p1 Z( ~% Q/ u* c5 e* d}
& V5 N7 w0 F6 H* O  |$ u8 f在合约中,我们引入了ontio-std库里面abi模块的Sink和Source数据类型。Source用于读取外部调用合约中的方法时传进来的方法名和方法参数信息,Sink用于合约中不同类型的数据序列化成 bytearray。ontio-std库里面的prelude模块提供了一些常用的数据类型,比如Address、U128和String等。把合约执行的结果返回给调用合约的程序,需要使用runtime::ret()方法,runtime模块封装与链交互的接口。
. b; q% U9 w4 x1 x) M# U. U至此,一个简单的返回传入参数的合约已经完成。
+ g$ [' {0 B9 m* d) H" f  \5 }编译合约
+ g$ V! P8 @+ O2 o& T4 {. H' ]! H用 Rust 编写的合约源代码需要编译成Wasm字节码,才能部署到链上,执行下面的命令编译合约:& t# k1 q' s+ o! h# o
RUSTFLAGS="-C link-arg=-zstack-size=32768" cargo build --release --target Wasm32-unknown-unknown
6 F* y: i; \2 f4 }9 E在上面的命令中,RUSTFLAGS="-C link-arg=-zstack-size=32768"表示设置 rustc 编译时使用的栈大小为32kb,rustc 编译默认设置的栈内存大小是1MB,对合约来说是巨大的浪费,因此在编译时设置下栈的大小,32kb 对于绝大多数合约来说是够用的。 Wasm32-unknown-unknown 表示在编译目标。
( t8 p6 u9 J$ K/ l5 S& j. K该代码执行后,会生成target文件夹,目录结构如下:% y; y$ X4 b, p: C6 p
.
, e/ n  Q2 `- S├── release4 H& `+ `2 Z5 j% Z; V6 o- E+ i
│   ├── build: }/ G/ h; I8 q/ v4 `: v. z
│   ├── deps* r- |3 z6 V/ ?, m8 |8 m
│   ├── examples; m3 j! @* ~* U9 c7 a$ ?- C5 m9 x
│   └── incremental7 s) Q( T6 P: d; ?
└── Wasm32-unknown-unknown6 O7 v! e+ k( t- C8 m
        └── release3 a* Q. g' v; S( u
编译好的合约字节码文件为 target/Wasm32-unknown-unknown/release/ 目录下名为helloworld.Wasm的文件。. n9 K3 m6 }0 n: P/ a9 M
优化合约字节码
! L/ n% ?' g: y/ r- ^; \编译好的Wasm字节码文件会比较大,部署到链上需要的存储空间会比较多,费用也会比较高,但是我们可以使用ontio-Wasm-build工具将 Wasm 字节码减小。( ~( U3 ~! [' @0 t* S0 S( y; W
执行下面的命令优化该合约字节码:3 G) g/ v, Q- m8 l" g
ontio-Wasm-build ./target/Wasm32-unknown-unknown/release/hellloworld.Wasm
5 A% b9 B) ]: |' ^8 v/ h0 W9 L; I/ u该命令执行完后,会在./target/Wasm32-unknown-unknown/release/生成的文件如下:
5 o# y$ D# D" yhelloworld_optimized.Wasm 优化后的Wasm合约字节码;9 F' i& u% \1 B7 a+ G
helloworld_optimized.Wasm.str 优化后的Wasm合约字节码的hex编码格式。" H8 {- l( s. J! o: L; T
测试合约2 i7 P1 F# A" |' D3 ~
我们将在本地测试网络中测试刚刚编写的合约。7 q0 n" w+ @! A. i* A; W7 f
首先,生成钱包文件,本地测试网启动需要钱包文件,执行如下的命令:
( f* k; y0 `/ t4 f./ontology account add
+ Q' C! p$ v+ m* J$ u/ w其次,启动我们搭建好的本地测试网节点,执行下面的命令:- L9 m0 `1 W4 s6 X; u* O
./ontology --testmode --loglevel 1
- [0 y! o1 r9 Y& [–testmode表示以测试的模式启动。
( d; J) [; X# _; I) h9 H–loglevel 1 表示将日志级别设置为 debug 模式。
2 b% R  ]: g+ d& }! ?8 N然后,部署合约:: }. q7 t/ V$ B+ o4 U
$ ./ontology contract deploy --vmtype 3 --code ./helloworld.Wasm.str --name helloworld --author "author" --email "email" --desc "desc" --gaslimit 222000003 f; j* ~' ?  {/ H4 K% R
Password:5 z) T* W4 l2 N5 U7 _3 k
Deploy contract:
. v; B) B& a# w) Q( K  b) H  Contract Address:913ea5298565123847ffe61ec93986a52e824a1b
# h6 B3 w( `" H  TxHash:8386410c2ccdc5127e5bd893072a152afaa5dcf20c9b736583f803cba4f461e6
. Z# A5 r/ G, |# B; dTip:
. \; A8 r, i2 Q7 b: v. D+ c  Using './ontology info status 8386410c2ccdc5127e5bd893072a152afaa5dcf20c9b736583f803cba4f461e6' to query transaction status.
: W$ L! f# I# e–vmtype 3 表示部署的合约类型是Wasm合约,目前 Ontology 链除了支持Wasm合约还支持NeoVM合约,部署的时候要注明合约类型。 --name helloworld 表示部署合约名字是helloworld。 --author “author” 表示部署合约作者是author。 --email “email” 表示部署合约 email 是email。 --gaslimit 22200000表示部署合约需要的费用 gaslimit 上限是22200000。% g+ j/ q4 Y3 ?$ h1 D
最后,调用合约中的方法。由于我们在 invoke 函数里仅定义了hello方法,并且该方法将输入的参数内容直接返回。所以,调用合约的时候,第一个参数是方法名,第二个参数是合约中的该方法需要的参数。
/ L( Q" q8 b4 O6 p0 f/ z因为我们调用的方法没有更新链上的数据,仅仅是把输入的参数返回,我们在调用合约的时候,要加上预执行标签–prepare,否则,我们看不到合约返回的结果。+ g4 y* ^. O+ Z
根据合约地址调用合约中的方法。该部分详细信息请参考命令行合约调用。; H) P1 i: A5 L: O
$ ./ontology contract invoke --address 913ea5298565123847ffe61ec93986a52e824a1b --vmtype 3 --params 'string:hello,string:hello world' --version 0 --prepare
, Y* D, g4 l; Z' h3 r& wInvoke:1b4a822ea58639c91ee6ff473812658529a53e91 Params:["hello","hello world"]
* X) r+ t( i% G! F  A2 I3 x) [Contract invoke successfully
) `) _8 c5 Z: v! u+ f; t$ y7 z  Gas limit:20000
1 b1 h5 m: b: V" _  Return:0b68656c6c6f20776f726c64 (raw value)- e3 i- o; H& o. T  O  ~, D
合约中我们的返回值是hello world,就像上一篇技术视点中提到的那样,执行结果返回了该值的hex编码68656c6c6f20776f726c64。9 ~# J, y8 @* _* m+ q9 T* q! \: d$ w
至此,我们在不依赖模板的情况下,完成了一个简单的 Ontology Wasm 合约,并进行了部署和调试。
8 G8 \& i( A/ d3 t- Q4 ]结语
+ A$ C; t5 r& N9 x在本期技术视点中,我们简单介绍了如何在不依赖模板的情况下,完成一个简单的 Ontology Wasm 合约的开发,并进行了测试。同时,我们也介绍了 Ontology Wasm 工具库 API 文档的生成方式,方便开发者查询和调用已提供的功能。相信你一定会有所收获。下期我们将会介绍 Ontology Wasm 合约获得调用参数以及合约中数据的序列化和反序列化。欢迎关注!
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

星火车品 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    12