ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Foundry 符号执行上下文修复:`vm.isContext` 在 `forge test`/`coverage`/`snapshot` 中的正确执行上下文

Foundry 符号执行上下文修复:`vm.isContext` 在 `forge test`/`coverage`/`snapshot` 中的正确执行上下文 Foundry 符号执行上下文修复vm.isContext在forge test/coverage/snapshot中的正确执行上下文【免费下载链接】foundryFoundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.项目地址: https://gitcode.com/GitHub_Trending/fo/foundry本文围绕 Foundry 仓库中一条patch级变更展开修复符号执行symbolic execution下vm.isContext使用真实 Forge 执行上下文含 coverage 与 snapshot 场景的问题。通过源码级拆解你将掌握vm.isContext的判定原理、ForgeContext枚举的完整语义、符号执行与具体执行在上下文处理上的差异以及如何用check*符号测试验证上下文行为。变更背景一条 changelog fragment 说明了什么Foundry 仓库使用tempoxyz/changelogs管理发布说明所有 Pull Request 必须添加或更新.changelog/*.md条目。本主题对应的 fragment 为.changelog/symbolic-is-context.md其 frontmatter 与正文如下--- forge: patch foundry-cheatcodes: patch foundry-evm-symbolic: patch --- Fixed symbolic vm.isContext to use the actual Forge execution context, including coverage and snapshot runs.该条目揭示了三个信息影响范围forgeCLI 入口、foundry-cheatcodescheatcode 实现、foundry-evm-symbolic原生符号执行引擎三个包同时打了patch级别补丁修复对象符号执行模式下vm.isContext的返回值修复目标让符号执行使用真实的 Forge 执行上下文特别补上了 coverageforge coverage和 snapshotforge snapshot两个此前可能失真的场景。要理解这条修复需要先弄清vm.isContext本身是什么、它依赖的上下文从何而来再分析符号执行路径为什么会在 coverage/snapshot 下失真。vm.isContext与ForgeContext运行时区分命令场景的开关vm.isContext是 Foundry 提供的一个view型 cheatcode用于在测试/脚本代码中判断当前进程到底运行在哪个 Forge 子命令之下。它的规范声明位于 crates/cheatcodes/spec/src/vm.rsfunction isContext(ForgeContext context) external view returns (bool result);其中ForgeContext是接口内定义的枚举crates/cheatcodes/spec/src/vm.rs共 9 个取值枚举值含义TestGroup测试组执行上下文test、coverage或snapshot三者之一Testforge test执行上下文Coverageforge coverage执行上下文Snapshotforge snapshot执行上下文ScriptGroup脚本组执行上下文dry run、broadcast 或 resume 之一ScriptDryRunforge script执行上下文ScriptBroadcastforge script --broadcast执行上下文ScriptResumeforge script --resume执行上下文Unknown未知的forge执行上下文在 cheatcode 注册表中该 cheatcode 的 ABI 元数据如下crates/cheatcodes/assets/cheatcodes.json声明function isContext(ForgeContext context) external view returns (bool result);签名isContext(uint8)选择器0x64af255d分组utilities状态stable安全性unsafe描述Returns true ifforgecommand was executed in given context.判断forge命令是否在给定上下文中执行。具体执行revm 路径的实现具体执行的isContext实现在 crates/cheatcodes/src/env.rsimpl Cheatcode for isContextCall { fn applyFEN: FoundryEvmNetwork(self, _state: mut CheatcodesFEN) - Result { let Self { context } self; Ok((FORGE_CONTEXT.get() Some(context)).abi_encode()) } }它直接与一个进程级全局变量比较。该全局变量定义于同一文件crates/cheatcodes/src/env.rs/// Stores the forge execution context for the duration of the program. pub static FORGE_CONTEXT: OnceLockForgeContext OnceLock::new(); /// Returns the current forge execution context, if it has been set. pub fn current_execution_context() - OptionForgeContext { FORGE_CONTEXT.get().copied() }关键设计点是OnceLock与set_execution_contextcrates/cheatcodes/src/env.rs/// Set forge command current execution context for the duration of the program. /// Execution context is immutable, subsequent calls of this function wont change the context. pub fn set_execution_context(context: ForgeContext) { let _ FORGE_CONTEXT.set(context); }OnceLock保证上下文进程内只设置一次、之后不可变。这也解释了isContext只能回答当前命令是什么而不能表示嵌套或动态切换的上下文。上下文在哪里被写入上下文在 Forge 子命令分发入口处写入crates/forge/src/args.rs/// Run the subcommand. pub fn run_command(args: Forge) - Result() { // Set the execution context based on the subcommand. let context match args.cmd { ForgeSubcommand::Test(_) | ForgeSubcommand::Fuzz(_) ForgeContext::Test, ForgeSubcommand::Coverage(_) ForgeContext::Coverage, ForgeSubcommand::Snapshot(_) ForgeContext::Snapshot, ForgeSubcommand::Script(cmd) { if cmd.broadcast { ForgeContext::ScriptBroadcast } else if cmd.resume { ForgeContext::ScriptResume } else { ForgeContext::ScriptDryRun } } _ ForgeContext::Unknown, }; set_execution_context(context); ... }可见映射关系为forge test/forge fuzz→ForgeContext::Testforge coverage→ForgeContext::Coverageforge snapshot→ForgeContext::Snapshotforge script/--broadcast/--resume→ 对应的三个脚本上下文其他子命令 →ForgeContext::Unknown。该全局上下文还被其他 cheatcode 复用。例如executeTransaction在ForgeContext::ScriptGroup下会被禁止crates/cheatcodes/src/evm.rs说明FORGE_CONTEXT是多个 cheatcode 共享的进程级状态。符号执行路径修复前的问题与修复方式foundry-evm-symbolic是 Foundry 的原生符号 EVM 执行器驱动forge test --symbolic详见 crates/evm/symbolic/README.md。它拥有独立于 revm 的 cheatcode 处理器因此vm.isContext在符号路径下并不是调用crates/cheatcodes/src/env.rs的isContextCall而是在 crates/evm/symbolic/src/executor/cheatcodes.rs 中单独处理isContextCall::SELECTOR { let context read_abi_concrete_word_arg( mut self.cx, state.memory, args_offset, 0, symbolic vm.isContext, )?; let context u8::try_from(context) .ok() .and_then(|context| ForgeContext::try_from(context).ok()) .ok_or(SymbolicError::Unsupported(symbolic vm.isContext invalid context))?; return Ok(CheatcodeOutcome::Continue(vec![SymExpr::constant( mut self.cx, U256::from(current_execution_context() Some(context)), )])); }这段代码揭示了本变更的两个要点真实上下文来源符号处理器调用的正是crates/cheatcodes/src/env.rs中的current_execution_context()——与具体执行路径共用同一个FORGE_CONTEXT全局变量。这正是使用实际的 Forge 执行上下文的代码体现参数必须为具体值isContext的入参通过read_abi_concrete_word_arg读取必须是可解析的具体uint8随后用ForgeContext::try_from转换为枚举非法取值会返回SymbolicError::Unsupported即符号执行无法处理符号化的上下文参数只能比较具体上下文。符号上下文注册还出现在 crates/evm/symbolic/src/runtime/cheatcodes.rs说明该选择器同样被符号运行时识别。修复前若符号路径对上下文的处理与具体路径不一致例如未读取全局上下文、或对 coverage/snapshot 场景返回了错误结果就会导致同一份测试代码在forge test与forge coverage/forge snapshot下得到不同错误的判定——这正是本次 patch 要消除的偏差。如何用符号测试验证checkForge*上下文用例仓库用集成测试锁定该行为。核心测试契约位于 crates/forge/tests/cli/context.rs其中既有具体执行的testForge*用例也有符号执行的变体。测试断言如下以test、snapshot、coverage三种命令为例function testForgeTestContext() external view { require(vm.isContext(Vm.ForgeContext.TestGroup) !vm.isContext(Vm.ForgeContext.ScriptGroup), wrong context); require(vm.isContext(Vm.ForgeContext.Test), wrong context); require(!vm.isContext(Vm.ForgeContext.Coverage), wrong context); require(!vm.isContext(Vm.ForgeContext.Snapshot), wrong context); } function testForgeSnapshotContext() external view { require(vm.isContext(Vm.ForgeContext.TestGroup) !vm.isContext(Vm.ForgeContext.ScriptGroup), wrong context); require(vm.isContext(Vm.ForgeContext.Snapshot), wrong context); require(!vm.isContext(Vm.ForgeContext.Test), wrong context); require(!vm.isContext(Vm.ForgeContext.Coverage), wrong context); } function testForgeCoverageContext() external view { require(vm.isContext(Vm.ForgeContext.TestGroup) !vm.isContext(Vm.ForgeContext.ScriptGroup), wrong context); require(vm.isContext(Vm.ForgeContext.Coverage), wrong context); require(!vm.isContext(Vm.ForgeContext.Test), wrong context); require(!vm.isContext(Vm.ForgeContext.Snapshot), wrong context); }注意语义组合TestGroup是Test | Coverage | Snapshot的并集因此forge test下TestGroup与Test同时为true而Coverage、Snapshot为falseScriptGroup与TestGroup互斥。脚本侧还有runDryRun/runBroadcast两个用例分别验证ScriptDryRun、ScriptBroadcast上下文crates/forge/tests/cli/context.rs。符号执行侧的验证是本变更的核心回归测试crates/forge/tests/cli/context.rsforgetest!(symbolic_uses_actual_forge_context, |prj, cmd| { if !Command::new(z3).arg(--version).output().is_ok_and(|output| output.status.success()) { return; } prj.insert_ds_test(); prj.add_source( ForgeContextTest.t.sol, FORGE_TEST_CONTEXT_CONTRACT.replace(testForge, checkForge), ); prj.update_config(|config| config.symbolic.enabled true); for (command, test) in [ (test, checkForgeTestContext), (coverage, checkForgeCoverageContext), (snapshot, checkForgeSnapshotContext), ] { cmd.forge_fuse().args([command, --match-test, test]).assert_success().stdout_eq( foundry_test_utils::str![[r# ... [PASS] checkForge[..] (paths: [..]) ... #]], ); } });该测试的要点需要本机存在z3否则跳过符号求解器前置条件把具体测试的testForge*函数名批量替换为checkForge*使其成为符号执行入口依次在test、coverage、snapshot三个子命令下运行断言全部输出[PASS]若符号执行使用了错误的上下文例如 coverage/snapshot 下仍按Test判定require将失败测试不会 PASS。这正好从测试层面印证了 changelog 中 including coverage and snapshot runs 的含义三个命令的符号路径必须与具体路径返回一致的上下文判定。从符号测试走向常规使用check*测试与vm.isContextcheck*/prove*是符号测试入口仅在开启符号模式时被发现详见 crates/evm/symbolic/README.md。一个组合使用vm.isContext的符号测试示例// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; import forge-std/Test.sol; interface VmCompat { enum ForgeContext { TestGroup, Test, Coverage, Snapshot, ScriptGroup, ScriptDryRun, ScriptBroadcast, ScriptResume, Unknown } function isContext(ForgeContext context) external view returns (bool result); } contract ContextSymbolicTest is Test { function check_only_in_test_context() external view { VmCompat compat VmCompat(address(vm)); require(compat.isContext(VmCompat.ForgeContext.Test), not in test context); require(!compat.isContext(VmCompat.ForgeContext.ScriptGroup), in script context); assertTrue(true); } }运行需要本机安装z3macOS 可用brew install z3Ubuntu 可用sudo apt-get install z3forge test --symbolic --match-test check_only_in_test_context如果把它换成check_only_in_coverage_context并在forge coverage --symbolic若当前构建支持下运行require(vm.isContext(Vm.ForgeContext.Coverage))将返回true——这正是本次修复保证的使用实际 Forge 执行上下文行为。符号执行中还兼容 Halmos 风格的上下文辅助接口crates/forge/tests/cli/test_cmd/symbolic_cheatcodes.rs例如同样通过SymbolicVmCompat接口调用isContext断言TestGroup与Test为真、ScriptGroup为假crates/forge/tests/cli/test_cmd/symbolic_cheatcodes.rs。实战建议与注意事项上下文是进程级、不可变的FORGE_CONTEXT由OnceLock承载一旦forge子命令确定即固定。因此vm.isContext适合在测试/脚本内做当前运行模式的门控判断不适合表达动态切换的运行时状态。符号模式下入参必须具体symbolic vm.isContext要求传入可解析的具体枚举值uint8符号化参数会触发Unsupported对应FAIL: incomplete symbolic execution而非证明失败。验证依赖 z3仓库中的符号上下文回归测试在无z3时直接跳过见 crates/forge/tests/cli/context.rs本地复现前需确保求解器可用。组合判定语义TestGroup与ScriptGroup互斥test、coverage、snapshot三个命令下TestGroup恒为true但具体到Test/Coverage/Snapshot则各不相同编写断言时应参考 crates/forge/tests/cli/context.rs 的组合写法。小结vm.isContext把当前 Forge 子命令是什么这一进程级事实暴露给 Solidity 代码而本次patch修复确保了符号执行引擎与具体执行引擎读取同一个FORGE_CONTEXT全局状态补齐了forge coverage与forge snapshot场景下符号判定的正确性。理解这条变更的完整链条——从.changelog条目、ForgeContext枚举、set_execution_context入口、符号路径的current_execution_context()调用到symbolic_uses_actual_forge_context回归测试——有助于你在编写可移植的 Forge 测试/脚本时正确运用执行上下文门控并理解符号执行与具体执行在 cheatcode 语义上的差异。【免费下载链接】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),仅供参考
返回列表