Skip to content

fix(levm): revert doesn't unmark the account as existing#6592

Merged
ilitteri merged 3 commits into
mainfrom
fix/eip7702-revert-authority-exists
May 11, 2026
Merged

fix(levm): revert doesn't unmark the account as existing#6592
ilitteri merged 3 commits into
mainfrom
fix/eip7702-revert-authority-exists

Conversation

@iovoid

@iovoid iovoid commented May 8, 2026

Copy link
Copy Markdown
Contributor

Motivation

When an account creation reverts, we incorrectly mark it as existing for the purposes of EIP7702, leading to charging gas incorrectly.

Description

Currently we track accounts that exist in the trie for purposes of charging EIP7702 gas (the price is different if the authority already exists in the trie). This is saved in the exists field of the account, which is currently not saved in the callframe backup.

This means when an account creation is reverted, we don't unmark it as existing. This causes an incorrect amount of gas to be charged.

The PR contains a regression test, that should fail on the first commit and pass on the second.

iovoid added 2 commits May 8, 2026 17:29
A reverted transaction that touched a non-existent account via
mark_modified leaves a stale exists=true in the cache. A subsequent
EIP-7702 SetCode tx whose authority is that address then sees
exists=true and applies the spurious REFUND_AUTH_PER_EXISTING_ACCOUNT.

The test compares the gas charged by an EIP-7702 SetCode tx with a
fresh authority in two scenarios within a single block: only the
EIP-7702 tx, and a reverting helper-call that touches the authority
(via a 1-wei sub-call) followed by the EIP-7702 tx. Both must charge
the same gas.
restore_cache_state restored info, status, and has_storage from the
backup but omitted exists. mark_modified() unconditionally sets
exists=true, so a reverted transaction that touched a non-existent
account leaves stale exists=true in the cache. A subsequent
EIP-7702 SetCode tx whose authority is that address then sees
exists=true and applies the spurious REFUND_AUTH_PER_EXISTING_ACCOUNT
(or the EIP-8037 STATE_GAS_NEW_ACCOUNT refund on Amsterdam+).

Restoring exists is only effective if the backup carries the
pre-modification state. VM::get_account_mut and the SELFDESTRUCT
hook both backed up via &mut returned from db.get_account_mut, which
runs mark_modified before the backup is taken. Reorder so the backup
captures the immutable account state and mark_modified runs after.
@iovoid iovoid requested a review from a team as a code owner May 8, 2026 20:35
@github-actions github-actions Bot added the levm Lambda EVM implementation label May 8, 2026
@github-actions

github-actions Bot commented May 8, 2026

Copy link
Copy Markdown

🤖 Kimi Code Review

This PR fixes a consensus-critical bug in the LEVM implementation regarding EIP-7702 authority account state handling during transaction reverts.

Summary of Changes

The fix ensures that the exists flag on accounts is properly backed up before any modification (which might flip exists to true) and restored during cache reversion. This prevents "pollution" of account existence state across reverted transactions.

Detailed Review

1. crates/vm/levm/src/db/gen_db.rs (lines 534-543)

Correctness: ✅ Good

The split into immutable then mutable lookup is the correct fix:

// Backup must be taken before mark_modified flips `exists` to true.
let account = self.db.get_account(address)?;  // Capture pre-modification state
// ... backup ...
let account = self.db.get_account_mut(address)?;  // Now modify

Note: This pattern introduces a double lookup (two hashmap lookups in the underlying DB), but this is acceptable for correctness in consensus-critical code. If performance becomes a concern, consider a get_account_mut_with_snapshot method, but the current approach is clearer and safer.

2. crates/vm/levm/src/hooks/default_hook.rs (lines 362-372)

Correctness: ✅ Good

Same pattern as above in delete_self_destruct_accounts. The backup captures the pre-destruction state before mark_destroyed() is called.

3. crates/vm/levm/src/utils.rs (line 85)

Critical Fix: ✅ Essential

current_account.exists = account.exists;

