Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

放弃六月们
106 0 0
Ontology Wasm 自从上线测试网以来便受到了社区开发人员的极大关注。 Wasm 的上线将使得业务逻辑复杂的 dApp 合约上链成本降低,极大丰富 dApp 生态。在进行 Wasm 合约开发时,开发者不仅可以使用 Rust,还可以使用 C++ 作为合约开发语言。本期我们将通过两个简单的示例来示范如何使用 C++ 进行Wasm 合约开发。9 m3 F# q* ]4 [6 G# ?9 S* z3 G
一、Hello World+ H  ?, x$ u2 ~) @0 {3 V& ]
按照惯例,我们还是从一个 Hello world 开始
" w. [  s" t( F6 i: x7 r#include#include3 T: E5 r  n: X* t% P
using namespace ontio;class hello:public contract {
% y( M, s) t; A6 [% E6 Zpublic:
" r& m) F  P0 G' A6 v1 qusing contract::contract:
( f2 K- {9 K$ |+ R5 x# [void sayHello(){
' ^( h. c# c! q9 h) c( H% yprintf("hello world!");
: ^+ k" n1 i( s}6 E! K) O! P5 A. y
};4 X) _6 Z2 ~% T9 G) U: X
ONTIO_DISPATCH(hello, (sayHello));
* J; {1 r, F7 ^3 ~2 M1.1 合约入口
: v! k! ]* [  D. EOntology Wasm CDT 编译器已经对入口和参数解析进行了封装,所以开发者不需要重新定义入口方法。接下来是定义合约的对外接口,这是智能合约对外提供服务的方法。+ E6 i. x1 G; C
ONTIO_DISPATCH(hello, (sayHello));8 \& ?% K* K$ W
在上面的例子中, 我们暂时只支持 sayHello 这个方法:
6 R4 E. H# ?5 Z1 U  z/ @2 X; Tprintf("hello world!");
" _  z1 c/ q" V! g! O这个“Hello world!”会在节点的日志中以调试信息打印出来。在实际的应用中, printf 只能用作调试的目的, 一个实际的智能合约,需要实现更多更复杂的功能。: }' i* p: C; d
1.2 智能合约 API8 W1 p: @8 j, y9 x
Ontology Wasm 提供如下 API 与区块链的底层进行交互:( G3 ]0 B9 u, K" }8 p! v; `
6 E6 R! |& ^% z0 j  U6 U9 k4 a
二、红包合约
% x6 c) i; b2 N" m4 Q- E* ^下面我们通过一个更加复杂的例子来演示如何通过这些 API 来开发一个完整的 Wasm 智能合约。
( [  Q8 m" `( `: Y3 T3 z7 u) j很多情况下我们都会通过各种 App,如微信等聊天工具发红包。我们可以给朋友发送红包,也可以抢其他人发送的红包,收到的钱会记入到个人微信账户中。. r, ~! R: v4 e! G7 t$ d
类似于微信的流程,我们将尝试创建一个智能合约。用户使用该合约,可以发送 ONT,ONG 或者是标准的 OEP-4的 Token 资产红包给他的朋友们,而朋友们抢到的红包可以直接转入到他们的钱包账户中。: A9 _3 N8 v7 q% U# B* K
2.1 创建合约  B+ E. H2 `8 {0 r4 I
首先,我们需要新建合约的源文件,暂且命名为 redEnvelope.cpp。这个合约我们需要三个接口:1 m( R3 R8 @8 e7 }9 F
createRedEnvelope: 创建红包
3 U$ z7 X, |- ?* o. o: jqueryEnvelope: 查询红包信息/ q3 Z0 v- {: _' X
claimEnvelope: 抢红包
! z' Z% F/ G8 @3 K#include$ k* w4 `$ u/ ?" f! c7 \& q3 Y6 [
using namespace ontio;
) Y& |# U; N* A/ u* Y9 m! F  H4 Dclass redEnvelope: public contract{( `: a& U/ _3 k
}1 {: n% @/ K2 \6 M# w
ONTIO_DISPATCH(redEnvelope, (createRedEnvelope)(queryEnvelope)(claimEnvelope));, S* T8 B# g# B+ ?# |4 p2 y
我们需要在存储中保存一些关键的数据。在智能合约中, 数据以 KV 的形式保存在该合约的上下文空间中,这些数据的 KEY 需要设置前缀以便于后面的查询。下面定义了三个不同的前缀供使用:
5 r3 P! ~9 G) o! G8 kstd::string rePrefix = "RE_PREFIX_";; I  V. E3 H& A2 }9 t/ P: z6 ]
std::string sentPrefix = "SENT_COUNT_";1 c8 w! {' {3 D4 L9 E/ u
std::string claimPrefix = "CLAIM_PREFIX_"0 w2 B4 E7 m+ R, @* {. G0 }8 i% n
因为我们的合约支持 ONT 和 ONG 这两种 Ontology 的原生资产, 我们可以预先定义好这两种资产的合约地址。不同于标准的智能合约, Ontology 原生合约(native contract)的合约地址是固定的,而不是根据合约代码的 hash 计算而来的。
9 F( V$ u: q+ l8 f# U( raddress ONTAddress = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1};# {# z" N5 g9 b) `1 W
address ONGAddress = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2};
/ @8 W' C: W9 Q6 o我们需要在合约中保存红包的信息, 如红包的资产信息(token 的合约地址, 红包的总金额, 红包的个数等等)
; m0 ]8 p$ O9 Q* U  zstruct receiveRecord{
: @& S) `! X+ j0 Laddress account; //用户地址- \/ ]3 T5 g& \$ E* H
asset amount; //抢到的金额+ g  g9 z3 O  ]* F$ P; F
ONTLIB_SERIALIZE(receiveRecord,(account)(amount))
% L3 s* P1 ]( a8 [- r# D: f};6 o: t, K5 L0 L4 k" p* R% P
struct EnvelopeStruct{, D4 w% j9 i0 a0 x
address tokenAddress; //资产token的地址
& I+ ^- \4 I3 ~: z; ~asset totalAmount; //红包总金额
# e+ Y9 b6 R$ ]6 G3 S; Aasset totalPackageCount; //红包总数3 ^+ m, T% D9 F7 k0 s
asset remainAmount; //当前剩余的金额" j2 `4 E$ e6 M/ F3 J
asset remainPackageCount; //当前剩余的红包数; W9 v7 N- y- ^6 c
std::vector records; //已经抢完的记录
! \' d8 ^( d* h" W0 X! ^3 X" r0 eONTLIB_SERIALIZE( EnvelopeStruct, (tokenAddress)(totalAmount)(totalPackageCount)(remainAmount)(remainPackageCount)(records) )
& N/ i0 d0 u+ w, J: o" ]};- e; R! t% N! Y2 ^
其中,
( Y) D) l0 `7 Q# T: e5 H/ T9 ?ONTLIB_SERIALIZE(receiveRecord,(account)(amount))
. @/ W/ O) X; a" q6 r& t是由 Ontology Wasm CDT 定义的宏操作,用于在将 struct 存储前进行序列化的操作。) ~( X: [  Q' @$ G' x) D
2.2 创建红包
" r3 E8 W  q9 G/ V3 g1 v准备工作差不多了,下面我们开始开发具体的接口逻辑。: G3 d, _# s6 h
创建红包需要指定创建者地址, 红包数量, 红包金额和资产的合约地址:
: v% B' j0 W' f1 O0 T0 c3 Q
1 V8 |$ H8 \( h7 Sbool createRedEnvelope(address owner,asset packcount, asset amount,address tokenAddr ){; w$ C- f2 P4 Z" Y$ j! f  A
return true;
' a% I. B3 \9 c, U% E" w5 j- T+ c- O}6 m& W) _9 B4 H& {+ q) ^
检查是否有创建者的签名, 否则交易回滚退出:
# h9 X. v( q& o6 o% n1 z* F  ~( A
  w, Z/ h8 n1 e% h# F# S3 V$ |: \5 xontio_assert(check_witness(owner),"checkwitness failed");1 j& e! S/ }" t, H
NOTE: ontio_assert(expr, errormsg):当 expr 为 false 时, 抛出异常并退出。+ @' @) `1 e1 g: C" ~
如果红包资产是 ONT,由于 ONT 的不可分割性(最小为1个 ONT), 红包的金额要大于或等于红包的数量,保证每个红包最少有1个 ONT:, q# b- s) M2 Y/ x" H- k

