Skip to content

fix: always include storage values for SLOAD and SSTORE in GethTrace#389

Merged
mattsse merged 6 commits into
paradigmxyz:mainfrom
Wodann:fix/geth-default-debug-tracer
Jan 20, 2026
Merged

fix: always include storage values for SLOAD and SSTORE in GethTrace#389
mattsse merged 6 commits into
paradigmxyz:mainfrom
Wodann:fix/geth-default-debug-tracer

Conversation

@Wodann
Copy link
Copy Markdown
Contributor

@Wodann Wodann commented Dec 20, 2025

The default tracer for debug_* RPC methods was not reporting warmed storage values. This deviates from Get's implementation and—seemingly—the documented expectation:

/// Represents a storage change during execution.
///
/// This maps to evm internals:
/// [JournalEntry::StorageChanged](revm::JournalEntry::StorageChanged)
///
/// It is used to track both storage change and warm load of a storage slot. For warm load in regard
/// to EIP-2929 AccessList had_value will be None.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct StorageChange {
    /// key of the storage slot
    pub key: U256,
    /// Current value of the storage slot
    pub value: U256,
    /// The previous value of the storage slot, if any
    pub had_value: Option<U256>,
    /// How this storage was accessed
    pub reason: StorageChangeReason,
}

In particular, the SLOAD for pc 235, 279, and 308 are missing "storage". This PR resolves the issue.

For reference:

revm-inspectors.json
geth.json

Copy link
Copy Markdown
Contributor

@mattsse mattsse left a comment

Choose a reason for hiding this comment

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

one question re the removed journal change check