This is the missing piece that caused the consensus bug. Without restoring the exists flag:

  • A reverted transaction that touched an authority address would leave exists = true
  • Subsequent EIP-7702 transactions would incorrectly treat the account as warm/existing
  • Gas accounting would differ from other clients, causing a consensus split

4. test/tests/blockchain/eip7702_revert_authority_tests.rs

Test Coverage: ✅ Comprehensive

The test correctly validates that:

  1. A reverted transaction touching an authority doesn't affect subsequent EIP-7702 authorization gas costs
  2. The exists flag is properly isolated across transaction boundaries

Minor suggestions:

  1. Line 45: Consider using a random or derived key instead of [0x42; 32] to avoid potential key collisions if tests run in parallel or if the authority address has special properties:

    // Could use: SecretKey::from_slice(&rand::random::<[u8; 32]>())
  2. Line 81: The path fixtures/genesis/execution-api.json assumes a specific working directory. Consider using a more robust path resolution or documenting the requirement that tests must run from the workspace root.

  3. Lines 265, 280: The gas calculation logic is correct but subtle. Consider adding a comment explaining that both branches return the gas used by the EIP-7702 transaction specifically (not cumulative).

Security Assessment

Severity: Critical Consensus Bug

This fix prevents a consensus split between ethrex and other Ethereum clients. The bug would have caused:

  • Different state roots after processing blocks with reverted EIP-7702 authorizations
  • Potential fund loss or invalid execution in L2 contexts using ethrex as the execution client

Recommendation

Approve with minor comments. The fix is correct, minimal, and well-tested. The double-lookup pattern is a clean solution that prioritizes correctness over micro-optimizations.

Consider adding a regression test comment referencing the specific EIP-7702 consensus rule being tested:

// Tests that authority account warming is properly reverted per EIP-7702:
// "If the transaction is reverted, the nonce increase and any authority 
//  account creation must be reverted."

Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt

@github-actions

github-actions Bot commented May 8, 2026

Copy link
Copy Markdown

Lines of code report

Total lines added: 3
Total lines removed: 0
Total lines changed: 3

Detailed view
+-------------------------------------------------+-------+------+
| File                                            | Lines | Diff |
+-------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/db/gen_db.rs          | 561   | +1   |
+-------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/hooks/default_hook.rs | 459   | +1   |
+-------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/utils.rs              | 461   | +1   |
+-------------------------------------------------+-------+------+

@greptile-apps

greptile-apps Bot commented May 8, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes a bug where a reverted account creation incorrectly left the exists flag set to true on the in-memory LevmAccount, causing EIP-7702 authority gas calculations to be wrong in subsequent transactions. The root cause was that exists was not included in the callframe backup/restore cycle.

  • restore_cache_state now restores the exists field alongside info, status, and has_storage, so a revert properly unmarks a newly-created account.
  • VM::get_account_mut and the SELFDESTRUCT path in default_hook.rs now capture the backup via the immutable get_account call before get_account_mut is invoked (which internally calls mark_modified, setting exists = true), ensuring the backed-up snapshot holds the pre-modification exists value.
  • A regression test (eip7702_revert_authority_tests.rs) validates that a reverted transfer to an authority address does not pollute the exists flag used for EIP-7702 gas accounting.

Confidence Score: 5/5

Safe to merge. The change is minimal and targeted — only the callframe backup/restore path for the exists field is touched, and a regression test directly verifies the fix.

The change is small and well-understood: one extra field restored on revert, and two sites where the backup snapshot is now taken before mark_modified flips exists. The regression test faithfully reproduces the original mis-accounting and confirms it is resolved. No unrelated logic was touched.

No files require special attention beyond the five changed here.

Important Files Changed

