Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

放弃六月们
208 0 0
Ontology Wasm 自从上线测试网以来便受到了社区开发人员的极大关注。 Wasm 的上线将使得业务逻辑复杂的 dApp 合约上链成本降低,极大丰富 dApp 生态。在进行 Wasm 合约开发时,开发者不仅可以使用 Rust,还可以使用 C++ 作为合约开发语言。本期我们将通过两个简单的示例来示范如何使用 C++ 进行Wasm 合约开发。
8 z  s. v+ A) u0 _( m一、Hello World
' X, c+ C, Z! V/ z8 m7 ]4 T按照惯例,我们还是从一个 Hello world 开始8 h7 R& _, @# H5 d1 k1 P5 U
#include#include
: B$ D6 Y- b1 J/ Gusing namespace ontio;class hello:public contract {, R8 r' N* P' P$ D" l2 z
public:3 ?. [# D  ?" |6 ~' G. j( t
using contract::contract:4 q6 V# H3 X- O$ L9 U9 J# M' R1 Q
void sayHello(){
# {) C3 V: w2 z9 Kprintf("hello world!");
' i. l4 P" n  ], b: f}
& o  E9 z- |0 K0 g+ p, ~};7 D" z/ e( f+ P* e  Y8 l
ONTIO_DISPATCH(hello, (sayHello));  N) K, J' ~0 ^. t5 r
1.1 合约入口: _; V' m3 A2 d& _: m% ?
Ontology Wasm CDT 编译器已经对入口和参数解析进行了封装,所以开发者不需要重新定义入口方法。接下来是定义合约的对外接口,这是智能合约对外提供服务的方法。
" o% w( |% ~* k0 T" ]. a: n/ EONTIO_DISPATCH(hello, (sayHello));
& d  O# v0 N5 L3 `  i3 a在上面的例子中, 我们暂时只支持 sayHello 这个方法:$ T5 I' D1 X9 I2 d/ g0 T+ f
printf("hello world!");7 p) \% S- a. k6 a8 [% e4 i
这个“Hello world!”会在节点的日志中以调试信息打印出来。在实际的应用中, printf 只能用作调试的目的, 一个实际的智能合约,需要实现更多更复杂的功能。
4 L" q8 ^/ Q* c8 l1.2 智能合约 API4 z* A# _; V% I% W% o' l4 f
Ontology Wasm 提供如下 API 与区块链的底层进行交互:
9 K7 F! X4 w1 ?5 I; \5 [7 B0 H2 y& N, a8 x2 k
二、红包合约
$ C! f0 b/ O+ `" E: t$ o下面我们通过一个更加复杂的例子来演示如何通过这些 API 来开发一个完整的 Wasm 智能合约。
+ p% q4 H6 y4 V9 g+ J* W很多情况下我们都会通过各种 App,如微信等聊天工具发红包。我们可以给朋友发送红包,也可以抢其他人发送的红包,收到的钱会记入到个人微信账户中。0 w" O& U2 u+ C' B. k9 `7 o# I
类似于微信的流程,我们将尝试创建一个智能合约。用户使用该合约,可以发送 ONT,ONG 或者是标准的 OEP-4的 Token 资产红包给他的朋友们,而朋友们抢到的红包可以直接转入到他们的钱包账户中。8 c3 X4 a9 y& k  j, k5 |$ @
2.1 创建合约
% N1 l5 G+ Y! _" o! I- ?首先,我们需要新建合约的源文件,暂且命名为 redEnvelope.cpp。这个合约我们需要三个接口:
: z  r, E0 Q7 `2 \4 P# G5 @, b& V( icreateRedEnvelope: 创建红包
3 P- i6 a. ~  W0 g8 [' N9 QqueryEnvelope: 查询红包信息
6 T. S0 ?9 m( t& S* SclaimEnvelope: 抢红包
7 Y- J% e( E3 ^, y, m#include6 E& u, U( w$ J
using namespace ontio;  K% k- @! }' q+ e) e" y/ |
class redEnvelope: public contract{; l" J- R$ Q3 |9 P$ `4 B( V
}
7 P* C! `! y0 }2 {; {5 O$ A. RONTIO_DISPATCH(redEnvelope, (createRedEnvelope)(queryEnvelope)(claimEnvelope));
/ Q9 d  O6 g& Q: P. r+ a+ Y' a我们需要在存储中保存一些关键的数据。在智能合约中, 数据以 KV 的形式保存在该合约的上下文空间中,这些数据的 KEY 需要设置前缀以便于后面的查询。下面定义了三个不同的前缀供使用:
( g# ~1 n3 \) U7 p2 ?9 }, O4 S1 Bstd::string rePrefix = "RE_PREFIX_";
) ^* {; I/ e" K0 N3 J: gstd::string sentPrefix = "SENT_COUNT_";
; F3 L/ I6 b: C' Rstd::string claimPrefix = "CLAIM_PREFIX_"/ d6 q, `0 Q* ]1 ?1 y7 ?
因为我们的合约支持 ONT 和 ONG 这两种 Ontology 的原生资产, 我们可以预先定义好这两种资产的合约地址。不同于标准的智能合约, Ontology 原生合约(native contract)的合约地址是固定的,而不是根据合约代码的 hash 计算而来的。
! w. Q: Z1 \3 K4 S* [# waddress ONTAddress = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1};5 n! A3 M3 ]; S0 t! S8 C
address ONGAddress = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2};0 J; S+ M% o! M+ z# J, `
我们需要在合约中保存红包的信息, 如红包的资产信息(token 的合约地址, 红包的总金额, 红包的个数等等)% a* ^" q' A7 Z! [- H/ I
struct receiveRecord{
3 G6 T& v7 T, P6 j8 \address account; //用户地址
4 x0 [+ p: s, Fasset amount; //抢到的金额" [' V; u- E% t1 ~
ONTLIB_SERIALIZE(receiveRecord,(account)(amount)), p$ T; D. }$ F+ u8 x
};7 Y! A1 `1 C/ W, \% h
struct EnvelopeStruct{# O* w, v* B4 P2 B$ s
address tokenAddress; //资产token的地址  B# x/ e$ Y2 t8 B: F- k
asset totalAmount; //红包总金额
2 Z* F) q$ O, C* T2 x. }1 n  V( yasset totalPackageCount; //红包总数
* b) Y8 h) W- B+ ]asset remainAmount; //当前剩余的金额
! V3 u1 d: `: J3 passet remainPackageCount; //当前剩余的红包数* u$ K6 W. V* y. V, w
std::vector records; //已经抢完的记录1 z# m) T8 ?' v$ j& O7 t
ONTLIB_SERIALIZE( EnvelopeStruct, (tokenAddress)(totalAmount)(totalPackageCount)(remainAmount)(remainPackageCount)(records) )
* ]4 E: x; w# I( W6 W$ G4 L5 ]  P};) |* I& e4 S" E6 K( T$ {, T7 p
其中,7 X5 S4 v' b3 b. r2 t! S
ONTLIB_SERIALIZE(receiveRecord,(account)(amount))
1 g1 s1 C! f- w是由 Ontology Wasm CDT 定义的宏操作,用于在将 struct 存储前进行序列化的操作。
2 s  J' n: _' M3 ~! _2 I2.2 创建红包( Q; m6 C& t/ X, ]
准备工作差不多了,下面我们开始开发具体的接口逻辑。9 H2 s5 B3 w$ P3 T1 V6 ]0 s
创建红包需要指定创建者地址, 红包数量, 红包金额和资产的合约地址:
+ @! F2 T9 N& N0 X' `7 G7 m
7 E1 J" l1 S0 G, ^1 t8 Pbool createRedEnvelope(address owner,asset packcount, asset amount,address tokenAddr ){
" i6 N# F0 F; d7 R$ c; preturn true;
1 K, m$ E  S# k}' l! r/ i& C0 w! ]2 |# G8 r
检查是否有创建者的签名, 否则交易回滚退出:8 @7 V2 F& b/ ?& y: V

