Skip to content

feat(l1): bal-devnet-3 #6216

Draft
edg-l wants to merge 50 commits intomainfrom
bal-devnet-3
Draft

feat(l1): bal-devnet-3 #6216
edg-l wants to merge 50 commits intomainfrom
bal-devnet-3

Conversation

@edg-l
Copy link
Contributor

@edg-l edg-l commented Feb 17, 2026

Motivation

Tracking branch for Amsterdam bal-devnet-3 alignment.

EIP Changes

EIP-7708: Rename Selfdestruct event to Burn

  • Updated topic hash to 0xcc16f5... (BURN_EVENT_TOPIC)
  • Renamed create_selfdestruct_log to create_burn_log

EIP-7928: BAL size cap

  • validate_block_access_list_size in validation.rs (3 call sites in blockchain.rs)
  • Fixed formula to match EELS: bal_items > gas_limit / GAS_BLOCK_ACCESS_LIST_ITEM (no system allowance or per-tx base cost)

EIP-8024: Branchless normalization + EXCHANGE extension

  • decode_single uses (x + 145) % 256 instead of branching
  • decode_pair uses XOR 0x8F instead of conditional subtraction
  • EXCHANGE valid range extended from 0x00-0x4F to 0x00-0x51 (two new swappable pairs)

EIP-8037: State Creation Gas Cost Increase

Full implementation of the reservoir model:

  • State gas reservoir derived from excess gas_limit beyond TX_MAX_GAS_LIMIT
  • Two-dimensional block gas accounting: block.gas_used = max(sum(regular), sum(state)) per EIP-7778
  • Receipt cumulative_gas_used = post-refund total (what user pays)
  • Reservoir subtraction from gas_used in finalize_execution
  • EIP-7702 auth refund to reservoir for Amsterdam+
  • Skip Osaka TX_MAX_GAS_LIMIT cap for Amsterdam (reservoir model replaces per-tx limit)
  • CREATE state gas charged before early-failure checks (balance/depth/nonce) and before child gas reservation (63/64 rule)
  • SSTORE state gas charged before regular gas per EELS ordering; refund via normal refund counter (subject to 1/5 cap per EIP-3529)
  • increase_state_gas reordered: charge first, mutate counters only on success (matches EELS)
  • State gas restoration on child revert: new_reservoir = current_reservoir + child_state_gas_used (matches EELS incorporate_child_on_error)
  • Nested child revert fix: correctly restores reservoir when sub-child also reverted
  • CREATE collision gas excluded from regular dimension (EELS escrow mechanism — gas_used=0 on collision)
  • Amsterdam intrinsic regular gas cap validation (max(intrinsic_regular, calldata_floor) > TX_MAX_GAS_LIMIT)

BAL Parallel Execution Validation

