Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

放弃六月们
209 0 0
Ontology Wasm 自从上线测试网以来便受到了社区开发人员的极大关注。 Wasm 的上线将使得业务逻辑复杂的 dApp 合约上链成本降低,极大丰富 dApp 生态。在进行 Wasm 合约开发时,开发者不仅可以使用 Rust,还可以使用 C++ 作为合约开发语言。本期我们将通过两个简单的示例来示范如何使用 C++ 进行Wasm 合约开发。: `8 T7 \6 h. ^. ?2 c( i, |6 O: v
一、Hello World' c# p6 x/ m& I* m
按照惯例,我们还是从一个 Hello world 开始
! ^9 n7 ^5 {3 v) k8 z; D#include#include' J2 {0 m2 z0 o) [4 o
using namespace ontio;class hello:public contract {
. d3 ]% B1 F; Ipublic:6 |/ g* z. U; L7 B% M' W8 L& O, [
using contract::contract:
# h9 ~9 r. H+ F7 x# jvoid sayHello(){
3 s: H5 t2 w+ b/ E& a2 s$ fprintf("hello world!");
. D$ M5 u( [# {" F' \  A3 ~0 J}
/ d# Q& c0 o. q9 I/ p; M};
/ j1 m' f* k4 \  TONTIO_DISPATCH(hello, (sayHello));
8 [0 f$ l5 h7 e2 H! v9 w1.1 合约入口- E4 Q4 n1 v2 E6 W" d, F
Ontology Wasm CDT 编译器已经对入口和参数解析进行了封装,所以开发者不需要重新定义入口方法。接下来是定义合约的对外接口,这是智能合约对外提供服务的方法。2 N! Q9 `5 K8 t: p& r9 A0 \
ONTIO_DISPATCH(hello, (sayHello));' c( d$ T* {3 J' M
在上面的例子中, 我们暂时只支持 sayHello 这个方法:
9 F. V/ W  X7 J5 o) l5 [0 [( rprintf("hello world!");
" F, s4 Z% S1 o$ p# L# S这个“Hello world!”会在节点的日志中以调试信息打印出来。在实际的应用中, printf 只能用作调试的目的, 一个实际的智能合约,需要实现更多更复杂的功能。
/ P/ @6 Z$ n+ k1.2 智能合约 API
) I3 P: o: \- \Ontology Wasm 提供如下 API 与区块链的底层进行交互:
' f7 g7 a3 M1 f4 e# `6 i) M7 T+ t- E* U$ w- ]
二、红包合约% {( e: z  [6 l
下面我们通过一个更加复杂的例子来演示如何通过这些 API 来开发一个完整的 Wasm 智能合约。
" |5 l' B. c" L很多情况下我们都会通过各种 App,如微信等聊天工具发红包。我们可以给朋友发送红包,也可以抢其他人发送的红包,收到的钱会记入到个人微信账户中。
4 N/ |$ h+ G* ^% H5 |& V类似于微信的流程,我们将尝试创建一个智能合约。用户使用该合约,可以发送 ONT,ONG 或者是标准的 OEP-4的 Token 资产红包给他的朋友们,而朋友们抢到的红包可以直接转入到他们的钱包账户中。- ^2 j5 O. p0 m
2.1 创建合约: m  I1 p2 b' f
首先,我们需要新建合约的源文件,暂且命名为 redEnvelope.cpp。这个合约我们需要三个接口:4 f+ K- g. z6 d+ _3 h
createRedEnvelope: 创建红包
, a  T, D7 t' }queryEnvelope: 查询红包信息- ?( x1 F' Q) i# L" t8 ]& s: c# Z
claimEnvelope: 抢红包
! s' A# ]4 Q" c: X#include1 A4 e  S3 ~6 [$ V$ A$ u; F
using namespace ontio;
! S/ F. K2 H/ {class redEnvelope: public contract{
  o% _4 s  X. S( _( t}
" X' b2 z: \% O) P- ?2 xONTIO_DISPATCH(redEnvelope, (createRedEnvelope)(queryEnvelope)(claimEnvelope));
) _  Z* P5 E, Y5 ~. A* @( h5 G我们需要在存储中保存一些关键的数据。在智能合约中, 数据以 KV 的形式保存在该合约的上下文空间中,这些数据的 KEY 需要设置前缀以便于后面的查询。下面定义了三个不同的前缀供使用:
6 T( h7 L: s& h; X. S# U# ?9 m# hstd::string rePrefix = "RE_PREFIX_";
" q5 g( m& F) ]5 r" u5 C# Istd::string sentPrefix = "SENT_COUNT_";% m, p7 k$ `' @, a
std::string claimPrefix = "CLAIM_PREFIX_"
1 e/ ]/ D3 e: U% }) ]' ~; v因为我们的合约支持 ONT 和 ONG 这两种 Ontology 的原生资产, 我们可以预先定义好这两种资产的合约地址。不同于标准的智能合约, Ontology 原生合约(native contract)的合约地址是固定的,而不是根据合约代码的 hash 计算而来的。
% j2 j( O9 j; Zaddress ONTAddress = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1};
1 q9 w: ?; m! x; o* F8 raddress ONGAddress = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2};' t4 x( W  P- o
我们需要在合约中保存红包的信息, 如红包的资产信息(token 的合约地址, 红包的总金额, 红包的个数等等)
+ z. ]" u3 S8 c3 Fstruct receiveRecord{4 V% ^+ V: q) H4 I( }9 e6 M8 {
address account; //用户地址8 I6 a1 c6 H: Q" f8 K2 ]2 u
asset amount; //抢到的金额
* o! @4 V# o) g4 J* b2 rONTLIB_SERIALIZE(receiveRecord,(account)(amount))
2 B/ B" u  n( t+ W" i# j  C};
8 J5 q. L6 v: A$ Zstruct EnvelopeStruct{: ]+ X+ F7 W" f% f0 ^& v0 C
address tokenAddress; //资产token的地址9 Y; h$ ^" U9 Z
asset totalAmount; //红包总金额
: X( a9 M! Z" {/ ^2 Fasset totalPackageCount; //红包总数6 _0 U, I9 {! d: l7 T
asset remainAmount; //当前剩余的金额5 O' L. c4 m0 i2 g7 s2 M) F' O0 L% Z
asset remainPackageCount; //当前剩余的红包数; s0 d6 z6 J- R0 s" p
std::vector records; //已经抢完的记录! Q2 @3 _- b0 b0 U$ Z- m
ONTLIB_SERIALIZE( EnvelopeStruct, (tokenAddress)(totalAmount)(totalPackageCount)(remainAmount)(remainPackageCount)(records) )
: F( d& m, n, Q# K& t};& i/ g0 g: v6 F, G! m& V! i4 |. k
其中,
- \9 V+ k8 S) N3 j6 BONTLIB_SERIALIZE(receiveRecord,(account)(amount))! L( K7 j9 B5 H0 R0 n! \2 N3 k
是由 Ontology Wasm CDT 定义的宏操作,用于在将 struct 存储前进行序列化的操作。
5 _* h3 s5 [1 x5 K( `6 n2.2 创建红包
$ {, T  @% P' P' C准备工作差不多了,下面我们开始开发具体的接口逻辑。
9 @, L9 ^% T4 d, z! m! a创建红包需要指定创建者地址, 红包数量, 红包金额和资产的合约地址:
" Y% }4 v  {$ I2 l
* U3 }9 F3 a+ k/ q* O+ i' Jbool createRedEnvelope(address owner,asset packcount, asset amount,address tokenAddr ){
2 L, v0 B8 ], l, sreturn true;
9 P9 p, i0 n4 ~5 ^9 _" m}# K) F: p2 T9 {+ v8 E& ?4 }  I" W
检查是否有创建者的签名, 否则交易回滚退出:7 U0 \# \5 Y3 u3 M
8 W( Q' S  l( S* C+ s; y  ?
ontio_assert(check_witness(owner),"checkwitness failed");
/ y! C% a6 C& _& ^6 Y6 J1 y" M# ZNOTE: ontio_assert(expr, errormsg):当 expr 为 false 时, 抛出异常并退出。1 [! A3 Z, i$ p; d  o% x' h
如果红包资产是 ONT,由于 ONT 的不可分割性(最小为1个 ONT), 红包的金额要大于或等于红包的数量,保证每个红包最少有1个 ONT:+ W: v4 f3 s, V0 p& n. U6 h& W

# T7 I+ a" m3 H* xif (isONTToken(tokenAddr)){
* `, N4 w1 k6 F/ ~5 `ontio_assert(amount >= packcount,"ont amount should greater than packcount");
( A& t0 v8 d- q- ^3 H4 R9 p0 g}
% e/ ^4 T- O3 ^+ E' ^对于每个红包的创建者,我们需要记录一下他发送红包的总数量:
" U* G7 N# f. k. [: f- R# S- x
1 o7 n1 G2 i5 U. b, jkey sentkey = make_key(sentPrefix,owner.tohexstring());" B, G9 V( O( t5 n% Q
asset sentcount = 0;
% N5 w* [+ D; u. x9 {storage_get(sentkey,sentcount);
- b+ v* Y5 g% k+ ?4 jsentcount += 1;
, f6 {% m2 n& Y( fstorage_put(sentkey,sentcount);7 E) Q, j" ~0 u+ E. ^0 k
生成红包 hash, 这个 hash 就是之后标识这个红包的唯一 ID:; u& D/ y4 Z8 q0 A2 ]
+ E, p9 `7 G9 T* I/ D
H256 hash ;- n, g: t9 J! k9 Y' c
hash256(make_key(owner,sentcount),hash) ;
* S7 V2 l& p9 I% |9 D8 R6 P/ c7 Wkey rekey = make_key(rePrefix,hash256ToHexstring(hash));
1 S6 K" S& `: N% W' m$ l根据 token 资产的类型,将资产转入合约中,self_address()可以取得当前执行的合约地址, 我们根据用户输入的 token 类型,将指定数量的 token 转入合约:5 ~( w  [% O& r; D
  n0 L* G& C* c
address selfaddr = self_address();8 ^+ c- l# B, v8 R1 o4 _. F
if (isONTToken(tokenAddr)){/ d4 C+ r" M# H: q* d
bool result = ont::transfer(owner,selfaddr ,amount);
4 W7 O5 e; y3 p3 F1 ~: @% Uontio_assert(result,"transfer native token failed!");
  K! }& m7 c0 m}else if (isONGToken(tokenAddr)){
; I* ^6 N$ A' `$ hbool result = ong::transfer(owner,selfaddr ,amount);+ q% F" B# o# n& q+ x
ontio_assert(result,"transfer native token failed!");
* J% {! _5 Y7 F7 j; {2 T}else{
8 [! W& b, I( |( N% {8 F3 ^std::vector params = pack(std::string("transfer"),owner,selfaddr,amount);
& Q3 H  a2 c( ~6 pbool res;" P4 [4 F' y, [; \' ~( @" h% s
call_contract(tokenAddr,params, res );/ @' c$ t; Y) `, H
ontio_assert(res,"transfer oep4 token failed!");
: d/ F% c, `9 q& u  @}
6 M0 ~3 y$ c: N- C# lNOTE 1:对于 ONT 和 ONG 这两种原生资产, Ontology Wasm CDT 提供了ont::transfer API 进行转账操作;而 OEP-4类的资产,需要按照普通的跨合约调用方法来转账。8 U, A& v1 x; U
NOTE 2:和普通的钱包地址一样, 合约地址也可以接受任意类型的资产。但是合约地址是由合约编译后的二进制代码 hash 产生的,所以没有对应的私钥,也就无法随意操作合约中的资产,如果你没有在合约中设置对资产的操作,就意味着你将无法控制这部分资产。% J! A) F$ z, d- a& I8 Z# r& }+ ~
将合约的信息保存在存储中:* @7 c, ^4 k4 _7 r! n  f! ?0 {

+ n/ U) W3 J; Y0 c" G- ^struct EnvelopeStruct es ;
# x( i$ j( u/ y0 ]; k! [6 u( zes.tokenAddress = tokenAddr;
1 T* r2 x+ `. ?7 f4 S$ `es.totalAmount = amount;
9 d2 R2 \6 t# Q2 u# Ees.totalPackageCount = packcount;
# b; }( j. T; }( Q* I/ y# @es.remainAmount = amount;, l' x) d1 U5 z. y% R7 v# m' L5 b8 v$ b
es.remainPackageCount = packcount;
# x2 z' L/ M$ V5 |es.records = {};
9 ?3 B9 `3 }+ E0 Ystorage_put(rekey, es);
& T- E' k+ a7 q- E$ L( y- s3 a发送创建红包的事件。对于智能合约的调用是一个异步的过程,合约会在执行成功后发送一个事件来通知客户端执行结果,这个事件的格式可以由合约的编写者来指定。0 c4 Y* ]  A' x

5 H. L3 d3 U- y  ?5 {4 X( z( Lchar buffer [100];+ [/ r. B1 K9 q# h0 y
sprintf(buffer, "{\"states\":[\"%s\", \"%s\", \"%s\"]}","createEnvelope",owner.tohexstring().c_str(),hash256ToHexstring(hash).c_str());/ w2 ^) H: L0 {' ]& a
notify(buffer);3 n& n9 }2 @* l( e7 h( E
return true;4 n5 v9 E/ @! [
一个简单的红包就创建完成了, 下一步我们需要实现如何查询这个红包的信息.
, w  Q1 D6 ?+ i% w2.3 查询红包
; k  G7 V6 d' k查询红包的逻辑非常简单, 只需要将存储中的红包信息取出并格式化返回即可:
$ _' Z* A" G* R! Hstd::string queryEnvelope(std::string hash){  \4 f  H& }4 w
key rekey = make_key(rePrefix, hash);) ~  J4 Q: x3 d. @4 S
struct EnvelopeStruct es;
. |( a8 m3 S8 M  W% a, |3 Mstorage_get(rekey, es);) ]2 M* X( ~  b, Y" ]
return formatEnvelope(es);% O$ Q) L4 M0 [, F
}
1 y3 `4 P4 ]+ b! @  E+ iNOTE:对于智能合约的只读操作(例如查询), 可以通过预执行(pre-exec)来读取结果。不同于普通的合约调用,预执行不需要钱包的签名,同时也就无需花费 ONG。最后,其他用户可以根据 hash(红包的 ID)来领取(抢)这个红包了。! a% E; W8 w! s: `
2.4 领取红包
7 J# `! P. f5 \3 ~# P/ @9 f我们已经把资产成功地转入到智能合约中了, 接下来就可以把这个红包的 ID 发送给你的朋友们让他们去抢红包了。
0 W$ S) F4 n, c& c$ e4 ~. i领取红包需要输入领取人的账户和红包的hash:
% f) W3 U4 o" a
8 S2 G4 ~! L' g* vbool claimEnvelope(address account, std::string hash){, D5 |9 c* B$ Z0 Z
return true;
+ h8 K  c$ }$ ^; H, I$ q$ t}: m3 c1 K6 e' f& I. T) ^* k
同样, 我们需要验证领取账户的签名, 不允许替其他人抢红包, 而且每个账户每个红包只能抢一次:* u0 x& I# _/ W7 s( ]1 E
' k( ^6 B# u& B
ontio_assert(check_witness(account),"checkwitness failed");
  w! ]7 L6 P4 {3 }) W) x' T) wkey claimkey = make_key(claimPrefix,hash,account);
- d. r+ U7 ~+ [$ ~0 uasset claimed = 0 ;
* P9 v0 G5 N: h1 ~storage_get(claimkey,claimed);7 k, M" t9 J) b$ n8 w5 F3 Y
ontio_assert(claimed == 0,"you have claimed this Envelope!");9 f- [- Q' r9 e$ g/ j, K
按照 hash 从存储中取出红包的信息, 判断这个红包是否没有被抢完:
( x& A0 t; h6 c, Z$ s% ?& l3 Q  G7 l& N, z. N; X5 w
key rekey = make_key(rePrefix,hash);
* [  G- T* n8 |5 T2 i9 k+ Astruct EnvelopeStruct es;
: P8 |9 h9 ~1 G$ Rstorage_get(rekey,es);
( |2 Q& p% u. r! K- j/ Montio_assert(es.remainAmount > 0, "the Envelope has been claimed over!");
: S9 U3 L" z0 Oontio_assert(es.remainPackageCount > 0, "the Envelope has been claimed over!");3 k5 a& j& c8 a/ M3 q# s
新建一条领取的记录:
+ D  d  o9 E  G
7 x+ h+ O+ b# I1 ~* tstruct receiveRecord record ;
6 x: t3 ?( H" jrecord.account = account;
% ]& S! l+ G' t% hasset claimAmount = 0;4 r7 k6 H( f* C6 O
计算本次领取红包的资产数量。如果是最后一个红包, 数量为剩余的金额, 否则根据当前区块 hash 计算随机数,确定本次领取的数量, 并更新红包信息:/ U" ]' C0 d+ a4 A3 M/ E$ _8 R

' ~- L1 q* a/ a8 eif (es.remainPackageCount == 1){
  {: N& h6 R8 o  P* I1 ^! `/ pclaimAmount = es.remainAmount;
9 d* O# o: N# X/ l3 y! t% y" Krecord.amount = claimAmount;
& d+ _5 S3 J9 T; I( g/ U* X}else{
+ ?+ Y9 n; n6 S/ AH256 random = current_blockhash() ;5 d& V' U9 i; F- ^
char part[8];9 C" V, F# W) f/ o+ \
memcpy(part,&random,8);+ S) s$ i2 \; R2 \! w* X& u/ _4 c( ^
uint64_t random_num = *(uint64_t*)part;
, F- ^3 \: Q. \% puint32_t percent = random_num % 100 + 1;
) n  I3 S. f5 s& n3 m% zclaimAmount = es.remainAmount * percent / 100;) T8 K, a. T" h. v
//ont case
0 o0 Q6 D) e& L% Y; ~0 _if (claimAmount == 0){: h* [/ W, l* j. O! }- k! V( V4 |
claimAmount = 1;
! Z7 [, o( r; H/ K& M}else if(isONTToken(es.tokenAddress)){, u) _" U& S/ G$ \
if ( (es.remainAmount - claimAmount) 根据计算结果, 将对应资产从合约中转到领取的账户:
1 m2 t$ `7 x8 q  M$ p% @! [
( J$ b( w, x. W1 u( l& c$ @address selfaddr = self_address();
. C3 W  o9 Z% Y1 W) ?; T0 r5 @/ aif (isONTToken(es.tokenAddress)){7 t  e! B8 G+ Z3 K5 X( b
bool result = ont::transfer(selfaddr,account ,claimAmount);0 Q7 [) n9 J' q$ G
ontio_assert(result,"transfer ont token failed!");: l  X$ Y" C/ s* a, m) V
} else if (isONGToken(es.tokenAddress)){
) z; u1 `9 C7 q2 }$ Y+ A1 E0 Qbool result = ong::transfer(selfaddr,account ,claimAmount);
; F  S9 v, t& `7 V4 G$ u3 M; hontio_assert(result,"transfer ong token failed!");( P% g% \- l3 {: Y+ g+ r
} else{
- s; ]) F' H$ O, B. Ostd::vector params = pack(std::string("transfer"),selfaddr,account,claimAmount);5 a8 E$ `2 l' a1 l$ p* m9 S& j
bool res = false;) _( y" l$ w. r' _" t8 ~
call_contract(es.tokenAddress,params, res );
$ Y0 ^1 k2 E& Y0 G' Z2 tontio_assert(res,"transfer oep4 token failed!");
* v& H5 _+ s1 C( s$ R}- W9 C7 r* W) Y4 m8 U2 H
记录领取的信息, 将更新后的红包信息写回存储并发送通知事件:
; n7 C" G, ]0 L  E4 E$ ~" ^% w2 S  O& d. h9 s) S$ C
storage_put(claimkey,claimAmount);
# y2 N5 {9 U3 L- Q0 f! _9 Gstorage_put(rekey,es);
" Q' J8 ^! _# ]* ?' N- ~char buffer [100];
0 g* M& I  t6 B/ j4 D. `8 rstd::sprintf(buffer, "{\"states\":[\"%s\",\"%s\",\"%s\",\"%lld\"]}","claimEnvelope",hash.c_str(),account.tohexstring().c_str(),claimAmount);
- W$ X2 _4 F: K3 L* ~notify(buffer);! K" w. t2 t2 X, t/ F0 A
return true;
' s8 H- F1 q5 Y如前面所说,这个合约只能通过 claimEnvelope 这个接口将资产转出合约。所以,合约中的资产是安全的,任何人都无法随意的取走里面的资产。% X% p5 q6 r& j9 }! K' c
至此, 一个简单的红包合约逻辑完成, 完整的合约代码如下:https://github.com/JasonZhouPW/pubdocs/blob/master/redEnvelope.cpp
( h- @8 y2 B- C% `3 L5 n2.5 合约测试" ]* B3 b( b3 @7 {2 ]7 V& d
合约测试可以有两种方法:. }3 j& N: b4 j
使用 CLI
; U1 M/ B6 J  q请参考:https://github.com/ontio/ontology-wasm-cdt-cpp/blob/master/How_To_Run_ontologywasm_node.md, C$ z! p  C$ O" }$ ^
使用 Golang SDK
4 A9 D& k6 g! C请参考:https://github.com/ontio/ontology-wasm-cdt-cpp/blob/master/example/other/main.go9 `( d( [0 S# \. z
三、总结# n4 O$ G8 y8 n8 x; L8 O; S; m5 {
本示例只是为了展示如何编写一个完整的 Wasm 智能合约, 如何通过调用 API 和底层的区块链进行交互。如果要作为正式的产品, 还需要解决红包的隐私问题: 所有人都可以通过监控合约的事件来取得红包的 hash, 意味着每个人都可以抢这个红包。一种比较简单的解决方法,就是在创建红包时指定哪些账户能够领取。如果有兴趣, 您也可以尝试修改测试一下。0 R4 \( ?& ?# p" C3 g/ J
我们欢迎更多的 Wasm 技术爱好者加入本体开发社区,共同打造技术生态。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

放弃六月们 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    8