, a# ~/ _, h4 w0 l9 ^) ]* `$ D" S2 Eontio_assert(check_witness(owner),"checkwitness failed");
: \$ U( ^4 C8 g* r8 x0 JNOTE: ontio_assert(expr, errormsg):当 expr 为 false 时, 抛出异常并退出。% e9 o; D2 y$ _  Z2 v2 }( B
如果红包资产是 ONT,由于 ONT 的不可分割性(最小为1个 ONT), 红包的金额要大于或等于红包的数量,保证每个红包最少有1个 ONT:
7 m9 g' G# m7 X0 X/ G
  o) ]* C, g$ v3 o# hif (isONTToken(tokenAddr)){
& D& D4 _% a; O/ i& x7 y( }ontio_assert(amount >= packcount,"ont amount should greater than packcount");9 o5 \% t# P9 z) _8 ?0 f
}
1 b: q7 c4 C- M! @  B- j  T对于每个红包的创建者,我们需要记录一下他发送红包的总数量:- n7 q5 h! ]4 g# S5 C: w' p

6 r: P, s* `2 d1 N6 gkey sentkey = make_key(sentPrefix,owner.tohexstring());0 a  p5 o% d: G- ~& b
asset sentcount = 0;
7 C: z8 a7 L& h6 I( [% Cstorage_get(sentkey,sentcount);
/ j( Q' c$ e$ g# Y6 @$ }& ]sentcount += 1;; J5 n( E( \! c2 R2 W2 \) Y
storage_put(sentkey,sentcount);7 o9 b/ _; O" X# W5 |. W
生成红包 hash, 这个 hash 就是之后标识这个红包的唯一 ID:
) z6 r" ]# w& j# i& k: {1 o! B/ Q5 H% w" i+ K) D
H256 hash ;
, ~+ S4 y4 ^  \hash256(make_key(owner,sentcount),hash) ;
1 I/ p1 p! n- ?7 \key rekey = make_key(rePrefix,hash256ToHexstring(hash));
" \& F3 q4 y. m2 }8 |根据 token 资产的类型,将资产转入合约中,self_address()可以取得当前执行的合约地址, 我们根据用户输入的 token 类型,将指定数量的 token 转入合约:( m: i. j# D# r2 m

! z" _* M) N& K' e* h+ ~# J" X2 qaddress selfaddr = self_address();# {5 y/ O8 @7 Q3 W0 O
if (isONTToken(tokenAddr)){
' i* f- [5 L6 [0 G" j/ m# Rbool result = ont::transfer(owner,selfaddr ,amount);# g6 ?9 {+ C, Y) r6 N( l
ontio_assert(result,"transfer native token failed!");
5 G! l' A8 }$ p) I) _}else if (isONGToken(tokenAddr)){
9 ]7 w& a3 i0 H1 y4 W1 j0 Hbool result = ong::transfer(owner,selfaddr ,amount);
; E; ], |7 y3 Z) t5 Tontio_assert(result,"transfer native token failed!");
9 A% }4 v- ^& f! `% y}else{
1 c, e) M+ |2 V& [) g' v! S  _8 S/ cstd::vector params = pack(std::string("transfer"),owner,selfaddr,amount);
( k2 B6 L5 w* l4 a" \% g: nbool res;
; ?. |3 B3 Z& V) P) ycall_contract(tokenAddr,params, res );; R5 n/ M& }" A/ j, v
ontio_assert(res,"transfer oep4 token failed!");+ G* u3 E& s9 \+ a& c
}
5 N2 W. l( ]. V# j% J" lNOTE 1:对于 ONT 和 ONG 这两种原生资产, Ontology Wasm CDT 提供了ont::transfer API 进行转账操作;而 OEP-4类的资产,需要按照普通的跨合约调用方法来转账。4 Y$ _) J7 A, g
NOTE 2:和普通的钱包地址一样, 合约地址也可以接受任意类型的资产。但是合约地址是由合约编译后的二进制代码 hash 产生的,所以没有对应的私钥,也就无法随意操作合约中的资产,如果你没有在合约中设置对资产的操作,就意味着你将无法控制这部分资产。6 j- z& n6 j& N& h
将合约的信息保存在存储中:
% I; p5 C  Y6 Q: }# e( X" m/ a- b1 O/ J( y  g; G1 j2 X
struct EnvelopeStruct es ;
! |# {: U/ ~- p3 u; Ies.tokenAddress = tokenAddr;% g4 i& `9 u4 p
es.totalAmount = amount;
! h# T$ |1 d# r9 l) d, Res.totalPackageCount = packcount;# V  S( {# ]$ h% u+ i" \% U0 ?
es.remainAmount = amount;- n; R+ c* e7 B& l
es.remainPackageCount = packcount;
* W9 ?0 s- e7 ~: H# g- N  U& P5 ies.records = {};
% L$ j1 G* U: }) E, jstorage_put(rekey, es);, S( d$ h3 _' d
发送创建红包的事件。对于智能合约的调用是一个异步的过程,合约会在执行成功后发送一个事件来通知客户端执行结果,这个事件的格式可以由合约的编写者来指定。
: |( _- B/ I- Y# z  ~! m) ^. Z
. i1 M) J* F% tchar buffer [100];6 u+ |0 J0 P5 e+ ~! o3 |
sprintf(buffer, "{\"states\":[\"%s\", \"%s\", \"%s\"]}","createEnvelope",owner.tohexstring().c_str(),hash256ToHexstring(hash).c_str());- g  e3 m& [1 W4 {2 E" c" Q) |
notify(buffer);/ y( r# u2 c: S5 i
return true;
; a3 y! z  F& ^4 O) ~) ?一个简单的红包就创建完成了, 下一步我们需要实现如何查询这个红包的信息.4 y' _$ k2 {( U) m( ?
2.3 查询红包
* t$ A2 e5 |2 @  j1 t0 v查询红包的逻辑非常简单, 只需要将存储中的红包信息取出并格式化返回即可:
# }8 B9 X4 }: Q' y% }; astd::string queryEnvelope(std::string hash){4 A5 q7 K, m! ], S9 u
key rekey = make_key(rePrefix, hash);! Y/ i8 |) b: \# q6 V3 ], N
struct EnvelopeStruct es;
  i+ D, Y) ^' a' G! b5 A' P6 Nstorage_get(rekey, es);
