Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

想用Wasm开发dApp 入门教程

星火车品
95 0 0
新建合约: G! u" u# |3 H- l% ^- v5 M
         Created library `helloworld` package
1 I7 C0 J& ^9 b0 _新建的合约目录结构如下:
, T8 g" w7 H' o8 }3 _1 {├── Cargo.toml, g1 U1 L. g' O
└── src
1 Y1 G5 x" P) |        └── lib.rs
5 j) p2 _4 Y; N一个 Rust 版本的 Wasm 合约包含两部分组成,一部分是Cargo.toml配置文件,用于配置项目信息,一部分是src/lib.rs用于编写合约逻辑。# l! y- l! ]( l3 V# i
引入Ontology Wasm合约开发工具库" y3 X! f; R- z# h7 K% x1 z) V
在生成的Cargo.toml文件中引入 Ontology Wasm 合约开发工具库ontio-std,使用[dependencies]配置项来完成引入该工具库的动作。
( i3 Y) p% z; s2 r# @name = "helloworld"
! B6 F  A. o% ]7 c# k% L) ]  f- Mversion = "0.1.0"
2 e0 d! P2 I$ Q$ |$ ~3 }8 z4 z' I9 {authors = ["Lucas "]! t, ~3 o9 ?5 X8 o  H1 k
edition = "2018"
3 x3 m: ]3 I& ]: e/ H# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1 F8 |, U- ?! G3 I[dependencies]
) K: E+ G3 B: [( m7 C3 Gontio-std = {git="https://github.com/ontio/ontology-Wasm-cdt-rust.git"}
: V+ M1 @8 R( ^+ Q由于我们合约要以库的形式进行编译,所以还需要在Cargo.toml文件里加上[lib]配置信息,一个完整的 Cargo.toml 配置文件如下:
! Q. g) b, Q' n& T) v$ N: n) hname = "helloworld"" Q9 @" N6 J" W4 |1 o  _6 l1 x7 Q3 |
version = "0.1.0"
5 ~5 l& u& \% M" K) w- `+ Q0 p* kauthors = ["Lucas "]
& x% J4 X& s! i. c! Q8 X8 kedition = "2018"' v7 a/ m) L: }- \9 E5 d4 t
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html. W1 B- ^  Y, [
[lib]
9 ?1 {9 d. y, u6 `( Qcrate-type = ["cdylib"]1 [5 j. A) R5 D5 w2 L# X
path = "src/lib.rs"2 t) x2 ?# _7 S9 v
[dependencies]
% h6 @3 e. ~: Y. u' c+ t5 \* \ontio-std = {git="https://github.com/ontio/ontology-Wasm-cdt-rust.git"}
" n/ ]. s  i. f% s0 b2 ]: O[features]. y1 M) d+ z4 j$ D
mock = ["ontio-std/mock"]1 K* d0 m: L3 @4 B. z& d6 k. t
[features]用于开启一些不稳定特性,只可在 nightly 版的编译器中使用。此处我们引入了ontio-std/mock模块,该模块模拟了与链交互的接口,也就是可以通过该模块进行合约中与链交互的模拟测试,主要方便了合约开发者在本地测试合约中与链交互的功能是否正常,无需部署到链上,就可以实现测试的功能,在后面的章节中我们会详细介绍该模块的使用方法。5 [4 j3 a3 E3 H
生成ontio-std库API文件
* ]/ o9 P9 l2 z" k虽然我们引入了开发 Ontology Wasm 合约需要的工具库,但是我们还不知道该工具库中都有哪些 API 可以用,我们可以通过下面的命令生成该库的 API 文档。
- H0 n7 _& T7 [- u1 r# ^1 e/ G) Hcargo doc% a4 U0 a. {" ?3 B/ T# ^
执行成功后的目录结构如下:- e8 a8 z5 h8 y7 m* q$ k* N6 l
├── Cargo.lock
$ J) P9 _# {' e+ L0 @├── Cargo.toml% _; Z, j+ _8 c/ e3 s5 C" Y* A" M
├── src
4 |. v# J7 _2 }! l* O│   └── lib.rs
2 ^% f+ t4 Z, z# G0 p└── target: j3 I% G: p  |! A9 D$ b# }
        ├── debug% @" e1 [; y$ Y- _  a  d
        └── doc
& X# Q! F: V( Q2 E- ], U( v生成的 API 接口文档在 doc 目录下。我们可以通过浏览器打开 settings.html 文件查看。如下图所示:0 c, Y. m- S, t" U
* K6 E# d$ _/ C* r; `/ N
请在左侧目录栏找到 ontio_std 库,点击该选项,如下图:
/ }( y' B3 }6 J; n( E) c  p" [# Z. M+ G) j7 A
上面列出了ontio_std库中封装好的所有模块,在开发合约的过程中,可以使用这些模块中的功能。
7 R% X4 X1 }5 s3 ]8 l编写合约逻辑
2 L" g# ]$ ]  P. q% R新建的 helloworld 合约lib.rs文件内容仅有一个测试代码,在项目根目录下,执行cargo test 来执行该测试代码。
  r$ S% l8 c& P! F- Z5 U0 X#[cfg(test)]
7 P% H3 s0 [3 F4 F% o8 M; r% Dmod tests {3 [/ z) y+ V0 p' E
        #[test]1 F5 [0 M- k! f1 `
        fn it_works() {( x) ?0 o$ v4 l. [
                assert_eq!(2 + 2, 4);
2 }. b1 n- X# G        }
& U$ F0 Z- k* i* M  ^/ K$ O, K}
; d9 l  Y* h0 E. r下面开始编写合约逻辑:+ u* \1 v1 Q  {& I  \
第一步:在lib.rs文件中引入刚才在Cargo.toml配置文件中添加的ontio-std依赖,为了屏蔽rust标准库中的方法,我们加上#![no_std]注解。+ x$ N2 w  {) q. m2 V
#![no_std]# l# S2 V1 M8 c9 G
extern crate ontio_std as ostd;7 t# F) H0 F6 S+ g7 k# O
#[cfg(test)]) _) Y' R+ d/ H3 X
mod tests {
+ ?% O7 ]. j7 {- A" Y/ H1 P( l   ...
% D. x, M' R; c5 w}" W# i- \$ C5 i% M0 u) \
第二步:添加invoke函数,该函数是 Ontology Wasm 默认的入口函数,在这个合约中,我们实现一个方法获得调用的参数并将参数返回出去,代码如下:
: ^4 `1 E5 q0 n) a) K* \#![no_std]+ e5 d# S: B! `+ H
extern crate ontio_std as ostd;' O( i5 `1 p4 G1 V! E
use ostd::abi::{Sink, Source};" E5 {- A) ?5 O" @7 o) J( d
use ostd::prelude::*;) O! s: Z2 [" c
use ostd::runtime;/ e6 j9 c  |2 c7 M
fn say_hello(msg: &str) -> String {# \+ ]$ I$ m$ F$ ^% A& f
        return msg.to_string();
. O2 O- e1 ?6 l% _- U- r4 ?}( r, h5 X2 B* Z' u! c: a
#[no_mangle]- s. H: U+ y9 _) b: r
fn invoke() {
2 N- e  s, ~0 _4 s. D, f4 M        let input = runtime::input();
: J1 g0 W; ]; ?; A4 x) p; _        let mut source = Source::new(&input);
- h( A; u7 f! i$ D$ p, S9 y$ G        let action: &[u8] = source.read().unwrap();2 {! S8 u, d0 y; b# M
        let mut sink = Sink::new(12);5 F. d1 }% O6 `
        match action {
# L+ j1 x) \4 C3 t9 k" Y                b"hello" => {
/ P5 x1 n3 b) v                let msg = source.read().unwrap();
  X5 ~: s! O4 B. G1 d4 L                sink.write(say_hello(msg));
. C) D1 a( d  J- _                },5 a% i6 x+ n8 G
                _ => panic!("unsupported action!"),6 P* D5 v+ ]$ P, |& c- x" d/ i: h
        }
, o  U- Q8 U$ v6 O. g        runtime::ret(sink.bytes())
5 {9 W+ A' K5 z8 x6 L}
! k# A: ?3 Z) L" R#[test]
8 O/ e8 Z! O8 J+ v; K/ r3 }: {fn test_hello() {5 U6 O* L8 I" o
        let res = say_hello("hello world");
4 B( c5 K: p; d( ]* U/ r" D        assert_eq!(res, "hello world".to_string());5 r. @4 ?& |" M& \. H
}
9 g4 c' w! V8 G8 {( L在合约中,我们引入了ontio-std库里面abi模块的Sink和Source数据类型。Source用于读取外部调用合约中的方法时传进来的方法名和方法参数信息,Sink用于合约中不同类型的数据序列化成 bytearray。ontio-std库里面的prelude模块提供了一些常用的数据类型,比如Address、U128和String等。把合约执行的结果返回给调用合约的程序,需要使用runtime::ret()方法,runtime模块封装与链交互的接口。8 ]( A6 W; I# M" `+ P9 J
至此,一个简单的返回传入参数的合约已经完成。
  M$ Y; y- w0 B( P0 @4 W+ ^编译合约
8 B# \6 n6 Z4 W! Z) S( z" c用 Rust 编写的合约源代码需要编译成Wasm字节码,才能部署到链上,执行下面的命令编译合约:
+ `" ]* \7 c9 E2 ~( ORUSTFLAGS="-C link-arg=-zstack-size=32768" cargo build --release --target Wasm32-unknown-unknown
4 i! z! t5 f, Z' d在上面的命令中,RUSTFLAGS="-C link-arg=-zstack-size=32768"表示设置 rustc 编译时使用的栈大小为32kb,rustc 编译默认设置的栈内存大小是1MB,对合约来说是巨大的浪费,因此在编译时设置下栈的大小,32kb 对于绝大多数合约来说是够用的。 Wasm32-unknown-unknown 表示在编译目标。
- o  y, M' w2 r) n该代码执行后,会生成target文件夹,目录结构如下:6 e: a+ _# W- G2 d
.6 k, z4 h+ h0 p; k  ~) I+ k
├── release7 K2 o9 u, K$ v% H
│   ├── build
# j# J" y/ \3 L, {  g, {│   ├── deps4 k" i, A! t7 y. ]
│   ├── examples5 v$ r/ {& w: J& P) j. S
│   └── incremental
- @  V% H5 B7 j( R7 W└── Wasm32-unknown-unknown3 G* c  I5 d+ e( v4 Z6 H
        └── release. R# f% R7 e+ ]; I3 Y! L
编译好的合约字节码文件为 target/Wasm32-unknown-unknown/release/ 目录下名为helloworld.Wasm的文件。! G( h+ Z- D! I: w% [. g  w
优化合约字节码1 I! K8 O3 s  b1 [* \
编译好的Wasm字节码文件会比较大,部署到链上需要的存储空间会比较多,费用也会比较高,但是我们可以使用ontio-Wasm-build工具将 Wasm 字节码减小。
. g, ~8 i# E7 O; Q, v9 H执行下面的命令优化该合约字节码:
2 O6 Q/ `9 S6 O3 Z( bontio-Wasm-build ./target/Wasm32-unknown-unknown/release/hellloworld.Wasm
. O- N* \" B0 v! |$ u  W4 u该命令执行完后,会在./target/Wasm32-unknown-unknown/release/生成的文件如下:
) v5 k4 k8 `/ y5 _helloworld_optimized.Wasm 优化后的Wasm合约字节码;6 c, _2 ^* \; {0 r. Q/ `& ?
helloworld_optimized.Wasm.str 优化后的Wasm合约字节码的hex编码格式。
1 |7 I" b/ t8 Y0 C! |* U9 r测试合约" L4 N! }2 N4 f' l' n5 I3 P$ f
我们将在本地测试网络中测试刚刚编写的合约。
6 f) p/ H# g0 ^2 ~" z3 P首先,生成钱包文件,本地测试网启动需要钱包文件,执行如下的命令:. [9 d; |% @& m& w" A
./ontology account add
* K- Q7 G( t+ J3 ^: r" I其次,启动我们搭建好的本地测试网节点,执行下面的命令:
7 [8 h* c" H# u+ j$ W./ontology --testmode --loglevel 1  c% U3 o8 _4 w3 t# k
–testmode表示以测试的模式启动。% E* A# I. [' c7 G) }" ?; Y
–loglevel 1 表示将日志级别设置为 debug 模式。
% A7 g6 P$ Z+ N, o然后,部署合约:
" T, w- S, t2 X0 f; u$ ./ontology contract deploy --vmtype 3 --code ./helloworld.Wasm.str --name helloworld --author "author" --email "email" --desc "desc" --gaslimit 22200000& C6 k) l! M1 h. _: i; o
Password:. |' c9 d; [, W7 K
Deploy contract:: ~1 V% t" O# m! o, _
  Contract Address:913ea5298565123847ffe61ec93986a52e824a1b6 }. q, C5 H6 w; c
  TxHash:8386410c2ccdc5127e5bd893072a152afaa5dcf20c9b736583f803cba4f461e61 G2 j* I2 z( K. T' y
Tip:
$ o6 M8 k  o3 N4 p  Using './ontology info status 8386410c2ccdc5127e5bd893072a152afaa5dcf20c9b736583f803cba4f461e6' to query transaction status.
: v( x9 m5 o7 Q–vmtype 3 表示部署的合约类型是Wasm合约,目前 Ontology 链除了支持Wasm合约还支持NeoVM合约,部署的时候要注明合约类型。 --name helloworld 表示部署合约名字是helloworld。 --author “author” 表示部署合约作者是author。 --email “email” 表示部署合约 email 是email。 --gaslimit 22200000表示部署合约需要的费用 gaslimit 上限是22200000。4 Z$ }1 {5 s9 ~8 k% W1 c; S& r! A
最后,调用合约中的方法。由于我们在 invoke 函数里仅定义了hello方法,并且该方法将输入的参数内容直接返回。所以,调用合约的时候,第一个参数是方法名,第二个参数是合约中的该方法需要的参数。( ~) A' K1 O/ |/ F
因为我们调用的方法没有更新链上的数据,仅仅是把输入的参数返回,我们在调用合约的时候,要加上预执行标签–prepare,否则,我们看不到合约返回的结果。
5 z! C' g( n0 l. O; Y4 `根据合约地址调用合约中的方法。该部分详细信息请参考命令行合约调用。& F6 C. K: w! A
$ ./ontology contract invoke --address 913ea5298565123847ffe61ec93986a52e824a1b --vmtype 3 --params 'string:hello,string:hello world' --version 0 --prepare
: Y$ Z# V6 K! e6 ]Invoke:1b4a822ea58639c91ee6ff473812658529a53e91 Params:["hello","hello world"]4 @; f3 Z" P% y2 V; w0 L; _( F! j7 o
Contract invoke successfully
) b0 a' d% d' W: [4 `; l  Gas limit:20000
, Y* U" x( o# L  Return:0b68656c6c6f20776f726c64 (raw value)5 \& C" g- _% t* K0 R3 z& {7 [
合约中我们的返回值是hello world,就像上一篇技术视点中提到的那样,执行结果返回了该值的hex编码68656c6c6f20776f726c64。! D6 K' d! k0 R8 X7 U
至此,我们在不依赖模板的情况下,完成了一个简单的 Ontology Wasm 合约,并进行了部署和调试。
# y4 F! R! g; E结语: A. d& G3 ^8 F4 f% x, n) K
在本期技术视点中,我们简单介绍了如何在不依赖模板的情况下,完成一个简单的 Ontology Wasm 合约的开发,并进行了测试。同时,我们也介绍了 Ontology Wasm 工具库 API 文档的生成方式,方便开发者查询和调用已提供的功能。相信你一定会有所收获。下期我们将会介绍 Ontology Wasm 合约获得调用参数以及合约中数据的序列化和反序列化。欢迎关注!
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

星火车品 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    12