Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

放弃六月们
127 0 0
Ontology Wasm 自从上线测试网以来便受到了社区开发人员的极大关注。 Wasm 的上线将使得业务逻辑复杂的 dApp 合约上链成本降低,极大丰富 dApp 生态。在进行 Wasm 合约开发时,开发者不仅可以使用 Rust,还可以使用 C++ 作为合约开发语言。本期我们将通过两个简单的示例来示范如何使用 C++ 进行Wasm 合约开发。% ~  H3 p' P* r" t; Y8 o. c
一、Hello World
: v0 s; I' @: s( _按照惯例,我们还是从一个 Hello world 开始
2 V) Y( L7 H3 _#include#include. V$ a- L0 ~' {. ?6 d
using namespace ontio;class hello:public contract {; \6 m- X! |- t) Q) `" T. f
public:1 s" o" H) r% f, D/ r7 o
using contract::contract:
  J5 M# y0 m# r' n3 C# z" [/ ]void sayHello(){  i+ |' A4 R6 k+ o
printf("hello world!");
/ B9 ]+ b2 y* R}
- ]4 _1 A  \' w; J; R' n};/ Q* H1 S0 u3 B  i$ f
ONTIO_DISPATCH(hello, (sayHello));
6 t( C9 c4 |; K9 Y1.1 合约入口
; C: T, Q- B2 TOntology Wasm CDT 编译器已经对入口和参数解析进行了封装,所以开发者不需要重新定义入口方法。接下来是定义合约的对外接口,这是智能合约对外提供服务的方法。; E4 b+ r3 g3 c0 S7 X8 |
ONTIO_DISPATCH(hello, (sayHello));
3 n2 i' W" h& G% D在上面的例子中, 我们暂时只支持 sayHello 这个方法:
7 ~. \- e$ w  a4 E- \7 Iprintf("hello world!");) T$ Z* T* i0 f
这个“Hello world!”会在节点的日志中以调试信息打印出来。在实际的应用中, printf 只能用作调试的目的, 一个实际的智能合约,需要实现更多更复杂的功能。1 w4 D' k4 }8 ?; Q) f: @
1.2 智能合约 API$ _8 ~! \* S* i
Ontology Wasm 提供如下 API 与区块链的底层进行交互:/ u0 s0 K0 w7 {- G
6 H0 F' e% l/ X: y" }
二、红包合约2 X0 X9 X+ }0 Q3 _, N1 ~+ ^  W, I  C
下面我们通过一个更加复杂的例子来演示如何通过这些 API 来开发一个完整的 Wasm 智能合约。
6 Q9 _8 r6 }' c2 }7 v9 O很多情况下我们都会通过各种 App,如微信等聊天工具发红包。我们可以给朋友发送红包,也可以抢其他人发送的红包,收到的钱会记入到个人微信账户中。
* |8 y  B  R/ h1 X类似于微信的流程,我们将尝试创建一个智能合约。用户使用该合约,可以发送 ONT,ONG 或者是标准的 OEP-4的 Token 资产红包给他的朋友们,而朋友们抢到的红包可以直接转入到他们的钱包账户中。& Y5 o; }* Z. ^
2.1 创建合约, t6 ~0 o) ]2 ?, S4 S1 e* n* w
首先,我们需要新建合约的源文件,暂且命名为 redEnvelope.cpp。这个合约我们需要三个接口:5 Z. s+ N& U$ G  [9 e( u$ Z+ h
createRedEnvelope: 创建红包# G- N6 ~4 H3 N7 b; l0 L
queryEnvelope: 查询红包信息  Q& v5 j8 ?, L8 D  O
claimEnvelope: 抢红包2 J  q  l! z0 k+ K+ E8 H) E4 T; n3 Y
#include
  C: {9 k" g. qusing namespace ontio;3 L5 F( M+ @8 K# g  v
class redEnvelope: public contract{
! N: c3 B6 I/ D6 L}9 u* Q! N/ @( D( v$ N% r4 I. F
ONTIO_DISPATCH(redEnvelope, (createRedEnvelope)(queryEnvelope)(claimEnvelope));6 R/ b7 B9 v& v6 |# u/ a
我们需要在存储中保存一些关键的数据。在智能合约中, 数据以 KV 的形式保存在该合约的上下文空间中,这些数据的 KEY 需要设置前缀以便于后面的查询。下面定义了三个不同的前缀供使用:5 ~+ c( w2 ^* k$ L+ k& F
std::string rePrefix = "RE_PREFIX_";" p  L. W: d2 P9 F
std::string sentPrefix = "SENT_COUNT_";
0 V4 ]- s' |# v4 ^  {& qstd::string claimPrefix = "CLAIM_PREFIX_"( }8 `& r6 o2 f3 G
因为我们的合约支持 ONT 和 ONG 这两种 Ontology 的原生资产, 我们可以预先定义好这两种资产的合约地址。不同于标准的智能合约, Ontology 原生合约(native contract)的合约地址是固定的,而不是根据合约代码的 hash 计算而来的。6 [4 M# D  E9 ]7 e$ _* D6 e
address ONTAddress = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1};% V' t( e5 z- U" h" V/ e
address ONGAddress = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2};4 A$ |( C* o4 A
我们需要在合约中保存红包的信息, 如红包的资产信息(token 的合约地址, 红包的总金额, 红包的个数等等)
  ~1 @6 f! C6 \8 w9 {* t1 lstruct receiveRecord{+ J; S- V$ N! s2 C1 A; w
address account; //用户地址8 Z: w8 x  G: z7 ]( v3 W
asset amount; //抢到的金额# I) D# E0 q! i7 }% ]% M
ONTLIB_SERIALIZE(receiveRecord,(account)(amount))
+ j4 G9 e7 }5 T* [( K( Q# k7 R  C};
2 _5 I9 b  N( [5 E1 E8 C  astruct EnvelopeStruct{
* W8 B/ l% o# J* }7 F% saddress tokenAddress; //资产token的地址4 {6 F  Q/ j8 v1 D* i' Z( h
asset totalAmount; //红包总金额) T& G. z  h4 l. e
asset totalPackageCount; //红包总数
& W: Q7 @/ f5 O7 u9 Fasset remainAmount; //当前剩余的金额
, n2 K$ H# Z; s7 c5 h! O, [6 |# J4 Dasset remainPackageCount; //当前剩余的红包数
! R& |4 r3 @$ d2 Q+ s. |std::vector records; //已经抢完的记录
8 O9 d! R4 S9 v7 S! U& ]/ k- ^7 sONTLIB_SERIALIZE( EnvelopeStruct, (tokenAddress)(totalAmount)(totalPackageCount)(remainAmount)(remainPackageCount)(records) )
2 G- [5 B1 Z0 }1 o) r8 c8 W};
$ |' M+ p) E- F! u. Z) g1 W其中,
; e& {& y& d+ B2 |# z; b. V- uONTLIB_SERIALIZE(receiveRecord,(account)(amount))
( M) y9 [- q: a( |, d* k是由 Ontology Wasm CDT 定义的宏操作,用于在将 struct 存储前进行序列化的操作。
- G# l4 Q+ n! V  p  T5 ]$ }2.2 创建红包5 A* G. O8 N9 u
准备工作差不多了,下面我们开始开发具体的接口逻辑。+ ~7 q1 N% @# f- M2 r
创建红包需要指定创建者地址, 红包数量, 红包金额和资产的合约地址:
/ V2 v# j1 v" i, _
, d3 R6 `( h: \( b' H8 Pbool createRedEnvelope(address owner,asset packcount, asset amount,address tokenAddr ){$ Z" z! _! U* ]$ w6 N8 z2 V' k2 q: B
return true;
) P* _) a( y; p/ W0 f& L, Y2 q}
* ~! _4 |4 m; m检查是否有创建者的签名, 否则交易回滚退出:4 ?* \2 T' \2 `# K' Y) D0 C
, e& i  ?! _; O# a+ B2 G
ontio_assert(check_witness(owner),"checkwitness failed");
' M. ]$ x2 l# d- R3 ZNOTE: ontio_assert(expr, errormsg):当 expr 为 false 时, 抛出异常并退出。
3 S7 L. S. w: S3 l, _如果红包资产是 ONT,由于 ONT 的不可分割性(最小为1个 ONT), 红包的金额要大于或等于红包的数量,保证每个红包最少有1个 ONT:2 _1 h! F$ T" N6 g7 f$ ~6 s3 A

7 X4 I2 H9 v. r$ J7 Dif (isONTToken(tokenAddr)){
9 j- Y# M) ~5 o: W: oontio_assert(amount >= packcount,"ont amount should greater than packcount");0 r; {9 [4 l$ C  f3 R1 C( |# E
}
' o% B) E) l  X5 E/ U对于每个红包的创建者,我们需要记录一下他发送红包的总数量:& V9 v/ `6 Y0 p4 x& j1 f/ |3 i1 e
) W: M( F% Q" f7 R% y7 b
key sentkey = make_key(sentPrefix,owner.tohexstring());
8 d4 J& Y' k. Z! E3 xasset sentcount = 0;
0 t' L. k+ C/ @% [storage_get(sentkey,sentcount);
: M# z+ h- K. l& o4 u3 rsentcount += 1;# z3 |4 M& j; q$ V6 Q5 t1 Q1 D
storage_put(sentkey,sentcount);* d! F2 n! L+ [4 T- k/ r% _, G
生成红包 hash, 这个 hash 就是之后标识这个红包的唯一 ID:
% J2 j4 Z1 v) P) r6 t: x
% e$ N, O- B+ M# P( WH256 hash ;
$ m9 T8 N1 M: l+ j# ?hash256(make_key(owner,sentcount),hash) ;
& T+ X" N0 ?/ i$ B% I! @! n' Okey rekey = make_key(rePrefix,hash256ToHexstring(hash));
" X( G+ U# {1 V( F# f2 U根据 token 资产的类型,将资产转入合约中,self_address()可以取得当前执行的合约地址, 我们根据用户输入的 token 类型,将指定数量的 token 转入合约:" C- S# v2 p+ R, H# M9 e
( _" t  n! P7 y9 y% @( P) P' b& \
address selfaddr = self_address();
! E% u6 p3 L: n* Xif (isONTToken(tokenAddr)){7 ?2 ~8 X+ \8 R$ y3 \
bool result = ont::transfer(owner,selfaddr ,amount);
- F3 A' [  [) ?! D! }; kontio_assert(result,"transfer native token failed!");% b, W$ q' J% }: ~
}else if (isONGToken(tokenAddr)){
4 D3 n  K4 x' K: jbool result = ong::transfer(owner,selfaddr ,amount);1 C+ u2 J! f9 U! @2 q( W+ a
ontio_assert(result,"transfer native token failed!");) k0 C5 w. p- H6 n* z/ D
}else{
# w1 m- W5 x; Y2 G" Zstd::vector params = pack(std::string("transfer"),owner,selfaddr,amount);/ c8 m1 H+ t# K4 Z
bool res;+ T- r4 W5 r8 I# U9 y
call_contract(tokenAddr,params, res );
/ P8 x" \5 g6 w2 j' l" vontio_assert(res,"transfer oep4 token failed!");: b# X' V& O7 Q( d: h+ h9 x
}" l4 s9 {4 M0 w7 L  j& x; J+ b% p
NOTE 1:对于 ONT 和 ONG 这两种原生资产, Ontology Wasm CDT 提供了ont::transfer API 进行转账操作;而 OEP-4类的资产,需要按照普通的跨合约调用方法来转账。$ _" I" D/ y& k3 @* ?
NOTE 2:和普通的钱包地址一样, 合约地址也可以接受任意类型的资产。但是合约地址是由合约编译后的二进制代码 hash 产生的,所以没有对应的私钥,也就无法随意操作合约中的资产,如果你没有在合约中设置对资产的操作,就意味着你将无法控制这部分资产。6 C% {+ p* V: S! O9 C% v% {3 I
将合约的信息保存在存储中:# l/ f% f* l- B2 V& k7 @

$ a# z( s; j7 C7 a4 ^0 jstruct EnvelopeStruct es ;, O& C1 ^$ ~4 g4 W. G
es.tokenAddress = tokenAddr;$ A# ]+ u+ ]. O. k
es.totalAmount = amount;+ P) e$ _1 L: }* i$ S
es.totalPackageCount = packcount;
# S5 b$ j7 S8 G7 M9 ~% p5 a' ces.remainAmount = amount;
% G  P! \# @; {& P% Des.remainPackageCount = packcount;
: e- Q/ ~( e  _1 A, X/ }2 O: Tes.records = {};5 R' h' u* {+ {5 Q7 R! i
storage_put(rekey, es);0 f1 H& b7 M+ _& H' n! U
发送创建红包的事件。对于智能合约的调用是一个异步的过程,合约会在执行成功后发送一个事件来通知客户端执行结果,这个事件的格式可以由合约的编写者来指定。) `5 A/ x! `4 J+ r+ L! F

5 }* ^7 }) T. i; e+ F4 qchar buffer [100];+ L) r9 I2 R$ B6 P" \
sprintf(buffer, "{\"states\":[\"%s\", \"%s\", \"%s\"]}","createEnvelope",owner.tohexstring().c_str(),hash256ToHexstring(hash).c_str());
3 i! U  w3 M! w6 z5 ynotify(buffer);
* y$ ~3 `% y' _% _return true;: q+ {2 A) H- b" x* \& p# |! Y
一个简单的红包就创建完成了, 下一步我们需要实现如何查询这个红包的信息.( M$ x/ N( r% p" n
2.3 查询红包% j: E! N$ D9 h3 ~5 T6 ^. `
查询红包的逻辑非常简单, 只需要将存储中的红包信息取出并格式化返回即可:
. ~3 U! |  x9 o7 Nstd::string queryEnvelope(std::string hash){& C' |% D+ l8 D  j4 l. Y# \
key rekey = make_key(rePrefix, hash);
% m+ e, [+ M( ~3 o6 T# E$ J8 O# k0 n, Ustruct EnvelopeStruct es;# \; Q+ G4 F* e. u% W
storage_get(rekey, es);+ S: A% `, I9 G$ e
return formatEnvelope(es);/ b* }, A: @: Z+ }
}: m: k0 v# ?9 e. F6 ?* s
NOTE:对于智能合约的只读操作(例如查询), 可以通过预执行(pre-exec)来读取结果。不同于普通的合约调用,预执行不需要钱包的签名,同时也就无需花费 ONG。最后,其他用户可以根据 hash(红包的 ID)来领取(抢)这个红包了。+ M+ H5 c) X& y
2.4 领取红包! I) K. c+ \+ y# |9 `5 l
我们已经把资产成功地转入到智能合约中了, 接下来就可以把这个红包的 ID 发送给你的朋友们让他们去抢红包了。4 A6 P$ x, N( o2 p. i! l
领取红包需要输入领取人的账户和红包的hash:
2 x5 w; [( d  q3 }; Z6 D4 W
' f4 y4 a% U: i8 ?6 I8 L4 K! m, ubool claimEnvelope(address account, std::string hash){( E1 L, S, k, t1 D
return true;
" v( f- O0 Q# u( x% w}
" p' `9 r7 ]0 x. a: z/ m- C+ b- n同样, 我们需要验证领取账户的签名, 不允许替其他人抢红包, 而且每个账户每个红包只能抢一次:
/ p! ?% q6 n9 r* c, x
$ v/ \: U4 G8 l0 H8 Jontio_assert(check_witness(account),"checkwitness failed");, ~+ D+ C) `; L" e
key claimkey = make_key(claimPrefix,hash,account);
7 E. ^  f4 D  l' p& hasset claimed = 0 ;
& n  a7 A+ v5 n0 W2 k5 I  Z0 L& ^storage_get(claimkey,claimed);' `) A8 G# G. _% ?
ontio_assert(claimed == 0,"you have claimed this Envelope!");
7 U) g! q9 j7 M按照 hash 从存储中取出红包的信息, 判断这个红包是否没有被抢完:2 Z6 T! m& r# I  [0 \( E& z

# T7 e/ a- v; F5 u( ~' w# I  Y- ^+ X* rkey rekey = make_key(rePrefix,hash);' }7 {$ u. p& `
struct EnvelopeStruct es;
. g! Y- b8 ]& ~7 Lstorage_get(rekey,es);" k( v  R: e- z4 k
ontio_assert(es.remainAmount > 0, "the Envelope has been claimed over!");$ F  |/ P' S7 k8 J- K& r
ontio_assert(es.remainPackageCount > 0, "the Envelope has been claimed over!");
% J, j; [/ W6 J新建一条领取的记录:
7 @6 k( Q! h# f: `3 \3 J# I# ~; d: x3 F
struct receiveRecord record ;$ P( W, A) i( m6 M5 n/ j
record.account = account;
) M! r2 Q. I0 U9 l8 Gasset claimAmount = 0;
4 z7 r3 Z& ?+ G; X; V* j+ t4 m5 T计算本次领取红包的资产数量。如果是最后一个红包, 数量为剩余的金额, 否则根据当前区块 hash 计算随机数,确定本次领取的数量, 并更新红包信息:
7 t! C  r- t. K: `. a- q+ A2 ^3 ?0 P: h
if (es.remainPackageCount == 1){
% ]& w& P7 k; W4 W. u2 q! u* l; ?# K6 ?claimAmount = es.remainAmount;
1 Q; u! w2 I8 o! [" a. H1 Y5 ?. ^4 r6 [record.amount = claimAmount;/ v) t3 a" C- O: I' W4 D
}else{
" y6 T1 F7 K1 z( n% p  `* c6 I8 n# HH256 random = current_blockhash() ;' [- x$ @: }1 X- `3 t+ c
char part[8];
8 _  T- h+ l. xmemcpy(part,&random,8);  o8 q+ N0 {  f- p% Y0 [: Y' H) H
uint64_t random_num = *(uint64_t*)part;
0 G/ N% V: S- [- a; Yuint32_t percent = random_num % 100 + 1;5 q9 S/ i7 R4 y9 E, A6 S9 Y4 S
claimAmount = es.remainAmount * percent / 100;
; O3 G% a; R2 [5 z+ H" @//ont case
3 Y( r2 _5 D6 ^# o# kif (claimAmount == 0){7 j, B& S0 ?7 C
claimAmount = 1;
. W" T; B2 X+ z8 W5 [! j}else if(isONTToken(es.tokenAddress)){3 _9 [- y$ m. C2 B# y  T  |
if ( (es.remainAmount - claimAmount) 根据计算结果, 将对应资产从合约中转到领取的账户:
! I5 s1 P- F1 b: C5 Y" z5 f4 C- _, m$ |7 O9 ]+ `5 r' ~2 n
address selfaddr = self_address();
! t7 p  S$ G* F, W) Z/ f% [% K& j, xif (isONTToken(es.tokenAddress)){+ ?; ]4 w" [9 l* t/ I% Y4 ?  J) M; F
bool result = ont::transfer(selfaddr,account ,claimAmount);
- B+ N9 V5 t7 H/ u# pontio_assert(result,"transfer ont token failed!");; ]5 ^7 w# R7 A2 t% J
} else if (isONGToken(es.tokenAddress)){
. W) A" T7 k* z0 g3 Obool result = ong::transfer(selfaddr,account ,claimAmount);: B+ T/ s2 ?3 d$ c! N# K2 ^
ontio_assert(result,"transfer ong token failed!");
8 u2 h9 e, o9 q} else{
+ B: W" e* M. Z3 Ustd::vector params = pack(std::string("transfer"),selfaddr,account,claimAmount);
: |0 ]; G! s5 \7 v: |bool res = false;8 l8 N; S8 u6 c4 Y+ I" r
call_contract(es.tokenAddress,params, res );
. E) b" B: {$ t+ p" P+ dontio_assert(res,"transfer oep4 token failed!");
. j* {; J9 G! K0 e8 q, Z}  p; b1 i% m6 P& m  m) q" V
记录领取的信息, 将更新后的红包信息写回存储并发送通知事件:
# s9 ?* |- F8 ?% N. w
& j- N5 b6 n& {9 Rstorage_put(claimkey,claimAmount);" n% p* ]) a  s3 F. E5 q  ?
storage_put(rekey,es);
8 w. U9 `! Y1 u) jchar buffer [100];
5 r: l2 R( O) Y! j7 m) astd::sprintf(buffer, "{\"states\":[\"%s\",\"%s\",\"%s\",\"%lld\"]}","claimEnvelope",hash.c_str(),account.tohexstring().c_str(),claimAmount);
( c. h3 f7 h* k: e, W6 rnotify(buffer);/ B4 P  W( L1 N% x8 x
return true;
; D' f: {5 y" D+ }如前面所说,这个合约只能通过 claimEnvelope 这个接口将资产转出合约。所以,合约中的资产是安全的,任何人都无法随意的取走里面的资产。
9 Y  ~9 p5 {( t5 i; i# k至此, 一个简单的红包合约逻辑完成, 完整的合约代码如下:https://github.com/JasonZhouPW/pubdocs/blob/master/redEnvelope.cpp# Z! W7 |, A7 {$ I7 D
2.5 合约测试6 B$ H5 c: R, h" H) V
合约测试可以有两种方法:) B1 q; z1 d2 E0 F2 G
使用 CLI
; s: p; w' }7 ]/ G$ A请参考:https://github.com/ontio/ontology-wasm-cdt-cpp/blob/master/How_To_Run_ontologywasm_node.md
% w; k- i/ D, @; t* Y/ `使用 Golang SDK# r1 {5 t9 t; |4 P: N! b1 r
请参考:https://github.com/ontio/ontology-wasm-cdt-cpp/blob/master/example/other/main.go
( p# C6 T% ?) D* V: ?三、总结
4 t' g/ X: Z' C% V% e6 J, B; t) }本示例只是为了展示如何编写一个完整的 Wasm 智能合约, 如何通过调用 API 和底层的区块链进行交互。如果要作为正式的产品, 还需要解决红包的隐私问题: 所有人都可以通过监控合约的事件来取得红包的 hash, 意味着每个人都可以抢这个红包。一种比较简单的解决方法,就是在创建红包时指定哪些账户能够领取。如果有兴趣, 您也可以尝试修改测试一下。
) }  m0 @' q' ~7 _9 T8 ?我们欢迎更多的 Wasm 技术爱好者加入本体开发社区,共同打造技术生态。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

放弃六月们 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    8