Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

想用Wasm开发dApp 入门教程

星火车品
191 0 0
新建合约
1 w$ ?' P4 G1 M$ f- n" b' |+ H         Created library `helloworld` package
9 H( q+ ]* ]. q* m% {# \/ Q新建的合约目录结构如下:
, B/ d# S+ ^7 F4 ^7 `2 a$ W' U$ H* @├── Cargo.toml% u$ z6 c, L! j$ P# l; u' u* @
└── src
% E, D. V) ]8 h+ b& e        └── lib.rs
+ y$ S% w7 }' {: B5 X一个 Rust 版本的 Wasm 合约包含两部分组成,一部分是Cargo.toml配置文件,用于配置项目信息,一部分是src/lib.rs用于编写合约逻辑。
4 D, A1 I6 B* q) u% d引入Ontology Wasm合约开发工具库
- }  v6 }2 d  }- g1 D在生成的Cargo.toml文件中引入 Ontology Wasm 合约开发工具库ontio-std,使用[dependencies]配置项来完成引入该工具库的动作。
) l# l- V# P& Dname = "helloworld"
4 Q2 Y9 r7 k) J) ]! y2 I" c$ pversion = "0.1.0"- d2 W/ ]5 k; W8 Q) y
authors = ["Lucas "]- B3 t9 b+ I8 h7 }; s
edition = "2018", [3 @+ X; t1 J0 G/ P2 I
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html: m! g' Q9 W$ s6 W5 H
[dependencies]& o0 b% T6 ~: B" p% B& c" X" E
ontio-std = {git="https://github.com/ontio/ontology-Wasm-cdt-rust.git"}! Z$ @4 x/ F) F0 Y2 X
由于我们合约要以库的形式进行编译,所以还需要在Cargo.toml文件里加上[lib]配置信息,一个完整的 Cargo.toml 配置文件如下:0 c/ i! C3 `7 b# x" \( R3 Q
name = "helloworld"1 W6 Z; a$ H! I8 n$ y
version = "0.1.0". U& A# I  E* ]
authors = ["Lucas "]
  j* C% K8 F3 b1 [! \$ p- S3 Oedition = "2018"
- S2 W3 d1 `  X! [& E- y# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
! |) }6 z& G9 C1 W( f- k7 O4 D[lib]
3 h/ }8 h- S: j4 s1 Q4 ]3 l6 Xcrate-type = ["cdylib"]
& Q; ?. Y5 v3 V$ ?$ i8 ^3 M* Spath = "src/lib.rs"
" v3 ~( k0 p9 R% |  t5 _, G[dependencies]/ f( ]$ Q! M( ?, q
ontio-std = {git="https://github.com/ontio/ontology-Wasm-cdt-rust.git"}: W4 e8 z5 ]2 b' `1 w+ m$ v* N0 Z
[features]8 O1 t" h  A2 u# ]
mock = ["ontio-std/mock"]
. ~# H9 r! o  D2 ~. ?- m6 x[features]用于开启一些不稳定特性,只可在 nightly 版的编译器中使用。此处我们引入了ontio-std/mock模块,该模块模拟了与链交互的接口,也就是可以通过该模块进行合约中与链交互的模拟测试,主要方便了合约开发者在本地测试合约中与链交互的功能是否正常,无需部署到链上,就可以实现测试的功能,在后面的章节中我们会详细介绍该模块的使用方法。
2 V1 ~! m2 H  L. W5 {生成ontio-std库API文件
: R9 C6 O& K6 b, }虽然我们引入了开发 Ontology Wasm 合约需要的工具库,但是我们还不知道该工具库中都有哪些 API 可以用,我们可以通过下面的命令生成该库的 API 文档。. _5 Y1 U+ E% G& ]  m
cargo doc
& y$ ^0 X( \* y) O# g执行成功后的目录结构如下:
8 R1 F  r2 n8 e- z8 d# G├── Cargo.lock6 x0 z9 u, J. w/ _* b
├── Cargo.toml
3 X! v' M0 N8 y) A├── src
' w! ~1 p; F! [6 r│   └── lib.rs/ l2 J# R' r/ j( {$ u  B
└── target
1 |  a9 G5 A+ c9 j% @! u        ├── debug
& Y* [- `* I  R9 w1 h        └── doc
4 H6 y% c9 p, F1 R8 g5 p生成的 API 接口文档在 doc 目录下。我们可以通过浏览器打开 settings.html 文件查看。如下图所示:) U3 k8 {: H- ?3 @& W; [% I' E0 C
. v7 t/ q# y$ D+ f9 N4 {
请在左侧目录栏找到 ontio_std 库,点击该选项,如下图:
  H5 K8 p, ~$ i& s0 W6 n& }1 }: ?. H9 ^9 `6 ~9 z' z6 T
上面列出了ontio_std库中封装好的所有模块,在开发合约的过程中,可以使用这些模块中的功能。
4 F- p' }6 N8 F) Q编写合约逻辑
+ ^0 {, h  K" n% T6 x, A: j* i新建的 helloworld 合约lib.rs文件内容仅有一个测试代码,在项目根目录下,执行cargo test 来执行该测试代码。* k  W) [6 R& _' B* b: {0 z
#[cfg(test)]$ j$ v% |$ f7 d5 T/ g
mod tests {
1 ~! ?$ m& r; h4 m3 ^        #[test]' \. }# O% F$ v+ o* b
        fn it_works() {* m% X% n0 Z) k. f6 P4 ~
                assert_eq!(2 + 2, 4);
6 W1 Y/ B; i9 W4 }        }/ a- q6 ?7 _$ _' u" s# D! D
}+ _' G4 R7 k/ Y0 H
下面开始编写合约逻辑:
; \* k4 j! P/ N第一步:在lib.rs文件中引入刚才在Cargo.toml配置文件中添加的ontio-std依赖,为了屏蔽rust标准库中的方法,我们加上#![no_std]注解。
7 \& S% o) @0 \2 h( h' b7 E#![no_std]
* @! ]' `3 R( j, i- i& `( ]: g9 sextern crate ontio_std as ostd;1 D0 U4 d$ @( F2 o, A" R/ s$ n$ Q) ?
#[cfg(test)]( H$ O, F. Y! l* K
mod tests {5 {9 h3 J& Z4 R& F& H  Q9 Y
   ...7 N, _3 m; s% K+ s! e
}
5 l2 p& p% r5 {2 Z) ~$ [' ]0 b第二步:添加invoke函数,该函数是 Ontology Wasm 默认的入口函数,在这个合约中,我们实现一个方法获得调用的参数并将参数返回出去,代码如下:! _9 O, c8 B/ j3 A* q% }/ ^
#![no_std]
* e$ r. E6 K3 K: Y' J: Z4 vextern crate ontio_std as ostd;% z* S3 W' \( b+ y. O9 M( E
use ostd::abi::{Sink, Source};
) C" S) P0 P( h7 t  e% U. fuse ostd::prelude::*;
5 a5 O$ Y& {9 q* Wuse ostd::runtime;. T7 W& @" w  y9 X: ^- t
fn say_hello(msg: &str) -> String {3 [4 }4 q- a! Y' S2 Z* w% b
        return msg.to_string();
7 D8 A+ z; H$ w7 o5 g" |4 A7 ?" L}
3 ^- S: H1 P( t, ~#[no_mangle], t6 P) |$ o2 j# T
fn invoke() {2 J) r: d1 D+ u0 A( o4 d: K5 q; j: W
        let input = runtime::input();& y8 q! _' a/ U1 a4 L
        let mut source = Source::new(&input);
8 C9 j* }+ o2 p$ Q: A  Z        let action: &[u8] = source.read().unwrap();
  |( |1 j8 h2 o0 o% f- [        let mut sink = Sink::new(12);
9 n0 H) o+ ]6 I. a0 i- g9 \& w8 y        match action {
, R4 i7 g) T+ B- J! _9 F, G9 r) C  N                b"hello" => {9 D& J& l0 i5 G! D: C- O
                let msg = source.read().unwrap();  e! ^% L. C0 a1 G- m
                sink.write(say_hello(msg));& D) `2 i: G- ]1 m  b5 r
                },5 y5 U9 K: f, w/ `3 K
                _ => panic!("unsupported action!"),* H  f6 w  m0 _1 X, K3 e
        }
: `2 G3 @  H1 H6 m" @        runtime::ret(sink.bytes())
, D% ?/ ?. f1 |# H}
" J9 }$ z' @0 b& g+ R7 s#[test]
  y4 b' c& P2 i  x, c5 sfn test_hello() {& _$ V' G" n, E1 X7 v* G7 n! d+ t
        let res = say_hello("hello world");* V) H+ v' ^! y; G8 Y- i9 h, X# C
        assert_eq!(res, "hello world".to_string());
3 p$ r( i, _" \6 n}* ]1 s, I; _" \+ h1 c/ ]5 P) {
在合约中,我们引入了ontio-std库里面abi模块的Sink和Source数据类型。Source用于读取外部调用合约中的方法时传进来的方法名和方法参数信息,Sink用于合约中不同类型的数据序列化成 bytearray。ontio-std库里面的prelude模块提供了一些常用的数据类型,比如Address、U128和String等。把合约执行的结果返回给调用合约的程序,需要使用runtime::ret()方法,runtime模块封装与链交互的接口。
2 t( I: n) q7 r# O! [至此,一个简单的返回传入参数的合约已经完成。, l9 J$ g% M9 n/ y3 }5 m/ `1 [
编译合约" J# U: M- b/ F8 t( T' l
用 Rust 编写的合约源代码需要编译成Wasm字节码,才能部署到链上,执行下面的命令编译合约:
7 ?$ _5 H. y5 H% N2 G8 lRUSTFLAGS="-C link-arg=-zstack-size=32768" cargo build --release --target Wasm32-unknown-unknown
* g6 t+ X7 H" p, L' I7 U在上面的命令中,RUSTFLAGS="-C link-arg=-zstack-size=32768"表示设置 rustc 编译时使用的栈大小为32kb,rustc 编译默认设置的栈内存大小是1MB,对合约来说是巨大的浪费,因此在编译时设置下栈的大小,32kb 对于绝大多数合约来说是够用的。 Wasm32-unknown-unknown 表示在编译目标。4 C6 R: d4 l) k4 l7 w; |
该代码执行后,会生成target文件夹,目录结构如下:( b8 G. n) f3 G" @
.
# M! [/ u) ^( g' \  @├── release
3 I4 m. t8 b' Y5 I$ u! t7 d│   ├── build
% P7 O" v; y+ Y' m+ s% a4 I6 i* p│   ├── deps
9 K& ?! B; c, ]  Y- G│   ├── examples
5 r: i7 Q3 c1 M. p$ i1 o$ [+ I4 H│   └── incremental
; Q4 d5 l6 w2 V  w└── Wasm32-unknown-unknown. w9 Q) y6 X6 S5 T; k
        └── release
# Q; t( v, b4 C3 D* O8 s; h4 E编译好的合约字节码文件为 target/Wasm32-unknown-unknown/release/ 目录下名为helloworld.Wasm的文件。. |* u, @, e' M
优化合约字节码
4 Q2 e4 I* b6 E7 G! I9 b' \9 x% J编译好的Wasm字节码文件会比较大,部署到链上需要的存储空间会比较多,费用也会比较高,但是我们可以使用ontio-Wasm-build工具将 Wasm 字节码减小。0 g( r/ @2 [6 t7 F8 p
执行下面的命令优化该合约字节码:) S. b( L1 K2 Y/ H3 E
ontio-Wasm-build ./target/Wasm32-unknown-unknown/release/hellloworld.Wasm: x+ _7 M  ~( A' U) S! {' A4 Q
该命令执行完后,会在./target/Wasm32-unknown-unknown/release/生成的文件如下:
6 S5 ~* n: X% ]! R( }helloworld_optimized.Wasm 优化后的Wasm合约字节码;8 Q% Y* ^) w# R9 p& P1 `8 I
helloworld_optimized.Wasm.str 优化后的Wasm合约字节码的hex编码格式。
6 ?" C7 z3 z# U' c测试合约) ~& M" z# K3 l/ l! Y+ y
我们将在本地测试网络中测试刚刚编写的合约。& Z9 e! b) g8 q6 C% M# E3 [' v
首先,生成钱包文件,本地测试网启动需要钱包文件,执行如下的命令:
2 ^& i3 H( |  {0 F' w./ontology account add: s) [  |. X& m
其次,启动我们搭建好的本地测试网节点,执行下面的命令:
6 y, W# C" F* [6 A. K( D( W( ^8 @./ontology --testmode --loglevel 1: S/ S# |$ Q" C8 C
–testmode表示以测试的模式启动。8 D; {% y' m8 p* o' y1 S
–loglevel 1 表示将日志级别设置为 debug 模式。: |" b2 T% Z6 y# a! Z/ u( R
然后,部署合约:
4 \: }: b  d3 q& v$ ./ontology contract deploy --vmtype 3 --code ./helloworld.Wasm.str --name helloworld --author "author" --email "email" --desc "desc" --gaslimit 222000002 l- ^) a! w, E8 p: H7 l4 X' c
Password:' z- x( \2 i& u% w0 J+ U
Deploy contract:
, b/ t% y# W% ~! ?  Contract Address:913ea5298565123847ffe61ec93986a52e824a1b
6 v2 v4 H( m) ]8 Q, Y+ r1 v  TxHash:8386410c2ccdc5127e5bd893072a152afaa5dcf20c9b736583f803cba4f461e6
1 U5 G; m, E2 A" HTip:
6 G0 `& {' T' P. [4 d4 d5 O  Using './ontology info status 8386410c2ccdc5127e5bd893072a152afaa5dcf20c9b736583f803cba4f461e6' to query transaction status.
- k! z+ M: l" {8 c" D–vmtype 3 表示部署的合约类型是Wasm合约,目前 Ontology 链除了支持Wasm合约还支持NeoVM合约,部署的时候要注明合约类型。 --name helloworld 表示部署合约名字是helloworld。 --author “author” 表示部署合约作者是author。 --email “email” 表示部署合约 email 是email。 --gaslimit 22200000表示部署合约需要的费用 gaslimit 上限是22200000。
) _/ ~' a% z- }3 E8 X最后,调用合约中的方法。由于我们在 invoke 函数里仅定义了hello方法,并且该方法将输入的参数内容直接返回。所以,调用合约的时候,第一个参数是方法名,第二个参数是合约中的该方法需要的参数。. z. w3 F. h/ M" D
因为我们调用的方法没有更新链上的数据,仅仅是把输入的参数返回,我们在调用合约的时候,要加上预执行标签–prepare,否则,我们看不到合约返回的结果。
* {. x$ T' _. c! a根据合约地址调用合约中的方法。该部分详细信息请参考命令行合约调用。7 h9 ^6 Z& W! }0 f! i- H# y
$ ./ontology contract invoke --address 913ea5298565123847ffe61ec93986a52e824a1b --vmtype 3 --params 'string:hello,string:hello world' --version 0 --prepare! Q! [, W1 y6 M. f" m
Invoke:1b4a822ea58639c91ee6ff473812658529a53e91 Params:["hello","hello world"]
, R3 W5 ~7 s" D2 }- |  i, ]- n& G; tContract invoke successfully
: d; W8 K; w" q- U  Gas limit:20000
. ^- H! v* V7 ^& z  Return:0b68656c6c6f20776f726c64 (raw value)
' |( R" S$ l) W! p6 l" `/ y合约中我们的返回值是hello world,就像上一篇技术视点中提到的那样,执行结果返回了该值的hex编码68656c6c6f20776f726c64。
9 u: g" _( U5 r至此,我们在不依赖模板的情况下,完成了一个简单的 Ontology Wasm 合约,并进行了部署和调试。( H  m. y1 R0 s
结语! g1 B- {7 I+ `8 W
在本期技术视点中,我们简单介绍了如何在不依赖模板的情况下,完成一个简单的 Ontology Wasm 合约的开发,并进行了测试。同时,我们也介绍了 Ontology Wasm 工具库 API 文档的生成方式,方便开发者查询和调用已提供的功能。相信你一定会有所收获。下期我们将会介绍 Ontology Wasm 合约获得调用参数以及合约中数据的序列化和反序列化。欢迎关注!
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

星火车品 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    12