7 L( C5 c) b' G/ Iif (isONTToken(tokenAddr)){
5 L2 F4 ]( P9 D4 N$ sontio_assert(amount >= packcount,"ont amount should greater than packcount");
, ^4 ]! C; {, d* B/ \% l}$ q8 X% }5 H. G* E7 x8 {
对于每个红包的创建者,我们需要记录一下他发送红包的总数量:5 |; O* f/ z  ?$ \; p$ |+ Z
$ |/ b4 J' C9 y% S* F
key sentkey = make_key(sentPrefix,owner.tohexstring());
3 D3 ]' u, _" }* g9 Masset sentcount = 0;0 B& T1 f% Q# }) v' L2 S' \6 ]9 N
storage_get(sentkey,sentcount);1 k; ]5 @7 @; h/ G6 v' x
sentcount += 1;- Z* ?8 q% x% [3 z/ z  K1 p( ^1 g
storage_put(sentkey,sentcount);
& G% s) j; t4 f生成红包 hash, 这个 hash 就是之后标识这个红包的唯一 ID:
8 F# v8 ?) r' k' x9 k! l$ w7 d7 N8 B! m; h! [
H256 hash ;# M7 j" O: K+ S6 {! s  b, S+ [
hash256(make_key(owner,sentcount),hash) ;
- v- ]1 X: @) u# R7 L" u" }key rekey = make_key(rePrefix,hash256ToHexstring(hash));# t2 \; h0 W9 _6 p0 \. @$ ?
根据 token 资产的类型,将资产转入合约中,self_address()可以取得当前执行的合约地址, 我们根据用户输入的 token 类型,将指定数量的 token 转入合约:  j. i% K% P2 q' R, Y2 n

  O" e2 n2 [9 H; n6 T: ?address selfaddr = self_address();7 ~# V: P- v2 \
if (isONTToken(tokenAddr)){- D& R4 B# M% v+ J& L* ]% V
bool result = ont::transfer(owner,selfaddr ,amount);
+ I2 T+ v# g8 bontio_assert(result,"transfer native token failed!");
  h+ N' f5 f* C( }) |$ a}else if (isONGToken(tokenAddr)){
