Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

放弃六月们
147 0 0
Ontology Wasm 自从上线测试网以来便受到了社区开发人员的极大关注。 Wasm 的上线将使得业务逻辑复杂的 dApp 合约上链成本降低,极大丰富 dApp 生态。在进行 Wasm 合约开发时,开发者不仅可以使用 Rust,还可以使用 C++ 作为合约开发语言。本期我们将通过两个简单的示例来示范如何使用 C++ 进行Wasm 合约开发。* K1 P' z/ s+ e: s
一、Hello World
5 H  S5 I' M3 P! A2 i1 j; H按照惯例,我们还是从一个 Hello world 开始8 c' {) ?2 f* E7 H. f
#include#include" P" W, K+ B9 ^8 x+ j6 X
using namespace ontio;class hello:public contract {. S$ B8 {) l( ~+ V- W2 Y2 p  h) n
public:
/ z0 t. n, `  }0 i! s. a, q0 ousing contract::contract:4 R! n) c0 U2 D9 S' ^* ]5 q% P
void sayHello(){
5 h) V4 {. v4 {. B/ vprintf("hello world!");# b6 G$ }' C2 L1 P
}) Q1 E( a  J, h9 m6 |; p
};5 e3 H/ J; O) b% e" K
ONTIO_DISPATCH(hello, (sayHello));+ E' v) U0 z. l: c! v' ~
1.1 合约入口* Q4 ?1 l& M0 T6 S
Ontology Wasm CDT 编译器已经对入口和参数解析进行了封装,所以开发者不需要重新定义入口方法。接下来是定义合约的对外接口,这是智能合约对外提供服务的方法。5 ]* J% f* O8 K. p; T6 w3 q$ }
ONTIO_DISPATCH(hello, (sayHello));
% H7 g9 Q, X* T) Z' T' W( O; \! z在上面的例子中, 我们暂时只支持 sayHello 这个方法:
9 k9 w" `; e) g/ eprintf("hello world!");: o# p2 y& ?! y  f2 ~! `; l
这个“Hello world!”会在节点的日志中以调试信息打印出来。在实际的应用中, printf 只能用作调试的目的, 一个实际的智能合约,需要实现更多更复杂的功能。
& R1 H4 l3 z/ H1.2 智能合约 API( T# ?9 ~2 ~. R
Ontology Wasm 提供如下 API 与区块链的底层进行交互:# w+ u! e' i! P7 p2 \# h: N/ P! p; N

; o7 W! f* k# H: M' l- J二、红包合约! K2 j  ~. k& |  q
下面我们通过一个更加复杂的例子来演示如何通过这些 API 来开发一个完整的 Wasm 智能合约。. h8 C  n" ?$ Y- a: l$ w
很多情况下我们都会通过各种 App,如微信等聊天工具发红包。我们可以给朋友发送红包,也可以抢其他人发送的红包,收到的钱会记入到个人微信账户中。/ P3 x! t. f! m1 j
类似于微信的流程,我们将尝试创建一个智能合约。用户使用该合约,可以发送 ONT,ONG 或者是标准的 OEP-4的 Token 资产红包给他的朋友们,而朋友们抢到的红包可以直接转入到他们的钱包账户中。
2 i* B% G" l  _3 s. w2.1 创建合约
9 _# b4 D) L" o  B. C首先,我们需要新建合约的源文件,暂且命名为 redEnvelope.cpp。这个合约我们需要三个接口:
0 d1 N1 d; ?) `2 l5 B! ]createRedEnvelope: 创建红包( Q- @) V* K5 @1 L1 q
queryEnvelope: 查询红包信息
  I0 u0 v: N: N; P5 X  `" |. N0 p) bclaimEnvelope: 抢红包9 d- g' D" k" B; l  a
