Summary
The EVM layer derives every behavior gate from one resolved spec via ordinal inclusion (MegaSpecId::is_enabled, same design as revm's SpecId::is_enabled_in), so spec additivity is structural there.
The block layer deviates: pre-block system-contract deploys and EIP gates consult per-fork hardfork registration (is_X_active_at_timestamp) instead of the resolved spec.
With a partial-ladder hardfork config (e.g. a genesis scheduling rex6Time without rex5Time), spec_id(timestamp) resolves REX6 but the Rex5-gated pre-block setup is silently skipped: the EIP-2935/EIP-4788 fail-closed checks, the SequencerRegistry deployment/updates, and the Rex5 oracle bytecode selection.
Every canonical schedule (mainnet/testnet/all-activated constants, real genesis files) carries the complete ladder, where both gating styles are bit-for-bit equivalent — so this is unreachable on well-formed configs and purely a robustness gap.
Raised by Codex review on #348: #348 (comment)
Affected gate sites
block/executor.rs:179,244 — is_rex_5_active_at_timestamp (EIP-2935/4788 fail-closed + SequencerRegistry deploy/update)
block/eips.rs:62,131 — is_rex_5_active_at_timestamp (result handling variants)
system/oracle.rs:61,69,72,119 — MiniRex deploy gate + Rex5/Rex2 bytecode selection
system/keyless_deploy.rs:62 — Rex2
system/control.rs:64, system/limit_control.rs:43 — Rex4
system/sequencer_registry.rs:144 — Rex5
Proposed fix
Resolve spec_id(block_timestamp) once at the pre-block entry point and gate the sites above on spec.is_enabled(MegaSpecId::X).
- On complete-ladder configs the change is observationally identical (each
is_X_active_at_timestamp(ts) ⇔ spec_id(ts).is_enabled(X) when the ladder is complete with monotone times), so stable-spec behavior and replay are unaffected.
- On partial-ladder configs, additivity becomes structural again: a Rex6-only config deploys all lower-spec system contracts at the Rex6 activation block and enforces the fail-closed checks.
Add a partial-ladder regression test: a config registering only Rex6 must deploy the registry/oracle and enforce the EIP-2935/4788 fail-closed checks at activation.
Related (separate repo)
mega-reth's MegaethChainSpec::from_genesis performs no ladder-completeness validation either (absent forks silently become ForkCondition::Never); a load-time predecessor check there is the matching defense-in-depth.
Summary
The EVM layer derives every behavior gate from one resolved spec via ordinal inclusion (
MegaSpecId::is_enabled, same design as revm'sSpecId::is_enabled_in), so spec additivity is structural there.The block layer deviates: pre-block system-contract deploys and EIP gates consult per-fork hardfork registration (
is_X_active_at_timestamp) instead of the resolved spec.With a partial-ladder hardfork config (e.g. a genesis scheduling
rex6Timewithoutrex5Time),spec_id(timestamp)resolvesREX6but the Rex5-gated pre-block setup is silently skipped: the EIP-2935/EIP-4788 fail-closed checks, the SequencerRegistry deployment/updates, and the Rex5 oracle bytecode selection.Every canonical schedule (mainnet/testnet/all-activated constants, real genesis files) carries the complete ladder, where both gating styles are bit-for-bit equivalent — so this is unreachable on well-formed configs and purely a robustness gap.
Raised by Codex review on #348: #348 (comment)
Affected gate sites
block/executor.rs:179,244—is_rex_5_active_at_timestamp(EIP-2935/4788 fail-closed + SequencerRegistry deploy/update)block/eips.rs:62,131—is_rex_5_active_at_timestamp(result handling variants)system/oracle.rs:61,69,72,119— MiniRex deploy gate + Rex5/Rex2 bytecode selectionsystem/keyless_deploy.rs:62— Rex2system/control.rs:64,system/limit_control.rs:43— Rex4system/sequencer_registry.rs:144— Rex5Proposed fix
Resolve
spec_id(block_timestamp)once at the pre-block entry point and gate the sites above onspec.is_enabled(MegaSpecId::X).is_X_active_at_timestamp(ts)⇔spec_id(ts).is_enabled(X)when the ladder is complete with monotone times), so stable-spec behavior and replay are unaffected.Add a partial-ladder regression test: a config registering only Rex6 must deploy the registry/oracle and enforce the EIP-2935/4788 fail-closed checks at activation.
Related (separate repo)
mega-reth's
MegaethChainSpec::from_genesisperforms no ladder-completeness validation either (absent forks silently becomeForkCondition::Never); a load-time predecessor check there is the matching defense-in-depth.