本文深入探讨了TON区块链中费用计算的复杂性以及不正确的gas预估可能导致的严重问题。TON 使用异步消息传递和细粒度的费用模型,开发者需要仔细验证消息附带的 TON 数量,以确保能够支付所有可能的执行分支的费用,包括计算费、转发费和存储费,尤其是在处理回弹消息时。从 TVM 12 开始,回弹消息的成本增加,使得保守的费用预估变得更加重要。
.png)
2025年12月16日
TON 是一种新型区块链,它结合了复杂的费用系统和异步消息。我们探讨了 TON 中如何计算费用,以及为什么正确估计费用至关重要。
开放网络 (TON) 是一种 Layer-1 区块链,最初由 Telegram 设计,现在由一个独立的社区维护。它结合了分片架构、异步消息传递和高度精细化的费用模型,以支持高吞吐量的智能合约执行。其核心是 TON 虚拟机 (TVM),它使合约能够对存储和传输的数据进行细粒度、位级别的控制。
TON 使用精细化的费用模型,费用以网络的原生货币 Toncoin (TON) 支付。每个合约都有一个 TON 余额,消息可以携带 TON,方式类似于 EVM 中的 msg.value。费用可以从两个来源支付:传入消息值被计入之前的合约余额,或者消息值被计入之后的余额。在第一种情况下,合约必须已经持有足够的 TON 来支付费用,而与消息值无关。在第二种情况下,费用仅在转移的值添加到合约余额后才扣除。
一般来说,有三种类型的费用需要考虑:
计算费用
与以太坊类似,TVM 执行的每条指令都有 gas 单位的成本。执行成本取决于交易期间消耗的 gas 单位。一个 gas 单位的价格在区块链配置中是固定的,只能通过验证者投票来改变。
简而言之,计算费用与计算费用价格和计算消耗的 gas 成正比。
$comp\_fee = comp\_fee\_price \times gas\_consumed$ (简化)
除非合约明确同意从自己的余额中支付计算费用(TVM 有一个特殊的指令用于此),否则计算费用不得超过消息值。如果附加的资金不足以支付计算费用,则执行会回滚。
存储费用
这是在区块链上存储合约所需支付的金额。
这些费用与合约的大小成正比,并且在合约部署的每秒都会累积。当合约收到消息时,存储费用会被结算。
$storage\_fee = storage\_fee\_price \times contract\_size\_bits \times seconds\_elapsed\_since\_last\_settle$ (简化)
通常,存储费用从消息值被计入之前的合约余额中支付。因此,合约必须有足够的资金来支付自己的租金。当余额不足时,存储费用会作为债务累积,如果未偿还,最终可能导致合约从链中移除。
转发费用
转发费用是为在合约之间发送消息而支付的成本。
转发费用与转发费用价格和消息大小成正比。
$fwd\_fee = fwd\_fee\_price \times msg\_bits$ (简化)
对于计算费用,通常预计附加到消息的资金足以支付转发费用。如果合约无法支付转发费用,则执行会回滚。
TON 消息是异步的,这意味着合约无法同步查询另一个合约,并在同一交易内立即根据结果继续执行。相反,响应可能会在稍后传递。
考虑以下示例,其中 Alice 的 USDC 钱包(合约 A)将代币发送到 Bob 的 USDC 钱包(合约 B)。
A 持有 10 USDC 的余额,B 持有 0 USDC 的余额。
Alice 调用 A.transfer(Bob, 8 USDC)。
A 计算 balance = 10 - 8 = 2 USDC
A 发送消息给 B。m = (transfer, 8 USDC)
B 接收 m,并通过计算 balance = 0 + 8 = 8 USDC 来增加其余额
转移成功:A 持有 2 USDC 的余额,B 持有 8 USDC 的余额。

现在让我们考虑 B 在处理 A 发送的消息时回滚的情况。与 EVM 不同,B 回滚不会导致 A 也原子性地回滚。因此,A 必须实现一种机制来检测 B 是否已回滚并采取相应措施。这是消息反弹的目标。
如果 B 在处理消息 $m$ 时回滚,则消息会被反弹,这意味着 $m$ 会被加上一个反弹标志并发送回发送者。
回到上面的例子,假设 Bob 的钱包 (B) 被列入黑名单,因此无法接收资金。
如上所述,A 发送消息给 B。m = (transfer, 8 USDC)
B 接收 $m$ 并回滚,将反弹标志添加到 $m$ 前面,产生 m' = (BOUNCED, transfer, 8USDC)
A 接收 m',检测到它是来自 B 的反弹消息,并通过计算 balance = 2 + 8 = 10 USDC 来撤消转移
转移失败,状态恢复到初始状态:A 持有 10 USDC 的余额,B 持有 0 USDC 的余额。

如所示,反弹消息及其正确的处理对于保持状态一致性至关重要。
我们已经发现了反弹消息的作用有多重要,但我们也注意到发送消息需要足够的资金。当合约耗尽余额,因此无法发送反弹消息时,就会出现一个有问题的场景。考虑一下前面示例中的这种情况。

为了防止上述情况发生,开发人员必须在调用路径的最开始验证附加到消息的 TON 数量。只有在附加的资金足以支付所有可能执行分支的所有费用时,合约执行才能继续进行。这确保了,无论执行如何展开,所有合约最终都将处于一致的状态。
在实践中,这意味着合约应该保守地估计 gas 使用量,并明确要求:
msg_value ≥ expected_compute_fee + expected_forwarding_fee
只有在满足此条件后,合约才能继续执行。
此外,如果预计执行流程会部署一个新的合约,则估算还必须包括足够的 TON 来支付新合约的存储费用。换句话说,部署应该使合约有足够的余额来支付其自身的租金一段时间,这段时间足以完成其目的。
为了估计这些费用,开发人员可以利用以下 TVM 指令:
从 TVM 12 开始,由于反弹消息的序列化和转发方式的更改,反弹消息的成本有所增加。特别是,反弹消息现在包含更多上下文数据,这增加了它们的大小,并因此增加了它们的转发费用。开发人员应考虑在包含反弹消息的调用路径中更高的转发成本。
- 原文链接: chainsecurity.com/blog/t...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!