Filename Overview
crates/vm/levm/src/utils.rs Adds current_account.exists = account.exists to restore_cache_state, completing the backup/restore cycle for the exists field — the core fix.
crates/vm/levm/src/db/gen_db.rs Reorders backup capture in VM::get_account_mut to use the immutable get_account snapshot before get_account_mut calls mark_modified (which sets exists = true), ensuring the backup always holds the pre-modification state.
crates/vm/levm/src/hooks/default_hook.rs Applies the same backup-before-mark_modified pattern to the SELFDESTRUCT account removal path, consistent with the fix in gen_db.rs.
test/tests/blockchain/eip7702_revert_authority_tests.rs New regression test that constructs two scenarios (with and without a preceding reverted touch to the authority) and asserts equal gas usage for the EIP-7702 transaction; correctly captures the bug described in the PR.
test/tests/blockchain/mod.rs Adds the new test module declaration — trivial change.

Reviews (1): Last reviewed commit: "fix(levm): restore exists flag on transa..." | Re-trigger Greptile

@github-actions

github-actions Bot commented May 8, 2026

Copy link
Copy Markdown

🤖 Claude Code Review

Now I have a thorough understanding of the changes. Let me write the review.


PR Review: fix(levm): revert doesn't unmark the account as existing

Overview

This PR fixes a two-part bug in LEVM's EIP-7702 gas accounting during reverts:

  • exists was not included in the fields restored by restore_cache_state, so a reverted account creation left exists=true permanently
  • The backup in get_account_mut (and the SELFDESTRUCT path in default_hook.rs) was taken after db.get_account_mut was called, which internally calls mark_modified() — flipping exists to true before the snapshot was captured

Both fixes together are necessary: without the ordering fix, the backup would capture exists=true even for a non-existent account, and without the restore fix, the backup value would simply be ignored.


Correctness

Fix 1 — utils.rs:85: Adding current_account.exists = account.exists; to restore_cache_state is the primary fix. The field was already being saved in backup_account_info, but silently ignored during restore.

Fix 2 — gen_db.rs:537-544 and default_hook.rs:363-373: Taking the backup from get_account (immutable, no side effects) before calling get_account_mut (which triggers mark_modified()exists = true) is correct and ensures the snapshot captures the pre-modification state.

Both fixes are necessary and complementary. The logic is sound.


Potential Concern: l2_hook.rs Has the Same Pattern

The subagent found a third direct call to vm.db.get_account_mut that bypasses the backup-before-modify ordering:

crates/vm/levm/src/hooks/l2_hook.rs (transfer_fee_token function)
vm.db.get_account_mut(fee_token)?.storage = new_storage;

Unlike the two call sites fixed in this PR, there is no preceding backup_account_info call for the fee_token account info — only storage slots are backed up. In practice this is unlikely to cause a bug today because the fee token is a genesis-deployed contract (exists is always true), but it's an inconsistency with the pattern now being enforced elsewhere and could become a latent bug if the invariant changes.

Worth either documenting the assumption or applying the same fix for consistency.


Minor Points

  • Double lookup overhead (get_account + get_account_mut): The extra read adds one cache lookup per get_account_mut call. Given LEVM keeps a current_accounts_state HashMap, this is a HashMap lookup (O(1)) with no disk I/O. The cost is negligible.

  • Test structure — run_scenario duplication: The block-building and receipt-fetching logic is duplicated between the if precede_with_revert_touch branch (early return) and the fall-through path. This is readable enough for a test, but the duplication is a slight maintainability concern. Extracting a run_block_and_get_7702_gas helper would clean it up without affecting correctness.

  • sign_auth_tupleInto::<i32>::into(recovery_id) as u64: The double conversion is safe but verbose. recovery_id as u8 as u64 or u64::from(recovery_id as u8) would be cleaner. Minor test-code nit.

  • REVERT_HELPER_BYTECODE correctness: The bytecode is correct. Stack state before CALL is (top→bottom): gas=0xFFFF, addr (from CALLDATALOAD), value=1, args_offset=0, args_size=0, ret_offset=0, ret_size=0 — matching the CALL argument order exactly. The 12-byte zero-pad + 20-byte address construction in create_revert_touch_tx correctly aligns with CALLDATALOAD at offset 0.


Summary

