
Foundry 新增类型化 FFI 输出解码vm.ffiUint / vm.ffiString / vm.ffiBytes 使用指南【免费下载链接】foundryFoundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.项目地址: https://gitcode.com/GitHub_Trending/fo/foundry本篇文章聚焦 Foundry 最新加入的vm.ffiUint、vm.ffiString、vm.ffiBytes三个类型化 FFI 输出解码 cheatcode讲解它们解决的传统vm.ffi输出歧义问题、各自严格的解码规则以及如何在测试与脚本中落地使用。读完本文你将掌握无歧义地把外部命令 stdout 解析为uint256、string与bytes的完整方案。一、变更背景changelog 条目说了什么本主题的源头是仓库中的变更记录条目 .changelog/typed-ffi-output.md。该条目声明这是一个forge: minor、foundry-cheatcodes: minor、foundry-cheatcodes-spec: minor级别的功能增强核心内容只有一句话Addedvm.ffiUint,vm.ffiString, andvm.ffiBytesfor unambiguous FFI output decoding.这句话包含了三个关键信息新增了三个 cheatcodevm.ffiUint、vm.ffiString、vm.ffiBytes它们面向 FFIForeign Function Interface输出解码即把外部命令的标准输出转换回 Solidity 类型目标是“无歧义”unambiguous这是相对传统vm.ffi而言的关键改进点下文会详细展开。由于本条目跨越了forge、foundry-cheatcodes、foundry-cheatcodes-spec三个包说明该能力不仅在 Forge 测试执行器中生效还同步更新了 cheatcode 的声明规范spec与对应的 Vm.sol 接口方便各语言绑定生成。二、传统 vm.ffi 的输出解码问题2.1 vm.ffi 的“先猜后验”解码传统vm.ffi的声明与实现位于 crates/cheatcodes/assets/cheatcodes.jsonfunction ffi(string[] calldata commandInput) external returns (bytes memory result);其核心解码逻辑在 crates/cheatcodes/src/fs.rs 的decode_ffi_stdout函数fn decode_ffi_stdout(stdout: str) - Bytes { hex::decode(stdout).unwrap_or_else(|_| stdout.as_bytes().to_vec()).into() }也就是说vm.ffi对 stdout 的处理策略是优先尝试按十六进制解码一旦失败就退化为按 UTF-8 原始字节返回。这带来两个问题结果类型不确定返回的bytes里到底装着 ABI 编码后的数据还是原始文本字节完全取决于命令输出“碰巧”是不是合法 hex。同一个字符串如42可能被理解为 ASCII 字符4、2的字节也可能被误当作十六进制数值。调用方必须二次解码测试代码需要像 testdata/default/cheats/Ffi.t.sol 中那样先拿到bytes再用abi.decode手动还原类型一旦猜错 ABI 类型就会得到错误结果。2.2 一个典型的歧义案例观察 testdata/default/cheats/Ffi.t.sol 的testFfiStringstring[] memory inputs new string[](3); inputs[0] echo; inputs[1] -n; inputs[2] gm; bytes memory res vm.ffi(inputs); assertEq(string(res), gm);gm不是合法 hexg、m不在0-9a-f范围内所以会走 UTF-8 分支恰好可用。但如果我们让命令输出42hex::decode(42)会成功得到单个字节0x42与调用方期望的字符串42两个 ASCII 字节完全不同——同一个输出两种解读结果南辕北辙。这正是“unambiguous”要根治的痛点。三、新增的类型化 FFI 解码 cheatcode3.1 函数签名与声明位置三个新 cheatcode 均声明为external、无状态修饰接受与vm.ffi完全一致的string[] calldata commandInput区别仅在返回值类型。接口声明同步出现在三个位置规范接口crates/cheatcodes/spec/src/vm.rs机器可读注册表crates/cheatcodes/assets/cheatcodes.json测试用 Solidity 接口仓库中的Vm.sol如 testdata/utils/Vm.sol四个 FFI 相关 cheatcode 一览selector 均来自 cheatcodes.jsoncheatcode返回值输出处理策略selectorffi(string[])bytes尝试 hex 解码失败回退原始字节0x89160467ffiUint(string[])uint256将 stdout 解析为十进制整数0xcdf7c6c4ffiString(string[])string将 stdout 原样作为字符串返回0x0d0d2f41ffiBytes(string[])bytes将 stdout 严格按十六进制解码失败即 revert0x32730d81在 cheatcodes.json 中三者均归属于filesystem分组、stable状态与vm.ffi一致同时注意vm.ffi标记为safe而真正的危险操作如vm.ffi底层命令执行需通过--ffi标志显式开启详见下文。3.2 三种严格解码语义三个新函数的实现集中在 crates/cheatcodes/src/fs.rsimpl Cheatcode for ffiUintCall { fn applyFEN: FoundryEvmNetwork(self, state: mut CheatcodesFEN) - Result { let Self { commandInput: input } self; parse(ffi_stdout(state, input)?, DynSolType::Uint(256)) } } impl Cheatcode for ffiStringCall { fn applyFEN: FoundryEvmNetwork(self, state: mut CheatcodesFEN) - Result { let Self { commandInput: input } self; Ok(ffi_stdout(state, input)?.abi_encode()) } } impl Cheatcode for ffiBytesCall { fn applyFEN: FoundryEvmNetwork(self, state: mut CheatcodesFEN) - Result { let Self { commandInput: input } self; let stdout ffi_stdout(state, input)?; Ok(hex::decode(stdout) .map_err(|err| fmt_err!(failed parsing ffi stdout as bytes: {err}))? .abi_encode()) } }逐一解读ffiUint调用parse(stdout, DynSolType::Uint(256))即要求 stdout 是合法的uint256文本解析失败即报错。echo -n 42得到42被解析为数值42。ffiStringffi_stdout(...)?.abi_encode()把去空白后的 stdout 直接作为字符串返回不再进行任何 hex 猜测。echo -n 42得到字符串42而不是字节0x42。ffiBytes强制走hex::decode且失败时通过fmt_err!直接 revert——这是与旧vm.ffi的“回退策略”最大的不同解码失败不再静默降级而是显式失败。echo -n 42得到单个字节0x42。注意ffi_stdout的细节crates/cheatcodes/src/fs.rs输出先经String::from_utf8转字符串并trim()去除首尾空白若命令退出码非 0 则直接报错命令向 stderr 写入内容时仅发出 warning 而不失败。3.3 共享的底层命令执行三个新 cheatcode 与旧vm.ffi共用同一个底层执行函数ffi_commandcrates/cheatcodes/src/fs.rs因此行为约束完全一致必须显式开启 FFI当配置项state.config.ffi为 false 时会返回FFI is disabled; add the \--ffi flag to allow tests to call external commands 错误。开启方式见下文配置小节。拒绝空命令input为空或首个元素为空字符串时直接报错。以项目根目录为工作目录cmd.current_dir(state.config.root)相对路径均相对foundry.toml所在目录解析。stdout 提取规则ffi_stdout要求命令成功退出非 0 即报错并对 stdout 做 trimstderr 非空仅告警。四、实战在测试中启用与使用4.1 开启 FFI 能力FFI 属于高危能力默认关闭。两种开启方式# foundry.toml [profile.default] ffi true或命令行方式forge test --ffi配置项定义可参考 crates/config/src/lib.rs 中ffi字段及其注释。若未开启所有四个 FFI cheatcode 都会立即 revert 并提示添加--ffi标志。4.2 完整可运行示例仓库自带的回归测试 testdata/default/cheats/Ffi.t.sol 正是围绕本特性新增的testTypedFfiOutput// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.18; import utils/Test.sol; contract FfiTest is Test { function testTypedFfiOutput() public { string[] memory inputs new string[](3); inputs[0] echo; inputs[1] -n; inputs[2] 42; assertEq(vm.ffiUint(inputs), 42); assertEq(vm.ffiString(inputs), 42); assertEq(vm.ffiBytes(inputs), hex42); inputs[2] 123; assertEq(vm.ffiUint(inputs), 123); assertEq(vm.ffiString(inputs), 123); } function testFfiBytesRejectsNonHexOutput() public { string[] memory inputs new string[](3); inputs[0] echo; inputs[1] -n; inputs[2] gm; vm.expectRevert(); this.ffiBytes(inputs); } function ffiBytes(string[] memory inputs) external returns (bytes memory) { return vm.ffiBytes(inputs); } }要点说明同一份输出42三个函数给出了三种确定性的解读数值42、字符串42、单字节0x42互不冲突——这正是“unambiguous”的含义testFfiBytesRejectsNonHexOutput验证了ffiBytes的严格性输出gm不是合法 hex调用ffiBytes直接 revert通过this.ffiBytes包装避免测试函数本身被标记失败vm.expectRevert()需要配合外部调用使用因此测试里用公共函数ffiBytes做了一层转发。一个更贴近业务场景的用例——读取系统时间戳并断言为合法uint256function testUseSystemDate() public { string[] memory inputs new string[](3); inputs[0] date; inputs[1] %s; uint256 ts vm.ffiUint(inputs); assertGt(ts, 1_700_000_000); // 2023 年之后的时间戳必然大于该值 }五、设计取舍与最佳实践5.1 与旧 vm.ffi 的迁移建议需要原始字节/ABI 编码数据的场景继续使用vm.ffi例如从命令读取 ABI 编码结果后用abi.decode还原需要十进制数值时优先vm.ffiUint避免手工abi.decode与类型猜测需要文本时优先vm.ffiString杜绝“输出恰好是 hex 被误解码”的隐患需要二进制数据时优先vm.ffiBytes且务必保证命令输出为严格 hex 格式如xxd -p、od -An -tx1否则测试会明确 revert而不是静默返回错误字节。5.2 已知边界ffiString的 trim 行为stdout 首尾空白会被去除中间空白保留ffiBytes要求 stdout 整体是连续 hex可含0x前缀由hex::decode决定含换行符的 hexdump 输出需先经tr -d \n等工具规整ffiUint的解析基于 alloy 的DynSolType::Uint(256)解析器超过uint256范围的数值会解析失败命令退出码非 0 时三者一律报错并附带 stderr 内容stderr 非空但退出成功时仅记录 warning。六、延伸阅读变更条目.changelog/typed-ffi-output.mdcheatcode 注册表与全部参数crates/cheatcodes/assets/cheatcodes.json底层实现crates/cheatcodes/src/fs.rs规范接口声明crates/cheatcodes/spec/src/vm.rs回归测试testdata/default/cheats/Ffi.t.solFFI 开关配置与 Foundry 全局配置说明crates/config/src/lib.rs【免费下载链接】foundryFoundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.项目地址: https://gitcode.com/GitHub_Trending/fo/foundry创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考