当前位置: 首页 > article >正文

第 5 章 | Solidity 合约中的整数溢出与精度陷阱全解析

🧮 第 5 章 | Solidity 合约中的整数溢出与精度陷阱全解析

——YAM 崩盘与算术误差复盘,如何正确使用 SafeMath 与 unchecked?


✅ 本章导读

在 Solidity 合约开发中,最危险的错误往往不是逻辑问题,而是你以为算对了,但其实错了

合约里的资产转账、比例分配、利息计算、奖励发放,全都涉及到整数运算精度控制
一旦数值不准,不是项目经济模型失衡,就是直接被套利、爆仓。

本章我们将系统拆解 Solidity 合约中常见的数值问题:

  1. 整数溢出与下溢(0.8.x 以后真的安全吗?)

  2. 运算顺序导致的精度丢失(浮点错误在 Solidity 是常态)

  3. decimals 精度管理误差(ERC20 中最常见的坑)

  4. unchecked{} 的使用与滥用

  5. 真正安全的乘除方式写法模板

  6. 真实事故复盘(YAM、Balancer)


1️⃣ 什么是整数溢出 / 下溢(Overflow / Underflow)?

✅ 概念

  • Solidity 使用固定大小的整数类型,如 uint256(0 ~ 2²⁵⁶-1)

  • 溢出(Overflow):变量加法超过最大值 → 回绕成 0

  • 下溢(Underflow):减法减出负数(uint 无法表示) → 回绕成最大值


✅ 例子(Solidity 0.7.x 以下)

uint8 a = 255;
a += 1; // ❌ 溢出 → a = 0
uint8 b = 0;
b -= 1; // ❌ 下溢 → b = 255

✅ Solidity 0.8.x 后自动加入检查

// Solidity 0.8+ 中这段代码将 revert:
uint256 a = 2**256 - 1;
a += 1; // 报错:overflow

✅ 但为什么 0.8.x 依然可能出错?

因为很多开发者在 unchecked{} 中包裹运算:

function uncheckedAdd(uint a, uint b) public pure returns (uint) {
    unchecked {
        return a + b;
    }
}

⚠️ 如果不明原因使用 unchecked,你等于手动关闭了安全带。
在性能需求极高的情况下确实可用,但必须经过严格测试。


2️⃣ 精度陷阱:为什么 1e18 * 0.25 有时 = 0?

✅ Solidity 不支持浮点数

你必须使用整数来近似表示所有“浮点”数据。

例如:

uint reward = total * 0.25; // ❌ 错误,0.25 会变成 0

✅ 正确做法:

uint reward = total * 25 / 100;

或者用 18 位精度管理:

uint reward = total * 25e16 / 1e18; // 25e16 = 0.25 * 1e18

3️⃣ 乘除顺序陷阱:一换顺序,直接归零

❌ 错误示范:

uint result = 1000 * 1e18 / 1e20; // ➜ 结果为 0

解释:1000 * 1e18 = 1e21,再除 1e20,得到 10。
但如果你先 1e18 / 1e20 = 0,整个式子就变成 1000 * 0 = 0


✅ 正确姿势:先乘后除
Never divide first. Always multiply first.

result = amount * ratio / scale;

4️⃣ ERC20 decimals 精度误差导致金额失真

✅ 案例 1:忘记考虑 token decimals

// 代币 A 有 6 decimals,但我们按 1e18 运算,结果发错数量
aToken.transfer(user, amount * reward / total); // ❌

解决方式:

uint tokenDecimals = 10 ** IERC20Metadata(token).decimals();
...
uint reward = total * userShare / poolTotal;
reward = reward / 1e18 * tokenDecimals;

5️⃣ 案例复盘:YAM 与 Balancer

💥 YAM 崩盘(2020)

  • 问题:rebase() 逻辑中执行复合计算

  • totalSupply = totalSupply * price / targetPrice

  • 忘记更新精度乘数导致乘除顺序不当

  • 结果出现整数溢出,DAO 无法投票提案修复

