' I- k% V* L$ |6 D$ v典型的信息处理程序TON 中典型的信息处理程序就是采用这种方法: - () handle_something(...) impure {
3 G* x7 w, u8 J* a7 O. j! c - (int total_supply, <a lot of vars>) = load_data();8 M; h& A& r: r- x
1 C% F% H2 v4 P: X- ... ;; do something, change data
9 R' Z+ K j) ~2 z6 X- [4 w
1 J9 r/ A8 N( a& Y6 n& v1 M9 N- save_data(total_supply, <a lot of vars>);; B; o+ |6 s; f4 P8 q- x
- }
复制代码不幸的是,我们注意到了一种趋势: <a lot of vars> 是对所有合约数据字段的真正枚举。例如 - (int total_supply, int swap_fee, int min_amount, int is_stopped, int user_count, int max_user_count,
2 d. K i9 n% G9 N - slice admin_address, slice router_address, slice jettonA_address, slice jettonA_wallet_address, * |2 Q' _" i% r0 G$ T
- int jettonA_balance, int jettonA_pending_balance, slice jettonB_address, slice jettonB_wallet_address,
! F' E% | N7 s& O' ? - int jettonB_balance, int jettonB_pending_balance, int mining_amount, int datetime_amount, int minable_time, ' d& \# o' q) n: O7 ^
- int half_life, int last_index, int last_mined, cell mining_rate_cell, cell user_info_dict, cell operation_gas,
* w9 D( l7 h0 O( I P5 ~, a - cell content, cell lp_wallet_code) = load_data();
复制代码这种方法有许多缺点。 * J3 V1 A7 ~ {7 P1 @( w
问题 1:难以更新存储结构增加一个字段就需要更新整个合约。 ; |; `0 D ?. k' j
首先,如果您决定添加另一个字段,例如 is_paused,那么您就需要更新整个合约中的 load_data()/save_data() 语句。这不仅耗费大量人力,还会导致难以捕捉的错误。 在最近的一次 CertiK 审核中,我们注意到开发人员在某些地方混淆了两个参数,并写道: - save_data(total_supply, min_amount, swap_fee, ...
, u& S0 ?; M8 s- {% ~ - instead of" l# R4 C1 ]( R* h, I" j
- save_data(total_supply, swap_fee, min_amount, ...
复制代码 在没有专家团队进行外部审计的情况下,很难发现这样的漏洞。出现错误的函数很少被使用,两个混淆的参数值通常都为零。要发现这样的错误,你必须知道自己在寻找什么。' Q' ~8 V R8 K+ W+ c$ v2 x3 [: H
问题 2:命名空间污染读取当前命名空间的所有存储字段会污染命名空间。
+ `( W/ Z6 Q0 y
其次是 "命名空间污染"。让我们用审计中的另一个例子来解释问题所在。在函数的中间部分,输入参数为 - (int total_supply, int swap_fee, int min_amount, <a lot of vars>) = load_data();
$ j ]: l# C) b - ...
8 f) z' }5 @0 R \# U9 W) U. { - int min_amount = in_msg_body~load_coins();
' Y5 F+ Z6 Q8 O( c - ...) e3 D' i4 ?+ N, z7 c8 h5 q
- save_data(total_supply, swap_fee, min_amount, <a lot of vars>);
复制代码也就是说,局部变量对存储字段进行了阴影处理,在函数结束时,这个被替换的值被存储在存储空间中。攻击者有机会覆盖合约的状态。 FunC 允许重新声明变量。 9 t# u% x, O) ~: Y' d1 N
问题 3:GAS成本增加最后 解决方案 1: 使用全局变量在原型设计阶段,由于合约中存储的内容并不完全明确,因此可以使用全局变量。 - global int var1;
* G9 k8 I" Z, F+ Y" @2 v. M( l1 U - global cell var2;
1 C, Q3 l* h( P: E6 f. _ - global slice var3;
8 g1 W1 j* D w" ]/ i* \ - 2 ]. u, s$ o3 l+ `) ?: ]
- () load_data() impure {0 `: k4 m" g' _4 f; c! z
- var cs = get_data().begin_parse();; K: p: c0 `/ b) d# C! W% J
- var1 = cs~load_coins();
0 l+ J% K3 |! ^& Z3 Y1 s+ }3 j - var2 = cs~load_ref();
! V8 G* k. ~( B m6 Z8 u2 M g - var3 = cs~load_bits(512);
+ h. N2 D( E l& T - }
' k) ~2 V* X6 B8 E - - P9 C$ A9 Q4 W2 e7 q) u; f& o
- () save_data() impure {+ l+ g& C' }4 F
- set_data(
. z+ P1 b! Q3 K! t ^ - begin_cell()4 ?9 q8 \! c) A0 q
- .store_coins(var1)
k# e# w: z: C$ W! }4 E) q& ^# t" W - .store_ref(var2)
' L+ f* l- {1 ~2 b - .store_bits(var3)4 A+ H2 g/ Y: s. z! g% M2 Z
- .end_cell()( F' @" j2 O0 C2 T1 k
- );
0 q2 m/ W( q" Q2 P9 E" | a - }
复制代码这样,如果发现需要另一个变量,只需添加一个新的全局变量并修改 load_data() 和 save_data()。整个合约无需任何改动。但是 全局变量不能超过 31 个。 全局变量比在堆栈中存储更昂贵。 0 A: Y( r2 s7 T) ]4 i9 k
9 a7 Z' w5 X Y) {
解决方案 2:使用 "嵌套 "存储原型设计完成后,我们建议采用这种存储组织方法: - () handle_something(...) impure {
" z6 {8 B1 y7 G c3 ]- g5 a( n0 E, j - (slice swap_data, cell liquidity_data, cell mining_data, cell discovery_data) = load_data();& v" D0 O) \$ D* w" {. w
- (int total_supply, int swap_fee, int min_amount, int is_stopped) = swap_data.parse_swap_data();
/ n% I6 w5 [: `* ?( H - …
% ?7 @ L- Q6 A2 O - swap_data = pack_swap_data(total_supply + lp_amount, swap_fee, min_amount, is_stopped);
& C1 y6 ~; m8 ? - save_data(swap_data, liquidity_data, mining_data, discovery_data);. G1 X* R) h- |
- }
复制代码存储空间由相关数据块组成。如果每个函数都使用一个参数,例如 is_paused,那么 load_data() 会立即提供该参数。如果一个参数组只在一种情况下需要使用,那么它就不需要解压缩,也不需要打包,更不会污染命名空间。 如果添加了新变量,需要更新的代码片段就会减少。
7 ?1 c+ B+ b1 E8 O. M' U0 h
如果存储结构需要更改(通常是添加一个新字段),那么需要进行的编辑就会大大减少。 嵌套变量可以再嵌套。 4 ?* q# O9 u$ c5 g
此外,这种方法还可以重复使用。如果我们的合约中有 30 个存储字段,那么一开始你可以得到四组,然后从第一组中得到几个变量和另一个子组。最主要的是不要做得太过分。 请注意,由于一个Cell最多可存储 1023 位数据和 4 个引用,因此无论如何都必须将数据拆分到不同的Cell中。 分层数据是 TON 的主要功能之一,让我们将其用于预期目的。 使用 end_parse()由于 TON 使用的是可变数据格式的位(bit)流,因此确保读取的数据量与写入的数据量相等是很有帮助的。这样可以节省一个小时的调试时间。 使用辅助函数,避免幻数这段代码来自一个真实的项目。由于有大量幻数,即使是经验丰富的开发人员也会被吓到 - var msg = begin_cell()9 I! h# f3 i* n# \# d
- .store_uint(0xc4ff, 17) ;; 0 11000100 0xff1 E S- g3 V( r+ Y; F! i# ~6 n
- .store_uint(config_addr, 256)
; s2 h" ^* B6 x0 w& W) c" B - .store_grams(1 << 30) ;; ~1 gram of value7 r F1 T8 @6 S1 `1 Y$ _5 m
- .store_uint(0, 107)0 M: H: j$ u* ?) p4 q/ l- Q4 z* D3 `
- .store_uint(0x4e565354, 32)
3 E# f1 G9 w2 D4 p! L - .store_uint(query_id, 64)' C l: Y: k. \+ W m2 I
- .store_ref(vset);
5 P9 R5 A& U5 M b -
8 I' `/ E; X0 R0 Q0 `. c1 G! V: r - send_raw_message(msg.end_cell(), 1
复制代码根据需要引入尽可能多的常量和封装器,以实现代码的表现力
9 r$ j; e5 T/ _$ `1 I0 G: W7 V- var msg = begin_cell()! X5 u, [' \2 S: Y! l8 b% } C* o
- .store_msg_flags(BOUNCEABLE)& O: w! D( q) x9 ~- Y
- .store_slice(to_wallet_address)
4 r/ Y/ c8 I9 C0 ]( }: A. } - .store_coins(amount)
. F/ s) k8 v& S$ _) Q$ w6 y$ W - .store_msgbody_prefix_stateinit()( U2 d% u; L% V& ^
- .store_ref(state_init)) w) S' q# o8 ~3 X" q7 Y
- .store_ref(master_msg);
6 }$ `: P, B4 ^! k: M0 n - 5 w7 p/ ?. S9 H" Y" K
- send_raw_message(msg.end_cell(), SEND_MODE_PAY_FEES_SEPARETELY);
复制代码 错误示例不要忘记所有与 TON 无关的传统陷阱和潜在错误。下面是一个实际项目中的例子。 - () handle_transfer(...) impure {1 J% ` v) `* C; p& t' D4 n
- ...
+ j1 A: i. f6 E. b+ r- c& T7 V X# c - (slice from_user_info, int from_flag) = user_info_dict.udict_get?(256, from_addr_hash);
* ?0 v- i1 R6 y4 ?* e - int from_balance = from_user_info~load_coins();
( @" {. w& @( K2 S0 G9 i7 m - ...
3 u- I3 C. v B2 X9 u9 h$ z - (slice to_user_info, int to_flag) = user_info_dict.udict_get?(256, to_addr_hash);
! N8 F, ^6 I- |" r0 R: r, h/ H; I - int to_balance = to_user_info~load_coins();+ D2 E8 S- B% I3 g' B; v/ X4 Q
- ...
3 y* P! K1 \1 D2 I - ;; save decreased from_balance to user_info_dict& g! ]% u0 s& s$ L
- ;; save increased to_balance to user_info_dict/ M/ ?+ m# ~- r0 z
- }
复制代码由于 to_balance 会覆盖已清零的 from_balance,因此向同一地址转账实际上会使余额翻倍。
/ B9 u& R) j3 m摘要$ h. G# q$ A& H) d
8 J8 ?; B6 G7 K9 I2 l6 v
# Q, L. x! c+ @9 N6 s( G% I0 r( j) h+ I" F
|