. S3 M  ]6 h  H& obool result = ong::transfer(owner,selfaddr ,amount);( \8 M$ M8 T/ o5 e
ontio_assert(result,"transfer native token failed!");
+ O5 L0 H% C/ E2 L* m+ C2 h}else{
! i  ~4 ^: V: l% ?std::vector params = pack(std::string("transfer"),owner,selfaddr,amount);
! C' c( |) x  ?, m% Tbool res;. ^/ U& h, M) D6 e6 {# O2 m
call_contract(tokenAddr,params, res );
. K! ^, O. Z3 Y9 }" D# sontio_assert(res,"transfer oep4 token failed!");
, z) X2 i$ l2 y; v% i7 I$ U. N}
; @) E4 B! ]" ^9 V  N* [, |  WNOTE 1:对于 ONT 和 ONG 这两种原生资产, Ontology Wasm CDT 提供了ont::transfer API 进行转账操作;而 OEP-4类的资产,需要按照普通的跨合约调用方法来转账。
2 w9 b2 o) K; {* @/ B& `' s! b* g1 cNOTE 2:和普通的钱包地址一样, 合约地址也可以接受任意类型的资产。但是合约地址是由合约编译后的二进制代码 hash 产生的,所以没有对应的私钥,也就无法随意操作合约中的资产,如果你没有在合约中设置对资产的操作,就意味着你将无法控制这部分资产。
  ?2 \* k8 x# F- @* `将合约的信息保存在存储中:
& e5 t- ~7 t: l7 H2 }7 N& R/ K. i/ L  r
struct EnvelopeStruct es ;6 u( u% @6 }2 e* L" X
es.tokenAddress = tokenAddr;9 `: t" L  P' \; u4 m
es.totalAmount = amount;$ B! |2 u  E8 L
es.totalPackageCount = packcount;' [- C+ w) L% ]: u5 u4 Q
es.remainAmount = amount;
4 `2 y& N) O7 M) F2 \2 Bes.remainPackageCount = packcount;( g5 D0 O0 ^( T$ L+ @% ?6 m! n
es.records = {};
5 }$ V* S( e) ^% s' Tstorage_put(rekey, es);
3 @: h0 n1 J& J) Z. T8 [发送创建红包的事件。对于智能合约的调用是一个异步的过程,合约会在执行成功后发送一个事件来通知客户端执行结果,这个事件的格式可以由合约的编写者来指定。# {8 }  U2 r7 D2 D( G0 @% Z
# @6 b4 v% r% v; b. Z
char buffer [100];
; y- a1 H- d2 G+ X( o& Nsprintf(buffer, "{\"states\":[\"%s\", \"%s\", \"%s\"]}","createEnvelope",owner.tohexstring().c_str(),hash256ToHexstring(hash).c_str());$ |% R& H( B; y5 L6 ^1 N1 F0 R
notify(buffer);
+ N/ b, p+ u4 G1 d3 x% ~return true;3 }- x9 C& P$ I* r' V) _3 C& \
一个简单的红包就创建完成了, 下一步我们需要实现如何查询这个红包的信息.
# i( O1 y. q' J$ U2.3 查询红包
5 H% _8 \" Y# B# o5 I8 |查询红包的逻辑非常简单, 只需要将存储中的红包信息取出并格式化返回即可:
0 L) C4 c& _0 c# [, i+ }1 ustd::string queryEnvelope(std::string hash){: x7 S- _8 a* R# X2 B" u
key rekey = make_key(rePrefix, hash);9 L+ ]. ~! x' A' L8 [
struct EnvelopeStruct es;
# x% P! p. N4 l4 Fstorage_get(rekey, es);
; F& w, U# m9 w9 r7 }return formatEnvelope(es);: ^2 H+ A0 k8 V
}6 h4 ?6 F7 I& g( l9 U1 a/ t& m
NOTE:对于智能合约的只读操作(例如查询), 可以通过预执行(pre-exec)来读取结果。不同于普通的合约调用,预执行不需要钱包的签名,同时也就无需花费 ONG。最后,其他用户可以根据 hash(红包的 ID)来领取(抢)这个红包了。5 o: |7 A* A, D4 N2 Q
2.4 领取红包
0 [7 \. r% C  o) V- n6 w2 \: b我们已经把资产成功地转入到智能合约中了, 接下来就可以把这个红包的 ID 发送给你的朋友们让他们去抢红包了。4 v9 o  ?: [5 m& k* N% r& ^
领取红包需要输入领取人的账户和红包的hash:
2 V# A' R8 w9 |1 X
- X. @, k# [& S) _bool claimEnvelope(address account, std::string hash){
+ t  u( B: R/ Z+ S& i* ~6 mreturn true;$ `5 ^3 Y9 K" g# y1 i  J. w
}; ^7 f6 [3 y. e2 L& Z
同样, 我们需要验证领取账户的签名, 不允许替其他人抢红包, 而且每个账户每个红包只能抢一次:
5 N8 V2 W/ ~2 L# z* \/ R. }( a! x8 I" k& c
ontio_assert(check_witness(account),"checkwitness failed");, \( ]5 D% e  U
key claimkey = make_key(claimPrefix,hash,account);
; @4 H0 Q1 @* U( N6 u. yasset claimed = 0 ;: P* Z  C& N/ ~
storage_get(claimkey,claimed);' m9 h5 _: }8 T2 y
ontio_assert(claimed == 0,"you have claimed this Envelope!");9 e  N0 D% x; G8 [7 t1 `
按照 hash 从存储中取出红包的信息, 判断这个红包是否没有被抢完:
4 [4 T1 U* @- @" \' l. c1 z* k9 \/ ~; K
key rekey = make_key(rePrefix,hash);. s) m, ?4 v9 j
struct EnvelopeStruct es;) z- B# h) m5 B, v& X
storage_get(rekey,es);
% A' K" @, c& b+ ~- E* g  J+ c5 Rontio_assert(es.remainAmount > 0, "the Envelope has been claimed over!");
! ^$ N" N% ?3 a. `4 Nontio_assert(es.remainPackageCount > 0, "the Envelope has been claimed over!");
# Y* h" L! W* }# [- G8 g新建一条领取的记录:
5 u' U/ m0 m0 M: s9 }
% b; w3 r# c- n: G) O1 O: i  U3 estruct receiveRecord record ;
6 }4 [8 ~% P. U) O( wrecord.account = account;
1 j, ]7 M) }+ z0 I& V$ tasset claimAmount = 0;7 U+ O. [/ J4 H; m" g
计算本次领取红包的资产数量。如果是最后一个红包, 数量为剩余的金额, 否则根据当前区块 hash 计算随机数,确定本次领取的数量, 并更新红包信息:/ N& Z$ E2 r7 O# ]8 s

7 G# s( u: H3 b6 Z4 qif (es.remainPackageCount == 1){$ q  ?/ {& @3 [" _5 [7 o1 E  n
claimAmount = es.remainAmount;9 k% M+ R2 T$ F& t
record.amount = claimAmount;
7 C2 t+ |; Q, V2 b# q( R9 f}else{
8 m1 _4 \' H% qH256 random = current_blockhash() ;" y  D) s' m, Z" v# c3 d6 Y; D- O. l
char part[8];. h2 c/ j3 z) a3 {$ p
memcpy(part,&random,8);
$ T8 E2 M% h+ e3 C& z  Uuint64_t random_num = *(uint64_t*)part;) _+ ?7 }, p$ @! [* m1 c8 |
uint32_t percent = random_num % 100 + 1;! x/ M/ ?' B* P' P  _
claimAmount = es.remainAmount * percent / 100;8 I5 n; h: u" }: g$ ], M
//ont case
/ j9 d+ s" }9 I2 h6 s- w9 P7 ~if (claimAmount == 0){- v2 J$ \# E; N) d
claimAmount = 1;
7 o7 L, u( U) r* C3 ^1 @: k, q2 D}else if(isONTToken(es.tokenAddress)){& E$ Q9 q& p! {7 N1 f* p8 A' t: B, i
if ( (es.remainAmount - claimAmount) 根据计算结果, 将对应资产从合约中转到领取的账户:9 f. D/ _. L0 @
6 n" I: K" P8 \  K
address selfaddr = self_address();$ e& x- G! D5 D* M3 t( c' \
if (isONTToken(es.tokenAddress)){; e+ S( t" c4 ?) I3 X
bool result = ont::transfer(selfaddr,account ,claimAmount);! w9 _1 ^1 \7 |1 P
ontio_assert(result,"transfer ont token failed!");$ N! l& q* c$ ?' a
} else if (isONGToken(es.tokenAddress)){7 K& _4 k2 W) q: O1 n% K
bool result = ong::transfer(selfaddr,account ,claimAmount);
+ N2 |* I" U0 sontio_assert(result,"transfer ong token failed!");2 r) P4 v, S, C: ?4 p
} else{1 J* v) u' b8 Q& o
std::vector params = pack(std::string("transfer"),selfaddr,account,claimAmount);8 C- m% P" J. C3 i& B# U
bool res = false;' M) M+ L* G4 ?
call_contract(es.tokenAddress,params, res );$ {$ ^3 u5 b) k: h1 @* \
ontio_assert(res,"transfer oep4 token failed!");: Z  V7 U9 E5 N, p5 B6 ]
}
6 q! H  J, O% p9 e& Z0 p# X. _记录领取的信息, 将更新后的红包信息写回存储并发送通知事件:. M, G( R7 E$ z* J