8 l) q0 s2 a1 i; nreturn formatEnvelope(es);0 Q& b: n6 m5 [  i
}
% m3 C3 l, J! H- Y# }$ YNOTE:对于智能合约的只读操作(例如查询), 可以通过预执行(pre-exec)来读取结果。不同于普通的合约调用,预执行不需要钱包的签名,同时也就无需花费 ONG。最后,其他用户可以根据 hash(红包的 ID)来领取(抢)这个红包了。
( z5 G& t2 L4 Z) i2.4 领取红包$ n; z1 I; D( v
我们已经把资产成功地转入到智能合约中了, 接下来就可以把这个红包的 ID 发送给你的朋友们让他们去抢红包了。( {; a6 ~4 j3 ?8 Z( z- R1 D( K( D
领取红包需要输入领取人的账户和红包的hash:
* A$ O; E* ^) \/ I1 Y5 h% D. Z/ O7 P4 K
bool claimEnvelope(address account, std::string hash){' ~/ i; P  ^) A" Y5 h. y
return true;( |" {' r! I2 E0 w& O/ e
}* _+ ^+ C6 |; j% a' y( E$ T) \% v
同样, 我们需要验证领取账户的签名, 不允许替其他人抢红包, 而且每个账户每个红包只能抢一次:" C- W' m9 m# W; q# a9 E0 B

( Q6 ]8 P! S  T/ n# bontio_assert(check_witness(account),"checkwitness failed");
$ s2 g8 Y, D/ e9 E3 L: k- P& C6 ]key claimkey = make_key(claimPrefix,hash,account);
$ w/ r: Q' T% ~5 Rasset claimed = 0 ;/ R) h6 @+ u) r+ Q$ a8 ^
storage_get(claimkey,claimed);
0 ?! G! z% h5 Tontio_assert(claimed == 0,"you have claimed this Envelope!");
2 \% D0 }1 J4 w" Z按照 hash 从存储中取出红包的信息, 判断这个红包是否没有被抢完:
/ P& @7 p7 T" E4 O- r6 F3 L
- i0 R* [5 r8 Q& \/ [& L' Wkey rekey = make_key(rePrefix,hash);
: }8 T) V1 K- `. Qstruct EnvelopeStruct es;
% J/ E  [& G4 ustorage_get(rekey,es);8 e% t# z$ D( x0 M
ontio_assert(es.remainAmount > 0, "the Envelope has been claimed over!");
9 S7 \, L% l" ]; i7 X/ uontio_assert(es.remainPackageCount > 0, "the Envelope has been claimed over!");
& J$ J$ D+ v" }# ]& q6 p$ g新建一条领取的记录:" s# ?) @' T6 \  o& r/ _! Q

( W& u. G# _+ A2 @4 h$ P3 m; Bstruct receiveRecord record ;3 `0 f. G3 a% Y6 N' d
record.account = account;% g, e2 Y( c0 e) f5 q
asset claimAmount = 0;
& K/ s1 K! q- k" `计算本次领取红包的资产数量。如果是最后一个红包, 数量为剩余的金额, 否则根据当前区块 hash 计算随机数,确定本次领取的数量, 并更新红包信息:
1 \, X  f# ?" O: N: b# i& f$ b' `
! h- d; r4 N6 o0 a5 Q2 I. Mif (es.remainPackageCount == 1){4 F! Z) N. N5 j5 r# n( f
claimAmount = es.remainAmount;5 U4 Z# A8 K1 J# c2 R+ n) P
record.amount = claimAmount;
+ Q. G/ x2 k4 C$ v0 Q" q/ q}else{3 n3 g. U; y) X# D9 d
H256 random = current_blockhash() ;& v9 m- n! J2 l
char part[8];
8 E, ]; f  M5 x$ W& I. E4 @5 imemcpy(part,&random,8);
# K- H) p2 f; M- juint64_t random_num = *(uint64_t*)part;) T6 K/ m  k) @; I' w
uint32_t percent = random_num % 100 + 1;) ~1 r6 G' N9 p5 b$ q* I( m" K
claimAmount = es.remainAmount * percent / 100;8 s; H! R; @- R. w, }8 P1 y
//ont case9 N, p: h" r8 W3 U7 n
if (claimAmount == 0){
- }) E0 e) ]7 y; [! u% jclaimAmount = 1;  n) r1 p* S6 V7 K5 e. C% @( x
}else if(isONTToken(es.tokenAddress)){
$ W* `/ h/ v  H$ [8 t! n  P: w. vif ( (es.remainAmount - claimAmount) 根据计算结果, 将对应资产从合约中转到领取的账户:
0 P1 z3 F0 y6 e3 ^" p  @
6 }/ ~% D) q( y+ a  @2 d2 naddress selfaddr = self_address();; j3 b) Z: u; B& O+ G1 q  R7 e
if (isONTToken(es.tokenAddress)){. Y: `" i3 m" b# n. q9 ]8 L
bool result = ont::transfer(selfaddr,account ,claimAmount);
$ `! \& {5 e+ Z5 M6 ~  uontio_assert(result,"transfer ont token failed!");
6 f3 g8 m1 c+ K. y} else if (isONGToken(es.tokenAddress)){/ u# G. q  |1 j* }
bool result = ong::transfer(selfaddr,account ,claimAmount);. X7 M7 P, T. x6 y$ C+ J
ontio_assert(result,"transfer ong token failed!");
1 A) Q% e! W$ l} else{- m& Y- o7 q; L' Y
std::vector params = pack(std::string("transfer"),selfaddr,account,claimAmount);% h- f: \3 I' K$ ^# z/ C* r9 F" o# P( k
bool res = false;( F9 ]! M# z) u9 i. y' i
call_contract(es.tokenAddress,params, res );
( k; z6 Y2 V4 V" s$ A3 V, J: O% a% X# oontio_assert(res,"transfer oep4 token failed!");
; ~* y* g' g4 t  i' L/ Z6 n- p}& O  y7 h& k( t2 |$ |8 F4 E
记录领取的信息, 将更新后的红包信息写回存储并发送通知事件:
, D! q$ i* y9 @) E  s& r* K
2 f- J$ b6 A4 O6 P0 b2 V$ I  Zstorage_put(claimkey,claimAmount);$ O5 Y# a5 a9 Q' ~) ]
storage_put(rekey,es);, D* O- ~8 J+ x% _4 ~/ x9 J6 P
char buffer [100];
* v# e* }1 |) E$ f: sstd::sprintf(buffer, "{\"states\":[\"%s\",\"%s\",\"%s\",\"%lld\"]}","claimEnvelope",hash.c_str(),account.tohexstring().c_str(),claimAmount);0 ~& W  J; S& U
notify(buffer);
: ~0 O, K& A7 e$ Q) s* }9 H' oreturn true;
- Z, Z" y+ T5 T* R# U# ~, J$ t如前面所说,这个合约只能通过 claimEnvelope 这个接口将资产转出合约。所以,合约中的资产是安全的,任何人都无法随意的取走里面的资产。
3 q. l; G% X0 s/ V, R$ x至此, 一个简单的红包合约逻辑完成, 完整的合约代码如下:https://github.com/JasonZhouPW/pubdocs/blob/master/redEnvelope.cpp. L' q' S3 p- q; E: l3 Y
2.5 合约测试! n* E3 D/ m( F
合约测试可以有两种方法:
7 f& ?  p# O% F' _4 \, o" Q  {使用 CLI/ m/ g, v$ X5 K; b
请参考:https://github.com/ontio/ontology-wasm-cdt-cpp/blob/master/How_To_Run_ontologywasm_node.md, T9 Y& `, d' D2 ?" O& Q0 A
使用 Golang SDK% }5 o  ^- U3 }# O6 y$ l2 m
请参考:https://github.com/ontio/ontology-wasm-cdt-cpp/blob/master/example/other/main.go
% ~: t3 j6 j9 c$ z6 N1 o三、总结
4 n0 _9 Y/ K$ S本示例只是为了展示如何编写一个完整的 Wasm 智能合约, 如何通过调用 API 和底层的区块链进行交互。如果要作为正式的产品, 还需要解决红包的隐私问题: 所有人都可以通过监控合约的事件来取得红包的 hash, 意味着每个人都可以抢这个红包。一种比较简单的解决方法,就是在创建红包时指定哪些账户能够领取。如果有兴趣, 您也可以尝试修改测试一下。
  s# j7 X1 a" R我们欢迎更多的 Wasm 技术爱好者加入本体开发社区,共同打造技术生态。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

放弃六月们 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    8