Skip to content

fix(l1): bump BAL fixtures to v7.3.2 and fix self-destruct-in-initcode + pre-fork newPayload BAL handling - #6842

Merged
edg-l merged 10 commits into
mainfrom
ci/bump-bal-fixtures-v7.3.0
Jun 17, 2026
Merged

edg-l merged 10 commits into
mainfrom
ci/bump-bal-fixtures-v7.3.0

Conversation

@edg-l

@edg-l edg-l commented Jun 11, 2026 •

Copy link
Copy Markdown
Contributor

Bumps the Amsterdam BAL fixture bundle to tests-bal@v7.3.2 (from v7.2.0) and folds in the correctness fixes that the newer fixtures surfaced.

Diff scope: this branch went v7.3.0 → v7.3.1 → v7.3.2 in-flight. The v7.3.0-era CREATE-in-static gas workaround and the stateless zkevm@v0.4.1 skips (tooling/ef_tests/blockchain/tests/all.rs, docs/known_issues.md) were added and then reverted within the branch, so they net to zero against main and do not appear in the diff. See "Why the CREATE-in-static workaround was reverted" below.

What actually changes vs main

  • ci: bump fixture pins to tests-bal@v7.3.2 (eels_commit d28f7977069d1b71984ff88ed8040b671f6e6a77) across amsterdam.yaml and the blockchain/engine/state .fixtures_url_amsterdam files.
  • fix (block_access_list.rs): self-destruct in initcode was recording an intermediate balance in the BAL instead of the final post-tx balance of 0. track_selfdestruct now collapses intermediate per-tx balance entries to the final (idx, 0).
  • fix (serde_utils.rs): an empty hex string ("0x") encodes the absence of a BAL, not an empty list. Deserialize it as None so pre-Amsterdam newPayload calls (which send "0x") parse instead of failing RLP decode.
  • fix (payload.rs): drop the bespoke V4 fork-boundary detector. A pre-Amsterdam header carrying block_access_list_hash produces a block_hash that won't match the one ethrex reconstructs (the field is omitted from the V4 header schema), so the mismatch now surfaces as PayloadStatus.INVALID via the normal block-hash check — matching the EELS fixture test_invalid_pre_fork_block_with_bal_hash_field (INVALID_BLOCK_HASH, no engine API error code).

Why the CREATE-in-static workaround was reverted

In v7.3.0, EELS generic_create reserved create_message_gas (the forwarded frame) before checking is_static, so a CREATE/CREATE2 in a static context charged that gas and then halted. To match, ethrex moved its is_static check after the reservation, burned the forwarded frame, and added create_static_regular_spill to carve that reserved-but-unused gas out of EIP-7778's regular-gas dimension. The stale zkevm@v0.4.1 stateless bundle (filled against ~v7.2.0) didn't have this behavior, hence the temporary skips + known_issues.md.

