Foundry 运行时硬分叉覆盖:Forge 与 Chisel 共享的--hardforkCLI 参数解析
【免费下载链接】foundryFoundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.项目地址: https://gitcode.com/GitHub_Trending/fo/foundry
本文基于 Foundry 仓库变更记录 runtime-hardfork-cli-override.md 展开。该变更引入了一个Forge 与 Chisel 命令共享的
--hardfork覆盖参数,用于直接指定 EVM 运行时的硬分叉(runtime EVM hardfork),并分别以chisel: minor、forge: minor、foundry-cli: minor三个版本级别发布。读完本文,你将掌握该参数的语法、命名空间规则、与foundry.toml配置及evm_version的优先级关系,以及它在源码中的解析与校验链路。
一、为什么需要运行时硬分叉覆盖
在 Foundry 中,"EVM 版本"存在两个容易混淆的维度:
- 编译期 EVM 版本(
evm_version):决定 Solidity 编译器(solc)以哪个版本语义生成字节码,配置项位于 config/src/lib.rs 中的evm_version: EvmVersion。 - 运行时 EVM 硬分叉(hardfork):决定 revm 执行字节码时采用哪套 opcode 语义、gas 规则与预编译合约,由
Config中的hardfork: Option<FoundryHardfork>字段承载。
在引入本次变更之前,Forge 与 Chisel 对运行时硬分叉的指定方式并不一致:Forge 主要依赖foundry.toml中的配置(以及部分命令各自携带的参数),而 Chisel 需要自行处理硬分叉解析。本次变更的核心动作是:在共享的 CLI 参数层(foundry-cli)新增统一的--hardfork参数,让 Forge 与 Chisel 都通过同一套机制覆盖运行时 EVM 硬分叉,从而消除两条命令之间行为不一致的问题。
从变更记录的 frontmatter 可以看到它同时触及三个 crate,说明这是一次跨命令的共享能力收拢,而非某个命令的独有特性。
二、参数定义:EvmArgs中的--hardfork
--hardfork参数定义在共享的 EVM 参数结构体EvmArgs中,位于 crates/cli/src/opts/evm.rs:
/// The runtime EVM hardfork to use. /// /// Network-specific hardforks must be namespaced, for example `tempo:T5`. #[arg(long, value_name = "HARDFORK")] #[serde(skip_serializing_if = "Option::is_none")] pub hardfork: Option<FoundryHardfork>,关键信息:
- 参数名为
--hardfork,取值类型是Option<FoundryHardfork>,即未指定时为空,由配置层兜底; - 文档注释明确提示:网络专属硬分叉必须带命名空间前缀,例如
tempo:T5; value_name = "HARDFORK"说明该参数接受一个字符串值;- 该字段通过 serde 序列化进 figment 配置提供器(
EvmArgs实现了Provider),从而可以无缝合并进Config的解析流程(见 evm.rs 的data()实现)。
EvmArgs在注释中明确说明了自己在配置层级中的位置:
EvmArgsandEnvArgstake the highest precedence in the Config/Figment hierarchy.
也就是说,通过--hardfork传入的值在配置合并时拥有最高优先级,能够覆盖foundry.toml中的同名配置。
三、FoundryHardfork:统一的硬分叉枚举
--hardfork的值类型FoundryHardfork定义于 crates/evm/hardforks/src/lib.rs,是一个覆盖多网络的统一枚举:
pub enum FoundryHardfork { Ethereum(EthereumHardfork), #[cfg(feature = "optimism")] Optimism(OpHardfork), Tempo(TempoHardfork), #[cfg(feature = "monad")] Monad(MonadHardfork), }它把四类网络的硬分叉收拢为一个类型:
| 网络 | 枚举变体 | 说明 |
|---|---|---|
| Ethereum | Ethereum(EthereumHardfork) | 即 alloy 的EthereumHardfork,如Shanghai、Cancun、Prague等 |
| Optimism | Optimism(OpHardfork) | 需要启用optimismfeature,如op:... |
| Tempo | Tempo(TempoHardfork) | Tempo 网络专属硬分叉,如tempo:T5 |
| Monad | Monad(MonadHardfork) | 需要启用monadfeature,如monad:MonadNine |
命名空间解析规则
FromStr实现(lib.rs)定义了 CLI 字符串到枚举的映射规则:
- 不带冒号的裸字符串(如
--hardfork shanghai):按 Ethereum 硬分叉解析; - 带命名空间的字符串(
ns:fork):按前缀匹配网络,且前缀大小写不敏感(to_ascii_lowercase),硬分叉名中的-与空格会被规范化为下划线; - 支持的命名空间别名:
eth/ethereum→ Ethereumop/optimism→ Optimism(需optimismfeature)t/tempo→ Tempom/monad→ Monad(需monadfeature)- 未知命名空间时回退尝试 Ethereum 解析,仍失败则报错
unknown hardfork '{raw}'
序列化与反序列化
- 序列化时(lib.rs),Ethereum 硬分叉直接输出裸名称(如
prague),其余网络输出network:hardfork格式(如optimism:...、tempo:T5、monad:MonadNine); Deserialize同样走FromStr,因此foundry.toml中的hardfork = "tempo:T3"与 CLI 上的--hardfork tempo:T3使用完全一致的解析语义。
四、实战用法
Forge:指定测试运行时的硬分叉
Forge 的命令(如forge test)通过共享EvmArgs获得该参数,例如:
# 用 Ethereum Prague 硬分叉运行测试 forge test --hardfork prague # 指定 Tempo 网络硬分叉(注意命名空间) forge test --hardfork tempo:T5 # 指定 Monad 硬分叉 forge test --hardfork monad:MonadNine在 Forge 执行链路上,--hardfork的值最终会进入执行配置:TestConfig中的hardfork: Option<FoundryHardfork>字段(见 multi_runner.rs),并在构建执行器时通过resolve_execution_spec解析为具体的spec_id,同时传递给调用跟踪解码器(with_hardfork),保证执行语义与 trace 解码使用同一个硬分叉(见 forge/src/cmd/test/mod.rs)。
Chisel:REPL 会话的硬分叉
Chisel 同样继承了该参数,例如:
# 以 Cancun 硬分叉启动交互式会话 chisel --hardfork cancun # 切换到 Tempo 网络硬分叉 chisel --hardfork tempo:T3Chisel 的会话配置结构体ChiselSessionConfig中新增了resolved_hardfork: Option<FoundryHardfork>字段(标注#[serde(skip)],不持久化),在执行前通过resolve_execution_spec解析并写入 EVM 环境(见 chisel/src/executor.rs),同时用于 trace 解码器的硬分叉上下文(见 chisel/src/dispatcher.rs)。
与--chain/--evm-version的关系
--evm-version(evm_version)仍只影响编译;--hardfork直接覆盖运行时语义;- 配置层提供了优先级兜底:
Config::evm_spec_id()的实现(config/src/lib.rs)为:
pub fn evm_spec_id<SPEC: FromEvmVersion>(&self) -> SPEC { self.hardfork.map(Into::into).unwrap_or_else(|| evm_spec_id(self.evm_version)) }即:只要配置了hardfork,运行时 spec 就以它为准;否则回退到evm_version推导。这解释了为什么--hardfork被称为"覆盖"(override)。
五、foundry.toml中的对应配置
--hardfork与配置文件字段一一对应。在foundry.toml中可以这样写:
[profile.default] # 与 --hardfork prague 等价 hardfork = "prague" # 网络专属硬分叉必须命名空间化 # hardfork = "tempo:T3"仓库测试 config/src/lib.rs 验证了该行为:
#[test] fn tempo_hardfork_infers_tempo_network() { figment::Jail::expect_with(|jail| { jail.create_file( "foundry.toml", r#" [profile.default] hardfork = "tempo:T3" "#, )?; let config = Config::load().unwrap(); assert_eq!(config.hardfork, Some(FoundryHardfork::Tempo(TempoHardfork::T3))); assert!(config.networks.is_tempo()); Ok(()) }); }注意一个"附带效果":指定命名空间的硬分叉会自动推断并激活对应网络。例如hardfork = "tempo:T3"会让config.networks.is_tempo()变为true;monad:MonadNine会推断 Monad 网络(见 config/src/lib.rs 的namespaced_hardfork_infers_monad_network测试)。
六、冲突校验与安全边界
Config在加载后会调用normalize_hardfork_settings()(config/src/lib.rs),对硬分叉与网络配置做一致性校验:
fn normalize_hardfork_settings(&mut self) -> Result<(), Error> { self.networks.validate().map_err(Error::from)?; let Some(hardfork) = self.hardfork else { return Ok(()) }; self.networks = self.networks.normalize_for_hardfork(hardfork).map_err(Error::from)?; Ok(()) }仓库测试hardfork_rejects_conflicting_network(config/src/lib.rs)验证了冲突检测逻辑:
#[test] fn hardfork_rejects_conflicting_network() { figment::Jail::expect_with(|jail| { jail.create_file( "foundry.toml", r#" [profile.default] tempo = true hardfork = "shanghai" "#, )?; let err = Config::load().unwrap_err(); assert!( err.to_string().to_lowercase().contains( "hardfork `shanghai` conflicts with network config `tempo`" ) ); Ok(()) }); }结论:当--hardfork指定的硬分叉与已激活的网络(如tempo = true)冲突时,配置加载会直接报错,错误信息形如hardforkshanghaiconflicts with network configtempo``,避免在错误的语义下静默执行。反之,celo_network_accepts_ethereum_hardfork测试(config/src/lib.rs)表明:Celo 网络可以合法地接受 Ethereum 硬分叉(如hardfork = "prague"),说明冲突校验是网络感知的,而非一刀切。
七、CLI 层的解析测试
EvmArgs自身的单元测试hardfork_arg_selects_network(evm.rs)完整演示了从 CLI 字符串到Config的整条链路:
#[test] fn hardfork_arg_selects_network() { let args = EvmArgs::parse_from(["foundry-cli", "--hardfork", "tempo:T5"]); let hardfork = "tempo:T5".parse::<FoundryHardfork>().unwrap(); assert_eq!(args.hardfork, Some(hardfork)); let config = Config::from_provider(Config::figment().merge(args)).unwrap(); assert_eq!(config.hardfork, Some(hardfork)); assert!(config.networks.is_tempo()); }该测试同时验证了三件事:
- clap 能正确解析
--hardfork tempo:T5; "tempo:T5"能通过FromStr解析为FoundryHardfork::Tempo(T5);EvmArgs作为 figmentProvider合并进Config后,config.hardfork被正确设置,且Tempo 网络被自动激活。
八、解析链路小结
从 CLI 输入到 EVM 执行,--hardfork的完整数据流为:
- 参数解析:clap 将
--hardfork <HARDFORK>解析为EvmArgs.hardfork: Option<FoundryHardfork>,字符串按FromStr规则转枚举(crates/cli/src/opts/evm.rs); - 配置合并:
EvmArgs作为Provider与Config(含foundry.toml)合并,CLI 值优先级最高(evm.rs); - 一致性校验:
normalize_hardfork_settings()校验与网络配置的冲突,并自动激活对应命名空间网络(crates/config/src/lib.rs); - Spec 解析:Forge / Chisel 在构建执行环境时调用
resolve_execution_spec(evm_version, hardfork, evm_env, context, ...),把FoundryHardfork转换为 revm 的SpecId并写入cfg_env.spec(crates/evm/core/src/opts.rs); - 执行与解码一致:解析出的
resolved_hardfork同时传给 trace 解码器(.with_hardfork(...)),保证执行语义与解码语义对齐。
九、适用前提与限制
- 网络硬分叉可用性取决于编译特性:
Optimism变体需要optimismfeature,Monad变体需要monadfeature(见 crates/evm/hardforks/src/lib.rs),未启用对应 feature 时,相关命名空间无法解析; - 历史 fork 场景以源链为准:
resolve_execution_spec的注释指出,ExecutionSpecContext::fork场景下,若 fork 端点上报了精确硬分叉(fork_hardfork),会优先采用端点硬分叉(见 crates/evm/core/src/opts.rs 及resolve_execution_spec_prefers_exact_endpoint_hardfork测试),此时本地--hardfork的覆盖能力受上下文约束; - 命名空间格式必须规范:网络专属硬分叉若遗漏前缀(如直接写
--hardfork T5),将按 Ethereum 解析并报unknown ethereum hardfork 'T5'。
结语
--hardfork共享参数的引入,把 Forge 与 Chisel 的运行时硬分叉选择统一到了foundry-cli的EvmArgs层:一套参数、一套解析语义、一套校验规则,覆盖 Ethereum / Optimism / Tempo / Monad 四类网络。对开发者而言,它提供了比evm_version更精确、比改foundry.toml更轻量的运行时语义控制手段;对项目而言,它消除了两条命令在硬分叉处理上的行为分叉,为后续 trace 解码、fork 场景与多网络执行奠定了统一的硬分叉上下文基础。
延伸阅读:本文关联的变更记录见 .changelog/runtime-hardfork-cli-override.md,共享 EVM 参数定义见 crates/cli/src/opts/evm.rs,硬分叉枚举与解析见 crates/evm/hardforks/src/lib.rs,配置字段与校验见 crates/config/src/lib.rs,执行 spec 解析见 crates/evm/core/src/opts.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),仅供参考