Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

手把手教你Wasm合约开发(C++篇)

放弃六月们
108 0 0
Ontology Wasm 自从上线测试网以来便受到了社区开发人员的极大关注。 Wasm 的上线将使得业务逻辑复杂的 dApp 合约上链成本降低,极大丰富 dApp 生态。在进行 Wasm 合约开发时,开发者不仅可以使用 Rust,还可以使用 C++ 作为合约开发语言。本期我们将通过两个简单的示例来示范如何使用 C++ 进行Wasm 合约开发。1 V1 J) H( N+ r
一、Hello World
5 F' i4 b' }3 R; `$ _, o& ~2 v  L按照惯例,我们还是从一个 Hello world 开始
" w$ y: `& k1 y. r2 |#include#include! w* Q" Q& D* q8 X
using namespace ontio;class hello:public contract {
% E( ^0 u' V7 Mpublic:
0 Q: L0 Y" O' k& b0 l" s' U6 \& Xusing contract::contract:9 p" [" U0 i# H6 o1 T  l2 A
void sayHello(){2 \" `! E- L! d# H- @1 f, C- m
printf("hello world!");' T/ o8 V( \& _6 g( Q
}0 p! _/ w8 P  B
};) F6 e% k% f" S* k" b$ ~) ~7 K
ONTIO_DISPATCH(hello, (sayHello));+ i) a4 E+ i- k! |
1.1 合约入口
0 z, K4 n* v" o) I* POntology Wasm CDT 编译器已经对入口和参数解析进行了封装,所以开发者不需要重新定义入口方法。接下来是定义合约的对外接口,这是智能合约对外提供服务的方法。, y5 W5 ~3 N5 p" @0 t
ONTIO_DISPATCH(hello, (sayHello));$ e$ Q, S% O, [$ r6 Z
在上面的例子中, 我们暂时只支持 sayHello 这个方法:: l! h; w+ v  Q# p4 l1 Z: v
printf("hello world!");* ^; r* p2 ]2 l8 h  \/ O/ u+ G
这个“Hello world!”会在节点的日志中以调试信息打印出来。在实际的应用中, printf 只能用作调试的目的, 一个实际的智能合约,需要实现更多更复杂的功能。% W3 B0 I+ e5 {; f
1.2 智能合约 API6 c5 N( W- i8 [# v6 T" G5 C
Ontology Wasm 提供如下 API 与区块链的底层进行交互:
" p+ W4 C" x9 L! }' {3 W" z4 c
4 W# z) ?. p9 F3 u) ~: \二、红包合约0 }1 @% y7 n! J6 i; U
下面我们通过一个更加复杂的例子来演示如何通过这些 API 来开发一个完整的 Wasm 智能合约。0 K3 R: n9 v( B1 F. C/ J
很多情况下我们都会通过各种 App,如微信等聊天工具发红包。我们可以给朋友发送红包,也可以抢其他人发送的红包,收到的钱会记入到个人微信账户中。; `" m+ o4 ]6 P9 D9 V
类似于微信的流程,我们将尝试创建一个智能合约。用户使用该合约,可以发送 ONT,ONG 或者是标准的 OEP-4的 Token 资产红包给他的朋友们,而朋友们抢到的红包可以直接转入到他们的钱包账户中。
8 l6 W0 {7 w+ X  U7 h' m2.1 创建合约
4 J7 Y9 V2 _1 \8 Z) M3 G( G* L/ n. a  P首先,我们需要新建合约的源文件,暂且命名为 redEnvelope.cpp。这个合约我们需要三个接口:
& b, f" V/ Z" U& X( ~& ecreateRedEnvelope: 创建红包6 F& g* T. v& i, V3 Z; y/ R$ v" }
queryEnvelope: 查询红包信息$ f1 l& A# M% b
claimEnvelope: 抢红包% D) S. A. v3 c  ~4 F7 d
#include8 o" {  y3 k& ~8 B
using namespace ontio;" W, z" a* F1 a* K% a$ z4 S
class redEnvelope: public contract{1 u0 w+ ^) `' Z" A. M0 A
}
3 Y+ g$ [# y4 m& {* d6 X4 W/ wONTIO_DISPATCH(redEnvelope, (createRedEnvelope)(queryEnvelope)(claimEnvelope));
& _+ {3 G. C$ @* ^我们需要在存储中保存一些关键的数据。在智能合约中, 数据以 KV 的形式保存在该合约的上下文空间中,这些数据的 KEY 需要设置前缀以便于后面的查询。下面定义了三个不同的前缀供使用:
1 G2 E  s0 D9 D- ]std::string rePrefix = "RE_PREFIX_";
4 C7 S' j  r/ O2 ^1 A6 Wstd::string sentPrefix = "SENT_COUNT_";2 c  D$ q5 t2 t% R/ d$ c, M
std::string claimPrefix = "CLAIM_PREFIX_"8 k6 ~# T; V  F# f; ~* J$ G7 f. v
因为我们的合约支持 ONT 和 ONG 这两种 Ontology 的原生资产, 我们可以预先定义好这两种资产的合约地址。不同于标准的智能合约, Ontology 原生合约(native contract)的合约地址是固定的,而不是根据合约代码的 hash 计算而来的。1 l1 ]! |  c$ v% Z* f7 U* F3 f
address ONTAddress = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1};
6 \0 N' J  d$ [6 E  |5 gaddress ONGAddress = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2};
" Z/ ~' D; q* w. ~9 H) j! w7 A0 Z' h我们需要在合约中保存红包的信息, 如红包的资产信息(token 的合约地址, 红包的总金额, 红包的个数等等)& `- p! ~% |0 o8 b+ Q& e0 I) q( n
struct receiveRecord{
- L3 l% C& L) k* T; f% paddress account; //用户地址
' U) q* [/ X) F( oasset amount; //抢到的金额
3 C7 `4 G6 x( N& T- f- ]/ ?* MONTLIB_SERIALIZE(receiveRecord,(account)(amount))
2 x. d3 n" J4 O: Y% `};
3 p) x7 h$ ~. |: \) E2 V$ @struct EnvelopeStruct{
. G5 d, q6 `. K; X) faddress tokenAddress; //资产token的地址; e' W. f" {, b6 U: G% ]  n
asset totalAmount; //红包总金额
& S( ^) [* Z4 B; r, J3 l' sasset totalPackageCount; //红包总数' p* [$ h$ Q" E7 F  @
asset remainAmount; //当前剩余的金额7 W% x  U7 x0 M
asset remainPackageCount; //当前剩余的红包数3 _, t; P. R/ |. q/ ^
std::vector records; //已经抢完的记录
- E$ t4 G# L! zONTLIB_SERIALIZE( EnvelopeStruct, (tokenAddress)(totalAmount)(totalPackageCount)(remainAmount)(remainPackageCount)(records) )0 o1 w3 S# d: K: n2 M
};8 h1 F* k; U, J: M- T( w
其中,
/ E0 m2 g, Z8 ?3 mONTLIB_SERIALIZE(receiveRecord,(account)(amount))  K" W, _3 A, [9 P  N; c0 `
是由 Ontology Wasm CDT 定义的宏操作,用于在将 struct 存储前进行序列化的操作。1 G" T  e1 O7 u! K& V
2.2 创建红包
' H& C% W# e% ^# N2 m+ [准备工作差不多了,下面我们开始开发具体的接口逻辑。
" g- l4 b, z4 m5 s0 S8 _* p  F, N$ H% \创建红包需要指定创建者地址, 红包数量, 红包金额和资产的合约地址:9 r& s- u, @: v& E' D. Y% H. D
4 _* K8 Y1 |. V. f2 D, E7 a) q8 U; \3 G
bool createRedEnvelope(address owner,asset packcount, asset amount,address tokenAddr ){2 L( M9 k0 K' G7 t, X  f, e
return true;# o" Y1 U/ \, p* d0 m" G
}5 J0 K2 @  Z6 `4 r5 I2 X& k
检查是否有创建者的签名, 否则交易回滚退出:
( N. j# ?& R2 y5 z7 ^  p
  k" [. A3 }$ Y# h, }3 [ontio_assert(check_witness(owner),"checkwitness failed");$ _5 L& D$ y3 M* H- m# n( A7 C
NOTE: ontio_assert(expr, errormsg):当 expr 为 false 时, 抛出异常并退出。! x+ B' c) M8 q& m
如果红包资产是 ONT,由于 ONT 的不可分割性(最小为1个 ONT), 红包的金额要大于或等于红包的数量,保证每个红包最少有1个 ONT:
6 ^) k6 K$ }. E8 h( {
& r5 o9 V& v2 H7 ]3 s: o/ }0 Z/ Oif (isONTToken(tokenAddr)){0 h( K, K. x; R# `# q' Z$ {  d
ontio_assert(amount >= packcount,"ont amount should greater than packcount");7 I6 }: T9 D7 E+ f5 B9 c  V
}
' H5 J' ~$ R8 g. j对于每个红包的创建者,我们需要记录一下他发送红包的总数量:
8 v% ~- ]0 q0 P' Y: K* |7 n6 Z! F$ w' Z+ i- C# ?: _2 Z& G
key sentkey = make_key(sentPrefix,owner.tohexstring());
- X7 i# O9 ]5 K3 P9 aasset sentcount = 0;& }1 j! i/ h; I3 F* B! I
storage_get(sentkey,sentcount);8 ]0 {( K9 i' Q2 F8 C. Y# {
sentcount += 1;
) d8 M. m( A; g/ j: xstorage_put(sentkey,sentcount);
6 d6 |/ b4 O5 y  E2 Y生成红包 hash, 这个 hash 就是之后标识这个红包的唯一 ID:" L+ g. E% R* t

! O; a4 S& ]( m* a6 yH256 hash ;
% v: O" `5 O3 e6 f' S- {" C) t, chash256(make_key(owner,sentcount),hash) ;
$ f' a5 _6 h7 mkey rekey = make_key(rePrefix,hash256ToHexstring(hash));
0 [$ a9 \* ]0 W根据 token 资产的类型,将资产转入合约中,self_address()可以取得当前执行的合约地址, 我们根据用户输入的 token 类型,将指定数量的 token 转入合约:; a/ h! u0 d: k' }8 J- a$ n
, O: T( M; W, j! g! Z
address selfaddr = self_address();
, q. V; S+ Q% M" f  U  X! {2 n2 |if (isONTToken(tokenAddr)){
' C+ s  ~% l& O  ]6 Q* F$ abool result = ont::transfer(owner,selfaddr ,amount);
1 V) y2 g! e) _6 hontio_assert(result,"transfer native token failed!");! Q8 _1 @- z" M9 }& F( ?7 q
}else if (isONGToken(tokenAddr)){
; ?3 o* [; h( G; mbool result = ong::transfer(owner,selfaddr ,amount);, l- ], @, P. ]6 _; v; X. t) N  P/ e" b
ontio_assert(result,"transfer native token failed!");& \6 B0 |; H2 |* S
}else{# K' n8 c, D9 l" r0 k/ E
std::vector params = pack(std::string("transfer"),owner,selfaddr,amount);
" h7 W& G9 c( @& P/ c' Ebool res;
. d) m; k9 I, Z$ w* Rcall_contract(tokenAddr,params, res );
% n8 r4 Y, X! u* {. P4 j  Dontio_assert(res,"transfer oep4 token failed!");+ `8 Z* ~& h5 J. A; a5 x
}
+ D1 S0 A: w0 d& _5 T- qNOTE 1:对于 ONT 和 ONG 这两种原生资产, Ontology Wasm CDT 提供了ont::transfer API 进行转账操作;而 OEP-4类的资产,需要按照普通的跨合约调用方法来转账。
2 ~: V( N  k3 GNOTE 2:和普通的钱包地址一样, 合约地址也可以接受任意类型的资产。但是合约地址是由合约编译后的二进制代码 hash 产生的,所以没有对应的私钥,也就无法随意操作合约中的资产,如果你没有在合约中设置对资产的操作,就意味着你将无法控制这部分资产。0 h' h; O0 ]6 p% B
将合约的信息保存在存储中:
; A6 ~* K5 G  l! _9 Z7 z$ N% V5 o
struct EnvelopeStruct es ;8 f  o& j8 f3 y* x$ @3 }
es.tokenAddress = tokenAddr;5 @6 e' [: R' P- ?& d1 x
es.totalAmount = amount;0 E: X8 c6 h9 u/ v
es.totalPackageCount = packcount;5 J, o# b& v8 S# j
es.remainAmount = amount;+ d4 b; j( ]4 n/ B7 Z0 w, Z1 H) H
es.remainPackageCount = packcount;
6 X7 p% V( f4 W% w: ^2 x/ Ses.records = {};" G) z9 ?, V1 P
storage_put(rekey, es);# n$ `8 f1 ~1 B* x
发送创建红包的事件。对于智能合约的调用是一个异步的过程,合约会在执行成功后发送一个事件来通知客户端执行结果,这个事件的格式可以由合约的编写者来指定。- r: ~* A& R$ |/ c
5 B) v+ l1 @1 F9 I) h
char buffer [100];
0 S! k2 H" c% ~9 Q# ysprintf(buffer, "{\"states\":[\"%s\", \"%s\", \"%s\"]}","createEnvelope",owner.tohexstring().c_str(),hash256ToHexstring(hash).c_str());& F- w/ ?. b4 h6 R, l$ a
notify(buffer);) g$ W" o  x( C' E+ _$ n. i
return true;" a6 j+ l" ~/ Z8 _
一个简单的红包就创建完成了, 下一步我们需要实现如何查询这个红包的信息.
7 @/ t+ ?9 r6 o) w# ^2.3 查询红包6 i' _* C* R) h& l* t
查询红包的逻辑非常简单, 只需要将存储中的红包信息取出并格式化返回即可:* q0 R+ Y- j0 ~  ?% l
std::string queryEnvelope(std::string hash){
+ K3 o" `3 ~) g) O! t2 N3 p4 w0 \key rekey = make_key(rePrefix, hash);
7 H6 p* y. N; t/ |0 Dstruct EnvelopeStruct es;
2 L, C$ ~0 b4 d2 ]# [" B: Estorage_get(rekey, es);
4 C8 U$ H/ O5 j9 g" |% Z; Kreturn formatEnvelope(es);
! w* q, E# I( t/ O9 U+ @# Z}
4 c! {. n8 h: V$ T. b  W, ~NOTE:对于智能合约的只读操作(例如查询), 可以通过预执行(pre-exec)来读取结果。不同于普通的合约调用,预执行不需要钱包的签名,同时也就无需花费 ONG。最后,其他用户可以根据 hash(红包的 ID)来领取(抢)这个红包了。
8 S( k; K/ W1 o9 d2.4 领取红包; ]1 L3 }, I, F3 |  j
我们已经把资产成功地转入到智能合约中了, 接下来就可以把这个红包的 ID 发送给你的朋友们让他们去抢红包了。
# i4 i2 r& x. p- f( W领取红包需要输入领取人的账户和红包的hash:* c: H5 q0 d% o$ L0 X