- ^2 b- B6 k( f1 hstorage_put(claimkey,claimAmount);& [/ R5 t8 d7 f1 c$ w
storage_put(rekey,es);
3 O- ^. `! J( `. m. d; s6 o! v& Q! fchar buffer [100];7 q3 r  W& X; }8 s# _8 K, _( i
std::sprintf(buffer, "{\"states\":[\"%s\",\"%s\",\"%s\",\"%lld\"]}","claimEnvelope",hash.c_str(),account.tohexstring().c_str(),claimAmount);
! q. }# a. z; l* M: s4 pnotify(buffer);
" m$ {% e4 w1 I, v- Y2 d( wreturn true;
) U: P8 J8 @% j; J+ u! ?如前面所说,这个合约只能通过 claimEnvelope 这个接口将资产转出合约。所以,合约中的资产是安全的,任何人都无法随意的取走里面的资产。6 b! `( I: B+ P$ ~% F
至此, 一个简单的红包合约逻辑完成, 完整的合约代码如下:https://github.com/JasonZhouPW/pubdocs/blob/master/redEnvelope.cpp
( K% ]& P1 B3 [( B$ {2.5 合约测试, R; d2 V9 Z$ _/ C7 L
合约测试可以有两种方法:
2 b" S1 V  O) a. `% n- Z使用 CLI7 X  Q. W5 }& F# |3 ]4 k
请参考:https://github.com/ontio/ontology-wasm-cdt-cpp/blob/master/How_To_Run_ontologywasm_node.md
0 F8 F) h* I0 J: X5 D使用 Golang SDK* f! R" n% t0 l/ O9 [3 y3 u5 h4 y
请参考:https://github.com/ontio/ontology-wasm-cdt-cpp/blob/master/example/other/main.go/ }; g9 b& o$ x) a( a/ L
三、总结
0 z' D9 ~( A3 q' l0 j( N9 Z( N本示例只是为了展示如何编写一个完整的 Wasm 智能合约, 如何通过调用 API 和底层的区块链进行交互。如果要作为正式的产品, 还需要解决红包的隐私问题: 所有人都可以通过监控合约的事件来取得红包的 hash, 意味着每个人都可以抢这个红包。一种比较简单的解决方法,就是在创建红包时指定哪些账户能够领取。如果有兴趣, 您也可以尝试修改测试一下。9 O, s! f$ y) `- E  W# |
我们欢迎更多的 Wasm 技术爱好者加入本体开发社区,共同打造技术生态。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

放弃六月们 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    8