Solidity合约越界测试的必要性及实现方法咨询
Solidity合约越界错误测试的实际价值
这类测试完全具备实际意义,甚至是合约安全测试里投入产出比极高的一类:
- Solidity本身的类型规则存在不少容易踩坑的点:0.8版本之前默认不做整数溢出/下溢检查,越界赋值会直接截断不会抛出异常;即便是0.8之后自带溢出检查,跨合约低级调用(比如
call、delegatecall)不会做参数类型校验,参数类型被误改、漏加边界判断的情况下,越界值可以直接进入业务逻辑,引发存储覆盖、权限绕过、资金计算错误等高危问题。 - 能低成本拦住迭代过程中的低级失误:就像你提到的参数类型被误改的场景,这类问题代码评审不一定能100%覆盖,但是测试用例一旦写好,每次改完代码跑测试就能第一时间发现问题,避免问题留到主网。
- 覆盖业务逻辑的边界场景:很多业务参数本身就有明确的取值范围(比如费率基数是10000、配置项ID上限是255),越界测试本质也是在校验业务规则的有效性,避免非法参数进入核心逻辑。
落地实现方法
核心逻辑非常简单,不需要特殊工具,用日常开发用的Foundry、Hardhat等测试框架就能实现:
- 先梳理所有涉及数值类型的入参、状态变量的合法取值区间,明确每个值的上下边界,比如
uint8的合法范围是0~255。 - 针对每个边界写两类用例:一类是临界合法值(比如255),验证传入后合约可以正常执行,避免误加过严的校验影响正常功能;另一类是越界值(比如256、类型最大值、低于类型最小值的数值),验证传入后合约必须触发回滚。
- 针对你提到的「防范参数类型被误从uint8改成uint256」的场景,越界回滚的用例可以直接覆盖这个风险:如果后续开发真的把参数类型改成了uint256又漏加边界校验,传入256的时候合约不会触发回滚,测试用例会直接失败,第一时间暴露问题。
举个可直接复用的实现示例:
首先是待测试的业务合约:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; contract ValueStore { mapping(uint8 => uint256) public store; // 原设计inputVal为uint8类型,取值范围0~255 function set(uint8 inputVal, uint256 val) external { store[inputVal] = val; } }
对应的Foundry测试用例:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "forge-std/Test.sol"; import "../src/ValueStore.sol"; contract ValueStoreTest is Test { ValueStore public app; function setUp() public { app = new ValueStore(); } // 验证边界合法值可正常执行 function test_set_validMaxVal() public { app.set(255, 999); assertEq(app.store(255), 999); } // 验证越界值必须回滚,拦截类型误改、漏加校验的问题 function test_set_revertWhenOverUint8Range() public { // 传入刚好超过uint8上限的256 vm.expectRevert(); app.set(256, 999); // 传入uint256最大值,覆盖极端越界场景 vm.expectRevert(); app.set(type(uint256).max, 999); } }
如果后续有人把set函数的inputVal参数从uint8改成uint256,又没加require(inputVal <= type(uint8).max)的边界校验,test_set_revertWhenOverUint8Range用例就会因为没有触发预期的回滚直接失败,刚好达到你要的校验效果。如果用Hardhat,逻辑完全一致,把回滚断言换成Hardhat常用的.to.be.reverted写法即可。
注意:如果你的合约用的是0.8之前的Solidity版本,因为没有默认溢出检查,越界传入参数不会自动触发回滚,这种情况一定要手动在合约里加边界校验,再配合上述用例测试,不要依赖编译器的自动检查。
内容的提问来源于stack exchange,提问作者Tarasko
相关产品推荐
相关产品推荐

