Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

放弃六月们
181 0 0
Ontology Wasm 自从上线测试网以来便受到了社区开发人员的极大关注。 Wasm 的上线将使得业务逻辑复杂的 dApp 合约上链成本降低,极大丰富 dApp 生态。在进行 Wasm 合约开发时,开发者不仅可以使用 Rust,还可以使用 C++ 作为合约开发语言。本期我们将通过两个简单的示例来示范如何使用 C++ 进行Wasm 合约开发。( j" Q# I9 u& N" j& A* Y- P
一、Hello World6 W9 b; ~- w' K
按照惯例,我们还是从一个 Hello world 开始5 V  ?& c8 D2 X2 G+ g$ v/ Q3 W
#include#include: P: l4 d$ i/ q! q2 R9 R4 R
using namespace ontio;class hello:public contract {0 x  w  C' x9 b" s& D, T3 I( C
public:
. P" |7 ^% n! H7 tusing contract::contract:
% W1 V/ |! r* V& vvoid sayHello(){
  s  g# Y) v5 L7 ^% dprintf("hello world!");2 o  o2 O7 u3 P' @+ w3 j
}' w5 c- ^% Q( B/ |8 K8 g4 m# i% X, {! z
};
1 R" e6 d2 q! z( I4 V& t7 y* T; fONTIO_DISPATCH(hello, (sayHello));9 ^( u, H4 _; Q2 }) Q
1.1 合约入口
1 ]/ d4 ?1 P" v. E/ POntology Wasm CDT 编译器已经对入口和参数解析进行了封装,所以开发者不需要重新定义入口方法。接下来是定义合约的对外接口,这是智能合约对外提供服务的方法。
8 s+ o- o7 `1 E7 X5 CONTIO_DISPATCH(hello, (sayHello));
) J: ~$ o+ w% O在上面的例子中, 我们暂时只支持 sayHello 这个方法:
' M  N: O- S1 ^9 r+ o; ^printf("hello world!");
, w, j$ S+ u, w这个“Hello world!”会在节点的日志中以调试信息打印出来。在实际的应用中, printf 只能用作调试的目的, 一个实际的智能合约,需要实现更多更复杂的功能。
. S: u* B3 V+ V) k, o1.2 智能合约 API$ o' T1 }# W  [3 y6 P1 C& Z  X
Ontology Wasm 提供如下 API 与区块链的底层进行交互:$ s' e* X5 |% F1 \& t/ r% }/ Y8 k

9 g- K* b: Y0 }$ j. {二、红包合约" |8 B8 T$ w2 q- E% U
下面我们通过一个更加复杂的例子来演示如何通过这些 API 来开发一个完整的 Wasm 智能合约。% v5 |. D- }; o4 A$ f8 C" P
很多情况下我们都会通过各种 App,如微信等聊天工具发红包。我们可以给朋友发送红包,也可以抢其他人发送的红包,收到的钱会记入到个人微信账户中。
) O  r5 y& A1 ~' q7 U# G" Z0 G类似于微信的流程,我们将尝试创建一个智能合约。用户使用该合约,可以发送 ONT,ONG 或者是标准的 OEP-4的 Token 资产红包给他的朋友们,而朋友们抢到的红包可以直接转入到他们的钱包账户中。  X  J9 Y( u8 e' `2 O0 z1 h4 o- j
2.1 创建合约& T- s6 ^2 W* P$ d. H
首先,我们需要新建合约的源文件,暂且命名为 redEnvelope.cpp。这个合约我们需要三个接口:1 G* i4 n2 v4 Q5 ^3 I4 y7 w0 A8 ?
createRedEnvelope: 创建红包
% |9 v$ K9 e! g, `queryEnvelope: 查询红包信息) _) |& ]% U9 }5 s" M
claimEnvelope: 抢红包
) O% e+ i! F7 y3 P! x#include
: W8 ]5 |0 j- Y! Z8 z7 G6 {using namespace ontio;
' L( r# ]+ _" w* |class redEnvelope: public contract{
' X8 w: Q9 _! X}
8 N( v% B- T% y9 P9 GONTIO_DISPATCH(redEnvelope, (createRedEnvelope)(queryEnvelope)(claimEnvelope));
* Y: s1 C/ N8 n  a我们需要在存储中保存一些关键的数据。在智能合约中, 数据以 KV 的形式保存在该合约的上下文空间中,这些数据的 KEY 需要设置前缀以便于后面的查询。下面定义了三个不同的前缀供使用:' t6 b- t8 f  a% r' K" b; B
std::string rePrefix = "RE_PREFIX_";0 S) ?3 b, s3 z5 [
std::string sentPrefix = "SENT_COUNT_";
  D* y+ m5 V$ k4 ^std::string claimPrefix = "CLAIM_PREFIX_"4 b! n2 N, `0 l4 G
