Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

想用Wasm开发dApp 入门教程

星火车品
249 0 0
新建合约1 V' z" [( q$ I" @9 m# z" `- d" U
         Created library `helloworld` package
* n2 ^7 S; k% d: ~新建的合约目录结构如下:. P+ s% L" h1 e; L5 @: [
├── Cargo.toml
' a8 N" {' k! R4 g└── src7 `4 G  n' w6 k4 m- A: k
        └── lib.rs
9 b$ g' w- u! H$ h% P一个 Rust 版本的 Wasm 合约包含两部分组成,一部分是Cargo.toml配置文件,用于配置项目信息,一部分是src/lib.rs用于编写合约逻辑。
  W& _3 `; o. F: a1 m引入Ontology Wasm合约开发工具库
6 c# Z2 J& l& \; D9 E在生成的Cargo.toml文件中引入 Ontology Wasm 合约开发工具库ontio-std,使用[dependencies]配置项来完成引入该工具库的动作。. `8 v$ X) K* ^4 C
name = "helloworld"* l! i2 c* d# h% z4 E8 z
version = "0.1.0"
& ]' m4 ?5 a4 |: c6 }authors = ["Lucas "]2 Q7 }4 c: j) b
edition = "2018"1 a6 G/ [1 m0 z+ V8 V
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
6 a( Y  a: ^4 I4 r[dependencies]
% I$ S& ~$ G/ j$ h# ^# e* Dontio-std = {git="https://github.com/ontio/ontology-Wasm-cdt-rust.git"}" K0 K3 y/ h8 l2 C9 F+ @
由于我们合约要以库的形式进行编译,所以还需要在Cargo.toml文件里加上[lib]配置信息,一个完整的 Cargo.toml 配置文件如下:6 G( n  K0 k# X! w6 F0 o5 U; k
name = "helloworld"
7 X$ w- V8 V: q1 F5 \) O% e5 X- R6 @version = "0.1.0"
$ l" r0 Y7 Z* s$ F; b& U# Sauthors = ["Lucas "]$ g( D& K9 J+ z- r
edition = "2018". p4 [9 F  y# c) x8 U7 t
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
' z0 P' n, Z6 Y[lib]
, @, k$ A+ i3 W# Jcrate-type = ["cdylib"]- v* e0 K& z9 a" P4 X
path = "src/lib.rs"& W' ?( Z% i! W$ n
[dependencies]: W9 k# u; B) e/ A4 K
ontio-std = {git="https://github.com/ontio/ontology-Wasm-cdt-rust.git"}
8 h: c+ _: o2 M/ [2 p7 Q[features]& b9 H* V; S+ M& R; }' a" T9 P
mock = ["ontio-std/mock"]  O' M: p& o# r% H% i# }
[features]用于开启一些不稳定特性,只可在 nightly 版的编译器中使用。此处我们引入了ontio-std/mock模块,该模块模拟了与链交互的接口,也就是可以通过该模块进行合约中与链交互的模拟测试,主要方便了合约开发者在本地测试合约中与链交互的功能是否正常,无需部署到链上,就可以实现测试的功能,在后面的章节中我们会详细介绍该模块的使用方法。$ f* F- E* p) ^, ^- i  K' X% b
生成ontio-std库API文件
2 a  H: C9 x  p虽然我们引入了开发 Ontology Wasm 合约需要的工具库,但是我们还不知道该工具库中都有哪些 API 可以用,我们可以通过下面的命令生成该库的 API 文档。
/ A3 Y6 z3 U5 R1 C8 r0 Vcargo doc
% {& ]& P; [0 Z/ ?8 a执行成功后的目录结构如下:# w1 O/ S: l' I& C9 x
├── Cargo.lock
4 y* d* z2 D4 Q4 i$ @1 |* f( N├── Cargo.toml
9 f8 d9 w+ I( h& ^- z├── src# {9 P  |3 D; x( D5 r8 x) [, _
│   └── lib.rs" m5 k5 I5 c7 B  ^8 m: G. ^
└── target
; m. d' J" {) Q9 a  p        ├── debug* s/ M$ p8 y. p7 T) S
        └── doc
7 K; H: t9 n' e生成的 API 接口文档在 doc 目录下。我们可以通过浏览器打开 settings.html 文件查看。如下图所示:
+ |2 A7 J' `0 i1 T0 F, x8 _3 i  R. W: y
请在左侧目录栏找到 ontio_std 库,点击该选项,如下图:2 m6 J6 Q; s2 A3 G1 O. @) L

2 E! S" [1 P) q1 O4 e9 |& p上面列出了ontio_std库中封装好的所有模块,在开发合约的过程中,可以使用这些模块中的功能。
+ C+ H# g5 ~  x, D3 [编写合约逻辑
) K7 Q- k$ g5 J/ z! i* V5 r5 Z# |新建的 helloworld 合约lib.rs文件内容仅有一个测试代码,在项目根目录下,执行cargo test 来执行该测试代码。9 i6 }" ]  b0 P, \
#[cfg(test)]
$ i  u, ]; A' q: Y- dmod tests {/ o$ c9 ?& y. N8 o* c- A6 d& ]
        #[test]
9 F5 K9 P. ?, C        fn it_works() {
; f- G- x  O& U& Y" e, h                assert_eq!(2 + 2, 4);
# `* V8 X: s4 ~8 b' y4 E: L        }/ m( q: g! B# _# g6 Q7 P
}
/ a* B; a: Y4 P; c- M# R. @& f下面开始编写合约逻辑:
7 b2 ~/ L; M1 e4 t  @; Q, j第一步:在lib.rs文件中引入刚才在Cargo.toml配置文件中添加的ontio-std依赖,为了屏蔽rust标准库中的方法,我们加上#![no_std]注解。& n3 ^" t6 c3 V: f) O4 F
#![no_std]
4 H( }5 ]7 V; i' xextern crate ontio_std as ostd;$ ~3 q9 ~1 r% a0 x; e' ]
#[cfg(test)]
, X# V, c; Y7 i+ H. M& G& ^$ J: K" Smod tests {# |6 I  U( D! y
   ...) m( E, \. g0 L( N3 J
}
( F. C7 b: f* ?: k第二步:添加invoke函数,该函数是 Ontology Wasm 默认的入口函数,在这个合约中,我们实现一个方法获得调用的参数并将参数返回出去,代码如下:" ]- U% f1 r8 c. A
#![no_std]8 e' s1 l; y7 m
extern crate ontio_std as ostd;
3 g; _" q; L4 A) euse ostd::abi::{Sink, Source};: Z; M6 ?# f+ h% v- b* X# L
use ostd::prelude::*;" J, T+ M# `, d. N% Y$ _; ~9 j
use ostd::runtime;8 d! {. r: B. V3 D6 ]
fn say_hello(msg: &str) -> String {; A4 Y3 @* _$ V  y% K6 D2 s
        return msg.to_string();
2 L0 Y5 ?- a4 c}
: D- m/ \( Z/ I9 M' Q% s# A2 v1 {#[no_mangle]" v4 ?9 J/ Z  t& d2 t* p
fn invoke() {
9 z5 z# p- |" D  A        let input = runtime::input();- ?: Y- l$ r: O/ l& J/ H: t
        let mut source = Source::new(&input);
2 I" a2 h/ x. b4 u+ D1 v2 `5 B        let action: &[u8] = source.read().unwrap();
9 t5 Z& H( s. d2 ]; B! i        let mut sink = Sink::new(12);5 Y: l) e+ _- Y; [! ]
        match action {
! H, g2 m; T, B9 u3 P; u                b"hello" => {
* J' B) K$ P. v2 r                let msg = source.read().unwrap();
8 W+ N' a# m4 S6 W0 ?2 G5 `4 b, X                sink.write(say_hello(msg));
& w+ @  r4 _. K, {. P                },
# |5 x7 }( d7 C' |: ^                _ => panic!("unsupported action!"),; R; B, ~3 g/ }& G2 y
        }
1 k) @4 [1 R8 w; j) g5 f$ R3 w+ G        runtime::ret(sink.bytes())
0 W* S" U3 D" f4 p5 [( a}2 _8 _  Q: c! Z# [
#[test]
( F& E3 i8 A# k* m$ ?fn test_hello() {
! D  m+ L# [% X$ y  M* O        let res = say_hello("hello world");  H$ Y9 O# D7 W, ~2 i4 k3 E9 |
        assert_eq!(res, "hello world".to_string());; Q- Q+ J( M* ]! v0 N, k4 I
}$ C5 @1 z- o8 [2 f
在合约中,我们引入了ontio-std库里面abi模块的Sink和Source数据类型。Source用于读取外部调用合约中的方法时传进来的方法名和方法参数信息,Sink用于合约中不同类型的数据序列化成 bytearray。ontio-std库里面的prelude模块提供了一些常用的数据类型,比如Address、U128和String等。把合约执行的结果返回给调用合约的程序,需要使用runtime::ret()方法,runtime模块封装与链交互的接口。
8 u$ h3 j, {' k% |) P3 f2 d, h  L/ ~至此,一个简单的返回传入参数的合约已经完成。# H0 D+ e4 V# @' z% q
编译合约
5 b; L- d7 j% j0 F用 Rust 编写的合约源代码需要编译成Wasm字节码,才能部署到链上,执行下面的命令编译合约:
) E0 N$ I# F8 Q2 _) lRUSTFLAGS="-C link-arg=-zstack-size=32768" cargo build --release --target Wasm32-unknown-unknown7 z, g% t5 D' S4 K+ b* B% o2 i* w
在上面的命令中,RUSTFLAGS="-C link-arg=-zstack-size=32768"表示设置 rustc 编译时使用的栈大小为32kb,rustc 编译默认设置的栈内存大小是1MB,对合约来说是巨大的浪费,因此在编译时设置下栈的大小,32kb 对于绝大多数合约来说是够用的。 Wasm32-unknown-unknown 表示在编译目标。
: f+ a! p' Q+ c4 [& ]; ]该代码执行后,会生成target文件夹,目录结构如下:
( h  @0 w3 \1 G' N4 G.
1 k* W- a- Y# |, ^├── release
  z5 ?6 l7 @+ U& t9 [2 _7 P( f│   ├── build
/ X% Q7 d  e9 H' Z3 b0 ~│   ├── deps
8 a1 x* f& D; Q4 |; h2 n; S│   ├── examples% ], b) \' B0 F9 U
│   └── incremental
" [0 v8 P0 t  h  _└── Wasm32-unknown-unknown2 T; g( e* G' W1 @
        └── release
5 K1 D5 l1 m- D5 s8 F9 k编译好的合约字节码文件为 target/Wasm32-unknown-unknown/release/ 目录下名为helloworld.Wasm的文件。
; Q7 j' M' l8 d$ y) ]优化合约字节码" L2 r' E  M+ x0 u* l! ^
编译好的Wasm字节码文件会比较大,部署到链上需要的存储空间会比较多,费用也会比较高,但是我们可以使用ontio-Wasm-build工具将 Wasm 字节码减小。: M( P4 j6 |, I4 H6 L5 d& x4 `# a
执行下面的命令优化该合约字节码:2 Q4 V1 G0 B/ Q5 f% O: {% p$ i" y$ W
ontio-Wasm-build ./target/Wasm32-unknown-unknown/release/hellloworld.Wasm1 g! \1 H1 n; G0 ]+ p
该命令执行完后,会在./target/Wasm32-unknown-unknown/release/生成的文件如下:- T$ Q1 x& Y0 R& f
helloworld_optimized.Wasm 优化后的Wasm合约字节码;
. S5 X1 @' b4 I9 z; C3 |helloworld_optimized.Wasm.str 优化后的Wasm合约字节码的hex编码格式。
  Z! y4 {- q; w测试合约2 k6 b+ W% ~6 X2 n0 _5 w' V
我们将在本地测试网络中测试刚刚编写的合约。
* o* [. o: X4 ~1 L; u1 U: S; R/ l) a/ @5 W首先,生成钱包文件,本地测试网启动需要钱包文件,执行如下的命令:+ p0 s: V0 t7 P. p' O8 B# K0 g( I
./ontology account add1 Z# C4 s  T2 Q9 R
其次,启动我们搭建好的本地测试网节点,执行下面的命令:
+ k8 h9 ~" i* S- x- t3 q./ontology --testmode --loglevel 1
% H2 I# m5 u  k( ?; q. B# Q0 p$ Z2 J–testmode表示以测试的模式启动。% ?- g( B6 f# ], Y
–loglevel 1 表示将日志级别设置为 debug 模式。  d$ \5 |6 A) ~# t8 l) c5 v5 E
然后,部署合约:
" G; v3 M. D! D% y1 u$ ./ontology contract deploy --vmtype 3 --code ./helloworld.Wasm.str --name helloworld --author "author" --email "email" --desc "desc" --gaslimit 22200000! {+ t8 E0 {3 ~3 v
Password:
, ~, O, q) O, h& gDeploy contract:
8 V# L) n- F+ \: }: w  Contract Address:913ea5298565123847ffe61ec93986a52e824a1b
3 W" I7 F1 f6 V& Z9 b) B. v2 H' Z( {  TxHash:8386410c2ccdc5127e5bd893072a152afaa5dcf20c9b736583f803cba4f461e6
, P, R# m0 Y( ]& e8 u* vTip:
& D# x1 n3 n. C+ h8 x" x  Using './ontology info status 8386410c2ccdc5127e5bd893072a152afaa5dcf20c9b736583f803cba4f461e6' to query transaction status.' l6 Y, \" x8 F) a1 J4 l. `1 Y, ^, S
–vmtype 3 表示部署的合约类型是Wasm合约,目前 Ontology 链除了支持Wasm合约还支持NeoVM合约,部署的时候要注明合约类型。 --name helloworld 表示部署合约名字是helloworld。 --author “author” 表示部署合约作者是author。 --email “email” 表示部署合约 email 是email。 --gaslimit 22200000表示部署合约需要的费用 gaslimit 上限是22200000。
2 u' K0 t; i9 M+ W最后,调用合约中的方法。由于我们在 invoke 函数里仅定义了hello方法,并且该方法将输入的参数内容直接返回。所以,调用合约的时候,第一个参数是方法名,第二个参数是合约中的该方法需要的参数。
$ d9 M: |( B7 p6 |8 c- s因为我们调用的方法没有更新链上的数据,仅仅是把输入的参数返回,我们在调用合约的时候,要加上预执行标签–prepare,否则,我们看不到合约返回的结果。
/ f- v7 X; g. L0 {' c, q- F/ ^! B# X根据合约地址调用合约中的方法。该部分详细信息请参考命令行合约调用。7 V5 R" |/ {+ M9 M( ?
$ ./ontology contract invoke --address 913ea5298565123847ffe61ec93986a52e824a1b --vmtype 3 --params 'string:hello,string:hello world' --version 0 --prepare5 r$ j# I! w4 x5 t: u2 \9 V9 B6 _
Invoke:1b4a822ea58639c91ee6ff473812658529a53e91 Params:["hello","hello world"]0 K% m2 h9 i5 I. d9 _4 M$ g6 g" }, ^
Contract invoke successfully
7 v; P. \# Q# b* G( T7 n9 k  Gas limit:20000
3 e% C# S. D4 p. G# C0 v# {3 B% g  Return:0b68656c6c6f20776f726c64 (raw value)
  f, H6 |( h" D6 y合约中我们的返回值是hello world,就像上一篇技术视点中提到的那样,执行结果返回了该值的hex编码68656c6c6f20776f726c64。
" _! c- u5 ~' R: W3 Z/ E至此,我们在不依赖模板的情况下,完成了一个简单的 Ontology Wasm 合约,并进行了部署和调试。
5 [: M& J7 r8 \结语
4 J0 t* V2 l9 ]! \4 P9 ]) n在本期技术视点中,我们简单介绍了如何在不依赖模板的情况下,完成一个简单的 Ontology Wasm 合约的开发,并进行了测试。同时,我们也介绍了 Ontology Wasm 工具库 API 文档的生成方式,方便开发者查询和调用已提供的功能。相信你一定会有所收获。下期我们将会介绍 Ontology Wasm 合约获得调用参数以及合约中数据的序列化和反序列化。欢迎关注!
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

星火车品 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    12