Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

放弃六月们
182 0 0
Ontology Wasm 自从上线测试网以来便受到了社区开发人员的极大关注。 Wasm 的上线将使得业务逻辑复杂的 dApp 合约上链成本降低,极大丰富 dApp 生态。在进行 Wasm 合约开发时,开发者不仅可以使用 Rust,还可以使用 C++ 作为合约开发语言。本期我们将通过两个简单的示例来示范如何使用 C++ 进行Wasm 合约开发。
1 _% X" F; u" ~4 G/ Z- J一、Hello World. z0 \+ ~" H0 Q# X
按照惯例,我们还是从一个 Hello world 开始% N/ F- P0 ]0 k' v5 P9 S0 B; \6 @
#include#include
" n) ~6 t* e8 H' t6 g- x2 ~. X0 yusing namespace ontio;class hello:public contract {
7 l  `7 a' s# E: i! k) Rpublic:9 d# F' Y# T; N' B& u  x6 g2 E9 k
using contract::contract:
) {3 q/ m* ^6 H6 }( n' r* Avoid sayHello(){
. j* J: W: \9 N3 cprintf("hello world!");
: O: \3 l% d: h}+ u7 E$ j+ _) X7 T$ J/ R9 E
};) y) p" Z/ A( R; L# m1 ]( X
ONTIO_DISPATCH(hello, (sayHello));
: ^  i/ e1 z! ?- `% ?1.1 合约入口3 e; X' Y$ ^# t* A4 j
Ontology Wasm CDT 编译器已经对入口和参数解析进行了封装,所以开发者不需要重新定义入口方法。接下来是定义合约的对外接口,这是智能合约对外提供服务的方法。
# E6 F- [* f( EONTIO_DISPATCH(hello, (sayHello));& H4 y  n' j; x
在上面的例子中, 我们暂时只支持 sayHello 这个方法:) S. p7 n- [/ k) ~% D" d
printf("hello world!");; H4 d. F( q$ Y  D  \
这个“Hello world!”会在节点的日志中以调试信息打印出来。在实际的应用中, printf 只能用作调试的目的, 一个实际的智能合约,需要实现更多更复杂的功能。
3 a* k2 q. O2 C# |7 p% m1.2 智能合约 API$ A2 X; r% j5 K- x9 h# ~  l
Ontology Wasm 提供如下 API 与区块链的底层进行交互:0 ]: U  l) Q' j' v- {) w+ g1 r" @  J$ v
% T5 @% R  l: _
二、红包合约
. q- M& B: J4 ^) ^: y3 l! Z下面我们通过一个更加复杂的例子来演示如何通过这些 API 来开发一个完整的 Wasm 智能合约。4 ?3 Y4 l% F5 F9 C% R4 n( m9 z
很多情况下我们都会通过各种 App,如微信等聊天工具发红包。我们可以给朋友发送红包,也可以抢其他人发送的红包,收到的钱会记入到个人微信账户中。4 R7 o! R; `- q0 s
类似于微信的流程,我们将尝试创建一个智能合约。用户使用该合约,可以发送 ONT,ONG 或者是标准的 OEP-4的 Token 资产红包给他的朋友们,而朋友们抢到的红包可以直接转入到他们的钱包账户中。
" b5 Y! N; C4 Y+ `+ Z/ P) c3 [2.1 创建合约
- P9 e9 @( v# R首先,我们需要新建合约的源文件,暂且命名为 redEnvelope.cpp。这个合约我们需要三个接口:9 Y/ h- p" n( z, Z: T3 ^- }7 Z
createRedEnvelope: 创建红包- s( Q8 S/ i+ [( u
queryEnvelope: 查询红包信息
( {1 |/ q* d* y; QclaimEnvelope: 抢红包
, \: s, k$ o$ ?: `, T3 o7 ^, R#include
) V! u6 w& d/ G9 v" u% k) D6 Yusing namespace ontio;
% E1 {3 e6 N" ^2 lclass redEnvelope: public contract{( @2 s/ ~4 @8 Z+ A
}
9 ~# k& }4 `& o# z' \7 f/ ~$ VONTIO_DISPATCH(redEnvelope, (createRedEnvelope)(queryEnvelope)(claimEnvelope));: N: \0 U: Y, M
我们需要在存储中保存一些关键的数据。在智能合约中, 数据以 KV 的形式保存在该合约的上下文空间中,这些数据的 KEY 需要设置前缀以便于后面的查询。下面定义了三个不同的前缀供使用:
) G3 B  j+ E# \: sstd::string rePrefix = "RE_PREFIX_";
2 c. q; |* j. h" estd::string sentPrefix = "SENT_COUNT_";
2 |, I9 W! l4 L! R- T* q! s# Estd::string claimPrefix = "CLAIM_PREFIX_"
6 i5 K2 E/ }0 ?% j/ l因为我们的合约支持 ONT 和 ONG 这两种 Ontology 的原生资产, 我们可以预先定义好这两种资产的合约地址。不同于标准的智能合约, Ontology 原生合约(native contract)的合约地址是固定的,而不是根据合约代码的 hash 计算而来的。
9 i( b& H, t2 Y) jaddress ONTAddress = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1};
! p5 J  k3 {/ H5 y) g, i. ]address ONGAddress = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2};8 l( P  w6 F* Z  ^( j
我们需要在合约中保存红包的信息, 如红包的资产信息(token 的合约地址, 红包的总金额, 红包的个数等等)
, a' R% X8 v; Z1 B0 `4 o! E" E# jstruct receiveRecord{1 F. u6 c* L8 A0 T# d, ?
address account; //用户地址
9 ]; Z/ H( {* d" ?. p' b. Kasset amount; //抢到的金额
0 }' t4 F; ]* p, O# B& m( _ONTLIB_SERIALIZE(receiveRecord,(account)(amount))- D4 M' l0 K+ x% A6 R
};  Q: ]' V: N1 h
struct EnvelopeStruct{3 b0 V& A- e9 B5 |" R' @
address tokenAddress; //资产token的地址
) }- E& }  Q* ?7 A% ~+ [asset totalAmount; //红包总金额7 J: |  p  e/ R1 k+ u$ [- D
asset totalPackageCount; //红包总数; S' h: o% V8 k7 ?3 Z. H! r
asset remainAmount; //当前剩余的金额  k" H: M5 v: s$ d
asset remainPackageCount; //当前剩余的红包数( B( R8 C( l' U
std::vector records; //已经抢完的记录, F7 X. T8 ~' Y# P8 t( k9 ?% i0 l
ONTLIB_SERIALIZE( EnvelopeStruct, (tokenAddress)(totalAmount)(totalPackageCount)(remainAmount)(remainPackageCount)(records) )5 G" B; H" P) ~) ?' K. N
};
& f- O% u  C" _# H: X其中,
& l* R/ n  o; K8 ]% g' ]6 WONTLIB_SERIALIZE(receiveRecord,(account)(amount))9 J8 l6 Q  o  S* m* r; s9 g
是由 Ontology Wasm CDT 定义的宏操作,用于在将 struct 存储前进行序列化的操作。  J" J) R1 W* ~, v" q1 A+ ~8 O6 J
2.2 创建红包+ C, U2 _& s+ ]$ ?) \( x" H
准备工作差不多了,下面我们开始开发具体的接口逻辑。! T- C# D/ m/ a, \
创建红包需要指定创建者地址, 红包数量, 红包金额和资产的合约地址:; y, ~  C" t5 A" V
6 p6 q1 i0 z+ ~2 Q
bool createRedEnvelope(address owner,asset packcount, asset amount,address tokenAddr ){& t/ S  k; G  w( v0 D' ^5 e. O* d
return true;- d4 w" p: @: k1 H& S% s
}( `0 ]2 g5 @; G8 W& I7 a4 X
检查是否有创建者的签名, 否则交易回滚退出:
' t4 U8 g) Z3 v: w0 P$ V7 @$ F+ q7 e* c$ N. n/ w
ontio_assert(check_witness(owner),"checkwitness failed");
: S+ F2 {8 m9 ^3 B+ h" ZNOTE: ontio_assert(expr, errormsg):当 expr 为 false 时, 抛出异常并退出。
' F+ Q9 V# G& o! B如果红包资产是 ONT,由于 ONT 的不可分割性(最小为1个 ONT), 红包的金额要大于或等于红包的数量,保证每个红包最少有1个 ONT:2 N1 u4 t  f; S% A' ?4 `* A# r/ o9 T9 W

+ w* c( Z+ K) w% cif (isONTToken(tokenAddr)){
4 s( i! a" v8 }1 G0 xontio_assert(amount >= packcount,"ont amount should greater than packcount");
$ D1 x; H! q8 `& b& c# \}: J4 }- y- E" r% v5 A' Z5 z
对于每个红包的创建者,我们需要记录一下他发送红包的总数量:
0 @( x3 [/ q/ `% A' Q9 N! n
8 \) }. v4 }# b* Bkey sentkey = make_key(sentPrefix,owner.tohexstring());9 R, A, U6 k: E/ J. F) ~7 H6 S
asset sentcount = 0;
6 H, S# v8 p$ `8 z# qstorage_get(sentkey,sentcount);
: C3 o- k+ ?& ^7 o" }' {7 Bsentcount += 1;
% D  ]# ~( ^$ U7 D4 W) Vstorage_put(sentkey,sentcount);
7 {% x" F2 M+ L! R. O生成红包 hash, 这个 hash 就是之后标识这个红包的唯一 ID:( x8 M* \1 v, n9 c8 V

& b$ n7 |1 _5 g5 R- v1 WH256 hash ;
" {, b% }0 _0 N/ m! r( {4 l2 ^$ q  @% Lhash256(make_key(owner,sentcount),hash) ;
* r. p4 z' Z+ p1 q; [- Hkey rekey = make_key(rePrefix,hash256ToHexstring(hash));' v1 s# x6 h* Q( N
根据 token 资产的类型,将资产转入合约中,self_address()可以取得当前执行的合约地址, 我们根据用户输入的 token 类型,将指定数量的 token 转入合约:
+ J1 g' z1 F$ u3 `7 T: S) H. P5 v7 \5 v+ g' K( n' f+ Q, E
address selfaddr = self_address();( g) u9 w- _8 x) P( O9 w. t
if (isONTToken(tokenAddr)){
: A) B: j$ ]6 e: T6 Tbool result = ont::transfer(owner,selfaddr ,amount);! \6 M: ?5 v4 ~
ontio_assert(result,"transfer native token failed!");- L( {7 Q5 a5 E, P% v$ c# U
}else if (isONGToken(tokenAddr)){
- ^1 L# A5 ]5 m- D8 y! L6 ybool result = ong::transfer(owner,selfaddr ,amount);
: v9 G1 f; h+ p( l% n( ]ontio_assert(result,"transfer native token failed!");, z1 F: q0 v* G7 b$ c8 }$ q' C
}else{
5 _1 ^) r/ F9 t$ p$ mstd::vector params = pack(std::string("transfer"),owner,selfaddr,amount);9 m* _5 T6 N+ ~; k! m2 J8 k
bool res;
2 j: z  B$ M" Dcall_contract(tokenAddr,params, res );
! _' G% E8 Q7 Xontio_assert(res,"transfer oep4 token failed!");
' v. G# D% ?, @( O& B( o}
! `% e0 \- ?7 u2 LNOTE 1:对于 ONT 和 ONG 这两种原生资产, Ontology Wasm CDT 提供了ont::transfer API 进行转账操作;而 OEP-4类的资产,需要按照普通的跨合约调用方法来转账。3 }2 k  W0 ~2 f$ v( B5 l) C
NOTE 2:和普通的钱包地址一样, 合约地址也可以接受任意类型的资产。但是合约地址是由合约编译后的二进制代码 hash 产生的,所以没有对应的私钥,也就无法随意操作合约中的资产,如果你没有在合约中设置对资产的操作,就意味着你将无法控制这部分资产。
' E4 G4 K1 T6 n* C0 T将合约的信息保存在存储中:
8 s- v: U+ Q" a  `
$ z5 a) J$ ^+ x. _+ [; rstruct EnvelopeStruct es ;. \5 O+ ^; J6 P3 F& G1 V
es.tokenAddress = tokenAddr;
5 p9 v5 _  Q8 E3 tes.totalAmount = amount;! f% `1 H: x  [6 @
es.totalPackageCount = packcount;
' q; a8 I" R+ g' \* a- Tes.remainAmount = amount;& s: _3 @- \' r0 V3 G7 _8 y. ^
es.remainPackageCount = packcount;
5 c1 L5 i) o" q% X) b: d% \( n0 Oes.records = {};, Y' `0 }5 `8 Y8 h$ m* d
storage_put(rekey, es);
( b) A8 R3 F) s. J发送创建红包的事件。对于智能合约的调用是一个异步的过程,合约会在执行成功后发送一个事件来通知客户端执行结果,这个事件的格式可以由合约的编写者来指定。
2 k  u8 g2 F# O& M& q! |# @' `+ l3 d! y% t! R& c; y  u) I
char buffer [100];% O9 Y5 @1 L9 q  @5 ^: L
sprintf(buffer, "{\"states\":[\"%s\", \"%s\", \"%s\"]}","createEnvelope",owner.tohexstring().c_str(),hash256ToHexstring(hash).c_str());  V. @3 Z5 v& M, B
notify(buffer);2 c* L# f( s; X4 `
return true;
1 O4 x, k" [* h一个简单的红包就创建完成了, 下一步我们需要实现如何查询这个红包的信息.
% {3 u+ n! V- k# F  {: z2.3 查询红包5 n0 {9 }# M& j9 l, W  a( }1 ]
查询红包的逻辑非常简单, 只需要将存储中的红包信息取出并格式化返回即可:
6 ]  Z4 ]/ A" l1 n& p' ~3 E; istd::string queryEnvelope(std::string hash){; ~' Z% }, L8 e: n, }
key rekey = make_key(rePrefix, hash);  e6 k" n  `. A& t$ a- f
struct EnvelopeStruct es;
. f+ `" A! I8 Ystorage_get(rekey, es);6 g8 H. U% C1 R* m% @
return formatEnvelope(es);
: R' W) c. U& ^  x; C}
2 e# j. l+ B0 y7 h) C# {: xNOTE:对于智能合约的只读操作(例如查询), 可以通过预执行(pre-exec)来读取结果。不同于普通的合约调用,预执行不需要钱包的签名,同时也就无需花费 ONG。最后,其他用户可以根据 hash(红包的 ID)来领取(抢)这个红包了。% s0 d; P- Y8 ?6 T) R
2.4 领取红包6 _, L* m, L6 {( ^6 e
我们已经把资产成功地转入到智能合约中了, 接下来就可以把这个红包的 ID 发送给你的朋友们让他们去抢红包了。4 K! B" B9 T& i, W: |1 ^
领取红包需要输入领取人的账户和红包的hash:$ L0 `& }/ `- v
/ q% L# ?1 z- z0 `' P* m( h
bool claimEnvelope(address account, std::string hash){
3 ?" S4 T( L9 Z- ~  N: P& N/ O0 ireturn true;) E6 L) V1 {- Q- ]" n) p
}0 v7 r  B  ^, c# \3 @# z0 S! I" e  d
同样, 我们需要验证领取账户的签名, 不允许替其他人抢红包, 而且每个账户每个红包只能抢一次:- ]6 b* `: N% j, Y9 T+ E9 N
5 A8 M# A5 |1 h' i
ontio_assert(check_witness(account),"checkwitness failed");+ C) L, c3 ?9 t/ ^
key claimkey = make_key(claimPrefix,hash,account);6 O( l  D2 U* d$ n
asset claimed = 0 ;( T6 v. c, r# \% p( |- ]
storage_get(claimkey,claimed);  _: I0 K& Q4 w" Y4 F4 c" A( E
ontio_assert(claimed == 0,"you have claimed this Envelope!");8 O1 m* U' W& @9 O: J8 k
按照 hash 从存储中取出红包的信息, 判断这个红包是否没有被抢完:; x' u1 E  q, K: [3 ?
$ G- @$ k, ^. N& Y
key rekey = make_key(rePrefix,hash);
3 z' O: n4 |" o0 {0 Z5 t5 a' Estruct EnvelopeStruct es;
5 k+ R3 @8 g+ x4 \; K7 O7 {storage_get(rekey,es);
& }/ o$ n5 O+ E; z/ dontio_assert(es.remainAmount > 0, "the Envelope has been claimed over!");
. C( ?: ?0 G+ M2 c( v. @, h* Kontio_assert(es.remainPackageCount > 0, "the Envelope has been claimed over!");& A* |, {# i: o' y
新建一条领取的记录:
" d9 Y. D% C, g
- M# _  x/ S+ q) cstruct receiveRecord record ;. J1 Y% Q- n# p0 J# \
record.account = account;; R4 T; G3 [; O7 l; g
asset claimAmount = 0;) F7 B! L: t) w' Y$ l* y# \
计算本次领取红包的资产数量。如果是最后一个红包, 数量为剩余的金额, 否则根据当前区块 hash 计算随机数,确定本次领取的数量, 并更新红包信息:
/ G' m3 `* ?* r) I8 m% @" {8 S8 v8 {: |. d8 q& N
if (es.remainPackageCount == 1){, w) Y3 y8 x: E# I9 _( j# u
claimAmount = es.remainAmount;, t/ b% ]4 w: ?% z
record.amount = claimAmount;. T( ?# S1 d8 u2 [4 k9 k3 {
}else{+ Z3 O# {8 y& e# R  h$ K) V
H256 random = current_blockhash() ;
0 u& X) K0 R) `" Kchar part[8];! z  Z3 E( a0 _1 M8 l. a1 v
memcpy(part,&random,8);* W3 q" p. n* ]
uint64_t random_num = *(uint64_t*)part;
. U/ Y: z% v% E6 J8 _uint32_t percent = random_num % 100 + 1;- z" V! b4 {8 g
claimAmount = es.remainAmount * percent / 100;8 L7 H2 W7 a+ {, {9 ]
//ont case1 [* S+ [. m+ O# h- F9 |- {8 N
if (claimAmount == 0){$ [- M$ R7 g- q- e( q+ Q" a1 t
claimAmount = 1;
! c- _6 |1 ]' V0 V. z8 F}else if(isONTToken(es.tokenAddress)){
% I4 G4 S) H& N% u2 Lif ( (es.remainAmount - claimAmount) 根据计算结果, 将对应资产从合约中转到领取的账户:
! c% W" A: k3 r1 W3 X3 ]& _. j! |2 |
# u7 D( F2 r9 f) |% W) [# Xaddress selfaddr = self_address();! `7 b  w  I) Z8 g
if (isONTToken(es.tokenAddress)){
. W+ y- h. O! B: D; ybool result = ont::transfer(selfaddr,account ,claimAmount);
% G) {; G& G7 f$ Aontio_assert(result,"transfer ont token failed!");
' ?+ L# C' e+ p& R" v} else if (isONGToken(es.tokenAddress)){# ?8 H& F. p. j6 V0 m4 P
bool result = ong::transfer(selfaddr,account ,claimAmount);
1 v$ n+ Z3 D. [0 X* y6 aontio_assert(result,"transfer ong token failed!");
5 ^5 u* |) |% p. j8 |1 t+ a4 ~9 S6 C} else{
: u4 m6 K! C4 O: }% H* Wstd::vector params = pack(std::string("transfer"),selfaddr,account,claimAmount);4 }3 K3 a6 `, q6 l
bool res = false;6 z5 A& m' X8 U4 L: p
call_contract(es.tokenAddress,params, res );- K3 s9 j% f) t8 Y, _
ontio_assert(res,"transfer oep4 token failed!");$ t' K8 G- d  f) S
}8 e* ~" P" W# N* }! f
记录领取的信息, 将更新后的红包信息写回存储并发送通知事件:
3 j* H5 ?3 z$ z# h2 [$ X8 K1 Z8 W! o
( h! E/ V* ~8 X3 v; {" }4 Tstorage_put(claimkey,claimAmount);
& M0 Q# w6 w. x& d0 U' R" V3 m' A: Jstorage_put(rekey,es);. u# ]. e% W4 \4 U9 l- i
char buffer [100];
) \2 Q' Q3 L( W& k' b$ |9 Fstd::sprintf(buffer, "{\"states\":[\"%s\",\"%s\",\"%s\",\"%lld\"]}","claimEnvelope",hash.c_str(),account.tohexstring().c_str(),claimAmount);
: k4 F% B- ]7 i  N- b5 qnotify(buffer);
" c/ e: ]% L7 d! S* Greturn true;
, Q" m2 n; l7 x4 f- f如前面所说,这个合约只能通过 claimEnvelope 这个接口将资产转出合约。所以,合约中的资产是安全的,任何人都无法随意的取走里面的资产。! J$ I- D8 l6 R( A( B3 a; Q, I
至此, 一个简单的红包合约逻辑完成, 完整的合约代码如下:https://github.com/JasonZhouPW/pubdocs/blob/master/redEnvelope.cpp# n8 ~5 d5 A0 h! z
2.5 合约测试" O8 R* l7 H6 R; L+ V9 e; @" K0 V
合约测试可以有两种方法:
) _$ `+ j  L& ~2 A使用 CLI0 R/ s7 R8 R9 g3 |6 p' _
请参考:https://github.com/ontio/ontology-wasm-cdt-cpp/blob/master/How_To_Run_ontologywasm_node.md
  _8 S9 c. @4 K. c- @" i$ M. F& h使用 Golang SDK
) |) G3 ?0 x* m, ~请参考:https://github.com/ontio/ontology-wasm-cdt-cpp/blob/master/example/other/main.go) q) n  z2 n$ s7 V, S
三、总结
/ P! o. A2 E! {. r: R& m本示例只是为了展示如何编写一个完整的 Wasm 智能合约, 如何通过调用 API 和底层的区块链进行交互。如果要作为正式的产品, 还需要解决红包的隐私问题: 所有人都可以通过监控合约的事件来取得红包的 hash, 意味着每个人都可以抢这个红包。一种比较简单的解决方法,就是在创建红包时指定哪些账户能够领取。如果有兴趣, 您也可以尝试修改测试一下。5 J% {: x  b  ?5 e
我们欢迎更多的 Wasm 技术爱好者加入本体开发社区,共同打造技术生态。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

放弃六月们 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    8