feat(revm): upgrade to upstream v107 (revm 38.0.0) + op-geth parity fixes for Mantle Elysium - #26
Merged
Merged
Conversation
* fix: use access list to decide if slot is cold * add test
…uealloy#3154) * fix(op): Ensure L1Block account is always loaded * dont panic on State::storage fn * add test for pre regolit tx
* feat: process precompile logs to inspector * ci nits * no_std * std import * add log and log_full * no_std * doc * revert &Log to Log * nits rm clones
bluealloy#3144) * feat(precompiles): add performant PrecompileError::OtherStr variant * Apply suggestion from @rakita * use cow * rm Other(String) and use Other(Cow<'static,str>) * fn other_static * revert api change
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* bump: v99 revm v32.0.0 * typo:
…luealloy#3159) The feature exists in the context crate but was not exposed in revm's Cargo.toml, making it inaccessible to users.
* feat(context): add mark_cold method to JournaledAccount Add a public `mark_cold()` method to `JournaledAccount` that delegates to the underlying `Account::mark_cold()`. This is analogous and provides a way to mark accounts as cold that was available in previous revm versions. This is used by celo-revm to provide backwards compatibility for the transfer precompile, which does not warm addresses in its initial implementation. * Declare mark_cold to be unsafe and add explanation. Co-authored-by: rakita <rakita@users.noreply.github.com> --------- Co-authored-by: rakita <rakita@users.noreply.github.com>
* fix(op-revm): return error when enveloped_tx is missing * fmt * Remove unnecessary clone
* fix: correctly handle selfdestruct cold load * add constantinople tests in script
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* chore(test): rn tar after download * try with latest * print size,and rm unused files * rm du prints
… precompile modules (bluealloy#3191) Co-authored-by: megakabi <jakevin@megatechnology.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* fix: reject transactions with nonce at u64::MAX to prevent overflow Incrementing a nonce of u64::MAX would overflow, so validate this upfront in transaction validation. * Apply suggestion from @rakita
* docs: add v106 migration guide entry * chore: v106 release prep — version bumps and changelogs * add revm version
…luealloy#3573) When building the BAL for a selfdestructed account, storage slots were only recorded as reads via `update_reads`. This meant subsequent transactions consuming the BAL would not see the storage zeroing and would fall through to the database, getting stale pre-selfdestruct values. Record proper writes to zero instead.
* refactor: pass reservoir into first_frame_input * fix * fix
Adds the new `&mut InitialAndFloorGas` parameter introduced in bluealloy#3577 to the OpHandler trait impl, and passes `&mut Default::default()` at all test call sites.
…sium upgrade
Merge upstream revm v98 → v107, spanning 7 major versions (revm 31→38).
Key upstream changes incorporated:
- EIP-8037 reservoir gas model (GasTracker refactor)
- EIP-7825 tx gas limit cap (disabled for Mantle, aligned with op-geth)
- EIP-7954 increased max contract size
- alloy-primitives, precompile, and handler API refactors
Mantle-specific conflict resolutions:
- op-revm/handler.rs: preserved Arsia fee model (operator_fee, fee recipients)
with ARSIA spec guard for historical block compatibility
- op-revm/l1block.rs: preserved GAS_ORACLE_CONTRACT + TOKEN_RATIO_SLOT loading
and token_ratio calculation, added reservoir gas handling
- catch_error: preserved BVM_ETH deposit logic with audit fix (Issue #5),
adapted to upstream's new Halt structure (ResultGas + logs)
- context/cfg.rs: preserved tx_gas_limit_cap = u64::MAX (EIP-7825 disabled)
- interpreter/gas.rs: added Gas::set_limit wrapper for token_ratio scaling
- database/alloydb.rs: migrated Audit fix (#25) to upstream AlloyDBError enum
- block_traces example: preserved Mantle L2 replay tooling
Base: mantle-xyz/revm main (tag v2.2.2, upstream v98)
Target: bluealloy/revm v107 (revm 38.0.0)
- Remove commented-out `drop(acc)` dead code in catch_error
- Add `[MANTLE] spec_id not used` doc on operator_fee_charge_inner
- Ensure L1BlockInfo is reloaded before ARSIA fee deduction in
validate_against_state_and_deduct_caller
- Normalize `[MANTLE]` annotation format in gas.rs and l1block.rs
…as APIs
- Add .max(1) to token_ratio in validate_initial_tx_gas and
last_frame_result, consistent with the existing guard in execution().
A zero token_ratio would zero out initial_total_gas, giving the
execution frame the full tx gas_limit instead of the correct amount.
- Replace deprecated Gas::spent() with total_gas_spent() and
ExecutionResult::gas_used() with tx_gas_used() per upstream v107.
- Fix Bedrock revert test assertions to match upstream behavior.
- Regenerate ee-tests snapshots for upstream ResultGas schema change
(removed intrinsic_gas/limit fields, added state_gas_spent).
byteflyfunny
approved these changes
May 11, 2026
The try_fetch(journal.db_mut()) guard in validate_against_state_and_deduct_caller bypassed the journal and read stale underlying-DB storage. Running after reset_l2_block() within a gas-oracle tx, it re-pinned l2_block to the current block with the pre-SSTORE token_ratio, so the next tx never refetched and the L1FeeVault was over-credited at the stale ratio. validate_initial_tx_gas always runs first (validate() before pre_execution()) and already handles L1BlockInfo refresh via try_fetch and reset_l2_block, so the guard was redundant. Add regression tests covering intra-block token_ratio updates.
EvmStorageSlot::mark_warm_with_transaction_id reset original_value=present whenever the slot was cold, but 'cold' also covers a slot flagged cold within the SAME transaction (access-list reset / reverted StorageWarmed). That wrongly re-baselined the EIP-2200 original for same-tx cold resets. Restrict the re-baseline to genuine cross-transaction access (transaction_id mismatch).
Deposit minting writes BVM_ETH via the journal (load_account/sload/sstore), which warms the BVM_ETH account + storage slots for the subsequent EVM execution. op-geth mints via StateDB.SetState(), which never touches the EVM access list, so it stays cold. This warm/cold divergence was patched over with a static BVM_ETH_MINT_GAS_COMPENSATION (4500) gated on `!tx.input().is_empty()`. That heuristic mis-fired for non-empty-calldata deposits to EOAs (hoodi-qa2 block 59294: reth 25628 vs geth 21320 -> receiptsRoot/blockHash fork), and over/under-compensated other access paths since the real cold-access cost is dynamic. Root-cause fix: after process_eth_deposit, reset BVM_ETH (account + touched storage slots) back to EIP-2929 cold via a new JournalColdExt, so the EVM observes it exactly like op-geth (cold then falls back to the tx access list / to / precompiles). The static compensation constant and the refund-stage hack are removed entirely. Scope: op-revm only, no core revm changes. The JournalColdExt bound is carried by OpContextTr so no per-call-site threading is needed. Tests: process_eth_deposit_leaves_bvm_eth_cold (mechanism); deposit_to_eoa_with_calldata_no_compensation_matches_geth (block 59294 replay asserts 21320, not 25628); existing mainnet fixture 89718944 (L2StandardBridge) still passes. cargo test -p op-revm: all green.
fix: BVM_ETH deposit gas parity on revm-19 (cold reset + cross-tx original re-baseline)
…o match op-geth (#32) * fix(op-revm): persist BVM_ETH Mint/Transfer logs on failed deposits to match op-geth A failed Mantle deposit (status=0) must persist its BVM_ETH `Mint` log (and the `Transfer` log when the transfer executed) into the receipt, matching op-geth. Dropping them forks the receipts/state root. op-geth model (core/state_transition.go): - Case A: innerExecute returns a Go error (transfer ErrEthTxValueTooLarge, intrinsic/validation) -> RevertToSnapshot -> only the pre-snapshot Mint persists, nonce++, gas = GasLimit. - Case B: the EVM ran then REVERT/OOG (vmerr, err == nil; "vm errors do not effect consensus") -> no rewind -> Mint + Transfer persist, gas = actual. Changes: - catch_error (Case A): capture journal logs before commit_tx so the mint-only Mint log survives into the FailedDeposit halt. - execution_result (Case B): for a post-Regolith deposit whose frame Reverts/Halts, keep the surviving logs + actual ResultGas and re-label as FailedDeposit (no checkpoint_revert, no re-mint -- the pre-frame state, including the CREATE recipient and caller nonce, is already correct). Verified against op-geth on the three known fork blocks: Sepolia 286432 (Case A, Mint only) and 286456 (Case B, Mint+Transfer) via real-block replay fixtures, and mainnet 96442768 (byte-exact Mint log); also covers CREATE deposit recipient parity and OOG. Regenerates the test_halted_deposit_tx golden (benign: non-consensus status flag + original_info.code only; material state byte-identical) and clears clippy -D warnings. * refactor(op-revm): dedupe execution_result cleanup into a single tail Compute the failed-deposit re-label result first, then run the commit_tx/clear_tx_l1_cost/local.clear/frame_stack.clear quartet once for both the deposit and normal paths instead of duplicating it across an early return. No behavior change (op-revm 121 tests pass, clippy -D warnings clean).
…posit `process_eth_deposit` gated the BVM_ETH mint/transfer on `is_some()`, so a `Some(0)` eth_value/eth_tx_value would mint/transfer zero — which is not a no-op: it touches the BVM_ETH balance/totalSupply storage slots and emits a zero-value Mint/Transfer log. op-geth gates these on `!= nil && != 0` (`core/state_transition.go`), so a stray `Some(0)` would diverge on both state root (touched slots) and receipts root (extra log). Filter zero to `None` at the top of `process_eth_deposit`, mirroring op-geth and keeping the guard at the execution layer (self-sufficient regardless of how the deposit was constructed: engine API, direct tx-env, or the conversion layer). Defense-in-depth, not a live bug on the current pipeline (op-node nils zero EthValue/EthTxValue; alloy-op-evm normalizes eth_value==0 -> None) — but that normalization was asymmetric (eth_tx_value passed through) and relied on the input pipeline rather than the execution layer. Test: process_eth_deposit_zero_eth_value_treated_as_none.
…-guard fix(op-revm): treat zero BVM_ETH eth_value/eth_tx_value as None in deposit (op-geth parity)
…us precompile set (#36) * fix(op-revm): gate CanTransfer and EIP-3860 on deposits to match op-geth Port the pre-call deposit gates from the arsia line to elysium (v107). op-geth applies all pre-call checks inside innerExecute() and, on a deposit, routes any failure to the mint-only path (RevertToSnapshot + gasUsed=GasLimit), keeping only the BVM_ETH Mint log. op-revm skips env validation for deposits, so two pre-call conditions leaked into the EVM frame and took the wrong route, diverging from op-geth: - native (MNT) value transfer the caller cannot cover after the mint (CanTransfer, state_transition.go L781) - CREATE deposit whose init code exceeds EIP-3860 MAX_INITCODE_SIZE (state_transition.go L786) Add both gates to validate_against_state_and_deduct_caller's deposit branch so they return a tx-level error -> catch_error -> mint-only, matching op-geth. Regression tests: - test_failed_deposit_native_value_too_large_keeps_eth_mint_only - test_failed_create_deposit_initcode_too_large_keeps_eth_mint_only * fix(op-revm): drop OP input-size limits from Isthmus precompiles to match Mantle op-geth Mantle maps its entire pre-Limb era (Skadi and genesis) to OpSpecId::ISTHMUS (see mantle-v2 alloy-op-evm `spec_by_timestamp_after_bedrock` and kona `mantle_spec_id`: anything below Limb -> ISTHMUS). op-geth's pre-Limb set `PrecompiledContractsMantleSkadi` is the standard Prague precompile set plus secp256r1 p256verify, using the standard Istanbul bn254 pairing and the standard EIP-2537 BLS12-381 precompiles -- none of which carry OP Stack input-size limits. op-revm's `isthmus()`, however, inherited `granite()`'s bn254 pairing limit (GRANITE_MAX_INPUT_SIZE = 112687) and the upstream OP Isthmus BLS12-381 input-size limits (G1 MSM / G2 MSM / pairing). This made op-revm reject oversized-but-valid bn254/BLS inputs that Mantle op-geth accepts, a consensus divergence for the pre-Limb era. Build the Isthmus set from `fjord()` (Cancun + p256verify, unrestricted Istanbul bn254 pairing) plus the unrestricted Prague BLS12-381 precompiles, matching `PrecompiledContractsMantleSkadi` exactly. `jovian()` is unaffected: it removes the variable-input precompiles by address and re-adds its own Jovian-limited versions. OSAKA/ARSIA (Limb and after) were already correct. Add a regression test asserting the Isthmus set imposes no OP input-size limit on bn254 pairing or BLS12-381 G1 MSM / G2 MSM / pairing. * test(ee-tests): add e2e regression for unrestricted Isthmus bn254 pairing Mirrors the existing bn254 fjord/granite tx-level tests with an Isthmus case. Feeds the bn254 pairing precompile an input that is a valid multiple of the pair-element length but larger than the Granite input cap, so the only thing that could reject it on length is the OP Stack cap. Asserts the call is NOT halted with the Granite-style "bn254 invalid pair length" error, proving the Isthmus set (which Mantle maps its pre-Limb era to) carries no OP input-size limit, consistent with op-geth PrecompiledContractsMantleSkadi. Verified RED on the pre-fix granite-based isthmus() (halts with "bn254 invalid pair length") and GREEN after the fix (runs past the length gate, out of precompile gas). --------- Co-authored-by: ivan <314130948@qq.com>
Resolves the divergence between the v98-based BVM_ETH fixes on `main` (PRs #27, #29, #31) and their v107-based rework on this branch (PRs #28, #32, #33, #36). Both lines implement the same op-geth parity semantics against different revm baselines, so `op-revm/handler.rs` and `transaction/bvm_eth.rs` conflicted broadly. Resolution principle: keep the v107 code shape (this branch), but ensure every semantic fix and every test present on `main` survives the merge. Conflict resolution (38 hunks across 7 files) - context/interface/result.rs: keep `gas: ResultGas` + logs on all three variants (upstream bluealloy#3413/bluealloy#3424). Retain main's `serde(default)` on `Halt.logs` so pre-existing serialized halts still decode, and its rationale doc. `logs()`/`into_logs()` now also surface Revert logs, which v107 carries and main's enum could not. - handler/post_execution.rs: keep the upstream path that forwards `take_logs()` into Halt. This is what lets a failed deposit's pre-frame BVM_ETH mint/transfer logs survive without main's checkpoint_revert-and-rebuild. - op-revm/handler.rs: take this branch. Its `execution_result` relabels the failed deposit in place instead of reverting and re-applying mint/transfer/nonce, avoiding the divergence risk of rebuilding state. main's test set here is a strict subset of this branch's. - op-revm/transaction/bvm_eth.rs: impl region verified byte-identical to this branch's pre-merge version. Ported main's 13 JournalColdExt cooling tests (PR #27), which had no counterpart here — cold/warm state drives gas and therefore the state root. - examples/block_traces: took main's per-receipt reconciliation (status/cumGas/logs/bloom vs on-chain) and range summary, ported to the v107 `tx_gas_used()`/`into_logs()` API, and folded the previous per-tx gas check back in as an extra `gas` field. - examples/custom_precompile_journal, ee-tests: v107 API shape only. The ported cooling tests pass against this branch's v107 implementation with no assertion changes — only `..Default::default()` added for the new `AccountInfo::account_id` field — which independently confirms the two cooling implementations are semantically equivalent. Verification - cargo test -p op-revm: 138 passed, 0 failed (125 + 13 ported) - cargo test --workspace --exclude revm-ee-tests: 411 passed, 0 failed - cargo clippy --workspace --all-targets: no warnings - cargo fmt --all --check: clean
Review of the origin/main merge found one semantic loss: the conflict resolution took this branch's side wholesale for bvm_eth.rs tests, which reverted `deposit_to_eoa_with_calldata_no_compensation_matches_geth` from ARSIA back to ISTHMUS. This branch never picked up main's 244f2cd. Block 59294 — and every deployed Mantle deposit — executes under ARSIA (eth_spec OSAKA). ISTHMUS is a different gas regime and leaves the is_arsia branches uncovered, so the replay was validating against a spec the chain does not run. The 21_320 EIP-7623 floor assertion still holds under ARSIA on v107. cargo test -p op-revm: 138 passed, 0 failed
Three CI jobs were red on this PR. Only the first is introduced by this branch; the other two also reproduce on main and are fixed here because they block the merge. - database/alloydb.rs: `[MANTLE]` in a doc comment is parsed by rustdoc as an intra-doc link and fails to resolve, breaking `cargo doc` for revm-database. Wrapped in backticks. This one is new in this PR — origin/main's alloydb.rs has no such comment (the Audit-fix #25 migration to AlloyDBError brought it in). The many `// [MANTLE]` line comments elsewhere are unaffected: rustdoc only parses `///`. - database/states/bundle_account.rs: manual `Option::filter` via and_then. Present verbatim on main too; only newer clippy in CI flags it (local clippy 0.1.95 does not). - op-revm/handler.rs: "REVERTs" trips typos v1.49 (tokenizes to REVER). Present on main as well. Lowercased to "reverts". cargo doc --workspace --all-features: clean cargo clippy --workspace --all-targets --all-features: clean cargo test -p op-revm: 138 passed; --workspace: 411 passed, 0 failed
… link The previous CI fix only escaped the two `[MANTLE]` markers in database/alloydb.rs, so `cargo doc` still failed on revm-interpreter. The local run passed at the time only because cargo's incremental fingerprint did not re-document the untouched crates — a false negative. Escaped every remaining Mantle marker in a doc comment: - interpreter/src/gas.rs:219 - op-revm/src/l1block.rs:185, 319 - op-revm/src/precompiles.rs:83 Also qualified `[`calculate_tx_l1_cost_arsia`]` as `Self::` — it is a private method, so the bare link resolved to nothing (warning only, but it would turn into an error under a stricter rustdoc lint config). Verified with a forced full rebuild (cargo clean --doc + touch every .rs) rather than an incremental run: - cargo doc --workspace --all-features --no-deps --document-private-items: 0 errors, 0 unresolved links - cargo test -p op-revm: 138 passed; --workspace: 411 passed, 0 failed - cargo clippy --all-targets --all-features: clean; cargo fmt: clean
pandainzoo
approved these changes
Aug 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Merge upstream
bluealloy/revmfrom v98 (revm 31.0.0) to v107 (revm 38.0.0) — spanning 7 major versions — and land the follow-up op-geth parity fixes for Mantle Elysium. Upgrading the bottom-most dependency in preparation for op-reth v2.2.0 integration.Scope: 230 commits, 357 files, +22468 / −6698 against
main(56aee9a6).Part 1 — Upstream merge (
3e3ea167)Key upstream changes
GasTrackerrefactored withreservoir(),record_state_cost(),record_regular_cost()!IsOptimism()behaviorcrates/state/src/bal/module (~850 LOC) plusexamples/bal_exampleMantle conflict resolutions (8 files, manually merged)
op-revm/handler.rs(13 conflicts)is_enabled_in(OpSpecId::ARSIA)guard for historical block compatibility; adopted upstreameffective_usedwith reservoir deduction; preserved BVM_ETHcatch_erroraudit fix (Issue #5) adapted to the newHalt { gas: ResultGas, logs }structureop-revm/l1block.rs(4 conflicts)GAS_ORACLE_CONTRACT+TOKEN_RATIO_SLOTloading; added reservoir gas tooperator_fee_charge; adopted upstreamget_tokens_in_calldata_istanbulrenamecontext/cfg.rstx_gas_limit_cap = u64::MAX(EIP-7825 disabled for Mantle)interpreter/gas.rsGas::set_limit()wrapper delegating toGasTracker::set_limit()database/alloydb.rsInvalidBytecode,Internalvariants) to upstreamAlloyDBErrorenumblock_traces/main.rsbuild_op())Historical block compatibility
All spec-gated conditions (
is_enabled_in(OpSpecId::ARSIA)) are preserved to ensure:eth_call/eth_estimateGasat historical blocks returns correct resultsPart 2 — Mantle fixes on top of the merge
Landed after the upstream merge, mostly op-geth behavioural parity for BVM_ETH deposits. Net effect on
crates/op-revm: 17 files, +1971 / −688.fcae1dabgas.rs,handler.rs,l1block.rs)232bb55bcargo fmt+cargo clippy --fixe637f61etoken_ratioagainst zero; migrate deprecated gas APIs; regenerate BLS12-381 goldens2b42441dL1BlockInforefetch in deduct-caller (+379 LOC of tests)5d5ac498crates/state: re-baseline SSTOREoriginal_valueonly across tx boundaries.mark_warm_with_transaction_idused to resetoriginal_value = presentwhenever the slot was cold — but "cold" also covers a slot flagged cold within the same transaction (access-list reset / revertedStorageWarmed), which wrongly re-baselined the EIP-2200 original. Now restricted to genuine cross-transaction access (transaction_idmismatch).e85c5821(#28)handler.rsintotransaction/bvm_eth.rse8f14818(#32)1ac02c34(#33)eth_value/eth_tx_valueasNonein deposit (op-geth parity)11b77ca1(#36)Test plan
cargo test -p op-revm— 125 passed, 0 failed (includes the BVM_ETH deposit replay fixtures and the Isthmus precompile set)cargo test --workspace --exclude revm-ee-tests— 398 passed, 0 failed across 51 test binaries