The fix is correct, minimal in scope, and the regression test accurately captures the bug scenario. The l2_hook.rs inconsistency is the only non-trivial concern worth addressing before or after merge. Everything else is minor style. Approve with the l2_hook.rs note for the author's awareness.


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

@github-actions

github-actions Bot commented May 8, 2026

Copy link
Copy Markdown

🤖 Codex Code Review

No findings.

The fix is coherent with the underlying bug: mark_modified() forces exists = true, so taking the backup before mutable access in gen_db.rs and in the selfdestruct cleanup path in default_hook.rs is the right correction. Restoring exists in utils.rs also matches the EIP-7702 refund logic that reads authority_exists in the auth validation path at utils.rs.

Residual risk: the new regression test in eip7702_revert_authority_tests.rs covers the value-transfer/revert case well, but it does not directly exercise the separate selfdestruct cleanup fix in default_hook.rs. I couldn’t run the test here because the sandbox blocks writable cargo/rustup cache state and dependency fetching.


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

@github-actions

github-actions Bot commented May 8, 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 3.010 ± 0.015 2.975 3.027 1.11 ± 0.01
main_levm_BubbleSort 2.709 ± 0.016 2.681 2.730 1.00
pr_revm_BubbleSort 3.003 ± 0.020 2.981 3.047 1.11 ± 0.01
pr_levm_BubbleSort 2.710 ± 0.015 2.695 2.746 1.00 ± 0.01

Benchmark Results: ERC20Approval

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_ERC20Approval 984.0 ± 9.6 972.3 1000.1 1.00
main_levm_ERC20Approval 1033.8 ± 13.8 1023.4 1069.6 1.05 ± 0.02
pr_revm_ERC20Approval 996.9 ± 7.9 987.8 1011.8 1.01 ± 0.01
pr_levm_ERC20Approval 1047.1 ± 21.8 1030.9 1107.5 1.06 ± 0.02

Benchmark Results: ERC20Mint

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_ERC20Mint 134.1 ± 3.5 132.3 143.7 1.01 ± 0.03
main_levm_ERC20Mint 157.9 ± 2.0 156.4 162.2 1.19 ± 0.02
pr_revm_ERC20Mint 133.2 ± 0.3 132.6 133.5 1.00
pr_levm_ERC20Mint 158.4 ± 2.3 156.3 163.3 1.19 ± 0.02

Benchmark Results: ERC20Transfer

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_ERC20Transfer 231.3 ± 0.8 230.3 232.7 1.00
main_levm_ERC20Transfer 258.4 ± 1.7 256.0 260.5 1.12 ± 0.01
pr_revm_ERC20Transfer 234.7 ± 4.1 232.1 246.0 1.01 ± 0.02
pr_levm_ERC20Transfer 259.2 ± 1.8 257.3 263.7 1.12 ± 0.01

Benchmark Results: Factorial

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_Factorial 229.8 ± 1.1 228.5 231.7 1.00 ± 0.01
main_levm_Factorial 244.6 ± 1.8 242.2 248.2 1.07 ± 0.01
pr_revm_Factorial 228.7 ± 0.8 226.8 229.5 1.00
pr_levm_Factorial 243.7 ± 1.4 242.0 245.8 1.07 ± 0.01

Benchmark Results: FactorialRecursive

Command Mean [s] Min [s] Max [s] Relative
main_revm_FactorialRecursive 1.674 ± 0.034 1.608 1.731 1.10 ± 0.02
main_levm_FactorialRecursive 1.547 ± 0.016 1.518 1.570 1.01 ± 0.01
pr_revm_FactorialRecursive 1.659 ± 0.050 1.557 1.738 1.09 ± 0.03
pr_levm_FactorialRecursive 1.525 ± 0.009 1.514 1.544 1.00

Benchmark Results: Fibonacci

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_Fibonacci 208.4 ± 4.2 205.5 220.0 1.00
main_levm_Fibonacci 228.3 ± 8.3 217.9 247.4 1.10 ± 0.05
pr_revm_Fibonacci 210.1 ± 1.9 204.8 211.4 1.01 ± 0.02
pr_levm_Fibonacci 222.5 ± 5.2 216.7 230.4 1.07 ± 0.03

