Skip to content

refactor: defer cache materialization for access-list touches#601

Open
antoniolocascio wants to merge 11 commits into
draft-0.4.0from
alocascio-storage-touch-refactor
Open

refactor: defer cache materialization for access-list touches#601
antoniolocascio wants to merge 11 commits into
draft-0.4.0from
alocascio-storage-touch-refactor

Conversation

@antoniolocascio
Copy link
Copy Markdown
Contributor

@antoniolocascio antoniolocascio commented Mar 30, 2026

What ❔

This PR defers cache materialization for EIP-2930 access-list touches in both storage and account caches.

It changes the touch paths to:

  • record warm, unobserved placeholders instead of immediately querying oracle-backed state
  • materialize storage slots and account data only when a real read/write path needs the value
  • move access-list charging into the lower storage/account cache layers instead of precharging in the bootloader
  • preserve iterator, persistence, and deconstruction behavior by continuing to skip unobserved entries until they are materialized

It also adds focused tests for:

  • touched-only storage slots staying hidden from iterators
  • touched storage slots materializing on first read
  • touched accounts staying unobserved until materialization
  • low-level CacheRecord / HistoryMap invariants used by the lazy materialization flow

Why ❔

The goal is to make access-list warming actually lazy.

Before this change, access-list touches could force unnecessary materialization and oracle IO even when execution never observed the touched slot/account value. That was inconsistent with geth execution witness, blocking ethproofs integration.

This change keeps the warmness/accounting semantics, avoids unnecessary reads, preserves the persistence invariant that observed state is materialized before it is consumed, and keeps the flat-model native charging path independent of cache status.

Is this a breaking change?

  • Yes
  • No

Checklist

  • PR title corresponds to the body of PR (we generate changelog entries from PRs).
  • Tests for the changes have been added / updated.
  • Documentation comments have been added / updated.
  • Code has been formatted.

antoniolocascio and others added 4 commits March 25, 2026 14:25
## What ❔

Reimplement the hints for ecrecover from ethproofs.
Instead of having a new full implementation of ecrecover, this PR
introduces "hooks" for 3 secp256k1 field operations. These hooks have
two implmentations: default, where the implementation is straightforward
(same implementation as before this PR) and oracle-based, where we use
the new callable oracles.
This way, most of the logic for ecrecover is reused.

Given that this is a critical part of the system, the PR includes:
* PBT for "good" case (showing equivalence when using the right oracle)
* Tests for the "bad" case (showing panics if the oracle lies)
* Modifies the fuzz target for ecrecover to compare the forward and
oracle runs. I've ran the fuzzer for over 1h without finding issues.

<!-- What are the changes this PR brings about? -->
<!-- Example: This PR adds a PR template to the repo. -->
<!-- (For bigger PRs adding more context is appreciated) -->

## Why ❔

<!-- Why are these changes done? What goal do they contribute to? What
are the principles behind them? -->
<!-- The `Why` has to be clear to non-Matter Labs entities running their
own ZK Chain -->
<!-- Example: PR templates ensure PR reviewers, observers, and future
iterators are in context about the evolution of repos. -->

## Is this a breaking change?
- [ ] Yes
- [ ] No

## Checklist

<!-- Check your PR fulfills the following items. -->
<!-- For draft PRs check the boxes as you complete them. -->