Comprehensive BAL validation for the parallel block execution path:

  • validate_bal_withdrawal_index: verify withdrawal/request-extraction index entries against actual post-withdrawal state
  • validate_tx_execution: smarter PART B check comparing absent-from-BAL accounts with pre-execution state to distinguish warm-access artifacts from genuine missing entries
  • validate_ordering: storage conflict check (slot in both storage_changes and storage_reads)
  • validate_header_bal_indices: validate BAL indices don't exceed max valid index for the block
  • accessed_accounts tracker in GeneralizedDatabase to detect extraneous BAL "pure-access" entries
  • Edge case fixes: EXTCODECOPY with size=0 now loads target account; storage_read validation handles DestroyedModified status
  • Parallel gas accounting: track regular and state gas separately, compute block_gas_used = max(regular, state) matching sequential path
  • BAL hash mismatch fix in block building (fix(l1): fix BAL hash mismatches in block building #6317)

CI Changes

  • Added Amsterdam consume-engine hive tests (~1000 tests) to PR workflow using bal@v5.3.0 fixtures
  • Bumped daily hive + ef_tests fixtures to bal@v5.3.0
  • Amsterdam hive tests marked as optional (fork spec still evolving)

Roadmap

  • EIP-7708: Rename Selfdestruct event to Burn with updated topic hash
  • EIP-7928: Implement BAL max items cap (corrected formula)
  • EIP-8024: Branchless normalization + EXCHANGE extension
  • EIP-8037: State creation gas cost increase — full reservoir model
  • Bump Amsterdam fixtures to bal@v5.3.0
  • Batched state reads
  • Parallel block execution
  • Parallel state root calculation
  • BAL parallel execution validation (extraneous entry detection)
  • Amsterdam consume-engine hive tests in PR CI

Checklist

  • Updated STORE_SCHEMA_VERSION (crates/storage/lib.rs) if the PR includes breaking changes to the Store requiring a re-sync.

@github-actions github-actions bot added the L1 Ethereum client label Feb 17, 2026
@github-actions
Copy link

🤖 Kimi Code Review

Review Summary

This PR changes EIP-7708 event naming from "Selfdestruct" to "Burn" across the codebase. The changes appear correct and follow the updated EIP specification.

Specific Issues Found

1. Test Function Naming Inconsistency (test file)

File: test/tests/levm/eip7708_tests.rs
Line: 330-340

The function assert_selfdestruct_log should be renamed to assert_burn_log to match the new terminology:

fn assert_burn_log(log: &Log, contract: Address, balance: U256) {
    // ... existing implementation ...
}

2. Test Comments Not Updated (test file)

File: test/tests/levm/eip7708_tests.rs

Several comments still refer to "Selfdestruct" instead of "Burn":

  • Line 334: "Selfdestruct log should have 2 topics"
  • Line 338: "First topic should be Selfdestruct event"
  • Line 1031: "Second log should be Selfdestruct event"
  • Line 1106: "Fourth log should be Selfdestruct (closure)"
  • Lines 1229-1232: "7th log should be Selfdestruct" / "8th log should be Selfdestruct"

These should be updated to use "Burn" terminology for consistency.

3. Missing Documentation Update (constants file)

File: crates/vm/levm/src/constants.rs
Line: 106

The comment should be updated to reflect the new event name:

/// EIP-7708: keccak256('Burn(address,uint256)')

Verification

The keccak256 hash for "Burn(address,uint256)" has been correctly calculated and matches the new BURN_EVENT_TOPIC constant. The implementation correctly handles the semantic change from "Selfdestruct" to "Burn" while maintaining the same underlying functionality.

Security Assessment

No security vulnerabilities introduced. The changes are purely semantic and don't affect the underlying logic or state transitions.


Automated review by Kimi (Moonshot AI) · custom prompt

@github-actions
Copy link

🤖 Codex Code Review

Findings

  1. Consensus-critical topic hash change — verify against final EIP-7708 spec: BURN_EVENT_TOPIC is now keccak256("Burn(address,uint256)") and used in receipts. If the finalized EIP-7708 signature differs, this would produce incorrect log topics and thus incorrect receipt roots (consensus-critical). Please confirm the exact signature in the final spec.
    References: crates/vm/levm/src/constants.rs:103, crates/vm/levm/src/utils.rs:670, crates/vm/levm/src/opcode_handlers/system.rs:691, crates/vm/levm/src/hooks/default_hook.rs:267

  2. Readability: test names and messages still say “Selfdestruct”: The assertions now validate BURN_EVENT_TOPIC but the helper name and error strings still say “Selfdestruct,” which is confusing for future maintenance. Consider renaming assert_selfdestruct_log and updating failure messages to “Burn.”
    References: test/tests/levm/eip7708_tests.rs:335, test/tests/levm/eip7708_tests.rs:1029, test/tests/levm/eip7708_tests.rs:1103

Summary
Beyond the spec verification in Item 1 and the minor naming cleanup in Item 2, the changes look straightforward and low-risk. No obvious security, memory-safety, or performance issues introduced in this diff.

Possible next steps

  1. Confirm the EIP-7708 event signature and update if needed.
  2. Rename test helpers/messages for clarity.

Automated review by OpenAI Codex · custom prompt

@github-actions
Copy link

🤖 Claude Code Review

Now I have a comprehensive understanding of the PR. Let me create the review.

Code Review: PR #6216 - bal-devnet-3: Rename EIP-7708 Selfdestruct event to Burn

Summary

This PR renames the EIP-7708 event from Selfdestruct(address,uint256) to Burn(address,uint256) across the codebase. The change affects constant names, function names, and comments, updating them from "Selfdestruct" to "Burn" terminology while maintaining the same functionality.

Overall Assessment

The changes are correct and well-executed. The renaming is systematic and complete across all relevant files. The hash constant has been correctly updated to match keccak256('Burn(address,uint256)'), and all references have been consistently updated.


Detailed Findings

1. Correctness

Hash Constant (crates/vm/levm/src/constants.rs:107-110)

  • The new BURN_EVENT_TOPIC hash appears correct
  • The test at test/tests/levm/eip7708_tests.rs:759-764 verifies it matches keccak256('Burn(address,uint256)')
  • Proper 32-byte H256 format maintained

Function Renaming (crates/vm/levm/src/utils.rs:668)

  • create_selfdestruct_logcreate_burn_log
  • Function signature and implementation unchanged (correct)
  • All call sites updated in system.rs and default_hook.rs

Import Updates (crates/vm/levm/src/opcode_handlers/system.rs:9)

  • Import statement correctly updated from create_selfdestruct_log to create_burn_log

2. Incomplete Renaming in Test File

Issue: The helper function and comments in test file still use old terminology

Location: test/tests/levm/eip7708_tests.rs:331-354

The function assert_selfdestruct_log should be renamed to assert_burn_log for consistency:

  • Function name: assert_selfdestruct_logassert_burn_log
  • Comment at line 336: "Selfdestruct log should have 2 topics" → "Burn log should have 2 topics"
  • Comment at line 339: "First topic should be Selfdestruct event" → "First topic should be Burn event"
  • Comment at line 347: "Second topic should be contract address" (this is fine, though could clarify "burned address")

All call sites at lines 1035, 1109 would need updating.


3. Comments Still Using Old Terminology

While the code is functionally correct, several comments still reference "Selfdestruct" when they should say "Burn":

test/tests/levm/eip7708_tests.rs:

  • Line 3: "Selfdestruct logs" → "Burn logs"
  • Line 8: "Selfdestruct logs" → "Burn logs"
  • Line 697: "SELFDESTRUCT Tests" - this is actually fine since it's describing SELFDESTRUCT opcode behavior
  • Line 918: "Selfdestruct log" → "Burn log"
  • Line 952: "no Selfdestruct)" → "no Burn)"
  • Line 964: "not Selfdestruct)" → "not Burn)"
  • Line 977: "a Selfdestruct log" → "a Burn log"
  • Line 1013: "Selfdestruct from self-destruct" → "Burn from self-destruct"
  • Line 1030, 1033: "Selfdestruct log/event" → "Burn log/event"
  • Line 1039: "Selfdestruct closure log" → "Burn closure log"
  • Line 1080: "1 Selfdestruct closure" → "1 Burn closure"
  • Line 1104, 1107: "Selfdestruct" → "Burn"
  • Line 1219-1220, 1223, 1229, 1233: "Selfdestruct" → "Burn"

Note: References to "SELFDESTRUCT" (the opcode) or "self-destruct" (the concept) are appropriate and should remain.


4. Documentation Alignment

Location: docs/eip.md:10

Current description: "All ETH transfers emit Transfer/Selfdestruct logs automatically"

Should be updated to: "All ETH transfers emit Transfer/Burn logs automatically"


Security & Correctness Verification

No security concerns - This is purely a renaming refactor
Event structure unchanged - Still LOG2 with same topics structure
Gas accounting unaffected - No changes to execution logic
EIP compliance - Assuming EIP-7708 specification uses "Burn" terminology


Recommendations

Priority 1 (Consistency):

  1. Rename assert_selfdestruct_log() to assert_burn_log() and update all references
  2. Update test comments to use "Burn log" instead of "Selfdestruct log" where referring to the event (not the opcode)
  3. Update docs/eip.md:10 description

Priority 2 (Clarity):
4. Consider adding a comment in the code explaining why the rename happened (if there's a specification change or clarification in EIP-7708)


Verdict

APPROVE with minor improvements suggested

The core changes are correct and complete. The new hash constant is accurate, all functional code has been updated, and the changes are systematic. The remaining issues are purely cosmetic (test helper names and comments) and don't affect correctness or security. These can be addressed in a follow-up or before merge.


Automated review by Claude (Anthropic) · custom prompt

@edg-l edg-l changed the title feat(l1): bal-devnet-3 feat(l1): bal-devnet-3 Feb 17, 2026
@edg-l edg-l moved this to In Progress in ethrex_l1 Feb 17, 2026
@github-actions
Copy link

github-actions bot commented Feb 17, 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 2.979 ± 0.018 2.952 3.002 1.00
main_levm_BubbleSort 3.159 ± 0.031 3.129 3.239 1.06 ± 0.01
pr_revm_BubbleSort 2.982 ± 0.018 2.960 3.010 1.00 ± 0.01
pr_levm_BubbleSort 3.146 ± 0.018 3.122 3.173 1.06 ± 0.01

Benchmark Results: ERC20Approval

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_ERC20Approval 984.7 ± 10.9 969.8 999.3 1.02 ± 0.02
main_levm_ERC20Approval 1208.5 ± 9.3 1193.6 1223.4 1.25 ± 0.02
pr_revm_ERC20Approval 968.4 ± 9.8 960.1 991.9 1.00
pr_levm_ERC20Approval 1220.9 ± 16.9 1196.6 1257.1 1.26 ± 0.02

Benchmark Results: ERC20Mint

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_ERC20Mint 134.2 ± 4.0 130.9 144.0 1.01 ± 0.03
main_levm_ERC20Mint 189.5 ± 4.0 183.5 196.7 1.42 ± 0.03
pr_revm_ERC20Mint 133.2 ± 1.0 131.6 134.8 1.00
pr_levm_ERC20Mint 188.5 ± 2.3 185.6 192.7 1.42 ± 0.02

Benchmark Results: ERC20Transfer

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_ERC20Transfer 231.0 ± 2.8 228.1 235.2 1.00
main_levm_ERC20Transfer 317.1 ± 7.0 313.0 336.2 1.37 ± 0.03
pr_revm_ERC20Transfer 231.8 ± 2.8 228.4 236.4 1.00 ± 0.02
pr_levm_ERC20Transfer 315.5 ± 2.9 312.0 320.6 1.37 ± 0.02

Benchmark Results: Factorial

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_Factorial 226.9 ± 1.0 225.6 229.1 1.00 ± 0.01
main_levm_Factorial 375.0 ± 3.2 369.1 377.8 1.66 ± 0.02
pr_revm_Factorial 226.5 ± 0.7 225.8 228.2 1.00
pr_levm_Factorial 385.8 ± 39.2 369.7 496.9 1.70 ± 0.17

Benchmark Results: FactorialRecursive

Command Mean [s] Min [s] Max [s] Relative
main_revm_FactorialRecursive 1.658 ± 0.078 1.485 1.743 1.00
main_levm_FactorialRecursive 10.719 ± 0.058 10.660 10.870 6.46 ± 0.31
pr_revm_FactorialRecursive 1.684 ± 0.042 1.613 1.747 1.02 ± 0.05
pr_levm_FactorialRecursive 10.694 ± 0.017 10.672 10.728 6.45 ± 0.30

Benchmark Results: Fibonacci

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_Fibonacci 208.1 ± 0.7 207.2 209.5 1.00
main_levm_Fibonacci 360.6 ± 10.1 350.9 386.1 1.73 ± 0.05
pr_revm_Fibonacci 208.9 ± 2.9 203.1 213.2 1.00 ± 0.01
pr_levm_Fibonacci 364.5 ± 3.3 358.1 368.4 1.75 ± 0.02

Benchmark Results: FibonacciRecursive

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_FibonacciRecursive 875.1 ± 6.8 861.2 886.2 1.01 ± 0.02
main_levm_FibonacciRecursive 982.2 ± 6.7 973.7 994.2 1.13 ± 0.02
pr_revm_FibonacciRecursive 867.0 ± 12.1 836.5 882.3 1.00
pr_levm_FibonacciRecursive 992.6 ± 17.3 982.3 1039.7 1.14 ± 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.00
main_levm_ManyHashes 11.0 ± 0.1 10.9 11.1 1.32 ± 0.01
pr_revm_ManyHashes 8.5 ± 0.3 8.3 9.3 1.01 ± 0.04
pr_levm_ManyHashes 11.1 ± 0.0 11.0 11.1 1.32 ± 0.01

Benchmark Results: MstoreBench

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_MstoreBench 266.0 ± 1.3 264.1 267.8 1.00 ± 0.01
main_levm_MstoreBench 450.9 ± 17.9 433.6 496.0 1.70 ± 0.07
pr_revm_MstoreBench 265.9 ± 1.7 264.4 270.2 1.00
pr_levm_MstoreBench 449.4 ± 24.2 431.4 512.7 1.69 ± 0.09

Benchmark Results: Push

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_Push 295.9 ± 1.2 294.9 298.2 1.00
main_levm_Push 460.7 ± 9.8 449.6 477.6 1.56 ± 0.03
pr_revm_Push 297.8 ± 3.1 294.5 304.3 1.01 ± 0.01
pr_levm_Push 444.5 ± 10.7 432.8 457.4 1.50 ± 0.04

Benchmark Results: SstoreBench_no_opt

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_SstoreBench_no_opt 167.1 ± 3.8 161.5 173.3 1.41 ± 0.03
main_levm_SstoreBench_no_opt 118.2 ± 0.7 117.3 119.3 1.00
pr_revm_SstoreBench_no_opt 169.1 ± 6.3 161.4 181.1 1.43 ± 0.05
pr_levm_SstoreBench_no_opt 120.9 ± 4.9 117.3 129.0 1.02 ± 0.04

@github-actions
Copy link

github-actions bot commented Feb 17, 2026

Lines of code report

Total lines added: 791
Total lines removed: 3
Total lines changed: 794

Detailed view
+------------------------------------------------------------------------+-------+------+
| File                                                                   | Lines | Diff |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/blockchain/blockchain.rs                                 | 2214  | +10  |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/blockchain/constants.rs                                  | 16    | +2   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/blockchain/payload.rs                                    | 743   | +9   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/common/common.rs                                         | 22    | +1   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/common/constants.rs                                      | 51    | +1   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/common/errors.rs                                         | 34    | +2   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/common/types/block_access_list.rs                        | 1090  | +126 |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/common/validation.rs                                     | 243   | +62  |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/backends/levm/mod.rs                                  | 2001  | +298 |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/call_frame.rs                                | 371   | +4   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/constants.rs                                 | 63    | +5   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/db/gen_db.rs                                 | 552   | +7   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/errors.rs                                    | 241   | +9   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/execution_handlers.rs                        | 162   | +17  |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/gas_cost.rs                                  | 856   | +22  |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/hooks/default_hook.rs                        | 460   | +51  |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/hooks/l2_hook.rs                             | 606   | +11  |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/opcode_handlers/dup.rs                       | 53    | -1   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/opcode_handlers/environment.rs               | 351   | +1   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/opcode_handlers/exchange.rs                  | 114   | -2   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs | 326   | +21  |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/opcode_handlers/system.rs                    | 950   | +48  |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/utils.rs                                     | 452   | +52  |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/vm.rs                                        | 549   | +32  |
+------------------------------------------------------------------------+-------+------+

@edg-l edg-l added the Upgrade Support for eips that are part of an ethereum upgrade/hard fork label Feb 17, 2026
Base automatically changed from bal-devnet-2 to main February 18, 2026 19:29
@edg-l edg-l force-pushed the bal-devnet-3 branch 2 times, most recently from b088951 to f13160d Compare February 26, 2026 13:32
edg-l added 11 commits March 3, 2026 09:44
Per ethereum/EIPs#11311, the EIP-7708 log event is renamed from
Selfdestruct(address,uint256) to Burn(address,uint256) with a new
topic hash.
Reject blocks whose block access list exceeds the gas-derived limit:
bal_items * ITEM_COST <= available_gas + system_allowance
Align DUPN, SWAPN, and EXCHANGE decoding with the updated EIP-8024 spec
(ethereum/EIPs#11306). decode_single now uses (x + 145) % 256 instead of
branching, and decode_pair uses XOR 0x8F instead of conditional subtraction.
EXCHANGE valid range extended from 0x00-0x4F to 0x00-0x51, adding two new
swappable pairs (14,15) and (14,16).
- Subtract remaining state_gas_reservoir from gas_used in
  finalize_execution (unused reservoir is returned, not consumed)
- EIP-7702 auth refund returns to state gas reservoir for Amsterdam+
  instead of regular refund counter
- Skip Osaka TX_MAX_GAS_LIMIT cap for Amsterdam+ (EIP-8037 reservoir
  model removes the per-tx gas limit restriction)
edg-l added 4 commits March 9, 2026 12:51
When a child frame reverts and its sub-child had also reverted, the
state gas reservoir gain from the sub-child was not captured by the
parent restoration formula. The old formula used reservoir_snapshot
diffs which broke when the reservoir increased during child execution.

New formula: new_reservoir = current_reservoir + child_state_gas_used,
which correctly matches EELS incorporate_child_on_error behavior where
parent.state_gas_left += child.state_gas_used + child.state_gas_left.

Fixes CallGoesOOGOnSecondLevel2 Amsterdam EF test failures.
…037)

Per EELS, CREATE/CREATE2 collisions return gas_used=0 — the user pays
for the consumed child gas but it should not count as regular gas in
block accounting (block gas_used = max(regular, state)).

Track collision-consumed gas separately and subtract it when computing
regular_gas in refund_sender. Fixes 8 EF test failures in collision
scenarios (eip7610, eip7928, eip8037, eip6780, stCreateTest).
Amsterdam fork spec is still evolving, so don't block normal PR
workflows on consume-engine Amsterdam failures.
@edg-l edg-l marked this pull request as ready for review March 9, 2026 15:34
@edg-l edg-l requested review from a team, ManuelBilbao, avilagaston9 and ilitteri as code owners March 9, 2026 15:34
@edg-l edg-l moved this from In Progress to In Review in ethrex_l1 Mar 9, 2026
@github-actions
Copy link

github-actions bot commented Mar 9, 2026

🤖 Kimi Code Review

Review Summary

This PR implements Amsterdam fork support with EIP-7928 (Block Access Lists), EIP-8037 (multidimensional gas), and EIP-7954 (increased initcode/code size limits). The changes are substantial and touch core consensus logic.

Critical Issues Found

  1. Potential integer overflow in validate_block_access_list_size (crates/common/validation.rs:240-241)

    let max_items = header.gas_limit / BAL_ITEM_COST;

    When header.gas_limit < BAL_ITEM_COST, this will be 0, allowing 0 items. This seems intentional per EIP-7928, but should be documented.

  2. Race condition in parallel BAL validation (crates/vm/backends/levm/mod.rs:789-792)
    The unread_storage_reads and unaccessed_pure_accounts HashSets are built from BAL but accessed across threads without synchronization. While the validation happens after parallel execution, the sets are modified during validation - this needs mutex protection.

  3. Missing validation for BAL canonical ordering (crates/common/types/block_access_list.rs:450)
    The validate_canonical_ordering function is defined but never called during block validation. This could allow non-canonical BALs.

Security Concerns

  1. State gas accounting edge cases (crates/vm/levm/src/vm.rs:549-560)
    The increase_state_gas function has complex spillover logic that could be exploited if gas_limit is manipulated. Consider adding bounds checking.

  2. CREATE collision handling (crates/vm/levm/src/vm.rs:784-790)
    The collision escrow logic might allow gas smuggling if collision_escrowed_gas isn't properly bounded.

Performance Issues

  1. Inefficient BAL validation (crates/vm/backends/levm/mod.rs:1050-1075)
    The nested loops in validate_tx_execution have O(n²) complexity for large BALs. Consider using HashMaps for faster lookups.

  2. Memory allocation in hot path (crates/vm/backends/levm/mod.rs:803-806)
    Creating new HashSets for every transaction in parallel execution could be expensive. Consider reusing allocated memory.

Code Quality Issues

  1. Inconsistent error handling (crates/blockchain/blockchain.rs:350-351)
    The error conversion ChainError::InvalidBlock(InvalidBlockError::InvalidBody(e)) loses context from the original error.

  2. Magic numbers (crates/vm/levm/src/constants.rs:35-36)
    Consider documenting the origin of TX_MAX_GAS_LIMIT_AMSTERDAM = 1 << 24 and COST_PER_STATE_BYTE = 1174.

  3. Dead code (crates/common/types/block_access_list.rs:495-510)
    The duplicate slot check between storage_changes and storage_reads appears unused.

Recommendations

  1. Add comprehensive tests for edge cases in BAL size validation
  2. Implement proper synchronization for parallel BAL validation
  3. Add benchmarks for large BAL scenarios
  4. Consider adding a feature flag for Amsterdam fork to allow gradual rollout
  5. Document the rationale for each EIP implementation choice

The PR appears to correctly implement the Amsterdam fork changes, but the parallel execution validation needs careful review for thread safety.


Automated review by Kimi (Moonshot AI) · custom prompt

@github-actions
Copy link

github-actions bot commented Mar 9, 2026

🤖 Codex Code Review

  1. Potential consensus under-validation for BAL storage_reads on destroyed accounts
    At mod.rs#L935 and mod.rs#L944, all unread slots for a destroyed account are marked as satisfied.
    This can let extraneous BAL storage_reads pass when an account selfdestructs after only some reads. If EIP-7928 requires exact reads, this is an acceptance bug.
    Suggestion: track actual SLOAD slot accesses directly (per-tx/per-account), instead of inferring from final storage map.

  2. Parallel path now retains full per-tx state for all txs (DoS/perf risk)
    At mod.rs#L824, mod.rs#L833, and mod.rs#L866, each worker returns full current_accounts_state and codes, and the coordinator keeps them until post-sort/post-gas checks.
    For large Amsterdam blocks, this can significantly increase peak memory and create avoidable OOM pressure.
    Suggestion: return only minimal validation artifacts (e.g., touched slots/accounts + mismatch flags) and avoid retaining full maps across all txs.

  3. Unchecked addition in gas-limit check can overflow in release
    At mod.rs#L890:
    if running_block_gas + tx_gas_limit > header.gas_limit
    This should use checked_add to avoid wraparound edge cases. Even if practically rare, consensus code should be overflow-explicit.

  4. Hot-path debug prints left in consensus execution
    At default_hook.rs#L184 and vm.rs#L548, unconditional eprintln! in transaction/opcode hot paths will hurt throughput and spam logs under load.
    Suggestion: remove or gate behind trace-level logging.

  5. Fork-gating inconsistency between L1 and L2 tx gas-limit checks
    L2 hook applies a pre-Amsterdam cap from Prague onward, while L1 path uses Osaka-only gating (default_hook.rs#L38, validation.rs#L47).
    If intentional, document explicitly; if not, this can cause divergent validity between execution paths.

Overall: the PR is strong and comprehensive on Amsterdam/EIP integration, but the BAL validation edge case and memory behavior in parallel execution are the most important items to address before merge.


Automated review by OpenAI Codex · custom prompt

@github-actions
Copy link

github-actions bot commented Mar 9, 2026

🤖 Claude Code Review

Now I have a thorough understanding of the PR. Let me compile the review.


PR Review: feat(l1): bal-devnet-3

This is a large tracking branch implementing several Amsterdam devnet-3 EIPs. The core logic is generally well-structured and aligned with EELS. Here are the findings:


Critical

Debug eprintln! statements left in production code

Two debug prints were not removed before this PR:

crates/vm/levm/src/hooks/default_hook.rs (~line 1528):

eprintln!(
    "[DBG] tx_gas_limit={} gas_used_before_reservoir={} reservoir={} state_gas_used={}...",
    ...
);

crates/vm/levm/src/vm.rs (~line 2379):

eprintln!(
    "[STATE_GAS] depth={} spill={} reservoir={} gas_remaining={} state_gas_used={}",
    ...
);

These will flood stderr on every Amsterdam transaction execution. They must be removed before merging.


Bug

l2_hook.rs: Incorrect fork range for EIP-7825 gas cap

The new first check in prepare_execution_fee_token:

// Wrong: applies Prague (pre-Osaka) where there is no per-tx cap yet
if vm.env.config.fork < Fork::Amsterdam && vm.tx.gas_limit() > TX_MAX_GAS_LIMIT_AMSTERDAM {
    return Err(...)
}

This rejects transactions with gas_limit > 16,777,216 on Prague (before Osaka), where EIP-7825 had not yet been introduced. The comment even says "EIP-7825 (Prague to pre-Amsterdam)", but EIP-7825 was introduced in Osaka.

The default hook (default_hook.rs) gets this right:

if vm.env.config.fork >= Fork::Osaka
    && vm.env.config.fork < Fork::Amsterdam
    && vm.tx.gas_limit() > POST_OSAKA_GAS_LIMIT_CAP

The second new check in l2_hook.rs (with the Osaka + Amsterdam range) is then dead code — it can never be reached because the first check already covers the entire < Amsterdam window. The fix is to match the default hook's guard exactly and remove the redundant second check.


Minor

validate_header_bal_indices: potential u16 overflow on pathological input

// crates/common/validation.rs
#[allow(clippy::cast_possible_truncation)]
let max_valid_index = transaction_count as u16 + 1;

If transaction_count is 65535, the as u16 truncates to 0xFFFF, then + 1 wraps to 0. In practice a block will never contain 65535 transactions, but since this is a validation function it would be more defensive to use u16::try_from(transaction_count).map_err(...) followed by checked_add(1).

Non-deterministic error reporting in BAL validation

if let Some((addr, key)) = unread_storage_reads.iter().next() { ... }
if let Some(addr) = unaccessed_pure_accounts.iter().next() { ... }

FxHashSet iteration order is unspecified, so the reported address/key in error messages will vary between runs. This makes failures hard to reproduce. Using BTreeSet or at least sorting before emitting the error would improve debuggability.

increase_state_gas increments state_gas_used for pre-Amsterdam

The else branch of increase_state_gas charges gas from gas_remaining (correct) but still falls through to increment state_gas_used (semantically wrong for pre-Amsterdam). All call sites currently guard against this with fork checks, but it's an internal inconsistency that makes the function unsafe to call without understanding the invariant. A comment or an early return of Ok(()) in the else branch after the gas charge would clarify the intent.


Observations (non-blocking)

  • EIP-8024 branchless decode: The wrapping_add(145) for DUPN/SWAPN and xor(0x8F) for EXCHANGE are correct per EIP-8024 spec, and the test vectors match.
  • EIP-7708 rename: Topic hash for Burn(address,uint256) is consistent; the constant is verified in tests against keccak256("Burn(address,uint256)").
  • EIP-7928 BAL size formula: bal_items > gas_limit / BAL_ITEM_COST matches the corrected EELS formula (no system allowance). The item_count() function correctly counts unique slots (storage_reads and storage_changes are mutually exclusive via the conflict check).
  • EIP-8037 reservoir model: The snapshot-based child revert logic (new_reservoir = current_reservoir + child_state_gas_used) is well-explained and matches EELS incorporate_child_on_error. The ordering of state gas before regular gas for SSTORE and CREATE deposit is correct.
  • code_from_bal helper: A good refactor that removes the duplicated code-hash computation logic.
  • validate_block_body call added to blockchain.rs: This is a good defensive addition for the sequential path.
  • continue-on-error for Amsterdam hive tests: Appropriate given the evolving spec. The TODO comment to remove it once the fork stabilizes is noted.

Automated review by Claude (Anthropic) · custom prompt

@greptile-apps
Copy link

greptile-apps bot commented Mar 9, 2026

Greptile Summary

This PR implements the Amsterdam bal-devnet-3 alignment across four EIPs (7708, 7928, 8024, 8037) and adds comprehensive BAL parallel execution validation. The changes are substantial and touch the core of gas accounting, opcode decoding, and block validation. Most of the implementation is well-structured and well-commented; however, two debug eprintln! statements were left in the hot path for Amsterdam transactions (default_hook.rs and vm.rs), and the L2 hook contains a gas-limit cap that is incorrectly applied to Prague forks (pre-Osaka).

Key changes:

  • EIP-7708: Selfdestruct event renamed to Burn with updated topic hash 0xcc16f5...; rename is applied consistently across logs, utils, and tests.
  • EIP-7928: validate_block_access_list_size added at three call-sites in blockchain.rs and in the parallel path; formula (gas_limit / BAL_ITEM_COST) matches EELS.
  • EIP-8024: DUPN/SWAPN branchless (x + 145) % 256 normalisation; EXCHANGE XOR 0x8F decode with valid range extended to 0x00-0x51; tests updated to match new mappings.
  • EIP-8037: Full reservoir model: intrinsic state gas split, per-frame snapshots for revert restoration, collision_escrowed_gas for block accounting, increase_state_gas drawing from reservoir before spilling to gas_remaining. Two debug eprintln! calls must be removed before production use.
  • BAL parallel validation: Extraneous-entry detection via unread_storage_reads / unaccessed_pure_accounts checklists, smarter absent-from-BAL account check comparing pre/post state, and validate_bal_withdrawal_index for the post-execution phase.
  • L2 hook bug: Gas-limit cap at l2_hook.rs:477 incorrectly applies to all [Prague, Amsterdam) forks instead of [Osaka, Amsterdam) (as correctly implemented in default_hook.rs), making the second guard dead code and incorrectly restricting Prague transactions.
  • CI: Amsterdam hive tests added as optional; bal@v5.3.0 / devnets/bal/3 bumped consistently across all fixture files.

Confidence Score: 2/5

  • Not safe to merge as-is: three blocking issues must be fixed (two debug eprintln! statements in hot paths, one L2 hook gas-limit logic bug).
  • The core EIP implementations (7708, 7928, 8024, 8037) and BAL parallel validation are well-implemented and documented. However, three concrete bugs prevent merging: (1) a debug eprintln! in default_hook.rs line 184 fires for every Amsterdam+ transaction and will spam stderr; (2) a debug eprintln! in vm.rs line 548 fires on every state gas reservoir spill during normal execution; (3) the L2 hook at line 477 incorrectly applies the gas-limit cap to [Prague, Amsterdam) instead of just [Osaka, Amsterdam), which contradicts the correct behavior in default_hook.rs and could incorrectly reject valid Prague-era L2 transactions.
  • crates/vm/levm/src/hooks/default_hook.rs (remove debug eprintln), crates/vm/levm/src/vm.rs (remove debug eprintln), crates/vm/levm/src/hooks/l2_hook.rs (fix gas-limit cap range and remove dead guard).

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Transaction enters VM] --> B{Fork >= Amsterdam?}
    B -- No --> C[Charge full intrinsic gas\nfrom gas_remaining]
    B -- Yes --> D[Split intrinsic gas:\nregular_gas + state_gas]
    D --> E[Set state_gas_reservoir\n= excess above TX_MAX_GAS_LIMIT]
    E --> F[Deduct reservoir from gas_remaining\nso GAS opcode ≤ TX_MAX_GAS_LIMIT]

    F --> G[EVM Execution]
    G --> H{State gas needed?}
    H -- Yes --> I[increase_state_gas:\ndraw from reservoir first]
    I --> J{Reservoir empty?}
    J -- No --> K[Deduct from reservoir]
    J -- Yes --> L[Spill to gas_remaining\nOOG if exhausted]

    G --> M{Child call reverts?}
    M -- Yes --> N[Restore reservoir:\nnew_reservoir += child_state_gas_used\nstate_gas_used = snapshot]

    G --> O{CREATE collision?}
    O -- Yes --> P[collision_escrowed_gas += gas_limit\nblock accounting excludes this]

    G --> Q[finalize_execution]
    Q --> R[gas_used -= state_gas_reservoir\nunused reservoir returned to sender]
    R --> S[regular_gas = gas_used - state_gas\nblock_gas_used = max sum_regular, sum_state]
Loading

Comments Outside Diff (1)

  1. crates/vm/levm/src/hooks/l2_hook.rs, line 477-495 (link)

    Gas-limit cap applied to all Prague forks including pre-Osaka

    The first guard (line 477) applies the TX_MAX_GAS_LIMIT_AMSTERDAM (16,777,216) cap to all forks in the range [Prague, Amsterdam). This means Prague (pre-Osaka) transactions with gas_limit > 16 M would be rejected by the L2 hook, even though EIP-7825 only introduced the cap at Osaka.

    The default_hook.rs correctly restricts the cap to [Osaka, Amsterdam). The L2 hook should mirror that behaviour.

    Additionally, because TX_MAX_GAS_LIMIT_AMSTERDAM == POST_OSAKA_GAS_LIMIT_CAP (both are 1 << 24), the second guard (lines 485-495) is entirely dead code under the first guard — it can never trigger.

    The first guard should be tightened to start at Osaka, and the second (now-redundant) guard should be removed:

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: crates/vm/levm/src/hooks/l2_hook.rs
    Line: 477-495
    
    Comment:
    Gas-limit cap applied to all Prague forks including pre-Osaka
    
    The first guard (line 477) applies the `TX_MAX_GAS_LIMIT_AMSTERDAM` (`16,777,216`) cap to all forks in the range `[Prague, Amsterdam)`. This means Prague (pre-Osaka) transactions with `gas_limit > 16 M` would be rejected by the L2 hook, even though EIP-7825 only introduced the cap at **Osaka**.
    
    The `default_hook.rs` correctly restricts the cap to `[Osaka, Amsterdam)`. The L2 hook should mirror that behaviour.
    
    Additionally, because `TX_MAX_GAS_LIMIT_AMSTERDAM == POST_OSAKA_GAS_LIMIT_CAP` (both are `1 << 24`), the second guard (lines 485-495) is entirely dead code under the first guard — it can never trigger.
    
    The first guard should be tightened to start at `Osaka`, and the second (now-redundant) guard should be removed:
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: 606ad4c

Comment on lines +184 to +193
eprintln!(
"[DBG] tx_gas_limit={} gas_used_before_reservoir={} reservoir={} state_gas_used={} state_gas_refund={} gas_remaining={} result={:?}",
vm.env.gas_limit,
ctx_result.gas_used,
vm.state_gas_reservoir,
vm.state_gas_used,
vm.intrinsic_state_gas_refund,
vm.current_call_frame.gas_remaining,
ctx_result.result
);
Copy link

Choose a reason for hiding this comment

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

Debug eprintln! left in production code

This [DBG] eprintln! statement fires for every Amsterdam+ transaction and will spam stderr in any environment running Amsterdam blocks. It was almost certainly left in during development of EIP-8037 and should be removed before merging.

Suggested change
eprintln!(
"[DBG] tx_gas_limit={} gas_used_before_reservoir={} reservoir={} state_gas_used={} state_gas_refund={} gas_remaining={} result={:?}",
vm.env.gas_limit,
ctx_result.gas_used,
vm.state_gas_reservoir,
vm.state_gas_used,
vm.intrinsic_state_gas_refund,
vm.current_call_frame.gas_remaining,
ctx_result.result
);
if vm.env.config.fork >= Fork::Amsterdam {
ctx_result.gas_used = ctx_result.gas_used.saturating_sub(vm.state_gas_reservoir);
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/vm/levm/src/hooks/default_hook.rs
Line: 184-193

Comment:
Debug `eprintln!` left in production code

This `[DBG]` `eprintln!` statement fires for every Amsterdam+ transaction and will spam stderr in any environment running Amsterdam blocks. It was almost certainly left in during development of EIP-8037 and should be removed before merging.

```suggestion
        if vm.env.config.fork >= Fork::Amsterdam {
            ctx_result.gas_used = ctx_result.gas_used.saturating_sub(vm.state_gas_reservoir);
        }
```

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

Comment on lines +548 to +555
eprintln!(
"[STATE_GAS] depth={} spill={} reservoir={} gas_remaining={} state_gas_used={}",
self.current_call_frame.depth,
spill,
self.state_gas_reservoir,
self.current_call_frame.gas_remaining,
self.state_gas_used
);
Copy link

Choose a reason for hiding this comment

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

Debug eprintln! left in production code

This [STATE_GAS] eprintln! fires every time the state gas reservoir is exhausted and spills into regular gas, which will happen during normal execution. This is debug instrumentation that should be removed (or replaced with a tracing::trace! call if the diagnostic information is genuinely needed).

Suggested change
eprintln!(
"[STATE_GAS] depth={} spill={} reservoir={} gas_remaining={} state_gas_used={}",
self.current_call_frame.depth,
spill,
self.state_gas_reservoir,
self.current_call_frame.gas_remaining,
self.state_gas_used
);
// Charge spill from gas_remaining first — if OOG, return early
// without mutating reservoir or state_gas_used (matches EELS behavior)
self.current_call_frame.increase_consumed_gas(spill)?;
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/vm/levm/src/vm.rs
Line: 548-555

Comment:
Debug `eprintln!` left in production code

This `[STATE_GAS]` `eprintln!` fires every time the state gas reservoir is exhausted and spills into regular gas, which will happen during normal execution. This is debug instrumentation that should be removed (or replaced with a `tracing::trace!` call if the diagnostic information is genuinely needed).

```suggestion
                // Charge spill from gas_remaining first — if OOG, return early
                // without mutating reservoir or state_gas_used (matches EELS behavior)
                self.current_call_frame.increase_consumed_gas(spill)?;
```

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

@edg-l edg-l marked this pull request as draft March 9, 2026 15:40
@ethrex-project-sync ethrex-project-sync bot moved this from In Review to In Progress in ethrex_l1 Mar 9, 2026
edg-l added 4 commits March 9, 2026 17:06
Remove two eprintln! calls left from a debug session:
- increase_state_gas: logged on every state gas spill (per-opcode)
- on_execution_end: logged on every Amsterdam tx exit (per-tx)
- Add STATE_GAS_NEW_ACCOUNT, STATE_GAS_STORAGE_SET, STATE_GAS_AUTH_TOTAL
  constants replacing 10 runtime checked_mul calls in SSTORE, CREATE,
  CREATE2, CALL, SELFDESTRUCT, and intrinsic gas handlers
- Remove redundant fork >= Amsterdam check inside increase_state_gas
  (all callers already guard), replace with debug_assert
- Merge BAL item_count into validate_block_access_list_hash loop,
  eliminating a second O(BAL) pass for sequential block validation
- Pre-size per-tx FxHashSet for accessed_accounts tracker (capacity 16)
Switch BlockAccessListRecorder to IndexSet/IndexMap for touched_addresses,
storage_reads, initial_balances, and addresses_with_initial_code. This
enables length-based tx-level checkpoint/restore (tx_checkpoint/tx_restore)
instead of cloning the entire recorder per tx attempt in block building.

- Per-tx cost goes from O(recorder_size) clone to O(addresses) snapshot
- build() sorts at finalization time for canonical BAL ordering (one-time)
- Checkpoint taken after set_bal_index flush to preserve previous tx's
  net-zero-to-read conversions
edg-l added a commit that referenced this pull request Mar 10, 2026
Add March 10 status update reflecting bal-devnet-3-dev progress:
EIP-8037 nested revert fixes, BAL parallel execution improvements,
bal@v5.3.0 fixtures, Amsterdam consume-engine hive tests in CI.
Fix PR review comments: reference link ordering, bullet point
count, and EIP-8037 table status columns. Update all references
from closed PR #6293 to tracking PR #6216.
edg-l added 3 commits March 10, 2026 13:33
In the parallel execution path, per-tx BAL validation ran before system
contract calls, causing BAL errors to mask SYSTEM_CONTRACT_CALL_FAILED
for blocks with failing system contracts and inconsistent BALs.
edg-l added 2 commits March 10, 2026 15:33
…lookup

seed_db_from_bal previously iterated ALL BAL accounts for every parallel
tx, making it O(N*M) where N=txs and M=accounts. Now uses a precomputed
sorted list of (min_block_access_index, account_idx) with binary search
to only visit accounts that have changes at indices <= max_idx.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

L1 Ethereum client Upgrade Support for eips that are part of an ethereum upgrade/hard fork

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

1 participant