本文介绍了智能合约中常见的舍入误差问题,特别是在 Solidity 等不支持浮点数的语言中。文章讨论了精度损失、不正确的舍入方向以及舍入为零等问题,强调了在智能合约开发中需要特别注意除法运算,以避免潜在的安全漏洞和价值损失。同时,作者建议开发者在进行除法运算时,应始终考虑舍入方向以及舍入为零的可能性,并推荐了一些有用的文章和视频资源。
你对舍入误差的思考是错误的
90 8.5K 舍入问题是 Solidity 和其他智能合约语言中常见的问题,特别是因为没有浮点数(Floating point arithmetic),那么我们如何处理小数?我们使用精度,以 ERC20 标准的小数为例,它使用 10^18 来表示一个 token 的单位,在这种情况下,要表示 0.5 个 token,你将使用 5*10^17。
在 Solidity、Rust 和许多其他语言中,整数除法会向零截断,我们通常称之为“向下舍入”。这意味着计算机只是丢弃小数部分,只保留整数部分。这就是为什么在 Solidity 中 1/2、100/200 等于零,或者 1000/550 等于 1。
因此,在智能合约中,总是会存在 1. 精度损失,特别是在乘法之前进行除法时。如果先进行除法,结果会永久减少(向下舍入),然后乘法只会放大减少的值:
在上面的例子中,feeAmount 的计算方式是 amounInterest 乘以 poolFee,但 amounInterest 已经执行了一次除法,正确的方法应该是先做乘法,最后做除法:
始终先乘后除。这在任何竞赛或私有审计中都不再是一个问题,但仍然是一个很酷的优化/低风险漏洞,你需要注意它。[1],[2],[3].
好的,但是什么时候精度损失会成为问题?当这种舍入方向不利于协议时,这里就出现了第二个问题,2. 不正确的舍入方向。
在上面的例子中,shares 使用标准的整数除法计算,这会截断(向下舍入)结果并丢弃任何小数部分,这导致 shares 值低于它应该有的值,因此用户的 share balance 也会减少,比它应该减少的量要少,从而为用户留下额外的 shares。随着时间的推移,这些额外的 shares 会不断累积,协议会向每次提款的用户泄漏价值。那么如何解决这个问题呢?向上舍入这个值,有一些库可以实现显式的向上舍入,你应该使用已建立的定点数学库,如 OpenZeppelin 的 Math 或 Solady。不正确的舍入方向是智能合约安全中常见的错误,通常是私有和公共审计中的中等风险漏洞。[1][2][3]
, 这里有一些在野外实际被利用的黑客攻击。这个糟糕的 6000 万美元的黑客攻击 , zklend exploit 好的,但是我们如何使事情变得更严重?我们已经在开始时说过,在 solidity 中 1/2, 100/200 等于零,3. 舍入为零。如果上面例子中的 shares 舍入为零,用户将收到 token 而不会减少他的 shares,通过这种方式,攻击者可以通过提取使 shares 舍入为零的金额来耗尽资金池。每次你看到除法时,你都必须考虑这个值是否可以舍入为零。看看这个 10 万美元的赏金 ,看看这个最近的黑客攻击:
// 假设 poolSize 为 1000,depositAmount 为 1
// shares = (depositAmount * totalSupply) / poolSize
shares = (1 * 1000) / 1000; // shares = 1
另一个常见的问题是费用舍入为零:
在上面的例子中,用户只需存入少量的 token 就可以绕过费用,类似的问题我在 @Uniswap 竞赛中获得了 5 位数的奖励:
// 假设 fee 为 0.01%,depositAmount 为 1
// feeAmount = depositAmount * fee
feeAmount = 1 * 0.0001; // feeAmount = 0
另一个例子是著名的 erc4626 标准中的通货膨胀攻击,它包括抢先交易第一个存款人,直接向合约发送 token,使存款人的 shares 值舍入为零。
但有时,即使你的舍入方向是好的,协议仍然可能通过存入或提取少量资金来夸大汇率而被黑客攻击,这使得调用者基本上每次调用都捐赠价值,但这只发生在一些奇特的逻辑中,研究一下这些黑客攻击:
// 攻击者存入少量 token,使汇率发生巨大变化
// 汇率 = (poolSize + tinyAmount) / totalSupply
exchangeRate = (1000 + 0.000001) / 1000; // 汇率略有增加
那么如何在智能合约中考虑舍入问题?每当你遇到除法时,无论是标准的整数除法还是定点算术(MulDivUp 或 MulDivDown 函数),你都应该问自己:
这个除法是否有利于协议?
如果这个除法舍入为零会发生什么?
你会惊讶地发现,在私有审计和公共竞赛中,某个操作的舍入方向是错误的频率有多高。但即使舍入方向是“正确的”(保守且有利于协议),真正的危险通常在于舍入为零。
一个微小的分数金额可能会完全消失,锁定价值,没收费用,或产生无法认领的灰尘。一旦你确定了一个操作可以舍入为零的地方,那就是你的起点,从那里可以产生创造性的漏洞(绕过费用,锁定用户余额,意外的协议收入损失,汇率操纵)。
将每个除法视为潜在的漏洞。提出以上两个问题,测试零的情况,你将会比以前发现更多的错误。
另一篇好文章:来自 @DevDacian 的 精度损失错误。危险的小数 精彩的 youtube 视频。
- 原文链接: x.com/tamayonft/status/2...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!