Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

想用Wasm开发dApp 入门教程

星火车品
96 0 0
新建合约
3 T1 T& U2 _, O         Created library `helloworld` package9 ]: j; i9 E) m  c; w
新建的合约目录结构如下:
2 G1 H0 r1 k; f├── Cargo.toml4 u& ~2 ^; {5 w# h, S9 v, `" d
└── src
% x1 r8 [* {% T* {* N' D( k        └── lib.rs/ W1 s6 \6 Z2 z% J2 |$ q
一个 Rust 版本的 Wasm 合约包含两部分组成,一部分是Cargo.toml配置文件,用于配置项目信息,一部分是src/lib.rs用于编写合约逻辑。; P! G7 h1 M/ K( z" P
引入Ontology Wasm合约开发工具库
6 U2 Z9 p7 r0 Y4 P/ ?在生成的Cargo.toml文件中引入 Ontology Wasm 合约开发工具库ontio-std,使用[dependencies]配置项来完成引入该工具库的动作。
0 \; l) w$ k3 O; k! F' U4 Nname = "helloworld"& G4 D" i: N  W' G# S( E2 o! U+ X
version = "0.1.0"
7 u5 J7 [7 e3 c* e5 R7 |authors = ["Lucas "]& G( U8 k. `$ ]/ i
edition = "2018"9 G& T2 w4 M5 m7 O2 L- b
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html5 R, H' @) k" X$ A  N
[dependencies]
6 O( {, S+ g2 k0 J8 A: Hontio-std = {git="https://github.com/ontio/ontology-Wasm-cdt-rust.git"}
3 A* I" r5 J' j6 M由于我们合约要以库的形式进行编译,所以还需要在Cargo.toml文件里加上[lib]配置信息,一个完整的 Cargo.toml 配置文件如下:
" i$ U$ O) b5 M! o9 \$ _& Rname = "helloworld"1 M2 |6 n4 {6 n5 |% p9 i
version = "0.1.0"
6 s6 p$ M. r0 ]2 ]; S: bauthors = ["Lucas "]4 e  W" S2 F% {
edition = "2018"
' f4 o' B8 q, w, T$ x1 `/ N  `# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8 I3 a, V" u* Q& V' G[lib]8 e- M/ Y; r- z
crate-type = ["cdylib"]
$ b& r3 ^5 ^3 \" e. r$ S. Y$ ipath = "src/lib.rs"/ r* l. t9 n1 S& X% E- H4 f( l
[dependencies]* }& _1 K2 j6 \& R0 V4 b  o7 V
ontio-std = {git="https://github.com/ontio/ontology-Wasm-cdt-rust.git"}+ ]$ ~8 ?! j- b. @
[features]
* M6 F; Y$ v2 smock = ["ontio-std/mock"]
! D! t" P: |/ d+ M% e% }, a& D) O[features]用于开启一些不稳定特性,只可在 nightly 版的编译器中使用。此处我们引入了ontio-std/mock模块,该模块模拟了与链交互的接口,也就是可以通过该模块进行合约中与链交互的模拟测试,主要方便了合约开发者在本地测试合约中与链交互的功能是否正常,无需部署到链上,就可以实现测试的功能,在后面的章节中我们会详细介绍该模块的使用方法。
" D9 i6 @4 s4 Q# T4 [生成ontio-std库API文件" E+ C5 P9 T0 j1 _4 q
虽然我们引入了开发 Ontology Wasm 合约需要的工具库,但是我们还不知道该工具库中都有哪些 API 可以用,我们可以通过下面的命令生成该库的 API 文档。
+ J0 G2 ^$ N2 t+ W  A! Vcargo doc
' `7 [8 T2 O6 h- L  u执行成功后的目录结构如下:$ F0 ~) e4 S6 W$ Q; v2 I$ ]
├── Cargo.lock; Q+ I  Q+ w9 @# l# W" z; q
├── Cargo.toml
* U8 t- s3 d! @* l  E├── src
0 Z; D, M9 W& m, F  @│   └── lib.rs8 h) j6 Y8 V5 X# X
└── target
8 E7 T3 s+ h" X- K/ s, i        ├── debug
! P3 S1 T' N, m* h8 l; M        └── doc& Y- Q8 H" c( H( m9 s, G' G+ M- @4 m
生成的 API 接口文档在 doc 目录下。我们可以通过浏览器打开 settings.html 文件查看。如下图所示:, v  l" B% G: r" Z- w
. Z  g1 P" n5 M, J4 p9 m+ @
请在左侧目录栏找到 ontio_std 库,点击该选项,如下图:7 ?7 ~& R: `6 j* h
; z( |$ H6 t- i
上面列出了ontio_std库中封装好的所有模块,在开发合约的过程中,可以使用这些模块中的功能。
: j7 P" ~+ V% d" E编写合约逻辑2 }  U1 Q$ c) x
新建的 helloworld 合约lib.rs文件内容仅有一个测试代码,在项目根目录下,执行cargo test 来执行该测试代码。+ v; G/ y% C' {: t2 V& E
#[cfg(test)]
- Z6 L+ U8 }; B, F: o& Gmod tests {
  p! K6 L4 u8 ^4 B( L) y        #[test]
* Q, C% `& n8 u& w. n4 `        fn it_works() {' ?) G4 r. W7 C. {1 W3 Q
                assert_eq!(2 + 2, 4);  [" P# d5 ]. Z6 @% p- {
        }  s7 Y) O% ?' i
}
7 D# C. B3 y+ ?/ N下面开始编写合约逻辑:
/ R$ C$ h* v- E第一步:在lib.rs文件中引入刚才在Cargo.toml配置文件中添加的ontio-std依赖,为了屏蔽rust标准库中的方法,我们加上#![no_std]注解。8 \; l" D2 {! _4 c9 _$ s- ~& M
#![no_std]
; p2 R: j- j* B; }( j3 Fextern crate ontio_std as ostd;- Q$ o* \# {% Q+ S6 g
#[cfg(test)]0 q0 o' J- F* l, i9 P2 f( b0 m
mod tests {
! [# q& K; s0 x% R- U5 h2 }9 T$ m   ...
: I5 j! o2 j/ Y& a( Z}  j2 v# T8 o' F$ H* D" w
第二步:添加invoke函数,该函数是 Ontology Wasm 默认的入口函数,在这个合约中,我们实现一个方法获得调用的参数并将参数返回出去,代码如下:
  B' E' V8 g( [' m9 F2 z6 E# J#![no_std]
# j( P: L2 g: q8 H* q! hextern crate ontio_std as ostd;
7 E9 l4 M3 \/ `5 ~$ Guse ostd::abi::{Sink, Source};
) k) z, z! }' }% s& k: Zuse ostd::prelude::*;
2 n1 l: h- ~7 [' N; `use ostd::runtime;/ c6 A7 w: ^& U/ [
fn say_hello(msg: &str) -> String {3 Q' D' n9 h) m5 h. D
        return msg.to_string();: r, x1 H, C: D1 w5 N, f3 s+ J! {
}
, y' W+ v( `8 L#[no_mangle]; V; ]; B5 a1 A' O  Q% |9 u( N
fn invoke() {  c, a; q! c" `: J+ m
        let input = runtime::input();
! x. u1 l/ u8 ?! V$ j, R" y- h        let mut source = Source::new(&input);
0 c0 S6 X) }% R! P        let action: &[u8] = source.read().unwrap();  o( P. o6 M- P+ z% g+ k- I6 G8 u
        let mut sink = Sink::new(12);
" b+ H/ V: Y$ q0 l  V' i) ^        match action {
  B, }9 I2 h9 e6 _/ v9 h" N2 M5 i                b"hello" => {* `1 q4 w) S2 v% D) u
                let msg = source.read().unwrap();
# b1 X- E: V& {  u; n4 T/ Z* ?                sink.write(say_hello(msg));
& O8 [1 K/ J1 M% x! ?5 }7 @+ _                },- Y* K. x" o! |& U; k
                _ => panic!("unsupported action!"),
8 }1 |0 J, Z5 G        }
$ }: l6 U7 l& D8 J6 E6 ~        runtime::ret(sink.bytes())
, c0 e8 u+ g0 T, s2 t' N, P" d}
3 ?! L- n% g# e' ^- ]( @#[test]
5 Z# a3 v- s4 o' A! v4 Pfn test_hello() {$ V  v7 \9 c. N  s2 e
        let res = say_hello("hello world");1 f, b! j, u1 w) Y( s7 S
        assert_eq!(res, "hello world".to_string());! f  R+ D# A) u- ^# G* D, X
}
0 S8 h. A6 q7 @1 Q( J) X+ G9 G* D在合约中,我们引入了ontio-std库里面abi模块的Sink和Source数据类型。Source用于读取外部调用合约中的方法时传进来的方法名和方法参数信息,Sink用于合约中不同类型的数据序列化成 bytearray。ontio-std库里面的prelude模块提供了一些常用的数据类型,比如Address、U128和String等。把合约执行的结果返回给调用合约的程序,需要使用runtime::ret()方法,runtime模块封装与链交互的接口。
% G$ F9 L. ?1 M至此,一个简单的返回传入参数的合约已经完成。
& S5 Z- v/ R3 a- i7 A% a编译合约1 U( O& a$ w/ S8 u7 `6 x* R
用 Rust 编写的合约源代码需要编译成Wasm字节码,才能部署到链上,执行下面的命令编译合约:
# A8 P. f% Z% i0 y8 d# p% NRUSTFLAGS="-C link-arg=-zstack-size=32768" cargo build --release --target Wasm32-unknown-unknown$ n) v2 I, d9 q2 U, E1 N
在上面的命令中,RUSTFLAGS="-C link-arg=-zstack-size=32768"表示设置 rustc 编译时使用的栈大小为32kb,rustc 编译默认设置的栈内存大小是1MB,对合约来说是巨大的浪费,因此在编译时设置下栈的大小,32kb 对于绝大多数合约来说是够用的。 Wasm32-unknown-unknown 表示在编译目标。
0 F/ }. h5 j) T& @7 D* ]该代码执行后,会生成target文件夹,目录结构如下:% u6 [/ L$ n- R* u3 o& t
.& m5 n8 o; ^8 J. X
├── release" v1 t+ X5 y* {: b" W! E
│   ├── build
3 g% X" l  r5 R. y7 o│   ├── deps
: ]  O" l+ U& U8 p) I# q$ B& K; _4 s│   ├── examples
4 e- h: l  C2 O6 y  m2 {3 O, G│   └── incremental/ d% ^2 R( F' F% n3 `. ?2 O
└── Wasm32-unknown-unknown4 A8 [0 k# [3 h8 J9 n8 B
        └── release& r5 j; Y/ J% g4 T3 S
编译好的合约字节码文件为 target/Wasm32-unknown-unknown/release/ 目录下名为helloworld.Wasm的文件。
& X* |; a& y% `" q# x优化合约字节码
! {  ?+ R: n" V4 s. b' S( {# s编译好的Wasm字节码文件会比较大,部署到链上需要的存储空间会比较多,费用也会比较高,但是我们可以使用ontio-Wasm-build工具将 Wasm 字节码减小。
2 H# v+ n* a% w$ _! a  |; p执行下面的命令优化该合约字节码:
2 V- O: U2 c1 T* ?ontio-Wasm-build ./target/Wasm32-unknown-unknown/release/hellloworld.Wasm) {/ S; e+ G+ t- ?( c( J0 i
该命令执行完后,会在./target/Wasm32-unknown-unknown/release/生成的文件如下:
- m6 s/ r$ W" }helloworld_optimized.Wasm 优化后的Wasm合约字节码;8 v$ y( t! `3 a( }. y% F% A9 P
helloworld_optimized.Wasm.str 优化后的Wasm合约字节码的hex编码格式。; q  ^: ]& u! W/ }" Y, V
测试合约
! D% d* ]1 C# ~0 h  }5 s  J* @7 l7 ~我们将在本地测试网络中测试刚刚编写的合约。
3 ~5 l7 x/ Q( g$ m5 Q' x2 B6 J首先,生成钱包文件,本地测试网启动需要钱包文件,执行如下的命令:8 t6 t6 {7 q, {* L8 |3 A4 j
./ontology account add
) }% D- n, J# m% [; l8 A8 @其次,启动我们搭建好的本地测试网节点,执行下面的命令:
, T' ^; l( ?- E. _./ontology --testmode --loglevel 1
  |% F9 E# W. x! F7 i/ R: _# o–testmode表示以测试的模式启动。