#include
/ I# d5 K0 ?: _# F' P3 a* Yusing namespace ontio;
; ^) f! N$ q9 K8 \class redEnvelope: public contract{
5 K' `) j' c  k0 [# Y6 s2 R3 n}1 U9 d! O1 L- ?9 Y6 m
ONTIO_DISPATCH(redEnvelope, (createRedEnvelope)(queryEnvelope)(claimEnvelope));( }: a+ _% t2 u! W+ m  B/ a
我们需要在存储中保存一些关键的数据。在智能合约中, 数据以 KV 的形式保存在该合约的上下文空间中,这些数据的 KEY 需要设置前缀以便于后面的查询。下面定义了三个不同的前缀供使用:: N6 X4 ?! Y9 R  u
std::string rePrefix = "RE_PREFIX_";
  C4 `* V, x1 }- z# Y, Ostd::string sentPrefix = "SENT_COUNT_";$ e2 o, J6 @. t0 u
std::string claimPrefix = "CLAIM_PREFIX_"
. W  O5 k* \& r7 U6 Z9 x: {因为我们的合约支持 ONT 和 ONG 这两种 Ontology 的原生资产, 我们可以预先定义好这两种资产的合约地址。不同于标准的智能合约, Ontology 原生合约(native contract)的合约地址是固定的,而不是根据合约代码的 hash 计算而来的。+ `' B: w4 e" V
address ONTAddress = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1};9 Z4 ^0 t, ^: i0 `( I& k
address ONGAddress = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2};; @& n* M  Q% m
我们需要在合约中保存红包的信息, 如红包的资产信息(token 的合约地址, 红包的总金额, 红包的个数等等)  C: E/ m9 D6 h: ^
struct receiveRecord{
& b3 E* p4 g/ c0 C' e$ T$ G- kaddress account; //用户地址, t$ K7 P+ n+ l
asset amount; //抢到的金额
: `4 f5 w& r' `; ^+ f, y2 K( o7 `ONTLIB_SERIALIZE(receiveRecord,(account)(amount))
0 e2 r- G& q. y5 y};- Y/ v/ ^3 G1 H8 C  S( P/ O) v2 ~
struct EnvelopeStruct{+ ?' Y8 `% r( p' [: @
address tokenAddress; //资产token的地址
6 }  `( U4 h; F: U# `asset totalAmount; //红包总金额$ ?( W; {% _7 K3 X7 @1 A& q
asset totalPackageCount; //红包总数
% h  m8 A8 R# R. g: Fasset remainAmount; //当前剩余的金额6 w( O: G* Z6 I' g9 f8 [) t
asset remainPackageCount; //当前剩余的红包数( S; ~4 O; p& C) L4 R/ [) I) s" H
std::vector records; //已经抢完的记录# r( ]% a+ n/ ^7 }
ONTLIB_SERIALIZE( EnvelopeStruct, (tokenAddress)(totalAmount)(totalPackageCount)(remainAmount)(remainPackageCount)(records) )' y1 k( D& {% s! u
};3 r+ B9 d, t2 A, {
其中,
9 }# r6 x2 m) lONTLIB_SERIALIZE(receiveRecord,(account)(amount))9 G' q3 A( u. z: L1 u
是由 Ontology Wasm CDT 定义的宏操作,用于在将 struct 存储前进行序列化的操作。3 `) c( f4 W4 ^- b1 ?
2.2 创建红包
1 k4 T$ m- k" d* G准备工作差不多了,下面我们开始开发具体的接口逻辑。
+ v% q% N5 W) j5 q6 b, E6 O3 C创建红包需要指定创建者地址, 红包数量, 红包金额和资产的合约地址:4 G9 T& P% |! h: k* _( \

+ ]8 f2 I6 j) z( V  {# Dbool createRedEnvelope(address owner,asset packcount, asset amount,address tokenAddr ){
" |' s+ L4 e' d$ j) [6 Qreturn true;
  l: Q- E/ G% A( i7 w- n}4 d# d4 n: v2 S' ^
检查是否有创建者的签名, 否则交易回滚退出:
: i0 y6 b+ Z& `- Q6 e/ m* F( X5 V* \* A1 f  r
ontio_assert(check_witness(owner),"checkwitness failed");* H3 V2 K  k/ ~8 v
NOTE: ontio_assert(expr, errormsg):当 expr 为 false 时, 抛出异常并退出。
) k' S3 B  t: ~) p如果红包资产是 ONT,由于 ONT 的不可分割性(最小为1个 ONT), 红包的金额要大于或等于红包的数量,保证每个红包最少有1个 ONT:: y; @& |8 k0 K/ ?4 d: l. _
' a1 q# u- I. [7 ]8 s6 C* P
if (isONTToken(tokenAddr)){, A$ @4 f1 E$ f$ j5 M+ z7 j
ontio_assert(amount >= packcount,"ont amount should greater than packcount");% B2 G. C, M0 P7 t
}
3 h9 u1 y" k7 s, S; q6 \对于每个红包的创建者,我们需要记录一下他发送红包的总数量:* U- y0 k: R9 ~: s0 }1 u% z6 N

