Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

放弃六月们
105 0 0
Ontology Wasm 自从上线测试网以来便受到了社区开发人员的极大关注。 Wasm 的上线将使得业务逻辑复杂的 dApp 合约上链成本降低,极大丰富 dApp 生态。在进行 Wasm 合约开发时,开发者不仅可以使用 Rust,还可以使用 C++ 作为合约开发语言。本期我们将通过两个简单的示例来示范如何使用 C++ 进行Wasm 合约开发。" T# Q; Y" p; m$ Y9 h/ {& d* i
一、Hello World
5 @# Z3 Q/ }, P+ h: g: V按照惯例,我们还是从一个 Hello world 开始
" n9 O, k0 y( |' B' Q4 `9 ?$ {#include#include
. O( \# V+ K: ?" E/ C7 q* R9 v* Yusing namespace ontio;class hello:public contract {
0 s2 `: e3 Y6 {; J; Hpublic:) c$ m/ W8 _" }* A) Q- A
using contract::contract:& k8 {6 q8 V6 D2 {9 M, S/ B% N8 D
void sayHello(){' `2 o" v/ A  V1 n; {
printf("hello world!");
3 i: q5 L' s9 c' h6 o5 G5 Z}
7 \, y. S) v. N+ W, @( }; Q};2 |+ R" |5 T. u! ^( L8 h1 `4 K
ONTIO_DISPATCH(hello, (sayHello));
$ z& U# L# I/ t" k- p. K. `1.1 合约入口
- W2 d% D, s9 ?8 X; d4 Y6 c# bOntology Wasm CDT 编译器已经对入口和参数解析进行了封装,所以开发者不需要重新定义入口方法。接下来是定义合约的对外接口,这是智能合约对外提供服务的方法。% X/ F2 r: }* Z9 F6 w  e1 L8 L
ONTIO_DISPATCH(hello, (sayHello));
6 Y! ]! S- Z3 {5 {  ?# Y5 P在上面的例子中, 我们暂时只支持 sayHello 这个方法:
9 a" b) |0 k% N2 G9 e0 v4 x. lprintf("hello world!");7 |, G" s& q( R5 z  ]+ Y  H; ~
这个“Hello world!”会在节点的日志中以调试信息打印出来。在实际的应用中, printf 只能用作调试的目的, 一个实际的智能合约,需要实现更多更复杂的功能。3 r, J& k2 ?; J6 o8 m
1.2 智能合约 API: `5 E# B& S4 m1 d0 a3 H# m& O; x1 l
Ontology Wasm 提供如下 API 与区块链的底层进行交互:: a5 ^+ T+ @! K- F

* ?0 U" ]. V% @# T* y' `二、红包合约8 M' Z* f$ {: M7 H6 I: ?3 s% v7 N
下面我们通过一个更加复杂的例子来演示如何通过这些 API 来开发一个完整的 Wasm 智能合约。
: J& Z* H& F2 D  O( {, O很多情况下我们都会通过各种 App,如微信等聊天工具发红包。我们可以给朋友发送红包,也可以抢其他人发送的红包,收到的钱会记入到个人微信账户中。$ ]1 T7 k% h0 q
类似于微信的流程,我们将尝试创建一个智能合约。用户使用该合约,可以发送 ONT,ONG 或者是标准的 OEP-4的 Token 资产红包给他的朋友们,而朋友们抢到的红包可以直接转入到他们的钱包账户中。
3 {: w# F/ W! M" p' o2.1 创建合约
1 X/ y; M  M2 x  P首先,我们需要新建合约的源文件,暂且命名为 redEnvelope.cpp。这个合约我们需要三个接口:
# b( _3 C/ H& [. a7 q5 S) q' n* ucreateRedEnvelope: 创建红包
& G% j1 D. X6 @. FqueryEnvelope: 查询红包信息: j9 w! @0 T* {5 ?6 R0 B$ m. ^
claimEnvelope: 抢红包; L* E. e) l; U. Z9 t
#include
5 K$ n8 D1 b& R$ }- ^$ Vusing namespace ontio;
. F/ v4 T1 E8 ]6 A* ?/ {+ s7 o1 Q0 Dclass redEnvelope: public contract{
; q; H' E2 Z# V}
; a# {( l4 `4 T( P- k2 LONTIO_DISPATCH(redEnvelope, (createRedEnvelope)(queryEnvelope)(claimEnvelope));
) ?0 Z. v9 w9 v7 a& @' g我们需要在存储中保存一些关键的数据。在智能合约中, 数据以 KV 的形式保存在该合约的上下文空间中,这些数据的 KEY 需要设置前缀以便于后面的查询。下面定义了三个不同的前缀供使用:
+ j0 T+ W. ^/ O6 {1 n- R9 ~/ f* Z) Wstd::string rePrefix = "RE_PREFIX_";, @; y. s! L, [& X2 [
std::string sentPrefix = "SENT_COUNT_";
  B- m, X9 v* C; Z  n# e' Qstd::string claimPrefix = "CLAIM_PREFIX_"8 U: u" d' M2 B8 o8 u6 |/ b$ u
因为我们的合约支持 ONT 和 ONG 这两种 Ontology 的原生资产, 我们可以预先定义好这两种资产的合约地址。不同于标准的智能合约, Ontology 原生合约(native contract)的合约地址是固定的,而不是根据合约代码的 hash 计算而来的。9 T! V1 e7 Q- [
address ONTAddress = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1};6 ^1 h- }: [* c- ?) X( `% f
address ONGAddress = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2};
5 r# w* z- t4 N, b" A我们需要在合约中保存红包的信息, 如红包的资产信息(token 的合约地址, 红包的总金额, 红包的个数等等)1 u2 h3 w1 G4 V5 J( v  |! |
struct receiveRecord{
& B) `! n/ G2 m! o1 b% zaddress account; //用户地址
5 H8 S- f' e& E' I/ sasset amount; //抢到的金额% z0 v. v( j: ?7 h
ONTLIB_SERIALIZE(receiveRecord,(account)(amount))( k8 W- v+ U# @3 ~+ ?7 o8 o
};
2 U) i* U* S5 I5 t4 {3 {. j0 m& estruct EnvelopeStruct{+ e6 Q) h% h, M) W3 L
address tokenAddress; //资产token的地址
, k% h% z3 `/ k0 y% }- a7 b" xasset totalAmount; //红包总金额
! }, X, s3 G# ~5 ~: b. P1 d8 Kasset totalPackageCount; //红包总数) F1 O& b1 h' o7 r; f
asset remainAmount; //当前剩余的金额
! U! q% C: x1 x% B* G+ Q2 wasset remainPackageCount; //当前剩余的红包数
! ]: Q2 r) s  Q" f  N# gstd::vector records; //已经抢完的记录5 [8 o1 i+ O& {. C
ONTLIB_SERIALIZE( EnvelopeStruct, (tokenAddress)(totalAmount)(totalPackageCount)(remainAmount)(remainPackageCount)(records) )
* y* n! i  t! T. ?, y};
* g! t. W7 P, h( U其中,) q4 J, m$ j; \# ?7 h. z( d$ t. [
ONTLIB_SERIALIZE(receiveRecord,(account)(amount))
$ z8 ?; q, H- y; @1 s, U是由 Ontology Wasm CDT 定义的宏操作,用于在将 struct 存储前进行序列化的操作。6 U4 U! `: R. Z2 p* k& J! ?+ a4 l$ A
2.2 创建红包, T- m2 b, h5 [1 b
准备工作差不多了,下面我们开始开发具体的接口逻辑。
+ s' r3 q* w$ P4 P" B创建红包需要指定创建者地址, 红包数量, 红包金额和资产的合约地址:
  G3 ?2 v. K0 ]6 Q' v* n/ K8 f* y4 }' N: O+ K5 n
bool createRedEnvelope(address owner,asset packcount, asset amount,address tokenAddr ){
4 h" i- p6 a! l" greturn true;
. s1 S, `- Z# x( }" c; r, d. |}
+ Z% Z! }# Q0 ~% E" Z. w& H! C6 Q( {检查是否有创建者的签名, 否则交易回滚退出:
% ?6 m/ N+ u( K/ h1 C  D6 f
; I; p$ h0 l3 u$ [& Nontio_assert(check_witness(owner),"checkwitness failed");
- n6 }  p5 m1 N- m4 d! g' ZNOTE: ontio_assert(expr, errormsg):当 expr 为 false 时, 抛出异常并退出。
' j, K0 M; u- K) `0 y% T如果红包资产是 ONT,由于 ONT 的不可分割性(最小为1个 ONT), 红包的金额要大于或等于红包的数量,保证每个红包最少有1个 ONT:
& [8 L/ b  `! E  g) X, g/ |* f, I; I7 x7 v
if (isONTToken(tokenAddr)){
* @9 _- D4 V, w7 E# a1 Montio_assert(amount >= packcount,"ont amount should greater than packcount");
5 q, t$ |4 C& A0 X2 Z5 t6 p/ u& Q/ x}( l' V( p8 h6 U; f
对于每个红包的创建者,我们需要记录一下他发送红包的总数量:
# v1 c! S) q: {4 I5 U% X% e0 J* f7 B- `9 Y! w" {& R8 k! o
key sentkey = make_key(sentPrefix,owner.tohexstring());! r0 J+ q/ {9 Q3 G5 Q
asset sentcount = 0;6 o9 K& B! L! E/ e5 \
storage_get(sentkey,sentcount);' Z0 F% B/ `! d7 W% a/ Y/ a5 V
sentcount += 1;
! f! s+ c" w) g' P4 E% rstorage_put(sentkey,sentcount);( N* h( e( l( f8 ^0 D$ R7 G* O
生成红包 hash, 这个 hash 就是之后标识这个红包的唯一 ID:8 d  U! a- ?# S- V" p" w/ Z. W
) [% L- B0 G  W4 W8 S- a9 d
H256 hash ;" M+ Y- B8 L6 ]& ?" j
hash256(make_key(owner,sentcount),hash) ;
# P7 i8 F3 H8 B$ n7 U# P- zkey rekey = make_key(rePrefix,hash256ToHexstring(hash));! @' _6 z& w& k* Z5 `% q
根据 token 资产的类型,将资产转入合约中,self_address()可以取得当前执行的合约地址, 我们根据用户输入的 token 类型,将指定数量的 token 转入合约:5 N$ P& {1 f! y
  ]3 h& T9 {" \8 \
address selfaddr = self_address();
: ^  e3 _5 ?) \7 aif (isONTToken(tokenAddr)){
7 i" Z  l8 v- m5 b0 L, t: |! Zbool result = ont::transfer(owner,selfaddr ,amount);1 S" V* z. m. z5 ^; _8 g0 n
ontio_assert(result,"transfer native token failed!");
) T! _  k/ h4 _( G8 t}else if (isONGToken(tokenAddr)){
6 H6 q5 K+ [% Q- D' Bbool result = ong::transfer(owner,selfaddr ,amount);( h/ }. {( F* z/ p0 \3 N- t3 Q
ontio_assert(result,"transfer native token failed!");4 a  L* A% f2 ?& z* g9 g
}else{
9 l" j$ w. j8 T6 Q; \" A7 O, `std::vector params = pack(std::string("transfer"),owner,selfaddr,amount);; S9 v/ G. d4 R: y7 ]) U
bool res;. Q8 s. H! G' u. L9 b
call_contract(tokenAddr,params, res );
5 h, z  c. b' Yontio_assert(res,"transfer oep4 token failed!");4 Y: `/ i5 N0 q4 z/ S
}
$ z1 w9 B' X' w& s: m. |  {NOTE 1:对于 ONT 和 ONG 这两种原生资产, Ontology Wasm CDT 提供了ont::transfer API 进行转账操作;而 OEP-4类的资产,需要按照普通的跨合约调用方法来转账。& U" p8 F' C8 Q& [, @
NOTE 2:和普通的钱包地址一样, 合约地址也可以接受任意类型的资产。但是合约地址是由合约编译后的二进制代码 hash 产生的,所以没有对应的私钥,也就无法随意操作合约中的资产,如果你没有在合约中设置对资产的操作,就意味着你将无法控制这部分资产。& ^& u' q- J. m' L6 k7 V
将合约的信息保存在存储中:
! p! y7 A4 A: p+ F6 y  q3 z, P
( q2 k: e" L! @  z' d1 l$ mstruct EnvelopeStruct es ;
% E4 ?+ u2 I% N9 Oes.tokenAddress = tokenAddr;
' U/ c( @. |' z- Pes.totalAmount = amount;
3 w8 X8 {; s5 d+ F3 ]/ x# y! hes.totalPackageCount = packcount;
/ a; \/ ]. D* r3 E/ i& G% nes.remainAmount = amount;
# x. N9 P* r: B1 wes.remainPackageCount = packcount;
7 \7 x. q$ I4 {/ O* I* v, mes.records = {};
9 l6 V7 }1 D# C4 sstorage_put(rekey, es);! t/ P  |3 q' a" X- @: G0 k
发送创建红包的事件。对于智能合约的调用是一个异步的过程,合约会在执行成功后发送一个事件来通知客户端执行结果,这个事件的格式可以由合约的编写者来指定。
& O% C; N/ ^9 g2 l- ~! }0 ^6 n/ j  H8 i4 w
char buffer [100];
' O4 N4 q8 i: _$ e2 W% esprintf(buffer, "{\"states\":[\"%s\", \"%s\", \"%s\"]}","createEnvelope",owner.tohexstring().c_str(),hash256ToHexstring(hash).c_str());
! c2 w; |4 [, l" Z, E& P8 W. [notify(buffer);
4 O$ P. n0 B8 ?+ ?: j- y; l  preturn true;
8 g! X* A# S3 T4 R一个简单的红包就创建完成了, 下一步我们需要实现如何查询这个红包的信息.3 v7 r/ a) K1 D
2.3 查询红包
& O  Q" \0 P6 |  L+ u% g查询红包的逻辑非常简单, 只需要将存储中的红包信息取出并格式化返回即可:
, r/ A, n; C4 R% z8 N& h" N5 v( bstd::string queryEnvelope(std::string hash){; J7 A' ?- S) ^% c
key rekey = make_key(rePrefix, hash);
1 h1 K( n; F, |7 }# c( Tstruct EnvelopeStruct es;
. y& ]9 Y2 Z3 c# W4 |9 X" Pstorage_get(rekey, es);
: }3 U& A" ^' G# N+ freturn formatEnvelope(es);
1 k* ]# |2 P3 Y9 Q  o}' Y' ?# m8 l) T. B9 m* K! b3 n
NOTE:对于智能合约的只读操作(例如查询), 可以通过预执行(pre-exec)来读取结果。不同于普通的合约调用,预执行不需要钱包的签名,同时也就无需花费 ONG。最后,其他用户可以根据 hash(红包的 ID)来领取(抢)这个红包了。
' a. p5 x. `7 e8 D. }: C, s2.4 领取红包" }) w# W! }8 G% v& Z
我们已经把资产成功地转入到智能合约中了, 接下来就可以把这个红包的 ID 发送给你的朋友们让他们去抢红包了。9 i/ ^, l- `/ Q2 b6 i/ Z; J2 U
领取红包需要输入领取人的账户和红包的hash:6 p) z, @* n* e+ J& ]" P
0 |0 z0 {/ i( Z, ]. j2 K
bool claimEnvelope(address account, std::string hash){
7 \; L' t4 M4 v2 I, v0 c3 I+ yreturn true;
/ k8 @5 r0 Y/ h: o4 R3 a# R) g}; `% O0 M7 e+ ~" n) t
同样, 我们需要验证领取账户的签名, 不允许替其他人抢红包, 而且每个账户每个红包只能抢一次:+ p% l- C% D' o* K- a2 d& V, I% j

% Q- L% X. l/ X0 I7 ?' C! qontio_assert(check_witness(account),"checkwitness failed");
/ o% ?+ @9 ^# }3 ]6 k8 y# nkey claimkey = make_key(claimPrefix,hash,account);: y3 E) [, m' Z+ p
asset claimed = 0 ;( u% ~0 {  ]$ f) c
storage_get(claimkey,claimed);& o9 N$ j7 r% R" h& p5 f7 C
ontio_assert(claimed == 0,"you have claimed this Envelope!");
: p5 i1 Q, ~* V7 c  t6 o* V  w按照 hash 从存储中取出红包的信息, 判断这个红包是否没有被抢完:. m" H1 B1 w) E0 K8 v5 c
. W; w$ @( y* F4 Y* q. o
key rekey = make_key(rePrefix,hash);% B  E. o5 {( }0 E2 N/ R
struct EnvelopeStruct es;
0 f: r6 y7 q- `  gstorage_get(rekey,es);/ i4 q5 H' p' K" L+ \% x" H" E
ontio_assert(es.remainAmount > 0, "the Envelope has been claimed over!");1 h* j4 }$ I% R3 G+ x+ J/ f+ ?
ontio_assert(es.remainPackageCount > 0, "the Envelope has been claimed over!");5 ]6 @6 p" h: S& _8 `1 |
新建一条领取的记录:( {' v- u! N3 o: A6 [

$ k( g+ `0 x+ l' X6 c! {$ rstruct receiveRecord record ;  \% u3 ^6 N# R% s6 h! q8 U  s! K, a
record.account = account;8 Q! ^! v* W6 s' {/ t* S
asset claimAmount = 0;6 A" X# ]8 X7 L' r- K
计算本次领取红包的资产数量。如果是最后一个红包, 数量为剩余的金额, 否则根据当前区块 hash 计算随机数,确定本次领取的数量, 并更新红包信息:
/ {0 P7 {9 g, ~/ y; A- Y# w: \6 h7 H. @
if (es.remainPackageCount == 1){
0 J6 k' b/ J' h8 M7 C7 ]# W1 RclaimAmount = es.remainAmount;
) g- S: W8 t* H+ J$ ]9 a# |# Drecord.amount = claimAmount;
5 M" |6 d! Z9 N) L$ x  \}else{+ r( E: ~$ t/ d  R
H256 random = current_blockhash() ;' U8 y  O5 D1 k4 J6 V
char part[8];# ?8 a* p  T: K, [* p: _  }& l6 g
memcpy(part,&random,8);% C3 H! I  c* s9 L5 |
uint64_t random_num = *(uint64_t*)part;
7 @5 i! O4 l# c0 W& {3 o1 suint32_t percent = random_num % 100 + 1;7 r9 a# a8 J0 Y( J
claimAmount = es.remainAmount * percent / 100;9 F3 V2 T! b9 X# L$ e
//ont case% C, u# J7 m( y) Y! L
if (claimAmount == 0){
% y* t( T" n2 D3 C  ^5 }) }claimAmount = 1;! z. P3 b6 g1 Z& X3 G) k  K! t
}else if(isONTToken(es.tokenAddress)){. `  t' o0 }8 z$ [8 s$ Y3 ~
if ( (es.remainAmount - claimAmount) 根据计算结果, 将对应资产从合约中转到领取的账户:7 w; C7 ~& o5 o; N  T1 L
" ~  Q0 c1 q: z9 J4 H/ o
address selfaddr = self_address();
: }4 j. q- R3 D* j6 S; bif (isONTToken(es.tokenAddress)){$ l, |+ h0 b$ P1 d
bool result = ont::transfer(selfaddr,account ,claimAmount);- l2 Y* S: @; f
ontio_assert(result,"transfer ont token failed!");0 `5 ]) V, G, Y0 i8 ^  |& v
} else if (isONGToken(es.tokenAddress)){& m7 V! [4 i; _3 {* X& r
bool result = ong::transfer(selfaddr,account ,claimAmount);7 H& J( `% S" D. h" I
ontio_assert(result,"transfer ong token failed!");! P7 ]/ T9 w& A% g  _
} else{
  d: U$ s" R+ I7 u- N$ N2 ?' tstd::vector params = pack(std::string("transfer"),selfaddr,account,claimAmount);
* G7 c% e& i& L/ ^9 [  P; mbool res = false;8 H" w4 k9 Y) B; Q7 K
call_contract(es.tokenAddress,params, res );
. d. g6 [# i0 p6 @9 E# {ontio_assert(res,"transfer oep4 token failed!");
0 j$ ~8 `4 C& X) L5 G+ s}
; K# V" e, ~5 X) w& i1 n) A记录领取的信息, 将更新后的红包信息写回存储并发送通知事件:
" l% S6 {. v( R& N0 v6 I' ]* k6 l0 \
storage_put(claimkey,claimAmount);, j! D" b( o# y5 x% Z3 c' M& v9 T
storage_put(rekey,es);
6 o# n$ [% ~7 ~( r% s1 ?3 j4 i& x! ychar buffer [100];
) w7 I/ ?! z6 U3 [5 J+ nstd::sprintf(buffer, "{\"states\":[\"%s\",\"%s\",\"%s\",\"%lld\"]}","claimEnvelope",hash.c_str(),account.tohexstring().c_str(),claimAmount);7 e+ K8 O4 o$ H! W2 U
notify(buffer);
8 e9 M/ |" G! {return true;
; c) T3 e  f$ c' h; O6 b/ v如前面所说,这个合约只能通过 claimEnvelope 这个接口将资产转出合约。所以,合约中的资产是安全的,任何人都无法随意的取走里面的资产。
3 f; v& h0 m( Q至此, 一个简单的红包合约逻辑完成, 完整的合约代码如下:https://github.com/JasonZhouPW/pubdocs/blob/master/redEnvelope.cpp3 G0 {% c2 H6 ?% m6 r" a/ Y0 N
2.5 合约测试3 H* C# s( _8 N0 O3 Q
合约测试可以有两种方法:
& l/ n5 ^1 v( p9 W# t' \' C使用 CLI
/ W$ G. G- z: Z2 j请参考:https://github.com/ontio/ontology-wasm-cdt-cpp/blob/master/How_To_Run_ontologywasm_node.md4 {; w7 v0 f$ h& b1 n
使用 Golang SDK
9 m9 v/ H8 T; o  h4 T& G! H) Y7 D; z6 `请参考:https://github.com/ontio/ontology-wasm-cdt-cpp/blob/master/example/other/main.go: y6 N+ G0 ^; c* Q2 ~
三、总结1 o' T8 @- D7 r* D. ]
本示例只是为了展示如何编写一个完整的 Wasm 智能合约, 如何通过调用 API 和底层的区块链进行交互。如果要作为正式的产品, 还需要解决红包的隐私问题: 所有人都可以通过监控合约的事件来取得红包的 hash, 意味着每个人都可以抢这个红包。一种比较简单的解决方法,就是在创建红包时指定哪些账户能够领取。如果有兴趣, 您也可以尝试修改测试一下。
9 C! O: A4 C6 _; ]/ U/ z. C我们欢迎更多的 Wasm 技术爱好者加入本体开发社区,共同打造技术生态。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

放弃六月们 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    8