, @3 j% Z0 K# g3 I! `–loglevel 1 表示将日志级别设置为 debug 模式。
% Q2 M& {/ ]+ @9 S" c, ~然后,部署合约:0 _) H& T  p7 H( Y' c" A
$ ./ontology contract deploy --vmtype 3 --code ./helloworld.Wasm.str --name helloworld --author "author" --email "email" --desc "desc" --gaslimit 22200000
( D4 q6 W, `, M) YPassword:
- @" T' E' M/ z2 bDeploy contract:
/ ?3 W& \" i3 W" a9 _, G  i/ ?  Contract Address:913ea5298565123847ffe61ec93986a52e824a1b
; H* Q- [: p* F: W- @  TxHash:8386410c2ccdc5127e5bd893072a152afaa5dcf20c9b736583f803cba4f461e6
' [1 A2 G4 ?9 i" H- d1 \Tip:
0 Y* k7 Y1 b! y7 F1 a  Using './ontology info status 8386410c2ccdc5127e5bd893072a152afaa5dcf20c9b736583f803cba4f461e6' to query transaction status.. l% ^" i$ h1 \% |) S0 A1 Q# ]
–vmtype 3 表示部署的合约类型是Wasm合约,目前 Ontology 链除了支持Wasm合约还支持NeoVM合约,部署的时候要注明合约类型。 --name helloworld 表示部署合约名字是helloworld。 --author “author” 表示部署合约作者是author。 --email “email” 表示部署合约 email 是email。 --gaslimit 22200000表示部署合约需要的费用 gaslimit 上限是22200000。
( c/ [- f/ h2 t1 W) I4 ?$ k最后,调用合约中的方法。由于我们在 invoke 函数里仅定义了hello方法,并且该方法将输入的参数内容直接返回。所以,调用合约的时候,第一个参数是方法名,第二个参数是合约中的该方法需要的参数。
* c: v2 {* `' ~% ~- t2 {6 W因为我们调用的方法没有更新链上的数据,仅仅是把输入的参数返回,我们在调用合约的时候,要加上预执行标签–prepare,否则,我们看不到合约返回的结果。' j( p3 P" V8 P7 e7 O
根据合约地址调用合约中的方法。该部分详细信息请参考命令行合约调用。
+ T% N; p+ n( |& R$ q$ H4 q$ ./ontology contract invoke --address 913ea5298565123847ffe61ec93986a52e824a1b --vmtype 3 --params 'string:hello,string:hello world' --version 0 --prepare, }5 X- I9 K' v6 k* J; c8 [
Invoke:1b4a822ea58639c91ee6ff473812658529a53e91 Params:["hello","hello world"]
( o. @0 f( d- ]5 ]( K2 x! \Contract invoke successfully
$ C) B$ r) W0 R$ h. `  Gas limit:20000
9 B6 R3 h( w2 e$ Z2 P5 ?  Return:0b68656c6c6f20776f726c64 (raw value)
' f/ v( J3 Z8 a$ T- j6 }合约中我们的返回值是hello world,就像上一篇技术视点中提到的那样,执行结果返回了该值的hex编码68656c6c6f20776f726c64。% Z1 J+ m4 U9 K5 {
至此,我们在不依赖模板的情况下,完成了一个简单的 Ontology Wasm 合约,并进行了部署和调试。7 J3 a7 P+ Z- T4 o" _' E6 y6 `+ n
结语
" [8 z7 U7 e1 T7 V. ~/ I$ o在本期技术视点中,我们简单介绍了如何在不依赖模板的情况下,完成一个简单的 Ontology Wasm 合约的开发,并进行了测试。同时,我们也介绍了 Ontology Wasm 工具库 API 文档的生成方式,方便开发者查询和调用已提供的功能。相信你一定会有所收获。下期我们将会介绍 Ontology Wasm 合约获得调用参数以及合约中数据的序列化和反序列化。欢迎关注!
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

星火车品 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    12