Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

想用Wasm开发dApp 入门教程

星火车品
126 0 0
新建合约
! c4 p8 y  M) f7 a4 B         Created library `helloworld` package
. c' L  v' E* Q" ^+ L新建的合约目录结构如下:
0 [5 A2 @5 h8 @' ~' [├── Cargo.toml' \* \+ {( R' j
└── src
; m2 ?7 z( \( V4 l: ?: c8 i        └── lib.rs8 A- O8 j7 E7 `4 j' r
一个 Rust 版本的 Wasm 合约包含两部分组成,一部分是Cargo.toml配置文件,用于配置项目信息,一部分是src/lib.rs用于编写合约逻辑。5 l  [. i  f" r7 r7 g+ p
引入Ontology Wasm合约开发工具库$ G7 P7 Z* V6 Y" {" m
在生成的Cargo.toml文件中引入 Ontology Wasm 合约开发工具库ontio-std,使用[dependencies]配置项来完成引入该工具库的动作。- B9 A) F0 n: a( [8 l
name = "helloworld") s+ G6 [+ z. ]/ r: Z
version = "0.1.0"
5 D: y0 y" J" T# p' A( uauthors = ["Lucas "]# ?; K, |0 y2 L2 \: n/ x
edition = "2018"* B: z) n5 E8 u9 S/ l* Y) F
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8 g& P$ x6 Y9 `; p[dependencies]( ]+ K1 `- x- j% h& z$ k) L4 ]
ontio-std = {git="https://github.com/ontio/ontology-Wasm-cdt-rust.git"}/ O- i3 p& @7 x8 l7 Q6 @# r4 |
由于我们合约要以库的形式进行编译,所以还需要在Cargo.toml文件里加上[lib]配置信息,一个完整的 Cargo.toml 配置文件如下:7 \4 j$ O' v2 M0 W7 j" Z
name = "helloworld"
9 r" ^9 g' ?0 p+ \) sversion = "0.1.0"6 G8 y3 r; m8 Q. E1 o. ^
authors = ["Lucas "]3 u* r1 {: ?- ?- r/ u( R$ ~
edition = "2018"
6 M+ R2 _" x, Z+ j# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html2 R; l& I& p/ q6 B7 D
[lib]( C* V3 y4 \- X# g
crate-type = ["cdylib"]
6 X* B) L; r% B5 Opath = "src/lib.rs"8 [& p; R0 i% k+ X) }1 v, R
[dependencies]
" v8 |1 W; S# q2 r# `ontio-std = {git="https://github.com/ontio/ontology-Wasm-cdt-rust.git"}- ]8 K0 g9 N5 Z5 ~' Y
[features]8 u- E7 j, |9 w+ L6 @
mock = ["ontio-std/mock"]9 j: B" W7 c+ T0 x6 ~2 A# H8 _
[features]用于开启一些不稳定特性,只可在 nightly 版的编译器中使用。此处我们引入了ontio-std/mock模块,该模块模拟了与链交互的接口,也就是可以通过该模块进行合约中与链交互的模拟测试,主要方便了合约开发者在本地测试合约中与链交互的功能是否正常,无需部署到链上,就可以实现测试的功能,在后面的章节中我们会详细介绍该模块的使用方法。
* }. o5 b4 A. d5 D; R生成ontio-std库API文件
+ `, K& f1 k& ^- e/ @虽然我们引入了开发 Ontology Wasm 合约需要的工具库,但是我们还不知道该工具库中都有哪些 API 可以用,我们可以通过下面的命令生成该库的 API 文档。
6 `$ o, D2 f5 {- D: M* Mcargo doc6 k! J# Q$ {  [
执行成功后的目录结构如下:* E, A# l" g; a4 j4 P! w( G; {
├── Cargo.lock
0 v& @* n7 H2 @├── Cargo.toml/ w, q4 t: Q/ G# b( U
├── src
* r* m1 q- t, J* s0 [│   └── lib.rs
% ?& p0 |7 m3 C  D+ I7 q7 h! K└── target
7 E4 ~* M& i3 e        ├── debug% v$ c/ l2 ]1 A! H5 F: ]+ A% l
        └── doc
" a, j1 K2 u. R+ C4 D2 C  n8 C- T生成的 API 接口文档在 doc 目录下。我们可以通过浏览器打开 settings.html 文件查看。如下图所示:: f, S8 h( W* q; c; y; W

9 Y8 j- t. }+ G2 }' Z# J1 F) N; w请在左侧目录栏找到 ontio_std 库,点击该选项,如下图:
+ V! T; c2 X" E! @: p! U# c
" u( u$ [; {( H3 P上面列出了ontio_std库中封装好的所有模块,在开发合约的过程中,可以使用这些模块中的功能。' v0 @0 }; Y3 o# o5 S# f3 q1 q
编写合约逻辑
, i0 Q/ p# l- [! Z  l. K) \新建的 helloworld 合约lib.rs文件内容仅有一个测试代码,在项目根目录下,执行cargo test 来执行该测试代码。3 r; f2 N" b5 }1 w
#[cfg(test)]
! e) C; l" n* }2 [* b) _mod tests {) {: O1 J2 q2 V" |1 D
        #[test]; w2 V5 a  ^# b- t/ ?$ d
        fn it_works() {
) I) S7 c  u  A1 G                assert_eq!(2 + 2, 4);
9 ?: Q; T0 o3 d2 H! B2 I- L; Q- ^        }. E1 l  D: u/ a4 A4 K% X9 X
}
3 M; B! I$ P) K/ ^5 k1 n# K下面开始编写合约逻辑:
" }4 x0 D, I1 R# P第一步:在lib.rs文件中引入刚才在Cargo.toml配置文件中添加的ontio-std依赖,为了屏蔽rust标准库中的方法,我们加上#![no_std]注解。( t3 Z* U( v6 H/ k. f. I5 l6 ~  \
#![no_std]9 A( `  D9 @" e7 T% h
extern crate ontio_std as ostd;) s0 a5 ^1 S1 d9 I: O
#[cfg(test)]
+ u9 A7 _- N& y% V$ [& h' Z% Q; Emod tests {, |6 ~7 J# w" o# Y3 y
   ...( j3 R* p" G4 _6 X+ }
}6 i6 {5 ~& B( Z( s
第二步:添加invoke函数,该函数是 Ontology Wasm 默认的入口函数,在这个合约中,我们实现一个方法获得调用的参数并将参数返回出去,代码如下:
; w  l  D% J- s2 C$ R#![no_std]
( z! x7 `9 |6 b6 R5 |! Xextern crate ontio_std as ostd;4 J# Q* c1 Y3 `' l
use ostd::abi::{Sink, Source};( N3 Z+ [' O* u! x0 m
use ostd::prelude::*;# J1 O( E& N4 p! [9 ?
use ostd::runtime;( Q* \  _& u  [; h5 o! a3 x  s
fn say_hello(msg: &str) -> String {
6 I* L/ z4 ~) `- [        return msg.to_string();
$ H8 |# o' x  S0 N* P3 D7 G}
9 v1 z2 a: c& h) v0 K$ k$ z#[no_mangle]
  S1 U6 Y6 k3 s2 `% wfn invoke() {
6 a+ Z8 ]) j, A; W% A        let input = runtime::input();
8 F, w' C; |9 e3 R* r& V& F" ?% }( J        let mut source = Source::new(&input);7 B# D! z/ R5 R+ _
        let action: &[u8] = source.read().unwrap();
0 ^% `2 B+ G! }1 L        let mut sink = Sink::new(12);
/ g2 M+ W. e: o7 k        match action {
  C  P* t" o% }- P" j                b"hello" => {
. D& D6 p4 c0 G! ~1 ?+ d) Y' T                let msg = source.read().unwrap();6 I1 F- L$ @  X$ w
                sink.write(say_hello(msg));5 C' a6 U2 Z$ i, h
                },4 E3 f& h* P" S
                _ => panic!("unsupported action!"),2 q: M& k' r" Z1 k- w, q" s. {/ O
        }
5 S3 r! o1 Z( A* I- o8 r        runtime::ret(sink.bytes())
/ S6 |3 p' S1 d0 V2 `}, Z6 {7 j8 }  @3 {
#[test]9 c  t3 e0 ?0 Z2 l6 ?$ j4 o4 W
fn test_hello() {) X# L2 O$ E9 l4 {  ?
        let res = say_hello("hello world");9 _7 _6 \; [4 [& E$ b" X
        assert_eq!(res, "hello world".to_string());6 S; `9 \% s2 h2 V  y0 l
}: T  w, k  D; b9 H# u4 k2 R3 v! j
在合约中,我们引入了ontio-std库里面abi模块的Sink和Source数据类型。Source用于读取外部调用合约中的方法时传进来的方法名和方法参数信息,Sink用于合约中不同类型的数据序列化成 bytearray。ontio-std库里面的prelude模块提供了一些常用的数据类型,比如Address、U128和String等。把合约执行的结果返回给调用合约的程序,需要使用runtime::ret()方法,runtime模块封装与链交互的接口。4 B! U4 N5 S, v# q" k- t
至此,一个简单的返回传入参数的合约已经完成。$ F5 k9 J* O. |1 E
编译合约; `# [( M/ b0 Z% l9 V: f1 \' f
用 Rust 编写的合约源代码需要编译成Wasm字节码,才能部署到链上,执行下面的命令编译合约:
/ F. C0 F! u4 L# j; D8 H  mRUSTFLAGS="-C link-arg=-zstack-size=32768" cargo build --release --target Wasm32-unknown-unknown4 p8 q8 y/ M5 |! s
在上面的命令中,RUSTFLAGS="-C link-arg=-zstack-size=32768"表示设置 rustc 编译时使用的栈大小为32kb,rustc 编译默认设置的栈内存大小是1MB,对合约来说是巨大的浪费,因此在编译时设置下栈的大小,32kb 对于绝大多数合约来说是够用的。 Wasm32-unknown-unknown 表示在编译目标。
: l# S0 j: i7 o- h6 m( x. Y该代码执行后,会生成target文件夹,目录结构如下:
  l- A0 _6 l! X.3 r: X8 G( r1 @- q( D7 k1 u. _
├── release
& E  _! x1 ]% \* o/ [# i* z│   ├── build3 D' h* \, P7 N
│   ├── deps
7 U: N8 ~% B& Z+ r│   ├── examples
5 l  w+ h# o3 g% y; Z  c│   └── incremental2 f7 k; Y  j2 j2 A- L: s' C' o6 s7 f
└── Wasm32-unknown-unknown
2 w) I4 x" f; I. m        └── release
5 ]/ H0 o( N* R编译好的合约字节码文件为 target/Wasm32-unknown-unknown/release/ 目录下名为helloworld.Wasm的文件。
# l$ e* D& N) |. j, `$ A优化合约字节码8 B( H  Y3 n+ _
编译好的Wasm字节码文件会比较大,部署到链上需要的存储空间会比较多,费用也会比较高,但是我们可以使用ontio-Wasm-build工具将 Wasm 字节码减小。5 k5 `* q4 B: K1 f" L, ?
执行下面的命令优化该合约字节码:9 U6 i) s" [& [" e
ontio-Wasm-build ./target/Wasm32-unknown-unknown/release/hellloworld.Wasm" u9 W9 {$ x8 b; s) s
该命令执行完后,会在./target/Wasm32-unknown-unknown/release/生成的文件如下:
0 y( l$ s0 e3 _9 d% m( S4 C" {helloworld_optimized.Wasm 优化后的Wasm合约字节码;9 Y9 W4 c  M8 z( j
helloworld_optimized.Wasm.str 优化后的Wasm合约字节码的hex编码格式。( S) n& b0 h  W
测试合约
; p: Y- q. R4 P( @) y2 |6 s# M/ G, p我们将在本地测试网络中测试刚刚编写的合约。
7 d/ A1 |. a( e7 h2 s! P首先,生成钱包文件,本地测试网启动需要钱包文件,执行如下的命令:, M  {0 m/ F# |, \2 Y* l. a) N
./ontology account add# f" ]- U4 W2 a% s7 r: N
其次,启动我们搭建好的本地测试网节点,执行下面的命令:
3 l( Q7 {4 m6 u6 }& w./ontology --testmode --loglevel 1
1 u  _1 a5 N+ r( d4 ]8 g–testmode表示以测试的模式启动。: G9 [2 h# i" ]' @  _  _& Q. o
–loglevel 1 表示将日志级别设置为 debug 模式。( d* O7 s) V- S: F3 H+ }0 X2 l
然后,部署合约:# n9 S+ R% {8 m5 ~- e* ], W
$ ./ontology contract deploy --vmtype 3 --code ./helloworld.Wasm.str --name helloworld --author "author" --email "email" --desc "desc" --gaslimit 22200000
( N5 z- |1 o. `9 o2 @/ Q' ~( ZPassword:! P8 |3 B" m6 v: x9 m& v
Deploy contract:7 U4 F8 ^& c. Y
  Contract Address:913ea5298565123847ffe61ec93986a52e824a1b7 l$ W5 [! ~! }4 b
  TxHash:8386410c2ccdc5127e5bd893072a152afaa5dcf20c9b736583f803cba4f461e6, a! I3 [8 x1 y* L8 t! @# b
Tip:
6 v6 d3 R6 Z1 M  Using './ontology info status 8386410c2ccdc5127e5bd893072a152afaa5dcf20c9b736583f803cba4f461e6' to query transaction status.
" H3 n6 k: d8 u  D–vmtype 3 表示部署的合约类型是Wasm合约,目前 Ontology 链除了支持Wasm合约还支持NeoVM合约,部署的时候要注明合约类型。 --name helloworld 表示部署合约名字是helloworld。 --author “author” 表示部署合约作者是author。 --email “email” 表示部署合约 email 是email。 --gaslimit 22200000表示部署合约需要的费用 gaslimit 上限是22200000。: N: u* J; A7 x- x! |/ x9 S
最后,调用合约中的方法。由于我们在 invoke 函数里仅定义了hello方法,并且该方法将输入的参数内容直接返回。所以,调用合约的时候,第一个参数是方法名,第二个参数是合约中的该方法需要的参数。8 c% ~/ N8 y' O4 u1 X" m) m
因为我们调用的方法没有更新链上的数据,仅仅是把输入的参数返回,我们在调用合约的时候,要加上预执行标签–prepare,否则,我们看不到合约返回的结果。  }; N/ p7 ~6 w7 U
根据合约地址调用合约中的方法。该部分详细信息请参考命令行合约调用。, G$ X' ~6 f4 J7 Q4 p" }
$ ./ontology contract invoke --address 913ea5298565123847ffe61ec93986a52e824a1b --vmtype 3 --params 'string:hello,string:hello world' --version 0 --prepare' x" F  u4 I8 i
Invoke:1b4a822ea58639c91ee6ff473812658529a53e91 Params:["hello","hello world"]4 Z2 b- p4 l) {: k+ b# v
Contract invoke successfully. q( P' n6 G5 v
  Gas limit:20000- j8 s, x' }& J# t  q5 H5 ~& ]( }2 r
  Return:0b68656c6c6f20776f726c64 (raw value)
: C7 w0 r6 e) D5 u. z+ F1 c" r合约中我们的返回值是hello world,就像上一篇技术视点中提到的那样,执行结果返回了该值的hex编码68656c6c6f20776f726c64。- C7 ^/ _4 |: I) N( x2 ^$ b; s3 R
至此,我们在不依赖模板的情况下,完成了一个简单的 Ontology Wasm 合约,并进行了部署和调试。8 q: F% l; p8 t, w. r
结语
  f& f  ^/ ?3 p! m% H" E$ ]. p. P- h; i在本期技术视点中,我们简单介绍了如何在不依赖模板的情况下,完成一个简单的 Ontology Wasm 合约的开发,并进行了测试。同时,我们也介绍了 Ontology Wasm 工具库 API 文档的生成方式,方便开发者查询和调用已提供的功能。相信你一定会有所收获。下期我们将会介绍 Ontology Wasm 合约获得调用参数以及合约中数据的序列化和反序列化。欢迎关注!
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

星火车品 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    12