Skip to content

feat(l1): support bal-devnet-4 (bal@v5.7.0)#6521

Open
edg-l wants to merge 9 commits intomainfrom
bal-devnet-4-pr
Open

feat(l1): support bal-devnet-4 (bal@v5.7.0)#6521
edg-l wants to merge 9 commits intomainfrom
bal-devnet-4-pr

Conversation

@edg-l
Copy link
Copy Markdown
Contributor

@edg-l edg-l commented Apr 23, 2026

Brings ethrex up to bal@v5.7.0 (bal-devnet-4). Replaces closed PR #6518 — that one was accidentally opened on the long-lived bal-devnet-4 branch that ethpanda tracks; this one uses the dedicated bal-devnet-4-pr branch so the PR lifecycle doesn't disturb the rolling progress branch.

What bal-devnet-4 requires

The spec update rolls up five EIPs that interlock:

  • EIP-7928 — Block Access List, BlockAccessIndex widened u16 → u32.
  • EIP-8037 — two-dimensional gas accounting for state creation, with dynamic cost_per_state_byte, per-frame reservoir, clamp-and-spill refunds, per-tx 2D inclusion check, immutable intrinsic state gas, same-tx SELFDESTRUCT refund, and top-level reservoir reset on tx failure.
  • EIP-7976 — calldata floor raised from 10 to 16 gas per token (effective 64 gas/byte).
  • EIP-7981 — access-list data bytes (20 per address, 32 per storage key) fold into the floor-token count.
  • EIP-7708 — transfer + burn logs at SYSTEM_ADDRESS with lexicographic ordering.