( u0 Z5 b- j  l% F, qkey sentkey = make_key(sentPrefix,owner.tohexstring());
+ o& e/ \/ e' G# a2 m+ Qasset sentcount = 0;
4 b' C1 ]2 i1 k0 t" Rstorage_get(sentkey,sentcount);0 s" ?# I6 e+ i7 a+ l
sentcount += 1;
! ~2 T/ E3 U$ i4 d0 Dstorage_put(sentkey,sentcount);& T# h% W$ p+ l
生成红包 hash, 这个 hash 就是之后标识这个红包的唯一 ID:$ f( d7 v3 |& o8 K( P& g9 a" {0 Z( h; v

. a# y3 B* j/ g- \; s( qH256 hash ;
$ J8 j7 k) s  ehash256(make_key(owner,sentcount),hash) ;1 @4 Q0 u, u3 T+ I, |' p2 A
key rekey = make_key(rePrefix,hash256ToHexstring(hash));
  X( N7 n! r8 E, s: F8 L! U! G根据 token 资产的类型,将资产转入合约中,self_address()可以取得当前执行的合约地址, 我们根据用户输入的 token 类型,将指定数量的 token 转入合约:7 Y7 m: f& P9 e1 `/ w% g* G" ]7 }

5 ~8 h- i) v3 v9 ^! p$ Kaddress selfaddr = self_address();
' C2 u2 x8 x: s: Zif (isONTToken(tokenAddr)){
/ V8 x& }0 I( w6 o1 S8 Y/ k+ Ubool result = ont::transfer(owner,selfaddr ,amount);
# M4 [2 `9 A/ e- wontio_assert(result,"transfer native token failed!");
2 |* e1 @' _5 T& A5 l}else if (isONGToken(tokenAddr)){7 _: F- w, s+ V/ f) K: R& W# x
bool result = ong::transfer(owner,selfaddr ,amount);6 f' v  ~* {/ V! G2 b) w
ontio_assert(result,"transfer native token failed!");
3 u" i* m( p6 M: Q8 [# C  m+ c}else{
7 m9 ^" j) S1 D+ w0 d8 g  ]std::vector params = pack(std::string("transfer"),owner,selfaddr,amount);
1 z% v' }! k2 T; C$ D* ]% O: j& Fbool res;
. }, Q* O$ w2 C! Ucall_contract(tokenAddr,params, res );" H" `, d# z4 e
ontio_assert(res,"transfer oep4 token failed!");
) f( i2 u& {4 o. a3 X}
' H4 f  u* F8 Y( |2 r) c* `NOTE 1:对于 ONT 和 ONG 这两种原生资产, Ontology Wasm CDT 提供了ont::transfer API 进行转账操作;而 OEP-4类的资产,需要按照普通的跨合约调用方法来转账。
" ]3 t+ R) p% k# UNOTE 2:和普通的钱包地址一样, 合约地址也可以接受任意类型的资产。但是合约地址是由合约编译后的二进制代码 hash 产生的,所以没有对应的私钥,也就无法随意操作合约中的资产,如果你没有在合约中设置对资产的操作,就意味着你将无法控制这部分资产。) J9 b$ S. M" H) P, c6 ]
将合约的信息保存在存储中:
8 E0 K5 R2 S0 a! x
" O/ U* [* B% u8 lstruct EnvelopeStruct es ;. s& r/ O/ `$ Q. l
es.tokenAddress = tokenAddr;
6 M  f: c- N" @es.totalAmount = amount;$ }2 u' p* r( N7 {5 ]
es.totalPackageCount = packcount;3 h" M' n* N3 z3 B. B; r/ i' n
es.remainAmount = amount;, A- c" s# l/ K3 w
es.remainPackageCount = packcount;) ], P9 {3 K1 E: \( x
es.records = {};
: y0 ^2 H, C( T6 Q- }storage_put(rekey, es);$ _$ `& p3 v" f
发送创建红包的事件。对于智能合约的调用是一个异步的过程,合约会在执行成功后发送一个事件来通知客户端执行结果,这个事件的格式可以由合约的编写者来指定。
8 Y/ r: C" X2 G- X0 m/ U% ~
. E3 R" H; I) b% \* bchar buffer [100];
9 A* a' {1 h: j0 {, G& ]! vsprintf(buffer, "{\"states\":[\"%s\", \"%s\", \"%s\"]}","createEnvelope",owner.tohexstring().c_str(),hash256ToHexstring(hash).c_str());1 w: i& B$ [5 ]6 [
notify(buffer);
4 V( ^+ |' ?% Z2 K0 c' d" Sreturn true;  `& _' |) f& b% T1 I
一个简单的红包就创建完成了, 下一步我们需要实现如何查询这个红包的信息.9 x6 k) L: S/ u! t
2.3 查询红包
) P) P# S" [: X查询红包的逻辑非常简单, 只需要将存储中的红包信息取出并格式化返回即可:
1 U, y' ]0 i& e' H1 Kstd::string queryEnvelope(std::string hash){
" s5 n1 k% D7 T5 ^key rekey = make_key(rePrefix, hash);2 }1 Y5 Z: a9 K4 c
struct EnvelopeStruct es;
2 h0 Z. t5 Y( ^5 l6 k9 Estorage_get(rekey, es);
' W$ o0 F: G" l% n) preturn formatEnvelope(es);
. A( H' q2 u. R. ^5 X}1 l. ^  ~; i- @5 m
NOTE:对于智能合约的只读操作(例如查询), 可以通过预执行(pre-exec)来读取结果。不同于普通的合约调用,预执行不需要钱包的签名,同时也就无需花费 ONG。最后,其他用户可以根据 hash(红包的 ID)来领取(抢)这个红包了。$ E; i7 {* S5 {8 A
2.4 领取红包7 F5 P- `' j( J* _* L
我们已经把资产成功地转入到智能合约中了, 接下来就可以把这个红包的 ID 发送给你的朋友们让他们去抢红包了。
, L8 Q. @' r- n; I/ e+ z领取红包需要输入领取人的账户和红包的hash:
6 O! d1 Z; J# A1 a$ N, R* o& y3 b
+ \5 C. M; P2 n7 z5 q/ _bool claimEnvelope(address account, std::string hash){6 g9 v- z- k: Q' `1 g" E5 Y. \
return true;
( a8 w- r7 H. B* T: W, u}
2 q* q8 j0 u  m! _0 O同样, 我们需要验证领取账户的签名, 不允许替其他人抢红包, 而且每个账户每个红包只能抢一次:0 X& T- w# [, G" B: g' V8 c. U

) }8 c7 u4 o, {( \: G" nontio_assert(check_witness(account),"checkwitness failed");
5 C1 [7 x# o$ _: F* g, J( Ckey claimkey = make_key(claimPrefix,hash,account);
$ Z+ w) w6 J" ?$ u0 e, l% s3 Oasset claimed = 0 ;
! t3 B  t- a8 d) S" Q3 wstorage_get(claimkey,claimed);4 k2 `2 \4 ^4 d" W
ontio_assert(claimed == 0,"you have claimed this Envelope!");
; N. X9 ?( t8 {- }按照 hash 从存储中取出红包的信息, 判断这个红包是否没有被抢完:
# Z% J. ]0 x& R, Y$ W2 M+ y$ [* r2 G8 C1 Y+ K" ^
key rekey = make_key(rePrefix,hash);
/ c8 ?' \  r( H+ A9 b$ R* istruct EnvelopeStruct es;1 R; w& G, A" m3 C( Z7 g7 p
storage_get(rekey,es);5 R; B! K) d/ U1 b" b9 x0 b; K
ontio_assert(es.remainAmount > 0, "the Envelope has been claimed over!");- N2 }$ m2 L0 m6 F! Z
ontio_assert(es.remainPackageCount > 0, "the Envelope has been claimed over!");
# d: s" ?- m5 v' i" k新建一条领取的记录:
5 a( ~3 h& [7 c$ F0 \1 N4 g2 _3 E& V$ w! m# z2 Z) r4 e! E
struct receiveRecord record ;& K6 @+ C! m5 Z, e
record.account = account;
( `6 [: K3 H1 Z; y" Aasset claimAmount = 0;- ~4 R" X& x3 w1 m5 D' g, r' V
计算本次领取红包的资产数量。如果是最后一个红包, 数量为剩余的金额, 否则根据当前区块 hash 计算随机数,确定本次领取的数量, 并更新红包信息:* H3 H( e1 r3 ^3 ]2 e- X3 |
- j% o4 P+ x/ Z. [6 C9 ^$ Y
if (es.remainPackageCount == 1){0 ]  o) H, R& I% F, D4 a5 C
claimAmount = es.remainAmount;. I. G3 q  L. @+ k1 p3 D# p
record.amount = claimAmount;
3 C3 G0 R, c# }  J3 F/ V6 l) q}else{
8 P) [+ e. @' _9 P. `% i6 NH256 random = current_blockhash() ;3 Y- \5 b( f7 F8 I  n0 j" o
char part[8];/ D; m* [, S! L; P: e4 x
memcpy(part,&random,8);8 S# k( N* k" m9 F; j6 b$ h7 Y8 @7 K
uint64_t random_num = *(uint64_t*)part;- {6 W" ]$ |$ T! z
uint32_t percent = random_num % 100 + 1;$ U# a7 u$ B  G1 a- [2 ^
claimAmount = es.remainAmount * percent / 100;
+ g7 y6 A* E+ ^6 d* h2 ?) [//ont case
- @" a% V9 d# Z9 jif (claimAmount == 0){& l( I' v$ H- L
claimAmount = 1;( h! f4 C) x: F6 M# C6 Q
}else if(isONTToken(es.tokenAddress)){/ J0 Y3 }) N( l
if ( (es.remainAmount - claimAmount) 根据计算结果, 将对应资产从合约中转到领取的账户:' ?: G, ^  w( v4 C( x
0 [$ F( N- Q9 L3 m1 c+ s
address selfaddr = self_address();
1 [' a9 R  ]  V, ?9 L/ R, Xif (isONTToken(es.tokenAddress)){4 y1 N7 H; C% @
bool result = ont::transfer(selfaddr,account ,claimAmount);
" m# K& g  `. A7 b$ ^8 J6 i" H) K4 gontio_assert(result,"transfer ont token failed!");
! d" J7 Y2 l& x( |; h9 f# P! Q} else if (isONGToken(es.tokenAddress)){
: ^$ Q% M' f+ e# tbool result = ong::transfer(selfaddr,account ,claimAmount);& O3 Q* s- e6 Y/ e5 F
ontio_assert(result,"transfer ong token failed!");( D2 `) A2 E4 H
} else{0 D7 p$ g3 @, H. Z9 |
std::vector params = pack(std::string("transfer"),selfaddr,account,claimAmount);
# g1 |6 y$ @6 @  ]bool res = false;) ]  [' C/ {( ~# I
call_contract(es.tokenAddress,params, res );
# B/ a2 }* {: ?6 l# @5 ~ontio_assert(res,"transfer oep4 token failed!");& {( \$ }% B* F( U% C  f( {
}' q; i. M$ B( m) m7 l' Z0 j7 G8 Y
记录领取的信息, 将更新后的红包信息写回存储并发送通知事件:5 z# B0 ~! R4 {0 v
* z; Y  [2 j9 C, l) N+ A/ R6 Q# S
storage_put(claimkey,claimAmount);2 ~- ?  L5 c3 D! O0 L$ r
storage_put(rekey,es);3 U& A$ }# Q( A3 i
char buffer [100];
: V7 M0 \3 o) Y' Cstd::sprintf(buffer, "{\"states\":[\"%s\",\"%s\",\"%s\",\"%lld\"]}","claimEnvelope",hash.c_str(),account.tohexstring().c_str(),claimAmount);9 C9 P  y# \+ X
notify(buffer);
% w: z; `& k5 Z$ Y9 h/ F5 ~return true;0 d& b6 M" }: U) v0 K
如前面所说,这个合约只能通过 claimEnvelope 这个接口将资产转出合约。所以,合约中的资产是安全的,任何人都无法随意的取走里面的资产。
* g$ _; T9 h) L; x$ c至此, 一个简单的红包合约逻辑完成, 完整的合约代码如下:https://github.com/JasonZhouPW/pubdocs/blob/master/redEnvelope.cpp
0 `$ ^* ^; i' }$ G5 r2.5 合约测试8 ~3 q0 M1 I3 I; X5 |4 I2 j, L
合约测试可以有两种方法:
7 i7 z+ T7 b. C使用 CLI
4 C! m( Z: g0 e- c, s请参考:https://github.com/ontio/ontology-wasm-cdt-cpp/blob/master/How_To_Run_ontologywasm_node.md9 K( Z1 g: ?% W
使用 Golang SDK" U  `9 {8 R$ [/ ^1 m
请参考:https://github.com/ontio/ontology-wasm-cdt-cpp/blob/master/example/other/main.go
& @9 v, C7 t! Q9 b三、总结
; z1 A. P9 J0 z$ r% ~* M本示例只是为了展示如何编写一个完整的 Wasm 智能合约, 如何通过调用 API 和底层的区块链进行交互。如果要作为正式的产品, 还需要解决红包的隐私问题: 所有人都可以通过监控合约的事件来取得红包的 hash, 意味着每个人都可以抢这个红包。一种比较简单的解决方法,就是在创建红包时指定哪些账户能够领取。如果有兴趣, 您也可以尝试修改测试一下。
& q# o9 H+ n6 m" t; V& w我们欢迎更多的 Wasm 技术爱好者加入本体开发社区,共同打造技术生态。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

放弃六月们 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    8