Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

放弃六月们
181 0 0
Ontology Wasm 自从上线测试网以来便受到了社区开发人员的极大关注。 Wasm 的上线将使得业务逻辑复杂的 dApp 合约上链成本降低,极大丰富 dApp 生态。在进行 Wasm 合约开发时,开发者不仅可以使用 Rust,还可以使用 C++ 作为合约开发语言。本期我们将通过两个简单的示例来示范如何使用 C++ 进行Wasm 合约开发。8 y; Z# M* z% ~7 Q& w
一、Hello World# M7 \& a# W+ b- Q+ ^$ e
按照惯例,我们还是从一个 Hello world 开始
: E% B# Q, `3 ]$ Q; z4 \#include#include/ o% @) P# i+ N
using namespace ontio;class hello:public contract {
5 G) B$ n; z9 G1 Fpublic:  `+ D9 G, ~/ O  I% `
using contract::contract:
: \7 e9 _0 {+ L2 x+ gvoid sayHello(){
- z$ S3 `2 D/ G! L4 Gprintf("hello world!");
- T  Z& j" G) A3 U, M0 K}
* ]0 [- G1 [$ s2 v& i};) R* {; s7 e: X6 R8 a, }
ONTIO_DISPATCH(hello, (sayHello));# k, j. T! t( B/ Y0 F. B1 W
1.1 合约入口
, c7 H2 L* U* }7 mOntology Wasm CDT 编译器已经对入口和参数解析进行了封装,所以开发者不需要重新定义入口方法。接下来是定义合约的对外接口,这是智能合约对外提供服务的方法。
9 ?& @' {- r/ |* `% ]4 AONTIO_DISPATCH(hello, (sayHello));
8 ^  b% O2 J  T) G8 F  u5 u& V在上面的例子中, 我们暂时只支持 sayHello 这个方法:
" s* i. f9 T! [; H+ x- e" v. H4 R8 D  Bprintf("hello world!");6 i( f1 a# w. T( s, Q0 l7 x
这个“Hello world!”会在节点的日志中以调试信息打印出来。在实际的应用中, printf 只能用作调试的目的, 一个实际的智能合约,需要实现更多更复杂的功能。- X, _7 G# Z8 F# @* ~* K* K5 i
1.2 智能合约 API
! ~6 G' t' i+ |2 V! m% V' z. gOntology Wasm 提供如下 API 与区块链的底层进行交互:; ^) P( J* @) c5 D: q3 d

9 R4 }4 D; G, ?% F# ]5 U6 H; U二、红包合约
0 V' {7 I2 X2 z9 z9 [; @下面我们通过一个更加复杂的例子来演示如何通过这些 API 来开发一个完整的 Wasm 智能合约。
+ S0 k, F( L4 ?! M很多情况下我们都会通过各种 App,如微信等聊天工具发红包。我们可以给朋友发送红包,也可以抢其他人发送的红包,收到的钱会记入到个人微信账户中。  k/ N1 b4 G6 N  g0 L% ]
类似于微信的流程,我们将尝试创建一个智能合约。用户使用该合约,可以发送 ONT,ONG 或者是标准的 OEP-4的 Token 资产红包给他的朋友们,而朋友们抢到的红包可以直接转入到他们的钱包账户中。
& \9 g6 X( h5 z- F* u4 t; x/ X2.1 创建合约: \, E5 w( ]! l
首先,我们需要新建合约的源文件,暂且命名为 redEnvelope.cpp。这个合约我们需要三个接口:& M  G: L. K3 U/ P, G) y) z$ j7 Z& S
createRedEnvelope: 创建红包: ~6 f  T; z& t; w7 K/ Y
queryEnvelope: 查询红包信息# g( [1 [1 r% q
claimEnvelope: 抢红包
- d1 k, \$ ]; m& a+ y( l! \  {#include
  f5 ?" ]" ], b. Eusing namespace ontio;
- p4 p* y) N, c9 ^4 }. |class redEnvelope: public contract{+ Z6 t7 M- f, \  [( \$ S) _5 i) o
}
3 S6 E; I, q( K; V+ u& OONTIO_DISPATCH(redEnvelope, (createRedEnvelope)(queryEnvelope)(claimEnvelope));2 R; x# N! f' i; R- i! R
我们需要在存储中保存一些关键的数据。在智能合约中, 数据以 KV 的形式保存在该合约的上下文空间中,这些数据的 KEY 需要设置前缀以便于后面的查询。下面定义了三个不同的前缀供使用:7 l5 \9 p1 r( S8 d# d4 b
std::string rePrefix = "RE_PREFIX_";
8 z- G: M. S/ X- E- M" j8 Cstd::string sentPrefix = "SENT_COUNT_";* H) d- Z5 J3 H7 N/ C# I. B
std::string claimPrefix = "CLAIM_PREFIX_"" f4 E/ Q- i9 m$ u
因为我们的合约支持 ONT 和 ONG 这两种 Ontology 的原生资产, 我们可以预先定义好这两种资产的合约地址。不同于标准的智能合约, Ontology 原生合约(native contract)的合约地址是固定的,而不是根据合约代码的 hash 计算而来的。
$ i2 l" G" ?& Yaddress ONTAddress = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1};
# {5 ]; |; d; L* o1 @4 e# @address ONGAddress = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2};7 m. ~* e7 v$ F
我们需要在合约中保存红包的信息, 如红包的资产信息(token 的合约地址, 红包的总金额, 红包的个数等等)
. [% ?) x1 r5 _" l5 ?) N0 f, \5 L7 Z- Cstruct receiveRecord{2 z$ o( L) ]* a, J9 w
address account; //用户地址
3 Y  P" @& o; N- ^0 I; x- Tasset amount; //抢到的金额
/ z* X0 S  K8 `" |ONTLIB_SERIALIZE(receiveRecord,(account)(amount))
3 x3 T" }( G) u' C) `% L5 j, D};! r8 G) B6 Z' {8 J" @# N7 i
struct EnvelopeStruct{
5 `) u. J! w+ @address tokenAddress; //资产token的地址  L# `; b" z, m, T0 j) |( Y
asset totalAmount; //红包总金额% d; S. j5 J5 m- N9 o8 z$ }, [
asset totalPackageCount; //红包总数5 |3 c+ c$ c* A2 I8 L
asset remainAmount; //当前剩余的金额, ]7 E3 J) L1 I1 y  C- R/ e
asset remainPackageCount; //当前剩余的红包数
9 j# r3 q  X' F6 h; b8 xstd::vector records; //已经抢完的记录
( W6 X; j* Y6 i; n4 LONTLIB_SERIALIZE( EnvelopeStruct, (tokenAddress)(totalAmount)(totalPackageCount)(remainAmount)(remainPackageCount)(records) )
% z; G$ Q0 K" P9 x};+ L  T2 l+ i+ X9 d" B9 K
其中,. h6 ]& \6 @( ?! w: j# d
ONTLIB_SERIALIZE(receiveRecord,(account)(amount))
) g3 k) e, X' L* M是由 Ontology Wasm CDT 定义的宏操作,用于在将 struct 存储前进行序列化的操作。9 I. S+ V* h1 Z; @7 z
2.2 创建红包3 j" P* w9 H! i) s+ i+ o
准备工作差不多了,下面我们开始开发具体的接口逻辑。
9 ~4 D0 s1 c/ _6 A: t$ ?创建红包需要指定创建者地址, 红包数量, 红包金额和资产的合约地址:
% G* h( J* z$ b& {  n9 q8 n2 F
" ~1 n, @* M7 y' G9 }$ p0 dbool createRedEnvelope(address owner,asset packcount, asset amount,address tokenAddr ){, z3 A3 P2 z1 J8 N
return true;' i. X6 x* _9 _0 i# B( H
}
! O# `; f) v1 W6 M3 k检查是否有创建者的签名, 否则交易回滚退出:6 h( y+ R; q9 Q+ {' z7 i% F7 c
+ m- p$ Y& r3 Z& K1 w' [) Z$ w
ontio_assert(check_witness(owner),"checkwitness failed");
- C# Y  `9 k5 F# y( S5 _& d2 rNOTE: ontio_assert(expr, errormsg):当 expr 为 false 时, 抛出异常并退出。
( e' w1 a) X0 Y( g如果红包资产是 ONT,由于 ONT 的不可分割性(最小为1个 ONT), 红包的金额要大于或等于红包的数量,保证每个红包最少有1个 ONT:4 `: \" V5 q- e9 U; w9 M2 h
% \; }1 R* |& x6 I0 {. y* N: @
if (isONTToken(tokenAddr)){6 A$ P  v$ }5 h2 V) z+ [0 ?: K
ontio_assert(amount >= packcount,"ont amount should greater than packcount");
. `( a3 s' |" U  K9 Z% m3 f}- L& c; ~4 T9 R$ p$ i
对于每个红包的创建者,我们需要记录一下他发送红包的总数量:
. V# G! {$ l( w! ]. w% M5 R; R2 f# V' {7 O, b
key sentkey = make_key(sentPrefix,owner.tohexstring());
% u6 u- j9 G3 @asset sentcount = 0;
0 F, D  z, @7 ]1 Wstorage_get(sentkey,sentcount);2 ~8 Y/ i+ T* H9 B2 e
sentcount += 1;
, L  Y( Z) u6 l* h; `storage_put(sentkey,sentcount);
1 n8 z0 a% O. ?7 g: y- q生成红包 hash, 这个 hash 就是之后标识这个红包的唯一 ID:8 {' v" x, a9 L' @: d# s+ I

# T$ \: j: j% n) G. kH256 hash ;
& g9 L$ \: {" c% v& Ohash256(make_key(owner,sentcount),hash) ;5 T% u. j: L9 U% h. [
key rekey = make_key(rePrefix,hash256ToHexstring(hash));5 a/ E) O+ V" B
根据 token 资产的类型,将资产转入合约中,self_address()可以取得当前执行的合约地址, 我们根据用户输入的 token 类型,将指定数量的 token 转入合约:  r# \! ]) b0 g: v* [
% g, R& S! i& X0 s1 ^
address selfaddr = self_address();
) Z+ l. t, r6 C, s: wif (isONTToken(tokenAddr)){; c$ s3 S  I3 E3 v( i7 l
bool result = ont::transfer(owner,selfaddr ,amount);9 Y% K! D5 [7 `
ontio_assert(result,"transfer native token failed!");
1 f  Q6 P" Q7 j/ O/ O}else if (isONGToken(tokenAddr)){
) w! t4 K1 X# H6 Q* I0 A: ^; W- }bool result = ong::transfer(owner,selfaddr ,amount);
1 e/ U' P6 Y  }6 ?" Z- Dontio_assert(result,"transfer native token failed!");
3 Q5 ]  f4 k# Y: b1 l" W9 S* b6 A0 x}else{
! e" U5 N) g  y% N2 v- lstd::vector params = pack(std::string("transfer"),owner,selfaddr,amount);
' U+ D* c* `3 \, o9 o- obool res;4 N  k( c$ }# z/ I
call_contract(tokenAddr,params, res );: S, q: U- {% j2 w) h0 o
ontio_assert(res,"transfer oep4 token failed!");, H2 t6 ~; H+ M. R) ~3 p
}: }+ x7 y* i, d* E4 w
NOTE 1:对于 ONT 和 ONG 这两种原生资产, Ontology Wasm CDT 提供了ont::transfer API 进行转账操作;而 OEP-4类的资产,需要按照普通的跨合约调用方法来转账。0 ]7 i8 l. H( r2 s' ]4 q4 V" {
NOTE 2:和普通的钱包地址一样, 合约地址也可以接受任意类型的资产。但是合约地址是由合约编译后的二进制代码 hash 产生的,所以没有对应的私钥,也就无法随意操作合约中的资产,如果你没有在合约中设置对资产的操作,就意味着你将无法控制这部分资产。
/ d& p8 {$ ]0 x. j- \- X将合约的信息保存在存储中:, q! j/ @7 W2 C+ u3 F: t
, _  P+ s' i2 f) \& ^" K7 v
struct EnvelopeStruct es ;( p0 h. L: i) k9 f, e4 T1 ?4 [4 i1 T
es.tokenAddress = tokenAddr;
6 O, `- [: A, ?6 Q, E. ges.totalAmount = amount;1 C9 W4 g- t7 @% U& I' |
es.totalPackageCount = packcount;! y" k( O; `0 g& m! l4 ^  z% [5 Q
es.remainAmount = amount;
3 A4 n; l- @' Z! }+ \% hes.remainPackageCount = packcount;( \: X7 P. X" \
es.records = {};; H" Z9 M; K% C5 Q6 a
storage_put(rekey, es);% V8 _' [3 v7 @9 J
发送创建红包的事件。对于智能合约的调用是一个异步的过程,合约会在执行成功后发送一个事件来通知客户端执行结果,这个事件的格式可以由合约的编写者来指定。
, Q# y# M' f8 F9 R1 J' D: z( B
/ [" P! u, k) ~, r$ Nchar buffer [100];$ b+ J  R7 a+ u. D- D' Y
sprintf(buffer, "{\"states\":[\"%s\", \"%s\", \"%s\"]}","createEnvelope",owner.tohexstring().c_str(),hash256ToHexstring(hash).c_str());& g1 D% S- U! p" d  ~3 j6 Q
notify(buffer);
8 W0 N) B$ F7 h2 o. M6 J: ]) j! @; B" ^return true;8 y0 z5 A* }  X) G
一个简单的红包就创建完成了, 下一步我们需要实现如何查询这个红包的信息.; ^1 Q$ j7 Y. h
2.3 查询红包! |! H% v" e. z' K5 J
查询红包的逻辑非常简单, 只需要将存储中的红包信息取出并格式化返回即可:
$ s% e. k* E0 b' Ostd::string queryEnvelope(std::string hash){. U3 }% N- _4 ~- A
key rekey = make_key(rePrefix, hash);
" e$ U5 l! T/ |- Z" B; Q" z. bstruct EnvelopeStruct es;
+ s) f, W+ {  Y6 istorage_get(rekey, es);
3 M; B; Z3 G* N0 v& A- Preturn formatEnvelope(es);
3 T7 O) B' J) C9 \2 d}: B: e: a7 N" u  O. ]0 K) ~, n& U
NOTE:对于智能合约的只读操作(例如查询), 可以通过预执行(pre-exec)来读取结果。不同于普通的合约调用,预执行不需要钱包的签名,同时也就无需花费 ONG。最后,其他用户可以根据 hash(红包的 ID)来领取(抢)这个红包了。& r. e# T, h. z' o
2.4 领取红包8 y8 b& H( g. k" l
我们已经把资产成功地转入到智能合约中了, 接下来就可以把这个红包的 ID 发送给你的朋友们让他们去抢红包了。  ~$ F* G* E& q
领取红包需要输入领取人的账户和红包的hash:
2 @' Z( K; m7 P6 G% C& l# u( A' v! R  A( n( O
bool claimEnvelope(address account, std::string hash){2 u- m! b* ~# ~$ ~+ T& ~+ e
return true;
9 [+ y4 i8 i1 i0 C}
& C) O4 N/ E4 E4 E4 n& D. x同样, 我们需要验证领取账户的签名, 不允许替其他人抢红包, 而且每个账户每个红包只能抢一次:
7 `9 t' N/ \9 }
" n! a3 _& R& W+ t, `/ Gontio_assert(check_witness(account),"checkwitness failed");; p# |7 g% c1 I4 c) O8 {1 u. U
key claimkey = make_key(claimPrefix,hash,account);
; U" X7 ]6 Z* R3 E! oasset claimed = 0 ;
6 }& e4 G. |% e% \' rstorage_get(claimkey,claimed);/ r7 b/ B1 y1 t+ p# D
ontio_assert(claimed == 0,"you have claimed this Envelope!");- E7 Z5 D1 J3 i
按照 hash 从存储中取出红包的信息, 判断这个红包是否没有被抢完:
3 |% n& y: [2 |8 G0 T- ]1 A/ h6 R  H' B
key rekey = make_key(rePrefix,hash);" j1 \) {% ~- D! u
struct EnvelopeStruct es;
1 }. m2 ], H: {/ @- |4 Pstorage_get(rekey,es);6 w! _- H. ~6 m1 S& j
ontio_assert(es.remainAmount > 0, "the Envelope has been claimed over!");
& L4 N% ^. [; U/ `' ?ontio_assert(es.remainPackageCount > 0, "the Envelope has been claimed over!");+ T  D2 S, U3 V4 M
新建一条领取的记录:
* G+ m) Y8 E+ K. c" s9 D4 k% u
7 f5 a6 O; c: ?. fstruct receiveRecord record ;$ w9 K( k9 F0 i
record.account = account;
: c* F2 g! a# Q) R* \asset claimAmount = 0;
1 R5 a: m, F! H7 x7 p2 A/ o$ ~/ ^计算本次领取红包的资产数量。如果是最后一个红包, 数量为剩余的金额, 否则根据当前区块 hash 计算随机数,确定本次领取的数量, 并更新红包信息:# ~+ z( A* J  |# v; ]8 v8 |: f
; b, v) R' L3 ?' ?. n5 |$ |& t; p* j* x- c
if (es.remainPackageCount == 1){+ t( K& k) {/ Y+ u2 e
claimAmount = es.remainAmount;
5 G, m8 b* J1 d/ V; e4 h) k! Erecord.amount = claimAmount;! }* I; r+ r2 J: A+ q+ b$ F3 C  Y$ V
}else{
& U* q3 ^1 Z: z' F1 W9 WH256 random = current_blockhash() ;% k6 J5 Q3 v, E- n- d7 N) F
char part[8];
( c* v8 Z5 M4 D. g/ }& r$ bmemcpy(part,&random,8);
1 g5 `8 D4 b, T) A( `( o! \6 xuint64_t random_num = *(uint64_t*)part;7 i: Y2 ?, U- i2 b/ ]
uint32_t percent = random_num % 100 + 1;
- T) `  ]& U" {1 x. Z. D# N7 lclaimAmount = es.remainAmount * percent / 100;9 Q. P1 Q8 H( o! O7 b3 a1 O
//ont case7 H1 i7 _4 J1 \
if (claimAmount == 0){
' D" p# R, M3 W3 kclaimAmount = 1;
' e7 l! ]0 _: P0 k+ G5 g0 ^}else if(isONTToken(es.tokenAddress)){4 u# t* N% z% q5 I1 p+ t( o- x
if ( (es.remainAmount - claimAmount) 根据计算结果, 将对应资产从合约中转到领取的账户:3 J# Y1 _9 F* ?  n* E( v2 p8 e
) N, j$ I# D: U: ]  l4 k! R
address selfaddr = self_address();$ j2 V. s# |/ o- D) k. U! K, Y. j: g8 V
if (isONTToken(es.tokenAddress)){
9 `6 I8 Z1 ^- T- N4 d& @bool result = ont::transfer(selfaddr,account ,claimAmount);+ m0 P# k% t; D! E6 V4 M
ontio_assert(result,"transfer ont token failed!");8 `; f1 ?2 Y) H$ i5 l
} else if (isONGToken(es.tokenAddress)){
. [2 B8 u  A; G6 D: a1 p4 K' v% ebool result = ong::transfer(selfaddr,account ,claimAmount);
+ l3 ?: c4 o3 K. Q1 Fontio_assert(result,"transfer ong token failed!");; c8 R' b7 T+ @- J$ ?% [
} else{
" c/ d4 {- }: t- }- j5 Lstd::vector params = pack(std::string("transfer"),selfaddr,account,claimAmount);
  x2 A3 {$ ^: M5 T+ U  `, @* [bool res = false;
1 `+ B; `% ^7 Acall_contract(es.tokenAddress,params, res );
* V2 g& d# J# ]ontio_assert(res,"transfer oep4 token failed!");4 w! c/ y! c' A( z0 x
}
+ x4 |0 H9 i5 P& h记录领取的信息, 将更新后的红包信息写回存储并发送通知事件:. @& w& v& J6 O$ y' `

4 U' H5 k3 a6 s7 u8 L# Istorage_put(claimkey,claimAmount);5 M1 ^) T; R" k: o& s
storage_put(rekey,es);; t# ?6 g6 ^# ~  e( j3 u! w% X
char buffer [100];2 _% N, M* G( a0 Q7 r! Y! z+ I
std::sprintf(buffer, "{\"states\":[\"%s\",\"%s\",\"%s\",\"%lld\"]}","claimEnvelope",hash.c_str(),account.tohexstring().c_str(),claimAmount);  T0 `! D+ a+ n7 M+ o, j4 w
notify(buffer);
# C, u9 s! i1 T0 oreturn true;
7 z) A" z7 C" C. ]  E- b; Y7 x如前面所说,这个合约只能通过 claimEnvelope 这个接口将资产转出合约。所以,合约中的资产是安全的,任何人都无法随意的取走里面的资产。+ M2 e" C! G! U& l
至此, 一个简单的红包合约逻辑完成, 完整的合约代码如下:https://github.com/JasonZhouPW/pubdocs/blob/master/redEnvelope.cpp  t0 J4 r' z1 v% T
2.5 合约测试
5 e4 S8 b6 `  r) v合约测试可以有两种方法:
( Q. }" s+ T" {) T' E2 D; z1 v使用 CLI6 c7 o1 A$ a7 G  n
请参考:https://github.com/ontio/ontology-wasm-cdt-cpp/blob/master/How_To_Run_ontologywasm_node.md
$ M4 A7 y' h) e+ k# z使用 Golang SDK
& Y' o) r5 x/ Q" i7 S+ c, a请参考:https://github.com/ontio/ontology-wasm-cdt-cpp/blob/master/example/other/main.go
! z5 A, C$ o* O" y7 E三、总结
! x3 f  T8 B) b' b; i, g本示例只是为了展示如何编写一个完整的 Wasm 智能合约, 如何通过调用 API 和底层的区块链进行交互。如果要作为正式的产品, 还需要解决红包的隐私问题: 所有人都可以通过监控合约的事件来取得红包的 hash, 意味着每个人都可以抢这个红包。一种比较简单的解决方法,就是在创建红包时指定哪些账户能够领取。如果有兴趣, 您也可以尝试修改测试一下。
+ D4 m) S/ W, H2 y我们欢迎更多的 Wasm 技术爱好者加入本体开发社区,共同打造技术生态。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

放弃六月们 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    8