- [ ] PR title corresponds to the body of PR (we generate changelog
entries from PRs).
- [ ] Tests for the changes have been added / updated.
- [ ] Documentation comments have been added / updated.
- [ ] Code has been formatted.
…coding (#572)

## What ❔

Remove the `artifacts_len` field and artifact bytes from the pubdata
diff compression encoding:

- **Format 0** (publish bytecode): remove `artifacts_len` field (4
bytes), publish only the raw code bytes (`unpadded_code_len`) instead of
the full bytecode blob (code + padding + artifacts)
- **Format 4** (not_publish_bytecode): remove `artifacts_len` from the
length calculation — fixes a pre-existing bug where
`diff_compression_length()` counted +4 bytes for `artifacts_len` but
`diff_compression()` never actually wrote it
- Bump `PUBDATA_ENCODING_VERSION` from 1 to 2
- Update format documentation comments and unit test

## Why ❔

Bytecode artifacts (jump table, etc.) are fully deterministic from the
raw bytecode and do not need to be published in pubdata. Removing them
reduces pubdata size for contract deployments without losing any
information — the receiver can recompute artifacts from the code.

## Is this a breaking change?
- [x] Yes
- [ ] No

This changes the pubdata encoding format (version 1 → 2). The settlement
layer / receiver must be updated to parse the new format.

## Checklist

- [x] PR title corresponds to the body of PR (we generate changelog
entries from PRs).
- [x] Tests for the changes have been added / updated.
- [x] Documentation comments have been added / updated.
- [x] Code has been formatted.

---------

Co-authored-by: Claude Code <claude-code@anthropic.com>
## What ❔

Per-opcode EVM benchmarking infrastructure that collects gas, native
resource, and RISC-V cycle stats for every EVM opcode execution. Enables
computing cycles/gas and native/gas ratios to guide proving cost
optimizations.

**Components:**
- `EvmOpcodeStatsTracer` — forward-mode tracer collecting per-opcode
gas, native, count with min/max/median
- Per-opcode cycle markers in the interpreter loop
(`opcode_start!/opcode_end!`) — aggregated in `print_cycle_markers()`
with min/max/median
- Per-execution sample dump mode — write `(gas, native)` and `cycles`
per execution for detailed ratio analysis
- `join_samples.py` — joins tracer + cycle samples, computes
per-execution cycles/gas with p50/p95/p99/max
- `compare_opcode_stats.py` — CI integration: compact diff table when
per-opcode stats change
- `visualize_opcode_stats.py` — total cycle bar chart + sorted
cycles/gas ratio curves
- Integration into `eth_runner` single-run and CI bench workflow

**Usage:**
```bash
# Basic run (prints per-opcode stats table)
bash bench_scripts/bench.sh quick

# Full per-execution analysis
OPCODE_SAMPLES_DIR=samples OPCODE_CYCLE_SAMPLES_DIR=cycle_samples bash bench_scripts/bench.sh quick
python3 bench_scripts/join_samples.py samples/ cycle_samples/ --summary --out-dir joined/
python3 bench_scripts/visualize_opcode_stats.py joined/ --out-dir charts/
```

**Benchmark impact:** +0.34% effective cycles with benchmarking features
enabled. Zero overhead in production builds (all behind `#[cfg(feature =
"cycle_marker")]`).

## Why ❔

To compute per-opcode `cycles/gas` and `native/gas` ratios for verifying
the native resource model against actual RISC-V proving costs.

## Is this a breaking change?
- [ ] Yes
- [x] No

All code is behind feature flags or in benchmarking-only paths.
Production proving builds are unaffected — verified by audit of all
changed files and benchmark comparison.

## Checklist

- [x] PR title corresponds to the body of PR (we generate changelog
entries from PRs).
- [x] Tests for the changes have been added / updated.
- [x] Documentation comments have been added / updated.
- [x] Code has been formatted.

---------

Co-authored-by: Antonio Locascio <antonio.locascio1@gmail.com>
Co-authored-by: vv-dev-ai <vv@matterlabs.dev>
Co-authored-by: Claude Code <claude-code@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## What

Add `compare_opcode_cycles.py` script and wire it into the bench CI
workflow so that per-opcode RISC-V cycle diffs are posted on PRs.

The bench CI already collected per-opcode cycle stats in `.bench` files
on both the base and head sides (via `opcode_start!/opcode_end!` cycle
markers), but had no script to compare them. The existing
`compare_opcode_stats.py` only compared forward-mode gas/native stats,
which don't change when the execution implementation changes without
modifying resource accounting (e.g. the custom U256 PR).

## Why

Without this, PRs that affect RISC-V execution performance (like the
custom U256 migration) show overall effective cycle improvements in the
benchmark report but lack visibility into which specific EVM opcodes
improved or regressed. This makes it harder to review and reason about
performance changes.

## Is this a breaking change?
- [ ] Yes
- [x] No

## Checklist

- [x] PR title corresponds to the body of PR (we generate changelog
entries from PRs).
- [x] Tests for the changes have been added / updated.
- [x] Documentation comments have been added / updated.
- [x] Code has been formatted.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@antoniolocascio antoniolocascio force-pushed the alocascio-storage-touch-refactor branch from 3428e30 to e0db1f1 Compare March 30, 2026 08:30
@antoniolocascio antoniolocascio marked this pull request as draft March 30, 2026 08:32
@antoniolocascio antoniolocascio force-pushed the alocascio-storage-touch-refactor branch from e0db1f1 to 573688a Compare March 30, 2026 08:56
## What ❔
This PR changes the way we compute prover input. Before, we needed to
run the slow risc-v simulator to do so.
Now, we can do on native architecture.
This run is also responsible for computing the pubdata.
<!-- What are the changes this PR brings about? -->
<!-- Example: This PR adds a PR template to the repo. -->
<!-- (For bigger PRs adding more context is appreciated) -->

## Why ❔

<!-- Why are these changes done? What goal do they contribute to? What
are the principles behind them? -->
<!-- The `Why` has to be clear to non-Matter Labs entities running their
own ZK Chain -->
<!-- Example: PR templates ensure PR reviewers, observers, and future
iterators are in context about the evolution of repos. -->

## Is this a breaking change?
- [ ] Yes
- [ ] No

## Checklist

<!-- Check your PR fulfills the following items. -->
<!-- For draft PRs check the boxes as you complete them. -->

- [ ] PR title corresponds to the body of PR (we generate changelog
entries from PRs).
- [ ] Tests for the changes have been added / updated.
- [ ] Documentation comments have been added / updated.
- [ ] Code has been formatted.
@antoniolocascio-bot
Copy link
Copy Markdown

Code Review Summary

Reviewed by: Claude Opus 4.6 (manual deep review of entire 4000-line diff + 6 automated review passes in progress)
Findings: 0 critical, 0 major, 3 minor, 0 nits


Critical

None found

Major

None found

Minor

1. mutate_current_in_place safety invariant is debug-onlyzk_ee/src/common_structs/history_map/mod.rs

The debug_assert_eq!(self.history.head, self.history.initial) and debug_assert_eq!(self.history.head, self.history.committed) guards on the unsafe pointer dereference only fire in debug builds. In release, a violated invariant would silently corrupt rollback state by mutating a record that head/initial/committed no longer all share.

All current call sites maintain the invariant correctly (verified: mutate_current_in_place is only called on entries that are either freshly inserted as placeholders or committed-without-history from a previous tx). However, for a security-critical ZK state transition function, a debug-only guard on an unsafe mutation path is worth considering. A future caller could violate this invariant without runtime detection in release builds.

Suggestion: Consider promoting to a runtime assert_eq! (or at minimum, document the invariant requirement clearly in the method's doc comment with a # Safety section even though the method is not unsafe fn).


2. unwrap_or_default() in diff iterators is a latent correctness riskstorage_model.rs and flat_storage_model/mod.rs

The storage diff iterators now use:

initial_value: initial_record.value().copied().unwrap_or_default(),
current_value: current_record.value().copied().unwrap_or_default(),

If an unobserved entry somehow reaches the diff iterator, this would silently produce Bytes32::ZERO as the value instead of failing loudly. Currently safe because iter_altered_since_commit() only returns entries with history, and placeholder-only entries never have history. But this creates a latent bug if the iteration logic changes in the future.

Suggestion: Consider using .expect("diff entries must be materialized") instead of .unwrap_or_default() to fail loudly on invariant violations, consistent with the materialized_value() pattern used elsewhere.


3. Benchmark regression — Informational

The benchmark bot shows: SLOAD +2.8% median cycles, real blocks +0.18–0.38%, verify_and_apply_batch +1.59%. This is the expected cost of adding the !is_value_observed() check to every materialize_element call in the cache read/write hot path. Whether this regression is acceptable is a product decision, but it's inherent to the lazy-materialization design.


Verified as Correct

The following areas were specifically reviewed and found to be correct:

  • Gas accounting: Old bootloader constants (PER_ADDRESS_ACCESS_LIST_NATIVE_COST = 2000, ergs = ACCESS_LIST_ADDRESS * ERGS_PER_GAS) match the new cache-layer constants exactly. The removal of with_infinite_ergs is correct since touch no longer performs oracle IO.
  • Cross-tx warmth: considered_warm() uses last_touched_in_tx == Some(current_tx_id), so touches from tx N are cold in tx N+1. Integration test test_access_list_touch_does_not_keep_slot_warm_in_next_tx validates this.
  • Rollback semantics: mutate_current_in_place for metadata ensures warmth survives reverts (per EIP-2929/2930), while value changes via update() are properly rolled back. Test touched_materialized_written_slot_is_rolled_back_on_revert validates this.
  • observe parameter removal: All former observe: false call sites (e.g., increment_nonce, deconstruct_account) now correctly get observation via materialize_element. This is semantically correct — the value IS accessed during materialization.
  • Preimage .expect(): Safe because preimage entries are always created with CacheRecord::new(), never new_empty().
  • StorageAccessPolicy implementors: Only EthereumLikeStorageAccessCostModel implements the trait in production. The new charge_access_list_storage_touch method is properly implemented.
  • Test coverage: Comprehensive — unit tests for CacheRecord, HistoryMap::mutate_current_in_place, GenericPubdataAwarePlainStorage::touch_impl, both account cache implementations; integration tests for gas accounting, cross-tx warmth, rollback; end-to-end tests with custom oracle factories verifying zero oracle queries on touch-only paths.

Automated review by Claude Code

Comment thread zk_ee/src/common_structs/history_map/mod.rs
Comment thread basic_system/src/system_implementation/ethereum_storage_model/storage_model.rs Outdated
@antoniolocascio-bot
Copy link
Copy Markdown

Consolidated Review — All 6 Review Rounds Complete

Reviewed by: Claude Opus 4.6 (3 rounds) + Codex (3 rounds), manually validated
Total review passes: 6 parallel rounds + manual deep review of entire 4030-line diff
Findings after deduplication & validation: 0 critical, 2 major, 6 minor, 2 nits

Findings flagged by multiple independent reviewers are marked with 🔺 (higher confidence).


Critical

None found (verified across all 6 reviewers)

Major

1. 🔺 mutate_current_in_place safety invariant is debug-onlyzk_ee/src/common_structs/history_map/mod.rs
(Flagged by: Claude R1, Claude R2, Claude R3, Codex R3 — 4/6 reviewers)

The debug_assert_eq!(head, initial) and debug_assert_eq!(head, committed) are the sole guards before an unsafe pointer mutation. In release builds, a violated invariant would silently corrupt rollback state. All current call sites are verified correct, but the invariant is not encoded in the type system, not checked in production, and the API name doesn't communicate how narrow the valid call surface is.

Suggestion: Promote to runtime assert_eq! (the cost of two pointer comparisons is negligible vs. the risk of silent state corruption in a ZK STF). Add a // SAFETY: comment per Rust conventions.


2. 🔺 unwrap_or_default() in diff iterators creates latent correctness riskstorage_model.rs:390,391,410,411 and flat_storage_model/mod.rs:448,449,468,469
(Flagged by: all 6 reviewers)

Diff iterators use value().copied().unwrap_or_default() which silently produces Bytes32::ZERO for any None value. Currently safe because these iterate iter_altered_since_commit() which only returns entries with history (placeholders never have history). But this creates a latent bug: if iteration logic changes, incorrect state diffs would be silently published.

Suggestion: Use .expect("diff entries must be materialized") or materialized_value()? for consistency and fail-loud behavior.


Minor

3. 🔺 Touch/materialization pattern duplicated across 3 locations
(Flagged by: Claude R3, Codex R3)

The same "charge → get_or_insert placeholder → check warmth → branch on is_value_observed → update vs mutate_current_in_place" logic exists in touch_impl, EthereumAccountCache::touch_account, and NewModelAccountCache::touch_account. Same for charge_access_list_account_touch duplicated in both account caches. This encodes the same subtle placeholder/warmth invariant in 3+ places.


4. CacheRecord::update leaks Option<V> to callerscache_record.rs
(Flagged by: Claude R3, Codex R3)

The update() closure now receives &mut Option<V> while update_materialized() receives &mut V. The naming doesn't clearly distinguish the "raw escape hatch" from the "normal path." Consider renaming update to update_raw or similar to make the distinction explicit.


5. 🔺 Rollback test misses warmth-survives-revert assertionrollback_semantics.rs
(Flagged by: Codex R1, Codex R3)

touched_materialized_written_slot_is_rolled_back_on_revert verifies value rollback but doesn't verify that warmth survives the revert (per EIP-2929/2930). An intra-tx nested-CALL scenario where the inner frame reverts but the outer frame sees the slot as warm would be a valuable addition.


6. Benchmark regression — Multiple files
(Flagged by: Claude R2, Codex R2, manual analysis)

SLOAD +2.8%, EXTCODESIZE +11.4% (small absolute), real blocks +0.18-0.38%. Caused by !is_value_observed() check on every cache read hot path. Inherent to the design. Consider likely! branch hints.


7. assert_eq! on oracle data can panicgeneric_pubdata_aware_plain_storage.rs:109-115
(Flagged by: Claude R1, Claude R2, Codex R3)

Pre-existing issue (just moved into query_initial_value helper). A malicious oracle returning is_new_storage_slot = true with a non-zero value triggers a panic. Per project panic policy, consider returning a typed error.


8. debug_assert_eq! with materialized_value()? is dead code in releasepersist_changes.rs
(Flagged by: Claude R1, Claude R2)

Multiple debug_assert_eq!(initial.value.materialized_value()?, ...) calls. In release builds, the entire expression (including the ? error propagation) is stripped. Subtle interaction that could hide errors in release.


Nits

9. initial_value_used always true after filterfull_storage_cache.rs, storage_cache.rs
(Flagged by: Claude R1, Claude R3, manual analysis)

After !is_value_observed() filter, initial_value_used = is_value_observed() is always true. Could simplify to let initial_value_used = true;.


10. TestOracle duplication — 6 copies across test modules
(Flagged by: Claude R3, Codex R3)

Near-identical oracle boilerplate in 6 test files. Consider extracting to a shared test utility.


False Positives Eliminated

  • Codex R2 Finding 2: Claimed type error at *x = Some(AccountProperties::TRIVIAL_VALUE)FALSE POSITIVE. The code correctly uses CacheRecord::update (not update_materialized), which passes &mut Option<V>.
  • is_value_observed persistence across rollbacks: Flagged as a concern by multiple reviewers but verified correct — mutate_current_in_place ensures the value also persists, keeping flags consistent.
  • Gas accounting change from with_infinite_ergs removal: Verified equivalent — same ergs/native amounts, touch no longer does IO so infinite ergs wrapper unnecessary.

Verified Correct (Consensus Across All Reviewers)

All 6 reviewers independently verified:

  • ✅ Gas accounting matches between old bootloader and new cache-layer charging
  • ✅ Cross-tx warmth semantics correct (considered_warm uses tx ID comparison)
  • ✅ Rollback semantics correct (value rolled back, warmth preserved per EIP-2929/2930)
  • ✅ Preimage .expect() is safe (preimage entries never use new_empty())
  • ✅ No double-charging in cold access paths
  • ✅ No credential/secret exposure or injection vulnerabilities

Automated review by Claude Code (6 parallel review passes)

@antoniolocascio
Copy link
Copy Markdown
Contributor Author

Fixed the major ones, the minors aren't valid/worth fixing

## What ❔

We had an invalid worst case calculation + small cleanup

## Why ❔

<!-- Why are these changes done? What goal do they contribute to? What
are the principles behind them? -->
<!-- The `Why` has to be clear to non-Matter Labs entities running their
own ZK Chain -->
<!-- Example: PR templates ensure PR reviewers, observers, and future
iterators are in context about the evolution of repos. -->

## Is this a breaking change?
- [ ] Yes
- [ ] No

## Checklist

<!-- Check your PR fulfills the following items. -->
<!-- For draft PRs check the boxes as you complete them. -->

- [ ] PR title corresponds to the body of PR (we generate changelog
entries from PRs).
- [ ] Tests for the changes have been added / updated.
- [ ] Documentation comments have been added / updated.
- [ ] Code has been formatted.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@antoniolocascio antoniolocascio force-pushed the alocascio-storage-touch-refactor branch from a54efb2 to 04642be Compare March 30, 2026 13:47
@0xVolosnikov
Copy link
Copy Markdown
Contributor

I see here that worst case for CALLDATACOPY is "15462.7" - it is strange, compare with #588 (comment)

Maybe I missed some fixes for benchmarks, you can ask AI to investiagate

@antoniolocascio antoniolocascio force-pushed the alocascio-storage-touch-refactor branch from e40e03d to b756e16 Compare March 31, 2026 09:03
@github-actions
Copy link
Copy Markdown
Contributor

Benchmark report

Benchmark Symbol Base Eff Head Eff (%) Base Raw Head Raw (%) Base Blake Head Blake (%) Base Bigint Head Bigint (%)
block_19299001 process_block 300,182,822 300,713,939 (+0.18%) 264,851,734 265,383,011 (+0.20%) 410,630 410,620 (-0.00%) 7,190,252 7,190,252 (+0.00%)
block_22244135 process_block 185,145,000 185,631,948 (+0.26%) 164,426,516 164,913,464 (+0.30%) 172,040 172,040 (+0.00%) 4,491,461 4,491,461 (+0.00%)
precompiles bn254_ecadd 53,268 53,268 (+0.00%) 47,816 47,816 (+0.00%) 0 0 (+0.00%) 1,363 1,363 (+0.00%)
precompiles bn254_ecmul 728,781 728,781 (+0.00%) 564,593 564,593 (+0.00%) 0 0 (+0.00%) 41,047 41,047 (+0.00%)
precompiles bn254_pairing 72,336,733 72,336,733 (+0.00%) 57,808,589 57,808,589 (+0.00%) 0 0 (+0.00%) 3,632,036 3,632,036 (+0.00%)
precompiles ecrecover 384,223 384,233 (+0.00%) 257,503 257,653 (+0.06%) 0 0 (+0.00%) 31,680 31,645 (-0.11%)
precompiles id 927 927 (+0.00%) 927 927 (+0.00%) 0 0 (+0.00%) 0 0 (+0.00%)
precompiles keccak 137,579 137,579 (+0.00%) 137,579 137,579 (+0.00%) 0 0 (+0.00%) 0 0 (+0.00%)
precompiles modexp 31,267,857 31,267,857 (+0.00%) 20,610,037 20,610,037 (+0.00%) 0 0 (+0.00%) 2,664,455 2,664,455 (+0.00%)
precompiles p256_verify 748,861 748,861 (+0.00%) 470,169 470,169 (+0.00%) 0 0 (+0.00%) 69,673 69,673 (+0.00%)
precompiles process_block 147,361,304 147,375,321 (+0.01%) 117,976,852 117,987,661 (+0.01%) 5,160 5,140 (-0.39%) 7,325,473 7,326,355 (+0.01%)
precompiles process_transaction 73,390,909 73,394,172 (+0.00%) 58,737,853 58,740,124 (+0.00%) 160 160 (+0.00%) 3,662,624 3,662,872 (+0.01%)
precompiles ripemd 8,013 8,013 (+0.00%) 8,013 8,013 (+0.00%) 0 0 (+0.00%) 0 0 (+0.00%)
precompiles run_tx_loop 146,710,404 146,720,516 (+0.01%) 117,405,632 117,412,216 (+0.01%) 180 180 (+0.00%) 7,325,473 7,326,355 (+0.01%)
precompiles sha256 13,168 13,168 (+0.00%) 13,168 13,168 (+0.00%) 0 0 (+0.00%) 0 0 (+0.00%)
precompiles system_init 46,790 46,790 (+0.00%) 46,790 46,790 (+0.00%) 0 0 (+0.00%) 0 0 (+0.00%)
precompiles verify_and_apply_batch 146,744 147,126 (+0.26%) 110,424 110,806 (+0.35%) 2,270 2,270 (+0.00%) 0 0 (+0.00%)

Per-opcode cycle diff

Opcode Count Med Cycles (%) Total Cycles (%) Med Cyc/Gas (%) Worst Cyc/Gas (%)
SHA3 2497 17,985 51,782,576 428.2 668.5
MULMOD 3372 3,732 (+0.7%) 10,875,273 (+0.7%) 466.5 (+0.7%) 473.0 (+0.7%)
ISZERO 28523 309 (+0.3%) 5,971,981 (+0.3%) 103.0 (+0.3%) 103.0 (+0.3%)
SLOAD 3237 1,441 (+1.1%) 5,505,684 (+2.7%) 7.8 (+1.3%) 29.4 (-0.1%)
ADD 35398 115 (+0.9%) 4,081,190 (+0.9%) 38.3 (+0.9%) 39.3 (+0.9%)
MSTORE 16177 166 (+1.8%) 3,506,407 (+1.4%) 55.3 (+1.8%) 55.3 (+1.8%)
SUB 24255 128 (+1.6%) 3,129,662 (+1.6%) 42.7 (+1.6%) 43.7 (+1.6%)
PUSH32 9778 310 (+0.6%) 2,973,837 (+0.7%) 103.3 (+0.6%) 106.0 (+0.6%)
SSTORE 950 2,434 (+1.5%) 2,424,136 (+1.8%) 0.8 (+1.3%) 44.9 (-0.7%)
MLOAD 12750 176 (+1.7%) 2,250,156 (+1.7%) 58.7 (+1.7%) 58.7 (+1.7%)
EXP 655 2,798 (+0.1%) 1,742,495 (+0.1%) 57.3 (+0.1%) 61.8 (+0.1%)
DIV 2058 810 (+0.1%) 1,721,108 (+0.1%) 162.0 (+0.1%) 323.4 (+0.1%)
EQ 7790 91 1,306,501 (+0.2%) 30.3 104.0 (+0.3%)
PUSH4 7849 156 (-0.6%) 1,239,155 (-0.6%) 52.0 (-0.6%) 58.3 (-0.6%)
CALLDATALOAD 4279 227 (+0.9%) 1,067,560 (+0.8%) 75.7 (+0.9%) 103.3 (+0.6%)
MUL 2278 326 (+0.6%) 746,009 (+0.6%) 65.2 (+0.6%) 66.4 (+0.6%)
EXTCODESIZE 291 462 (+0.4%) 569,209 (+8.4%) 3.6 (-1.6%) 75.6 (+1066.0%)
LOG3 341 1,052 (+0.2%) 366,280 (+0.1%) 0.6 (+0.9%) 0.8 (+0.2%)
CALLDATACOPY 371 403 (-0.5%) 284,574 (-0.2%) 23.8 (-0.5%) 51.2 (-0.6%)
SIGNEXTEND 609 426 (+0.2%) 252,389 (+0.2%) 85.2 (+0.2%) 86.0 (+0.2%)
SLT 1506 126 (+0.8%) 186,162 (+0.8%) 42.0 (+0.8%) 42.3 (+0.8%)
PUSH29 350 325 (+0.6%) 112,530 (+0.6%) 108.3 (+0.6%) 108.3 (+0.6%)
ADDMOD 207 185 (+1.6%) 104,373 (+0.6%) 23.1 (+1.6%) 150.4 (+0.2%)
PUSH16 305 305 (+0.7%) 90,418 (+0.7%) 101.7 (+0.7%) 104.3 (+0.6%)
RETURNDATACOPY 241 237 62,866 (+0.1%) 39.5 48.2
RETURN 652 90 (+1.1%) 56,250 (+1.2%) n/a n/a
PUSH28 198 272 (+0.4%) 52,222 (+0.4%) 90.7 (+0.4%) 97.0 (+0.3%)
LOG2 49 940 (-0.2%) 49,244 (-0.2%) 0.7 (+1.4%) 0.8 (-0.1%)
SDIV 49 807 (+0.2%) 43,976 (+0.2%) 161.4 (+0.2%) 269.0 (+0.1%)
LOG1 46 863 (+0.1%) 42,908 (-0.0%) 0.7 (+0.1%) 0.9 (+0.1%)
CODECOPY 109 369 (-0.5%) 39,510 (-0.5%) 30.4 (-0.4%) 52.2 (-0.6%)
LOG4 28 1,195 (+0.6%) 38,930 (+0.3%) 0.5 (+0.3%) 0.6 (+0.1%)
SGT 218 159 (+1.3%) 35,023 (+1.0%) 53.0 (+1.3%) 131.0 (+0.5%)
PUSH5 192 162 (-0.6%) 30,953 (-0.6%) 54.0 (-0.6%) 61.3 (-0.5%)
SELFBALANCE 58 298 18,796 (-0.4%) 59.6 95.6 (-1.8%)
SAR 61 262 (+1.2%) 15,377 (+1.0%) 87.3 (+1.2%) 100.3 (+1.7%)
SMOD 26 585 (+0.3%) 15,266 (+0.3%) 117.0 (+0.3%) 182.0 (+0.2%)
MCOPY 34 467 (+0.2%) 15,174 (+0.2%) 18.3 (+0.2%) 31.0
PUSH31 25 309 (+0.7%) 7,615 (+0.7%) 103.0 (+0.7%) 106.7 (+0.6%)
ORIGIN 62 69 (-1.4%) 4,278 (-1.4%) 34.5 (-1.4%) 34.5 (-1.4%)
PUSH24 16 239 (+0.8%) 4,026 (+0.8%) 79.7 (+0.8%) 103.0 (+0.7%)
PUSH30 14 269 (+0.4%) 3,795 (+0.4%) 89.8 (+0.4%) 102.0 (+0.3%)
PUSH26 11 286 (+0.7%) 3,135 (+0.7%) 95.3 (+0.7%) 95.3 (+0.7%)
TLOAD 3 582 (-6.3%) 2,250 (-3.4%) 5.8 (-6.3%) 10.9
TSTORE 2 1,045 (-3.6%) 2,091 (-3.6%) 10.5 (-3.6%) 10.8 (-3.5%)
EXTCODEHASH 3 629 (-1.9%) 1,887 (-1.9%) 6.3 (-1.9%) 6.3 (-1.9%)
PUSH25 6 274 (+0.4%) 1,620 (+0.4%) 91.5 (+0.4%) 97.3 (+0.3%)
PUSH27 1 302 (+0.7%) 302 (+0.7%) 100.7 (+0.7%) 100.7 (+0.7%)
GASPRICE 2 72 (-1.4%) 144 (-1.4%) 36.0 (-1.4%) 36.0 (-1.4%)

@antoniolocascio antoniolocascio marked this pull request as ready for review March 31, 2026 11:18
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.

4 participants