文章详细介绍了模糊测试及其在Solidity智能合约安全中的应用。它解释了模糊测试如何通过生成随机输入来发现传统测试遗漏的边缘案例,并深入阐述了不变式(Invariants)的概念及其在DeFi协议中确保系统稳定性的关键作用。文章还探讨了无状态与有状态模糊测试、边界模型检查以及模糊测试与端到端测试的结合。
2026年2月24日
引言
让我们先了解攻击是如何发生的。在大多数情况下,软件中的安全漏洞是由于你在单元测试期间没有预料到的边缘情况,因此没有为它们编写测试而导致的。但是,如果我告诉你有一种方法可以通过一种单一的方法来应对这些不可预测的场景,这种方法可以压力测试几乎所有可能性呢?
什么是fuzz测试,以及它如何应用于Solidity智能合约?
我们将首先了解fuzz测试本身,并在本文结束时将其与Solidity智能合约联系起来。现在,如果你是为了fuzz测试的正式教科书定义而来,我会省去你的麻烦,因为那不是你想要的,对吗?🙂
让我为你简化一下。在编写测试时,目标通常是实现100%的代码覆盖率。然而,即使拥有完美的覆盖率,你也不能保证代码中没有潜藏的错误。这就是fuzzers发挥作用的地方。Fuzzers生成一系列输入,用于测试你在单元测试中可能遗漏的边界。
正式定义是:“Fuzz测试(或fuzzing)涉及向系统模拟输入随机数据,以试图破坏它。”
然而,fuzzing的有效性,取决于你编写的fuzz测试的质量。Fuzzers作为软件工具,本质上是不智能的。它们在计算边界内运行,缺乏上下文或决策能力。
例如,在一个具有多种可能操作的系统中,fuzzer可能会随机选择一个不恰当的操作。以onlyOwner函数为例——如果fuzzer使用了一个无效地址,它自然会触发回滚。这类情况是可预测且微不足道的,通常被归类为低垂的果实。由于你预期这些失败,它们应该已经在单元测试中涵盖了。
为了避免在这些不相关的场景上浪费宝贵的fuzzing资源,绕过明显的失败并专注于重要的边缘情况至关重要。因此,编写有效的fuzz测试,是为了从过程中榨取最大价值。
介绍不变量:必须始终成立的核心属性
现在我们来谈谈不变量(invariants),也称为属性(properties)。这方面会变得稍微更微妙,但并没有看起来那么复杂。简而言之,不变量是你断言必须始终保持为真的属性或规则。
与单元测试不同,在单元测试中你提供一个单一输入并验证预期结果,而不变量测试则验证特定属性在许多随机输入下都成立。Fuzzers通过广泛的值重复测试这些属性,确保系统在所有场景下都按预期运行。
让我进一步简化。在DeFi世界中,不变量是协议赖以维持稳定性和公平性的基础规则。这些是无论执行何种操作都绝不能被打破的“法则”。
借贷市场不变量
在Compound或Aave等借贷协议中,一个关键规则是用户的借贷价值永远不能超过他们的抵押品。为了进一步解释,当你借入资产时,你必须存入价值高于借贷价值的抵押品。这类似于抵押贷款,你不能借入超过你房屋价值的金额。协议会阻止任何会将账户置于这种不安全状态或恶化已存在风险状况的操作。
AMM不变量
像Uniswap或SushiSwap这样的自动化做市商(AMM)依赖于一个数学不变量来维持流动性池的平衡。这条规则表示为x * y = k,其中x和y代表代币数量,k是一个常数。如果有人购买了更多的一种代币,价格会按比例上涨以保持不变量。
质押/流动性挖矿不变量
在质押协议中,有一个简单但关键的规则:用户只能提取他们最初存入的相同数量的代币。例如,如果你质押了10个代币,你就可以精确地取回10个代币。虽然质押会赚取奖励,但本金金额保持不变。
示例时间?
| ```solidity |
pragma solidity ^0.8.0; contract UniswapInvariantCheck { uint256 public reserveX; // 代币X的储备 uint256 public reserveY; // 代币Y的储备 constructor (uint256 _initialX, uint256 _initialY) { reserveX = _initialX; // 初始化储备 reserveY = _initialY; } // 模拟代币交换的函数 function swap (uint256 inputX, uint256 inputY) public { require (inputX == 0 || inputY == 0, "Only one token can be swapped"); // 确保只交换一种代币 if (inputX > 0) { // 代币X正在被交换到池中 uint256 newReserveX = reserveX + inputX; // 更新X的储备 uint252 newReserveY = (reserveX reserveY) / newReserveX; // 使用不变量计算Y的新储备 reserveX = newReserveX; // 更新状态 reserveY = newReserveY; } else if (inputY > 0) { // 代币Y正在被交换到池中 uint256 newReserveY = reserveY + inputY; // 更新Y的储备 uint256 newReserveX = (reserveX reserveY) / newReserveY; // 使用不变量计算X的新储备 reserveX = newReserveX; // 更新状态 reserveY = newReserveY; } } // 检查不变量是否成立的函数 function invariantHolds () public view returns (bool) { uint252 k = reserveX reserveY; // 计算常数k return k == reserveX reserveY; // 验证 x * y = k 是否成立 } }
1. **不变量**规则(x \* y = k):
* 在Uniswap V3中,`reserveX`和`reserveY`代表流动性池中两种代币的数量。
* **不变量**x \* y = k确保在交换过程中这些储备的乘积保持不变。
* 这条规则对于维持代币价格的平衡至关重要。
2. `swap`函数的工作原理:
* **输入验证:** `swap`函数确保一次只交换一种代币(X或Y)(`require(inputX == 0 || inputY == 0)`)。
* **更新储备:** 如果交换代币X,则使用**不变量**公式计算Y的新储备:
**newReserveY = (reserveX \* reserveY) / newReserveX**
* 同样,如果交换代币Y,则计算X的新储备:
**newReserveX = (reserveX \* reserveY) / newReserveY**
* **不变量验证:**
`invariantHolds`函数检查`reserveX`和`reserveY`的乘积是否保持一致。如果任何操作破坏了这种平衡,**不变量**将不成立,表明实现或逻辑中存在问题。
**无状态fuzzing与有状态fuzzing:两种系统破坏方法**
为了理解**fuzzing**技术,让我们使用易碎玻璃的类比。
**无状态fuzzing**独立测试每个场景。想象一下通过以下方式测试一个玻璃杯:
1. 用勺子敲击它。
2. 向其中投入一颗鹅卵石。
3. 将它摔到地上。
在每种情况下,你都使用一个新的玻璃杯。过去的操作不会影响下一次测试。虽然这种方法速度快,但它忽略了早期操作可能如何影响结果。
另一方面,**有状态fuzzing**在所有测试中都使用同一个玻璃杯。如果你在第一步敲击玻璃杯,第二步投入鹅卵石,第三步将其摔到地上,你将观察到累积效应。这种方法反映了真实世界系统,其中之前的操作会塑造未来的行为,从而发现更深层次的错误。
**有界模型检查(BMC):为有效测试设定限制**
**有界模型检查**(Bounded Model Checking,BMC)通过限制识别错误所采取的步骤来改进**fuzz测试**。BMC没有无休止地探索输入,而是设置了逻辑“**边界**”。
例如,向AMM存入零代币可能会触发`MIN_INITIAL_SHARES`错误。由于这是一个可预测的失败,你引导fuzzer避免此类输入,转而关注有意义的**边缘情况**。
将BMC想象成在迷宫中导航。你决定只检查少于或等于10步的路径。如果在此**边界**内存在错误,你就会找到它。如果不是,系统在该范围内保持稳定。
**端到端测试与Fuzzing的结合:完美组合**
**端到端测试**(End-to-End,E2E testing)模拟真实世界的用户操作,确保系统按预期运行。例如,针对注册表单的E2E测试将验证各种输入:空白字段、无效电子邮件和有效凭据。
当与**fuzz测试**结合时,E2E测试变得更加强大。E2E测试检查正常工作流程,而**fuzzing**引入不可预测性,以测试系统在压力下的响应方式。它们共同提供了一个强大的框架,用于验证预期和意外行为。
**结论:**
**Fuzz测试**是发现传统测试可能遗漏的隐藏漏洞的**颠覆性技术**。通过将**fuzzing**与**不变量测试**相结合,我们超越了仅仅测试单个函数,而是关注系统的整体行为和稳定性。**有状态fuzzing**、**有界模型检查**和**端到端测试**等方法协同工作,以发现**边缘情况**并使智能合约更加安全。
>- 原文链接: [blog.immunebytes.com/202...](https://blog.immunebytes.com/2025/03/24/fuzz-invariant-testing)
>- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~ 如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!