Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

想用Wasm开发dApp 入门教程

星火车品
113 0 0
新建合约/ W- E9 h, Q8 M+ D
         Created library `helloworld` package
' x. K6 M1 \+ |/ |8 |  x  z" A2 p新建的合约目录结构如下:
7 x  V9 g* |! X3 p% p* j5 K5 y$ n├── Cargo.toml& H4 z) ^* j/ T# H, j
└── src
* c4 T$ w+ P0 o        └── lib.rs# e; I7 k+ r( H( o1 y
一个 Rust 版本的 Wasm 合约包含两部分组成,一部分是Cargo.toml配置文件,用于配置项目信息,一部分是src/lib.rs用于编写合约逻辑。) P1 \+ B8 \& U! \: x/ s6 A- }( I
引入Ontology Wasm合约开发工具库4 q/ _$ e' a; J3 T
在生成的Cargo.toml文件中引入 Ontology Wasm 合约开发工具库ontio-std,使用[dependencies]配置项来完成引入该工具库的动作。' _" m9 w& W2 ]# e# h
name = "helloworld"
) Y$ T3 n% I, D  l: Zversion = "0.1.0"4 @" s) B% b/ J) l) f" ?
authors = ["Lucas "]+ H/ S# g% d: Z) L. `
edition = "2018"
. O2 K& ~; j, {# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html2 m$ p0 ?- H* A8 H) J
[dependencies]
' [" D( d+ h/ r2 u: i" t( u: Wontio-std = {git="https://github.com/ontio/ontology-Wasm-cdt-rust.git"}, S9 Z' K0 e# n; O$ |+ H
由于我们合约要以库的形式进行编译,所以还需要在Cargo.toml文件里加上[lib]配置信息,一个完整的 Cargo.toml 配置文件如下:
. A+ H7 t6 ]  a( Y7 w- [4 Yname = "helloworld"7 ?7 N# b* I$ v6 b; x
version = "0.1.0"' R) ^3 r' `, h
authors = ["Lucas "]
- ~5 A! t- r5 @" S) Redition = "2018"
) F/ @# N8 T/ O% H3 f# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
# L: c/ r) }/ ][lib]
0 @1 D* n1 ^+ H' T2 N! D7 }% Vcrate-type = ["cdylib"]5 c. X) ~% y7 q. w* _4 P
path = "src/lib.rs"
' F6 N0 W+ t  _5 H[dependencies]
- s( J% Z( Q, T' W% n) Contio-std = {git="https://github.com/ontio/ontology-Wasm-cdt-rust.git"}* b& |9 n3 L* B6 }
[features]
( d8 i* g* p1 {6 J" C. r  z& hmock = ["ontio-std/mock"]- M2 u$ N1 c% X) u5 d% q! [
[features]用于开启一些不稳定特性,只可在 nightly 版的编译器中使用。此处我们引入了ontio-std/mock模块,该模块模拟了与链交互的接口,也就是可以通过该模块进行合约中与链交互的模拟测试,主要方便了合约开发者在本地测试合约中与链交互的功能是否正常,无需部署到链上,就可以实现测试的功能,在后面的章节中我们会详细介绍该模块的使用方法。
3 \$ o# |0 }" w* b1 g' k生成ontio-std库API文件9 z% p  b4 |2 a* F. k0 a5 K& H
虽然我们引入了开发 Ontology Wasm 合约需要的工具库,但是我们还不知道该工具库中都有哪些 API 可以用,我们可以通过下面的命令生成该库的 API 文档。
# \& H5 N! W1 z5 u8 I! x' M2 Qcargo doc
3 S" w6 \. u1 l1 X" f: D% v执行成功后的目录结构如下:
5 P1 k7 [0 k% O* g( R6 U9 _├── Cargo.lock
) q4 \6 i) T# _* O( W% V5 z├── Cargo.toml) ?4 ]3 l! r; o5 q6 r7 l  V
├── src+ c( \. S/ Z7 }4 ~) t
│   └── lib.rs7 j5 {9 \! v. G* m# h8 F7 ]
└── target: W  w, i$ Y! D% x1 z5 ~
        ├── debug
) O' T/ G/ W' q        └── doc+ {+ f3 D& _) `9 _
生成的 API 接口文档在 doc 目录下。我们可以通过浏览器打开 settings.html 文件查看。如下图所示:- n# d% O6 G9 \4 t
2 ?% ]5 F- q1 ^9 J5 v
请在左侧目录栏找到 ontio_std 库,点击该选项,如下图:* n1 h/ @! g4 j* s( C
5 |4 O. v4 R7 N% r- ?% V* U/ c
上面列出了ontio_std库中封装好的所有模块,在开发合约的过程中,可以使用这些模块中的功能。
( K. j6 b! K' L, w0 F编写合约逻辑
; r9 J( P4 ^, x3 e% K新建的 helloworld 合约lib.rs文件内容仅有一个测试代码,在项目根目录下,执行cargo test 来执行该测试代码。
" g! j% N( Z* @9 S( @#[cfg(test)]
/ t  f$ N( F* @- m% l- fmod tests {
: @3 f1 r7 _" B* ^        #[test]
8 y$ Q5 H2 @! H( c" s9 v( B        fn it_works() {7 L# b1 z; C$ E3 N0 W4 U2 \* v8 c
                assert_eq!(2 + 2, 4);6 q# f/ N0 H# Z9 Y- O1 g3 v
        }
# V; C& s# C1 x3 N/ X$ G}
4 h8 x; H5 B$ I0 J$ J下面开始编写合约逻辑:, B7 \. m- G2 |/ y9 E
第一步:在lib.rs文件中引入刚才在Cargo.toml配置文件中添加的ontio-std依赖,为了屏蔽rust标准库中的方法,我们加上#![no_std]注解。
2 M" L( i  B/ G1 ^: c#![no_std]6 M  C+ P8 U- m9 Y. p, v+ h/ \
extern crate ontio_std as ostd;: q6 u( N) _4 d" e; U
#[cfg(test)]
7 t1 [3 P! H, S: M4 K/ L% Omod tests {' `* B/ J9 z8 I8 ^+ g( T! h
   ...
% x7 Q' j8 Z+ `1 w9 z3 D}8 B; C8 D" c' |, [. h
第二步:添加invoke函数,该函数是 Ontology Wasm 默认的入口函数,在这个合约中,我们实现一个方法获得调用的参数并将参数返回出去,代码如下:
: F9 z8 s, I* \- V7 F. M- u#![no_std]( I4 ^9 L; e5 \! e
extern crate ontio_std as ostd;- i/ y( ?; N# J6 Y% L& `
use ostd::abi::{Sink, Source};
6 m+ G: a6 L; X: a$ P! ~7 ]' v7 muse ostd::prelude::*;
. s* |, b7 B* m% T( a* w$ P3 ause ostd::runtime;7 B" `4 ^6 J% b4 {6 k; r5 m. F
fn say_hello(msg: &str) -> String {9 R3 }4 h. [3 c2 O* E* e  e7 @, h
        return msg.to_string();
  o" a0 S2 a% {0 K}
; U: z8 `( t) H! ~+ q#[no_mangle]& a8 T. h/ h; M$ M9 s
fn invoke() {
; V$ ^1 I* Q, L: R) E        let input = runtime::input();! S& |& d% j# z8 S
        let mut source = Source::new(&input);( r* r; y5 T: N4 a) E; S  k
        let action: &[u8] = source.read().unwrap();; z) A# H. A  U6 {' K
        let mut sink = Sink::new(12);9 w! u3 E& {& I  p
        match action {3 |, t: f3 u, e9 R! H
                b"hello" => {. [5 Z/ Y: ?! b1 A7 t
                let msg = source.read().unwrap();
5 |! o& {8 u5 ~; u& c                sink.write(say_hello(msg));
  _% D( X, E0 ]& N( M! E$ }* W0 l                },
7 I6 D1 b4 `+ K' w                _ => panic!("unsupported action!"),
0 v/ W9 D/ s% a. v' N        }
3 R5 a' ]7 y9 \/ M        runtime::ret(sink.bytes())
$ r( \! e# _" U}
3 S$ `, z& K9 i# B8 P#[test]
: R( M- |" T/ X2 o; X( }  a. ufn test_hello() {% H; ~& ~" C, L& F; y. g' d, Z
        let res = say_hello("hello world");
% i( a5 P  b6 N1 s$ H; W        assert_eq!(res, "hello world".to_string());5 F9 H+ U. Z5 u$ J, i0 i/ Y5 ^
}- e$ l% N0 W, i5 W& t2 Q
在合约中,我们引入了ontio-std库里面abi模块的Sink和Source数据类型。Source用于读取外部调用合约中的方法时传进来的方法名和方法参数信息,Sink用于合约中不同类型的数据序列化成 bytearray。ontio-std库里面的prelude模块提供了一些常用的数据类型,比如Address、U128和String等。把合约执行的结果返回给调用合约的程序,需要使用runtime::ret()方法,runtime模块封装与链交互的接口。
* o) A3 ?* G: L2 c至此,一个简单的返回传入参数的合约已经完成。& Z+ w+ L* P* a
编译合约
. s% o- m: M/ B7 U用 Rust 编写的合约源代码需要编译成Wasm字节码,才能部署到链上,执行下面的命令编译合约:
6 |0 d/ e+ {$ ], U( _* c. MRUSTFLAGS="-C link-arg=-zstack-size=32768" cargo build --release --target Wasm32-unknown-unknown
# j, H( P' u8 \- M6 ]在上面的命令中,RUSTFLAGS="-C link-arg=-zstack-size=32768"表示设置 rustc 编译时使用的栈大小为32kb,rustc 编译默认设置的栈内存大小是1MB,对合约来说是巨大的浪费,因此在编译时设置下栈的大小,32kb 对于绝大多数合约来说是够用的。 Wasm32-unknown-unknown 表示在编译目标。! u! N+ Y. r; h% M2 w1 b6 k
该代码执行后,会生成target文件夹,目录结构如下:
# b/ {; c) f: o7 k. L.; ?- M) Z9 ~$ {" g2 W9 x/ m: G
├── release7 x7 V& Y+ Q: v( e
│   ├── build2 _0 E$ r. C  @! U# F/ ~
│   ├── deps
; P: Q+ \; Q* p. R1 f│   ├── examples" E  F: {* u3 ~" G
│   └── incremental; J; H6 C' m$ j( p& k, W+ N
└── Wasm32-unknown-unknown% o7 S2 d" f) k, g: [( C% L
        └── release% f% y/ d+ X6 y9 ^0 |0 V) h5 I
编译好的合约字节码文件为 target/Wasm32-unknown-unknown/release/ 目录下名为helloworld.Wasm的文件。7 _4 d/ H( z# }4 }; y6 S& |2 k2 P
优化合约字节码2 J7 g1 H7 j+ M* ^- g1 N
编译好的Wasm字节码文件会比较大,部署到链上需要的存储空间会比较多,费用也会比较高,但是我们可以使用ontio-Wasm-build工具将 Wasm 字节码减小。
/ [( |, C% l0 H执行下面的命令优化该合约字节码:
- m- b9 i! }2 m# R* _1 qontio-Wasm-build ./target/Wasm32-unknown-unknown/release/hellloworld.Wasm
6 w% q  U+ a. P该命令执行完后,会在./target/Wasm32-unknown-unknown/release/生成的文件如下:
4 B' s3 E: Y& g$ ^+ |helloworld_optimized.Wasm 优化后的Wasm合约字节码;
# ?3 Y' c) s9 x: {; qhelloworld_optimized.Wasm.str 优化后的Wasm合约字节码的hex编码格式。
( f: o3 {* d2 B% ]测试合约
" f- N' Q" i6 A' b/ l3 l+ e我们将在本地测试网络中测试刚刚编写的合约。
2 X2 F/ Z8 I- b! \. K首先,生成钱包文件,本地测试网启动需要钱包文件,执行如下的命令:3 o+ j% \8 ]/ w3 X: n
./ontology account add
, z: b) m" i* A* G' l其次,启动我们搭建好的本地测试网节点,执行下面的命令:
7 b2 G$ l( U3 n0 J' Z; W./ontology --testmode --loglevel 1
+ b& X5 U. F% F3 t  n5 G8 I1 `% [–testmode表示以测试的模式启动。. J/ w- q! F1 N+ l1 P6 M( Z: N
–loglevel 1 表示将日志级别设置为 debug 模式。
( I) X1 U. ^; e9 {, `然后,部署合约:
$ s+ V7 p) U* B9 G& u% }% u# B, N$ ./ontology contract deploy --vmtype 3 --code ./helloworld.Wasm.str --name helloworld --author "author" --email "email" --desc "desc" --gaslimit 222000008 S' k  F0 N' A1 }/ N) r# m
Password:) d7 D& U: o8 @+ W
Deploy contract:, K$ ?& m9 m  o
  Contract Address:913ea5298565123847ffe61ec93986a52e824a1b
7 n3 f; ?8 F  V/ `  TxHash:8386410c2ccdc5127e5bd893072a152afaa5dcf20c9b736583f803cba4f461e67 W& n2 l$ V5 q, f, t) Y0 K9 G
Tip:- v" d, a- Z$ L+ _4 J* f8 V" Y
  Using './ontology info status 8386410c2ccdc5127e5bd893072a152afaa5dcf20c9b736583f803cba4f461e6' to query transaction status.6 X0 c1 O  b3 Y- J
–vmtype 3 表示部署的合约类型是Wasm合约,目前 Ontology 链除了支持Wasm合约还支持NeoVM合约,部署的时候要注明合约类型。 --name helloworld 表示部署合约名字是helloworld。 --author “author” 表示部署合约作者是author。 --email “email” 表示部署合约 email 是email。 --gaslimit 22200000表示部署合约需要的费用 gaslimit 上限是22200000。
# y8 _1 e+ w% n! D% b最后,调用合约中的方法。由于我们在 invoke 函数里仅定义了hello方法,并且该方法将输入的参数内容直接返回。所以,调用合约的时候,第一个参数是方法名,第二个参数是合约中的该方法需要的参数。
- x6 N3 H1 T+ l7 o# ^! j( i; F因为我们调用的方法没有更新链上的数据,仅仅是把输入的参数返回,我们在调用合约的时候,要加上预执行标签–prepare,否则,我们看不到合约返回的结果。! m$ A! P- q! l5 d* {
根据合约地址调用合约中的方法。该部分详细信息请参考命令行合约调用。" a. g  i0 g/ t- ^4 x/ _
$ ./ontology contract invoke --address 913ea5298565123847ffe61ec93986a52e824a1b --vmtype 3 --params 'string:hello,string:hello world' --version 0 --prepare: j' u; u' j! M- K5 [
Invoke:1b4a822ea58639c91ee6ff473812658529a53e91 Params:["hello","hello world"]7 t. R' |* O2 Q1 o
Contract invoke successfully
$ \( L& X: w; N1 ?4 b9 V% P  Gas limit:20000
# D) d5 A8 `- ]/ }- W8 R) [  Return:0b68656c6c6f20776f726c64 (raw value)
  f9 D) a$ Z4 C1 V( H, L4 c合约中我们的返回值是hello world,就像上一篇技术视点中提到的那样,执行结果返回了该值的hex编码68656c6c6f20776f726c64。, S9 F6 s( b' c
至此,我们在不依赖模板的情况下,完成了一个简单的 Ontology Wasm 合约,并进行了部署和调试。
% {7 C1 V0 W* ]结语
" P* r: ?: ~1 H% X  z7 T在本期技术视点中,我们简单介绍了如何在不依赖模板的情况下,完成一个简单的 Ontology Wasm 合约的开发,并进行了测试。同时,我们也介绍了 Ontology Wasm 工具库 API 文档的生成方式,方便开发者查询和调用已提供的功能。相信你一定会有所收获。下期我们将会介绍 Ontology Wasm 合约获得调用参数以及合约中数据的序列化和反序列化。欢迎关注!
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

星火车品 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    12