( ?& C9 h8 ?% U' a" s3 `% r: H
典型的信息处理程序TON 中典型的信息处理程序就是采用这种方法: - () handle_something(...) impure {; V0 ? ?( M& t( ]+ g f% [9 K
- (int total_supply, <a lot of vars>) = load_data();5 H9 d* J* C- w$ j; Z) X/ u6 q, }
8 y8 L, [! S8 I& ]4 {- ... ;; do something, change data- O) n, s# }" B6 T5 `. d
" a2 i6 Z% a& x! d q- save_data(total_supply, <a lot of vars>);
( `' \, P8 e: [: B. L8 M - }
复制代码不幸的是,我们注意到了一种趋势: <a lot of vars> 是对所有合约数据字段的真正枚举。例如 - (int total_supply, int swap_fee, int min_amount, int is_stopped, int user_count, int max_user_count, , q2 @$ a" S$ B; z' k. O4 S
- slice admin_address, slice router_address, slice jettonA_address, slice jettonA_wallet_address,
U+ m/ K3 _0 ^; T - int jettonA_balance, int jettonA_pending_balance, slice jettonB_address, slice jettonB_wallet_address, k9 [; }" d" F: r6 E" P
- int jettonB_balance, int jettonB_pending_balance, int mining_amount, int datetime_amount, int minable_time, " H; R$ a# U N
- int half_life, int last_index, int last_mined, cell mining_rate_cell, cell user_info_dict, cell operation_gas, 4 }% k$ X) I7 ?; o
- cell content, cell lp_wallet_code) = load_data();
复制代码这种方法有许多缺点。 ( @' I, e1 t4 U! I8 D2 X; \1 M
问题 1:难以更新存储结构增加一个字段就需要更新整个合约。 ) [( H2 l, K8 m* ?! L! d e
首先,如果您决定添加另一个字段,例如 is_paused,那么您就需要更新整个合约中的 load_data()/save_data() 语句。这不仅耗费大量人力,还会导致难以捕捉的错误。 在最近的一次 CertiK 审核中,我们注意到开发人员在某些地方混淆了两个参数,并写道: - save_data(total_supply, min_amount, swap_fee, ...
% s8 b7 J+ F0 P) E/ I - instead of
, f) ~3 E, l! i; o5 } - save_data(total_supply, swap_fee, min_amount, ...
复制代码 在没有专家团队进行外部审计的情况下,很难发现这样的漏洞。出现错误的函数很少被使用,两个混淆的参数值通常都为零。要发现这样的错误,你必须知道自己在寻找什么。9 R s+ V4 X- X, j v; _
问题 2:命名空间污染读取当前命名空间的所有存储字段会污染命名空间。
$ t, S/ n+ y: A5 `, a
其次是 "命名空间污染"。让我们用审计中的另一个例子来解释问题所在。在函数的中间部分,输入参数为 - (int total_supply, int swap_fee, int min_amount, <a lot of vars>) = load_data();' X P" U: t8 m
- ...
`! i% G5 x, a8 C - int min_amount = in_msg_body~load_coins();
( ?: v* h! L2 i8 A& M1 A - ...5 L; u0 m2 W3 F5 \5 K5 Q; J0 x' h
- save_data(total_supply, swap_fee, min_amount, <a lot of vars>);
复制代码也就是说,局部变量对存储字段进行了阴影处理,在函数结束时,这个被替换的值被存储在存储空间中。攻击者有机会覆盖合约的状态。 FunC 允许重新声明变量。
: W, m4 q! I8 ]8 L% S6 \, ~ 问题 3:GAS成本增加最后 解决方案 1: 使用全局变量在原型设计阶段,由于合约中存储的内容并不完全明确,因此可以使用全局变量。 - global int var1;
& R' J, |* Q1 }! p7 F/ r - global cell var2;
( p* d* y+ w- F; u- @ - global slice var3;
9 }2 u, R+ ?& s; W+ n5 Z% ~8 U
* m! S2 A9 n4 p: B$ [& ]% |5 g- () load_data() impure {
$ i9 b L/ w4 D9 P* t) S3 k - var cs = get_data().begin_parse();' u4 R8 i1 q/ S
- var1 = cs~load_coins();
; K' ]+ K0 R& k- b) Q) Q. O - var2 = cs~load_ref();
6 u- `% ~" y$ A" C& ~- y. a - var3 = cs~load_bits(512);
. w( S2 @- Y, I7 K7 U+ J% \6 x7 \ - }& c9 E4 x- r+ h, [; h/ s
- & I& |3 ^5 o- p" ]' ?2 @
- () save_data() impure { l& R H8 B' u/ ?9 v0 O
- set_data(
7 T8 {1 H0 @* k; ]/ g8 d+ f - begin_cell()
3 P) v: `$ h4 Q1 f+ }" ? - .store_coins(var1)1 d. J v) S$ j
- .store_ref(var2)/ F4 q- d' |1 V5 [8 p
- .store_bits(var3)- \% e# Q P8 D& s( E. K" z% Q' o
- .end_cell()
$ \; d1 Y$ D9 u6 e1 L" b$ p0 ~9 \/ x - );8 t& A5 m: p( K! W5 s7 ]
- }
复制代码这样,如果发现需要另一个变量,只需添加一个新的全局变量并修改 load_data() 和 save_data()。整个合约无需任何改动。但是 全局变量不能超过 31 个。 全局变量比在堆栈中存储更昂贵。
5 _3 A& u' Z" I5 W, E2 m+ ] % N4 o1 y# J: a( O6 Y2 C: v
解决方案 2:使用 "嵌套 "存储原型设计完成后,我们建议采用这种存储组织方法: - () handle_something(...) impure {
' z% |" ?+ j. |: p - (slice swap_data, cell liquidity_data, cell mining_data, cell discovery_data) = load_data();4 W( B" Y7 b+ F
- (int total_supply, int swap_fee, int min_amount, int is_stopped) = swap_data.parse_swap_data();( v9 \4 ~2 R! Y0 l" D; _2 z
- …
; g: l @* q; u1 D7 T, X - swap_data = pack_swap_data(total_supply + lp_amount, swap_fee, min_amount, is_stopped);
1 q' z Y- j; t2 z' s - save_data(swap_data, liquidity_data, mining_data, discovery_data);
2 `. m8 t& M6 |6 E" p3 A3 c - }
复制代码存储空间由相关数据块组成。如果每个函数都使用一个参数,例如 is_paused,那么 load_data() 会立即提供该参数。如果一个参数组只在一种情况下需要使用,那么它就不需要解压缩,也不需要打包,更不会污染命名空间。 如果添加了新变量,需要更新的代码片段就会减少。
1 X1 o8 N% k$ r) P8 Q* z
如果存储结构需要更改(通常是添加一个新字段),那么需要进行的编辑就会大大减少。 嵌套变量可以再嵌套。
8 I! v0 Y2 N; `9 z: ]8 X6 q) d! N8 o
此外,这种方法还可以重复使用。如果我们的合约中有 30 个存储字段,那么一开始你可以得到四组,然后从第一组中得到几个变量和另一个子组。最主要的是不要做得太过分。 请注意,由于一个Cell最多可存储 1023 位数据和 4 个引用,因此无论如何都必须将数据拆分到不同的Cell中。 分层数据是 TON 的主要功能之一,让我们将其用于预期目的。 使用 end_parse()由于 TON 使用的是可变数据格式的位(bit)流,因此确保读取的数据量与写入的数据量相等是很有帮助的。这样可以节省一个小时的调试时间。 使用辅助函数,避免幻数这段代码来自一个真实的项目。由于有大量幻数,即使是经验丰富的开发人员也会被吓到 - var msg = begin_cell()
% C8 ^: z h- D$ e8 w1 }: z - .store_uint(0xc4ff, 17) ;; 0 11000100 0xff& @/ e/ E5 [, b" D3 I* C: K" \0 ~
- .store_uint(config_addr, 256)
3 Q0 B" S$ ^1 I - .store_grams(1 << 30) ;; ~1 gram of value6 X& G3 b$ L, e7 O
- .store_uint(0, 107)
: n+ i. _9 `% b) G6 m - .store_uint(0x4e565354, 32)
. D, R) h5 N7 A4 u$ @$ S2 B6 { - .store_uint(query_id, 64)8 f5 t8 c) e8 x
- .store_ref(vset);
0 v- N8 w+ _" _# ?* C5 j; P9 i -
4 U. A9 ?% x1 D3 y4 L6 E - send_raw_message(msg.end_cell(), 1
复制代码根据需要引入尽可能多的常量和封装器,以实现代码的表现力
/ F4 K8 p) q" G- var msg = begin_cell()9 M( \5 v1 ^5 J9 n; {: k4 A" a
- .store_msg_flags(BOUNCEABLE)$ O; H% b6 m* E! ?$ @0 ]3 W. X0 b
- .store_slice(to_wallet_address)2 j' ~: C- W$ W0 v
- .store_coins(amount)
8 E" n6 D% Z0 h, D0 o1 j - .store_msgbody_prefix_stateinit()0 q2 A$ o- v) o. e8 s
- .store_ref(state_init)
. o6 M: M0 R$ t) R* x2 s - .store_ref(master_msg);
+ [& {& T+ u0 W& u
+ b2 \' j# }# U0 w- send_raw_message(msg.end_cell(), SEND_MODE_PAY_FEES_SEPARETELY);
复制代码 错误示例不要忘记所有与 TON 无关的传统陷阱和潜在错误。下面是一个实际项目中的例子。 - () handle_transfer(...) impure {9 {' }9 J" S, C, K) A
- ...% P1 D2 q6 ]2 B7 y- }
- (slice from_user_info, int from_flag) = user_info_dict.udict_get?(256, from_addr_hash);
# O0 ^, K. F$ o% g: Y% Q - int from_balance = from_user_info~load_coins();
0 G+ T7 y4 |- F) y, G - ...
+ h- s3 g' w. @4 D+ `% Z; b( O% w: T - (slice to_user_info, int to_flag) = user_info_dict.udict_get?(256, to_addr_hash);
, S$ k5 Q9 V" G: F. [. Q - int to_balance = to_user_info~load_coins();
( i3 Y$ z6 r# h& L( |1 Y - ...
1 g c0 D' @- Y" R* \/ G, v - ;; save decreased from_balance to user_info_dict
& Y6 h* ~9 o+ c! O - ;; save increased to_balance to user_info_dict3 G$ `7 x, a4 S, X
- }
复制代码由于 to_balance 会覆盖已清零的 from_balance,因此向同一地址转账实际上会使余额翻倍。
6 A( [9 J# x- I1 j% |3 Y5 M摘要6 q5 E1 p- s2 }2 v6 y
" M3 Z4 S4 Y) H; y) z2 e! X
' A" Y+ F( a! V/ `; y( P9 c* p
$ n' x/ T" h7 ~. P( N/ d
|