资讯动态

WTF-Solidity 工具链核心:forge-std 标准库实战指南(stdError / stdStorage / stdCheats / console)

发布时间:2026/9/15 20:10:24 来源:尧图企业网站定制
WTF-Solidity 工具链核心forge-std 标准库实战指南stdError / stdStorage / stdCheats / console【免费下载链接】WTF-SolidityWTF Solidity 极简入门教程供小白们使用。Now supports English! 官网: https://wtf.academy项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity导读forge-stdForge Standard Library是 Foundry 测试框架的官方标准库它以 Solidity 库与合约的形式封装了 Forge 的 cheatcodes作弊码让编写与执行测试更快、更顺手同时显著改善了作弊码的使用体验。在本仓库WTF-Solidity中forge-std 被作为 Foundry 项目的基础依赖随 TOOL07 Foundry 工具教程 一同引入是仓库内所有 Foundry 测试*.t.sol与部署脚本*.s.sol的共同地基。读完本文你将掌握 forge-std 的安装方式、stdError、stdStorage、stdCheats、断言工具与console/console2日志工具的正确用法并能直接在 WTF-Solidity 仓库的 hello_wtf 示例工程 与 lib/forge-std 源码中找到一一对应的实现证据。一、forge-std 是什么Forge Standard Library 是一组专供 Foundry/Forge 使用的合约与库的集合。它的核心设计目标有两点让测试更快、更易写把常见的断言、地址生成、余额调整、存储槽定位等重复劳动封装成一行调用改善 cheatcodes 的使用体验UX原生 cheatcode 往往暴露底层细节如存储布局、EVN 状态操作forge-std 在其上提供了更安全、更符合直觉的包装。从源码结构看forge-std 的src目录下包含Test.sol、Script.sol、StdAssertions.sol、StdCheats.sol、StdStorage.sol、StdError.sol、StdMath.sol、StdUtils.sol、StdJson.sol、StdToml.sol、StdChains.sol、StdInvariant.sol、StdStyle.sol等组件见 lib/forge-std/src。其中默认测试基类 Test.sol 通过多继承一次性聚合了断言、链信息、作弊码包装、不变量与工具函数abstract contract Test is TestBase, StdAssertions, StdChains, StdCheats, StdInvariant, StdUtils { // Note: IS_TEST() must return true. bool public IS_TEST true; }也就是说只要测试合约is Test上述所有能力便开箱即用。二、安装 forge-stdforge-std 以 git 子依赖的方式安装在 Foundry 项目根目录执行forge install foundry-rs/forge-std安装完成后依赖会被放置到项目的lib/forge-std目录本仓库根目录下的 lib/forge-std 即如此引入的。forge init初始化新工程时也会默认自动安装 forge-std——这与 TOOL07 Foundry 教程 中forge init hello_wtf后lib/forge-std被自动拉取的现象完全一致。在测试或脚本中按需导入即可import forge-std/Test.sol; // 测试基类推荐间接引入全部组件 import forge-std/Script.sol; // 部署脚本基类 import forge-std/console2.sol; // 高级日志 import forge-std/StdStorage.sol; // stdstore 存储定位工具注意标准库文件顶部声明了pragma solidity 0.8.13 0.9.0见 StdError.sol因此使用 forge-std 的项目需要满足该 Solidity 版本区间这也是 Foundry 工程默认采用^0.8.13以上编译器的原因之一。三、stdError内置错误的即用常量3.1 作用与原理stdError是一个错误码辅助库专门配合vm.expectRevert作弊码使用。它把 Solidity 编译器内置的Panic(uint256)各类错误码全部预编码成bytes常量。实现位于 lib/forge-std/src/StdError.sol完整错误码如下常量编码含义assertionErrorPanic(0x01)assert断言失败arithmeticErrorPanic(0x11)算术运算溢出/除零如a a - 100下溢divisionErrorPanic(0x12)除法或取模除零enumConversionErrorPanic(0x21)枚举非法值转换encodeStorageErrorPanic(0x22)存储编码错误popErrorPanic(0x31)对空数组popindexOOBErrorPanic(0x32)数组/字节数组索引越界memOverflowErrorPanic(0x41)内存分配溢出zeroVarErrorPanic(0x51)对零变量执行未检查除法/取模等操作3.2 典型用法import forge-std/Test.sol; contract TestContract is Test { ErrorsTest test; function setUp() public { test new ErrorsTest(); } function testExpectArithmetic() public { vm.expectRevert(stdError.arithmeticError); test.arithmeticError(10); } } contract ErrorsTest { function arithmeticError(uint256 a) public { a a - 100; // 当 a 100 时触发 Panic(0x11) } }相比手写abi.encodeWithSignature(Panic(uint256), 0x11)stdError.arithmeticError语义更清晰、不易写错。forge-std 自身的测试 StdError.t.sol 对每个错误常量都有对应的预期断言可作为验证标准。四、stdStorage无需关心布局的存储槽定位与写入4.1 核心能力stdstorestdStorage库是recordaccesses两个 cheatcode 之上的高层包装见 lib/forge-std/src/StdStorage.sol。它可以在不知道目标合约存储布局的情况下找到并改写某个状态变量对应的存储槽。实现要点见 stdStorageSafe.find先通过vm.record()记录目标地址在一次函数调用中发生的所有SLOAD用vm.accesses()拿到被读取的槽位列表逐个槽位尝试“改写后重跑调用观察返回值是否变化”checkSlotMutatesCall见 源码 L55-L67以此确认真实槽位。关键限制对于打包packed存储变量槽位可以定位但无法安全写入——除非该槽尚未初始化值为bytes32(0)。如果强行向已初始化的打包槽写入执行会抛出错误。4.2 链式调用 APIstdstore采用流式链式调用常用方法.target(address)指定目标合约地址.sig(func())或.sig(func.selector)指定要定位的状态变量传入 getter 的函数选择器或签名.with_key(...)定位 mapping 时传入 key支持address/uint256/bytes32重载.depth(n)定位 struct 时指定字段深度0 为第一个字段1 为第二个依此类推.find()返回槽位号.checked_write(...)校验后写入值写入失败会 revert 并回滚.read_uint()/.read_int()/.read_address()/.read_bool()/.read_bytes32()读取并解码当前值。4.3 完整示例import forge-std/Test.sol; contract TestContract is Test { using stdStorage for StdStorage; Storage test; function setUp() public { test new Storage(); } function testFindExists() public { // 通过 getter 函数选择器找到 public 变量 exists 的槽位 uint256 slot stdstore.target(address(test)).sig(exists()).find(); assertEq(slot, 0); } function testWriteExists() public { // 直接改写槽位值 stdstore.target(address(test)).sig(exists()).checked_write(100); assertEq(test.exists(), 100); } // 支持任意存储布局例如通过汇编随机哈希定位的存储槽 function testFindHidden() public { uint256 slot stdstore.target(address(test)).sig(test.hidden.selector).find(); assertEq(slot, uint256(keccak256(my.random.var))); } // 定位 mapping 时需传入 key function testFindMapping() public { uint256 slot stdstore .target(address(test)) .sig(test.map_addr.selector) .with_key(address(this)) .find(); assertEq(uint(vm.load(address(test), bytes32(slot))), 1); } // 定位 struct 时指定字段深度 function testFindStruct() public { uint256 slotForA stdstore.target(address(test)).sig(test.basicStruct.selector).depth(0).find(); uint256 slotForB stdstore.target(address(test)).sig(test.basicStruct.selector).depth(1).find(); assertEq(uint(vm.load(address(test), bytes32(slotForA))), 1); assertEq(uint(vm.load(address(test), bytes32(slotForB))), 2); } } contract Storage { struct UnpackedStruct { uint256 a; // depth 0 uint256 b; // depth 1 } constructor() { map_addr[msg.sender] 1; } uint256 public exists 1; mapping(address uint256) public map_addr; mapping(address mapping(address uint256)) public deep_map; mapping(address mapping(address UnpackedStruct)) public deep_map_struct; UnpackedStruct public basicStruct UnpackedStruct({a: 1, b: 2}); function hidden() public view returns (bytes32 t) { // 一个极其隐蔽的存储槽 bytes32 slot keccak256(my.random.var); assembly { t : sload(slot) } } }4.4 在 WTF-Solidity 中的真实应用stdstore最常见的实战场景是无差别修改任意 ERC20 代币余额在 StdCheats.sol 的deal实现 中正是用stdstore.target(token).sig(0x70a08231).with_key(to).checked_write(give)直接改写balanceOf(address)槽位0x70a08231即balanceOf(address)的选择器并可选地联动修正totalSupply。这解释了 TOOL07 Foundry 教程的测试示例 中deal(address(dai), alice, 1 ether)为什么能直接给测试地址充值 DAI——不需要走真实的transfer转账。五、stdCheats作弊码的安全包装5.1 设计动机为什么需要包装stdCheats是对杂项 cheatcode 的开发者友好包装。文档明确指出一个关键安全点prank不会自动给地址充 ETH出于安全考虑。而hoax会“先给地址打钱再 prank”但只应作用于余额可被覆盖的地址地址已有 ETH → 直接用prank只想显式改余额 → 用deal两者都要 → 用hoax。5.2 hoax / startHoax 用法从源码 StdCheats.sol L663-L704 可以看到hoax本质是vm.dealvm.prank的组合默认给1 128weistartHoax则是vm.dealvm.startPrank并支持传tx.origin参数的重载// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.0; import forge-std/Test.sol; contract StdCheatsTest is Test { Bar test; function setUp() public { test new Bar(); } function testHoax() public { // 给 address(1337) 打 ETH然后以它身份调用一次 hoax(address(1337)); test.bar{value: 100}(address(1337)); // 重载显式指定初始 ETH 数量 hoax(address(1337), 1); test.bar{value: 1}(address(1337)); } function testStartHoax() public { // 持续 prank直到 stopPrank startHoax(address(1337)); test.bar{value: 100}(address(1337)); test.bar{value: 100}(address(1337)); vm.stopPrank(); test.bar(address(this)); } } contract Bar { function bar(address expectedSender) public payable { require(msg.sender expectedSender, !prank); } }5.3 stdCheats 的其他高频能力除hoax外StdCheats.sol 还封装了大量日常测试工具均可从源码确认makeAddr(name)/makeAddrAndKey(name)/makeAccount(name)基于名称确定性派生私钥与地址并打标签L539-L567deal(address token, address to, uint256 give, bool adjust)改 ERC20 余额可选同步 totalSupply另有dealERC1155、dealERC721版本L720-L802deployCode/deployCodeTo从 artifacts 直接取字节码部署到指定地址L501-L536skip/rewind跳转/回拨block.timestampL653-L660assumeNotBlacklisted对 USDCisBlacklisted选择器0xfe575a87与 USDTisBlackListed选择器0xe47d6060做黑名单过滤L207-L233noGasMetering修饰器暂停/恢复 gas 计量L601-L620readTx1559/readReceipt解析forge script生成的 broadcast 工件 JSON读取交易与回执。六、Std Assertions断言工具集StdAssertions提供了各种断言函数assertEq、assertTrue、assertGt、assertLt、assertApproxEqRel等是 Test.sol 继承链的一部分因此在任何is Test的测试合约中可直接使用。支持uint256、int256、address、bytes32、string、bytes等类型的两两相等断言数值比较断言大于/小于等近似相等断言assertApproxEqRel按相对误差比较浮点场景失败时自动打印左右两侧的实际值与期望值便于定位。WTF-Solidity 的 Foundry 测试均采用这一套断言例如 hello_wtf 示例工程的 Counter.t.sol 中assertEq(counter.number(), 1)即来自StdAssertions。七、console.log 与 console2.log测试日志7.1 推荐使用 console2日志的调用格式与 Hardhat 的console.log保持一致但强烈推荐使用console2.solForge 能对console2的日志在 trace堆栈跟踪中做解码显示定位问题更直观。// 通过 Test.sol 间接引入 import forge-std/Test.sol; // 或直接引入 import forge-std/console2.sol; // ... console2.log(someValue);7.2 何时必须用 console如果项目需要与 Hardhat 保持日志兼容就必须改用标准的console.sol// 通过 Test.sol 间接引入 import forge-std/Test.sol; // 或直接引入 import forge-std/console.sol; // ... console.log(someValue);注意已知 bug由于console.sol存在解码缺陷使用uint256或int256类型的日志将无法在 Forge trace 中被正确解码。因此纯 Foundry 项目应默认选择console2。7.3 在 Forge 中查看日志日志需要足够高的详细等级才会显示。使用forge test -vvv # 显示 console2.log 等日志输出 forge test -vvvv # 显示全部测试的堆栈跟踪 forge test -vvv -w # 热更新watch模式下持续监听这正是 TOOL07 Foundry 教程 中forge test -vvv --fork-url$ETH_RPC_URL -w能看到before:/after:日志输出的原因。八、从 WTF-Solidity 仓库快速上手验证本仓库已内置了可直接运行的 forge-std 完整源码与配套测试适合边读边验证标准库源码lib/forge-std/srcTest.sol、Script.sol、StdCheats.sol、StdStorage.sol、StdError.sol等标准库自测用例lib/forge-std/testStdError.t.sol、StdStorage.t.sol、StdCheats.t.sol、StdAssertions.t.sol等可作为每种 API 的“活文档”Foundry 实战示例Topics/Tools/TOOL07_Foundry/hello_wtf含test/Counter.t.sol、test/Test.t.sol等基于 forge-std 的测试配套教程TOOL07_Foundry/readme.mdforge init、forge test、cast、anvil的完整上手流程。在hello_wtf工程中执行forge test -vvv或按教程配合--fork-url$ETH_RPC_URLfork 主网测试 DAI 余额调整即可亲身验证stdstore改写存储槽、deal充值、vm.prank切换msg.sender等本节全部能力。结语forge-std 是 Foundry 测试体验的核心引擎stdError让expectRevert语义化、stdStorage让存储操作摆脱布局约束、stdCheats让作弊码远离误用、StdAssertions让断言一目了然、console2让日志可解码。理解这些组件的源码实现Test.sol、StdError.sol、StdStorage.sol、StdCheats.sol之后再回到 WTF-Solidity 的 Foundry 实战你会发现仓库中每一个vm.*调用与assertEq背后都有 forge-std 提供的坚实支撑。【免费下载链接】WTF-SolidityWTF Solidity 极简入门教程供小白们使用。Now supports English! 官网: https://wtf.academy项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价