Comment thread src/tracing/mod.rs
let reason = match op {
opcode::SLOAD => StorageChangeReason::SLOAD,
opcode::SSTORE => StorageChangeReason::SSTORE,
_ => unreachable!(),
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.

I'm not sure if this is still unreachable now

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

if matches!(op, opcode::SLOAD | opcode::SSTORE) on line 540 guarantees this.

Comment thread src/tracing/mod.rs
Comment thread src/tracing/mod.rs
Copy link
Copy Markdown
Contributor

@mattsse mattsse left a comment

Choose a reason for hiding this comment

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

oh I get it now, ty

I think we need to update the snapshots now

@Wodann
Copy link
Copy Markdown
Contributor Author

Wodann commented Dec 22, 2025

oh I get it now, ty

I think we need to update the snapshots now

I incorporated your feedback and split the solution in two parts:

  1. Fix "state changes" to ensure that we log the first "storage warming"
  2. Fix the GethTrace generation by inserting the complete storage state for SLOAD and SSTORE

For part 2, my understanding of geth's code is that we need to add the contract address's complete list of storage changes up to now:

	// Copy a snapshot of the current storage to a new container
	var storage Storage
	if !l.cfg.DisableStorage && (op == vm.SLOAD || op == vm.SSTORE) {
		// initialise new changed values storage container for this contract
		// if not present.
		if l.storage[contractAddr] == nil {
			l.storage[contractAddr] = make(Storage)
		}
		// capture SLOAD opcodes and record the read entry in the local storage
		if op == vm.SLOAD && stackLen >= 1 {
			var (
				address = common.Hash(stack[stackLen-1].Bytes32())
				value   = l.env.StateDB.GetState(contractAddr, address)
			)
			l.storage[contractAddr][address] = value
			storage = maps.Clone(l.storage[contractAddr]) // <-- FULL CONTRACT STORAGE IS CLONED
		} else if op == vm.SSTORE && stackLen >= 2 {
			// capture SSTORE opcodes and record the written entry in the local storage.
			var (
				value   = common.Hash(stack[stackLen-2].Bytes32())
				address = common.Hash(stack[stackLen-1].Bytes32())
			)
			l.storage[contractAddr][address] = value
			storage = maps.Clone(l.storage[contractAddr]) // <-- FULL CONTRACT STORAGE IS CLONED
		}
	}
	log.Storage = storage

Should I add more tests? If so, is there a test format that I can easily extend to generate some tests with SLOAD and SSTORE?

@Wodann
Copy link
Copy Markdown
Contributor Author

Wodann commented Dec 22, 2025

I updated the snapshot writer logic to handle warmed storage slots. This also uncovered a (seemingly) erroneous "changed" error in some of the old snapshots. I stepped through all of the examples and I believe that the new values are in fact correct

Addressed by 7ce652d

Please let me know whether the update snapshot format is acceptable.

Comment thread src/tracing/writer.rs Outdated
}
})
.or_insert_with(|| {
if let Some(had_value) = change.had_value {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The Option in had_value is used to signal whether this was a "warm" or a "change", as per the docs:

/// Represents a storage change during execution.
///
/// This maps to evm internals:
/// [JournalEntry::StorageChanged](revm::JournalEntry::StorageChanged)
///
/// It is used to track both storage change and warm load of a storage slot. For warm load in regard
/// to EIP-2929 AccessList had_value will be None.

Comment thread src/tracing/writer.rs Outdated
value_after = num_or_hex(value_after),
)?;
match change {
StorageChange::Changed { value, had_value } => {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This maintains the old format

Comment thread src/tracing/writer.rs Outdated
value_after = num_or_hex(value),
)?;
}
StorageChange::Warmed { value } => {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This introduces a new format for "warmed" storage slots.

I explicitly didn't use the same format as changes, since it's possible to warm a storage slot that's set to zero. That would result in a confusing "0 → 0"

│ └─ ← ret1, ret2
├─ storage changes:
│ @ 0: 70 → 71
│ @ 0: 69 → 71
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I believe this was an error in the old logic.

Test 7 is a call to nest1:

function nest1() public {
    emit Log1(number, "hi from 1");
    this.nest2();
    increment();
}

function nest2() public {
    increment();
    this.nest3();
    emit Log2(number, 123, "hi from 2");
}

Prior to the call, number (storage slot: 0) is 69, due to call(Counter::setNumberCall { newNumber: U256::from(69) }.abi_encode());

  1. nest1 calls nest2
  2. nest2 call increment (69 → 70)
  3. nest1 calls increment (70 → 71)

@Wodann Wodann changed the title fix: warmed storage values for GethTrace using DebugInspector fix: always include changed storage values for SLOAD and SSTORE in GethTrace using DebugInspector Dec 22, 2025
@Wodann Wodann changed the title fix: always include changed storage values for SLOAD and SSTORE in GethTrace using DebugInspector fix: always include storage values for SLOAD and SSTORE in GethTrace using DebugInspector Dec 22, 2025
@Wodann Wodann changed the title fix: always include storage values for SLOAD and SSTORE in GethTrace using DebugInspector fix: always include storage values for SLOAD and SSTORE in GethTrace Dec 22, 2025
@Wodann Wodann requested a review from mattsse December 22, 2025 19:50
Copy link
Copy Markdown
Contributor

@mattsse mattsse left a comment

Choose a reason for hiding this comment

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

unsure if this should be printed like this @DaniPopes

imo having

│ @ 0: 0

isnt that useful, perhaps even wrong, because nothing changed?

Comment thread src/tracing/writer.rs
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.

should we keep all of this as it was and simply filter for changed values ad exclude warmed? I feel like this is separate from the fix

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Reported changes now contain storage warming too, so I'll need to handle it somehow. I can filter out changes with had_value.is_none() which should always and only be true for storage warming. That leaves the snapshots as-is.

Would you prefer that?

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.

I'd prefer that yes

we can still make a decision on the writer later

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I reverted my changes and instead ignored the storage warming "storage changes".

@Wodann
Copy link
Copy Markdown
Contributor Author

Wodann commented Jan 8, 2026

@mattsse is there anything else I need to do before we can consider this for merging?

@mattsse mattsse merged commit 62f29b8 into paradigmxyz:main Jan 20, 2026
12 checks passed
asdv23 added a commit to mantle-xyz/revm-inspectors that referenced this pull request May 11, 2026
…#9)

* chore: bump revm 30.0.2 (paradigmxyz#379)

bump patch

* chore: bump revm 30.0.2 (paradigmxyz#380)

* chore: bump to revm 33 (paradigmxyz#381)

## Motivation

<!--
Explain the context and why you're making that change. What is the
problem
you're trying to solve? In some cases there is not a problem and this
can be
thought of as being the motivation for your change.
-->

Per: https://github.com/bluealloy/revm/releases/tag/v100

* chore: release 0.33.0

* feat: Fix geth prestate filtering for prefunded creations (paradigmxyz#383)

This aims at resolving issues mentioned in paradigmxyz#382 , typically this one :
<img width="1818" height="694" alt="image"
src="https://github.com/user-attachments/assets/51b82944-c711-421b-8479-05095ae9bbce"
/>

To fix this , what i did was : 
- Keep track of addresses that are marked as Create and were truly empty
in the database snapshot. Only these addresses are filtered from the
prestate diff so prefunded contract deployments remain visible.

- Introduce account_was_empty helper to avoid re-computing the emptiness
check and add a regression test
(prestate_diff_keeps_prefunded_created_accounts) that covers a prefunded
creation vs. a true empty-address creation.

cc @mattsse  Please check it out if it resolves 

should close this one paradigmxyz/reth#19703

---------

Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>

* feat: upstream DebugInspector from reth (paradigmxyz#384)

Moves `DebugInspector` from reth to revm-inspectors as requested in
paradigmxyz/reth#19932.

This was introduced in paradigmxyz/reth#19925 to unify debug tracing
across all Geth tracer types.

* chore: release 0.33.1

* test: add test for string error decode (paradigmxyz#386)

* fix: use pre code if hash matches (paradigmxyz#387)

this fixes an issue where we would return diffs like

```
                "post": {
                    "0x093f6c270ac22ec240f0c6fd7414ea774ca8d3e5": {},
                    "0x2641c2ded63a0c640629f5edf1189e0f53c06561": {},
```

for `0x91b066df39661db7bcca1cb8bb8afd11816414408d44cbfcf6144f440d5dfe3b`
ref paradigmxyz/reth#19703

however nothing changed in the account here and these should have been
filtered out via:


https://github.com/paradigmxyz/revm-inspectors/blob/f7503520888efc11eac3142fe7d611c519811b25/src/tracing/builder/geth.rs#L349-L349

and cleared from post via:


https://github.com/alloy-rs/alloy/blob/35e20437670d987680a6715bcc173f3d357f8254/crates/rpc-types-trace/src/geth/pre_state.rs#L188-L201

the reason why this didn't work is because:

the code can be None here which would make retain_change not remove the
values

```
          "result": {
                "post": {
                    "0x093f6c270ac22ec240f0c6fd7414ea774ca8d3e5": {},
                    "0x2641c2ded63a0c640629f5edf1189e0f53c06561": {},
                    "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": {
                        "balance": "0xc87826afc2b88254"
                    },
                    "0xbf5495efe5db9ce00f80364c8b423567e58d2110": {
                        "storage": {
                            "0x0000000000000000000000000000000000000000000000000000000000000035": "0x000000000000000000000000000000000000000000004b7b53b759cac06bc3b2",
                            "0xec8c572bc2e66bfe14376dece39002d6d97d311020c59bc23a93016013e92439": "0x0000000000000000000000000000000000000000000000000f294a8cb09ad9cb"
                        }
                    },
                    "0xbfe474605fb7f1cf3693a3f21af2e329f7222462": {
                        "balance": "0x269f4a55a240465",
                        "nonce": 5
                    },
                    "0xf2f305d14dcd8aaef887e0428b3c9534795d0d60": {
                        "balance": "0xe984e9959d534d3cdf"
                    }
                },
                "pre": {
                    "0x093f6c270ac22ec240f0c6fd7414ea774ca8d3e5": {
                        "balance": "0x1ea3abe47d76b5e00",
                        "code": "..",
                        "nonce": 1
                    },
                    "0x2641c2ded63a0c640629f5edf1189e0f53c06561": {
                        "balance": "0x1cb3ca6ff6dc41400",
                        "code": "..",
                        "nonce": 1
                    },
                    "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": {
                        "balance": "0xc8781d351add1146",
                        "nonce": 797368
                    },
                    "0xbf5495efe5db9ce00f80364c8b423567e58d2110": {
                        "balance": "0x0",
                        "code": "...",
                        "nonce": 1,
                        "storage": {
                            "0x0000000000000000000000000000000000000000000000000000000000000035": "0x000000000000000000000000000000000000000000004b7b448e0f3e0fd0e9e7"
                        }
                    },
                    "0xbfe474605fb7f1cf3693a3f21af2e329f7222462": {
                        "balance": "0x11d7cc57700ae2ed",
                        "nonce": 4
                    },
                    "0xf2f305d14dcd8aaef887e0428b3c9534795d0d60": {
                        "balance": "0xe975a599714e5f3cdf",
                        "code": "...",
                        "nonce": 1
                    }
                }
            },

```

we can always use the precode here if the hashes match

* chore: release 0.33.2

* feat: add support for erc7562 tracer (paradigmxyz#392)

This aims to resolve this issue :
paradigmxyz#391

cc @mattsse

* feat: stage revm to 34.0.0 (paradigmxyz#385)

stage latest main commit

---------

Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>

* chore: release 0.34.0

* ci: update to tempoxyz (paradigmxyz#395)

* fix: always include storage values for `SLOAD` and `SSTORE` in `GethTrace` (paradigmxyz#389)

The default tracer for `debug_*` RPC methods was not reporting warmed
storage values. This deviates from Get's implementation
and—seemingly—the documented expectation:

```rs
/// Represents a storage change during execution.
///
/// This maps to evm internals:
/// [JournalEntry::StorageChanged](revm::JournalEntry::StorageChanged)
///
/// It is used to track both storage change and warm load of a storage slot. For warm load in regard
/// to EIP-2929 AccessList had_value will be None.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct StorageChange {
    /// key of the storage slot
    pub key: U256,
    /// Current value of the storage slot
    pub value: U256,
    /// The previous value of the storage slot, if any
    pub had_value: Option<U256>,
    /// How this storage was accessed
    pub reason: StorageChangeReason,
}
```

In particular, the `SLOAD` for pc `235`, `279`, and `308` are missing
`"storage"`. This PR resolves the issue.


For reference:


[revm-inspectors.json](https://github.com/user-attachments/files/24275105/revm-inspectors.json)

[geth.json](https://github.com/user-attachments/files/24275106/geth.json)

* test: add prestate replay testing infrastructure (paradigmxyz#399)

Add reusable tooling for debugging RPC tracing using prestate tracer
responses captured from mainnet transactions.

## Changes

- Add `tests/it/repro.rs` with:
  - `ReproTestFixture`/`TxData` types for deserializing JSON fixtures
  - `spec_id_from_ethereum_hardfork`/`spec_id_from_block` helpers
  - `build_db_from_prestate` to populate CacheDB from AccountState
  - `ReproContext` builder for easy test setup

- Add `testdata/repro/tx-selfdestruct.json` fixture from reth's e2e test
  (mainnet tx 0x391f4b6a...selfdestruct via USDT withdrawal)

- Add `alloy-hardforks` dev dependency for block-to-hardfork mapping

## Usage

```rust
const FIXTURE: &str = include_str!("../../testdata/repro/my-fixture.json");

#[test]
fn test_my_trace() {
    let ctx = ReproContext::load(FIXTURE);
    let config = PreStateConfig::default();

    let mut inspector = TracingInspector::new(
        TracingInspectorConfig::from_geth_prestate_config(&config)
    );

    let db = ctx.db.clone();
    let mut evm = Context::mainnet()
        .with_db(ctx.db.clone())
        .modify_cfg_chained(|cfg| cfg.spec = ctx.spec_id)
        .build_mainnet()
        .with_inspector(&mut inspector);

    let res = evm.inspect_tx(ctx.tx_env()).unwrap();
    // ... assertions on trace results
}
```

This enables writing repro tests with the flow:
**raw transaction + prestate JSON → configure tracer → run → assert**

Refs:
https://github.com/paradigmxyz/reth/blob/main/crates/ethereum/node/tests/e2e/prestate.rs

---------

Co-authored-by: Amp <amp@ampcode.com>

* fix: use Default::default() for TransactionInfo for forward compatibility (paradigmxyz#401)

Use `..Default::default()` pattern to ensure forward compatibility when
new fields are added to `TransactionInfo`.

This prepares the crate for upcoming changes to add a `block_timestamp`
field.

Related: ethereum/execution-apis#729

Co-authored-by: Amp <amp@ampcode.com>

* chore: release 0.34.1

* feat: expose refund_counter in `CallTrace` (paradigmxyz#402)

Required to be able to recover pre-refund totals from purely just the
`CallTrace` for gas introspection in foundry.
foundry-rs/foundry#13262

* chore: release 0.34.2

* chore: bump MSRV to 1.91 (paradigmxyz#407)

Bump MSRV from 1.86 to 1.91.

* feat: implement Clone for DebugInspector (paradigmxyz#406)

Implement `Clone` for `DebugInspector` to enable use with `TxTracer`,
which requires `Inspector: Clone`.

Non-JS variants clone trivially. The `Js` variant reconstructs the
`JsInspector` from its stored `code` and `config` — same approach
`fuse()` already uses.

* devnet2-revm: bump revm patch for release procedure (paradigmxyz#408)

## Summary
This PR is part of the **revm release procedure** (`devnet2-revm`
chain).

It bumps revm-inspectors to revm v35.0.0.

## Changes
- Bump `revm` dependency from 34.0.0 to 35.0.0 in `Cargo.toml`.
- Adapt to upstream API change: `journal().precompile_addresses()` now
returns a type that requires `.iter().copied().collect()` instead of
`.clone()`.
- Lower `gas_limit` in selfdestruct tests from 100M to 10M to comply
with EIP-7825 transaction gas limit cap enforced in revm 35.

## Validation
- `cargo check`
- `cargo +nightly clippy --workspace --all-targets --all-features`

## Procedure Context
This is an intermediate dependency bump in the revm release flow before
downstream updates in `alloy-evm` and `reth`.

* chore: release 0.35.0

* chore: bump revm to 36.0.0 (paradigmxyz#409)

## Summary
- Bump `revm` dependency from 35.0.0 to 36.0.0

## Test plan
- [x] `cargo check` passes
- [x] `cargo test` passes (all 37 tests)

* chore: release 0.36.0

* fix: omit empty returnData in geth struct log trace (paradigmxyz#411)

## Summary

Only include `returnData` in structLog output when the return-data
buffer is non-empty, matching geth's behavior.

## Motivation

[ethpandaops trace comparison
report](https://investigations.ethpandaops.io/2026-01/trace-comparisons/reth/)
shows reth emitting `returnData: "0x"` on **every** structLog step
(~1.1M occurrences across 500 txs), while geth/erigon/nethermind/besu
all omit it.

Geth applies two independent gates before including `returnData`:
1. `cfg.EnableReturnData == true` (opt-in config) — we had this
2. `len(ReturnData) > 0` in `toLegacyJSON` — we were missing this

## Changes

- `src/tracing/builder/geth.rs`: add `!step.returndata.is_empty()` check
alongside the existing `opts.is_return_data_enabled()` gate

## Testing

`cargo test --workspace` — all 24 tests pass, clippy clean.

Co-Authored-By: Matthias Seitz
<19890894+mattsse@users.noreply.github.com>

Prompted by: mattsse

Co-authored-by: Matthias Seitz <19890894+mattsse@users.noreply.github.com>

* feat: add set_transaction_caller and set_transaction_target (paradigmxyz#412)

Adds `set_transaction_caller` and `set_transaction_target` to
`TracingInspector`, following the same pattern as
`set_transaction_gas_used` / `set_transaction_gas_limit`.

Custom transaction types (e.g. AA batches) may execute via a multi-call
handler where the EVM's `call()` entry point doesn't reflect the actual
transaction sender/recipient. This causes `callTracer` to report zeroed
`from`/`to` on the root frame. These methods allow the execution layer
to correct the root trace after execution.

Co-Authored-By: zhygis <5236121+Zygimantass@users.noreply.github.com>

Prompted by: zygis

---------

Co-authored-by: zhygis <5236121+Zygimantass@users.noreply.github.com>
Co-authored-by: Arsenii Kulikov <klkvrr@gmail.com>

* chore: release 0.36.1

* chore: relax `CTX` bounds on `StorageInspector` `Inspector` impl (paradigmxyz#416)

Relax `CTX` bounds on `StorageInspector` `Inspector` impl as it doesn't
use `Context`.

* fix: eip-7708 logs (paradigmxyz#413)

CallLog discarded the original Log.address during conversion and
geth_empty_call_frame() reconstructed it from execution_address(). This
is wrong for any log whose emitter differs from the call frame's
execution context — most notably EIP-7708 system-level ETH transfer
logs, which are emitted from SYSTEM_ADDRESS
(0xfffffffffffffffffffffffffffffffffffffffe) rather than the executing
contract.

Changes

src/tracing/types.rs
- Add address: Address field to CallLog to preserve the original log
emitter
- Update From<Log> to store log.address instead of discarding it
- Fix geth_empty_call_frame() to use log.address instead of
self.execution_address()

tests/it/geth.rs
- test_geth_calltracer_logs_eip7708: value-transferring CALL to a
precompile under AMSTERDAM — verifies the log address is
ETH_TRANSFER_LOG_ADDRESS, not the contract or precompile address
- test_geth_calltracer_logs_address_regular: regular LOG0 emission —
verifies the log address is the emitting contract
- test_geth_calltracer_logs_delegatecall: DELEGATECALL regression —
verifies the log address is the proxy (execution context), not the
implementation (bytecode) address

Breaking

CallLog is a public struct with a new address: Address field. Downstream
consumers that construct CallLog manually will need to provide this
field.

Co-authored-by: Kai Yu <kai.yu@Kai-Yu-MacBook-Pro.local>

* chore: bump alloy to 2.0 (paradigmxyz#421)

Bump alloy dependencies to released 2.0.0. No `[patch.crates-io]`
needed.

Supersedes paradigmxyz#419

* chore: release 0.37.0

* tip1016: revm state-gas integration (paradigmxyz#405)

## Summary
- Update revm rev to `e3223d5b38f7affb901601ed917fa7cdc466be74`
(rakita/state-gas branch) for TIP-1016 dual-limit gas accounting

## Test plan
- [x] `cargo check` compiles clean
- [x] `cargo +nightly clippy` passes
- [x] `cargo nextest run` — all 37 tests pass

---------

Co-authored-by: Federico Gimenez <federico.gimenez@gmail.com>

* chore: release 0.38.0

* fix: check if opcode is valid (paradigmxyz#422)

TracingInspector creates OpCodes via `new_unchecked` at `start_step` to
handle unknown/custom opcodes. In `step_end`, `step.op.outputs()` calls
`OpCode::info()` which panics with `"opcode not found"` when the opcode
byte has no entry in `OPCODE_INFO`.

Guard with `is_valid()` — unknown opcodes default to 0 outputs.

Prompted by: Dragan

---------

Co-authored-by: rakita <13179220+rakita@users.noreply.github.com>
Co-authored-by: rakita <rakita@users.noreply.github.com>

* chore: release 0.38.1

* fix(tracing): always include refund counter, wire enableReturnData config (paradigmxyz#424)

Two fixes based on the [ethpandaops trace
comparison](https://investigations.ethpandaops.io/2026-01/trace-comparisons/):

1. Always include the refund counter in struct logs, even when zero.
Previously omitted when value was 0, causing missing fields in trace
output.

2. Wire `enableReturnData` from `GethDefaultTracingOptions` through to
`TracingInspectorConfig::record_returndata_snapshots`. Previously this
option was ignored, so returnData was never captured even when
explicitly requested.

Verified on a live reth archive node:
- Refund: 22,476/67,028 steps → **67,028/67,028** steps
- returnData with `enableReturnData: true`: 0 steps → **24,537** steps

* fix(tracing): align memory encoding and returnData gating (paradigmxyz#425)

Two fixes to align with the [opcode tracer
spec](ethereum/execution-apis#762):

1. Memory chunks now use `0x`-prefixed `bytes32` encoding. Previously
`convert_memory()` used bare `hex::encode` without the prefix.

2. `returnData` is now included on every step when `enableReturnData` is
true, even when the buffer is empty. Previously skipped when empty.

---------

Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>

* chore: bump revm to 38.0.0 (paradigmxyz#427)

## Summary
- Bumps `revm` from `37.0.0` to `38.0.0`.

## Test plan
- [ ] `cargo build` passes
- [ ] `cargo test` passes
- [ ] CI green

* chore: release 0.39.0

* fix: use .values() instead of iter with unused key to satisfy clippy

* Revert "fix: use .values() instead of iter with unused key to satisfy clippy"

This reverts commit d7d6d46.

* Reapply "fix: use .values() instead of iter with unused key to satisfy clippy"

This reverts commit 43a426e.

---------

Co-authored-by: rakita <rakita@users.noreply.github.com>
Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com>
Co-authored-by: Arsenii Kulikov <klkvrr@gmail.com>
Co-authored-by: Karl Yu <43113774+0xKarl98@users.noreply.github.com>
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
Co-authored-by: Alex Pikme <30472093+reject-i@users.noreply.github.com>
Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com>
Co-authored-by: Wodann <Wodann@users.noreply.github.com>
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: Philippe Dumonet <philippe@dumo.net>
Co-authored-by: stevencartavia <112043913+stevencartavia@users.noreply.github.com>
Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com>
Co-authored-by: Matthias Seitz <19890894+mattsse@users.noreply.github.com>
Co-authored-by: zhygis <5236121+Zygimantass@users.noreply.github.com>
Co-authored-by: Mablr <59505383+mablr@users.noreply.github.com>
Co-authored-by: Kai Yu <kai.yu@circle.com>
Co-authored-by: Kai Yu <kai.yu@Kai-Yu-MacBook-Pro.local>
Co-authored-by: Federico Gimenez <federico.gimenez@gmail.com>
Co-authored-by: rakita <13179220+rakita@users.noreply.github.com>
Co-authored-by: figtracer <me@figtracer.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