Against bal@v5.6.1 the notable deltas are the u32 index widening, dynamic cpsb (previously a static 1174), immutable intrinsic state gas (PR #2711), and the per-tx 2D inclusion check (PR #2703). Fixtures were regenerated accordingly.

Results

  • Hive eels/consume-engine amsterdam: 1342 pass / 0 fail (from 10 failing at the start).
  • ef_tests/blockchain: 8781 pass (6 EIP-8025 witness_codes_* zkevm@v0.3.x fixtures skipped — filled against bal@v5.6.1 pre-chore(l1): fix precompile tests from EIP-7702 #2711, explicit skip in tooling/ef_tests/blockchain/tests/all.rs, re-enable when zkevm@v0.4.x ships).
  • Full LEVM + blockchain unit test suite: 478 pass.

Builder/validator parity

The bigger-than-expected part of this PR is making sure ethrex-as-builder and ethrex-as-validator agree on every Amsterdam output. An audit turned up three miss-slot risks on the builder side (Amsterdam CREATE intrinsic gas in the mempool, missing 2D inclusion check in fill_transactions, missing BAL checkpoint/restore in the L2 builder). All three are fixed.

To keep this from regressing silently, there's a new test file, test/tests/blockchain/builder_validator_parity_tests.rs, that builds a block via the payload builder and then runs the built block+BAL through the validator pipeline. The module doc explains the rationale and what a failure means (P0: would cause a missed slot on devnet). Nine positive scenarios exercise the shared path; five negative scenarios corrupt the BAL deterministically and assert the validator rejects — each mirrors one of the Hive test_bal_invalid_* cases we fixed in this PR.

Where to focus review

  • crates/vm/backends/levm/mod.rs — biggest diff. Parallel validator with the shadow BAL recorder, PART B validation interactions, SYSTEM_ADDRESS handling.
  • crates/vm/levm/src/vm.rs — new state-gas counters + CallFrame snapshot fields for the EIP-8037 reservoir model.
  • crates/vm/levm/src/gas_cost.rs::cost_per_state_byte — formula implementation, matched against EELS. Sanity: cpsb(120_000_000) == 1174.
  • crates/vm/levm/src/utils.rsget_intrinsic_gas (VM) and intrinsic_gas_dimensions (standalone, used by mempool + builder) must stay in sync. Parity is checked by test_intrinsic_parity_*.
  • crates/blockchain/payload.rs::fill_transactions — 2D inclusion check + BAL checkpoint semantics.
  • test/tests/blockchain/builder_validator_parity_tests.rs — the primary regression guard for everything else.

Relationship to PR #6463

#6463 (withdrawal-phase BAL reverse check) merged into main before this branch was rebased. This PR builds on its validate_bal_withdrawal_index Part B; the u32 widening + #6463's new &BalAddressIndex parameter are merged into a combined signature. Not re-introducing #6463's changes.

Test plan

  • make -C tooling/ef_tests/blockchain test
  • make run-hive-eels-amsterdam
  • cargo test -p ethrex-test --test ethrex_tests
  • cargo fmt --all + cargo check --workspace --lib

Pins

  • Fixtures: bal@v5.7.0
  • EELS: 524b44617e410ab21b5122f0be5113b62a0e76ee on branch devnets/bal/4

edg-l added 4 commits April 23, 2026 15:08
Brings ethrex up to bal-devnet-4 fixture spec. Rolls up EIP-7928,
EIP-8037, EIP-7976, EIP-7981, EIP-7708 and misc BAL validation fixes
into one change set.

BAL (EIP-7928)
- Widen BlockAccessIndex and related recorder/index fields to u32.
- Shadow BAL recorder on per-tx tx_dbs in the parallel validator:
  diff touched_addresses / storage_reads against header BAL to catch
  missing pure-access entries and missing storage_reads.
- Fall back to pre-state code_hash in validate_tx_execution PART B
  when the BAL has no code_changes entry (missing_code_change).
- Stop whitelisting SYSTEM_ADDRESS from unaccessed_pure_accounts via
  system_seed / current_accounts_state scrubs; user-tx touches still
  remove it via the per-tx tracked_accounts path.

EIP-8037 (state gas 2D accounting)
- Dynamic cost_per_state_byte(block_gas_limit), Amsterdam only.
- Two-counter reservoir: state_gas_spill_outstanding +
  state_gas_credit_against_drain for correct revert math across
  nested sub-calls (PR #2733 clamp-and-spill).
- Per-tx 2D inclusion check (PR #2703) in sequential + parallel
  paths: reject with GAS_ALLOWANCE_EXCEEDED when tx.gas worst-case
  exceeds remaining block regular/state budget.
- intrinsic_state_gas immutable across the tx (PR #2711) and
  subtracted separately when deriving block-dimensional regular gas.
- CREATE collision/early/child failure refunds account state gas.
- Same-tx SELFDESTRUCT refunds state gas clamped against
  execution-only state gas (PR #2707), not total state_gas_used.
- Revert-path reservoir refill uses the PR #2733 X - Z formula.
- Top-level reservoir reset on tx failure (PR #2689).
- Zero gas_remaining on precompile exceptional halt so block
  accounting sees the full intrinsic.

Calldata / access-list floors
- TOTAL_COST_FLOOR_PER_TOKEN 10 -> 16 under Amsterdam (EIP-7976).
- Access-list data bytes fold into floor-token count (EIP-7981).

EIP-7708
- Lex-ordered burn logs, no coinbase priority-fee log, SELFDESTRUCT-
  destination coalescing.

Tests
- New levm tests for EIP-7976/7981, EIP-8037 refund/code-deposit/
  top-level-failure paths.
- Skip 6 zkevm@v0.3.0 EIP-8025 fixtures filled against bal@v5.6.1
  (re-enable once zkevm@v0.4.x ships).

Hive consume-engine amsterdam: 1339 pass, 3 remaining (withdrawal
missing-entry cases addressed by PR #6463, cherry-pick pending).
Addresses miss-slot risks found in the builder/validator parity audit of
the bal-devnet-4 rollup. Three builder-side paths could produce blocks
the validator rejects, plus minor hardening.

- Mempool intrinsic gas was using `TX_CREATE_GAS_COST = 53000`
  unconditionally for CREATE. Under Amsterdam the VM charges the
  `(regular, state)` split derived from `intrinsic_gas_dimensions`
  (`REGULAR_GAS_CREATE + STATE_BYTES_PER_NEW_ACCOUNT * cpsb`). Route
  through the shared helper for Amsterdam+ so admission matches VM charge.

- Payload builder (`fill_transactions`) had no EIP-8037 PR #2703 per-tx
  2D inclusion check. A tx passing execution in the builder could still
  fail the check in the validator's aggregation loop and invalidate the
  block. Expose `check_2d_gas_allowance` as pub and call it before any
  BAL touches so rejected txs contribute nothing.

- L2 payload builder recorded sender/recipient BAL touches before
  executing, with no checkpoint/restore for the `undo_last_tx` path
  (invalid L2 out-message) or apply-tx error. Mirror the L1 builder:
  take a `bal_checkpoint` after `set_bal_index`, restore on both
  rejection paths.

- `execute_block_parallel` now computes `is_amsterdam` locally and
  explicitly gates the 2D inclusion loop, keeping the Amsterdam-only
  invariant checkable rather than implicit in the caller.

- `check_2d_gas_allowance` doc now explains why our
  `block_regular_gas_used` aggregates `max(raw_regular, floor)` at
  tx-report time (matching EELS `block_output.block_gas_used`).

- Post-exec 2D overflow rollback in `apply_plain_transaction` replaces
  the unchecked `-=` on `cumulative_gas_spent` with `saturating_sub` +
  `debug_assert`.

- Missing-code-change per-tx BAL validation: when a BAL account has no
  `code_changes` entry (`seeded_pos == 0`), fall back to the
  `system_seed` / store pre-state code_hash. Fixes the remaining
  `missing_code_change` Hive test.

Hive consume-engine amsterdam: 1340 pass, 2 remaining (withdrawal
missing-entry cases addressed by PR #6463).
Regression guards for builder/validator drift on Amsterdam blocks. Both
paths share the VM core but diverge in plumbing (mempool admission,
shadow BAL recorder, 2D inclusion check, BAL checkpoint/restore, coinbase
and SYSTEM_ADDRESS filters), and a disagreement means a missed slot on
devnet that a green ef-tests run cannot catch — ef-tests only consume
blocks, never produce them.

Two groups of tests:

Positive parity (9). Builder produces a legitimate block, validator
pipeline (parallel, BAL-seeded) must accept. Covers: empty block with
only pre-exec system calls; plain transfer; CREATE tx (Amsterdam
intrinsic split); SSTORE state gas; BALANCE of untouched account
(pure-access BAL entry); calldata floor (EIP-7976); access-list floor
(EIP-7981); user-tx touch of SYSTEM_ADDRESS; multi-tx multi-sender
aggregation.

Negative parity (5). Builder produces a legitimate block, the test
corrupts the BAL (drops / mutates / appends entries), re-hashes the
header, and the validator must reject. Each scenario mirrors one of the
Hive test_bal_invalid_* cases fixed in session 3:

  parity_reject_missing_pure_access_account
  parity_reject_surplus_system_address
  parity_reject_missing_storage_read
  parity_reject_missing_storage_change
  parity_reject_missing_code_change

If one of the negative tests ever flips to "accept" the corresponding
BAL-validation check has regressed; treat as P0.
Follow-up to PR #6518 addressing the test-gap list documented in the
session-3 review. Covers every remaining item in TODO.md except the
upstream zkevm@v0.4.x fixture re-enable (tracked externally).

Tests (10 new):

- `test_cpsb_clamp_to_one_for_tiny_gas_limit`,
  `test_cpsb_30m_bin_boundary` — cpsb quantization boundaries. Guards
  against an off-by-one in the `if quantized > CPSB_OFFSET` branch and
  against bin boundary regressions in the 5M-30M range.

- `test_change_variants_rlp_roundtrip_index_above_u16_max` — RLP
  round-trip for all 4 BAL change variants at index 70_000, guarding
  against an accidental revert to the pre-devnet-4 `u16` type that
  would silently truncate high indices.

- `amsterdam_create_intrinsic_matches_vm_dimensions` — mempool
  admission for Amsterdam CREATE txs must match the VM's `(regular,
  state)` split (TX_BASE + REGULAR_GAS_CREATE +
  STATE_BYTES_PER_NEW_ACCOUNT * cpsb), not the legacy 53000.

- `test_intrinsic_parity_plain_transfer` /
  `test_intrinsic_parity_create_tx` /
  `test_intrinsic_parity_with_calldata_and_access_list` /
  `test_intrinsic_parity_eip7702_auth_list` — parity between the
  standalone `intrinsic_gas_dimensions` helper (used by mempool and
  payload builder) and `VM::get_intrinsic_gas` (used during execution).
  Run across Prague / Osaka / Amsterdam at 30M and 120M block gas
  limits.

- `test_call_to_empty_account_with_value_retains_parent_state_gas` —
  EIP-8037 CALL-to-empty-with-value charges new-account state gas in
  the caller's frame, retained across successful parent continuation.
  Pairs with the existing `test_child_charge_then_revert_returns_state_gas_to_parent`
  for the revert direction.

Code polish:

- Clarifying comment on the `frame_outstanding_delta` invariant in
  `credit_state_gas_refund` (`crates/vm/levm/src/vm.rs`). The
  subtraction is fragile — documenting why it must read
  `state_gas_spill_outstanding` and not `state_gas_spill`.

- `debug_assert!` guards on tx count vs `u32::MAX` at each block-exec
  entry (`execute_block`, `execute_block_pipeline`), keeping the
  EIP-7928 `BlockAccessIndex` invariant explicit rather than implicit
  in the ~10 downstream `u32::try_from(...).unwrap_or(u32::MAX)` sites.

Docs:

- `docs/roadmaps/forks-roadmap.md` — EIP-7976 / EIP-7981 flipped
  🔴→✅, EIP-8037 status line expanded (dynamic cpsb, clamp-and-spill,
  2D inclusion, same-tx SELFDESTRUCT refund), priority note updated
  for bal-devnet-4 + PR #6518.

All 478 tests pass. No behavior changes — these are regression guards
and documentation for the bal-devnet-4 work landed in PR #6518.
@github-actions github-actions Bot added the L1 Ethereum client label Apr 23, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 23, 2026

Lines of code report

Total lines added: 699
Total lines removed: 2
Total lines changed: 701

Detailed view
+------------------------------------------------------------------------+-------+------+
| File                                                                   | Lines | Diff |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/blockchain/constants.rs                                  | 18    | +2   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/blockchain/mempool.rs                                    | 464   | +11  |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/blockchain/payload.rs                                    | 832   | +19  |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/common/types/block_access_list.rs                        | 1107  | +15  |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/common/types/genesis.rs                                  | 1021  | +3   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/common/validation.rs                                     | 243   | -2   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/l2/sequencer/block_producer/payload_builder.rs           | 271   | +37  |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/backends/levm/mod.rs                                  | 2357  | +147 |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/call_frame.rs                                | 382   | +12  |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/environment.rs                               | 98    | +1   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/gas_cost.rs                                  | 879   | +23  |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/hooks/default_hook.rs                        | 536   | +78  |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs | 327   | +1   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/opcode_handlers/system.rs                    | 1017  | +68  |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/utils.rs                                     | 603   | +143 |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/vm.rs                                        | 694   | +136 |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/lib.rs                                                | 16    | +3   |
+------------------------------------------------------------------------+-------+------+

@github-actions
Copy link
Copy Markdown

🤖 Codex Code Review

  1. crates/l2/sequencer/block_producer/payload_builder.rs still uses legacy scalar gas checks (remaining_gas, gas_used() + tx.gas_limit(), and remaining_gas < tx.gas_limit()) and never applies the new Amsterdam 2D admission rule that L1 now enforces in crates/blockchain/payload.rs. On Amsterdam this is not equivalent: the L2 builder can reject valid transactions with large tx.gas_limit() but acceptable (regular,state) contribution, and it can also accept transactions that fit the scalar heuristic but violate the configured block cap in one dimension. This is a consensus/liveness issue for L2 payload construction; the builder should use the same check_2d_gas_allowance logic, but against configured_block_gas_limit.

  2. crates/l2/sequencer/block_producer/payload_builder.rs rolls back remaining_gas, block_value, and cumulative_gas_spent when an emitted L2 message targets an unregistered chain, but it does not restore context.block_regular_gas_used / context.block_state_gas_used after context.vm.undo_last_tx(). After Amsterdam, those counters drive context.gas_used() and final header.gas_used, so this leaves phantom gas from a rejected tx in the payload context and can skew subsequent inclusion decisions or the final block header.

Aside from those L2 builder regressions, the L1-side Amsterdam changes look coherent: mempool intrinsic gas now matches the VM split, the builder/validator both enforce the 2D rule, and the BAL index widening to u32 is consistent.


Automated review by OpenAI Codex · gpt-5.4 · custom prompt

@edg-l edg-l moved this to In Review in ethrex_l1 Apr 23, 2026
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 23, 2026

Greptile Summary

This PR brings ethrex to bal@v5.7.0 (bal-devnet-4) compliance, implementing nine EIPs end-to-end: EIP-7928 (BAL u16→u32 widening + shadow recorder for parallel validator), EIP-8037 (dynamic cost_per_state_byte, two-counter spill/drain reservoir, clamp-and-spill refund math, 2D inclusion check, CREATE/SELFDESTRUCT state-gas refunds), EIP-7976/7981 (raised calldata floor + access-list byte folding), and EIP-7708 (burn logs). Builder/validator parity gaps (mempool CREATE intrinsic, L1/L2 payload 2D check, L2 BAL checkpoint/restore) are fixed and guarded by 14 new parity tests plus 10 regression guards.

Confidence Score: 5/5

Safe to merge — all remaining findings are P2 style suggestions; extensive test coverage (8781 ef_tests + 1342 hive + 24 new unit tests) validates correctness.

No P0 or P1 issues found. The complex EIP-8037 spill/drain accounting is well-documented, internally consistent, and validated by the full test suite. The three P2 comments are defensive hardening suggestions and do not affect correctness.

crates/vm/backends/levm/mod.rs (withdrawal_idx overflow pattern), crates/vm/levm/src/utils.rs (intrinsic_gas_dimensions divergence risk)

Important Files Changed

Filename Overview
crates/vm/backends/levm/mod.rs Shadow BAL recorder on per-tx parallel executor, SYSTEM_ADDRESS whitelist, EIP-8037 2D inclusion check, u16→u32 BAL index migration — minor u32::try_from().map(
crates/vm/levm/src/vm.rs New EIP-8037 state-gas counters (spill/drain split, clamped reservoir math), credit_state_gas_refund, top-level failure wipe — complex but well-commented and test-covered
crates/vm/levm/src/utils.rs New standalone intrinsic_gas_dimensions mirrors VM::get_intrinsic_gas; EIP-7976/7981 floor token updates; duplication is guarded by parity tests but is a maintenance risk
crates/vm/levm/src/gas_cost.rs Dynamic cost_per_state_byte formula (sanity-checked at 120M gas → 1174), EIP-7976/7981 floor helpers; usize→u64 cast in access_list_bytes is benign on all real targets
crates/blockchain/payload.rs 2D inclusion check before BAL touches, u16→u32 BAL index, saturating_sub rollback guard — parity with validator path now explicit
crates/blockchain/mempool.rs Routes Amsterdam CREATE intrinsic through intrinsic_gas_dimensions so mempool admission matches VM charge — correct fix for EIP-8037/7976/7981 parity
crates/common/types/block_access_list.rs u16→u32 widening of BlockAccessIndex throughout; take_touched_addresses/take_storage_reads helpers for shadow recorder extraction; no logic changes to existing BAL recording semantics
crates/vm/levm/src/hooks/default_hook.rs Revised refund_sender block-accounting formula (raw_consumed - intrinsic_state - reservoir_initial - spill), same-tx SELFDESTRUCT state refund, EIP-7976/7981 floor updates
crates/l2/sequencer/block_producer/payload_builder.rs BAL checkpoint/restore added to both rejection paths in L2 fill_transactions so failed txs leave no trace in BAL — parity with L1 builder
test/tests/blockchain/builder_validator_parity_tests.rs 14 new positive/negative parity tests: 9 builder→validator round-trips + 5 BAL corruption rejection guards mirroring Hive test_bal_invalid_* cases

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    TX[Transaction] --> MEM{Mempool\nAmsterdam?}
    MEM -->|yes| IGD[intrinsic_gas_dimensions\nEIP-7976/7981/8037]
    MEM -->|no| LEGACYG[legacy intrinsic gas]
    IGD --> ADMIT{regular+state\n≤ gas_limit?}
    ADMIT -->|no| REJECT[Reject tx]
    ADMIT -->|yes| POOL[Mempool]
    POOL --> BUILD[Payload Builder]
    BUILD --> CHECK2D{check_2d_gas_allowance\nEIP-8037 PR2703}
    CHECK2D -->|fail| SKIP[Skip tx]
    CHECK2D -->|pass| BALCHK[BAL checkpoint\nset_bal_index u32]
    BALCHK --> EXECB[Execute tx]
    EXECB -->|error| RESTORE[tx_restore checkpoint]
    EXECB -->|ok| COMMIT[Commit BAL + gas]
    COMMIT --> HEADER[Block Header BAL hash]
    HEADER --> VAL{Validator}
    VAL --> PARAL[execute_block_parallel]
    PARAL --> SEED[seed_db_from_bal per-tx CacheDB]
    SEED --> SHADOW[Shadow BAL recorder\ntouched_addresses / storage_reads]
    SHADOW --> EXECV[Execute tx\nEIP-8037 reservoir spill/drain]
    EXECV --> CHKGAS[check_2d_gas_allowance\nper running totals]
    EXECV --> CHKBAL[BAL shadow validation\nmissing account/slot?]
    CHKBAL -->|mismatch| INVALID[Block INVALID]
    CHKBAL -->|ok| VALID[Block VALID]
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: crates/vm/backends/levm/mod.rs
Line: 444-446

Comment:
**Potential u32 arithmetic overflow on withdrawal index**

`u32::try_from(...).map(|n| n + 1)` performs the `+ 1` inside `u32`. If `transactions.len()` happens to equal `u32::MAX` (which the `debug_assert` guards against in debug builds but not release), `n + 1` wraps to `0` in release mode instead of `u32::MAX`. The other call sites in this PR consistently use `u32::try_from(n + 1).unwrap_or(u32::MAX)` (usize addition, then fallible conversion), which is more defensive.

```suggestion
            let withdrawal_idx =
                u32::try_from(block.body.transactions.len() + 1).unwrap_or(u32::MAX);
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: crates/vm/levm/src/gas_cost.rs
Line: 660-661

Comment:
**`usize → u64` cast in `access_list_bytes`**

`keys.len() as u64` is an unchecked `usize → u64` cast. On any 64-bit platform this is lossless, but it suppresses the lint that `saturating_mul` already guards the product. For consistency with the rest of the codebase (which uses `try_into` or `saturating_mul` chains) consider using `u64::try_from(keys.len()).unwrap_or(u64::MAX)` instead.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: crates/vm/levm/src/utils.rs
Line: 639-644

Comment:
**Duplicated intrinsic gas logic risks silent divergence**

`intrinsic_gas_dimensions` mirrors `VM::get_intrinsic_gas` almost line-for-line. The PR adds `test_intrinsic_parity_*` guards, which is the right mitigation, but any future change to one function that misses the other will silently break mempool admission or the 2D inclusion check. A doc comment cross-referencing the sister function (e.g. `/// KEEP IN SYNC WITH [`VM::get_intrinsic_gas`]`) would make the coupling explicit for future maintainers.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "test(l1): bal-devnet-4 follow-up regress..." | Re-trigger Greptile

Comment thread crates/vm/backends/levm/mod.rs Outdated
Comment on lines 444 to 446

// Validate BAL entries at the withdrawal index against actual
// post-withdrawal/request state.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Potential u32 arithmetic overflow on withdrawal index

u32::try_from(...).map(|n| n + 1) performs the + 1 inside u32. If transactions.len() happens to equal u32::MAX (which the debug_assert guards against in debug builds but not release), n + 1 wraps to 0 in release mode instead of u32::MAX. The other call sites in this PR consistently use u32::try_from(n + 1).unwrap_or(u32::MAX) (usize addition, then fallible conversion), which is more defensive.

Suggested change
// Validate BAL entries at the withdrawal index against actual
// post-withdrawal/request state.
let withdrawal_idx =
u32::try_from(block.body.transactions.len() + 1).unwrap_or(u32::MAX);
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/vm/backends/levm/mod.rs
Line: 444-446

Comment:
**Potential u32 arithmetic overflow on withdrawal index**

`u32::try_from(...).map(|n| n + 1)` performs the `+ 1` inside `u32`. If `transactions.len()` happens to equal `u32::MAX` (which the `debug_assert` guards against in debug builds but not release), `n + 1` wraps to `0` in release mode instead of `u32::MAX`. The other call sites in this PR consistently use `u32::try_from(n + 1).unwrap_or(u32::MAX)` (usize addition, then fallible conversion), which is more defensive.

```suggestion
            let withdrawal_idx =
                u32::try_from(block.body.transactions.len() + 1).unwrap_or(u32::MAX);
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +660 to +661
/// 20 bytes per address entry + 32 bytes per storage key.
pub fn access_list_bytes(access_list: &AccessList) -> u64 {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 usize → u64 cast in access_list_bytes

keys.len() as u64 is an unchecked usize → u64 cast. On any 64-bit platform this is lossless, but it suppresses the lint that saturating_mul already guards the product. For consistency with the rest of the codebase (which uses try_into or saturating_mul chains) consider using u64::try_from(keys.len()).unwrap_or(u64::MAX) instead.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/vm/levm/src/gas_cost.rs
Line: 660-661

Comment:
**`usize → u64` cast in `access_list_bytes`**

`keys.len() as u64` is an unchecked `usize → u64` cast. On any 64-bit platform this is lossless, but it suppresses the lint that `saturating_mul` already guards the product. For consistency with the rest of the codebase (which uses `try_into` or `saturating_mul` chains) consider using `u64::try_from(keys.len()).unwrap_or(u64::MAX)` instead.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +639 to +644
/// Compute `(regular, state)` intrinsic gas for a transaction without needing
/// a full VM instance. Mirrors `VM::get_intrinsic_gas` but operates on the raw
/// transaction, fork, and block gas limit (for cpsb derivation). Pre-Amsterdam
/// returns `(regular, 0)`.
///
/// Used by the block executor to perform the EIP-8037 (PR #2703) per-tx 2D
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Duplicated intrinsic gas logic risks silent divergence

intrinsic_gas_dimensions mirrors VM::get_intrinsic_gas almost line-for-line. The PR adds test_intrinsic_parity_* guards, which is the right mitigation, but any future change to one function that misses the other will silently break mempool admission or the 2D inclusion check. A doc comment cross-referencing the sister function (e.g. /// KEEP IN SYNC WITH [VM::get_intrinsic_gas]) would make the coupling explicit for future maintainers.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/vm/levm/src/utils.rs
Line: 639-644

Comment:
**Duplicated intrinsic gas logic risks silent divergence**

`intrinsic_gas_dimensions` mirrors `VM::get_intrinsic_gas` almost line-for-line. The PR adds `test_intrinsic_parity_*` guards, which is the right mitigation, but any future change to one function that misses the other will silently break mempool admission or the 2D inclusion check. A doc comment cross-referencing the sister function (e.g. `/// KEEP IN SYNC WITH [`VM::get_intrinsic_gas`]`) would make the coupling explicit for future maintainers.

How can I resolve this? If you propose a fix, please make it concise.

@github-actions
Copy link
Copy Markdown

🤖 Claude Code Review

Now I have enough to write a comprehensive review.


PR #6521 Review — feat(l1): support bal-devnet-4 (bal@v5.7.0)

This is a large but well-structured spec bump covering EIP-7928 (BAL index widening), EIP-8037 (dynamic cost_per_state_byte, clamp-and-spill, 2D inclusion, same-tx SELFDESTRUCT refund), EIP-7976 (calldata floor 10→16), and EIP-7981 (access-list data bytes in floor tokens). The test coverage — 14 parity tests plus 10 regression guards — is exemplary for this class of change.


Correctness: cost_per_state_byte formula

crates/vm/levm/src/gas_cost.rs

The quantization formula is correct. Manual verification: for block_gas_limit = 120_000_000:

num = 120_000_000 × 2_628_000 = 315_360_000_000_000
denom = 2 × 107_374_182_400 = 214_748_364_800
raw = ceil(315_360_000_000_000 / 214_748_364_800) = 1468
shifted = 1468 + 9578 = 11046  (14 bits)
shift = 14 − 5 = 9
quantized = (11046 >> 9) << 9 = 21 × 512 = 10752
result = 10752 − 9578 = 1174 ✓

Matches the sanity-check comment. The u64→u128 widening is safe and well-guarded by the #[expect(clippy::as_conversions)] annotation.


Potential bug: transaction_intrinsic_gas (mempool) misses the EIP-7976/7623 floor

crates/blockchain/mempool.rs:513–71

The new Amsterdam fast-path returns intrinsic_regular + intrinsic_state, where intrinsic_regular comes from intrinsic_gas_dimensions. That function computes the weighted EIP-2028 calldata cost plus EIP-7981 access-list data cost, but never computes the unweighted floor (TX_BASE_COST + 16 × total_bytes × 4 for Amsterdam). A transaction with mostly zero bytes can have a floor that is many times its weighted cost:

100 zero-byte calldata:
  weighted cost  = 100 × 4  = 400 gas
  Amsterdam floor = 100 × 64 = 6400 gas

A transaction with gas_limit ≥ 400 + state passes the mempool check but will fail validate_min_gas_limit with TxValidationError::IntrinsicGasTooLow during block building (skipped via continue) and validator execution (block invalid if somehow included). The practical consequence is mempool pollution rather than consensus failure, but it opens a targeted DoS avenue: spam the mempool with zero-heavy transactions priced just above the weighted floor.

The pre-Amsterdam path has the same gap (pre-existing), but the Amsterdam floor is 60 % higher (16 vs 10 per token), making it more severe.

Suggested fix in transaction_intrinsic_gas:

if config.is_amsterdam_activated(header.timestamp) {
    let fork = config.fork(header.timestamp);
    let (regular, state) = intrinsic_gas_dimensions(tx, fork, header.gas_limit)
        .map_err(|_| MempoolError::TxGasOverflowError)?;
    // Apply EIP-7976 floor so the mempool matches the VM's validate_min_gas_limit check.
    let floor = compute_floor_gas(tx, fork).map_err(|_| MempoolError::TxGasOverflowError)?;
    let effective_regular = regular.max(floor);
    return effective_regular
        .checked_add(state)
        .ok_or(MempoolError::TxGasOverflowError);
}

Where compute_floor_gas mirrors the validate_min_gas_limit token computation. The existing get_min_gas_used logic in the VM could be factored into a shared function to avoid a third copy.


Minor: as u64 cast in access_list_bytes

crates/vm/levm/src/gas_cost.rs

bytes = bytes.saturating_add(32_u64.saturating_mul(keys.len() as u64));

keys.len() as u64 truncates on 32-bit targets. On all realistic Ethereum execution environments (x86-64, ARM64) usize is 64 bits so this is safe in practice, but u64::try_from(keys.len()).unwrap_or(u64::MAX) is more defensive and consistent with the rest of the PR's conversion style.


Minor: usize overflow before try_from in BAL index computations

Multiple sites (payload.rs, mod.rs):

u32::try_from(context.payload.body.transactions.len() + 1).unwrap_or(u32::MAX)

If len() is usize::MAX (impossible for a valid block, but theoretically possible), the + 1 overflows before try_from is reached. In debug builds the debug_assert!(len < u32::MAX as usize) fires first; in release builds the addition would wrap or panic depending on overflow-checks. Prefer:

u32::try_from(context.payload.body.transactions.len())
    .map(|n| n.saturating_add(1))
    .unwrap_or(u32::MAX)

Minor: suppressing unused parameter with let _ =

crates/vm/levm/src/hooks/default_hook.rs:490

} else {
    let _ = gas_used_pre_refund;
    ctx_result.gas_used = gas_spent;

Silencing an unused parameter with an interior let _ is unconventional. Either prefix the parameter name (_gas_used_pre_refund) or remove it and update all callers. The parameter is used in the Amsterdam branch, so the _ prefix on a conditional dead parameter reads oddly — a _ prefix on the name makes the intent clear at the declaration site.


Design concern: saturating_sub in revert reservoir formula

crates/vm/levm/src/opcode_handlers/system.rs (handle_return_call / handle_return_create)

self.state_gas_reservoir = state_gas_reservoir_snapshot
    .saturating_add(outstanding_delta)
    .saturating_sub(credit_against_drain_delta);

The invariant outstanding_delta ≥ credit_against_drain_delta should hold: credit_against_drain only accumulates the portion of a clamped refund that was not matched against outstanding spill, so it cannot exceed the spill itself. But if a future change violates this invariant, saturating_sub silently clamps to R_snap + outstanding_delta (higher than correct), mischarging the block's regular dimension without any observable signal. A debug_assert! here would make the invariant explicit:

debug_assert!(
    outstanding_delta >= credit_against_drain_delta,
    "reservoir invariant violated: credit_against_drain_delta > outstanding_delta"
);

Design observation: duplicated intrinsic_gas_dimensions vs VM::get_intrinsic_gas

crates/vm/levm/src/utils.rs:2171–2276

The standalone intrinsic_gas_dimensions function comments that it mirrors VM::get_intrinsic_gas, but there is no compile-time or test-time enforcement of this. Both paths now include EIP-7981 access-list data cost and EIP-7976 calldata tokens, which increases the maintenance surface. Consider a single source-of-truth: extract the shared computation into a struct IntrinsicCost { regular, state } builder that both the VM and the standalone path instantiate, or add a round-trip test that calls both with known inputs and asserts equality.


Positive notes

  • The is_system_call flag correctly gates the gas-allowance check in validate_gas_allowance and the block_gas_limit correction in generic_system_contract_levm (passing block_header.gas_limit instead of i64::MAX) is an important fix: using i64::MAX would make cost_per_state_byte astronomically large, OOG-ing any SSTORE to a new slot in a system call.
  • The shadow BAL recorder in execute_block_parallel is a clean approach: per-tx tx_db isolation avoids cross-task contention while giving exact EIP-7928 Group B (missing-access) diagnostics.
  • The apply_same_tx_selfdestruct_state_refund logic correctly reads the account's current storage (all slots of a same-tx-created account are necessarily new) and clamps against execution-only state gas to avoid double-refunding the intrinsic portion.
  • The SYSTEM_ADDRESS whitelist fix (filtering it from both the outer db.current_accounts_state walk and the system_seed walk in execute_block_parallel) closes a real BAL-surplus attack vector.
  • The test module header is an unusually thorough explanation of the failure modes and the mapping to Hive test cases. Very helpful for future maintainers.

Summary

No consensus-critical bugs found. The main actionable item is the mempool floor gap (Item 1 above), which is a pre-existing issue now made more pronounced by the higher Amsterdam floor multiplier. Items 2–5 are low-risk style/robustness improvements. The clamp-and-spill mechanism is complex but the math checks out, and the test coverage (including the sstore_restoration_create_init_revert regression guard mentioned in the comments) provides good confidence.


Automated review by Claude (Anthropic) · sonnet · custom prompt

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 23, 2026

Benchmark Results Comparison

No significant difference was registered for any benchmark run.

Detailed Results

Benchmark Results: BubbleSort

Command Mean [s] Min [s] Max [s] Relative
main_revm_BubbleSort 3.014 ± 0.025 2.986 3.064 1.12 ± 0.02
main_levm_BubbleSort 2.686 ± 0.031 2.657 2.749 1.00
pr_revm_BubbleSort 3.010 ± 0.026 2.976 3.051 1.12 ± 0.02
pr_levm_BubbleSort 2.712 ± 0.096 2.651 2.969 1.01 ± 0.04

Benchmark Results: ERC20Approval

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_ERC20Approval 981.2 ± 7.5 970.1 992.9 1.00
main_levm_ERC20Approval 1032.4 ± 15.8 1018.7 1073.8 1.05 ± 0.02
pr_revm_ERC20Approval 984.5 ± 12.1 973.4 1011.3 1.00 ± 0.01
pr_levm_ERC20Approval 1029.6 ± 11.2 1017.1 1053.8 1.05 ± 0.01

Benchmark Results: ERC20Mint

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_ERC20Mint 133.3 ± 0.6 132.8 134.7 1.00
main_levm_ERC20Mint 151.2 ± 1.2 149.7 153.8 1.13 ± 0.01
pr_revm_ERC20Mint 134.2 ± 0.7 133.3 135.3 1.01 ± 0.01
pr_levm_ERC20Mint 151.1 ± 1.4 149.5 154.5 1.13 ± 0.01

Benchmark Results: ERC20Transfer

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_ERC20Transfer 233.9 ± 2.5 230.6 238.4 1.00 ± 0.01
main_levm_ERC20Transfer 252.1 ± 1.5 250.1 255.2 1.08 ± 0.01
pr_revm_ERC20Transfer 232.9 ± 1.4 231.5 236.5 1.00
pr_levm_ERC20Transfer 252.6 ± 1.4 249.8 254.1 1.08 ± 0.01

Benchmark Results: Factorial

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_Factorial 223.4 ± 1.7 222.5 228.1 1.00
main_levm_Factorial 246.9 ± 2.5 244.2 251.8 1.11 ± 0.01
pr_revm_Factorial 223.7 ± 0.8 222.6 225.2 1.00 ± 0.01
pr_levm_Factorial 250.6 ± 9.0 246.1 276.0 1.12 ± 0.04

Benchmark Results: FactorialRecursive

Command Mean [s] Min [s] Max [s] Relative
main_revm_FactorialRecursive 1.611 ± 0.040 1.563 1.671 1.00
main_levm_FactorialRecursive 9.122 ± 0.020 9.092 9.155 5.66 ± 0.14
pr_revm_FactorialRecursive 1.631 ± 0.037 1.538 1.658 1.01 ± 0.03
pr_levm_FactorialRecursive 9.138 ± 0.022 9.106 9.175 5.67 ± 0.14

Benchmark Results: Fibonacci

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_Fibonacci 206.8 ± 9.7 201.8 232.4 1.02 ± 0.05
main_levm_Fibonacci 224.4 ± 4.9 219.7 233.3 1.11 ± 0.02
pr_revm_Fibonacci 202.7 ± 1.0 201.2 204.4 1.00
pr_levm_Fibonacci 225.9 ± 5.7 220.2 233.5 1.11 ± 0.03

Benchmark Results: FibonacciRecursive

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_FibonacciRecursive 843.4 ± 7.9 827.5 853.3 1.37 ± 0.02
main_levm_FibonacciRecursive 626.5 ± 27.2 606.8 699.2 1.01 ± 0.05
pr_revm_FibonacciRecursive 851.2 ± 11.0 830.7 861.2 1.38 ± 0.03
pr_levm_FibonacciRecursive 617.4 ± 9.2 609.3 638.2 1.00

Benchmark Results: ManyHashes

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_ManyHashes 8.4 ± 0.1 8.2 8.5 1.00
main_levm_ManyHashes 9.8 ± 0.1 9.7 10.0 1.17 ± 0.02
pr_revm_ManyHashes 8.4 ± 0.1 8.3 8.5 1.01 ± 0.01
pr_levm_ManyHashes 9.9 ± 0.1 9.7 10.0 1.18 ± 0.02

Benchmark Results: MstoreBench

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_MstoreBench 260.0 ± 1.9 258.2 264.6 1.12 ± 0.01
main_levm_MstoreBench 231.2 ± 0.9 229.7 232.6 1.00
pr_revm_MstoreBench 265.0 ± 19.6 257.6 320.8 1.15 ± 0.08
pr_levm_MstoreBench 232.5 ± 5.4 228.7 247.1 1.01 ± 0.02

Benchmark Results: Push

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_Push 287.4 ± 2.8 285.8 295.3 1.00 ± 0.01
main_levm_Push 287.1 ± 0.9 285.9 288.7 1.00
pr_revm_Push 287.8 ± 2.9 285.9 295.5 1.00 ± 0.01
pr_levm_Push 292.2 ± 11.8 286.3 323.6 1.02 ± 0.04

Benchmark Results: SstoreBench_no_opt

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_SstoreBench_no_opt 171.0 ± 1.7 168.2 173.7 1.70 ± 0.02
main_levm_SstoreBench_no_opt 100.7 ± 0.3 100.3 101.2 1.00
pr_revm_SstoreBench_no_opt 171.9 ± 5.4 168.1 186.8 1.71 ± 0.05
pr_levm_SstoreBench_no_opt 100.8 ± 0.7 100.0 102.5 1.00 ± 0.01

edg-l added 2 commits April 23, 2026 16:28
Two bot reviews (Codex, Claude) + one code reviewer (Greptile) flagged 7
issues. All 7 verified real and fixed in-PR per user request.

Critical (L2 consensus/liveness):

- `crates/l2/sequencer/block_producer/payload_builder.rs` now enforces
  the EIP-8037 PR #2703 per-tx 2D inclusion check against the L2's
  `configured_block_gas_limit` before executing each tx, matching the
  L1 builder. Without this, the L2 builder could reject valid txs or
  accept txs that violate one dimension of the block cap.

- `fill_transactions` now snapshots and restores
  `block_regular_gas_used` / `block_state_gas_used` around
  `apply_plain_transaction` on the `undo_last_tx` rollback path
  (invalid L2 out-message). Previously those two counters stayed
  inflated after a rejected tx, polluting `gas_used()` and the final
  header `gas_used`.

High (mempool DoS avenue):

- `crates/blockchain/mempool.rs::transaction_intrinsic_gas` now
  enforces `max(intrinsic_regular + intrinsic_state, floor)` for
  Amsterdam+, matching the VM's `validate_min_gas_limit` check.
  Previously a tx with mostly zero calldata could pass mempool
  admission at the weighted EIP-2028 cost (400 gas for 100 zero
  bytes) but fail the VM's 6400-gas unweighted floor at block
  inclusion, polluting the pool.

  New standalone helper `intrinsic_gas_floor(tx, fork)` added in
  `crates/vm/levm/src/utils.rs` mirroring `VM::get_min_gas_used` so
  the mempool / payload builder can compute the floor without a VM
  instance. Re-exported from `ethrex-vm`.

Medium:

- `crates/vm/backends/levm/mod.rs` withdrawal index computation
  switched from `.map(|n| n + 1)` to `.map(|n| n.saturating_add(1))`.
  The prior form wraps to 0 in release builds when `n == u32::MAX`
  (the `debug_assert` only fires in debug).

- `crates/vm/levm/src/opcode_handlers/system.rs` adds `debug_assert!`
  at the two reservoir-revert sites verifying
  `outstanding_delta >= credit_against_drain_delta`. If that invariant
  is ever violated, the `saturating_sub` silently mischarges the
  block's regular dimension; a loud debug panic is preferable.

Style:

- `crates/vm/levm/src/gas_cost.rs::access_list_bytes` — replace
  `keys.len() as u64` with `u64::try_from(...).unwrap_or(u64::MAX)`
  for consistency with the rest of the codebase.

- `crates/vm/levm/src/hooks/default_hook.rs::refund_sender` — rename
  the currently-unused `gas_used_pre_refund` parameter to
  `_gas_used_pre_refund` at the signature and drop the interior
  `let _ =` that was silencing it. Expanded doc explains it's kept
  in the signature for future reintroduction.

All 478 tests pass; no behavior changes except the three
intentional ones (mempool floor, L2 2D check, L2 counter rollback)
plus the already-exercised saturation edge.
…erals

`Environment` gained an `is_system_call: bool` field earlier in the
bal-devnet-4 rollup, but two tooling-side struct literals still
constructed it without the new field. CI Lint + EF Tests Check fail
with E0063 (no default fallback because they're full literals, not
`..Default::default()` spreads).

- tooling/ef_tests/state/runner/levm_runner.rs:205
- tooling/ef_tests/state_v2/src/modules/runner.rs:126

Both transaction runners for the ef_tests harnesses are not running
system calls, so `is_system_call: false` is the correct value.
edg-l and others added 3 commits April 27, 2026 12:48
bal-devnet-4 testing requires the same fixed cost_per_state_byte value
that bal-devnet-3 used (1174). Replace the dynamic formula body with a
constant return; the formula constants (BLOCKS_PER_YEAR,
TARGET_STATE_GROWTH_PER_YEAR, CPSB_SIGNIFICANT_BITS, CPSB_OFFSET) and
all VM field plumbing are kept intact so this commit can be reverted
with a single \`git revert\` to restore the dynamic formula.

Mark formula-specific unit tests and one calibration-sensitive deposit
OOG test as #[ignore] with a re-enable note.
Updates the fixtures URL (and matching docs/labels) from bal@v5.7.0 to
sn%C3%B8bal-devnet-4%40v1.0.0/fixtures_snobal-devnet-4.tar.gz across the
.fixtures_url_amsterdam file, Makefile, hive amsterdam.yaml, and hive.md.
…esis (#6534)

Not having this was making our genesis management different from clients
like nethermind.
///
/// Used by the block executor to perform the EIP-8037 (PR #2703) per-tx 2D
/// inclusion check before the tx runs.
pub fn intrinsic_gas_dimensions(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Greptile P2) intrinsic_gas_dimensions mirrors VM::get_intrinsic_gas line-for-line. test_intrinsic_parity_* is the right immediate guard, but the duplication will eventually drift — a future Amsterdam-tweak adds a charge to one and forgets the other. The good outcome: parity test catches it. The bad outcome: someone disables/relaxes the parity test under deadline pressure. Worth a follow-up that extracts shared logic into compute_intrinsic_gas_internal(tx, fork, block_gas_limit, mode: { Vm | Standalone }) and calls it from both. Not blocking this PR.

/// TARGET_STATE_GROWTH_PER_YEAR / CPSB_SIGNIFICANT_BITS / CPSB_OFFSET) is preserved
/// in the consts above so this commit can be reverted with a single `git revert` to
/// restore the formula body. See execution-specs#2687.
pub fn cost_per_state_byte(_block_gas_limit: u64) -> u64 {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Description / implementation mismatch: PR description says "dynamic cpsb (previously a static 1174)" as a delta against bal@v5.6.1, but this is still pinned to 1174 with the same TEMPORARY for bal-devnet-4 doc that #6546 has been working to clean up. Either (a) the dynamic formula isn't actually being used yet on this branch and the description's delta refers to the spec change, or (b) the dynamic body is in a follow-up. Worth clarifying the description, since reading it suggests dynamic CPSB lands here. Also intersects with #6546 — the EIP-11573 sync that nukes the dynamic formula entirely. Whichever lands second should be aware of the ordering.

// gas; our devnet-4 (bal@v5.7.0) impl correctly keeps intrinsic_state_gas
// immutable and routes the refund to the reservoir only. Re-enable once the
// zkevm@v0.4.x release ships fixtures regenerated against devnet-4.
"witness_codes_redelegation_old_marker_included_new_marker_excluded",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action items for these skipped fixtures:

  • Add a TODO comment naming the upstream version that lifts the skip (zkevm@v0.4.x), so a future updater knows when to remove it without re-deriving the reasoning.
  • File a tracking issue (or link an existing one) so the skip doesn't outlive its trigger silently.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

L1 Ethereum client

Projects

Status: In Review

Development

Successfully merging this pull request may close these issues.

3 participants