Skip to content

fix(erc7562): enable state diffs to capture SLOAD in accessedSlots.reads#451

Merged
mattsse merged 1 commit into
paradigmxyz:mainfrom
stephancill:fix-erc7562-storage-reads-main
Jun 1, 2026
Merged

fix(erc7562): enable state diffs to capture SLOAD in accessedSlots.reads#451
mattsse merged 1 commit into
paradigmxyz:mainfrom
stephancill:fix-erc7562-storage-reads-main

Conversation

@stephancill
Copy link
Copy Markdown
Contributor

@stephancill stephancill commented Jun 1, 2026

The erc7562Tracer config starts from Self::none() which sets record_state_diff: false. This causes SLOAD operations to not populate accessedSlots.reads because the SLOAD handler in geth_erc7562_traces() depends on step.storage_change, which is only set when record_state_diff is enabled.

Problem

// geth_erc7562_traces() — SLOAD handler (builder/geth.rs:427)
opcode::SLOAD => {
    if let Some(stack) = &step.stack {
        if let Some(slot) = stack.get(stack.len().saturating_sub(1)) {
            if !already_read && !already_written {
                if let Some(change) = &step.storage_change {  // <-- always None
                    accessed_slots.reads.entry(slot).or_default().push(value);
                }
            }
        }
    }
}

step.storage_change is only populated in step_end() when self.config.record_state_diff is true:

// tracing/mod.rs:554
step.storage_change = if matches!(op, opcode::SLOAD | opcode::SSTORE) {
    // only reached when record_state_diff is true

But from_geth_erc7562_config() never enables it:

pub fn from_geth_erc7562_config(config: &Erc7562Config) -> Self {
    Self::none()                         // record_state_diff: false
        .set_record_logs(...)
        .set_memory_snapshots(true)
        .set_stack_snapshots(StackSnapshotType::Full)
        .steps()                         // missing .set_state_diffs(true)
}

Steps to reproduce

  1. Start a reth node and deploy a contract that uses SLOAD (e.g. a counter with get() and increment()):
contract Counter {
    uint256 public count;
    function increment() external { count += 1; }
}
  1. Call increment() a few times so slot 0 has a non-zero value.

  2. Trace an increment() call with the erc7562Tracer:

curl -X POST http://localhost:8545 -H "Content-Type: application/json" -d '{ 
  "jsonrpc": "2.0", "id": 1,
  "method": "debug_traceCall",
  "params": [{
    "to": "<counter_address>",
    "data": "0xd09de08a"
  }, "latest", {"tracer": "erc7562Tracer"}]
}'
  1. Observe that accessedSlots.reads is empty even though increment() does SLOAD on slot 0 before SSTORE:
{
  "accessedSlots": {
    "reads": {},
    "writes": {"0x00..00": 1}
  }
}

Expected: reads should contain {"0x00..00": ["<current_value>"]} because the EVM performs an SLOAD before the SSTORE in count += 1.

Actual: reads is always empty.

Note: geth's implementation of the same tracer correctly populates accessedSlots.reads.

Impact

ERC-4337 bundlers (e.g. Rundler) need (slot, initial_value) pairs from SLOAD to enforce ERC-7562 storage access rules ([STO-010], [STO-031], etc.). Without this fix, the native erc7562Tracer cannot replace custom JS tracers, which are 4-100x slower due to per-opcode JS interpretation.

Fix

One line: .set_state_diffs(true) in from_geth_erc7562_config().

@stephancill stephancill force-pushed the fix-erc7562-storage-reads-main branch from 9615c8b to 256508a Compare June 1, 2026 20:21
The erc7562Tracer config starts from `Self::none()` which sets
`record_state_diff: false`. This causes SLOAD operations to not
populate `accessedSlots.reads` because the SLOAD handler in
`geth_erc7562_traces()` depends on `step.storage_change`, which
is only set when `record_state_diff` is enabled.

Without this fix, `accessedSlots.reads` is always empty, making
the erc7562Tracer unusable for ERC-4337 bundlers that need storage
read tracking to enforce ERC-7562 storage access rules.

Note: geth's implementation of the same tracer correctly tracks
storage reads.
@stephancill stephancill force-pushed the fix-erc7562-storage-reads-main branch from 256508a to affd2d5 Compare June 1, 2026 20:23
@mattsse mattsse merged commit 3895078 into paradigmxyz:main Jun 1, 2026
12 checks passed
stephancill added a commit to stephancill/base that referenced this pull request Jun 1, 2026
Bumps revm-inspectors from 0.34.3 to 0.34.4 which includes the fix
for accessedSlots.reads being empty in the erc7562Tracer.

Release: https://github.com/paradigmxyz/revm-inspectors/commits/v0.34.4/
Fix: paradigmxyz/revm-inspectors#451
stephancill added a commit to stephancill/base that referenced this pull request Jun 1, 2026
Bumps revm-inspectors from 0.34.3 to 0.34.4 which includes the fix
for accessedSlots.reads being empty in the erc7562Tracer.

Release: https://github.com/paradigmxyz/revm-inspectors/commits/v0.34.4/
Fix: paradigmxyz/revm-inspectors#451
stephancill added a commit to base/base that referenced this pull request Jun 1, 2026
…3131)

Bumps revm-inspectors from 0.34.3 to 0.34.4 which includes the fix
for accessedSlots.reads being empty in the erc7562Tracer.

Release: https://github.com/paradigmxyz/revm-inspectors/commits/v0.34.4/
Fix: paradigmxyz/revm-inspectors#451

Co-authored-by: Stephan Cilliers <stephancill@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants