在这一课中,我们将练习执行更多不同的命令、创建函数,甚至从合约内部发送信息。我们将在现有合约的基础上完成所有这些工作。 我们的计划是什么?让我们来分析一下: 我们的合约对指令的要求将变得更加严格,我们将引入一个新的操作码,用于向合约存入资金的逻辑。 我们要引入的另一个操作代码是提取资金。在提取资金过程中,资金实际上是以消息的形式发送到地址的,因此我们将学习如何在合约中发送消息 任何以未知操作代码到达的资金都将返回给发送方 我们将在存储中引入一个新值--合约所有者。只有合约所有者才能提取资金。我们还将在函数中分离存储加载和写入逻辑,以保持主代码的整洁 3 n: |4 i" g5 |+ V# I
准备好了吗?就绪。开始! 分离存储管理逻辑首先,让我们来处理合约所有者存储中的数据(我们需要记住,我们必须在启动合约时将该地址放入存储中)。我们还将创建两个新函数-load_data 和 save_data: - (int, slice, slice) load_data() inline {& W% z2 \5 A+ {$ `! G
- var ds = get_data().begin_parse();$ M: L9 D0 X4 q* l* Q" j+ v
- return (6 g; ~# ]! P0 I6 ~9 w0 Y
- ds~load_uint(32), ;; counter_value
9 ~/ ?4 @: N( Y2 T, {9 b - ds~load_msg_addr(), ;; the most recent sender: f& p" R& b0 | ?1 \ s1 C- j, h
- ds~load_msg_addr() ;; owner_address
% t2 T0 p' B( y$ q - );
7 Y6 ^" ]% q$ ]0 s - }" Y9 v4 _( G6 ], ]) K, O3 e
1 [2 {* I' E4 @+ N/ N$ _- () save_data(int counter_value, slice recent_sender, slice owner_address) impure inline {( p k. L* K4 v
- set_data(begin_cell()) }) `* t3 ^: s" e8 t
- .store_uint(counter_value, 32) ;; counter_value8 ^% K+ Z2 b& ]$ E6 Z
- .store_slice(recent_sender) ;; the most recent sender
# Y) m/ t; r% J. T - .store_slice(owner_address) ;; owner_address
; T* G J: c) j+ p: O+ p5 ` - .end_cell());. r: O0 K& J/ V3 d
- }
复制代码这里有几点需要注意: 我们将在代码中进一步使用这些函数: - var (counter_value, recent_sender, owner_address) = load_data();2 ?7 i" |" \6 P5 e
- + W5 Z5 e* d" ?
- save_data(counter_value,recent_sender,owner_address);
复制代码有几点需要注意: 总的来说,代码看起来很简洁,不是吗? 存款和取款对我们来说,存款 (op == 2) 非常简单,因为在这种情况下,我们只需成功完成执行,使得资金被接受。否则,资金将退回给发件人。 - if (op == 2) {8 J9 V' U- g9 b% s4 Y
- return();
2 X0 o- F N( P- b - }
复制代码不过,取款时情况会变得复杂一些。我们必须将发件人地址与智能合约的所有者地址进行比较。让我们来看看,为了实现这个逻辑,我们需要知道哪些事情: 要比较所有者地址和发送者地址,我们使用 FunC 标准函数 equal_slice_bits() 我们使用 throw_unless() 函数在比较结果为 false. 还有另一种通过错误的方法-throw_if(),如果传入该函数的条件返回 true,该函数就会抛出错误. 带有此操作的消息正文还需要有一个整数,说明要求提取的金额。我们将此金额与合约的实际余额进行比较(标准 FunC 函数 get_balance())。 我们希望我们的合约总是有一些资金,能够支付租金和必要的费用(在第 1 章中了解更多关于费用的信息),因此我们需要设置合约中必须保留的最低金额,如果请求的金额不允许,则会抛出错误。 最后,我们需要学习如何通过内部信息从合约内部发送代币 ) r. G6 d- H8 R" s$ s K
# Q f2 d# Y! p) Y1 g! I我们看看这一切是如何实现的。 首先,我们设置最小所需存储空间的常数: - const const::min_tons_for_storage = 10000000; ;; 0.01 TON
/ J9 c% q" f5 |; H1 s3 P4 c
复制代码然后,我们实现提款的逻辑。它看起来是这样的:
+ g$ P) X# X( S' \( k% B- if (op == 3) {0 _8 T l9 [- h5 b# D; p
- throw_unless(103, equal_slice_bits(sender_address, owner_address));* {4 Z9 A4 w+ z
0 N' O& L$ Y+ J8 j7 l- int withdraw_amount = in_msg_body~load_coins();
( m/ H w5 ]- P! a/ G) F - var [balance, _] = get_balance();
0 R, u$ Q/ n& z) c/ r& j - throw_unless(104, balance >= withdraw_amount);
& ~/ L3 X8 P3 v( X" J& C; Z - - w k- y* ^1 i' \3 K
- int return_value = min(withdraw_amount, balance - const::min_tons_for_storage);- r. e3 m# C) X( k. X. `
- 3 N7 V4 k/ t7 b2 t, X5 J# \. J) X9 W; V
- $ U& S1 S/ J8 w W3 `1 g: L% H# E; Z. G
- ;; TODO: Sending internal message with funds
f) H# V3 \; |3 h - " B4 M, L1 C9 J9 q( `
- return();
9 o$ @) _1 K% L$ e) l - }
复制代码如您所见,我们正在读取提款的代币数量(将存储在我们的 in_msg_body 在操作代码之后,我们将在下一课中完成) 检查余额是否大于或等于提款金额。 我们还使用了一种很棒的技术,确保在合约中保留最低存储金额。 让我们来详细谈谈这个发送实际资金的逻辑。 发送内部信息send_raw_message 是一个标准函数,它接受一个带有信息的Cell 和一个包含 mode 和 flag 的 integer(整数). 目前有 3 种模式和 3 个信息标志。您可以将单个模式与多个(也可以一个都没有)标志相结合,以获得所需的模式。简单地说,组合就是获得它们的值之和。关于模式和标志的说明,请参阅下面的表格 this documentation part. 在我们的示例中,我们要发送常规消息并单独支付转账费用,因此我们使用模式 0 和标志 +1 得到 mode = 1. 我们在将其传递到 send_raw_message() 之前编写的信息Cell,可能是目前你需要了解的最高级的内容。 我们将花一点时间来确保这部分内容变得清晰明了。不过,在开始之前,请允许我给你们两个建议: 让我们来分析一下要传入 send_raw_message 的信息Cell的结构。正如你已经理解的那样,我们需要在Cell中放入若干位bits,而哪个位bits负责哪项工作是有一定逻辑的。 信息Cell以 1 位前缀 0 开始,然后是三个 1 位标志,即是否禁用即时超立方路由(目前始终为 true)、如果处理过程中出现错误是否应跳转消息、消息本身是否是跳转的结果。然后,源地址和目标地址被序列化,接着是信息值和四个与信息转发费用和时间有关的整数。 如果从智能合约发送信息,其中一些字段将被改写为正确的值。特别是,验证器将重写 bounced, src, ihr_fee, fwd_fee, created_lt 和 created_at. 这意味着两件事:第一,另一个智能合约在处理信息时可以信任这些字段(发件人可以不伪造源地址、退回标志等);第二,在序列化过程中,我们可以在这些字段中加入任何有效值(无论如何,这些值都会被覆盖). - var msg = begin_cell()! U N1 M4 x" }# f3 Q
- .store_uint(0, 1) ;; tag
" u4 A9 ?' t0 P# h* v* B - .store_uint(1, 1) ;; ihr_disabled. o" v' s8 R g6 ?( U
- .store_uint(1, 1) ;; allow bounces: w+ G7 @( h _9 e5 `; ~
- .store_uint(0, 1) ;; not bounced itself
' G0 Y" }) k1 a) l# v6 H: X - .store_slice(source) ;; ex. addr_none, N3 S8 g' Q6 f' {3 N2 N
- .store_slice(destination)+ T7 e) N0 T4 h5 J! I
- ;; serialize CurrencyCollection (see below)0 t5 v+ W, L+ O; w& Q
- .store_coins(amount)% k' y9 |- g. H% p3 {; l4 V
- .store_dict(extra_currencies)
1 M/ e! i) k0 \7 d' | - .store_coins(0) ;; ihr_fee9 o! s; k3 {. u8 c
- .store_coins(fwd_value) ;; fwd_fee
$ S/ ]& n1 r2 `5 c$ C - .store_uint(cur_lt(), 64) ;; lt of transaction- Y9 ~: S0 [) ~; s; |
- .store_uint(now(), 32) ;; unixtime of transaction
]; w7 Z9 k5 \& | - .store_uint(0, 1) ;; no init-field flag (Maybe)8 \, S, ?3 A! k5 D$ a
- .store_uint(0, 1) ;; inplace message body flag (Either), |1 D) v$ P) N, \! B
- .store_slice(msg_body)) { \2 ]4 I' Z2 }8 X( Y% ^" r
- .end_cell();
复制代码不过,开发人员通常会使用快捷方式,而不是逐步序列化所有字段。因此,让我们举例说明如何从智能合约中发送信息: / X7 M) h* w( l
- var msg = begin_cell()* y0 V$ k0 ]+ Q* a3 f) q
- .store_uint(0x18, 6)5 C3 G: w2 _# H+ o5 @6 @# j
- .store_slice(addr)9 i, l% I' U& ~2 n
- .store_coins(grams)
9 _) H- Z& ?8 @/ A) \( j( W0 u - .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
# T* r$ `+ x- M - .store_uint(op_code, 32)
. w4 M X) a+ U; p6 d; r1 K* ?4 J - .store_uint(query_id, 64);
, S$ s, b# ?9 \6 q; P. \8 B/ e& q -
- @1 d! _# u: k! I9 L; J - send_raw_message(msg.end_cell(), mode);
复制代码我们仔细看看到底发生了什么。 .store_uint(0x18, 6)! x+ M }% o( e4 u
我们开始组成一个Cell时,将 0x18 值转换成 6 位,即 0b011000 如果我们从十六进制翻译过来。这是什么?
2 R' x1 U# ~1 e1 s0b011000 因此,.store_uint(0x18, 6) 是对标签和前 4 个字段进行序列化的优化方法。 .store_slice(addr) - 这一行将目标地址序列化。 .store_coins(grams) - 这一行只是将代币的数量序列化(grams 只是一个带有代币数量的变量)。 .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) - 这个问题很有趣。从技术上讲,我们只是在Cell中写入了大量的零(即零的数量等于 1 + 4 + 4 + 64 + 32 + 1 + 1)。为什么会这样?我们知道,这些值的消耗有一个清晰的结构,这意味着我们放在这里的每一个 0 都是有原因的。% j5 {$ K. r o! C
有趣的是,这些零有两种工作方式--其中一些被设置为 0,因为无论如何验证器都会重写值;另一些被设置为 0,因为目前还不支持该功能(如额外货币)。
9 P# T2 \) P7 S/ j3 |为了确保我们理解为什么会有这么多零,让我们来分析一下它的预期结构: 第一位代表空的额外货币字典。 这样我们就有了两个 4 位长的字段。因为 ihr_fee 和 fwd_fee 将被覆盖,因此我们不妨将其归零。 然后,我们把 0 添加到 created_lt 和 created_at 字段。 这些字段也将被覆盖;不过,与费用相比,这些字段的长度是固定的,因此被编码为 64 位和 32 位长字符串。 下一个零位表示没有初始字段。 最后一个零位表示 msg_body 将就地序列化。 这基本上表示 msg_body 是否带有自定义布局。 : s6 W2 q6 ?, T5 j3 u
.store_uint(op_code, 32).store_uint(query_id, 64); 这部分是最简单的--我们传递的是一个具有自定义布局的信息正文,这意味着只要接收方知道如何处理,我们就可以在其中放置任何数据。
1 }' O, h1 t3 a) r& ?7 f
, P p% L5 }0 [我们看看如何将其应用到我们的提款代码中:
! u+ G8 ]' J& A/ D- if (op == 3) {
: h8 s1 J* P8 w4 b* c - throw_unless(103, equal_slice_bits(sender_address, owner_address));" o! O, W4 F0 g1 ~; I) R, I( m
* }( ^: h; {$ s( m4 `& R- int withdraw_amount = in_msg_body~load_coins();% _% u7 N( r$ _# F7 ~% e$ R* |
- var [balance, _] = get_balance();
: [9 Q5 q( b0 g: o3 W6 K - throw_unless(104, balance >= withdraw_amount);
5 b3 M. _$ |6 ?' X1 s - - h2 W$ D! x5 D$ y
- int return_value = min(withdraw_amount, balance - const::min_tons_for_storage);: x# b* T$ @3 S5 l
- - E+ X5 ^# B' d& T! ` U( w, G; r
- int msg_mode = 1; ;; 0 (Ordinary message) + 1 (Pay transfer fees separately from the message value)$ H' C' w* @ k2 w: P$ p5 H5 [5 B
-
9 v* j6 W: c6 F4 C5 l$ H - var msg = begin_cell()
4 @( y& W3 Z: U, E- I - .store_uint(0x18, 6)
6 V, X) v8 }0 T* g - .store_slice(sender_address); x$ [& v- F+ b# Z6 r
- .store_coins(return_value)1 G5 G; {$ j9 r: Q$ D
- .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1);
- p2 f: `) e6 e* x7 R( _ -
/ R1 M* t) @4 l7 p' H - send_raw_message(msg.end_cell(), msg_mode);
2 M3 {. a! _# s" r4 N -
# |* |9 ?* b9 c, d# }6 h1 R# T# I1 R - return();
, I' p% x0 f9 C5 G6 U. h - }
复制代码 敲定我们的合约代码最后一件事是添加一个新的 getter 方法,用于返回合约余额: - int balance() method_id {
8 C z( l. V. ^ j. L9 G( m* R - var [balance, _] = get_balance();
' K/ A1 _6 o& r: I - return balance;
3 d2 p4 L* Z2 W( ~ - }
复制代码 我们再来看看最终代码的样子:
# O' D- n. I0 A- #include "imports/stdlib.fc";3 A! G( `& K" y8 u3 h8 }* q
. }' j! B7 u9 c m2 N. x- const const::min_tons_for_storage = 10000000; ;; 0.01 TON6 z3 i+ \8 s, k$ @* G* W, Z9 `
0 `& d0 a2 B6 b* M/ O- T- (int, slice, slice) load_data() inline {! `2 `& q0 q# l
- var ds = get_data().begin_parse();
" U0 I1 B5 S x! a( a, G - return (2 e7 R7 }. e, w1 S5 j; ]) x
- ds~load_uint(32), ;; counter_value" t$ E8 u* `; f9 H; S6 X0 F3 h
- ds~load_msg_addr(), ;; the most recent sender
# W( e% H* y' f8 n" y1 k- P( _ - ds~load_msg_addr() ;; owner_address5 z. g) y% h( {4 z0 T: ?
- );
" @# N* S8 J7 f- D4 Q - }
6 K- ^6 T$ g& q; i& O7 g7 A& y - - K1 p; X! u+ G
- () save_data(int counter_value, slice recent_sender, slice owner_address) impure inline {
4 c2 z* V: k; C2 L7 | - set_data(begin_cell()8 l& P/ R% ^. K, d8 J2 s! D6 o
- .store_uint(counter_value, 32) ;; counter_value. L6 j& ?& q: _! [: n/ P! I
- .store_slice(recent_sender) ;; the most recent sender5 Q/ Z. O( u2 D
- .store_slice(owner_address) ;; owner_address
- m0 @4 s9 e" J - .end_cell());* t2 q$ l" U9 t! ^
- }0 B/ j ~+ F& U
5 l# `* p& X7 F! d: U- () recv_internal(int msg_value, cell in_msg, slice in_msg_body) impure {1 e& w% D, D, v4 j
- slice cs = in_msg.begin_parse(); w/ j' b3 m# R, y* v2 t
- int flags = cs~load_uint(4);( M# D6 D5 o8 B8 @! a) R. Z
- slice sender_address = cs~load_msg_addr();
@9 T6 K. O& p. c: N/ ~, Q
$ s+ m8 T3 v5 D& O' z4 V `" j- int op = in_msg_body~load_uint(32);! C& U) e- l9 E- o
& X8 X" i' i9 `. P& B- var (counter_value, recent_sender, owner_address) = load_data();6 m3 C. V" h: b' c T" F
- 9 x$ ]. v: l/ I: I" B; T8 w
- if (op == 1) {/ P% A& K k& p* J! \
- save_data(counter_value + 1, sender_address, owner_address);) e/ D' M" n6 u' a' G
- return();
7 ^$ ^; d2 }" s( n6 O& K6 H - }* A* c. l' Z6 d( K& e
4 W- `5 ^& Q5 ^2 E9 z- if (op == 2) {
8 Y) S) p( K, N$ D7 \ - return();0 Z4 t1 W3 ~7 M& Y& |0 u; d5 o5 s2 I, y
- }
0 Y; q3 Q% v/ [0 b7 z - . G s! ~. i- w8 }! W
- if (op == 3) {. ]$ d" a; ~$ T/ l
- throw_unless(103, equal_slice_bits(sender_address, owner_address));
) W# Z$ e1 q8 d1 K r9 `0 P - : q% s8 Y5 P9 Z( f7 U/ D5 R/ l
- int withdraw_amount = in_msg_body~load_coins();
4 b2 `" \% G' S* ^6 E - var [balance, _] = get_balance();
1 j' i+ I9 x- v9 \4 ] - throw_unless(104, balance >= withdraw_amount);+ v' `" [0 x0 d/ H4 c
M$ q7 F: L4 q3 P9 O1 |- int return_value = min(withdraw_amount, balance - const::min_tons_for_storage);: J( Z. z$ q; r
- * i- e' x; x! \
- int msg_mode = 1; ;; 0 (Ordinary message) + 1 (Pay transfer fees separately from the message value)
: Z3 K. y! T) q1 b. w
. p$ t: P6 d* N9 P$ r H9 ]. q- var msg = begin_cell()
1 J& P8 Q4 e8 A, I6 S5 W - .store_uint(0x18, 6)& _8 |1 }$ _# h f# I* W; k
- .store_slice(sender_address)
w* y1 `3 q: ?9 l1 S: z! Z - .store_coins(return_value)' Q% {$ H: @% e" A8 }
- .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1);4 I1 t; A3 @$ P4 u9 H% H( O
- ! y5 y0 A1 K/ Y! v c
- send_raw_message(msg.end_cell(), msg_mode);3 w) _" J' ?% Y+ K! U
! ^8 I5 r6 Y3 E- return();
" h# v( l8 B! z( r4 L3 L+ j: H - }$ k8 @1 X4 O* S# }8 g0 y; l+ g4 e
# T+ h7 L7 T- T' _; A- throw(777);
$ ]2 ~5 R8 e/ h( |; |3 p - }
. H5 d% N2 H. W7 B' g - 3 J' t: o% L! j& }* a
- (int, slice, slice) get_contract_storage_data() method_id {
9 w5 q! Q" {2 s* `4 Q) Y" B6 ^ - var (counter_value, recent_sender, owner_address) = load_data();
% V o2 N, K3 v; x) e2 ] - return (
. F J7 ]! J. d( ^ - counter_value,
2 m1 q, k, i- x - recent_sender,
4 {1 u6 @& G8 }4 n - owner_address+ V4 p! X/ R% A7 C/ P
- );1 D; h$ e2 O* }' [2 P6 \
- }
8 X7 u! U2 f3 c6 ~2 a& ^ - ) H: X" u: l7 z& p; ^; l
- int balance() method_id {
: C3 c1 W! ?. A9 y" I( R - var [balance, _] = get_balance();% n [6 \ {! a; e. ?: ^
- return balance;3 M% @9 W. S4 x/ v2 q) E& I2 T
- }
复制代码 我们检查一下我们的代码是否能用 yarn compile 然后 让我们开始为更新后的合约编写测试。
3 a& V0 G# O. Z
0 \( |$ G$ A4 O# m( P6 _) K, p( \- L; ~
|