Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

放弃六月们
188 0 0
Ontology Wasm 自从上线测试网以来便受到了社区开发人员的极大关注。 Wasm 的上线将使得业务逻辑复杂的 dApp 合约上链成本降低,极大丰富 dApp 生态。在进行 Wasm 合约开发时,开发者不仅可以使用 Rust,还可以使用 C++ 作为合约开发语言。本期我们将通过两个简单的示例来示范如何使用 C++ 进行Wasm 合约开发。
) D/ q! A% S) J' O& e一、Hello World
( i/ B/ D0 V0 j9 e/ ^按照惯例,我们还是从一个 Hello world 开始+ u) j8 O8 i/ v2 q
#include#include8 V; \. b1 Y* p6 N5 t
using namespace ontio;class hello:public contract {9 O  o) L& `' g3 j. j" e; P
public:8 h* h( I6 h- ]: i/ d
using contract::contract:' i# d1 o1 ?/ ?! n
void sayHello(){, D( V8 ~7 c- U( ?
printf("hello world!");
, Q2 k0 W5 J9 W3 ]. _5 [9 g}4 r; G+ Q* y$ R" }& R
};
; A4 a: o5 \( yONTIO_DISPATCH(hello, (sayHello));
' {+ c- w, [# n& f1.1 合约入口
! L( ~% [& K" S* AOntology Wasm CDT 编译器已经对入口和参数解析进行了封装,所以开发者不需要重新定义入口方法。接下来是定义合约的对外接口,这是智能合约对外提供服务的方法。
. p$ l7 b; W- YONTIO_DISPATCH(hello, (sayHello));
# D2 x) Q3 r* {7 S8 |* c, M在上面的例子中, 我们暂时只支持 sayHello 这个方法:
+ B7 k( P$ N, N. `9 rprintf("hello world!");; Z- T" L4 i- A& A! Q5 c9 O' X
这个“Hello world!”会在节点的日志中以调试信息打印出来。在实际的应用中, printf 只能用作调试的目的, 一个实际的智能合约,需要实现更多更复杂的功能。6 c! h) P4 L0 u7 W# p3 p
1.2 智能合约 API
# h$ v, E- |8 LOntology Wasm 提供如下 API 与区块链的底层进行交互:
. s9 f( y( s( ^- n, y3 t' S! m3 B' J/ t% n# \8 R; C7 a
二、红包合约( X6 q$ J3 V9 Y' Y0 i4 ?
下面我们通过一个更加复杂的例子来演示如何通过这些 API 来开发一个完整的 Wasm 智能合约。
, g& I- ~1 Y- `9 r9 Z) I( K很多情况下我们都会通过各种 App,如微信等聊天工具发红包。我们可以给朋友发送红包,也可以抢其他人发送的红包,收到的钱会记入到个人微信账户中。( K; U, T) ]* U7 G8 i( K
类似于微信的流程,我们将尝试创建一个智能合约。用户使用该合约,可以发送 ONT,ONG 或者是标准的 OEP-4的 Token 资产红包给他的朋友们,而朋友们抢到的红包可以直接转入到他们的钱包账户中。* d! _+ X% Y. @% _% ~
2.1 创建合约
. H  [3 [: e% G2 x6 J: m首先,我们需要新建合约的源文件,暂且命名为 redEnvelope.cpp。这个合约我们需要三个接口:# Z, n% T- `; x/ ^
createRedEnvelope: 创建红包$ U! C6 [- L9 u
queryEnvelope: 查询红包信息
! R+ u3 N- X& [3 q5 t# aclaimEnvelope: 抢红包
! N% V9 s0 T' R: h4 R& ^#include/ s, Z& ~: t2 p1 a2 W
using namespace ontio;
, b0 c0 w! z* q/ fclass redEnvelope: public contract{4 H) R2 k6 B1 [, [7 |
}
+ i8 U. J% V$ U' QONTIO_DISPATCH(redEnvelope, (createRedEnvelope)(queryEnvelope)(claimEnvelope));
! [" o5 ^; `  v我们需要在存储中保存一些关键的数据。在智能合约中, 数据以 KV 的形式保存在该合约的上下文空间中,这些数据的 KEY 需要设置前缀以便于后面的查询。下面定义了三个不同的前缀供使用:& k8 F) Q2 `  }! [+ p2 k
std::string rePrefix = "RE_PREFIX_";
, i4 I# X- q2 kstd::string sentPrefix = "SENT_COUNT_";
- \; X# U$ A) }6 o) K0 y2 P0 ystd::string claimPrefix = "CLAIM_PREFIX_"
$ ^) F! A5 i: ^: b因为我们的合约支持 ONT 和 ONG 这两种 Ontology 的原生资产, 我们可以预先定义好这两种资产的合约地址。不同于标准的智能合约, Ontology 原生合约(native contract)的合约地址是固定的,而不是根据合约代码的 hash 计算而来的。' M& z- ^8 ]: @' t- O
address ONTAddress = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1};
5 v, _; `4 @3 b$ ~) kaddress ONGAddress = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2};$ n7 S# p1 m' `# b6 L
我们需要在合约中保存红包的信息, 如红包的资产信息(token 的合约地址, 红包的总金额, 红包的个数等等)2 V: `0 z! X% U3 y2 s
struct receiveRecord{8 {0 C% B( P. K& ~" ?3 n
address account; //用户地址
) T! w% \# K3 q* X- @) Gasset amount; //抢到的金额
; h: P. p* w9 z7 @ONTLIB_SERIALIZE(receiveRecord,(account)(amount))/ T# q/ r5 o: K  P* T1 Q" E
};
% ]$ P7 z, D8 V2 K4 vstruct EnvelopeStruct{
9 G% r* ~: Q% w8 i' r- Uaddress tokenAddress; //资产token的地址
! O4 E0 K  z) r/ C( iasset totalAmount; //红包总金额  [. Y( R  u& u2 I6 S
asset totalPackageCount; //红包总数, E7 z% n7 E' Q
asset remainAmount; //当前剩余的金额5 T" W( b# s% x5 Q- F
asset remainPackageCount; //当前剩余的红包数
$ ]5 T3 e1 F& G2 hstd::vector records; //已经抢完的记录
- R  }9 `) w6 C7 O' l, xONTLIB_SERIALIZE( EnvelopeStruct, (tokenAddress)(totalAmount)(totalPackageCount)(remainAmount)(remainPackageCount)(records) )1 g3 }# J. E/ p6 O
};
5 m7 S% x9 F) ^+ B其中,2 u# ~0 u- [+ J  t  f3 k
ONTLIB_SERIALIZE(receiveRecord,(account)(amount))
: Y. Q% a& ?+ n是由 Ontology Wasm CDT 定义的宏操作,用于在将 struct 存储前进行序列化的操作。
& v% M' t* h$ L+ g8 ]2.2 创建红包8 w8 B5 y% T) `7 z5 G# ~9 d
准备工作差不多了,下面我们开始开发具体的接口逻辑。
3 a/ D- q9 J& q( L' K% k, n( h" y5 W创建红包需要指定创建者地址, 红包数量, 红包金额和资产的合约地址:* g# S# r1 F6 w) W8 C
) _+ Z" R2 r; f( z8 T# n
bool createRedEnvelope(address owner,asset packcount, asset amount,address tokenAddr ){
! O" ]0 r. G  }3 G! r4 z$ R3 areturn true;
( O) L' L' T7 h' q  ~! _}
2 Q( }& S' Y" K& @: I/ k检查是否有创建者的签名, 否则交易回滚退出:
5 ^3 E  F  {/ b5 ]7 ?" c  K" Y  {0 r  y/ J" ?6 J' p
ontio_assert(check_witness(owner),"checkwitness failed");' n6 M: {. o1 @* \
NOTE: ontio_assert(expr, errormsg):当 expr 为 false 时, 抛出异常并退出。1 w4 R, Z' t# k& p% X
如果红包资产是 ONT,由于 ONT 的不可分割性(最小为1个 ONT), 红包的金额要大于或等于红包的数量,保证每个红包最少有1个 ONT:( n9 ?+ M3 n5 y+ V) U, Z
' B# }* O* r. |8 Y) M& g! z( m
if (isONTToken(tokenAddr)){8 F& G& q4 K# C# n) G
ontio_assert(amount >= packcount,"ont amount should greater than packcount");
- B9 C2 a: M: d4 m) h}
" y  e* o8 j' {- N对于每个红包的创建者,我们需要记录一下他发送红包的总数量:  i) R, t0 I7 y' v

0 s% {/ l1 Q$ c$ w4 a( skey sentkey = make_key(sentPrefix,owner.tohexstring());' Y( w2 m% s: ?
asset sentcount = 0;% L" L# {. w. a5 i$ I
storage_get(sentkey,sentcount);
: |5 x& G7 h$ ?1 ^8 csentcount += 1;
: ~; U: T3 n- [. _; U9 Pstorage_put(sentkey,sentcount);
+ Z" j) d9 p: i6 w" f* [: w6 Q生成红包 hash, 这个 hash 就是之后标识这个红包的唯一 ID:' e6 \! A: J- K1 k; @4 D1 y7 n( `# z
- |4 v' }! x5 C# R- m3 x
H256 hash ;
( J+ v: s% m$ _% yhash256(make_key(owner,sentcount),hash) ;
6 b1 w' ^( V; j$ b$ J+ l( l2 Wkey rekey = make_key(rePrefix,hash256ToHexstring(hash));
1 q1 I+ u+ V0 F4 K/ D根据 token 资产的类型,将资产转入合约中,self_address()可以取得当前执行的合约地址, 我们根据用户输入的 token 类型,将指定数量的 token 转入合约:
8 F! u  {' |6 j- ~( H3 q4 m4 b8 i& f. W' x8 a6 E  x% w  i8 |7 h" i
address selfaddr = self_address();
$ l3 V8 ^, B' b. o/ o0 ^if (isONTToken(tokenAddr)){
3 {" ^5 R6 F- T" Gbool result = ont::transfer(owner,selfaddr ,amount);
- e9 `. d8 y8 T0 h8 F; p. i9 fontio_assert(result,"transfer native token failed!");3 A2 W/ N' }3 z2 `7 T' Z1 j
}else if (isONGToken(tokenAddr)){
6 w* P- ?1 ]1 x; R1 D7 tbool result = ong::transfer(owner,selfaddr ,amount);" w/ F0 J& M# i
ontio_assert(result,"transfer native token failed!");
6 i. p/ |: b* \, @, Q}else{
2 o$ |) {( l! @' gstd::vector params = pack(std::string("transfer"),owner,selfaddr,amount);
# E7 Z2 M4 I6 E) m7 w, t! }' pbool res;
, A3 r* ~3 a% v5 C4 ~. J" C7 Zcall_contract(tokenAddr,params, res );6 ~- q% t) S0 _% Z
ontio_assert(res,"transfer oep4 token failed!");9 H5 {6 u: e- J) `. M/ }
}1 z* l' V4 H, Q, s2 O6 I1 Z
NOTE 1:对于 ONT 和 ONG 这两种原生资产, Ontology Wasm CDT 提供了ont::transfer API 进行转账操作;而 OEP-4类的资产,需要按照普通的跨合约调用方法来转账。1 q, J0 R# ?7 ?
NOTE 2:和普通的钱包地址一样, 合约地址也可以接受任意类型的资产。但是合约地址是由合约编译后的二进制代码 hash 产生的,所以没有对应的私钥,也就无法随意操作合约中的资产,如果你没有在合约中设置对资产的操作,就意味着你将无法控制这部分资产。
# M5 y$ _( X. G' V) _+ F+ L5 h将合约的信息保存在存储中:. F0 E* I1 O* C! D4 Q

' s# T+ ^: J9 _% f7 y1 q1 ^struct EnvelopeStruct es ;
8 g9 |: U- B+ t4 Aes.tokenAddress = tokenAddr;$ R, U% @7 I/ q
es.totalAmount = amount;
# a" N; B) r: A/ ^es.totalPackageCount = packcount;
. O" F  \: g, xes.remainAmount = amount;
! o; i. V1 q  |- |  e+ }+ Les.remainPackageCount = packcount;
: w9 I/ g5 ~) e% A( N% L  res.records = {};, j! }4 c& }4 a/ s" M, K
storage_put(rekey, es);
, Q" q4 P3 P& z$ I" n2 z发送创建红包的事件。对于智能合约的调用是一个异步的过程,合约会在执行成功后发送一个事件来通知客户端执行结果,这个事件的格式可以由合约的编写者来指定。
; r2 F4 v0 N: ^) d% q* h7 t2 [: G  r8 F% J+ l; K" g
char buffer [100];' }: l+ }  n+ K" J1 E* Z
sprintf(buffer, "{\"states\":[\"%s\", \"%s\", \"%s\"]}","createEnvelope",owner.tohexstring().c_str(),hash256ToHexstring(hash).c_str());
2 R8 {  x7 f! \( qnotify(buffer);
! A: G( [$ j# [4 rreturn true;0 u; l- n' P) A8 n
一个简单的红包就创建完成了, 下一步我们需要实现如何查询这个红包的信息.' `6 V% k  |/ C) g2 U; w1 ^
2.3 查询红包* ]; K* V1 t/ f4 w6 h4 o
查询红包的逻辑非常简单, 只需要将存储中的红包信息取出并格式化返回即可:6 ?+ p; ]7 d! y+ `; K, |
std::string queryEnvelope(std::string hash){1 n; k2 E5 H0 W( o! A5 ~
key rekey = make_key(rePrefix, hash);$ z& u/ N( {2 y$ _6 w  f+ e3 \7 ~
struct EnvelopeStruct es;4 i! `- Y0 [! u1 s8 M
storage_get(rekey, es);- W3 k; v$ V9 {0 \4 `) r3 n
return formatEnvelope(es);& \6 P; K1 {5 B. G
}1 j* |' {% V2 u2 _5 E
NOTE:对于智能合约的只读操作(例如查询), 可以通过预执行(pre-exec)来读取结果。不同于普通的合约调用,预执行不需要钱包的签名,同时也就无需花费 ONG。最后,其他用户可以根据 hash(红包的 ID)来领取(抢)这个红包了。$ l. R/ v8 X, z: M
2.4 领取红包4 x8 C3 s" m- |0 O( B0 G
我们已经把资产成功地转入到智能合约中了, 接下来就可以把这个红包的 ID 发送给你的朋友们让他们去抢红包了。
  b2 T6 A1 M. C5 l% Y- e& C4 b领取红包需要输入领取人的账户和红包的hash:
7 q1 m: I' G) }3 I, |& L9 n& O+ M; M. {/ a8 w0 U* o& `
bool claimEnvelope(address account, std::string hash){
; i* H- t1 X+ x0 Lreturn true;
& E2 t" g7 N4 {4 |! d}
- }/ S- l6 V) U- S1 r2 R同样, 我们需要验证领取账户的签名, 不允许替其他人抢红包, 而且每个账户每个红包只能抢一次:
# C0 ], ?# {# T2 m8 R
$ I7 a) U3 i! H; b  R; v1 }ontio_assert(check_witness(account),"checkwitness failed");+ o' V* R7 g( N
key claimkey = make_key(claimPrefix,hash,account);# _3 T3 t) ]8 Z7 `6 i# n
asset claimed = 0 ;
5 y5 Z& f+ V0 L7 V0 ?storage_get(claimkey,claimed);! |& A  I% w6 N: s1 Q
ontio_assert(claimed == 0,"you have claimed this Envelope!");
3 N/ U" J* ?, A$ ^# F6 |: M+ c8 D按照 hash 从存储中取出红包的信息, 判断这个红包是否没有被抢完:( U7 i# j4 ]0 @* G/ G2 A4 w

- f7 T# ?" F& i* z' q0 j3 rkey rekey = make_key(rePrefix,hash);
- ~% w4 N$ X; a5 T5 Gstruct EnvelopeStruct es;
# [$ j* i( w: Estorage_get(rekey,es);( [* k& {' m4 O; E: J7 h; ?0 N
ontio_assert(es.remainAmount > 0, "the Envelope has been claimed over!");3 s# J5 m& t: f; e- P
ontio_assert(es.remainPackageCount > 0, "the Envelope has been claimed over!");
2 J1 ]" ~; l! M8 U' i新建一条领取的记录:
, P9 k" D6 ], E& G! `0 s
7 H, |3 n7 M3 p# [4 X* ]$ r  l- ?/ Ystruct receiveRecord record ;/ C# E( {* {: Z/ n2 \# z
record.account = account;0 {$ U  X* A# S( T8 k/ `; g% z
asset claimAmount = 0;0 w6 o# n2 H# p$ o
计算本次领取红包的资产数量。如果是最后一个红包, 数量为剩余的金额, 否则根据当前区块 hash 计算随机数,确定本次领取的数量, 并更新红包信息:- [" {* Z" K; U8 z1 {. _2 `1 ^
' Q' z' t! s8 P# F$ `8 K
if (es.remainPackageCount == 1){. o+ p$ T, b5 A8 j$ S& Y
claimAmount = es.remainAmount;% e& }8 J% }; D5 x7 H+ k3 T) ]
record.amount = claimAmount;% m7 `& z0 P; ?, {7 X& B+ o
}else{
# ?. G: W4 w/ M; J! p& CH256 random = current_blockhash() ;& |7 B: l' e( j! [5 q; U
char part[8];
2 A0 h9 w5 U& ^4 `; v/ ^) h5 B5 Qmemcpy(part,&random,8);
" ~: }# ~' l6 z" z$ zuint64_t random_num = *(uint64_t*)part;& O6 m6 P6 x6 y# Z- w% g
uint32_t percent = random_num % 100 + 1;
# w* N' b7 b- ?3 ^6 q* e) XclaimAmount = es.remainAmount * percent / 100;7 k) o) u, I- K. }+ g
//ont case* c1 ?" `4 @9 K4 R9 n  ^
if (claimAmount == 0){
+ d# A, i8 A6 X: @claimAmount = 1;
4 F) F( Q  X6 a4 s( x}else if(isONTToken(es.tokenAddress)){# V$ K/ M: [9 d- ^7 E" P
if ( (es.remainAmount - claimAmount) 根据计算结果, 将对应资产从合约中转到领取的账户:
3 T1 _/ i( l5 W1 x+ ^
% J' {. |; K" B7 a- vaddress selfaddr = self_address();5 @4 z9 E3 g4 h+ f
if (isONTToken(es.tokenAddress)){2 [$ s8 [5 |; l" X
bool result = ont::transfer(selfaddr,account ,claimAmount);
" }1 W6 I9 ]+ qontio_assert(result,"transfer ont token failed!");$ {4 _3 Q3 s; M. V3 ]
} else if (isONGToken(es.tokenAddress)){% U( x% o3 @/ P3 O
bool result = ong::transfer(selfaddr,account ,claimAmount);
  E( y; Z( h' H9 G% montio_assert(result,"transfer ong token failed!");
$ f+ W7 N/ e. w+ R8 O  I" B% E* z, G} else{2 _/ u0 F5 k; }+ o* T; w* n
std::vector params = pack(std::string("transfer"),selfaddr,account,claimAmount);
5 h) w1 a: k* V# }( N9 Y9 P, i0 P: e' lbool res = false;; S1 v$ m, U- S4 t! _' P
call_contract(es.tokenAddress,params, res );& ]& U  g3 t1 p$ M$ J) f
ontio_assert(res,"transfer oep4 token failed!");- U( q$ E, I$ b* e1 t2 B
}
% S. W: h( @8 U+ ?3 r# ~+ M( d3 @6 P记录领取的信息, 将更新后的红包信息写回存储并发送通知事件:+ u+ U9 m2 E: f
( w$ q4 \& X; l7 ?+ P9 g
storage_put(claimkey,claimAmount);
$ x7 z: i$ D* F6 L, {1 Xstorage_put(rekey,es);
0 S- ?8 \  c7 R+ }char buffer [100];: h2 P7 V& ~  U4 q
std::sprintf(buffer, "{\"states\":[\"%s\",\"%s\",\"%s\",\"%lld\"]}","claimEnvelope",hash.c_str(),account.tohexstring().c_str(),claimAmount);
& G. H; i2 l, Q& n% y4 r0 J# Anotify(buffer);
- ~# N5 s# [9 C) a% b" u% Hreturn true;, m. B: b' H, S+ I+ m; Z
如前面所说,这个合约只能通过 claimEnvelope 这个接口将资产转出合约。所以,合约中的资产是安全的,任何人都无法随意的取走里面的资产。. E- E$ j3 \6 H
至此, 一个简单的红包合约逻辑完成, 完整的合约代码如下:https://github.com/JasonZhouPW/pubdocs/blob/master/redEnvelope.cpp
) d) {1 [- f8 [$ ?$ k9 @6 d% k( A7 M2.5 合约测试' G- t' q4 W. t) ]8 u
合约测试可以有两种方法:+ a+ {2 `4 Z0 {- i" o) K9 |0 W
使用 CLI0 V7 J, x! ?" z. h( c
请参考:https://github.com/ontio/ontology-wasm-cdt-cpp/blob/master/How_To_Run_ontologywasm_node.md7 C( w* @* p6 Z% c
使用 Golang SDK
. ^7 \( M4 {4 v+ S请参考:https://github.com/ontio/ontology-wasm-cdt-cpp/blob/master/example/other/main.go6 P8 i/ @4 k% m, \) v0 ~4 |/ }
三、总结
3 t2 M6 A% q) }* ^2 H' _本示例只是为了展示如何编写一个完整的 Wasm 智能合约, 如何通过调用 API 和底层的区块链进行交互。如果要作为正式的产品, 还需要解决红包的隐私问题: 所有人都可以通过监控合约的事件来取得红包的 hash, 意味着每个人都可以抢这个红包。一种比较简单的解决方法,就是在创建红包时指定哪些账户能够领取。如果有兴趣, 您也可以尝试修改测试一下。( s( s5 f) y4 f1 v' ]7 t! ]- F
我们欢迎更多的 Wasm 技术爱好者加入本体开发社区,共同打造技术生态。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

放弃六月们 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    8