Benchmark Results: FibonacciRecursive

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_FibonacciRecursive 866.6 ± 9.2 855.9 884.9 1.25 ± 0.02
main_levm_FibonacciRecursive 693.1 ± 5.1 687.6 705.3 1.00
pr_revm_FibonacciRecursive 859.1 ± 9.8 847.8 874.8 1.24 ± 0.02
pr_levm_FibonacciRecursive 695.1 ± 15.1 682.9 734.9 1.00 ± 0.02

Benchmark Results: ManyHashes

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_ManyHashes 8.8 ± 0.1 8.7 8.9 1.01 ± 0.02
main_levm_ManyHashes 10.2 ± 0.1 10.1 10.5 1.17 ± 0.02
pr_revm_ManyHashes 8.7 ± 0.1 8.6 9.0 1.00
pr_levm_ManyHashes 10.1 ± 0.1 10.0 10.2 1.16 ± 0.02

Benchmark Results: MstoreBench

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_MstoreBench 271.0 ± 18.5 263.9 323.6 1.20 ± 0.08
main_levm_MstoreBench 228.0 ± 2.5 225.9 232.9 1.01 ± 0.01
pr_revm_MstoreBench 268.3 ± 6.4 263.3 281.0 1.19 ± 0.03
pr_levm_MstoreBench 226.0 ± 0.6 225.2 227.0 1.00

Benchmark Results: Push

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_Push 296.4 ± 2.2 293.8 300.0 1.09 ± 0.01
main_levm_Push 278.5 ± 2.9 276.2 284.3 1.02 ± 0.01
pr_revm_Push 295.8 ± 1.2 293.6 298.2 1.08 ± 0.01
pr_levm_Push 273.0 ± 1.5 271.1 276.0 1.00

Benchmark Results: SstoreBench_no_opt

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_SstoreBench_no_opt 165.5 ± 2.3 162.7 168.3 1.65 ± 0.03
main_levm_SstoreBench_no_opt 100.5 ± 1.1 99.5 102.1 1.00
pr_revm_SstoreBench_no_opt 167.5 ± 3.4 163.3 175.8 1.67 ± 0.04
pr_levm_SstoreBench_no_opt 101.2 ± 2.7 99.7 108.4 1.01 ± 0.03

@edg-l

edg-l commented May 11, 2026

Copy link
Copy Markdown
Contributor

Reviewed the patch and ran a merge-tree test against a long-lived branch (no conflicts). Fix is clean and the test exercises the exact path. Two follow-up observations:

1. One more raw db.get_account_mut callsite that has the same antipattern

Grepping the levm tree turns up a third caller that bypasses VM::get_account_mut:

// crates/vm/levm/src/hooks/l2_hook.rs:780
vm.db.get_account_mut(fee_token)?.storage = new_storage;

transfer_fee_token backs up storage slots via vm.backup_storage_slot(...) (lines 773-779) but never calls backup_account_info(fee_token, ...). So mark_modified flips exists=true for the fee-token account with no pre-flip backup, and on revert restore_cache_state (now with the new line) would restore an exists that was already true — practically a no-op since fee-token contracts are deployed at genesis and always pre-exist, but it's the same code smell. Worth either:

  • applying the same backup-before-flip pattern there, or
  • adding a comment justifying why the fee-token path doesn't need it.

Either way it keeps the antipattern from proliferating to a future site where the account isn't guaranteed to pre-exist.

2. Worth auditing the storage-slot side separately

The exists bug exists because mark_modified is called inside db.get_account_mut before any backup. Storage slots have an analogous risk surface: if anyone reads/writes db.get_account_mut(addr)?.storage without calling backup_storage_slot first, a revert could leak the storage write. Out of scope for this PR (which is just exists), but a quick grep for that idiom in a follow-up would be cheap insurance.