因为我们的合约支持 ONT 和 ONG 这两种 Ontology 的原生资产, 我们可以预先定义好这两种资产的合约地址。不同于标准的智能合约, Ontology 原生合约(native contract)的合约地址是固定的,而不是根据合约代码的 hash 计算而来的。6 B) R5 e: W$ L& h+ p7 Q
address ONTAddress = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1};' {1 [" @  ~/ e/ I& R" ~# m, j
address ONGAddress = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2};
) j$ K4 i/ V8 O: v9 H" c6 e我们需要在合约中保存红包的信息, 如红包的资产信息(token 的合约地址, 红包的总金额, 红包的个数等等)
7 Q* t0 z) E! Z# ]/ O, pstruct receiveRecord{- B. g3 l/ O  Q- e$ ]0 h9 X3 O
address account; //用户地址
# ]& J3 o& \- q+ \' qasset amount; //抢到的金额( C' n# F1 o& P- [
ONTLIB_SERIALIZE(receiveRecord,(account)(amount))
4 n) ]' i" J1 O0 _, w};3 r+ R4 ?7 p- M+ m/ x+ h
struct EnvelopeStruct{
# B/ s5 H; h. r3 l+ q% Q- Yaddress tokenAddress; //资产token的地址
: H4 t9 }. q3 B4 dasset totalAmount; //红包总金额
' t$ C, j- W' nasset totalPackageCount; //红包总数
* K7 X' E# n) f7 masset remainAmount; //当前剩余的金额& X' B7 `0 r3 m) i
asset remainPackageCount; //当前剩余的红包数
. O/ p: p% ]7 G+ tstd::vector records; //已经抢完的记录
+ i+ ?6 l" o8 N5 hONTLIB_SERIALIZE( EnvelopeStruct, (tokenAddress)(totalAmount)(totalPackageCount)(remainAmount)(remainPackageCount)(records) ). k% n7 I) n' ~9 j
};
+ _$ ?; z) m: w& I' F/ F其中,
: e2 J. K7 h: K. WONTLIB_SERIALIZE(receiveRecord,(account)(amount))- q: f' _1 r& y& `! T
是由 Ontology Wasm CDT 定义的宏操作,用于在将 struct 存储前进行序列化的操作。
' ]  F  ?# Y# k7 y. p2 f& {2.2 创建红包
/ |$ i# X* a# L0 c准备工作差不多了,下面我们开始开发具体的接口逻辑。
4 ]: A' X- F& i- x2 D# g9 G创建红包需要指定创建者地址, 红包数量, 红包金额和资产的合约地址:
6 O; q; _$ W8 V3 {0 r. Q
( B( O/ \8 d! t7 |bool createRedEnvelope(address owner,asset packcount, asset amount,address tokenAddr ){" u' v9 p3 n) G% i
return true;
" V! Y  L: n' F& m4 q}
: J9 r) \" n8 W检查是否有创建者的签名, 否则交易回滚退出:
, }4 B/ e$ [- P7 x
% v/ h2 X8 g! Q' n" t3 Lontio_assert(check_witness(owner),"checkwitness failed");: n( v; R; U/ p- d$ E6 j
NOTE: ontio_assert(expr, errormsg):当 expr 为 false 时, 抛出异常并退出。' H/ n& x$ O" a
如果红包资产是 ONT,由于 ONT 的不可分割性(最小为1个 ONT), 红包的金额要大于或等于红包的数量,保证每个红包最少有1个 ONT:2 K5 T& {! h- G, y+ P* t  T! l

6 i) `- ^4 j, Eif (isONTToken(tokenAddr)){+ S0 S1 s# a2 q
ontio_assert(amount >= packcount,"ont amount should greater than packcount");
3 b- p( C$ k9 M3 x6 ~2 E}+ `6 y& ^# {; ^7 s- y3 M& ~2 R! x6 s
对于每个红包的创建者,我们需要记录一下他发送红包的总数量:- q& d  {" k, x, J1 k2 B1 \

! P" y4 n$ r: j3 {key sentkey = make_key(sentPrefix,owner.tohexstring());
* k3 @5 E$ o& iasset sentcount = 0;
; d. ~. Q% q. n2 bstorage_get(sentkey,sentcount);
6 x; ]4 [9 V5 d% s5 Xsentcount += 1;8 G* l2 k# m+ Z
storage_put(sentkey,sentcount);/ h1 J; U) \- T3 T' x! x( h
生成红包 hash, 这个 hash 就是之后标识这个红包的唯一 ID:) z* C/ _8 H. ?4 U# b
' @: u! w6 u+ e" j2 L* O4 @
H256 hash ;/ C0 I# W8 R. r2 a, q  M
hash256(make_key(owner,sentcount),hash) ;
+ M' Z. p2 f* M$ C6 z% wkey rekey = make_key(rePrefix,hash256ToHexstring(hash));
" U# W4 @+ `5 i$ [- \根据 token 资产的类型,将资产转入合约中,self_address()可以取得当前执行的合约地址, 我们根据用户输入的 token 类型,将指定数量的 token 转入合约:
" N# T' E* l) A2 o7 W3 y% ^
; D5 i& e  s$ n1 E2 Y( taddress selfaddr = self_address();9 i, ?5 n4 {3 J" I
if (isONTToken(tokenAddr)){
6 z( Y5 A0 K5 W1 Obool result = ont::transfer(owner,selfaddr ,amount);* M% `' T: s& |: u1 }( R' ^
ontio_assert(result,"transfer native token failed!");
- Y) }- M3 R( T6 ?2 a( x}else if (isONGToken(tokenAddr)){
' ^! E8 v1 l5 F& R3 G$ U/ {bool result = ong::transfer(owner,selfaddr ,amount);
1 L1 E' d# z) K: {: E! `ontio_assert(result,"transfer native token failed!");
3 J4 e7 I+ g8 W/ t4 @; n  l}else{
8 o1 G# J. v* g' cstd::vector params = pack(std::string("transfer"),owner,selfaddr,amount);. _0 [* Q- M; q- z2 u) I" ~
bool res;
" i% M: e) x3 }# {3 P9 F) [call_contract(tokenAddr,params, res );
0 h, U# V( C; p! h, r& {ontio_assert(res,"transfer oep4 token failed!");! y* T5 {* M. F! s' [
}
- k- x6 j/ a3 e& V! fNOTE 1:对于 ONT 和 ONG 这两种原生资产, Ontology Wasm CDT 提供了ont::transfer API 进行转账操作;而 OEP-4类的资产,需要按照普通的跨合约调用方法来转账。
/ q9 `- C, T$ KNOTE 2:和普通的钱包地址一样, 合约地址也可以接受任意类型的资产。但是合约地址是由合约编译后的二进制代码 hash 产生的,所以没有对应的私钥,也就无法随意操作合约中的资产,如果你没有在合约中设置对资产的操作,就意味着你将无法控制这部分资产。
5 k; K; T5 P. I% Q将合约的信息保存在存储中:4 ~: `  @: O$ e- s8 H" ]/ t

% I3 y4 C7 d3 f8 g( O8 sstruct EnvelopeStruct es ;
: Q- s, l0 E0 {" Q* f% ges.tokenAddress = tokenAddr;
4 S  q5 v" `& d/ x* `" }9 @" oes.totalAmount = amount;
9 v/ w0 F5 w, {( u: d1 \: w- w8 ees.totalPackageCount = packcount;
( e9 I/ T( o1 s4 _6 Tes.remainAmount = amount;
* o5 I: U- X2 F4 ^  B8 Q4 Aes.remainPackageCount = packcount;: c$ I* Y. E# C0 v, A+ A
es.records = {};
/ L' c$ z& O7 k$ B7 j* Zstorage_put(rekey, es);5 t. H9 }- @) b/ ?$ ?+ g( f
发送创建红包的事件。对于智能合约的调用是一个异步的过程,合约会在执行成功后发送一个事件来通知客户端执行结果,这个事件的格式可以由合约的编写者来指定。! S' K" ?( q$ t

: z8 D3 T5 E$ B6 H+ y8 [( dchar buffer [100];/ j; F# k. K; v: ?) s. c' }
sprintf(buffer, "{\"states\":[\"%s\", \"%s\", \"%s\"]}","createEnvelope",owner.tohexstring().c_str(),hash256ToHexstring(hash).c_str());
- R8 r' e) A2 V7 ynotify(buffer);6 K/ J& q$ B$ k6 T8 P
return true;" M6 j+ c: q+ h! ]+ O
一个简单的红包就创建完成了, 下一步我们需要实现如何查询这个红包的信息.# f6 Y0 B& {& x* @
2.3 查询红包
6 y1 N) s" X! R% k6 I查询红包的逻辑非常简单, 只需要将存储中的红包信息取出并格式化返回即可:# Y/ l) ^$ r0 L! s/ \+ O' h
std::string queryEnvelope(std::string hash){
2 n2 Q$ Q+ B) S; x! O: \  m2 wkey rekey = make_key(rePrefix, hash);$ l1 j! ]  u3 |( }
struct EnvelopeStruct es;& B  O5 ^, v# e$ N0 B
storage_get(rekey, es);7 ~, c/ `4 |: m) X" l0 y
return formatEnvelope(es);' q+ o2 H7 A1 E4 f! D9 Y
}- D/ P1 Y/ J  ?8 P
NOTE:对于智能合约的只读操作(例如查询), 可以通过预执行(pre-exec)来读取结果。不同于普通的合约调用,预执行不需要钱包的签名,同时也就无需花费 ONG。最后,其他用户可以根据 hash(红包的 ID)来领取(抢)这个红包了。% Y# }. T+ z6 b6 O3 J
2.4 领取红包
1 e: `7 v- |2 a- e我们已经把资产成功地转入到智能合约中了, 接下来就可以把这个红包的 ID 发送给你的朋友们让他们去抢红包了。8 j/ X; Q' X2 A' ~1 x
领取红包需要输入领取人的账户和红包的hash:7 D, O1 O: r. i' c: R
! e& s1 S7 A9 I9 B( U% I
bool claimEnvelope(address account, std::string hash){. y9 w7 r" M2 @; _
return true;6 K$ }  R9 t" A8 U) r6 ^% X4 |
}
7 Q$ t' y, u& p同样, 我们需要验证领取账户的签名, 不允许替其他人抢红包, 而且每个账户每个红包只能抢一次:2 W/ n2 t" t; Z/ H0 {8 @) i9 ^/ B

, A3 q. D8 i3 |" c0 ^ontio_assert(check_witness(account),"checkwitness failed");# F6 {2 `% a: z8 h" h
key claimkey = make_key(claimPrefix,hash,account);
% d  \7 @/ f3 w6 z& tasset claimed = 0 ;
# p1 B. S2 i: j( \storage_get(claimkey,claimed);2 \$ e- f( x: ^" f% A9 n4 Q
ontio_assert(claimed == 0,"you have claimed this Envelope!");/ _) h5 @5 i. ]1 V1 q/ W: V# V
按照 hash 从存储中取出红包的信息, 判断这个红包是否没有被抢完:
1 y0 H! l$ m3 [
3 l5 C! d4 U4 j% ^key rekey = make_key(rePrefix,hash);
0 n: f) S" x$ [0 ]9 T( ?7 [* istruct EnvelopeStruct es;! E; ?6 T- C" K3 X1 v: ^5 S  T
storage_get(rekey,es);: t- ~5 W  W. p- e
ontio_assert(es.remainAmount > 0, "the Envelope has been claimed over!");
: z1 j0 x4 [8 J% s2 G$ S: Vontio_assert(es.remainPackageCount > 0, "the Envelope has been claimed over!");) ~* {! k& g+ p9 @' M* y- U- C
新建一条领取的记录:
0 {- h* }' i) m) r
3 c) S! t1 W- w$ i3 Mstruct receiveRecord record ;# U& X' r. C% u% e0 K. \
record.account = account;
- [; U# T( ?0 j4 u( Tasset claimAmount = 0;
) N; M3 t! ?5 l4 Z/ `9 V1 Z7 H计算本次领取红包的资产数量。如果是最后一个红包, 数量为剩余的金额, 否则根据当前区块 hash 计算随机数,确定本次领取的数量, 并更新红包信息:9 \2 k  A  D' T4 u$ `# x
% `' _: @2 f5 R1 E" u+ d
if (es.remainPackageCount == 1){! _0 i% t/ H& w
claimAmount = es.remainAmount;. ^9 A% t; E& k8 d2 y
record.amount = claimAmount;
* _, V3 x# j% @  e( p}else{
4 s' k) N+ K  m" T% UH256 random = current_blockhash() ;
( B1 z, y  R' o# C0 echar part[8];
9 E% u# S& T+ \% I6 N3 q- ]8 p! Hmemcpy(part,&random,8);2 I/ C) @- L% I  o3 N" A8 U- W
uint64_t random_num = *(uint64_t*)part;
7 q- R& b  L) s# I% W$ _7 h& Puint32_t percent = random_num % 100 + 1;% I. v! o3 c; }* w3 t8 Y: U
claimAmount = es.remainAmount * percent / 100;
- d( g' f! U3 j& m+ }! Y  @//ont case
0 b1 |: q" E2 K3 Tif (claimAmount == 0){. p- n& h, `9 u! I
claimAmount = 1;
+ J8 I. R. n1 [0 K6 f& a}else if(isONTToken(es.tokenAddress)){
2 _' L; y  Q5 b! ?# W5 b! M: ~if ( (es.remainAmount - claimAmount) 根据计算结果, 将对应资产从合约中转到领取的账户:
$ r  d% O: D' `2 Q& V. O
% y( Y  O# i4 n, b. r% l& A: }address selfaddr = self_address();" x; U) n! o0 }( X; j6 d1 Q
if (isONTToken(es.tokenAddress)){
6 \1 o! X) L7 ubool result = ont::transfer(selfaddr,account ,claimAmount);
8 C8 ?9 ]$ Z) q# ?5 gontio_assert(result,"transfer ont token failed!");
& d/ ^- X* ~- e# d3 {" W' h} else if (isONGToken(es.tokenAddress)){
3 k6 C6 c6 g( W: F; X: y$ Cbool result = ong::transfer(selfaddr,account ,claimAmount);; I9 C6 B0 Y9 E* J1 |! Q. }! u
ontio_assert(result,"transfer ong token failed!");* @1 M, n1 O0 I7 [& Z$ p' a5 P
} else{
* \0 w& o% x2 l  k8 m; sstd::vector params = pack(std::string("transfer"),selfaddr,account,claimAmount);' n6 f  d# [1 n+ I, q% m
bool res = false;, E. {% T' ?3 i3 {* A2 S
call_contract(es.tokenAddress,params, res );
* L8 z. `: J3 y5 _- a$ {ontio_assert(res,"transfer oep4 token failed!");/ j' U0 c5 A* _
}8 H- d2 D7 P8 t$ L# e
记录领取的信息, 将更新后的红包信息写回存储并发送通知事件:  t6 }. w5 N9 w7 |+ f; ]) U

3 i  ~9 R8 r- I9 t/ _' T0 ]storage_put(claimkey,claimAmount);5 R1 O' P2 N/ o1 z
storage_put(rekey,es);
6 m/ U, L2 _2 h9 m  `6 O1 [char buffer [100];7 Y2 g7 j2 J5 p, {1 S1 k
std::sprintf(buffer, "{\"states\":[\"%s\",\"%s\",\"%s\",\"%lld\"]}","claimEnvelope",hash.c_str(),account.tohexstring().c_str(),claimAmount);/ j/ a# W9 |/ d) x, f9 y. X  v* I
notify(buffer);; B$ v% A5 ^( c6 c( q. f
return true;
1 B3 p- X' l. N3 L9 S如前面所说,这个合约只能通过 claimEnvelope 这个接口将资产转出合约。所以,合约中的资产是安全的,任何人都无法随意的取走里面的资产。% W2 T7 K- C' B( A+ E7 p2 Z
至此, 一个简单的红包合约逻辑完成, 完整的合约代码如下:https://github.com/JasonZhouPW/pubdocs/blob/master/redEnvelope.cpp$ f$ D5 C) Q% I2 t- P. ~* }; k+ h2 i
2.5 合约测试2 a$ j( A- \, ^6 ?1 ~6 U0 p4 q
合约测试可以有两种方法:
0 _: E' H8 F# J7 @" t使用 CLI) [6 W! I  N9 h! p3 g, Y1 P
请参考:https://github.com/ontio/ontology-wasm-cdt-cpp/blob/master/How_To_Run_ontologywasm_node.md0 h, ^- j$ S1 _( f. C# B' X
使用 Golang SDK
7 W2 p* ^7 F: C$ H  @) \4 f7 U& e请参考:https://github.com/ontio/ontology-wasm-cdt-cpp/blob/master/example/other/main.go5 M0 d& W, _8 T* C$ x) L
三、总结2 C$ f) S  N, }+ N8 V/ ?4 F: b0 |
本示例只是为了展示如何编写一个完整的 Wasm 智能合约, 如何通过调用 API 和底层的区块链进行交互。如果要作为正式的产品, 还需要解决红包的隐私问题: 所有人都可以通过监控合约的事件来取得红包的 hash, 意味着每个人都可以抢这个红包。一种比较简单的解决方法,就是在创建红包时指定哪些账户能够领取。如果有兴趣, 您也可以尝试修改测试一下。
5 q$ \2 k" P. h5 J/ f% D) @我们欢迎更多的 Wasm 技术爱好者加入本体开发社区,共同打造技术生态。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

放弃六月们 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    8