v7.3.1 (eels 59b466e → a8b705f) moved the is_static check back upfront: raise WriteInStaticContext now runs before any gas is charged, so create_message_gas is never reserved in a static context. That made the workaround unnecessary — the spill field and refund_sender subtraction were removed, the check returned to its original upfront position (which already matched ethrex's pre-existing behavior), and the stateless skips were dropped since the divergence they masked no longer exists.

@github-actions

github-actions Bot commented Jun 11, 2026 •

Copy link
Copy Markdown

⚠️ Known Issues — intentionally skipped tests

Source: docs/known_issues.md

Known issues

Stateless (zkevm) suite skips CREATE-in-static gas tests (outdated bundle)

The stateless ef-test suite (make -C tooling/ef_tests/blockchain test-stateless) runs the
tests-zkevm@v0.4.1 fixture bundle, which is the latest zkevm release available upstream but is
filled against an older Amsterdam base (tests-bal@v7.2.0 era). It predates the
tests-bal@v7.3.0 refinement of CREATE/CREATE2-in-static-context gas accounting, where EIP-7778's
regular-gas dimension excludes the reserved (unused) create_message_gas instead of burning the
whole forwarded frame.

ethrex implements the v7.3.0 behavior (verified by the amsterdam test-levm suite, which passes),
so it intentionally mismatches the stale zkevm block headers for these tests. They are skipped in
the stateless run via EXTRA_SKIPS in tooling/ef_tests/blockchain/tests/all.rs:

  • bal_create_in_static_context (CREATE/CREATE2, with/without value)
  • create2check_fields_in_initcode (d3, d7)
  • test_static_call_create.py (d1)

Action: remove these skips once a tests-zkevm bundle filled against tests-bal@v7.3.0 (or later)
is released and .fixtures_url_zkevm is bumped.

@github-actions github-actions Bot added the L1 Ethereum client label Jun 11, 2026
@github-actions

github-actions Bot commented Jun 11, 2026 •

Copy link
Copy Markdown

Lines of code report

Total lines added: 3
Total lines removed: 11
Total lines changed: 14

Detailed view
+-------------------------------------------------+-------+------+
| File                                            | Lines | Diff |
+-------------------------------------------------+-------+------+
| ethrex/crates/common/serde_utils.rs             | 618   | +2   |
+-------------------------------------------------+-------+------+
| ethrex/crates/common/types/block_access_list.rs | 1413  | +1   |
+-------------------------------------------------+-------+------+
| ethrex/crates/networking/rpc/engine/payload.rs  | 1370  | -11  |
+-------------------------------------------------+-------+------+

@github-actions

github-actions Bot commented Jun 11, 2026 •

Copy link
Copy Markdown

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 2.955 ± 0.023 2.933 3.004 1.11 ± 0.01
main_levm_BubbleSort 2.669 ± 0.009 2.661 2.685 1.00
pr_revm_BubbleSort 2.946 ± 0.021 2.924 2.984 1.10 ± 0.01
pr_levm_BubbleSort 2.672 ± 0.012 2.659 2.703 1.00 ± 0.01

Benchmark Results: ERC20Approval

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_ERC20Approval 963.2 ± 8.5 954.4 981.1 1.00
main_levm_ERC20Approval 1011.9 ± 9.4 1000.8 1030.2 1.05 ± 0.01
pr_revm_ERC20Approval 974.6 ± 25.0 954.7 1027.7 1.01 ± 0.03
pr_levm_ERC20Approval 1009.8 ± 9.1 997.1 1033.1 1.05 ± 0.01

Benchmark Results: ERC20Mint

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_ERC20Mint 132.2 ± 1.4 130.9 134.8 1.01 ± 0.01
main_levm_ERC20Mint 155.7 ± 1.5 153.4 158.6 1.18 ± 0.01
pr_revm_ERC20Mint 131.6 ± 1.0 130.4 133.7 1.00
pr_levm_ERC20Mint 155.6 ± 1.8 154.4 160.6 1.18 ± 0.02

Benchmark Results: ERC20Transfer

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_ERC20Transfer 228.4 ± 2.9 226.8 234.7 1.00
main_levm_ERC20Transfer 253.7 ± 2.4 249.8 259.0 1.11 ± 0.02
pr_revm_ERC20Transfer 228.5 ± 3.1 225.4 233.7 1.00 ± 0.02
pr_levm_ERC20Transfer 254.9 ± 3.5 250.6 262.0 1.12 ± 0.02

Benchmark Results: Factorial

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_Factorial 227.8 ± 1.2 226.1 229.7 1.00 ± 0.01
main_levm_Factorial 260.2 ± 6.9 256.1 279.0 1.15 ± 0.03
pr_revm_Factorial 226.6 ± 1.3 224.5 229.4 1.00
pr_levm_Factorial 257.9 ± 2.1 255.5 262.5 1.14 ± 0.01

Benchmark Results: FactorialRecursive

Command Mean [s] Min [s] Max [s] Relative
main_revm_FactorialRecursive 1.618 ± 0.027 1.575 1.659 1.01 ± 0.07
main_levm_FactorialRecursive 9.848 ± 0.051 9.770 9.937 6.17 ± 0.39
pr_revm_FactorialRecursive 1.596 ± 0.100 1.327 1.676 1.00
pr_levm_FactorialRecursive 9.806 ± 0.038 9.742 9.867 6.14 ± 0.39

Benchmark Results: Fibonacci

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_Fibonacci 206.7 ± 2.5 201.7 212.2 1.01 ± 0.01
main_levm_Fibonacci 244.8 ± 3.9 240.1 251.3 1.19 ± 0.02
pr_revm_Fibonacci 205.1 ± 1.2 201.8 205.9 1.00
pr_levm_Fibonacci 242.6 ± 3.1 237.3 248.6 1.18 ± 0.02

Benchmark Results: FibonacciRecursive

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_FibonacciRecursive 836.1 ± 11.3 816.8 849.6 1.14 ± 0.02
main_levm_FibonacciRecursive 735.1 ± 8.2 727.2 753.0 1.00
pr_revm_FibonacciRecursive 840.9 ± 13.7 823.3 870.4 1.14 ± 0.02
pr_levm_FibonacciRecursive 746.3 ± 20.8 730.9 800.3 1.02 ± 0.03

Benchmark Results: ManyHashes

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_ManyHashes 8.4 ± 0.1 8.3 8.5 1.01 ± 0.01
main_levm_ManyHashes 9.4 ± 0.2 9.3 9.9 1.13 ± 0.03
pr_revm_ManyHashes 8.3 ± 0.1 8.2 8.5 1.00
pr_levm_ManyHashes 9.3 ± 0.1 9.2 9.4 1.12 ± 0.02

Benchmark Results: MstoreBench

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_MstoreBench 271.2 ± 5.4 267.2 284.5 1.19 ± 0.02
main_levm_MstoreBench 227.9 ± 1.1 226.1 229.6 1.00 ± 0.01
pr_revm_MstoreBench 269.4 ± 3.9 266.6 280.1 1.18 ± 0.02
pr_levm_MstoreBench 227.7 ± 1.0 226.1 229.3 1.00

Benchmark Results: Push

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_Push 298.9 ± 1.8 295.3 301.0 1.04 ± 0.01
main_levm_Push 287.0 ± 1.3 286.0 290.5 1.00
pr_revm_Push 296.3 ± 0.9 295.2 298.3 1.03 ± 0.01
pr_levm_Push 287.8 ± 2.3 286.0 294.0 1.00 ± 0.01

Benchmark Results: SstoreBench_no_opt

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_SstoreBench_no_opt 167.6 ± 3.1 163.1 171.5 1.55 ± 0.03
main_levm_SstoreBench_no_opt 108.4 ± 0.5 107.8 109.5 1.00
pr_revm_SstoreBench_no_opt 169.3 ± 2.3 166.0 171.5 1.56 ± 0.02
pr_levm_SstoreBench_no_opt 108.5 ± 0.9 107.6 110.2 1.00 ± 0.01

@edg-l
edg-l marked this pull request as ready for review June 11, 2026 09:10
@edg-l
edg-l requested a review from a team as a code owner June 11, 2026 09:10
@ethrex-project-sync ethrex-project-sync Bot moved this to In Review in ethrex_l1 Jun 11, 2026
@github-actions

Copy link
Copy Markdown

🤖 Kimi Code Review

⚠️ Review failed: Kimi API request failed with status 429: The engine is currently overloaded, please try again later


Automated review by Kimi (Moonshot AI)

@github-actions

Copy link
Copy Markdown

🤖 Codex Code Review

No findings.

The LEVM change in [crates/vm/levm/src/opcode_handlers/system.rs](/home/runner/work/ethrex/ethrex/crates/vm/levm/src/opcode_handlers/system.rs:787) and [crates/vm/levm/src/hooks/default_hook.rs](/home/runner/work/ethrex/ethrex/crates/vm/levm/src/hooks/default_hook.rs:264) is internally consistent: Amsterdam CREATE/CREATE2-in-static now burns the forwarded frame for the sender while excluding that reserved create_message_gas from the block-level regular dimension, which matches the intent described in the comments and the surrounding 2D gas-accounting code. The BAL change in [crates/common/types/block_access_list.rs](/home/runner/work/ethrex/ethrex/crates/common/types/block_access_list.rs:1570) also looks correct for same-tx selfdestruct cleanup: it collapses intermediate balance transitions to the final post-tx zero state instead of leaving stale nonzero snapshots behind.

Residual risk: I did not find a focused local regression test for the new create_static_regular_spill path, and I couldn’t run the Rust test targets in this sandbox because cargo/rustup need write access outside the workspace (/home/runner/.rustup and /home/runner/.cargo).


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

@greptile-apps

greptile-apps Bot commented Jun 11, 2026 •

Copy link
Copy Markdown

Greptile Summary

Bumps the Amsterdam BAL fixture bundle to tests-bal@v7.3.0 and fixes two correctness bugs surfaced by the new fixtures.

  • CREATE/CREATE2 in static context (EIP-7778): The create_message_gas reservation in generic_create now happens before the static-context check, matching EELS generic_create ordering. The consumed gas is tracked in the new VM::create_static_regular_spill field and subtracted from the block-level regular-gas dimension in refund_sender, so the block header reflects the correct regular-gas count while the user receipt still reflects the full burned amount.
  • Self-destruct BAL cleanup: track_selfdestruct now collapses all intermediate per-tx balance entries to a single (idx, 0) when the pre-tx balance was non-zero, instead of leaving intermediate values in the BAL. The three stateless-suite tests that conflict with the older zkevm@v0.4.1 bundle are skipped and documented in docs/known_issues.md.

Confidence Score: 4/5

Safe to merge; both fixes are well-motivated by EELS behaviour, all 8753 amsterdam tests pass, and the two stateless skips are documented with a clear removal plan.

The CREATE-in-static gas accounting and the self-destruct BAL collapse are both small, targeted fixes with thorough in-code documentation and a passing test run. The only rough edge is that track_selfdestruct correctness silently depends on record_balance_change being called before add_selfdestruct (currently always true, but not enforced by the function itself), and the stateless skip list is slightly over-broad for create2check_fields_in_initcode.

crates/common/types/block_access_list.rs — the track_selfdestruct guard relies on an implicit ordering invariant from the call site; worth hardening if the function is ever called from a new context. tooling/ef_tests/blockchain/tests/all.rs — the create2check_fields_in_initcode skip is broader than the documented d3/d7 variants.

Important Files Changed

Filename Overview
crates/vm/levm/src/opcode_handlers/system.rs Moves the CREATE/CREATE2 static-context check to after the create_message_gas reservation in generic_create; pre-Amsterdam check retained before reservation
crates/common/types/block_access_list.rs track_selfdestruct now collapses intermediate balance entries to a single (idx, 0) for pre-existing addresses; guard silently no-ops if balance_changes has no entry for the address (benign today due to call-site ordering)
crates/vm/levm/src/vm.rs Adds create_static_regular_spill VM-level field initialized to 0 for EIP-7778 regular-gas dimension exclusion
crates/vm/levm/src/hooks/default_hook.rs refund_sender subtracts create_static_regular_spill from the regular-gas dimension, keeping receipt gas intact
tooling/ef_tests/blockchain/tests/all.rs Adds three skip entries for the stateless suite; create2check_fields_in_initcode matches all d-variants, not only the documented d3 and d7
docs/known_issues.md New file documenting the stateless-suite skip rationale and the action item to remove skips once a newer zkevm bundle is released
.github/config/hive/amsterdam.yaml Fixture pin and EELS commit bumped from tests-bal@v7.2.0 to v7.3.0

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[CREATE / CREATE2 opcode] --> B[Pop stack args\nCharge static gas]
    B --> C{fork >= Amsterdam\nAND is_static?}
    C -- Pre-Amsterdam --> D[Check is_static\nbefore gas reservation\nHalt immediately]
    C -- No --> E[Reserve create_message_gas\nmax_message_call_gas]
    C -- Yes --> F[Add gas_limit to\ncreate_static_regular_spill]
    F --> G[Halt: OpcodeNotAllowedInStaticContext\nfull frame burned to user]
    E --> H[Execute initcode / deployment]
    H --> I{SELFDESTRUCT\nin initcode?}
    I -- Yes --> J[record_balance_change addr 0\nadd_selfdestruct addr]
    I -- No --> K[Normal CREATE completion]
    J --> L[destroy_accounts:\ntrack_selfdestruct addr]
    L --> M{pre_balance == 0?}
    M -- Yes --> N[Remove all idx entries\nno BAL change recorded]
    M -- No --> O[Remove all idx entries\nPush idx 0 as final balance]
    G --> P[refund_sender:\nsubtract create_static_regular_spill\nfrom regular-gas dimension]
Loading

Comments Outside Diff (1)

  1. crates/common/types/block_access_list.rs, line 1585-1595 (link)

    P2 Guard may silently skip the (idx, 0) push for pre-existing contracts

    If balance_changes has no entry for address at all but pre_balance != 0, the outer if let Some(changes) returns None and the (idx, 0) change is never pushed. EELS would still record a pre-tx → 0 diff for such an address. In practice this can't arise on the Amsterdam code path because record_balance_change(to, U256::zero()) is always called immediately before add_selfdestruct(to) (guaranteeing at least one entry exists), but the guard makes the invariant implicit. A fallback that inserts the entry when the map has no slot for the address would make the function self-sufficient against future callers who don't uphold that ordering.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: crates/common/types/block_access_list.rs
    Line: 1585-1595
    
    Comment:
    **Guard may silently skip the (idx, 0) push for pre-existing contracts**
    
    If `balance_changes` has no entry for `address` at all but `pre_balance != 0`, the outer `if let Some(changes)` returns `None` and the `(idx, 0)` change is never pushed. EELS would still record a pre-tx → 0 diff for such an address. In practice this can't arise on the Amsterdam code path because `record_balance_change(to, U256::zero())` is always called immediately before `add_selfdestruct(to)` (guaranteeing at least one entry exists), but the guard makes the invariant implicit. A fallback that inserts the entry when the map has no slot for the address would make the function self-sufficient against future callers who don't uphold that ordering.
    
    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!

Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
crates/common/types/block_access_list.rs:1585-1595
**Guard may silently skip the (idx, 0) push for pre-existing contracts**

If `balance_changes` has no entry for `address` at all but `pre_balance != 0`, the outer `if let Some(changes)` returns `None` and the `(idx, 0)` change is never pushed. EELS would still record a pre-tx → 0 diff for such an address. In practice this can't arise on the Amsterdam code path because `record_balance_change(to, U256::zero())` is always called immediately before `add_selfdestruct(to)` (guaranteeing at least one entry exists), but the guard makes the invariant implicit. A fallback that inserts the entry when the map has no slot for the address would make the function self-sufficient against future callers who don't uphold that ordering.

### Issue 2 of 2
tooling/ef_tests/blockchain/tests/all.rs:45-49
**`create2check_fields_in_initcode` skip matches all d-variants, not only d3 and d7**

`known_issues.md` documents only `d3` and `d7` as the affected vectors, but the skip string `"create2check_fields_in_initcode"` is a substring match and will suppress every variant (d0–d9) in the stateless run. This is temporarily over-broad and could mask regressions in the unaffected variants (d0, d1, d2, d4, d5, d6, d8, d9) until the zkevm bundle is bumped.

Reviews (1): Last reviewed commit: "test(l1): skip CREATE-in-static stateles..." | Re-trigger Greptile

Comment on lines +45 to +49
const EXTRA_SKIPS: &[&str] = &[
"bal_create_in_static_context",
"create2check_fields_in_initcode",
"test_static_call_create.py",
];

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 create2check_fields_in_initcode skip matches all d-variants, not only d3 and d7

known_issues.md documents only d3 and d7 as the affected vectors, but the skip string "create2check_fields_in_initcode" is a substring match and will suppress every variant (d0–d9) in the stateless run. This is temporarily over-broad and could mask regressions in the unaffected variants (d0, d1, d2, d4, d5, d6, d8, d9) until the zkevm bundle is bumped.

Prompt To Fix With AI
This is a comment left during a code review.
Path: tooling/ef_tests/blockchain/tests/all.rs
Line: 45-49

Comment:
**`create2check_fields_in_initcode` skip matches all d-variants, not only d3 and d7**

`known_issues.md` documents only `d3` and `d7` as the affected vectors, but the skip string `"create2check_fields_in_initcode"` is a substring match and will suppress every variant (d0–d9) in the stateless run. This is temporarily over-broad and could mask regressions in the unaffected variants (d0, d1, d2, d4, d5, d6, d8, d9) until the zkevm bundle is bumped.

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 context for a thorough review.


PR #6842 Review: fix(l1): bump BAL fixtures to v7.3.0 and fix CREATE-in-static gas + self-destruct BAL

Summary

Two correctness fixes for Amsterdam BAL accounting, both triggered by new v7.3.0 fixtures. The implementation is well-reasoned, well-commented, and verifiably matches EELS behavior. One minor defensive-coding gap and one implicit assumption worth noting.


Fix 1 — track_selfdestruct: collapse to final post-tx balance of 0

File: crates/common/types/block_access_list.rs:1585–1595

Old behavior: The cleanup block only ran when pre_balance.is_zero(). When pre_balance != 0, intermediate balance entries (e.g. (idx, V)) were left in balance_changes, causing the BAL to record a stale intermediate value instead of the final post-tx balance of 0.

New behavior: Always strips all (idx, _) entries, then pushes (idx, 0) only when pre_balance != 0 (a true B→0 transition). Correct per EIP-7928 spec.

Gap — unguarded None arm:

if let Some(changes) = self.balance_changes.get_mut(&address) {
    changes.retain(|(i, _)| *i != idx);
    if !pre_balance.is_zero() {
        changes.push((idx, U256::zero()));
    }
    // ...
}
// If there is no entry AND pre_balance != 0, (idx, 0) is silently not recorded.

When pre_balance != 0 but balance_changes has no entry for address (the None arm), the required (idx, 0) is never inserted. In the most common case this is unreachable — SELFDESTRUCT always calls decrease_account_balance first, which records a balance change — but a defensive rewrite using entry() would be safer:

let changes = self.balance_changes.entry(address).or_default();
changes.retain(|(i, _)| *i != idx);
if !pre_balance.is_zero() {
    changes.push((idx, U256::zero()));
}
if changes.is_empty() {
    self.balance_changes.remove(&address);
}

This handles all four logical cases without relying on the ordering guarantee that "a balance change was always recorded before track_selfdestruct is called."


Fix 2 — create_static_regular_spill: exclude forwarded gas from regular dimension

Files: crates/vm/levm/src/opcode_handlers/system.rs:795–800, crates/vm/levm/src/vm.rs:522, crates/vm/levm/src/hooks/default_hook.rs:268–270

The logic is correct. For Amsterdam+, the static-context check is now placed after increase_consumed_gas(gas_limit), mirroring EELS generic_create where gas_left -= create_message_gas precedes raise WriteInStaticContext. The accumulated gas_limit is correctly excluded from the block's regular-gas dimension via subtraction in refund_sender, while the receipt/user charge reflects the full burn.

  • checked_add for accumulation is correct.
  • The field is initialized to 0 in both VM::new (line 732) and therefore in VM::new_pooled (which delegates to the same shared constructor), so no per-transaction reset is needed.
  • Multiple CREATE/CREATE2-in-static calls in one transaction accumulate correctly.

One implicit assumption: state_gas_new_account is charged at line 769, before the Amsterdam+ static-context check at line 795. For a static-context CREATE failure (an exceptional halt), that state gas is not refunded — unlike the soft-failure paths (balance/depth/nonce checks at lines 841–857) which explicitly call credit_state_gas_refund. This appears intentional and consistent with exceptional-halt semantics, but it's worth confirming the EELS generic_create reference charges state gas before raising WriteInStaticContext.


Other observations

Opcode-level gas correctly excluded from spill: The create_gas / create2_gas (memory expansion, initcode cost) charged in the opcode handlers are not included in create_static_regular_spill. Only the forwarded gas_limit from max_message_call_gas is. This is correct per EIP-7778 — those opcode-level costs remain part of the regular-gas dimension.

Pre-Amsterdam static check preserved: The early static-context return at line 776 (fork < Amsterdam) still fires before gas reservation, so pre-Amsterdam behavior is unchanged.

saturating_sub chain: The use of saturating_sub for create_static_regular_spill is consistent with the surrounding accounting. It can silently clamp to 0 if accounting is off, but that's an existing pattern throughout this gas code and no worse than before.

Fixture bump and test skips: The docs/known_issues.md addition is clear and includes the exact removal condition ("once a tests-zkevm bundle filled against tests-bal@v7.3.0 or later is released"). The three skipped test names are specific enough to act on.


Verdict

The core logic of both fixes is correct and well-documented. The one actionable item is the None-arm gap in track_selfdestruct (bullet point above): while likely unreachable given current call ordering, making the insert unconditional via entry().or_default() removes a hidden assumption and eliminates the gap entirely at negligible cost.


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

Comment thread tooling/ef_tests/blockchain/tests/all.rs Outdated
Comment thread crates/vm/levm/src/opcode_handlers/system.rs Outdated
// Drop all intermediate balance changes recorded for this tx.
changes.retain(|(i, _)| *i != idx);
// Record the final post-tx balance of 0 unless it equals the pre-tx balance.
if !pre_balance.is_zero() {

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.

Under Amsterdam+ EIP-7928 BAL + EIP-6780 SELFDESTRUCT semantics, this if !pre_balance.is_zero() branch is unreachable in practice: track_selfdestruct is only called for contracts created and destroyed in the same tx (per the function-level comment + the is_account_created gate in system.rs:680), so by definition initial_balances[address] was either unset (→ unwrap_or_default = 0) or set during the in-tx CREATE with the creation balance (typically 0 — CREATE-with-value sets initial_balance only if balance > 0, but that path is the CREATE side, not the SELFDESTRUCT side that reaches here).

The branch is sound defensive code matching the EELS post-state diff exactly, but the doc comment above (lines 1568-1581) reads as though the non-zero pre-balance case is a real scenario. Worth tightening the comment to: "In practice pre_balance is always 0 under EIP-6780 (same-tx-created); the non-zero branch mirrors EELS's general pre/post diff and guards against future spec changes or test fixtures that exercise alternate paths." Otherwise a future reader will hunt for the scenario.

@edg-l edg-l changed the title fix(l1): bump BAL fixtures to v7.3.0 and fix CREATE-in-static gas + self-destruct BAL fix(l1): bump BAL fixtures to v7.3.1 and fix CREATE-in-static gas + self-destruct BAL Jun 12, 2026
@edg-l edg-l changed the title fix(l1): bump BAL fixtures to v7.3.1 and fix CREATE-in-static gas + self-destruct BAL fix(l1): bump BAL fixtures to v7.3.1 and fix self-destruct-in-initcode BAL Jun 12, 2026
edg-l added 6 commits June 15, 2026 11:03
v7.3.1 moves the is_static check upfront in CREATE/CREATE2 (eels a8b705f),
so create_message_gas is never reserved in a static context. This undoes
the v7.3.0-era workaround (7defce9) that charged it and carved it out of
the regular-gas dimension, and removes the stateless zkevm@v0.4.1 skips
(839074d) that only existed to mask that divergence.
@edg-l
edg-l force-pushed the ci/bump-bal-fixtures-v7.3.0 branch from b7bd710 to 059d42f Compare June 15, 2026 09:03
@edg-l
edg-l force-pushed the ci/bump-bal-fixtures-v7.3.0 branch from d75c1e9 to 2a757ce Compare June 15, 2026 12:53
…BAL hash field

Deserialize empty blockAccessList ('0x') as None and drop the V4
fork-boundary detector so a pre-Amsterdam header carrying
block_access_list_hash fails the normal block-hash check (INVALID_BLOCK_HASH)
instead of returning -32602. Matches tests-bal@v7.3.2 fixture
test_invalid_pre_fork_block_with_bal_hash_field.
edg-l added a commit that referenced this pull request Jun 17, 2026
…e BAL (#6842)

Squash-merge of PR #6842 (ci/bump-bal-fixtures-v7.3.0):
- bump BAL fixtures to tests-bal@v7.3.0/v7.3.1/v7.3.2
- record destroyed account's final 0 balance in BAL (self-destruct in initcode)
- exclude create_message_gas from regular dimension on CREATE-in-static (EIP-7778)
- return PayloadStatus.INVALID for pre-fork newPayloadV4 with BAL hash field

@ElFantasma ElFantasma left a comment

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.

One of the previous comments is still relevant, but it is non-blocking. LGTM

@edg-l edg-l changed the title fix(l1): bump BAL fixtures to v7.3.1 and fix self-destruct-in-initcode BAL fix(l1): bump BAL fixtures to v7.3.2 and fix self-destruct-in-initcode + pre-fork newPayload BAL handling Jun 17, 2026
@edg-l
edg-l enabled auto-merge June 17, 2026 14:54
@edg-l
edg-l added this pull request to the merge queue Jun 17, 2026
Merged via the queue into main with commit 4a902a3 Jun 17, 2026
62 checks passed
@edg-l
edg-l deleted the ci/bump-bal-fixtures-v7.3.0 branch June 17, 2026 16:11
@github-project-automation github-project-automation Bot moved this from In Review to Done in ethrex_l1 Jun 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Glamsterdam L1 Ethereum client

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants