Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

放弃六月们
122 0 0
Ontology Wasm 自从上线测试网以来便受到了社区开发人员的极大关注。 Wasm 的上线将使得业务逻辑复杂的 dApp 合约上链成本降低,极大丰富 dApp 生态。在进行 Wasm 合约开发时,开发者不仅可以使用 Rust,还可以使用 C++ 作为合约开发语言。本期我们将通过两个简单的示例来示范如何使用 C++ 进行Wasm 合约开发。
4 F) L# g; c* m一、Hello World; |5 K! c7 s5 l4 z$ B5 h
按照惯例,我们还是从一个 Hello world 开始
. |; b8 ~( D+ `4 p8 L, c#include#include
6 g$ Z% z2 R  g2 D$ A4 E2 q9 ?6 Cusing namespace ontio;class hello:public contract {
' o( l  _* I' Zpublic:2 m& O  v1 ]+ n2 _& z
using contract::contract:
! [: n6 F! w3 y0 Dvoid sayHello(){
: F( P0 V, Q# p7 h1 Yprintf("hello world!");
' j+ e1 a1 u! y# K}: e$ N( r+ T5 B4 o7 ]2 M
};
6 \  [- R  J* Z0 d, _ONTIO_DISPATCH(hello, (sayHello));# S! M' X( M6 l& ^% S2 x0 Z
1.1 合约入口
. Z/ o9 d" F. cOntology Wasm CDT 编译器已经对入口和参数解析进行了封装,所以开发者不需要重新定义入口方法。接下来是定义合约的对外接口,这是智能合约对外提供服务的方法。
* U1 U1 \- y3 D# zONTIO_DISPATCH(hello, (sayHello));* L: V, k8 h% O! W# X5 [* J
在上面的例子中, 我们暂时只支持 sayHello 这个方法:7 m' X- l( I  ^) b  B
printf("hello world!");
, ~, e: @+ ~7 K+ }, k这个“Hello world!”会在节点的日志中以调试信息打印出来。在实际的应用中, printf 只能用作调试的目的, 一个实际的智能合约,需要实现更多更复杂的功能。# t# R, L# t$ ]: e$ D( Q+ j
1.2 智能合约 API
$ Z, E; s2 m5 N# y4 a9 lOntology Wasm 提供如下 API 与区块链的底层进行交互:
# N7 F6 K+ R; n6 O' D
+ \" B) n- G- R4 k二、红包合约
+ s8 O2 g' B) I# N2 n" `$ X4 M下面我们通过一个更加复杂的例子来演示如何通过这些 API 来开发一个完整的 Wasm 智能合约。
/ g7 m/ L5 A0 l很多情况下我们都会通过各种 App,如微信等聊天工具发红包。我们可以给朋友发送红包,也可以抢其他人发送的红包,收到的钱会记入到个人微信账户中。
% z$ c# C2 a8 h7 m3 [9 Q类似于微信的流程,我们将尝试创建一个智能合约。用户使用该合约,可以发送 ONT,ONG 或者是标准的 OEP-4的 Token 资产红包给他的朋友们,而朋友们抢到的红包可以直接转入到他们的钱包账户中。
* E/ n1 N% Y" m0 u9 m" C2.1 创建合约
3 F, A  l( j  B: B, u首先,我们需要新建合约的源文件,暂且命名为 redEnvelope.cpp。这个合约我们需要三个接口:, B$ Z# c2 ]( x; h* Z( [$ H
createRedEnvelope: 创建红包
+ b: Z+ Z" H& l/ GqueryEnvelope: 查询红包信息8 e3 }* j$ p& K, ^% |' C; V
claimEnvelope: 抢红包7 `5 ^/ T/ e6 a! I
#include
: ?: u- }' {+ `- f, R9 Cusing namespace ontio;! F! n4 e. s& l. F6 I0 ^: d
class redEnvelope: public contract{
& X; z, O# L, d3 |}% x' p( F7 J: s" [, @# Y3 J
ONTIO_DISPATCH(redEnvelope, (createRedEnvelope)(queryEnvelope)(claimEnvelope));& i$ L' J% F; A0 M9 m4 t9 I
我们需要在存储中保存一些关键的数据。在智能合约中, 数据以 KV 的形式保存在该合约的上下文空间中,这些数据的 KEY 需要设置前缀以便于后面的查询。下面定义了三个不同的前缀供使用:
6 J3 \9 }( R0 o) h; `" }- w, u& }std::string rePrefix = "RE_PREFIX_";# u9 _0 y) y5 X/ L( [1 k# ~
std::string sentPrefix = "SENT_COUNT_";
: b9 x* A! F( A. ?. H* Q; }( {1 sstd::string claimPrefix = "CLAIM_PREFIX_"5 ~: ]. ~; W' @. S
因为我们的合约支持 ONT 和 ONG 这两种 Ontology 的原生资产, 我们可以预先定义好这两种资产的合约地址。不同于标准的智能合约, Ontology 原生合约(native contract)的合约地址是固定的,而不是根据合约代码的 hash 计算而来的。
! w" G9 M" O9 j1 eaddress ONTAddress = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1};7 s1 l2 n5 U% {
address ONGAddress = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2};$ u- _: l% |7 w
我们需要在合约中保存红包的信息, 如红包的资产信息(token 的合约地址, 红包的总金额, 红包的个数等等)
, ~" Y9 a7 L1 v: ]8 i1 estruct receiveRecord{
  [) }& P8 c! }; h  a) {; T$ Eaddress account; //用户地址
, B  d  a1 h6 I6 Q! j# N5 M! Aasset amount; //抢到的金额
. p; d# d3 ~% E5 j7 }3 ?ONTLIB_SERIALIZE(receiveRecord,(account)(amount))4 u) ~3 W5 ^$ g2 W
};8 s) U1 _0 x! s/ O2 v9 ^
struct EnvelopeStruct{
' H' _4 z+ n; I" A! {address tokenAddress; //资产token的地址
& r9 h5 i2 V/ \9 y7 ]* Aasset totalAmount; //红包总金额8 l3 L& r$ h8 J
asset totalPackageCount; //红包总数, m' t  W- f% `* y  y; c5 c
asset remainAmount; //当前剩余的金额8 l  {, R: p2 t' ]  w+ u* u9 G7 \
asset remainPackageCount; //当前剩余的红包数
) x; F0 T4 V& c+ ?# ~% Vstd::vector records; //已经抢完的记录" Q; ?5 j: ]8 ~; q
ONTLIB_SERIALIZE( EnvelopeStruct, (tokenAddress)(totalAmount)(totalPackageCount)(remainAmount)(remainPackageCount)(records) )$ p0 ]; c6 [& Q4 F2 \/ l0 [2 i
};- G3 ]6 r2 Y2 y- ]; a& v
其中,; k/ L& H% t4 n  H$ B
ONTLIB_SERIALIZE(receiveRecord,(account)(amount))
' s& @# P6 B$ ?1 i! k& S$ y是由 Ontology Wasm CDT 定义的宏操作,用于在将 struct 存储前进行序列化的操作。; `9 _" Z& A, V
2.2 创建红包# r  f4 G: \6 {& N" g5 F" X
准备工作差不多了,下面我们开始开发具体的接口逻辑。
2 |5 |) f9 [5 [& l创建红包需要指定创建者地址, 红包数量, 红包金额和资产的合约地址:
$ H$ s, G8 [/ _/ F4 I+ n! H1 t9 L/ |$ B6 m  Z
bool createRedEnvelope(address owner,asset packcount, asset amount,address tokenAddr ){
, K- Z  m4 p6 jreturn true;/ x+ n; A0 H6 ^% `# S5 B( n
}
  h' \2 I  @! e检查是否有创建者的签名, 否则交易回滚退出:
; m9 B2 y& Y& ?/ K4 t) C, f! S! m% w1 E
ontio_assert(check_witness(owner),"checkwitness failed");3 h8 D: \2 h, q' ]* v/ g
NOTE: ontio_assert(expr, errormsg):当 expr 为 false 时, 抛出异常并退出。
0 D' t  ^9 W/ N0 f1 X% i如果红包资产是 ONT,由于 ONT 的不可分割性(最小为1个 ONT), 红包的金额要大于或等于红包的数量,保证每个红包最少有1个 ONT:  P! o. y4 E3 U
- `" i! n+ o4 \5 @: N- X& o6 C
if (isONTToken(tokenAddr)){
# X. z, W4 N- |  k& [  Zontio_assert(amount >= packcount,"ont amount should greater than packcount");
& o$ [: G: f  ~3 S" v}
! R7 j# ?) a9 l5 H4 E对于每个红包的创建者,我们需要记录一下他发送红包的总数量:
* ?2 @% X& y: M3 v4 S4 q
" O* N( U3 Y* ^$ P- zkey sentkey = make_key(sentPrefix,owner.tohexstring());
  w; b* T0 ~7 xasset sentcount = 0;
5 h0 @) I' n, X1 Ostorage_get(sentkey,sentcount);7 |  o- `4 J' I
sentcount += 1;* U2 S6 y; x! _5 I
storage_put(sentkey,sentcount);4 P+ H. q% D6 s' ?0 Z- I
生成红包 hash, 这个 hash 就是之后标识这个红包的唯一 ID:" X5 H  D3 q7 p7 g

. v/ M. T5 X; p, GH256 hash ;  \! M) E5 y" m* K! F0 U6 t
hash256(make_key(owner,sentcount),hash) ;$ W& O9 y; k$ Y5 r# N+ D
key rekey = make_key(rePrefix,hash256ToHexstring(hash));
% I7 v5 E. N3 {0 g根据 token 资产的类型,将资产转入合约中,self_address()可以取得当前执行的合约地址, 我们根据用户输入的 token 类型,将指定数量的 token 转入合约:7 \/ R+ ]! f" |5 _' n" T- S

) n7 {5 g" U) e( j* i4 P1 raddress selfaddr = self_address();
- P8 ~% O" \. z  F# Yif (isONTToken(tokenAddr)){
) c. q9 E! V  s5 Kbool result = ont::transfer(owner,selfaddr ,amount);
# ]+ v; M& P) Y0 fontio_assert(result,"transfer native token failed!");# _7 F1 K1 s6 Q8 D- o& f! I, Q/ `
}else if (isONGToken(tokenAddr)){
; p- P! A/ f4 v* B* U" kbool result = ong::transfer(owner,selfaddr ,amount);! }  N# a% Q4 e; y. @
ontio_assert(result,"transfer native token failed!");
' O% f, o$ L6 T}else{
* `5 K; Q. M- ]+ k( t* B8 vstd::vector params = pack(std::string("transfer"),owner,selfaddr,amount);9 @% i: G4 r) G( A2 T
bool res;
2 |4 V& n! a0 x9 q; _call_contract(tokenAddr,params, res );# j4 p8 l! K* ^) S6 k4 a
ontio_assert(res,"transfer oep4 token failed!");7 d  I1 Y4 t, a9 J+ }* m
}
) r$ ]1 c, l6 ^; n/ wNOTE 1:对于 ONT 和 ONG 这两种原生资产, Ontology Wasm CDT 提供了ont::transfer API 进行转账操作;而 OEP-4类的资产,需要按照普通的跨合约调用方法来转账。' i- L; _" a# t# G# u
NOTE 2:和普通的钱包地址一样, 合约地址也可以接受任意类型的资产。但是合约地址是由合约编译后的二进制代码 hash 产生的,所以没有对应的私钥,也就无法随意操作合约中的资产,如果你没有在合约中设置对资产的操作,就意味着你将无法控制这部分资产。# O5 D) B' c# B* v& G4 q! y# n
将合约的信息保存在存储中:% A1 X8 t8 U3 e
8 O2 e1 s7 w' t/ s: }$ ]
struct EnvelopeStruct es ;
. ^& Y7 K5 F. b% R2 d0 Z" ges.tokenAddress = tokenAddr;
& {  p, F/ c( f* n& r9 ?* I& xes.totalAmount = amount;5 C, G3 [3 B  K) l& b1 f
es.totalPackageCount = packcount;3 T% u7 S+ X7 }0 W2 [: h* j
es.remainAmount = amount;, G$ `8 k/ l2 h
es.remainPackageCount = packcount;
/ b3 [' B+ M2 j6 l4 m4 Ses.records = {};
$ m7 H, d5 b/ ?4 D8 u, `storage_put(rekey, es);
/ P3 s+ j0 U! F& j: L! B- [发送创建红包的事件。对于智能合约的调用是一个异步的过程,合约会在执行成功后发送一个事件来通知客户端执行结果,这个事件的格式可以由合约的编写者来指定。/ g- H+ N) l) U3 }# l  E. t

3 m( K; X# d5 S+ N) K+ fchar buffer [100];7 X$ f: `! y! k; L& ]4 m7 t
sprintf(buffer, "{\"states\":[\"%s\", \"%s\", \"%s\"]}","createEnvelope",owner.tohexstring().c_str(),hash256ToHexstring(hash).c_str());  L4 N- b3 @1 [3 M% X( J7 `' n3 B
notify(buffer);
2 N, K$ t* T$ h9 N7 Mreturn true;1 C0 y0 D% i  Z$ n/ F4 V! V6 _6 i+ q
一个简单的红包就创建完成了, 下一步我们需要实现如何查询这个红包的信息.5 Z% o9 ]& j" l4 W; `
2.3 查询红包. U9 B! m& M/ j
查询红包的逻辑非常简单, 只需要将存储中的红包信息取出并格式化返回即可:
1 o  o5 M* s# R, l' Q  s+ U# Cstd::string queryEnvelope(std::string hash){
. l3 p7 k( i0 h0 m* Ckey rekey = make_key(rePrefix, hash);* m! d1 M& i; S  \9 [1 ^0 J
struct EnvelopeStruct es;  A2 b  Y7 j/ M0 l2 m1 f) n/ J$ P
storage_get(rekey, es);
# J: V' T0 c6 P8 u3 @return formatEnvelope(es);
, z. w; _0 U8 p( H# ]5 r% s/ n' R}
0 f# m1 m% S2 B4 _5 J5 g" tNOTE:对于智能合约的只读操作(例如查询), 可以通过预执行(pre-exec)来读取结果。不同于普通的合约调用,预执行不需要钱包的签名,同时也就无需花费 ONG。最后,其他用户可以根据 hash(红包的 ID)来领取(抢)这个红包了。9 V# X7 ^6 X# H( Y/ B' \* v8 f
2.4 领取红包
  L1 W( ]6 {3 {我们已经把资产成功地转入到智能合约中了, 接下来就可以把这个红包的 ID 发送给你的朋友们让他们去抢红包了。7 k/ T9 c4 Q1 i& E4 `$ E" F, E
领取红包需要输入领取人的账户和红包的hash:) B# P6 }% e0 \9 f8 E/ j: h. G& K) P
3 R9 t! W7 {( d/ k% p  d/ ?
bool claimEnvelope(address account, std::string hash){
. l' z2 h/ j3 P, W# N4 |; oreturn true;
3 I. r: i( C$ F6 R4 f4 j}  {1 i  y# ]# r4 K
同样, 我们需要验证领取账户的签名, 不允许替其他人抢红包, 而且每个账户每个红包只能抢一次:
7 {# I! V9 S$ D! V9 v
2 Z  m" G* h1 A% ~4 oontio_assert(check_witness(account),"checkwitness failed");
) h) z, s6 |0 k- v4 _! ~* k% xkey claimkey = make_key(claimPrefix,hash,account);* R6 E7 v  P, W+ s
asset claimed = 0 ;: Q" |$ x% U0 V
storage_get(claimkey,claimed);% V8 ?- v- ^# t5 T. X
ontio_assert(claimed == 0,"you have claimed this Envelope!");
1 {' ^, I4 X8 T0 T按照 hash 从存储中取出红包的信息, 判断这个红包是否没有被抢完:
& \+ G6 `; W' g% U4 k
% n) o6 A; Q6 {9 k# Q+ j/ R+ mkey rekey = make_key(rePrefix,hash);
/ g7 {$ p6 J; {/ Q7 k9 c& B0 M1 Ystruct EnvelopeStruct es;% \1 g; F: E- K! {6 P, T! W
storage_get(rekey,es);
1 |5 Z; C# _6 ~2 N: d+ A, h) qontio_assert(es.remainAmount > 0, "the Envelope has been claimed over!");
8 J( h( u% c: @: w" H6 l; M: l! eontio_assert(es.remainPackageCount > 0, "the Envelope has been claimed over!");
$ k1 d* P9 \1 Z8 w+ m, |新建一条领取的记录:$ k3 R5 p: {$ h+ d
- j/ Y/ M; u* \6 U
struct receiveRecord record ;
% G# S/ `/ f8 M+ V) J# srecord.account = account;* a# t0 p- j5 B6 n: W4 A& E# R$ J
asset claimAmount = 0;
' |4 j- ^4 W; w1 f% x4 ]! ^计算本次领取红包的资产数量。如果是最后一个红包, 数量为剩余的金额, 否则根据当前区块 hash 计算随机数,确定本次领取的数量, 并更新红包信息:. \: c+ o* L+ x) C7 X2 \% G

. F: A8 O2 V8 J* J( ^if (es.remainPackageCount == 1){. e# }4 K0 M" y: V' y7 \, j. i
claimAmount = es.remainAmount;
! S! x# L4 Z3 F1 P, C$ F: e8 k6 ~record.amount = claimAmount;5 A6 ~2 n" w/ o, S; ~1 N5 e: r+ t
}else{
+ I( J( u- d6 r5 I  |H256 random = current_blockhash() ;
8 z" {' K$ A% i7 c) z6 A1 vchar part[8];' Y2 N7 i; |* r
memcpy(part,&random,8);
" j( ^( b* e  o2 C+ `% }; {uint64_t random_num = *(uint64_t*)part;
5 y6 I: \# a# X6 |+ b; guint32_t percent = random_num % 100 + 1;5 D. O) J) o9 Q! l
claimAmount = es.remainAmount * percent / 100;2 e* R/ a* Q' D. O9 T$ ]! W: ]4 ]* q
//ont case
0 H; q1 t3 h8 ^2 F: |if (claimAmount == 0){
6 @5 A- q6 L0 DclaimAmount = 1;0 y# C9 V$ k. z% o+ G
}else if(isONTToken(es.tokenAddress)){% M: v, \7 u/ j4 ~2 T
if ( (es.remainAmount - claimAmount) 根据计算结果, 将对应资产从合约中转到领取的账户:3 u- v& A5 D! w- Z5 r+ A* z$ Q
, Z. k( [; f$ c+ T1 O
address selfaddr = self_address();$ p2 b) Q+ |3 `+ S- o
if (isONTToken(es.tokenAddress)){4 _9 U8 U# O) W, ~
bool result = ont::transfer(selfaddr,account ,claimAmount);
' z. O* }8 x/ G8 ~ontio_assert(result,"transfer ont token failed!");3 `+ T3 O: a  Q. N& S& q" @
} else if (isONGToken(es.tokenAddress)){' F4 o1 Q- [) I/ ]8 y* E
bool result = ong::transfer(selfaddr,account ,claimAmount);
" a% d. c! S* |, q# D  `  yontio_assert(result,"transfer ong token failed!");9 E) t! u$ X  d. `2 ]" l5 a
} else{2 r% M' y! R+ J, Y
std::vector params = pack(std::string("transfer"),selfaddr,account,claimAmount);
, U9 g8 [- I8 S+ Z) P: \8 B0 L) A6 D8 ebool res = false;' j. U- K4 a. Z2 a+ L& n+ y% A- K
call_contract(es.tokenAddress,params, res );* H  D0 U! X8 {% b; J
ontio_assert(res,"transfer oep4 token failed!");" `) h. s* A5 s2 v$ h
}
; ]$ |$ ]& r9 O1 g2 j7 X4 \3 r记录领取的信息, 将更新后的红包信息写回存储并发送通知事件:8 ^5 H. [4 w3 Z, `0 g) y, ]
0 h4 I5 L! r; I% t  N" c; i9 h; g
storage_put(claimkey,claimAmount);2 J( T# W- G" V8 N9 z/ d
storage_put(rekey,es);: T  {. ^& F+ f6 B6 w' r4 g
char buffer [100];
0 l* [! t1 _+ |! Ystd::sprintf(buffer, "{\"states\":[\"%s\",\"%s\",\"%s\",\"%lld\"]}","claimEnvelope",hash.c_str(),account.tohexstring().c_str(),claimAmount);
: S, b: g; r' \( @# @9 @6 I0 r% ynotify(buffer);- @$ a$ k1 B0 b
return true;, v2 ]! N% E. J! \  d' `+ g: E* [
如前面所说,这个合约只能通过 claimEnvelope 这个接口将资产转出合约。所以,合约中的资产是安全的,任何人都无法随意的取走里面的资产。
7 O5 g& P8 z' u. z至此, 一个简单的红包合约逻辑完成, 完整的合约代码如下:https://github.com/JasonZhouPW/pubdocs/blob/master/redEnvelope.cpp
5 b6 C" L: i  d2 g( g% F& w" I6 w. e2.5 合约测试
! a6 S' a2 h7 E  t合约测试可以有两种方法:
# t: Q. g( f2 ~. r使用 CLI/ o& A; O2 T2 T; U( G2 G
请参考:https://github.com/ontio/ontology-wasm-cdt-cpp/blob/master/How_To_Run_ontologywasm_node.md
* w- O8 C. ?! g' H6 D: R5 q4 s! i使用 Golang SDK" j+ ^! T  v. z# D7 ]7 m
请参考:https://github.com/ontio/ontology-wasm-cdt-cpp/blob/master/example/other/main.go
2 @+ f; K5 \' [. v- g三、总结
  y* p2 }9 K9 }" K3 H0 Z本示例只是为了展示如何编写一个完整的 Wasm 智能合约, 如何通过调用 API 和底层的区块链进行交互。如果要作为正式的产品, 还需要解决红包的隐私问题: 所有人都可以通过监控合约的事件来取得红包的 hash, 意味着每个人都可以抢这个红包。一种比较简单的解决方法,就是在创建红包时指定哪些账户能够领取。如果有兴趣, 您也可以尝试修改测试一下。3 P& |& u# f/ T3 {# J
我们欢迎更多的 Wasm 技术爱好者加入本体开发社区,共同打造技术生态。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

放弃六月们 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    8