📎 结局:项目治理瘫痪,只能重启部署


💥 Balancer 被套利(2021)

  • 问题: 奖励代币复利结算计算精度错误

  • 攻击者通过 Flashloan 操控 ratePerSecond,精准制造浮点误差

  • 每轮套利可稳定提取超额奖励

📎 教训:任何时间、利息、折扣计算都要使用 FixedPointMathLib 等高精度工具


✅ 推荐写法模板:安全数值操作

// 推荐使用 Foundry 的 fixed point math lib
import "solmate/utils/FixedPointMathLib.sol";

// 假设 reward = baseAmount * rate / 1e18
uint reward = FixedPointMathLib.mulDivDown(baseAmount, rate, 1e18);

或使用:

SafeMath.mul().div()

✅ 本章实战模板代码:安全乘除结构

function calculateReward(uint amount, uint rewardRate, uint base) public pure returns (uint) {
    // reward = amount * rewardRate / base
    // 避免精度丢失
    return amount * rewardRate / base;
}

或:

function safeMulDiv(uint a, uint b, uint denom) internal pure returns (uint) {
    return a * b / denom;
}

🛠 精度审计 Checklist(可直接用于项目自检)

检查项✅/❌
是否统一使用了 18 位精度(或 token decimals)
是否所有比例运算都使用“乘后除”结构?
是否使用 FixedPointMathLib / SafeMath?
是否所有乘除操作都在 0.8+ 安全检查下运行?
是否误用 unchecked{} 封装关键运算?

🧪 课后挑战

  1. 写一个 calcReward() 函数,接受 3 个参数:用户 stake、池子总量、奖励总额

    • 实现高精度奖励分配

    • 加入 decimals 参数支持不同 token

    • 模拟 10000 用户参与下的极小精度误差(unit test)

  2. 模拟 YAM 的溢出 bug

    • 创建一个带 rebase() 函数的合约

    • 故意写错乘除顺序,复现溢出

    • 修复后对比 Gas 与正确性


✅ 本章总结

  • Solidity 没有浮点数,所有比例运算都要手动模拟

  • 0.8+ 自动检查溢出已提高安全,但不是万能

  • unchecked{} 只能用于确定不会出错、且性能敏感的逻辑

  • 精度管理 = 财务安全的第一步,不能凭经验猜


✅ 下一章预告|第 6 章:预言机操控与闪电贷攻击

👉 Mango Markets 如何操控喂价 + 抬高自身抵押品价值?
👉 如何在合约中正确使用 Chainlink 与 TWAP?
👉 闪电贷攻击的核心机制、常用攻击路径、真实复现案例


http://www.kler.cn/a/600820.html

相关文章:

  • 笔记整理三
  • 开源模型应用落地-语音转文本-whisper模型-AIGC应用探索(五)
  • 最大连续子序列和(动态规划 -- 经典Kadane算法)
  • 可视化工程项目管理软件:让复杂工程数据一目了然
  • rabbitmq承接MES客户端服务器
  • sourcetree中的“master“,“origin/master“,“origin/HEAD“这三个图标都是什么意思?GIT 超详细➕通俗易懂版本
  • influxdb在centOS stream 9安装教程
  • 3、孪生网络/连体网络(Siamese Network)
  • <KeepAlive>和<keep-alive>有什么区别
  • 基于51单片机的多点位水位监测proteus仿真
  • Java学习总结-Stream流
  • 微信小程序中使用WebSocket通信
  • 使用Python爬虫获取1688商品(按图搜索)接口
  • 状态空间模型解析 (State-Space Model, SS)
  • 人工智能与区块链融合:开启数字信任新时代
  • (一)LeetCode热题100——哈希
  • 家庭网络结构之局域网通信
  • 监控告警+webhook一键部署
  • PAT乙级1007
  • jvm中每个类的Class对象是唯一的吗