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

server/2025/3/26 5:30:01/

🧮 第 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.ppmy.cn/server/179152.html

相关文章

欢迎来到未来:探索 Dify 开源大语言模型应用开发平台

欢迎来到未来:探索 Dify 开源大语言模型应用开发平台 如果你对 AI 世界有所耳闻,那么你一定听说过大语言模型(LLM)。这些智能巨兽能够生成文本、回答问题、甚至编写代码!但是,如何将它们变成真正的实用工具…

Linux UDP网络编程套接字sockets

目录 一、预备知识 1、IP地址 2、端口号 3、Socket网络通信 4、认识TCP/UDP协议 (1)TCP协议 (2)UDP协议 (3)网络字节序 二、socket网络套接字 1、概念 2、Socket 的地址结构和一系列转换函数 &a…

Python(request库)

request库 一、request库介绍 (1)简介 requests是用python语言编写的简单易用的http库,用来做接口测试的库 (2)接口测试自动化库有哪些? requests、urllib 、urllib2、urllib3、 httplib 等&#xff08…

git的进阶使用

一.协作冲突 举个简单的例子,公司里两个人(A,B)同一天上班,都拉取了远程仓库数据。然后A做完了所有的工作,进行了x文件的修改并提交至远程仓库。而B在做自己工作的时候不小心或者需要修改x文件,B认为A没有操作x文件直接push没有问…

Webview详解(上)

第一阶段:基础入门 WebView基础概念 什么是Webview? WebView是一种用于在移动应用程序中展示网页内容的嵌入式浏览器组件。它允许开发者将网页内容直接加载到应用界面中,用户无需离开应用即可浏览网页。WebView 通常用于加载 HTML、CSS、J…

【大模型算法工程】大模型应用工具化、忠诚度以及知识库场景下PDF双栏解析问题的讨论

1. 大模型时代应用工具化以及无忠诚度现象讨论 接触大模型久了,也慢慢探到一些大模型能力表现非常自然和突出的场景,比如AI搜索(依赖大模型的理解总结能力)、AI对话(即chat,依赖大模型的生成能力&#xff0…

深入解析 Service Worker 在 Chrome 扩展中的应用

1. 什么是 Service Worker? Service Worker 是一种运行在后台的 JavaScript 线程,与网页主线程独立。它的主要作用是拦截网络请求、缓存资源、提供离线支持,并执行后台任务,如推送通知和后台同步。在 Chrome 扩展(Ext…

leetcode-45.跳跃游戏II

题很简单,怎么少点写代码比较困难。 这题很明显贪心算法,每次跳跃我们都希望理论上能到达的地方更远。一开始站在初始点,那么可选范围就是0到nums[0],在这里面找能跳得最远的点i,接下来遍历nums[0]1到 inums[i]&#x…