! m+ Y+ R; F6 J* }$ ibool claimEnvelope(address account, std::string hash){  b* c5 ?  K) l6 p+ a3 r( q' |
return true;# d) M0 e* t9 t
}
% @# P6 }& A0 c9 D! T  P$ W& }同样, 我们需要验证领取账户的签名, 不允许替其他人抢红包, 而且每个账户每个红包只能抢一次:$ I0 e5 g5 x1 I3 ^- B

5 {' n7 c5 ^) m6 _+ xontio_assert(check_witness(account),"checkwitness failed");0 _2 `( O4 _1 f7 |9 K, {
key claimkey = make_key(claimPrefix,hash,account);
* L5 q+ Z2 _0 h- m( Nasset claimed = 0 ;
9 x+ A2 L* o! |4 astorage_get(claimkey,claimed);( G/ n) Q2 T& r
ontio_assert(claimed == 0,"you have claimed this Envelope!");
; x5 {  F0 m4 W" a8 C按照 hash 从存储中取出红包的信息, 判断这个红包是否没有被抢完:4 a4 m5 v$ }: _0 T+ W/ L% B

4 b; ]7 ^* c+ x3 L7 l1 nkey rekey = make_key(rePrefix,hash);; q' N* d5 j$ Q1 _1 j6 G+ G
struct EnvelopeStruct es;
# Q) o8 M/ v. B: d3 x- sstorage_get(rekey,es);4 Y! \. r; g8 ^) d- z
ontio_assert(es.remainAmount > 0, "the Envelope has been claimed over!");& w& S2 s! K% V& w5 G6 n
ontio_assert(es.remainPackageCount > 0, "the Envelope has been claimed over!");
* P# l4 t) e( b. E* [1 S新建一条领取的记录:
2 F9 Z# h. s1 T: ~, B# E/ y, G( _  H1 ~) m2 e8 J9 f/ j
struct receiveRecord record ;( F/ |# C5 ?1 r" D1 y( }
record.account = account;
1 I- z1 `% P; `& H; L, M3 P, qasset claimAmount = 0;
4 a: O3 `( y! [; ]& V7 {8 |计算本次领取红包的资产数量。如果是最后一个红包, 数量为剩余的金额, 否则根据当前区块 hash 计算随机数,确定本次领取的数量, 并更新红包信息:
8 u" s2 B0 T& ?0 k+ i7 D2 H
0 ^1 I) r% p  q" Yif (es.remainPackageCount == 1){
: r8 a4 e: S! q2 q4 d! \3 xclaimAmount = es.remainAmount;* Y2 U; R& A1 ], R$ ^( B
record.amount = claimAmount;
$ q& X, d' N1 ~5 o  }1 Z1 I. q}else{
5 r. K5 \; w9 k+ l/ SH256 random = current_blockhash() ;7 g! a3 x9 K2 W' h/ l' O3 v
char part[8];
1 U, r3 k7 Q* y2 j7 Ymemcpy(part,&random,8);0 `8 a' i' N8 @3 f
uint64_t random_num = *(uint64_t*)part;
; Z; x1 C: c+ I4 buint32_t percent = random_num % 100 + 1;, a/ q; v, I9 B% T, T2 i
claimAmount = es.remainAmount * percent / 100;! ]; R' U7 @6 z
//ont case8 z. P. t$ J2 U; Q+ f, n" n
if (claimAmount == 0){
, M7 E) b7 L' A3 n% U2 `  dclaimAmount = 1;
. v5 v1 @% ^0 Y. T! y0 c}else if(isONTToken(es.tokenAddress)){  i7 r2 u4 C0 P
if ( (es.remainAmount - claimAmount) 根据计算结果, 将对应资产从合约中转到领取的账户:
8 o3 L. y6 h' W2 h. O, b: h7 C( Z. v+ Q4 X% F
address selfaddr = self_address();" M6 f8 o$ j; c' A
if (isONTToken(es.tokenAddress)){) D$ q* F, r, r8 U% K( j4 |; w
bool result = ont::transfer(selfaddr,account ,claimAmount);" m- c+ y: u! s) y+ E( e
ontio_assert(result,"transfer ont token failed!");$ p& ]1 N) |8 k# u# ]. t
} else if (isONGToken(es.tokenAddress)){. _) d2 |2 e" A; F
bool result = ong::transfer(selfaddr,account ,claimAmount);# L( H& x: s' @9 ]7 T0 X, u
ontio_assert(result,"transfer ong token failed!");0 k( O8 `: Y, N- e0 q' ~
} else{$ f- w( C. W! i8 U' x, r; f4 \8 I
std::vector params = pack(std::string("transfer"),selfaddr,account,claimAmount);
( F4 k" _" Q) T4 pbool res = false;
" m: }. }- e. a* X8 {# \2 B; Wcall_contract(es.tokenAddress,params, res );" g$ i/ d/ z8 h- P+ p& [
ontio_assert(res,"transfer oep4 token failed!");5 U9 w2 ~( ^/ P* ]9 T1 a& u" B
}
  f. X6 T% Y* `* y/ T/ E记录领取的信息, 将更新后的红包信息写回存储并发送通知事件:8 t7 V$ p, ~- V+ N9 `

" P; p8 ~2 k4 X0 t1 D2 r1 G9 Q8 k) q$ Bstorage_put(claimkey,claimAmount);/ a9 J! ~+ f4 h2 O
storage_put(rekey,es);
3 h1 q6 R3 J+ xchar buffer [100];
: _/ F1 H8 \$ S$ ~. mstd::sprintf(buffer, "{\"states\":[\"%s\",\"%s\",\"%s\",\"%lld\"]}","claimEnvelope",hash.c_str(),account.tohexstring().c_str(),claimAmount);. G. u6 B' T2 D8 N) s
notify(buffer);% v, A$ d5 o7 a
return true;
4 G( k6 b$ e/ y* j如前面所说,这个合约只能通过 claimEnvelope 这个接口将资产转出合约。所以,合约中的资产是安全的,任何人都无法随意的取走里面的资产。
9 w3 Q  R7 S8 X9 n9 C" O: i$ q! A至此, 一个简单的红包合约逻辑完成, 完整的合约代码如下:https://github.com/JasonZhouPW/pubdocs/blob/master/redEnvelope.cpp
2 G  t6 J, M. N6 u: |+ |2.5 合约测试
: @% Z0 j/ E9 K/ n$ \+ ?合约测试可以有两种方法:6 N& g1 K  C) N: X+ i2 m
使用 CLI) s8 I; U* A. y; l
请参考:https://github.com/ontio/ontology-wasm-cdt-cpp/blob/master/How_To_Run_ontologywasm_node.md! S/ h0 {. S! z" n, T
使用 Golang SDK7 T2 z0 d2 p5 C6 Z7 b% p
请参考:https://github.com/ontio/ontology-wasm-cdt-cpp/blob/master/example/other/main.go% P0 _4 L4 C- V% v7 w# E' o& I
三、总结0 H' s" h& |7 t
本示例只是为了展示如何编写一个完整的 Wasm 智能合约, 如何通过调用 API 和底层的区块链进行交互。如果要作为正式的产品, 还需要解决红包的隐私问题: 所有人都可以通过监控合约的事件来取得红包的 hash, 意味着每个人都可以抢这个红包。一种比较简单的解决方法,就是在创建红包时指定哪些账户能够领取。如果有兴趣, 您也可以尝试修改测试一下。
  ~8 }# O9 [! o$ D, Y( z" q我们欢迎更多的 Wasm 技术爱好者加入本体开发社区,共同打造技术生态。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

放弃六月们 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    8