Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

想用Wasm开发dApp 入门教程

星火车品
97 0 0
新建合约
. i1 N, M1 F4 v  ?- f         Created library `helloworld` package+ J# Q7 A6 c- V' x7 k! Z
新建的合约目录结构如下:
7 T- D. O! w& J& v3 L├── Cargo.toml+ q/ a! G0 O" l6 L% z
└── src
* R- Q: d& U! ?5 Z" m- E" M0 s% M        └── lib.rs  v8 x* |8 X) M3 ~% [5 P0 r
一个 Rust 版本的 Wasm 合约包含两部分组成,一部分是Cargo.toml配置文件,用于配置项目信息,一部分是src/lib.rs用于编写合约逻辑。: Q* T9 }! D$ N6 @' ?6 p) {
引入Ontology Wasm合约开发工具库
/ E0 u" u% z$ m3 N% a: p在生成的Cargo.toml文件中引入 Ontology Wasm 合约开发工具库ontio-std,使用[dependencies]配置项来完成引入该工具库的动作。
7 H4 o+ I' i& x% B  G* Uname = "helloworld"" r. O* Z& q- x- n0 v% c
version = "0.1.0"5 L$ b/ n( i( Q! M! ?
authors = ["Lucas "]
1 m% U* }& y* Y! q0 |edition = "2018"
/ N& y: L) [0 O) @2 @  z# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html% d' f4 G, F8 I1 Z
[dependencies]$ a3 w0 @+ Q! O4 i- N7 Q
ontio-std = {git="https://github.com/ontio/ontology-Wasm-cdt-rust.git"}/ C7 g' f7 E5 Z: X/ i
由于我们合约要以库的形式进行编译,所以还需要在Cargo.toml文件里加上[lib]配置信息,一个完整的 Cargo.toml 配置文件如下:) B8 z; x! R% s6 W
name = "helloworld"* m# X' [; T; T+ l, D9 c% q
version = "0.1.0"
0 }. |5 T4 w3 ?' `9 jauthors = ["Lucas "]3 B9 R$ V& ?" l0 j% ^  i1 h( ]+ m
edition = "2018"
: ]+ H$ w5 D1 c$ g& ~8 |# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html# [2 o- z  H. d' v
[lib]
3 W4 J) Y7 _# f! k% [crate-type = ["cdylib"]
! y5 v8 u/ Q, v7 Y2 xpath = "src/lib.rs"
  k: P* J" U( y* L0 t. X2 G6 O[dependencies]8 |% G" M3 y* g$ `( d
ontio-std = {git="https://github.com/ontio/ontology-Wasm-cdt-rust.git"}
2 L1 t3 c- g9 D3 e& K[features]. `# |/ Q* i2 H1 q' O1 j% B
mock = ["ontio-std/mock"]
8 m% s- I8 ]% N# o; s3 ~/ f[features]用于开启一些不稳定特性,只可在 nightly 版的编译器中使用。此处我们引入了ontio-std/mock模块,该模块模拟了与链交互的接口,也就是可以通过该模块进行合约中与链交互的模拟测试,主要方便了合约开发者在本地测试合约中与链交互的功能是否正常,无需部署到链上,就可以实现测试的功能,在后面的章节中我们会详细介绍该模块的使用方法。4 X8 T# `3 A. s4 Q, Z8 m* M& F, p
生成ontio-std库API文件
  W( n. o' ~' l% i+ N: a虽然我们引入了开发 Ontology Wasm 合约需要的工具库,但是我们还不知道该工具库中都有哪些 API 可以用,我们可以通过下面的命令生成该库的 API 文档。
) ^. m8 X; S4 G; Qcargo doc, ?! _$ Q2 j; i) L
执行成功后的目录结构如下:/ E% [9 B7 [/ O/ Q
├── Cargo.lock* b& r# Z9 F& K  i: g# A4 J# f
├── Cargo.toml* Z3 M5 e. f8 K9 I  i
├── src; a7 G8 Y; y6 u" k6 |4 \5 }+ {% ~- _' k
│   └── lib.rs6 _' I7 U$ U& t% Z0 V
└── target! S2 w1 E# n0 R+ V0 b
        ├── debug8 |$ s2 B* H' K; _
        └── doc& e9 n# C! X( q! @# D& \0 Z# G6 Y
生成的 API 接口文档在 doc 目录下。我们可以通过浏览器打开 settings.html 文件查看。如下图所示:
5 ?* |6 W2 b) @: x
- I" R& w' L* P0 d7 F请在左侧目录栏找到 ontio_std 库,点击该选项,如下图:
, u/ M, a# p1 e2 ~& M0 i, ^/ t, n! r
上面列出了ontio_std库中封装好的所有模块,在开发合约的过程中,可以使用这些模块中的功能。
/ R- C- P# E$ l5 J& m; @: `4 N9 B编写合约逻辑
7 a6 T0 `4 L# }. U! Y新建的 helloworld 合约lib.rs文件内容仅有一个测试代码,在项目根目录下,执行cargo test 来执行该测试代码。
; ^0 g7 [( F& Y/ b. p, Y# H, u#[cfg(test)]
. `- @7 [$ \- k+ emod tests {+ R. x4 W! I% M0 {6 x" |! J2 |
        #[test]9 h( l' v  b) r' m
        fn it_works() {
6 ?6 Q; d$ L: s8 C" i9 z                assert_eq!(2 + 2, 4);
* \* E5 W4 }8 V5 d/ b" h% {* i; T        }
4 e' v0 }; k* O}
  X9 s5 B. O$ Q0 [- p下面开始编写合约逻辑:8 Y0 O6 O  G3 J( b5 u6 S
第一步:在lib.rs文件中引入刚才在Cargo.toml配置文件中添加的ontio-std依赖,为了屏蔽rust标准库中的方法,我们加上#![no_std]注解。
! }$ {6 s; T0 k#![no_std]
0 c& i: f* `5 G% U# l4 yextern crate ontio_std as ostd;
  B" L5 h% o2 B, j#[cfg(test)]
2 l9 A8 }4 R' M; M4 q" hmod tests {
1 n! J. v7 x4 p3 q2 O( Z& R5 u# W2 z   ...
/ K  I. f3 T4 @! {/ F}. H2 A# F# t0 x1 o( m0 n
第二步:添加invoke函数,该函数是 Ontology Wasm 默认的入口函数,在这个合约中,我们实现一个方法获得调用的参数并将参数返回出去,代码如下:8 i' d; v% O; w. h
#![no_std]
! T, S: k" X2 Q/ q; Y. v. y3 ^extern crate ontio_std as ostd;+ y5 g' E  P9 Y' d! B( L
use ostd::abi::{Sink, Source};
- h4 n4 i4 v* t& r* C# y' W( x' vuse ostd::prelude::*;
1 _4 o' V5 m0 A) d1 ?4 N. Buse ostd::runtime;  O$ ^2 k5 {8 @  f/ _# u
fn say_hello(msg: &str) -> String {
3 ^' n8 c$ u% V; M        return msg.to_string();" {! Z, u, x6 o) U
}" A. s- I; a6 m+ @$ l* ?+ a
#[no_mangle]) ~& V1 [5 m% J! @0 X5 d
fn invoke() {
: v- D' j) O. o4 X  X, k. H# _- |* @9 G        let input = runtime::input();
: i7 L/ B2 y2 D        let mut source = Source::new(&input);
( C. y, W( ^1 |3 Q; F/ @        let action: &[u8] = source.read().unwrap();! G+ Z( a' ~, P% X( D
        let mut sink = Sink::new(12);
, ]/ A$ F, ^- {: }6 d( @8 @, M        match action {& y: q; e; _" i* z0 T9 x& p4 r
                b"hello" => {$ V  ?6 `) i5 T1 X+ @7 l
                let msg = source.read().unwrap();" c3 e3 a/ B0 w0 W7 Y
                sink.write(say_hello(msg));# V! b4 {% }* F" {# f) Z2 t
                },& w9 H& s# p8 N! I* r% A
                _ => panic!("unsupported action!"),2 |) l* D# P# P
        }! G* F+ `% c6 C+ w  L
        runtime::ret(sink.bytes())
( y! J9 |; ~5 w}7 @! \1 j3 @0 ]9 G" ?
#[test]0 P3 _' p' D& P+ T
fn test_hello() {
( E0 n5 Y6 s4 ?7 X        let res = say_hello("hello world");
2 \! t  q% i$ f) R8 @4 Z$ |        assert_eq!(res, "hello world".to_string());
- N1 w& r- B$ }1 g. `}
- O9 T& f2 P. B2 f在合约中,我们引入了ontio-std库里面abi模块的Sink和Source数据类型。Source用于读取外部调用合约中的方法时传进来的方法名和方法参数信息,Sink用于合约中不同类型的数据序列化成 bytearray。ontio-std库里面的prelude模块提供了一些常用的数据类型,比如Address、U128和String等。把合约执行的结果返回给调用合约的程序,需要使用runtime::ret()方法,runtime模块封装与链交互的接口。4 }/ A. c) C* o7 m: A9 q$ Y* Z& k) s
至此,一个简单的返回传入参数的合约已经完成。
: U  g! ]9 a$ q5 K# y7 P$ N+ G编译合约
) q+ `$ M  |2 l; z1 @用 Rust 编写的合约源代码需要编译成Wasm字节码,才能部署到链上,执行下面的命令编译合约:
: p- K( e) v6 }/ T5 i! ^RUSTFLAGS="-C link-arg=-zstack-size=32768" cargo build --release --target Wasm32-unknown-unknown
1 k4 \# r$ f) i5 j0 I* v) Q' n在上面的命令中,RUSTFLAGS="-C link-arg=-zstack-size=32768"表示设置 rustc 编译时使用的栈大小为32kb,rustc 编译默认设置的栈内存大小是1MB,对合约来说是巨大的浪费,因此在编译时设置下栈的大小,32kb 对于绝大多数合约来说是够用的。 Wasm32-unknown-unknown 表示在编译目标。) J! B8 k2 m' Y) p3 E$ r$ {6 S
该代码执行后,会生成target文件夹,目录结构如下:- F0 F" {& U( Z& g' y
.6 L4 q3 K, Q4 Y& _  X
├── release
4 o, U, ~0 h+ q# y0 f! S│   ├── build
2 A% [( W$ Z. c" ]/ A9 Y' d$ }│   ├── deps0 d7 u; S9 \9 r% Z- q( T# I( U
│   ├── examples! Q0 J) S: V6 _* {/ u) @
│   └── incremental" {! Y" J5 ]$ _4 m
└── Wasm32-unknown-unknown
  [1 Z. ^8 x2 D  q        └── release
; l% I. Q! W( `$ T! ~0 D: Z编译好的合约字节码文件为 target/Wasm32-unknown-unknown/release/ 目录下名为helloworld.Wasm的文件。% B$ Z  U+ e! E4 f2 t* T
优化合约字节码
6 S/ b/ _" p" e3 L编译好的Wasm字节码文件会比较大,部署到链上需要的存储空间会比较多,费用也会比较高,但是我们可以使用ontio-Wasm-build工具将 Wasm 字节码减小。
# d; z! u4 Y9 d* ~5 l  i执行下面的命令优化该合约字节码:
" d2 A0 h" p( @. `* E. q" eontio-Wasm-build ./target/Wasm32-unknown-unknown/release/hellloworld.Wasm
- y4 t( u$ T8 H/ Z% l( s. {; J该命令执行完后,会在./target/Wasm32-unknown-unknown/release/生成的文件如下:
$ n* R. g; I: n9 }helloworld_optimized.Wasm 优化后的Wasm合约字节码;( O, r4 n& u0 _/ D' \& o; n$ N" n
helloworld_optimized.Wasm.str 优化后的Wasm合约字节码的hex编码格式。
+ ~: W" F" ]: m% @4 C! Z. F# x测试合约. d' ^1 s; \  j7 i& T* @9 g! f
我们将在本地测试网络中测试刚刚编写的合约。7 s1 p- X9 f4 e/ _. X  F9 e
首先,生成钱包文件,本地测试网启动需要钱包文件,执行如下的命令:7 \: ]; M# V: t/ l
./ontology account add
" }- Q, Z. l. T其次,启动我们搭建好的本地测试网节点,执行下面的命令:
* m. _  w' G4 ~  ?  p./ontology --testmode --loglevel 15 b' P# _7 H" C" R7 d
–testmode表示以测试的模式启动。
+ J: U7 ?3 N% L& |4 M; [–loglevel 1 表示将日志级别设置为 debug 模式。3 e' D- n! ~6 N: J( a5 M
然后,部署合约:: Q" C# P' Z% c  p1 W  I5 ?
$ ./ontology contract deploy --vmtype 3 --code ./helloworld.Wasm.str --name helloworld --author "author" --email "email" --desc "desc" --gaslimit 22200000
$ ]3 |( d& L4 k6 JPassword:
8 _* k1 J* K, O& c5 }; ?+ e3 J. dDeploy contract:: G* ]' D& T& |7 K) m8 W
  Contract Address:913ea5298565123847ffe61ec93986a52e824a1b
& c* Q' J6 c  R7 q  TxHash:8386410c2ccdc5127e5bd893072a152afaa5dcf20c9b736583f803cba4f461e6& b7 y; E: {6 h- a4 G
Tip:
7 U) X$ l: C- O- s+ W' U% A  Using './ontology info status 8386410c2ccdc5127e5bd893072a152afaa5dcf20c9b736583f803cba4f461e6' to query transaction status.) v( w- \/ [. k
–vmtype 3 表示部署的合约类型是Wasm合约,目前 Ontology 链除了支持Wasm合约还支持NeoVM合约,部署的时候要注明合约类型。 --name helloworld 表示部署合约名字是helloworld。 --author “author” 表示部署合约作者是author。 --email “email” 表示部署合约 email 是email。 --gaslimit 22200000表示部署合约需要的费用 gaslimit 上限是22200000。
; \4 G4 M0 \$ S* G2 `  J" [最后,调用合约中的方法。由于我们在 invoke 函数里仅定义了hello方法,并且该方法将输入的参数内容直接返回。所以,调用合约的时候,第一个参数是方法名,第二个参数是合约中的该方法需要的参数。! i  k3 ~6 ~) N( u0 h8 y" I
因为我们调用的方法没有更新链上的数据,仅仅是把输入的参数返回,我们在调用合约的时候,要加上预执行标签–prepare,否则,我们看不到合约返回的结果。
9 o2 @% h: I0 H5 g根据合约地址调用合约中的方法。该部分详细信息请参考命令行合约调用。- E8 R- [9 i* n
$ ./ontology contract invoke --address 913ea5298565123847ffe61ec93986a52e824a1b --vmtype 3 --params 'string:hello,string:hello world' --version 0 --prepare$ Z- g0 o& }3 V* {5 R5 f% ]% X
Invoke:1b4a822ea58639c91ee6ff473812658529a53e91 Params:["hello","hello world"]: n2 i2 O9 A7 s% n; q* K
Contract invoke successfully
( F, T  `, M* n- ^( N" O, d5 H  Gas limit:20000' P6 j: C5 Q& @
  Return:0b68656c6c6f20776f726c64 (raw value)
( L5 A' f2 e9 |7 F! x合约中我们的返回值是hello world,就像上一篇技术视点中提到的那样,执行结果返回了该值的hex编码68656c6c6f20776f726c64。$ ^. f3 _+ R" `) W. t9 U% H
至此,我们在不依赖模板的情况下,完成了一个简单的 Ontology Wasm 合约,并进行了部署和调试。
# m# j* l! ^+ W6 s! C结语2 u% W! P- z8 l/ f0 O
在本期技术视点中,我们简单介绍了如何在不依赖模板的情况下,完成一个简单的 Ontology Wasm 合约的开发,并进行了测试。同时,我们也介绍了 Ontology Wasm 工具库 API 文档的生成方式,方便开发者查询和调用已提供的功能。相信你一定会有所收获。下期我们将会介绍 Ontology Wasm 合约获得调用参数以及合约中数据的序列化和反序列化。欢迎关注!
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

星火车品 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    12