Otherwise the fix looks correct:

  • backup pre-flip + restore on revert is the right two-sided fix
  • LevmAccount now has all 5 fields (info, storage, has_storage, status, exists) consistently captured and restored
  • the regression test correctly fails without commit 2 and passes with it (verified by tracing the path through restore_cache_state)
  • one extra db.get_account lookup per VM::get_account_mut call is O(1) on FxHashMap, no perf concern

@github-project-automation github-project-automation Bot moved this to In Review in ethrex_l1 May 11, 2026
@ilitteri ilitteri added this pull request to the merge queue May 11, 2026
Merged via the queue into main with commit 0419d68 May 11, 2026
68 checks passed
@ilitteri ilitteri deleted the fix/eip7702-revert-authority-exists branch May 11, 2026 18:43
@github-project-automation github-project-automation Bot moved this from In Review to Done in ethrex_l1 May 11, 2026
dicethedev pushed a commit to dicethedev/ethrex that referenced this pull request May 12, 2026
**Motivation**

Prepare the v12.0.0 release by bumping the workspace version and merging
the release branch back to main.

**Description**

- Bump workspace version from 11.0.0 to 12.0.0 in the root `Cargo.toml`,
the guest-program `Cargo.toml` files (sp1, risc0, openvm, zisk) and
`crates/l2/tee/quote-gen/Cargo.toml`; refresh lockfiles via `make
update-cargo-lock`.
- Update the `--builder.extra-data` default in `docs/CLI.md` to `ethrex
12.0.0` in both ethrex and ethrex L2 sections.
- Merge `main` into the release branch, picking up the two levm fixes
that landed during release stabilization (lambdaclass#6591, lambdaclass#6592) along with their
tests:
- `fix(levm): account erroneously considered as existing after
zero-value transfer` (lambdaclass#6591) — avoids marking an account as modified
when a balance increase/decrease is zero.
- `fix(levm): revert doesn't unmark the account as existing` (lambdaclass#6592) —
`restore_cache_state` now restores `exists` alongside `info`, `status`,
and `has_storage` so a reverted transaction no longer leaves a stale
`exists = true` in the cache (which would cause a later EIP-7702 SetCode
tx to apply a spurious `REFUND_AUTH_PER_EXISTING_ACCOUNT` / EIP-8037
`STATE_GAS_NEW_ACCOUNT` refund). Also reorders the backup in
`VM::get_account_mut` and the SELFDESTRUCT hook so the backup captures
pre-modification state.

These fixes were also cherry-picked directly onto the release branch
before the merge; the merge from `main` adds their tests.

**Checklist**

- [x] Updated `STORE_SCHEMA_VERSION` — N/A, no storage schema change.

---------

Co-authored-by: Lucas Fiegl <iovoid@users.noreply.github.com>
edg-l pushed a commit that referenced this pull request May 13, 2026
**Motivation**

When an account creation reverts, we incorrectly mark it as existing for
the purposes of EIP7702, leading to charging gas incorrectly.

**Description**

Currently we track accounts that exist in the trie for purposes of
charging EIP7702 gas (the price is different if the authority already
exists in the trie). This is saved in the `exists` field of the account,
which is currently not saved in the callframe backup.

This means when an account creation is reverted, we don't unmark it as
existing. This causes an incorrect amount of gas to be charged.

The PR contains a regression test, that should fail on the first commit
and pass on the second.
edg-l pushed a commit that referenced this pull request May 13, 2026
**Motivation**

When an account creation reverts, we incorrectly mark it as existing for
the purposes of EIP7702, leading to charging gas incorrectly.

**Description**

Currently we track accounts that exist in the trie for purposes of
charging EIP7702 gas (the price is different if the authority already
exists in the trie). This is saved in the `exists` field of the account,
which is currently not saved in the callframe backup.

This means when an account creation is reverted, we don't unmark it as
existing. This causes an incorrect amount of gas to be charged.

The PR contains a regression test, that should fail on the first commit
and pass on the second.
edg-l pushed a commit that referenced this pull request May 13, 2026
**Motivation**

When an account creation reverts, we incorrectly mark it as existing for
the purposes of EIP7702, leading to charging gas incorrectly.

**Description**

Currently we track accounts that exist in the trie for purposes of
charging EIP7702 gas (the price is different if the authority already
exists in the trie). This is saved in the `exists` field of the account,
which is currently not saved in the callframe backup.

This means when an account creation is reverted, we don't unmark it as
existing. This causes an incorrect amount of gas to be charged.

The PR contains a regression test, that should fail on the first commit
and pass on the second.
akshay-ap pushed a commit to akshay-ap/ethrex that referenced this pull request May 19, 2026
**Motivation**

Bring ethrex up to bal-devnet-7 (BAL fixtures `bal@v7.1.1`). Stacked
on top of #bal-devnet-6-pr (now in main).

**Description**

Aligns EIP-8037 state-gas accounting with bal-devnet-7 spec progression
(EELS PRs lambdaclass#2815 / lambdaclass#2816 / lambdaclass#2823 / lambdaclass#2827 / lambdaclass#2828 / lambdaclass#2836 / lambdaclass#2845 /
lambdaclass#2848),
bumps Amsterdam fixtures from `snobal-devnet-6@v1.1.0` to `bal@v7.1.1`,
and bumps the pinned hive version past the ethrex `--http.api` fix.

Main changes:

- EIP-8037 state-gas alignment with bal-devnet-7:
  - System-call state-gas reservoir.
  - Halt refunds spilled state gas (Policy A).
  - Tx-level CREATE failure refunds intrinsic `NEW_ACCOUNT`;
    `intrinsic_state_gas_charged` preserved across the failure path.
  - Tx-CREATE collision refund with regular-gas burn; billing matches
    EELS.
  - Cross-frame revert leaks inline credits.
  - Cross-frame revert reservoir formula fix.
  - Block-level `state_gas_used` subtracts `state_refund`.
- Remove same-tx SELFDESTRUCT state-gas refund (EELS PR lambdaclass#2845, v7.1.0).
- EIP-7702:
  - `set_delegation` refund via dedicated `state_refund` channel.
  - `set_delegation` refunds `AUTH_BASE` on existing delegation
    (EELS PR lambdaclass#2836).
  - `set_delegation` refunds `AUTH_BASE` on delegation clear
    (EELS PR lambdaclass#2848, v7.1.1).
- levm fixes pulled from main:
  - `revert` doesn't unmark the account as existing (lambdaclass#6592).
  - Account erroneously considered as existing after zero-value transfer
    (lambdaclass#6591).
- Tooling / tests:
  - Per-tx gas-dimension dump on block `gas_used` mismatch.
  - Bump Amsterdam fixtures to `bal@v7.1.1`.
  - Annotate BAL balance-mismatch errors with gas-equivalent diff and
    recognised state-gas constant multiples.
  - Unskip 74 bal-devnet-6 Amsterdam fixtures now passing.
  - Skip 21 stale EIP-8025 fixtures pinned at `bal@v5.7.0`
    (zkevm@v0.3.3 bundle, pre-bal-7).
  - Drop stale bal-devnet-6 known-issues entries from
    `docs/known_issues.md` and hive `KNOWN_EXCLUDED_TESTS`.
- CI:
  - Bump pinned hive version past the ethrex `--http.api` flag
    feature-detect fix (`c4d839b3`, hive lambdaclass#1485). Without this, hive
    starts ethrex with the default HTTP namespace allowlist
    (`eth,net,web3`) and tests touching `admin_*`/`debug_*`/`txpool_*`
    fail.

**Local test run**

`./run_test.sh` against `tests-bal@v7.1.1`: 2,145 / 2,145 pass.
`cargo test -p ethrex-test --tests`: 453 / 453 pass.

**Checklist**

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

---------

Co-authored-by: Lucas Fiegl <iovoid@users.noreply.github.com>
Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

levm Lambda EVM implementation

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

5 participants