From 4619699151319fcf1507803fee8a43ace5d5099b Mon Sep 17 00:00:00 2001 From: rakita Date: Tue, 15 Apr 2025 12:24:27 +0200 Subject: [PATCH 001/225] chore: backport from release branch (bluealloy/revm#2415) (bluealloy/revm#2416) --- CHANGELOG.md | 237 +++++++++ Cargo.toml | 75 +++ LICENSE | 21 + src/api.rs | 3 + src/api/builder.rs | 45 ++ src/api/default_ctx.rs | 46 ++ src/api/exec.rs | 135 +++++ src/constants.rs | 53 ++ src/evm.rs | 832 +++++++++++++++++++++++++++++ src/fast_lz.rs | 187 +++++++ src/handler.rs | 934 +++++++++++++++++++++++++++++++++ src/l1block.rs | 593 +++++++++++++++++++++ src/lib.rs | 27 + src/precompiles.rs | 332 ++++++++++++ src/result.rs | 14 + src/spec.rs | 197 +++++++ src/transaction.rs | 28 + src/transaction/abstraction.rs | 196 +++++++ src/transaction/deposit.rs | 42 ++ src/transaction/error.rs | 78 +++ 20 files changed, 4075 insertions(+) create mode 100644 CHANGELOG.md create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 src/api.rs create mode 100644 src/api/builder.rs create mode 100644 src/api/default_ctx.rs create mode 100644 src/api/exec.rs create mode 100644 src/constants.rs create mode 100644 src/evm.rs create mode 100644 src/fast_lz.rs create mode 100644 src/handler.rs create mode 100644 src/l1block.rs create mode 100644 src/lib.rs create mode 100644 src/precompiles.rs create mode 100644 src/result.rs create mode 100644 src/spec.rs create mode 100644 src/transaction.rs create mode 100644 src/transaction/abstraction.rs create mode 100644 src/transaction/deposit.rs create mode 100644 src/transaction/error.rs diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000000..464cc115270 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,237 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [3.0.2](https://github.com/bluealloy/revm/compare/op-revm-v3.0.1...op-revm-v3.0.2) - 2025-04-15 + +### Other + +## [3.0.1](https://github.com/bluealloy/revm/compare/op-revm-v3.0.0...op-revm-v3.0.1) - 2025-04-13 + +### Fixed + +- *(isthmus)* Add input size limitations to bls12-381 {G1/G2} MSM + pairing ([#2406](https://github.com/bluealloy/revm/pull/2406)) + +## [3.0.0](https://github.com/bluealloy/revm/compare/op-revm-v2.0.0...op-revm-v3.0.0) - 2025-04-09 + +### Added + +- support for system calls ([#2350](https://github.com/bluealloy/revm/pull/2350)) + +### Other + +- bump alloy 13.0.0 and alloy-primitives v1.0.0 ([#2394](https://github.com/bluealloy/revm/pull/2394)) +- fixed `EIP` to `RIP` ([#2388](https://github.com/bluealloy/revm/pull/2388)) +- clean unsed indicatif ([#2379](https://github.com/bluealloy/revm/pull/2379)) +- *(op-inspector)* Add test for inspecting logs ([#2352](https://github.com/bluealloy/revm/pull/2352)) +- *(op-tx)* Cover DepositTransactionParts constructor in test ([#2358](https://github.com/bluealloy/revm/pull/2358)) +- add 0x prefix to b256! and address! calls ([#2345](https://github.com/bluealloy/revm/pull/2345)) + +## [2.0.0](https://github.com/bluealloy/revm/compare/op-revm-v1.0.0...op-revm-v2.0.0) - 2025-03-28 + +### Added + +- cache precompile warming ([#2317](https://github.com/bluealloy/revm/pull/2317)) +- Add arkworks wrapper for bls12-381 ([#2316](https://github.com/bluealloy/revm/pull/2316)) +- provide more context to precompiles ([#2318](https://github.com/bluealloy/revm/pull/2318)) +- Add a wrapper for arkworks for EIP196 ([#2305](https://github.com/bluealloy/revm/pull/2305)) + +### Fixed + +- *(isthmus)* Correctly filter refunds for deposit transactions ([#2330](https://github.com/bluealloy/revm/pull/2330)) + +### Other + +- Remove LATEST variant from SpecId enum ([#2299](https://github.com/bluealloy/revm/pull/2299)) + +## [1.0.0](https://github.com/bluealloy/revm/compare/op-revm-v1.0.0-alpha.6...op-revm-v1.0.0) - 2025-03-24 + +### Other + +- *(op-precompiles)* Add test for checking that op default precompiles is updated ([#2291](https://github.com/bluealloy/revm/pull/2291)) +- *(op-precompiles)* Add missing g2 add tests ([#2253](https://github.com/bluealloy/revm/pull/2253)) + +## [1.0.0-alpha.6](https://github.com/bluealloy/revm/compare/op-revm-v1.0.0-alpha.5...op-revm-v1.0.0-alpha.6) - 2025-03-21 + +### Added + +- InspectEvm fn renames, inspector docs, book cleanup ([#2275](https://github.com/bluealloy/revm/pull/2275)) +- Return Fatal error on bls precompiles if in no_std ([#2249](https://github.com/bluealloy/revm/pull/2249)) +- Remove PrecompileError from PrecompileProvider ([#2233](https://github.com/bluealloy/revm/pull/2233)) + +### Fixed + +- *(op)* deposit txs are identifier 126 or 0x7e not 0x7f ([#2237](https://github.com/bluealloy/revm/pull/2237)) + +### Other + +- bring operator fee fixes to trunk ([#2273](https://github.com/bluealloy/revm/pull/2273)) +- *(op-test-cov)* Add test for serializing deposit transaction parts ([#2267](https://github.com/bluealloy/revm/pull/2267)) +- *(op-precompiles)* Check subset of l1 precompiles in op ([#2204](https://github.com/bluealloy/revm/pull/2204)) +- *(op-handler)* Add test for halted deposit tx post regolith ([#2269](https://github.com/bluealloy/revm/pull/2269)) +- *(op)* Remove redundant trait DepositTransaction ([#2265](https://github.com/bluealloy/revm/pull/2265)) +- Fix sys deposit tx gas test ([#2263](https://github.com/bluealloy/revm/pull/2263)) +- remove wrong `&mut` and duplicated spec ([#2276](https://github.com/bluealloy/revm/pull/2276)) +- *(op-precompiles)* clean up op tx tests ([#2242](https://github.com/bluealloy/revm/pull/2242)) +- make str to SpecId conversion fallible ([#2236](https://github.com/bluealloy/revm/pull/2236)) +- *(op-precompiles)* Add tests for bls12-381 map fp to g ([#2241](https://github.com/bluealloy/revm/pull/2241)) +- add a safe blst wrapper ([#2223](https://github.com/bluealloy/revm/pull/2223)) +- *(op-precompiles)* Reuse tests for bls12-381 msm tests for pairing ([#2239](https://github.com/bluealloy/revm/pull/2239)) +- *(op-precompiles)* add bls12-381 g2 add and msm tests ([#2231](https://github.com/bluealloy/revm/pull/2231)) +- *(op-precompiles)* Add test for g1 msm ([#2227](https://github.com/bluealloy/revm/pull/2227)) +- simplify single UT for OpSpecId compatibility. ([#2216](https://github.com/bluealloy/revm/pull/2216)) +- use AccessListItem associated type instead of AccessList ([#2214](https://github.com/bluealloy/revm/pull/2214)) + +## [1.0.0-alpha.5](https://github.com/bluealloy/revm/compare/op-revm-v1.0.0-alpha.4...op-revm-v1.0.0-alpha.5) - 2025-03-16 + +### Added + +- *(docs)* MyEvm example and book cleanup ([#2218](https://github.com/bluealloy/revm/pull/2218)) +- add test for calling `bn128_pair` before and after granite ([#2200](https://github.com/bluealloy/revm/pull/2200)) + +### Other + +- *(op-precompiles)* Add test for calling g1 add ([#2205](https://github.com/bluealloy/revm/pull/2205)) +- *(op-test)* Clean up precompile tests ([#2206](https://github.com/bluealloy/revm/pull/2206)) +- fix typo in method name ([#2202](https://github.com/bluealloy/revm/pull/2202)) +- Add tests for checking fjord precompile activation ([#2199](https://github.com/bluealloy/revm/pull/2199)) + +## [1.0.0-alpha.4](https://github.com/bluealloy/revm/compare/op-revm-v1.0.0-alpha.3...op-revm-v1.0.0-alpha.4) - 2025-03-12 + +### Added + +- Add tx/block to EvmExecution trait ([#2195](https://github.com/bluealloy/revm/pull/2195)) +- rename inspect_previous to inspect_replay ([#2194](https://github.com/bluealloy/revm/pull/2194)) + +### Other + +- add debug to precompiles type ([#2193](https://github.com/bluealloy/revm/pull/2193)) + +## [1.0.0-alpha.3](https://github.com/bluealloy/revm/compare/op-revm-v1.0.0-alpha.2...op-revm-v1.0.0-alpha.3) - 2025-03-11 + +### Fixed + +- fix(op) enable proper precompiles p256 ([#2186](https://github.com/bluealloy/revm/pull/2186)) +- *(op)* fix inspection call ([#2184](https://github.com/bluealloy/revm/pull/2184)) +- correct propagate features ([#2177](https://github.com/bluealloy/revm/pull/2177)) + +### Other + +- remove CTX phantomdata from precompile providers ([#2178](https://github.com/bluealloy/revm/pull/2178)) + +## [1.0.0-alpha.2](https://github.com/bluealloy/revm/compare/op-revm-v1.0.0-alpha.1...op-revm-v1.0.0-alpha.2) - 2025-03-10 + +### Other + +- updated the following local packages: revm + +## [1.0.0-alpha.1](https://github.com/bluealloy/revm/releases/tag/revm-optimism-v1.0.0-alpha.1) - 2025-02-16 + +### Added + +- Split Inspector trait from EthHandler into standalone crate (#2075) +- Introduce Auth and AccessList traits (#2079) +- derive Eq for OpSpec (#2073) +- *(op)* Isthmus precompiles (#2054) +- Evm structure (Cached Instructions and Precompiles) (#2049) +- simplify InspectorContext (#2036) +- Context execution (#2013) +- EthHandler trait (#2001) +- extract and export `estimate_tx_compressed_size` (#1985) +- *(EIP-7623)* Increase calldata cost. backport from rel/v51 (#1965) +- simplify Transaction trait (#1959) +- Split inspector.rs (#1958) +- align Block trait (#1957) +- expose precompile address in Journal, DB::Error: StdError (#1956) +- add isthmus spec (#1938) +- integrate codspeed (#1935) +- Make Ctx journal generic (#1933) +- Restucturing Part7 Handler and Context rework (#1865) +- restructuring Part6 transaction crate (#1814) +- Restructuring Part3 inspector crate (#1788) +- restructure Part2 database crate (#1784) +- project restructuring Part1 (#1776) +- introducing EvmWiring, a chain-specific configuration (#1672) +- *(examples)* generate block traces (#895) +- implement EIP-4844 (#668) +- *(Shanghai)* All EIPs: push0, warm coinbase, limit/measure initcode (#376) +- Migrate `primitive_types::U256` to `ruint::Uint<256, 4>` (#239) +- Introduce ByteCode format, Update Readme (#156) + +### Fixed + +- make macro crate-agnostic (#1802) +- fix typos ([#620](https://github.com/bluealloy/revm/pull/620)) + +### Other + +- set alpha.1 version +- backport op l1 fetch perf (#2076) +- remove OpSpec (#2074) +- Add helpers with_inspector with_precompile (#2063) +- *(op)* backport isthmus operator fee (#2059) +- Bump licence year to 2025 (#2058) +- rename OpHaltReason (#2042) +- simplify some generics (#2032) +- align crates versions (#1983) +- Make inspector use generics, rm associated types (#1934) +- add OpTransaction conversion tests (#1939) +- fix comments and docs into more sensible (#1920) +- Rename PRAGUE_EOF to OSAKA (#1903) +- refactor L1BlockInfo::tx_estimated_size_fjord (#1856) +- *(primitives)* replace HashMap re-exports with alloy_primitives::map (#1805) +- Test for l1 gas used and l1 fee for ecotone tx (#1748) +- *(deps)* bump anyhow from 1.0.88 to 1.0.89 (#1772) +- Bump new logo (#1735) +- *(README)* add rbuilder to used-by (#1585) +- added simular to used-by (#1521) +- add Trin to used by list (#1393) +- Fix typo in readme ([#1185](https://github.com/bluealloy/revm/pull/1185)) +- Add Hardhat to the "Used by" list ([#1164](https://github.com/bluealloy/revm/pull/1164)) +- Add VERBS to used by list ([#1141](https://github.com/bluealloy/revm/pull/1141)) +- license date and revm docs (#1080) +- *(docs)* Update the benchmark docs to point to revm package (#906) +- *(docs)* Update top-level benchmark docs (#894) +- clang requirement (#784) +- Readme Updates (#756) +- Logo (#743) +- book workflow ([#537](https://github.com/bluealloy/revm/pull/537)) +- add example to revm crate ([#468](https://github.com/bluealloy/revm/pull/468)) +- Update README.md ([#424](https://github.com/bluealloy/revm/pull/424)) +- add no_std to primitives ([#366](https://github.com/bluealloy/revm/pull/366)) +- revm-precompiles to revm-precompile +- Bump v20, changelog ([#350](https://github.com/bluealloy/revm/pull/350)) +- typos (#232) +- Add support for old forks. ([#191](https://github.com/bluealloy/revm/pull/191)) +- revm bump 1.8. update libs. snailtracer rename ([#159](https://github.com/bluealloy/revm/pull/159)) +- typo fixes +- fix readme typo +- Big Refactor. Machine to Interpreter. refactor instructions. call/create struct ([#52](https://github.com/bluealloy/revm/pull/52)) +- readme. debuger update +- Bump revm v0.3.0. README updated +- readme +- Add time elapsed for tests +- readme updated +- Include Basefee into cost calc. readme change +- Initialize precompile accounts +- Status update. Taking a break +- Merkle calc. Tweaks and debugging for eip158 +- Replace aurora bn lib with parity's. All Bn128Add/Mul/Pair tests passes +- TEMP +- one tab removed +- readme +- README Example simplified +- Gas calculation for Call/Create. Example Added +- readme usage +- README changes +- Static gas cost added +- Subroutine changelogs and reverts +- Readme postulates +- Spelling +- Restructure project +- First iteration. Machine is looking okay diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000000..27ba034738f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,75 @@ +[package] +name = "op-revm" +description = "Optimism variant of Revm" +version = "3.0.2" +authors.workspace = true +edition.workspace = true +keywords.workspace = true +license.workspace = true +repository.workspace = true +readme.workspace = true + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[lints.rust] +unreachable_pub = "warn" +unused_must_use = "deny" +rust_2018_idioms = "deny" + +[lints.rustdoc] +all = "warn" + +[dependencies] +# revm +revm = { workspace = true, features = ["secp256r1"] } +auto_impl.workspace = true + +# static precompile sets. +once_cell = { workspace = true, features = ["alloc"] } + +# Optional +serde = { workspace = true, features = ["derive", "rc"], optional = true } + +[dev-dependencies] +anyhow.workspace = true +rstest.workspace = true +alloy-sol-types.workspace = true +sha2.workspace = true +serde_json = { workspace = true, features = ["alloc"] } + +[features] +default = ["std", "c-kzg", "secp256k1", "portable", "blst"] +std = [ + "serde?/std", + "revm/std", + "alloy-sol-types/std", + "once_cell/std", + "sha2/std", + "serde_json/std", +] +hashbrown = ["revm/hashbrown"] +serde = ["dep:serde", "revm/serde"] +portable = ["revm/portable"] + +dev = [ + "memory_limit", + "optional_balance_check", + "optional_block_gas_limit", + "optional_eip3607", + "optional_no_base_fee", +] +memory_limit = ["revm/memory_limit"] +optional_balance_check = ["revm/optional_balance_check"] +optional_block_gas_limit = ["revm/optional_block_gas_limit"] +optional_eip3607 = ["revm/optional_eip3607"] +optional_no_base_fee = ["revm/optional_no_base_fee"] + +# See comments in `revm-precompile` +secp256k1 = ["revm/secp256k1"] +c-kzg = ["revm/c-kzg"] +# `kzg-rs` is not audited but useful for `no_std` environment, use it with causing and default to `c-kzg` if possible. +kzg-rs = ["revm/kzg-rs"] +blst = ["revm/blst"] +bn = ["revm/bn"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000000..be6d350ebe4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021-2025 draganrakita + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/api.rs b/src/api.rs new file mode 100644 index 00000000000..101ee66c477 --- /dev/null +++ b/src/api.rs @@ -0,0 +1,3 @@ +pub mod builder; +pub mod default_ctx; +pub mod exec; diff --git a/src/api/builder.rs b/src/api/builder.rs new file mode 100644 index 00000000000..20c980ee08a --- /dev/null +++ b/src/api/builder.rs @@ -0,0 +1,45 @@ +use crate::{evm::OpEvm, transaction::OpTxTr, L1BlockInfo, OpSpecId}; +use revm::{ + context::{Cfg, JournalOutput}, + context_interface::{Block, JournalTr}, + handler::instructions::EthInstructions, + interpreter::interpreter::EthInterpreter, + Context, Database, +}; + +/// Trait that allows for optimism OpEvm to be built. +pub trait OpBuilder: Sized { + /// Type of the context. + type Context; + + /// Build the op. + fn build_op(self) -> OpEvm>; + + /// Build the op with an inspector. + fn build_op_with_inspector( + self, + inspector: INSP, + ) -> OpEvm>; +} + +impl OpBuilder for Context +where + BLOCK: Block, + TX: OpTxTr, + CFG: Cfg, + DB: Database, + JOURNAL: JournalTr, +{ + type Context = Self; + + fn build_op(self) -> OpEvm> { + OpEvm::new(self, ()) + } + + fn build_op_with_inspector( + self, + inspector: INSP, + ) -> OpEvm> { + OpEvm::new(self, inspector) + } +} diff --git a/src/api/default_ctx.rs b/src/api/default_ctx.rs new file mode 100644 index 00000000000..17fa5de7651 --- /dev/null +++ b/src/api/default_ctx.rs @@ -0,0 +1,46 @@ +use crate::{L1BlockInfo, OpSpecId, OpTransaction}; +use revm::{ + context::{BlockEnv, CfgEnv, TxEnv}, + database_interface::EmptyDB, + Context, Journal, MainContext, +}; + +/// Type alias for the default context type of the OpEvm. +pub type OpContext = + Context, CfgEnv, DB, Journal, L1BlockInfo>; + +/// Trait that allows for a default context to be created. +pub trait DefaultOp { + /// Create a default context. + fn op() -> OpContext; +} + +impl DefaultOp for OpContext { + fn op() -> Self { + Context::mainnet() + .with_tx(OpTransaction::default()) + .with_cfg(CfgEnv::new_with_spec(OpSpecId::BEDROCK)) + .with_chain(L1BlockInfo::default()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::api::builder::OpBuilder; + use revm::{ + inspector::{InspectEvm, NoOpInspector}, + ExecuteEvm, + }; + + #[test] + fn default_run_op() { + let ctx = Context::op(); + // convert to optimism context + let mut evm = ctx.build_op_with_inspector(NoOpInspector {}); + // execute + let _ = evm.replay(); + // inspect + let _ = evm.inspect_replay(); + } +} diff --git a/src/api/exec.rs b/src/api/exec.rs new file mode 100644 index 00000000000..e5c822492fe --- /dev/null +++ b/src/api/exec.rs @@ -0,0 +1,135 @@ +use crate::{ + evm::OpEvm, handler::OpHandler, transaction::OpTxTr, L1BlockInfo, OpHaltReason, OpSpecId, + OpTransactionError, +}; +use revm::{ + context::{ContextSetters, JournalOutput}, + context_interface::{ + result::{EVMError, ExecutionResult, ResultAndState}, + Cfg, ContextTr, Database, JournalTr, + }, + handler::{ + instructions::EthInstructions, system_call::SystemCallEvm, EthFrame, EvmTr, Handler, + PrecompileProvider, SystemCallTx, + }, + inspector::{InspectCommitEvm, InspectEvm, Inspector, InspectorHandler, JournalExt}, + interpreter::{interpreter::EthInterpreter, InterpreterResult}, + DatabaseCommit, ExecuteCommitEvm, ExecuteEvm, +}; + +// Type alias for Optimism context +pub trait OpContextTr: + ContextTr< + Journal: JournalTr, + Tx: OpTxTr, + Cfg: Cfg, + Chain = L1BlockInfo, +> +{ +} + +impl OpContextTr for T where + T: ContextTr< + Journal: JournalTr, + Tx: OpTxTr, + Cfg: Cfg, + Chain = L1BlockInfo, + > +{ +} + +/// Type alias for the error type of the OpEvm. +type OpError = EVMError<<::Db as Database>::Error, OpTransactionError>; + +impl ExecuteEvm + for OpEvm, PRECOMPILE> +where + CTX: OpContextTr + ContextSetters, + PRECOMPILE: PrecompileProvider, +{ + type Output = Result, OpError>; + + type Tx = ::Tx; + + type Block = ::Block; + + fn set_tx(&mut self, tx: Self::Tx) { + self.0.ctx.set_tx(tx); + } + + fn set_block(&mut self, block: Self::Block) { + self.0.ctx.set_block(block); + } + + fn replay(&mut self) -> Self::Output { + let mut h = OpHandler::<_, _, EthFrame<_, _, _>>::new(); + h.run(self) + } +} + +impl ExecuteCommitEvm + for OpEvm, PRECOMPILE> +where + CTX: OpContextTr + ContextSetters, + PRECOMPILE: PrecompileProvider, +{ + type CommitOutput = Result, OpError>; + + fn replay_commit(&mut self) -> Self::CommitOutput { + self.replay().map(|r| { + self.ctx().db().commit(r.state); + r.result + }) + } +} + +impl InspectEvm + for OpEvm, PRECOMPILE> +where + CTX: OpContextTr + ContextSetters, + INSP: Inspector, + PRECOMPILE: PrecompileProvider, +{ + type Inspector = INSP; + + fn set_inspector(&mut self, inspector: Self::Inspector) { + self.0.inspector = inspector; + } + + fn inspect_replay(&mut self) -> Self::Output { + let mut h = OpHandler::<_, _, EthFrame<_, _, _>>::new(); + h.inspect_run(self) + } +} + +impl InspectCommitEvm + for OpEvm, PRECOMPILE> +where + CTX: OpContextTr + ContextSetters, + INSP: Inspector, + PRECOMPILE: PrecompileProvider, +{ + fn inspect_replay_commit(&mut self) -> Self::CommitOutput { + self.inspect_replay().map(|r| { + self.ctx().db().commit(r.state); + r.result + }) + } +} + +impl SystemCallEvm + for OpEvm, PRECOMPILE> +where + CTX: OpContextTr + ContextSetters, + PRECOMPILE: PrecompileProvider, +{ + fn transact_system_call( + &mut self, + data: revm::primitives::Bytes, + system_contract_address: revm::primitives::Address, + ) -> Self::Output { + self.set_tx(CTX::Tx::new_system_tx(data, system_contract_address)); + let mut h = OpHandler::<_, _, EthFrame<_, _, _>>::new(); + h.run_system_call(self) + } +} diff --git a/src/constants.rs b/src/constants.rs new file mode 100644 index 00000000000..3d6c79c6dce --- /dev/null +++ b/src/constants.rs @@ -0,0 +1,53 @@ +use revm::primitives::{address, Address, U256}; + +pub const ZERO_BYTE_COST: u64 = 4; +pub const NON_ZERO_BYTE_COST: u64 = 16; + +/// The two 4-byte Ecotone fee scalar values are packed into the same storage slot as the 8-byte sequence number. +/// Byte offset within the storage slot of the 4-byte baseFeeScalar attribute. +pub const BASE_FEE_SCALAR_OFFSET: usize = 16; +/// The two 4-byte Ecotone fee scalar values are packed into the same storage slot as the 8-byte sequence number. +/// Byte offset within the storage slot of the 4-byte blobBaseFeeScalar attribute. +pub const BLOB_BASE_FEE_SCALAR_OFFSET: usize = 20; + +/// The Isthmus operator fee scalar values are similarly packed. Byte offset within +/// the storage slot of the 4-byte operatorFeeScalar attribute. +pub const OPERATOR_FEE_SCALAR_OFFSET: usize = 20; +/// The Isthmus operator fee scalar values are similarly packed. Byte offset within +/// the storage slot of the 8-byte operatorFeeConstant attribute. +pub const OPERATOR_FEE_CONSTANT_OFFSET: usize = 24; + +/// The fixed point decimal scaling factor associated with the operator fee scalar. +/// +/// Allows users to use 6 decimal points of precision when specifying the operator_fee_scalar. +pub const OPERATOR_FEE_SCALAR_DECIMAL: u64 = 1_000_000; + +pub const L1_BASE_FEE_SLOT: U256 = U256::from_limbs([1u64, 0, 0, 0]); +pub const L1_OVERHEAD_SLOT: U256 = U256::from_limbs([5u64, 0, 0, 0]); +pub const L1_SCALAR_SLOT: U256 = U256::from_limbs([6u64, 0, 0, 0]); + +/// [ECOTONE_L1_BLOB_BASE_FEE_SLOT] was added in the Ecotone upgrade and stores the L1 blobBaseFee attribute. +pub const ECOTONE_L1_BLOB_BASE_FEE_SLOT: U256 = U256::from_limbs([7u64, 0, 0, 0]); + +/// As of the ecotone upgrade, this storage slot stores the 32-bit basefeeScalar and blobBaseFeeScalar attributes at +/// offsets [BASE_FEE_SCALAR_OFFSET] and [BLOB_BASE_FEE_SCALAR_OFFSET] respectively. +pub const ECOTONE_L1_FEE_SCALARS_SLOT: U256 = U256::from_limbs([3u64, 0, 0, 0]); + +/// This storage slot stores the 32-bit operatorFeeScalar and operatorFeeConstant attributes at +/// offsets [OPERATOR_FEE_SCALAR_OFFSET] and [OPERATOR_FEE_CONSTANT_OFFSET] respectively. +pub const OPERATOR_FEE_SCALARS_SLOT: U256 = U256::from_limbs([8u64, 0, 0, 0]); + +/// An empty 64-bit set of scalar values. +pub const EMPTY_SCALARS: [u8; 8] = [0u8; 8]; + +/// The address of L1 fee recipient. +pub const L1_FEE_RECIPIENT: Address = address!("0x420000000000000000000000000000000000001A"); + +/// The address of the operator fee recipient. +pub const OPERATOR_FEE_RECIPIENT: Address = address!("0x420000000000000000000000000000000000001B"); + +/// The address of the base fee recipient. +pub const BASE_FEE_RECIPIENT: Address = address!("0x4200000000000000000000000000000000000019"); + +/// The address of the L1Block contract. +pub const L1_BLOCK_CONTRACT: Address = address!("0x4200000000000000000000000000000000000015"); diff --git a/src/evm.rs b/src/evm.rs new file mode 100644 index 00000000000..45c530c97d7 --- /dev/null +++ b/src/evm.rs @@ -0,0 +1,832 @@ +use crate::precompiles::OpPrecompiles; +use revm::{ + context::{ContextSetters, Evm}, + context_interface::ContextTr, + handler::{ + instructions::{EthInstructions, InstructionProvider}, + EvmTr, PrecompileProvider, + }, + inspector::{InspectorEvmTr, JournalExt}, + interpreter::{interpreter::EthInterpreter, Interpreter, InterpreterAction, InterpreterTypes}, + Inspector, +}; + +pub struct OpEvm, P = OpPrecompiles>( + pub Evm, +); + +impl OpEvm, OpPrecompiles> { + pub fn new(ctx: CTX, inspector: INSP) -> Self { + Self(Evm { + ctx, + inspector, + instruction: EthInstructions::new_mainnet(), + precompiles: OpPrecompiles::default(), + }) + } +} + +impl OpEvm { + /// Consumed self and returns a new Evm type with given Inspector. + pub fn with_inspector(self, inspector: OINSP) -> OpEvm { + OpEvm(self.0.with_inspector(inspector)) + } + + /// Consumes self and returns a new Evm type with given Precompiles. + pub fn with_precompiles(self, precompiles: OP) -> OpEvm { + OpEvm(self.0.with_precompiles(precompiles)) + } + + /// Consumes self and returns the inner Inspector. + pub fn into_inspector(self) -> INSP { + self.0.into_inspector() + } +} + +impl InspectorEvmTr for OpEvm +where + CTX: ContextTr + ContextSetters, + I: InstructionProvider< + Context = CTX, + InterpreterTypes: InterpreterTypes, + >, + P: PrecompileProvider, + INSP: Inspector, +{ + type Inspector = INSP; + + fn inspector(&mut self) -> &mut Self::Inspector { + &mut self.0.inspector + } + + fn ctx_inspector(&mut self) -> (&mut Self::Context, &mut Self::Inspector) { + (&mut self.0.ctx, &mut self.0.inspector) + } + + fn run_inspect_interpreter( + &mut self, + interpreter: &mut Interpreter< + ::InterpreterTypes, + >, + ) -> <::InterpreterTypes as InterpreterTypes>::Output + { + self.0.run_inspect_interpreter(interpreter) + } +} + +impl EvmTr for OpEvm +where + CTX: ContextTr, + I: InstructionProvider< + Context = CTX, + InterpreterTypes: InterpreterTypes, + >, + P: PrecompileProvider, +{ + type Context = CTX; + type Instructions = I; + type Precompiles = P; + + fn run_interpreter( + &mut self, + interpreter: &mut Interpreter< + ::InterpreterTypes, + >, + ) -> <::InterpreterTypes as InterpreterTypes>::Output + { + let context = &mut self.0.ctx; + let instructions = &mut self.0.instruction; + interpreter.run_plain(instructions.instruction_table(), context) + } + + fn ctx(&mut self) -> &mut Self::Context { + &mut self.0.ctx + } + + fn ctx_ref(&self) -> &Self::Context { + &self.0.ctx + } + + fn ctx_instructions(&mut self) -> (&mut Self::Context, &mut Self::Instructions) { + (&mut self.0.ctx, &mut self.0.instruction) + } + + fn ctx_precompiles(&mut self) -> (&mut Self::Context, &mut Self::Precompiles) { + (&mut self.0.ctx, &mut self.0.precompiles) + } +} + +#[cfg(test)] +mod tests { + use crate::{ + precompiles::bn128_pair::GRANITE_MAX_INPUT_SIZE, + transaction::deposit::DEPOSIT_TRANSACTION_TYPE, DefaultOp, L1BlockInfo, OpBuilder, + OpHaltReason, OpSpecId, OpTransaction, + }; + use revm::{ + bytecode::opcode, + context::{ + result::{ExecutionResult, OutOfGasError}, + BlockEnv, CfgEnv, TxEnv, + }, + context_interface::result::HaltReason, + database::{BenchmarkDB, EmptyDB, BENCH_CALLER, BENCH_CALLER_BALANCE, BENCH_TARGET}, + interpreter::{ + gas::{calculate_initial_tx_gas, InitialAndFloorGas}, + Interpreter, InterpreterTypes, + }, + precompile::{bls12_381_const, bls12_381_utils, bn128, secp256r1, u64_to_address}, + primitives::{Address, Bytes, Log, TxKind, U256}, + state::Bytecode, + Context, ExecuteEvm, InspectEvm, Inspector, Journal, + }; + use std::vec::Vec; + + #[test] + fn test_deposit_tx() { + let ctx = Context::op() + .modify_tx_chained(|tx| { + tx.enveloped_tx = None; + tx.deposit.mint = Some(100); + tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE; + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::HOLOCENE); + + let mut evm = ctx.build_op(); + + let output = evm.replay().unwrap(); + + // balance should be 100 + assert_eq!( + output + .state + .get(&Address::default()) + .map(|a| a.info.balance), + Some(U256::from(100)) + ); + } + + #[test] + fn test_halted_deposit_tx() { + let ctx = Context::op() + .modify_tx_chained(|tx| { + tx.enveloped_tx = None; + tx.deposit.mint = Some(100); + tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE; + tx.base.caller = BENCH_CALLER; + tx.base.kind = TxKind::Call(BENCH_TARGET); + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::HOLOCENE) + .with_db(BenchmarkDB::new_bytecode(Bytecode::new_legacy( + [opcode::POP].into(), + ))); + + // POP would return a halt. + let mut evm = ctx.build_op(); + + let output = evm.replay().unwrap(); + + // balance should be 100 + previous balance + assert_eq!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::FailedDeposit, + gas_used: 30_000_000 + } + ); + assert_eq!( + output.state.get(&BENCH_CALLER).map(|a| a.info.balance), + Some(U256::from(100) + BENCH_CALLER_BALANCE) + ); + } + + fn p256verify_test_tx() -> Context< + BlockEnv, + OpTransaction, + CfgEnv, + EmptyDB, + Journal, + L1BlockInfo, + > { + const SPEC_ID: OpSpecId = OpSpecId::FJORD; + + let InitialAndFloorGas { initial_gas, .. } = + calculate_initial_tx_gas(SPEC_ID.into(), &[], false, 0, 0, 0); + + Context::op() + .modify_tx_chained(|tx| { + tx.base.kind = TxKind::Call(u64_to_address(secp256r1::P256VERIFY_ADDRESS)); + tx.base.gas_limit = initial_gas + secp256r1::P256VERIFY_BASE_GAS_FEE; + }) + .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID) + } + + #[test] + fn test_tx_call_p256verify() { + let ctx = p256verify_test_tx(); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert successful call to P256VERIFY + assert!(output.result.is_success()); + } + + #[test] + fn test_halted_tx_call_p256verify() { + let ctx = p256verify_test_tx().modify_tx_chained(|tx| tx.base.gas_limit -= 1); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert out of gas for P256VERIFY + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), + .. + } + )); + } + + fn bn128_pair_test_tx( + spec: OpSpecId, + ) -> Context< + BlockEnv, + OpTransaction, + CfgEnv, + EmptyDB, + Journal, + L1BlockInfo, + > { + let input = Bytes::from([1; GRANITE_MAX_INPUT_SIZE + 2]); + let InitialAndFloorGas { initial_gas, .. } = + calculate_initial_tx_gas(spec.into(), &input[..], false, 0, 0, 0); + + Context::op() + .modify_tx_chained(|tx| { + tx.base.kind = TxKind::Call(bn128::pair::ADDRESS); + tx.base.data = input; + tx.base.gas_limit = initial_gas; + }) + .modify_cfg_chained(|cfg| cfg.spec = spec) + } + + #[test] + fn test_halted_tx_call_bn128_pair_fjord() { + let ctx = bn128_pair_test_tx(OpSpecId::FJORD); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert out of gas + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), + .. + } + )); + } + + #[test] + fn test_halted_tx_call_bn128_pair_granite() { + let ctx = bn128_pair_test_tx(OpSpecId::GRANITE); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert bails early because input size too big + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::PrecompileError), + .. + } + )); + } + + #[test] + fn test_halted_tx_call_bls12_381_g1_add_out_of_gas() { + let ctx = Context::op() + .modify_tx_chained(|tx| { + tx.base.kind = TxKind::Call(bls12_381_const::G1_ADD_ADDRESS); + tx.base.gas_limit = 21_000 + bls12_381_const::G1_ADD_BASE_GAS_FEE - 1; + }) + .modify_chain_chained(|l1_block| { + l1_block.operator_fee_constant = Some(U256::ZERO); + l1_block.operator_fee_scalar = Some(U256::ZERO) + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); + + let mut evm = ctx.build_op(); + + let output = evm.replay().unwrap(); + + // assert out of gas + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), + .. + } + )); + } + + #[test] + fn test_halted_tx_call_bls12_381_g1_add_input_wrong_size() { + let ctx = Context::op() + .modify_tx_chained(|tx| { + tx.base.kind = TxKind::Call(bls12_381_const::G1_ADD_ADDRESS); + tx.base.gas_limit = 21_000 + bls12_381_const::G1_ADD_BASE_GAS_FEE; + }) + .modify_chain_chained(|l1_block| { + l1_block.operator_fee_constant = Some(U256::ZERO); + l1_block.operator_fee_scalar = Some(U256::ZERO) + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert fails post gas check, because input is wrong size + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::PrecompileError), + .. + } + )); + } + + fn g1_msm_test_tx() -> Context< + BlockEnv, + OpTransaction, + CfgEnv, + EmptyDB, + Journal, + L1BlockInfo, + > { + const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; + + let input = Bytes::from([1; bls12_381_const::G1_MSM_INPUT_LENGTH]); + let InitialAndFloorGas { initial_gas, .. } = + calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); + let gs1_msm_gas = bls12_381_utils::msm_required_gas( + 1, + &bls12_381_const::DISCOUNT_TABLE_G1_MSM, + bls12_381_const::G1_MSM_BASE_GAS_FEE, + ); + + Context::op() + .modify_tx_chained(|tx| { + tx.base.kind = TxKind::Call(bls12_381_const::G1_MSM_ADDRESS); + tx.base.data = input; + tx.base.gas_limit = initial_gas + gs1_msm_gas; + }) + .modify_chain_chained(|l1_block| { + l1_block.operator_fee_constant = Some(U256::ZERO); + l1_block.operator_fee_scalar = Some(U256::ZERO) + }) + .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID) + } + + #[test] + fn test_halted_tx_call_bls12_381_g1_msm_input_wrong_size() { + let ctx = g1_msm_test_tx().modify_tx_chained(|tx| tx.base.data = tx.base.data.slice(1..)); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert fails pre gas check, because input is wrong size + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::PrecompileError), + .. + } + )); + } + + #[test] + fn test_halted_tx_call_bls12_381_g1_msm_out_of_gas() { + let ctx = g1_msm_test_tx().modify_tx_chained(|tx| tx.base.gas_limit -= 1); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert out of gas + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), + .. + } + )); + } + + #[test] + fn test_halted_tx_call_bls12_381_g1_msm_wrong_input_layout() { + let ctx = g1_msm_test_tx(); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert fails post gas check, because input is wrong layout + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::PrecompileError), + .. + } + )); + } + + #[test] + fn test_halted_tx_call_bls12_381_g2_add_out_of_gas() { + let ctx = Context::op() + .modify_tx_chained(|tx| { + tx.base.kind = TxKind::Call(bls12_381_const::G2_ADD_ADDRESS); + tx.base.gas_limit = 21_000 + bls12_381_const::G2_ADD_BASE_GAS_FEE - 1; + }) + .modify_chain_chained(|l1_block| { + l1_block.operator_fee_constant = Some(U256::ZERO); + l1_block.operator_fee_scalar = Some(U256::ZERO) + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); + + let mut evm = ctx.build_op(); + + let output = evm.replay().unwrap(); + + // assert out of gas + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), + .. + } + )); + } + + #[test] + fn test_halted_tx_call_bls12_381_g2_add_input_wrong_size() { + let ctx = Context::op() + .modify_tx_chained(|tx| { + tx.base.kind = TxKind::Call(bls12_381_const::G2_ADD_ADDRESS); + tx.base.gas_limit = 21_000 + bls12_381_const::G2_ADD_BASE_GAS_FEE; + }) + .modify_chain_chained(|l1_block| { + l1_block.operator_fee_constant = Some(U256::ZERO); + l1_block.operator_fee_scalar = Some(U256::ZERO) + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); + + let mut evm = ctx.build_op(); + + let output = evm.replay().unwrap(); + + // assert fails post gas check, because input is wrong size + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::PrecompileError), + .. + } + )); + } + + fn g2_msm_test_tx() -> Context< + BlockEnv, + OpTransaction, + CfgEnv, + EmptyDB, + Journal, + L1BlockInfo, + > { + const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; + + let input = Bytes::from([1; bls12_381_const::G2_MSM_INPUT_LENGTH]); + let InitialAndFloorGas { initial_gas, .. } = + calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); + let gs2_msm_gas = bls12_381_utils::msm_required_gas( + 1, + &bls12_381_const::DISCOUNT_TABLE_G2_MSM, + bls12_381_const::G2_MSM_BASE_GAS_FEE, + ); + + Context::op() + .modify_tx_chained(|tx| { + tx.base.kind = TxKind::Call(bls12_381_const::G2_MSM_ADDRESS); + tx.base.data = input; + tx.base.gas_limit = initial_gas + gs2_msm_gas; + }) + .modify_chain_chained(|l1_block| { + l1_block.operator_fee_constant = Some(U256::ZERO); + l1_block.operator_fee_scalar = Some(U256::ZERO) + }) + .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID) + } + + #[test] + fn test_halted_tx_call_bls12_381_g2_msm_input_wrong_size() { + let ctx = g2_msm_test_tx().modify_tx_chained(|tx| tx.base.data = tx.base.data.slice(1..)); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert fails pre gas check, because input is wrong size + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::PrecompileError), + .. + } + )); + } + + #[test] + fn test_halted_tx_call_bls12_381_g2_msm_out_of_gas() { + let ctx = g2_msm_test_tx().modify_tx_chained(|tx| tx.base.gas_limit -= 1); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert out of gas + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), + .. + } + )); + } + + #[test] + fn test_halted_tx_call_bls12_381_g2_msm_wrong_input_layout() { + let ctx = g2_msm_test_tx(); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert fails post gas check, because input is wrong layout + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::PrecompileError), + .. + } + )); + } + + fn bl12_381_pairing_test_tx() -> Context< + BlockEnv, + OpTransaction, + CfgEnv, + EmptyDB, + Journal, + L1BlockInfo, + > { + const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; + + let input = Bytes::from([1; bls12_381_const::PAIRING_INPUT_LENGTH]); + let InitialAndFloorGas { initial_gas, .. } = + calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); + + let pairing_gas: u64 = + bls12_381_const::PAIRING_MULTIPLIER_BASE + bls12_381_const::PAIRING_OFFSET_BASE; + + Context::op() + .modify_tx_chained(|tx| { + tx.base.kind = TxKind::Call(bls12_381_const::PAIRING_ADDRESS); + tx.base.data = input; + tx.base.gas_limit = initial_gas + pairing_gas; + }) + .modify_chain_chained(|l1_block| { + l1_block.operator_fee_constant = Some(U256::ZERO); + l1_block.operator_fee_scalar = Some(U256::ZERO) + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS) + } + + #[test] + fn test_halted_tx_call_bls12_381_pairing_input_wrong_size() { + let ctx = bl12_381_pairing_test_tx() + .modify_tx_chained(|tx| tx.base.data = tx.base.data.slice(1..)); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert fails pre gas check, because input is wrong size + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::PrecompileError), + .. + } + )); + } + + #[test] + fn test_halted_tx_call_bls12_381_pairing_out_of_gas() { + let ctx = bl12_381_pairing_test_tx().modify_tx_chained(|tx| tx.base.gas_limit -= 1); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert out of gas + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), + .. + } + )); + } + + #[test] + fn test_tx_call_bls12_381_pairing_wrong_input_layout() { + let ctx = bl12_381_pairing_test_tx(); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert fails post gas check, because input is wrong layout + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::PrecompileError), + .. + } + )); + } + + fn fp_to_g1_test_tx() -> Context< + BlockEnv, + OpTransaction, + CfgEnv, + EmptyDB, + Journal, + L1BlockInfo, + > { + const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; + + let input = Bytes::from([1; bls12_381_const::PADDED_FP_LENGTH]); + let InitialAndFloorGas { initial_gas, .. } = + calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); + + Context::op() + .modify_tx_chained(|tx| { + tx.base.kind = TxKind::Call(bls12_381_const::MAP_FP_TO_G1_ADDRESS); + tx.base.data = input; + tx.base.gas_limit = initial_gas + bls12_381_const::MAP_FP_TO_G1_BASE_GAS_FEE; + }) + .modify_chain_chained(|l1_block| { + l1_block.operator_fee_constant = Some(U256::ZERO); + l1_block.operator_fee_scalar = Some(U256::ZERO) + }) + .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID) + } + + #[test] + fn test_halted_tx_call_bls12_381_map_fp_to_g1_out_of_gas() { + let ctx = fp_to_g1_test_tx().modify_tx_chained(|tx| tx.base.gas_limit -= 1); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert out of gas + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), + .. + } + )); + } + + #[test] + fn test_halted_tx_call_bls12_381_map_fp_to_g1_input_wrong_size() { + let ctx = fp_to_g1_test_tx().modify_tx_chained(|tx| tx.base.data = tx.base.data.slice(1..)); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert fails post gas check, because input is wrong size + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::PrecompileError), + .. + } + )); + } + + fn fp2_to_g2_test_tx() -> Context< + BlockEnv, + OpTransaction, + CfgEnv, + EmptyDB, + Journal, + L1BlockInfo, + > { + const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; + + let input = Bytes::from([1; bls12_381_const::PADDED_FP2_LENGTH]); + let InitialAndFloorGas { initial_gas, .. } = + calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); + + Context::op() + .modify_tx_chained(|tx| { + tx.base.kind = TxKind::Call(bls12_381_const::MAP_FP2_TO_G2_ADDRESS); + tx.base.data = input; + tx.base.gas_limit = initial_gas + bls12_381_const::MAP_FP2_TO_G2_BASE_GAS_FEE; + }) + .modify_chain_chained(|l1_block| { + l1_block.operator_fee_constant = Some(U256::ZERO); + l1_block.operator_fee_scalar = Some(U256::ZERO) + }) + .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID) + } + + #[test] + fn test_halted_tx_call_bls12_381_map_fp2_to_g2_out_of_gas() { + let ctx = fp2_to_g2_test_tx().modify_tx_chained(|tx| tx.base.gas_limit -= 1); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert out of gas + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), + .. + } + )); + } + + #[test] + fn test_halted_tx_call_bls12_381_map_fp2_to_g2_input_wrong_size() { + let ctx = + fp2_to_g2_test_tx().modify_tx_chained(|tx| tx.base.data = tx.base.data.slice(1..)); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert fails post gas check, because input is wrong size + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::PrecompileError), + .. + } + )); + } + + #[derive(Default, Debug)] + struct LogInspector { + logs: Vec, + } + + impl Inspector for LogInspector { + fn log(&mut self, _interp: &mut Interpreter, _context: &mut CTX, log: Log) { + self.logs.push(log) + } + } + + #[test] + fn test_log_inspector() { + // simple yul contract emits a log in constructor + + /*object "Contract" { + code { + log0(0, 0) + } + }*/ + + let contract_data: Bytes = Bytes::from([ + opcode::PUSH1, + 0x00, + opcode::DUP1, + opcode::LOG0, + opcode::STOP, + ]); + let bytecode = Bytecode::new_raw(contract_data); + + let ctx = Context::op() + .with_db(BenchmarkDB::new_bytecode(bytecode.clone())) + .modify_tx_chained(|tx| { + tx.base.caller = BENCH_CALLER; + tx.base.kind = TxKind::Call(BENCH_TARGET); + }); + + let mut evm = ctx.build_op_with_inspector(LogInspector::default()); + + // Run evm. + let _ = evm.inspect_replay().unwrap(); + + let inspector = &evm.0.inspector; + assert!(!inspector.logs.is_empty()); + } +} diff --git a/src/fast_lz.rs b/src/fast_lz.rs new file mode 100644 index 00000000000..c3a21bb1538 --- /dev/null +++ b/src/fast_lz.rs @@ -0,0 +1,187 @@ +/// Returns the length of the data after compression through FastLZ, based on +/// +/// +/// The u32s match op-geth's Go port: +/// +pub(crate) fn flz_compress_len(input: &[u8]) -> u32 { + let mut idx: u32 = 2; + + let idx_limit: u32 = if input.len() < 13 { + 0 + } else { + input.len() as u32 - 13 + }; + + let mut anchor = 0; + + let mut size = 0; + + let mut htab = [0; 8192]; + + while idx < idx_limit { + let mut r: u32; + let mut distance: u32; + + loop { + let seq = u24(input, idx); + let hash = hash(seq); + r = htab[hash as usize]; + htab[hash as usize] = idx; + distance = idx - r; + if idx >= idx_limit { + break; + } + idx += 1; + if distance < 8192 && seq == u24(input, r) { + break; + } + } + + if idx >= idx_limit { + break; + } + + idx -= 1; + + if idx > anchor { + size = literals(idx - anchor, size); + } + + let len = cmp(input, r + 3, idx + 3, idx_limit + 9); + size = flz_match(len, size); + + idx = set_next_hash(&mut htab, input, idx + len); + idx = set_next_hash(&mut htab, input, idx); + anchor = idx; + } + + literals(input.len() as u32 - anchor, size) +} + +fn literals(r: u32, size: u32) -> u32 { + let size = size + 0x21 * (r / 0x20); + let r = r % 0x20; + if r != 0 { + size + r + 1 + } else { + size + } +} + +fn cmp(input: &[u8], p: u32, q: u32, r: u32) -> u32 { + let mut l = 0; + let mut r = r - q; + while l < r { + if input[(p + l) as usize] != input[(q + l) as usize] { + r = 0; + } + l += 1; + } + l +} + +fn flz_match(l: u32, size: u32) -> u32 { + let l = l - 1; + let size = size + (3 * (l / 262)); + if l % 262 >= 6 { + size + 3 + } else { + size + 2 + } +} + +fn set_next_hash(htab: &mut [u32; 8192], input: &[u8], idx: u32) -> u32 { + htab[hash(u24(input, idx)) as usize] = idx; + idx + 1 +} + +fn hash(v: u32) -> u16 { + let hash = (v as u64 * 2654435769) >> 19; + hash as u16 & 0x1fff +} + +fn u24(input: &[u8], idx: u32) -> u32 { + u32::from(input[idx as usize]) + + (u32::from(input[(idx + 1) as usize]) << 8) + + (u32::from(input[(idx + 2) as usize]) << 16) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::api::{builder::OpBuilder, default_ctx::DefaultOp}; + use alloy_sol_types::sol; + use alloy_sol_types::SolCall; + use revm::{ + bytecode::Bytecode, + database::{BenchmarkDB, EEADDRESS, FFADDRESS}, + primitives::{bytes, Bytes, TxKind, U256}, + }; + use revm::{Context, ExecuteEvm}; + use rstest::rstest; + use std::vec::Vec; + + #[rstest] + #[case::empty(&[], 0)] + #[case::thousand_zeros(&[0; 1000], 21)] + #[case::thousand_forty_twos(&[42; 1000], 21)] + #[case::short_hex(&bytes!("FACADE"), 4)] + #[case::sample_contract_call(&bytes!("02f901550a758302df1483be21b88304743f94f80e51afb613d764fa61751affd3313c190a86bb870151bd62fd12adb8e41ef24f3f000000000000000000000000000000000000000000000000000000000000006e000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e5831000000000000000000000000000000000000000000000000000000000003c1e5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000148c89ed219d02f1a5be012c689b4f5b731827bebe000000000000000000000000c001a033fd89cb37c31b2cba46b6466e040c61fc9b2a3675a7f5f493ebd5ad77c497f8a07cdf65680e238392693019b4092f610222e71b7cec06449cb922b93b6a12744e"), 202)] + #[case::base_0x5dadeb52979f29fc7a7494c43fdabc5be1d8ff404f3aafe93d729fa8e5d00769(&bytes!("b9047c02f904788221050883036ee48409c6c87383037f6f941195cf65f83b3a5768f3c496d3a05ad6412c64b78644364c5bb000b90404d123b4d80000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000038000000000000000000000000000000000f6476f90447748c19248ccaa31e6b8bfda4eb9d830f5f47df7f0998f7c2123d9e6137761b75d3184efb0f788e3b14516000000000000000000000000000000000000000000000000000044364c5bb000000000000000000000000000f38e53bd45c8225a7c94b513beadaa7afe5d222d0000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084d6574614d61736b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035697066733a2f2f516d656852577a743347745961776343347564745657557233454c587261436746434259416b66507331696f48610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000cd0d83d9e840f8e27d5c2e365fd365ff1c05b2480000000000000000000000000000000000000000000000000000000000000ce40000000000000000000000000000000000000000000000000000000000000041e4480d358dbae20880960a0a464d63b06565a0c9f9b1b37aa94b522247b23ce149c81359bf4239d1a879eeb41047ec710c15f5c0f67453da59a383e6abd742971c00000000000000000000000000000000000000000000000000000000000000c001a0b57f0ff8516ea29cb26a44ac5055a5420847d1e16a8e7b03b70f0c02291ff2d5a00ad3771e5f39ccacfff0faa8c5d25ef7a1c179f79e66e828ffddcb994c8b512e"), 471)] + fn test_flz_compress_len(#[case] input: &[u8], #[case] expected: u32) { + assert_eq!(flz_compress_len(input), expected); + } + + #[test] + fn test_flz_compress_len_no_repeats() { + let mut input = Vec::new(); + let mut len = 0; + + for i in 0..256 { + input.push(i as u8); + let prev_len = len; + len = flz_compress_len(&input); + assert!(len > prev_len); + } + } + + #[rstest] + #[case::short_hex(bytes!("FACADE"))] + #[case::sample_contract_call(bytes!("02f901550a758302df1483be21b88304743f94f80e51afb613d764fa61751affd3313c190a86bb870151bd62fd12adb8e41ef24f3f000000000000000000000000000000000000000000000000000000000000006e000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e5831000000000000000000000000000000000000000000000000000000000003c1e5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000148c89ed219d02f1a5be012c689b4f5b731827bebe000000000000000000000000c001a033fd89cb37c31b2cba46b6466e040c61fc9b2a3675a7f5f493ebd5ad77c497f8a07cdf65680e238392693019b4092f610222e71b7cec06449cb922b93b6a12744e"))] + #[case::base_0x5dadeb52979f29fc7a7494c43fdabc5be1d8ff404f3aafe93d729fa8e5d00769(bytes!("b9047c02f904788221050883036ee48409c6c87383037f6f941195cf65f83b3a5768f3c496d3a05ad6412c64b78644364c5bb000b90404d123b4d80000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000038000000000000000000000000000000000f6476f90447748c19248ccaa31e6b8bfda4eb9d830f5f47df7f0998f7c2123d9e6137761b75d3184efb0f788e3b14516000000000000000000000000000000000000000000000000000044364c5bb000000000000000000000000000f38e53bd45c8225a7c94b513beadaa7afe5d222d0000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000002c000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084d6574614d61736b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035697066733a2f2f516d656852577a743347745961776343347564745657557233454c587261436746434259416b66507331696f48610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000cd0d83d9e840f8e27d5c2e365fd365ff1c05b2480000000000000000000000000000000000000000000000000000000000000ce40000000000000000000000000000000000000000000000000000000000000041e4480d358dbae20880960a0a464d63b06565a0c9f9b1b37aa94b522247b23ce149c81359bf4239d1a879eeb41047ec710c15f5c0f67453da59a383e6abd742971c00000000000000000000000000000000000000000000000000000000000000c001a0b57f0ff8516ea29cb26a44ac5055a5420847d1e16a8e7b03b70f0c02291ff2d5a00ad3771e5f39ccacfff0faa8c5d25ef7a1c179f79e66e828ffddcb994c8b512e"))] + #[case::base_0xfaada76a2dac09fc17f5a28d066aaabefc6d82ef6589b211ed8c9f766b070721(bytes!("b87602f873822105528304320f8409cfe5c98252089480c67432656d59144ceff962e8faf8926599bcf888011dfe52d06b633f80c001a08632f069f837aea7a28bab0affee14dda116956bd5a850a355c045d25afedd17a0084b8f273efffe17ece527116053e5781a4915ff89ab9c379f1e62c25b697687"))] + #[case::base_0x112864e9b971af6a1dac840018833c5a5a659acc187cfdaba919ad1da013678d(bytes!("b8b302f8b0822105308304320f8409cfe5c9827496944ed4e862860bed51a9570b96d89af5e1b0efefed80b844095ea7b3000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba300000000000000000000000000000000000000000000015e10fb0973595fffffc001a02020e39f07917c1a852feb131c857e12478c7e88a20772b91a8bf5cee38c5aeea06055981727f9aaa3471c1af800555b35a77916c154be3f9d02ad1a63029455ab"))] + #[case::base_0x6905051352691641888d0c427fb137c5b95afb5870d5169ff014eff1d0952195(bytes!("b87202f86f8221058303dc6c8310db1f84068fa8d7838954409436af2ff952a7355c8045fcd5e88bc9f6c8257f7b8080c001a0b89e7ff3d7694109e73e7f4244e032581670313c36e48e485c9c94b853bd81d2a038ffaf8f10859ce21d1f7f7046c3d08027fb8aa15b69038f6102be97aaa1179a"))] + #[case::base_0x6a38e9a26d7202a2268de69d2d47531c1a9829867579a483fb48d78e9e0b080d(bytes!("b9049b02f904978221058201618506fc23ac008506fc23ac008306ddd0943fc91a3afd70395cd496c647d5a6cc9d4b2b7fad80b904243593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000006641d67b00000000000000000000000000000000000000000000000000000000000000030a000c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000088487bd8c3222d64d1d0b3fa7098dcf9d94d79e000000000000000000000000ffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000006669635d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad000000000000000000000000000000000000000000000000000000006641d78900000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000418661369ca026f92ff88347bd0e3625a7b5ed65071b366368c68ad7c55aed136c18659b34f9246e30a784227a53dd374fbd3d2124696808c678cd987c4e954a681b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000549e5c020c764dbfffff00000000000000000000000000000000000000000000000002e5a629c093a2b600000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002b088487bd8c3222d64d1d0b3fa7098dcf9d94d79e0027104200000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000c001a014a3acef764ff6d3bb9bd81e420bfa94171a5734ab997dfbc9b41b653ce018a4a01ff5fccb01ef5c60ba3aef67d4e74f3f47312dd78bfbbff9e5090fbf2d3d62bb"))] + fn test_flz_native_evm_parity(#[case] input: Bytes) { + // This bytecode and ABI is for a contract, which wraps the LibZip library for easier fuzz testing. + // The source of this contract is here: https://github.com/danyalprout/fastlz/blob/main/src/FastLz.sol#L6-L10 + sol! { + interface FastLz { + function fastLz(bytes input) external view returns (uint256); + } + } + + let contract_bytecode = Bytecode::new_raw(bytes!("608060405234801561001057600080fd5b506004361061002b5760003560e01c8063920a769114610030575b600080fd5b61004361003e366004610374565b610055565b60405190815260200160405180910390f35b600061006082610067565b5192915050565b60606101e0565b818153600101919050565b600082840393505b838110156100a25782810151828201511860001a1590930292600101610081565b9392505050565b825b602082106100d75782516100c0601f8361006e565b5260209290920191601f19909101906021016100ab565b81156100a25782516100ec600184038361006e565b520160010192915050565b60006001830392505b61010782106101385761012a8360ff1661012560fd6101258760081c60e0018961006e565b61006e565b935061010682039150610100565b600782106101655761015e8360ff16610125600785036101258760081c60e0018961006e565b90506100a2565b61017e8360ff166101258560081c8560051b018761006e565b949350505050565b80516101d890838303906101bc90600081901a600182901a60081b1760029190911a60101b17639e3779b90260131c611fff1690565b8060021b6040510182815160e01c1860e01b8151188152505050565b600101919050565b5060405161800038823961800081016020830180600d8551820103826002015b81811015610313576000805b50508051604051600082901a600183901a60081b1760029290921a60101b91909117639e3779b9810260111c617ffc16909101805160e081811c878603811890911b9091189091528401908183039084841061026857506102a3565b600184019350611fff821161029d578251600081901a600182901a60081b1760029190911a60101b17810361029d57506102a3565b5061020c565b8383106102b1575050610313565b600183039250858311156102cf576102cc87878886036100a9565b96505b6102e3600985016003850160038501610079565b91506102f08782846100f7565b9650506103088461030386848601610186565b610186565b915050809350610200565b5050617fe061032884848589518601036100a9565b03925050506020820180820383525b81811161034e57617fe08101518152602001610337565b5060008152602001604052919050565b634e487b7160e01b600052604160045260246000fd5b60006020828403121561038657600080fd5b813567ffffffffffffffff8082111561039e57600080fd5b818401915084601f8301126103b257600080fd5b8135818111156103c4576103c461035e565b604051601f8201601f19908116603f011681019083821181831017156103ec576103ec61035e565b8160405282815287602084870101111561040557600080fd5b82602086016020830137600092810160200192909252509594505050505056fea264697066735822122000646b2953fc4a6f501bd0456ac52203089443937719e16b3190b7979c39511264736f6c63430008190033")); + + let native_val = flz_compress_len(&input); + + let mut evm = Context::op() + .with_db(BenchmarkDB::new_bytecode(contract_bytecode.clone())) + .modify_tx_chained(|tx| { + tx.base.caller = EEADDRESS; + tx.base.kind = TxKind::Call(FFADDRESS); + tx.base.data = FastLz::fastLzCall::new((input,)).abi_encode().into(); + tx.base.gas_limit = 3_000_000; + tx.enveloped_tx = Some(Bytes::default()); + }) + .build_op(); + + let result_and_state = evm.replay().unwrap(); + + let output = result_and_state.result.output().unwrap(); + let evm_val = FastLz::fastLzCall::abi_decode_returns(output).unwrap(); + + assert_eq!(U256::from(native_val), evm_val); + } +} diff --git a/src/handler.rs b/src/handler.rs new file mode 100644 index 00000000000..f19f58d39f0 --- /dev/null +++ b/src/handler.rs @@ -0,0 +1,934 @@ +//!Handler related to Optimism chain +use crate::{ + api::exec::OpContextTr, + constants::{BASE_FEE_RECIPIENT, L1_FEE_RECIPIENT, OPERATOR_FEE_RECIPIENT}, + transaction::{deposit::DEPOSIT_TRANSACTION_TYPE, OpTransactionError, OpTxTr}, + L1BlockInfo, OpHaltReason, OpSpecId, +}; +use revm::{ + context_interface::{ + result::{EVMError, ExecutionResult, FromStringError, ResultAndState}, + Block, Cfg, ContextTr, JournalTr, Transaction, + }, + handler::{ + handler::EvmTrError, validation::validate_tx_against_account, EvmTr, Frame, FrameResult, + Handler, MainnetHandler, + }, + inspector::{Inspector, InspectorEvmTr, InspectorFrame, InspectorHandler}, + interpreter::{interpreter::EthInterpreter, FrameInput, Gas}, + primitives::hardfork::SpecId, + primitives::{HashMap, U256}, + state::Account, + Database, +}; + +pub struct OpHandler { + pub mainnet: MainnetHandler, + pub _phantom: core::marker::PhantomData<(EVM, ERROR, FRAME)>, +} + +impl OpHandler { + pub fn new() -> Self { + Self { + mainnet: MainnetHandler::default(), + _phantom: core::marker::PhantomData, + } + } +} + +impl Default for OpHandler { + fn default() -> Self { + Self::new() + } +} + +pub trait IsTxError { + fn is_tx_error(&self) -> bool; +} + +impl IsTxError for EVMError { + fn is_tx_error(&self) -> bool { + matches!(self, EVMError::Transaction(_)) + } +} + +impl Handler for OpHandler +where + EVM: EvmTr, + ERROR: EvmTrError + From + FromStringError + IsTxError, + // TODO `FrameResult` should be a generic trait. + // TODO `FrameInit` should be a generic. + FRAME: Frame, +{ + type Evm = EVM; + type Error = ERROR; + type Frame = FRAME; + type HaltReason = OpHaltReason; + + fn validate_env(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> { + // Do not perform any extra validation for deposit transactions, they are pre-verified on L1. + let ctx = evm.ctx(); + let tx = ctx.tx(); + let tx_type = tx.tx_type(); + if tx_type == DEPOSIT_TRANSACTION_TYPE { + // Do not allow for a system transaction to be processed if Regolith is enabled. + if tx.is_system_transaction() + && evm.ctx().cfg().spec().is_enabled_in(OpSpecId::REGOLITH) + { + return Err(OpTransactionError::DepositSystemTxPostRegolith.into()); + } + return Ok(()); + } + self.mainnet.validate_env(evm) + } + + fn validate_tx_against_state(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> { + let context = evm.ctx(); + let spec = context.cfg().spec(); + let block_number = context.block().number(); + if context.tx().tx_type() == DEPOSIT_TRANSACTION_TYPE { + return Ok(()); + } else { + // The L1-cost fee is only computed for Optimism non-deposit transactions. + if context.chain().l2_block != block_number { + // L1 block info is stored in the context for later use. + // and it will be reloaded from the database if it is not for the current block. + *context.chain() = L1BlockInfo::try_fetch(context.db(), block_number, spec)?; + } + } + + let enveloped_tx = context + .tx() + .enveloped_tx() + .expect("all not deposit tx have enveloped tx") + .clone(); + + // compute L1 cost + let mut additional_cost = context.chain().calculate_tx_l1_cost(&enveloped_tx, spec); + + if spec.is_enabled_in(OpSpecId::ISTHMUS) { + let gas_limit = U256::from(context.tx().gas_limit()); + let operator_fee_charge = context + .chain() + .operator_fee_charge(&enveloped_tx, gas_limit); + + additional_cost = additional_cost.saturating_add(operator_fee_charge); + } + + let tx_caller = context.tx().caller(); + + // Load acc + let account = context.journal().load_account_code(tx_caller)?; + let account = account.data.info.clone(); + + validate_tx_against_account(&account, context, additional_cost)?; + Ok(()) + } + + fn deduct_caller(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> { + let ctx = evm.ctx(); + let spec = ctx.cfg().spec(); + let caller = ctx.tx().caller(); + let is_deposit = ctx.tx().tx_type() == DEPOSIT_TRANSACTION_TYPE; + + // If the transaction is a deposit with a `mint` value, add the mint value + // in wei to the caller's balance. This should be persisted to the database + // prior to the rest of execution. + let mut tx_l1_cost = U256::ZERO; + if is_deposit { + let tx = ctx.tx(); + if let Some(mint) = tx.mint() { + let mut caller_account = ctx.journal().load_account(caller)?; + caller_account.info.balance += U256::from(mint); + } + } else { + let enveloped_tx = ctx + .tx() + .enveloped_tx() + .expect("all not deposit tx have enveloped tx") + .clone(); + tx_l1_cost = ctx.chain().calculate_tx_l1_cost(&enveloped_tx, spec); + } + + // We deduct caller max balance after minting and before deducing the + // L1 cost, max values is already checked in pre_validate but L1 cost wasn't. + self.mainnet.deduct_caller(evm)?; + + // If the transaction is not a deposit transaction, subtract the L1 data fee from the + // caller's balance directly after minting the requested amount of ETH. + // Additionally deduct the operator fee from the caller's account. + if !is_deposit { + let ctx = evm.ctx(); + + // Deduct the operator fee from the caller's account. + let gas_limit = U256::from(ctx.tx().gas_limit()); + let enveloped_tx = ctx + .tx() + .enveloped_tx() + .expect("all not deposit tx have enveloped tx") + .clone(); + + let mut operator_fee_charge = U256::ZERO; + if spec.is_enabled_in(OpSpecId::ISTHMUS) { + operator_fee_charge = ctx.chain().operator_fee_charge(&enveloped_tx, gas_limit); + } + + let mut caller_account = ctx.journal().load_account(caller)?; + caller_account.info.balance = caller_account + .info + .balance + .saturating_sub(tx_l1_cost.saturating_add(operator_fee_charge)); + } + Ok(()) + } + + fn last_frame_result( + &self, + evm: &mut Self::Evm, + frame_result: &mut ::FrameResult, + ) -> Result<(), Self::Error> { + let ctx = evm.ctx(); + let tx = ctx.tx(); + let is_deposit = tx.tx_type() == DEPOSIT_TRANSACTION_TYPE; + let tx_gas_limit = tx.gas_limit(); + let is_regolith = ctx.cfg().spec().is_enabled_in(OpSpecId::REGOLITH); + + let instruction_result = frame_result.interpreter_result().result; + let gas = frame_result.gas_mut(); + let remaining = gas.remaining(); + let refunded = gas.refunded(); + + // Spend the gas limit. Gas is reimbursed when the tx returns successfully. + *gas = Gas::new_spent(tx_gas_limit); + + if instruction_result.is_ok() { + // On Optimism, deposit transactions report gas usage uniquely to other + // transactions due to them being pre-paid on L1. + // + // Hardfork Behavior: + // - Bedrock (success path): + // - Deposit transactions (non-system) report their gas limit as the usage. + // No refunds. + // - Deposit transactions (system) report 0 gas used. No refunds. + // - Regular transactions report gas usage as normal. + // - Regolith (success path): + // - Deposit transactions (all) report their gas used as normal. Refunds + // enabled. + // - Regular transactions report their gas used as normal. + if !is_deposit || is_regolith { + // For regular transactions prior to Regolith and all transactions after + // Regolith, gas is reported as normal. + gas.erase_cost(remaining); + gas.record_refund(refunded); + } else if is_deposit { + let tx = ctx.tx(); + if tx.is_system_transaction() { + // System transactions were a special type of deposit transaction in + // the Bedrock hardfork that did not incur any gas costs. + gas.erase_cost(tx_gas_limit); + } + } + } else if instruction_result.is_revert() { + // On Optimism, deposit transactions report gas usage uniquely to other + // transactions due to them being pre-paid on L1. + // + // Hardfork Behavior: + // - Bedrock (revert path): + // - Deposit transactions (all) report the gas limit as the amount of gas + // used on failure. No refunds. + // - Regular transactions receive a refund on remaining gas as normal. + // - Regolith (revert path): + // - Deposit transactions (all) report the actual gas used as the amount of + // gas used on failure. Refunds on remaining gas enabled. + // - Regular transactions receive a refund on remaining gas as normal. + if !is_deposit || is_regolith { + gas.erase_cost(remaining); + } + } + Ok(()) + } + + fn reimburse_caller( + &self, + evm: &mut Self::Evm, + exec_result: &mut ::FrameResult, + ) -> Result<(), Self::Error> { + self.mainnet.reimburse_caller(evm, exec_result)?; + + let context = evm.ctx(); + if context.tx().tx_type() != DEPOSIT_TRANSACTION_TYPE { + let caller = context.tx().caller(); + let spec = context.cfg().spec(); + let operator_fee_refund = context.chain().operator_fee_refund(exec_result.gas(), spec); + + let caller_account = context.journal().load_account(caller)?; + + // In additional to the normal transaction fee, additionally refund the caller + // for the operator fee. + caller_account.data.info.balance = caller_account + .data + .info + .balance + .saturating_add(operator_fee_refund); + } + + Ok(()) + } + + fn refund( + &self, + evm: &mut Self::Evm, + exec_result: &mut ::FrameResult, + eip7702_refund: i64, + ) { + exec_result.gas_mut().record_refund(eip7702_refund); + + let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE; + let is_regolith = evm.ctx().cfg().spec().is_enabled_in(OpSpecId::REGOLITH); + + // Prior to Regolith, deposit transactions did not receive gas refunds. + let is_gas_refund_disabled = is_deposit && !is_regolith; + if !is_gas_refund_disabled { + exec_result.gas_mut().set_final_refund( + evm.ctx() + .cfg() + .spec() + .into_eth_spec() + .is_enabled_in(SpecId::LONDON), + ); + } + } + + fn reward_beneficiary( + &self, + evm: &mut Self::Evm, + exec_result: &mut ::FrameResult, + ) -> Result<(), Self::Error> { + let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE; + + // Transfer fee to coinbase/beneficiary. + if !is_deposit { + self.mainnet.reward_beneficiary(evm, exec_result)?; + let basefee = evm.ctx().block().basefee() as u128; + + // If the transaction is not a deposit transaction, fees are paid out + // to both the Base Fee Vault as well as the L1 Fee Vault. + let ctx = evm.ctx(); + let enveloped = ctx.tx().enveloped_tx().cloned(); + let spec = ctx.cfg().spec(); + let l1_block_info = ctx.chain(); + + let Some(enveloped_tx) = &enveloped else { + return Err(ERROR::from_string( + "[OPTIMISM] Failed to load enveloped transaction.".into(), + )); + }; + + let l1_cost = l1_block_info.calculate_tx_l1_cost(enveloped_tx, spec); + let mut operator_fee_cost = U256::ZERO; + if spec.is_enabled_in(OpSpecId::ISTHMUS) { + operator_fee_cost = l1_block_info.operator_fee_charge( + enveloped_tx, + U256::from(exec_result.gas().spent() - exec_result.gas().refunded() as u64), + ); + } + // Send the L1 cost of the transaction to the L1 Fee Vault. + let mut l1_fee_vault_account = ctx.journal().load_account(L1_FEE_RECIPIENT)?; + l1_fee_vault_account.mark_touch(); + l1_fee_vault_account.info.balance += l1_cost; + + // Send the base fee of the transaction to the Base Fee Vault. + let mut base_fee_vault_account = + evm.ctx().journal().load_account(BASE_FEE_RECIPIENT)?; + base_fee_vault_account.mark_touch(); + base_fee_vault_account.info.balance += U256::from(basefee.saturating_mul( + (exec_result.gas().spent() - exec_result.gas().refunded() as u64) as u128, + )); + + // Send the operator fee of the transaction to the coinbase. + let mut operator_fee_vault_account = + evm.ctx().journal().load_account(OPERATOR_FEE_RECIPIENT)?; + operator_fee_vault_account.mark_touch(); + operator_fee_vault_account.data.info.balance += operator_fee_cost; + } + Ok(()) + } + + fn output( + &self, + evm: &mut Self::Evm, + result: ::FrameResult, + ) -> Result, Self::Error> { + let result = self.mainnet.output(evm, result)?; + let result = result.map_haltreason(OpHaltReason::Base); + if result.result.is_halt() { + // Post-regolith, if the transaction is a deposit transaction and it halts, + // we bubble up to the global return handler. The mint value will be persisted + // and the caller nonce will be incremented there. + let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE; + if is_deposit && evm.ctx().cfg().spec().is_enabled_in(OpSpecId::REGOLITH) { + return Err(ERROR::from(OpTransactionError::HaltedDepositPostRegolith)); + } + } + evm.ctx().chain().clear_tx_l1_cost(); + Ok(result) + } + + fn catch_error( + &self, + evm: &mut Self::Evm, + error: Self::Error, + ) -> Result, Self::Error> { + let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE; + let output = if error.is_tx_error() && is_deposit { + let ctx = evm.ctx(); + let spec = ctx.cfg().spec(); + let tx = ctx.tx(); + let caller = tx.caller(); + let mint = tx.mint(); + let is_system_tx = tx.is_system_transaction(); + let gas_limit = tx.gas_limit(); + // If the transaction is a deposit transaction and it failed + // for any reason, the caller nonce must be bumped, and the + // gas reported must be altered depending on the Hardfork. This is + // also returned as a special Halt variant so that consumers can more + // easily distinguish between a failed deposit and a failed + // normal transaction. + + // Increment sender nonce and account balance for the mint amount. Deposits + // always persist the mint amount, even if the transaction fails. + let account = { + let mut acc = Account::from( + evm.ctx() + .db() + .basic(caller) + .unwrap_or_default() + .unwrap_or_default(), + ); + acc.info.nonce = acc.info.nonce.saturating_add(1); + acc.info.balance = acc + .info + .balance + .saturating_add(U256::from(mint.unwrap_or_default())); + acc.mark_touch(); + acc + }; + let state = HashMap::from_iter([(caller, account)]); + + // The gas used of a failed deposit post-regolith is the gas + // limit of the transaction. pre-regolith, it is the gas limit + // of the transaction for non system transactions and 0 for system + // transactions. + let gas_used = if spec.is_enabled_in(OpSpecId::REGOLITH) || !is_system_tx { + gas_limit + } else { + 0 + }; + // clear the journal + Ok(ResultAndState { + result: ExecutionResult::Halt { + reason: OpHaltReason::FailedDeposit, + gas_used, + }, + state, + }) + } else { + Err(error) + }; + // do cleanup + evm.ctx().chain().clear_tx_l1_cost(); + evm.ctx().journal().clear(); + + output + } +} + +impl InspectorHandler for OpHandler +where + EVM: InspectorEvmTr< + Context: OpContextTr, + Inspector: Inspector<<::Evm as EvmTr>::Context, EthInterpreter>, + >, + ERROR: EvmTrError + From + FromStringError + IsTxError, + // TODO `FrameResult` should be a generic trait. + // TODO `FrameInit` should be a generic. + FRAME: InspectorFrame< + Evm = EVM, + Error = ERROR, + FrameResult = FrameResult, + FrameInit = FrameInput, + IT = EthInterpreter, + >, +{ + type IT = EthInterpreter; +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{api::default_ctx::OpContext, DefaultOp, OpBuilder}; + use revm::{ + context::{Context, TransactionType}, + context_interface::result::InvalidTransaction, + database::InMemoryDB, + database_interface::EmptyDB, + handler::EthFrame, + interpreter::{CallOutcome, InstructionResult, InterpreterResult}, + primitives::{bytes, Address, Bytes, B256}, + state::AccountInfo, + }; + use rstest::rstest; + use std::boxed::Box; + + /// Creates frame result. + fn call_last_frame_return( + ctx: OpContext, + instruction_result: InstructionResult, + gas: Gas, + ) -> Gas { + let mut evm = ctx.build_op(); + + let mut exec_result = FrameResult::Call(CallOutcome::new( + InterpreterResult { + result: instruction_result, + output: Bytes::new(), + gas, + }, + 0..0, + )); + + let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new(); + + handler + .last_frame_result(&mut evm, &mut exec_result) + .unwrap(); + handler.refund(&mut evm, &mut exec_result, 0); + *exec_result.gas() + } + + #[test] + fn test_revert_gas() { + let ctx = Context::op() + .modify_tx_chained(|tx| { + tx.base.gas_limit = 100; + tx.enveloped_tx = None; + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::BEDROCK); + + let gas = call_last_frame_return(ctx, InstructionResult::Revert, Gas::new(90)); + assert_eq!(gas.remaining(), 90); + assert_eq!(gas.spent(), 10); + assert_eq!(gas.refunded(), 0); + } + + #[test] + fn test_consume_gas() { + let ctx = Context::op() + .modify_tx_chained(|tx| { + tx.base.gas_limit = 100; + tx.deposit.source_hash = B256::ZERO; + tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE; + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); + + let gas = call_last_frame_return(ctx, InstructionResult::Stop, Gas::new(90)); + assert_eq!(gas.remaining(), 90); + assert_eq!(gas.spent(), 10); + assert_eq!(gas.refunded(), 0); + } + + #[test] + fn test_consume_gas_with_refund() { + let ctx = Context::op() + .modify_tx_chained(|tx| { + tx.base.gas_limit = 100; + tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE; + tx.deposit.source_hash = B256::ZERO; + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); + + let mut ret_gas = Gas::new(90); + ret_gas.record_refund(20); + + let gas = call_last_frame_return(ctx.clone(), InstructionResult::Stop, ret_gas); + assert_eq!(gas.remaining(), 90); + assert_eq!(gas.spent(), 10); + assert_eq!(gas.refunded(), 2); // min(20, 10/5) + + let gas = call_last_frame_return(ctx, InstructionResult::Revert, ret_gas); + assert_eq!(gas.remaining(), 90); + assert_eq!(gas.spent(), 10); + assert_eq!(gas.refunded(), 0); + } + + #[test] + fn test_consume_gas_deposit_tx() { + let ctx = Context::op() + .modify_tx_chained(|tx| { + tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE; + tx.base.gas_limit = 100; + tx.deposit.source_hash = B256::ZERO; + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::BEDROCK); + let gas = call_last_frame_return(ctx, InstructionResult::Stop, Gas::new(90)); + assert_eq!(gas.remaining(), 0); + assert_eq!(gas.spent(), 100); + assert_eq!(gas.refunded(), 0); + } + + #[test] + fn test_consume_gas_sys_deposit_tx() { + let ctx = Context::op() + .modify_tx_chained(|tx| { + tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE; + tx.base.gas_limit = 100; + tx.deposit.source_hash = B256::ZERO; + tx.deposit.is_system_transaction = true; + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::BEDROCK); + let gas = call_last_frame_return(ctx, InstructionResult::Stop, Gas::new(90)); + assert_eq!(gas.remaining(), 100); + assert_eq!(gas.spent(), 0); + assert_eq!(gas.refunded(), 0); + } + + #[test] + fn test_commit_mint_value() { + let caller = Address::ZERO; + let mut db = InMemoryDB::default(); + db.insert_account_info( + caller, + AccountInfo { + balance: U256::from(1000), + ..Default::default() + }, + ); + + let mut ctx = Context::op() + .with_db(db) + .with_chain(L1BlockInfo { + l1_base_fee: U256::from(1_000), + l1_fee_overhead: Some(U256::from(1_000)), + l1_base_fee_scalar: U256::from(1_000), + ..Default::default() + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); + ctx.modify_tx(|tx| { + tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE; + tx.deposit.source_hash = B256::ZERO; + tx.deposit.mint = Some(10); + }); + + let mut evm = ctx.build_op(); + + let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new(); + handler.deduct_caller(&mut evm).unwrap(); + + // Check the account balance is updated. + let account = evm.ctx().journal().load_account(caller).unwrap(); + assert_eq!(account.info.balance, U256::from(1010)); + } + + #[test] + fn test_remove_l1_cost_non_deposit() { + let caller = Address::ZERO; + let mut db = InMemoryDB::default(); + db.insert_account_info( + caller, + AccountInfo { + balance: U256::from(1000), + ..Default::default() + }, + ); + let ctx = Context::op() + .with_db(db) + .with_chain(L1BlockInfo { + l1_base_fee: U256::from(1_000), + l1_fee_overhead: Some(U256::from(1_000)), + l1_base_fee_scalar: U256::from(1_000), + ..Default::default() + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH) + .modify_tx_chained(|tx| { + tx.base.gas_limit = 100; + tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE; + tx.deposit.mint = Some(10); + tx.enveloped_tx = Some(bytes!("FACADE")); + tx.deposit.source_hash = B256::ZERO; + }); + + let mut evm = ctx.build_op(); + + let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new(); + handler.deduct_caller(&mut evm).unwrap(); + + // Check the account balance is updated. + let account = evm.ctx().journal().load_account(caller).unwrap(); + assert_eq!(account.info.balance, U256::from(1010)); + } + + #[test] + fn test_remove_l1_cost() { + let caller = Address::ZERO; + let mut db = InMemoryDB::default(); + db.insert_account_info( + caller, + AccountInfo { + balance: U256::from(1049), + ..Default::default() + }, + ); + let ctx = Context::op() + .with_db(db) + .with_chain(L1BlockInfo { + l1_base_fee: U256::from(1_000), + l1_fee_overhead: Some(U256::from(1_000)), + l1_base_fee_scalar: U256::from(1_000), + ..Default::default() + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH) + .modify_tx_chained(|tx| { + tx.base.gas_limit = 100; + tx.deposit.source_hash = B256::ZERO; + tx.enveloped_tx = Some(bytes!("FACADE")); + }); + + let mut evm = ctx.build_op(); + let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new(); + + // l1block cost is 1048 fee. + handler.deduct_caller(&mut evm).unwrap(); + + // Check the account balance is updated. + let account = evm.ctx().journal().load_account(caller).unwrap(); + assert_eq!(account.info.balance, U256::from(1)); + } + + #[test] + fn test_remove_operator_cost() { + let caller = Address::ZERO; + let mut db = InMemoryDB::default(); + db.insert_account_info( + caller, + AccountInfo { + balance: U256::from(151), + ..Default::default() + }, + ); + let ctx = Context::op() + .with_db(db) + .with_chain(L1BlockInfo { + operator_fee_scalar: Some(U256::from(10_000_000)), + operator_fee_constant: Some(U256::from(50)), + ..Default::default() + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS) + .modify_tx_chained(|tx| { + tx.base.gas_limit = 10; + tx.enveloped_tx = Some(bytes!("FACADE")); + }); + + let mut evm = ctx.build_op(); + let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new(); + + // operator fee cost is operator_fee_scalar * gas_limit / 1e6 + operator_fee_constant + // 10_000_000 * 10 / 1_000_000 + 50 = 150 + handler.deduct_caller(&mut evm).unwrap(); + + // Check the account balance is updated. + let account = evm.ctx().journal().load_account(caller).unwrap(); + assert_eq!(account.info.balance, U256::from(1)); + } + + #[test] + fn test_remove_l1_cost_lack_of_funds() { + let caller = Address::ZERO; + let mut db = InMemoryDB::default(); + db.insert_account_info( + caller, + AccountInfo { + balance: U256::from(48), + ..Default::default() + }, + ); + let ctx = Context::op() + .with_db(db) + .with_chain(L1BlockInfo { + l1_base_fee: U256::from(1_000), + l1_fee_overhead: Some(U256::from(1_000)), + l1_base_fee_scalar: U256::from(1_000), + ..Default::default() + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH) + .modify_tx_chained(|tx| { + tx.enveloped_tx = Some(bytes!("FACADE")); + }); + + // l1block cost is 1048 fee. + let mut evm = ctx.build_op(); + let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new(); + + // l1block cost is 1048 fee. + assert_eq!( + handler.validate_tx_against_state(&mut evm), + Err(EVMError::Transaction( + InvalidTransaction::LackOfFundForMaxFee { + fee: Box::new(U256::from(1048)), + balance: Box::new(U256::from(48)), + } + .into(), + )) + ); + } + + #[test] + fn test_validate_sys_tx() { + // mark the tx as a system transaction. + let ctx = Context::op() + .modify_tx_chained(|tx| { + tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE; + tx.deposit.is_system_transaction = true; + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); + + let mut evm = ctx.build_op(); + let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new(); + + assert_eq!( + handler.validate_env(&mut evm), + Err(EVMError::Transaction( + OpTransactionError::DepositSystemTxPostRegolith + )) + ); + + evm.ctx().modify_cfg(|cfg| cfg.spec = OpSpecId::BEDROCK); + + // Pre-regolith system transactions should be allowed. + assert!(handler.validate_env(&mut evm).is_ok()); + } + + #[test] + fn test_validate_deposit_tx() { + // Set source hash. + let ctx = Context::op() + .modify_tx_chained(|tx| { + tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE; + tx.deposit.source_hash = B256::ZERO; + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); + + let mut evm = ctx.build_op(); + let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new(); + + assert!(handler.validate_env(&mut evm).is_ok()); + } + + #[test] + fn test_validate_tx_against_state_deposit_tx() { + // Set source hash. + let ctx = Context::op() + .modify_tx_chained(|tx| { + tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE; + tx.deposit.source_hash = B256::ZERO; + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); + + let mut evm = ctx.build_op(); + let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new(); + + // Nonce and balance checks should be skipped for deposit transactions. + assert!(handler.validate_env(&mut evm).is_ok()); + } + + #[test] + fn test_halted_deposit_tx_post_regolith() { + let ctx = Context::op() + .modify_tx_chained(|tx| { + tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE; + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); + + let mut evm = ctx.build_op(); + let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new(); + + assert_eq!( + handler.output( + &mut evm, + FrameResult::Call(CallOutcome { + result: InterpreterResult { + result: InstructionResult::OutOfGas, + output: Default::default(), + gas: Default::default(), + }, + memory_offset: Default::default(), + }) + ), + Err(EVMError::Transaction( + OpTransactionError::HaltedDepositPostRegolith + )) + ) + } + + #[rstest] + #[case::deposit(true)] + #[case::dyn_fee(false)] + fn test_operator_fee_refund(#[case] is_deposit: bool) { + const SENDER: Address = Address::ZERO; + const GAS_PRICE: u128 = 0xFF; + const OP_FEE_MOCK_PARAM: u128 = 0xFFFF; + + let ctx = Context::op() + .modify_tx_chained(|tx| { + tx.base.tx_type = if is_deposit { + DEPOSIT_TRANSACTION_TYPE + } else { + TransactionType::Eip1559 as u8 + }; + tx.base.gas_price = GAS_PRICE; + tx.base.gas_priority_fee = None; + tx.base.caller = SENDER; + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); + + let mut evm = ctx.build_op(); + let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new(); + + // Set the operator fee scalar & constant to non-zero values in the L1 block info. + evm.ctx().chain.operator_fee_scalar = Some(U256::from(OP_FEE_MOCK_PARAM)); + evm.ctx().chain.operator_fee_constant = Some(U256::from(OP_FEE_MOCK_PARAM)); + + let mut gas = Gas::new(100); + gas.set_spent(10); + let mut exec_result = FrameResult::Call(CallOutcome::new( + InterpreterResult { + result: InstructionResult::Return, + output: Default::default(), + gas, + }, + 0..0, + )); + + // Reimburse the caller for the unspent portion of the fees. + handler + .reimburse_caller(&mut evm, &mut exec_result) + .unwrap(); + + // Compute the expected refund amount. If the transaction is a deposit, the operator fee refund never + // applies. If the transaction is not a deposit, the operator fee refund is added to the refund amount. + let mut expected_refund = + U256::from(GAS_PRICE * (gas.remaining() + gas.refunded() as u64) as u128); + let op_fee_refund = evm + .ctx() + .chain() + .operator_fee_refund(&gas, OpSpecId::ISTHMUS); + assert!(op_fee_refund > U256::ZERO); + + if !is_deposit { + expected_refund += op_fee_refund; + } + + // Check that the caller was reimbursed the correct amount of ETH. + let account = evm.ctx().journal().load_account(SENDER).unwrap(); + assert_eq!(account.info.balance, expected_refund); + } +} diff --git a/src/l1block.rs b/src/l1block.rs new file mode 100644 index 00000000000..fdb1983d02c --- /dev/null +++ b/src/l1block.rs @@ -0,0 +1,593 @@ +use crate::{ + constants::{ + BASE_FEE_SCALAR_OFFSET, BLOB_BASE_FEE_SCALAR_OFFSET, ECOTONE_L1_BLOB_BASE_FEE_SLOT, + ECOTONE_L1_FEE_SCALARS_SLOT, EMPTY_SCALARS, L1_BASE_FEE_SLOT, L1_BLOCK_CONTRACT, + L1_OVERHEAD_SLOT, L1_SCALAR_SLOT, NON_ZERO_BYTE_COST, OPERATOR_FEE_CONSTANT_OFFSET, + OPERATOR_FEE_SCALARS_SLOT, OPERATOR_FEE_SCALAR_DECIMAL, OPERATOR_FEE_SCALAR_OFFSET, + ZERO_BYTE_COST, + }, + transaction::estimate_tx_compressed_size, + OpSpecId, +}; +use core::ops::Mul; +use revm::{ + database_interface::Database, interpreter::Gas, primitives::hardfork::SpecId, primitives::U256, +}; + +/// L1 block info +/// +/// We can extract L1 epoch data from each L2 block, by looking at the `setL1BlockValues` +/// transaction data. This data is then used to calculate the L1 cost of a transaction. +/// +/// Here is the format of the `setL1BlockValues` transaction data: +/// +/// setL1BlockValues(uint64 _number, uint64 _timestamp, uint256 _basefee, bytes32 _hash, +/// uint64 _sequenceNumber, bytes32 _batcherHash, uint256 _l1FeeOverhead, uint256 _l1FeeScalar) +/// +/// For now, we only care about the fields necessary for L1 cost calculation. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct L1BlockInfo { + /// The L2 block number. If not same as the one in the context, + /// L1BlockInfo is not valid and will be reloaded from the database. + pub l2_block: u64, + /// The base fee of the L1 origin block. + pub l1_base_fee: U256, + /// The current L1 fee overhead. None if Ecotone is activated. + pub l1_fee_overhead: Option, + /// The current L1 fee scalar. + pub l1_base_fee_scalar: U256, + /// The current L1 blob base fee. None if Ecotone is not activated, except if `empty_ecotone_scalars` is `true`. + pub l1_blob_base_fee: Option, + /// The current L1 blob base fee scalar. None if Ecotone is not activated. + pub l1_blob_base_fee_scalar: Option, + /// The current L1 blob base fee. None if Isthmus is not activated, except if `empty_ecotone_scalars` is `true`. + pub operator_fee_scalar: Option, + /// The current L1 blob base fee scalar. None if Isthmus is not activated. + pub operator_fee_constant: Option, + /// True if Ecotone is activated, but the L1 fee scalars have not yet been set. + pub(crate) empty_ecotone_scalars: bool, + /// Last calculated l1 fee cost. Uses as a cache between validation and pre execution stages. + pub tx_l1_cost: Option, +} + +impl L1BlockInfo { + /// Try to fetch the L1 block info from the database. + pub fn try_fetch( + db: &mut DB, + l2_block: u64, + spec_id: OpSpecId, + ) -> Result { + // Ensure the L1 Block account is loaded into the cache after Ecotone. With EIP-4788, it is no longer the case + // that the L1 block account is loaded into the cache prior to the first inquiry for the L1 block info. + if spec_id.into_eth_spec().is_enabled_in(SpecId::CANCUN) { + let _ = db.basic(L1_BLOCK_CONTRACT)?; + } + + let l1_base_fee = db.storage(L1_BLOCK_CONTRACT, L1_BASE_FEE_SLOT)?; + + if !spec_id.is_enabled_in(OpSpecId::ECOTONE) { + let l1_fee_overhead = db.storage(L1_BLOCK_CONTRACT, L1_OVERHEAD_SLOT)?; + let l1_fee_scalar = db.storage(L1_BLOCK_CONTRACT, L1_SCALAR_SLOT)?; + + Ok(L1BlockInfo { + l1_base_fee, + l1_fee_overhead: Some(l1_fee_overhead), + l1_base_fee_scalar: l1_fee_scalar, + ..Default::default() + }) + } else { + let l1_blob_base_fee = db.storage(L1_BLOCK_CONTRACT, ECOTONE_L1_BLOB_BASE_FEE_SLOT)?; + let l1_fee_scalars = db + .storage(L1_BLOCK_CONTRACT, ECOTONE_L1_FEE_SCALARS_SLOT)? + .to_be_bytes::<32>(); + + let l1_base_fee_scalar = U256::from_be_slice( + l1_fee_scalars[BASE_FEE_SCALAR_OFFSET..BASE_FEE_SCALAR_OFFSET + 4].as_ref(), + ); + let l1_blob_base_fee_scalar = U256::from_be_slice( + l1_fee_scalars[BLOB_BASE_FEE_SCALAR_OFFSET..BLOB_BASE_FEE_SCALAR_OFFSET + 4] + .as_ref(), + ); + + // Check if the L1 fee scalars are empty. If so, we use the Bedrock cost function. + // The L1 fee overhead is only necessary if `empty_ecotone_scalars` is true, as it was deprecated in Ecotone. + let empty_ecotone_scalars = l1_blob_base_fee.is_zero() + && l1_fee_scalars[BASE_FEE_SCALAR_OFFSET..BLOB_BASE_FEE_SCALAR_OFFSET + 4] + == EMPTY_SCALARS; + let l1_fee_overhead = empty_ecotone_scalars + .then(|| db.storage(L1_BLOCK_CONTRACT, L1_OVERHEAD_SLOT)) + .transpose()?; + + if spec_id.is_enabled_in(OpSpecId::ISTHMUS) { + let operator_fee_scalars = db + .storage(L1_BLOCK_CONTRACT, OPERATOR_FEE_SCALARS_SLOT)? + .to_be_bytes::<32>(); + + // Post-isthmus L1 block info + // The `operator_fee_scalar` is stored as a big endian u32 at + // OPERATOR_FEE_SCALAR_OFFSET. + let operator_fee_scalar = U256::from_be_slice( + operator_fee_scalars + [OPERATOR_FEE_SCALAR_OFFSET..OPERATOR_FEE_SCALAR_OFFSET + 4] + .as_ref(), + ); + // The `operator_fee_constant` is stored as a big endian u64 at + // OPERATOR_FEE_CONSTANT_OFFSET. + let operator_fee_constant = U256::from_be_slice( + operator_fee_scalars + [OPERATOR_FEE_CONSTANT_OFFSET..OPERATOR_FEE_CONSTANT_OFFSET + 8] + .as_ref(), + ); + Ok(L1BlockInfo { + l2_block, + l1_base_fee, + l1_base_fee_scalar, + l1_blob_base_fee: Some(l1_blob_base_fee), + l1_blob_base_fee_scalar: Some(l1_blob_base_fee_scalar), + empty_ecotone_scalars, + l1_fee_overhead, + operator_fee_scalar: Some(operator_fee_scalar), + operator_fee_constant: Some(operator_fee_constant), + tx_l1_cost: None, + }) + } else { + // Pre-isthmus L1 block info + Ok(L1BlockInfo { + l1_base_fee, + l1_base_fee_scalar, + l1_blob_base_fee: Some(l1_blob_base_fee), + l1_blob_base_fee_scalar: Some(l1_blob_base_fee_scalar), + empty_ecotone_scalars, + l1_fee_overhead, + ..Default::default() + }) + } + } + } + + /// Calculate the operator fee for executing this transaction. + /// + /// Introduced in isthmus. Prior to isthmus, the operator fee is always zero. + pub fn operator_fee_charge(&self, input: &[u8], gas_limit: U256) -> U256 { + // If the input is a deposit transaction or empty, the default value is zero. + if input.first() == Some(&0x7E) { + return U256::ZERO; + } + + self.operator_fee_charge_inner(gas_limit) + } + + /// Calculate the operator fee for the given `gas`. + fn operator_fee_charge_inner(&self, gas: U256) -> U256 { + let operator_fee_scalar = self + .operator_fee_scalar + .expect("Missing operator fee scalar for isthmus L1 Block"); + let operator_fee_constant = self + .operator_fee_constant + .expect("Missing operator fee constant for isthmus L1 Block"); + + let product = + gas.saturating_mul(operator_fee_scalar) / (U256::from(OPERATOR_FEE_SCALAR_DECIMAL)); + + product.saturating_add(operator_fee_constant) + } + + /// Calculate the operator fee for executing this transaction. + /// + /// Introduced in isthmus. Prior to isthmus, the operator fee is always zero. + pub fn operator_fee_refund(&self, gas: &Gas, spec_id: OpSpecId) -> U256 { + if !spec_id.is_enabled_in(OpSpecId::ISTHMUS) { + return U256::ZERO; + } + + let operator_cost_gas_limit = self.operator_fee_charge_inner(U256::from(gas.limit())); + let operator_cost_gas_used = self.operator_fee_charge_inner(U256::from( + gas.limit() - (gas.remaining() + gas.refunded() as u64), + )); + + operator_cost_gas_limit.saturating_sub(operator_cost_gas_used) + } + + /// Calculate the data gas for posting the transaction on L1. Calldata costs 16 gas per byte + /// after compression. + /// + /// Prior to fjord, calldata costs 16 gas per non-zero byte and 4 gas per zero byte. + /// + /// Prior to regolith, an extra 68 non-zero bytes were included in the rollup data costs to + /// account for the empty signature. + pub fn data_gas(&self, input: &[u8], spec_id: OpSpecId) -> U256 { + if spec_id.is_enabled_in(OpSpecId::FJORD) { + let estimated_size = self.tx_estimated_size_fjord(input); + + return estimated_size + .saturating_mul(U256::from(NON_ZERO_BYTE_COST)) + .wrapping_div(U256::from(1_000_000)); + }; + + let mut rollup_data_gas_cost = U256::from(input.iter().fold(0, |acc, byte| { + acc + if *byte == 0x00 { + ZERO_BYTE_COST + } else { + NON_ZERO_BYTE_COST + } + })); + + // Prior to regolith, an extra 68 non zero bytes were included in the rollup data costs. + if !spec_id.is_enabled_in(OpSpecId::REGOLITH) { + rollup_data_gas_cost += U256::from(NON_ZERO_BYTE_COST).mul(U256::from(68)); + } + + rollup_data_gas_cost + } + + // Calculate the estimated compressed transaction size in bytes, scaled by 1e6. + // This value is computed based on the following formula: + // max(minTransactionSize, intercept + fastlzCoef*fastlzSize) + fn tx_estimated_size_fjord(&self, input: &[u8]) -> U256 { + U256::from(estimate_tx_compressed_size(input)) + } + + /// Clears the cached L1 cost of the transaction. + pub fn clear_tx_l1_cost(&mut self) { + self.tx_l1_cost = None; + } + + /// Calculate the gas cost of a transaction based on L1 block data posted on L2, depending on the [OpSpecId] passed. + pub fn calculate_tx_l1_cost(&mut self, input: &[u8], spec_id: OpSpecId) -> U256 { + if let Some(tx_l1_cost) = self.tx_l1_cost { + return tx_l1_cost; + } + // If the input is a deposit transaction or empty, the default value is zero. + let tx_l1_cost = if input.is_empty() || input.first() == Some(&0x7E) { + return U256::ZERO; + } else if spec_id.is_enabled_in(OpSpecId::FJORD) { + self.calculate_tx_l1_cost_fjord(input) + } else if spec_id.is_enabled_in(OpSpecId::ECOTONE) { + self.calculate_tx_l1_cost_ecotone(input, spec_id) + } else { + self.calculate_tx_l1_cost_bedrock(input, spec_id) + }; + + self.tx_l1_cost = Some(tx_l1_cost); + tx_l1_cost + } + + /// Calculate the gas cost of a transaction based on L1 block data posted on L2, pre-Ecotone. + fn calculate_tx_l1_cost_bedrock(&self, input: &[u8], spec_id: OpSpecId) -> U256 { + let rollup_data_gas_cost = self.data_gas(input, spec_id); + rollup_data_gas_cost + .saturating_add(self.l1_fee_overhead.unwrap_or_default()) + .saturating_mul(self.l1_base_fee) + .saturating_mul(self.l1_base_fee_scalar) + .wrapping_div(U256::from(1_000_000)) + } + + /// Calculate the gas cost of a transaction based on L1 block data posted on L2, post-Ecotone. + /// + /// [OpSpecId::ECOTONE] L1 cost function: + /// `(calldataGas/16)*(l1BaseFee*16*l1BaseFeeScalar + l1BlobBaseFee*l1BlobBaseFeeScalar)/1e6` + /// + /// We divide "calldataGas" by 16 to change from units of calldata gas to "estimated # of bytes when compressed". + /// Known as "compressedTxSize" in the spec. + /// + /// Function is actually computed as follows for better precision under integer arithmetic: + /// `calldataGas*(l1BaseFee*16*l1BaseFeeScalar + l1BlobBaseFee*l1BlobBaseFeeScalar)/16e6` + fn calculate_tx_l1_cost_ecotone(&self, input: &[u8], spec_id: OpSpecId) -> U256 { + // There is an edgecase where, for the very first Ecotone block (unless it is activated at Genesis), we must + // use the Bedrock cost function. To determine if this is the case, we can check if the Ecotone parameters are + // unset. + if self.empty_ecotone_scalars { + return self.calculate_tx_l1_cost_bedrock(input, spec_id); + } + + let rollup_data_gas_cost = self.data_gas(input, spec_id); + let l1_fee_scaled = self.calculate_l1_fee_scaled_ecotone(); + + l1_fee_scaled + .saturating_mul(rollup_data_gas_cost) + .wrapping_div(U256::from(1_000_000 * NON_ZERO_BYTE_COST)) + } + + /// Calculate the gas cost of a transaction based on L1 block data posted on L2, post-Fjord. + /// + /// [OpSpecId::FJORD] L1 cost function: + /// `estimatedSize*(baseFeeScalar*l1BaseFee*16 + blobFeeScalar*l1BlobBaseFee)/1e12` + fn calculate_tx_l1_cost_fjord(&self, input: &[u8]) -> U256 { + let l1_fee_scaled = self.calculate_l1_fee_scaled_ecotone(); + let estimated_size = self.tx_estimated_size_fjord(input); + + estimated_size + .saturating_mul(l1_fee_scaled) + .wrapping_div(U256::from(1_000_000_000_000u64)) + } + + // l1BaseFee*16*l1BaseFeeScalar + l1BlobBaseFee*l1BlobBaseFeeScalar + fn calculate_l1_fee_scaled_ecotone(&self) -> U256 { + let calldata_cost_per_byte = self + .l1_base_fee + .saturating_mul(U256::from(NON_ZERO_BYTE_COST)) + .saturating_mul(self.l1_base_fee_scalar); + let blob_cost_per_byte = self + .l1_blob_base_fee + .unwrap_or_default() + .saturating_mul(self.l1_blob_base_fee_scalar.unwrap_or_default()); + + calldata_cost_per_byte.saturating_add(blob_cost_per_byte) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use revm::primitives::{bytes, hex}; + + #[test] + fn test_data_gas_non_zero_bytes() { + let l1_block_info = L1BlockInfo { + l1_base_fee: U256::from(1_000_000), + l1_fee_overhead: Some(U256::from(1_000_000)), + l1_base_fee_scalar: U256::from(1_000_000), + ..Default::default() + }; + + // 0xFACADE = 6 nibbles = 3 bytes + // 0xFACADE = 1111 1010 . 1100 1010 . 1101 1110 + + // Pre-regolith (ie bedrock) has an extra 68 non-zero bytes + // gas cost = 3 non-zero bytes * NON_ZERO_BYTE_COST + NON_ZERO_BYTE_COST * 68 + // gas cost = 3 * 16 + 68 * 16 = 1136 + let input = bytes!("FACADE"); + let bedrock_data_gas = l1_block_info.data_gas(&input, OpSpecId::BEDROCK); + assert_eq!(bedrock_data_gas, U256::from(1136)); + + // Regolith has no added 68 non zero bytes + // gas cost = 3 * 16 = 48 + let regolith_data_gas = l1_block_info.data_gas(&input, OpSpecId::REGOLITH); + assert_eq!(regolith_data_gas, U256::from(48)); + + // Fjord has a minimum compressed size of 100 bytes + // gas cost = 100 * 16 = 1600 + let fjord_data_gas = l1_block_info.data_gas(&input, OpSpecId::FJORD); + assert_eq!(fjord_data_gas, U256::from(1600)); + } + + #[test] + fn test_data_gas_zero_bytes() { + let l1_block_info = L1BlockInfo { + l1_base_fee: U256::from(1_000_000), + l1_fee_overhead: Some(U256::from(1_000_000)), + l1_base_fee_scalar: U256::from(1_000_000), + ..Default::default() + }; + + // 0xFA00CA00DE = 10 nibbles = 5 bytes + // 0xFA00CA00DE = 1111 1010 . 0000 0000 . 1100 1010 . 0000 0000 . 1101 1110 + + // Pre-regolith (ie bedrock) has an extra 68 non-zero bytes + // gas cost = 3 non-zero * NON_ZERO_BYTE_COST + 2 * ZERO_BYTE_COST + NON_ZERO_BYTE_COST * 68 + // gas cost = 3 * 16 + 2 * 4 + 68 * 16 = 1144 + let input = bytes!("FA00CA00DE"); + let bedrock_data_gas = l1_block_info.data_gas(&input, OpSpecId::BEDROCK); + assert_eq!(bedrock_data_gas, U256::from(1144)); + + // Regolith has no added 68 non zero bytes + // gas cost = 3 * 16 + 2 * 4 = 56 + let regolith_data_gas = l1_block_info.data_gas(&input, OpSpecId::REGOLITH); + assert_eq!(regolith_data_gas, U256::from(56)); + + // Fjord has a minimum compressed size of 100 bytes + // gas cost = 100 * 16 = 1600 + let fjord_data_gas = l1_block_info.data_gas(&input, OpSpecId::FJORD); + assert_eq!(fjord_data_gas, U256::from(1600)); + } + + #[test] + fn test_calculate_tx_l1_cost() { + let mut l1_block_info = L1BlockInfo { + l1_base_fee: U256::from(1_000), + l1_fee_overhead: Some(U256::from(1_000)), + l1_base_fee_scalar: U256::from(1_000), + ..Default::default() + }; + + let input = bytes!("FACADE"); + let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::REGOLITH); + assert_eq!(gas_cost, U256::from(1048)); + l1_block_info.clear_tx_l1_cost(); + + // Zero rollup data gas cost should result in zero + let input = bytes!(""); + let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::REGOLITH); + assert_eq!(gas_cost, U256::ZERO); + l1_block_info.clear_tx_l1_cost(); + + // Deposit transactions with the EIP-2718 type of 0x7E should result in zero + let input = bytes!("7EFACADE"); + let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::REGOLITH); + assert_eq!(gas_cost, U256::ZERO); + } + + #[test] + fn test_calculate_tx_l1_cost_ecotone() { + let mut l1_block_info = L1BlockInfo { + l1_base_fee: U256::from(1_000), + l1_base_fee_scalar: U256::from(1_000), + l1_blob_base_fee: Some(U256::from(1_000)), + l1_blob_base_fee_scalar: Some(U256::from(1_000)), + l1_fee_overhead: Some(U256::from(1_000)), + ..Default::default() + }; + + // calldataGas * (l1BaseFee * 16 * l1BaseFeeScalar + l1BlobBaseFee * l1BlobBaseFeeScalar) / (16 * 1e6) + // = (16 * 3) * (1000 * 16 * 1000 + 1000 * 1000) / (16 * 1e6) + // = 51 + let input = bytes!("FACADE"); + let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::ECOTONE); + assert_eq!(gas_cost, U256::from(51)); + l1_block_info.clear_tx_l1_cost(); + + // Zero rollup data gas cost should result in zero + let input = bytes!(""); + let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::ECOTONE); + assert_eq!(gas_cost, U256::ZERO); + l1_block_info.clear_tx_l1_cost(); + + // Deposit transactions with the EIP-2718 type of 0x7E should result in zero + let input = bytes!("7EFACADE"); + let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::ECOTONE); + assert_eq!(gas_cost, U256::ZERO); + l1_block_info.clear_tx_l1_cost(); + + // If the scalars are empty, the bedrock cost function should be used. + l1_block_info.empty_ecotone_scalars = true; + let input = bytes!("FACADE"); + let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::ECOTONE); + assert_eq!(gas_cost, U256::from(1048)); + } + + #[test] + fn calculate_tx_l1_cost_ecotone() { + // rig + + // l1 block info for OP mainnet ecotone block 118024092 + // 1710374401 (ecotone timestamp) + // 1711603765 (block 118024092 timestamp) + // 1720627201 (fjord timestamp) + // + // decoded from + let l1_block_info = L1BlockInfo { + l1_base_fee: U256::from_be_bytes(hex!( + "0000000000000000000000000000000000000000000000000000000af39ac327" + )), // 47036678951 + l1_base_fee_scalar: U256::from(1368), + l1_blob_base_fee: Some(U256::from_be_bytes(hex!( + "0000000000000000000000000000000000000000000000000000000d5ea528d2" + ))), // 57422457042 + l1_blob_base_fee_scalar: Some(U256::from(810949)), + ..Default::default() + }; + + // second tx in OP mainnet ecotone block 118024092 + // + const TX: &[u8] = &hex!("02f8b30a832253fc8402d11f39842c8a46398301388094dc6ff44d5d932cbd77b52e5612ba0529dc6226f180b844a9059cbb000000000000000000000000d43e02db81f4d46cdf8521f623d21ea0ec7562a50000000000000000000000000000000000000000000000008ac7230489e80000c001a02947e24750723b48f886931562c55d9e07f856d8e06468e719755e18bbc3a570a0784da9ce59fd7754ea5be6e17a86b348e441348cd48ace59d174772465eadbd1"); + + // l1 gas used for tx and l1 fee for tx, from OP mainnet block scanner + // + let expected_l1_gas_used = U256::from(2456); + let expected_l1_fee = U256::from_be_bytes(hex!( + "000000000000000000000000000000000000000000000000000006a510bd7431" // 7306020222001 wei + )); + + // test + + let gas_used = l1_block_info.data_gas(TX, OpSpecId::ECOTONE); + + assert_eq!(gas_used, expected_l1_gas_used); + + let l1_fee = l1_block_info.calculate_tx_l1_cost_ecotone(TX, OpSpecId::ECOTONE); + + assert_eq!(l1_fee, expected_l1_fee) + } + + #[test] + fn test_calculate_tx_l1_cost_fjord() { + // l1FeeScaled = baseFeeScalar*l1BaseFee*16 + blobFeeScalar*l1BlobBaseFee + // = 1000 * 1000 * 16 + 1000 * 1000 + // = 17e6 + let mut l1_block_info = L1BlockInfo { + l1_base_fee: U256::from(1_000), + l1_base_fee_scalar: U256::from(1_000), + l1_blob_base_fee: Some(U256::from(1_000)), + l1_blob_base_fee_scalar: Some(U256::from(1_000)), + ..Default::default() + }; + + // fastLzSize = 4 + // estimatedSize = max(minTransactionSize, intercept + fastlzCoef*fastlzSize) + // = max(100e6, 836500*4 - 42585600) + // = 100e6 + let input = bytes!("FACADE"); + // l1Cost = estimatedSize * l1FeeScaled / 1e12 + // = 100e6 * 17 / 1e6 + // = 1700 + let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::FJORD); + assert_eq!(gas_cost, U256::from(1700)); + l1_block_info.clear_tx_l1_cost(); + + // fastLzSize = 202 + // estimatedSize = max(minTransactionSize, intercept + fastlzCoef*fastlzSize) + // = max(100e6, 836500*202 - 42585600) + // = 126387400 + let input = bytes!("02f901550a758302df1483be21b88304743f94f80e51afb613d764fa61751affd3313c190a86bb870151bd62fd12adb8e41ef24f3f000000000000000000000000000000000000000000000000000000000000006e000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e5831000000000000000000000000000000000000000000000000000000000003c1e5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000148c89ed219d02f1a5be012c689b4f5b731827bebe000000000000000000000000c001a033fd89cb37c31b2cba46b6466e040c61fc9b2a3675a7f5f493ebd5ad77c497f8a07cdf65680e238392693019b4092f610222e71b7cec06449cb922b93b6a12744e"); + // l1Cost = estimatedSize * l1FeeScaled / 1e12 + // = 126387400 * 17 / 1e6 + // = 2148 + let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::FJORD); + assert_eq!(gas_cost, U256::from(2148)); + l1_block_info.clear_tx_l1_cost(); + + // Zero rollup data gas cost should result in zero + let input = bytes!(""); + let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::FJORD); + assert_eq!(gas_cost, U256::ZERO); + l1_block_info.clear_tx_l1_cost(); + + // Deposit transactions with the EIP-2718 type of 0x7E should result in zero + let input = bytes!("7EFACADE"); + let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::FJORD); + assert_eq!(gas_cost, U256::ZERO); + } + + #[test] + fn calculate_tx_l1_cost_fjord() { + // rig + + // L1 block info for OP mainnet fjord block 124665056 + // + let l1_block_info = L1BlockInfo { + l1_base_fee: U256::from(1055991687), + l1_base_fee_scalar: U256::from(5227), + l1_blob_base_fee_scalar: Some(U256::from(1014213)), + l1_blob_base_fee: Some(U256::from(1)), + ..Default::default() // L1 fee overhead (l1 gas used) deprecated since Fjord + }; + + // Second tx in OP mainnet Fjord block 124665056 + // + const TX: &[u8] = &hex!("02f904940a8303fba78401d6d2798401db2b6d830493e0943e6f4f7866654c18f536170780344aa8772950b680b904246a761202000000000000000000000000087000a300de7200382b55d40045000000e5d60e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000022482ad56cb0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000dc6ff44d5d932cbd77b52e5612ba0529dc6226f1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b300000000000000000000000021c4928109acb0659a88ae5329b5374a3024694c0000000000000000000000000000000000000000000000049b9ca9a6943400000000000000000000000000000000000000000000000000000000000000000000000000000000000021c4928109acb0659a88ae5329b5374a3024694c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024b6b55f250000000000000000000000000000000000000000000000049b9ca9a694340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000415ec214a3950bea839a7e6fbb0ba1540ac2076acd50820e2d5ef83d0902cdffb24a47aff7de5190290769c4f0a9c6fabf63012986a0d590b1b571547a8c7050ea1b00000000000000000000000000000000000000000000000000000000000000c080a06db770e6e25a617fe9652f0958bd9bd6e49281a53036906386ed39ec48eadf63a07f47cf51a4a40b4494cf26efc686709a9b03939e20ee27e59682f5faa536667e"); + + // L1 gas used for tx and L1 fee for tx, from OP mainnet block scanner + // https://optimistic.etherscan.io/tx/0x1059e8004daff32caa1f1b1ef97fe3a07a8cf40508f5b835b66d9420d87c4a4a + let expected_data_gas = U256::from(4471); + let expected_l1_fee = U256::from_be_bytes(hex!( + "00000000000000000000000000000000000000000000000000000005bf1ab43d" + )); + + // test + + let data_gas = l1_block_info.data_gas(TX, OpSpecId::FJORD); + + assert_eq!(data_gas, expected_data_gas); + + let l1_fee = l1_block_info.calculate_tx_l1_cost_fjord(TX); + + assert_eq!(l1_fee, expected_l1_fee) + } + + #[test] + fn test_operator_fee_refund() { + let gas = Gas::new(50000); + + let l1_block_info = L1BlockInfo { + l1_base_fee: U256::from(1055991687), + l1_base_fee_scalar: U256::from(5227), + operator_fee_scalar: Some(U256::from(2000)), + operator_fee_constant: Some(U256::from(5)), + ..Default::default() + }; + + let refunded = l1_block_info.operator_fee_refund(&gas, OpSpecId::ISTHMUS); + + assert_eq!(refunded, U256::from(100)) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000000..485d5adc173 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,27 @@ +//! Optimism-specific constants, types, and helpers. +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(not(feature = "std"))] +extern crate alloc as std; + +pub mod api; +pub mod constants; +pub mod evm; +pub mod fast_lz; +pub mod handler; +pub mod l1block; +pub mod precompiles; +pub mod result; +pub mod spec; +pub mod transaction; + +pub use api::{ + builder::OpBuilder, + default_ctx::{DefaultOp, OpContext}, +}; +pub use evm::OpEvm; +pub use l1block::L1BlockInfo; +pub use result::OpHaltReason; +pub use spec::*; +pub use transaction::{error::OpTransactionError, estimate_tx_compressed_size, OpTransaction}; diff --git a/src/precompiles.rs b/src/precompiles.rs new file mode 100644 index 00000000000..4c6481d062c --- /dev/null +++ b/src/precompiles.rs @@ -0,0 +1,332 @@ +use crate::OpSpecId; +use once_cell::race::OnceBox; +use revm::{ + context::Cfg, + context_interface::ContextTr, + handler::{EthPrecompiles, PrecompileProvider}, + interpreter::{InputsImpl, InterpreterResult}, + precompile::{ + self, bn128, secp256r1, PrecompileError, PrecompileResult, PrecompileWithAddress, + Precompiles, + }, + primitives::{hardfork::SpecId, Address}, +}; +use std::boxed::Box; +use std::string::String; + +// Optimism precompile provider +#[derive(Debug, Clone)] +pub struct OpPrecompiles { + /// Inner precompile provider is same as Ethereums. + inner: EthPrecompiles, + spec: OpSpecId, +} + +impl OpPrecompiles { + /// Create a new precompile provider with the given OpSpec. + #[inline] + pub fn new_with_spec(spec: OpSpecId) -> Self { + let precompiles = match spec { + spec @ (OpSpecId::BEDROCK + | OpSpecId::REGOLITH + | OpSpecId::CANYON + | OpSpecId::ECOTONE) => Precompiles::new(spec.into_eth_spec().into()), + OpSpecId::FJORD => fjord(), + OpSpecId::GRANITE | OpSpecId::HOLOCENE => granite(), + OpSpecId::ISTHMUS | OpSpecId::INTEROP | OpSpecId::OSAKA => isthmus(), + }; + + Self { + inner: EthPrecompiles { + precompiles, + spec: SpecId::default(), + }, + spec, + } + } +} + +/// Returns precompiles for Fjord spec. +pub fn fjord() -> &'static Precompiles { + static INSTANCE: OnceBox = OnceBox::new(); + INSTANCE.get_or_init(|| { + let mut precompiles = Precompiles::cancun().clone(); + // RIP-7212: secp256r1 P256verify + precompiles.extend([secp256r1::P256VERIFY]); + Box::new(precompiles) + }) +} + +/// Returns precompiles for Granite spec. +pub fn granite() -> &'static Precompiles { + static INSTANCE: OnceBox = OnceBox::new(); + INSTANCE.get_or_init(|| { + let mut precompiles = fjord().clone(); + // Restrict bn256Pairing input size + precompiles.extend([bn128_pair::GRANITE]); + Box::new(precompiles) + }) +} + +/// Returns precompiles for isthumus spec. +pub fn isthmus() -> &'static Precompiles { + static INSTANCE: OnceBox = OnceBox::new(); + INSTANCE.get_or_init(|| { + let mut precompiles = granite().clone(); + // Prague bls12 precompiles + precompiles.extend(precompile::bls12_381::precompiles()); + // Isthmus bls12 precompile modifications + precompiles.extend([ + bls12_381::ISTHMUS_G1_MSM, + bls12_381::ISTHMUS_G2_MSM, + bls12_381::ISTHMUS_PAIRING, + ]); + Box::new(precompiles) + }) +} + +impl PrecompileProvider for OpPrecompiles +where + CTX: ContextTr>, +{ + type Output = InterpreterResult; + + #[inline] + fn set_spec(&mut self, spec: ::Spec) -> bool { + if spec == self.spec { + return false; + } + *self = Self::new_with_spec(spec); + true + } + + #[inline] + fn run( + &mut self, + context: &mut CTX, + address: &Address, + inputs: &InputsImpl, + is_static: bool, + gas_limit: u64, + ) -> Result, String> { + self.inner + .run(context, address, inputs, is_static, gas_limit) + } + + #[inline] + fn warm_addresses(&self) -> Box> { + self.inner.warm_addresses() + } + + #[inline] + fn contains(&self, address: &Address) -> bool { + self.inner.contains(address) + } +} + +impl Default for OpPrecompiles { + fn default() -> Self { + Self::new_with_spec(OpSpecId::ISTHMUS) + } +} + +pub mod bn128_pair { + use super::*; + + pub const GRANITE_MAX_INPUT_SIZE: usize = 112687; + pub const GRANITE: PrecompileWithAddress = + PrecompileWithAddress(bn128::pair::ADDRESS, |input, gas_limit| { + run_pair(input, gas_limit) + }); + + pub fn run_pair(input: &[u8], gas_limit: u64) -> PrecompileResult { + if input.len() > GRANITE_MAX_INPUT_SIZE { + return Err(PrecompileError::Bn128PairLength); + } + bn128::run_pair( + input, + bn128::pair::ISTANBUL_PAIR_PER_POINT, + bn128::pair::ISTANBUL_PAIR_BASE, + gas_limit, + ) + } +} + +pub mod bls12_381 { + use super::*; + use revm::{ + precompile::bls12_381_const::{G1_MSM_ADDRESS, G2_MSM_ADDRESS, PAIRING_ADDRESS}, + primitives::Bytes, + }; + + #[cfg(not(feature = "std"))] + use crate::std::string::ToString; + + pub const ISTHMUS_G1_MSM_MAX_INPUT_SIZE: usize = 513760; + pub const ISTHMUS_G2_MSM_MAX_INPUT_SIZE: usize = 488448; + pub const ISTHMUS_PAIRING_MAX_INPUT_SIZE: usize = 235008; + + pub const ISTHMUS_G1_MSM: PrecompileWithAddress = + PrecompileWithAddress(G1_MSM_ADDRESS, run_g1_msm); + pub const ISTHMUS_G2_MSM: PrecompileWithAddress = + PrecompileWithAddress(G2_MSM_ADDRESS, run_g2_msm); + pub const ISTHMUS_PAIRING: PrecompileWithAddress = + PrecompileWithAddress(PAIRING_ADDRESS, run_pair); + + pub fn run_g1_msm(input: &Bytes, gas_limit: u64) -> PrecompileResult { + if input.len() > ISTHMUS_G1_MSM_MAX_INPUT_SIZE { + return Err(PrecompileError::Other( + "G1MSM input length too long for OP Stack input size limitation".to_string(), + )); + } + precompile::bls12_381::g1_msm::g1_msm(input, gas_limit) + } + + pub fn run_g2_msm(input: &Bytes, gas_limit: u64) -> PrecompileResult { + if input.len() > ISTHMUS_G2_MSM_MAX_INPUT_SIZE { + return Err(PrecompileError::Other( + "G2MSM input length too long for OP Stack input size limitation".to_string(), + )); + } + precompile::bls12_381::g2_msm::g2_msm(input, gas_limit) + } + + pub fn run_pair(input: &Bytes, gas_limit: u64) -> PrecompileResult { + if input.len() > ISTHMUS_PAIRING_MAX_INPUT_SIZE { + return Err(PrecompileError::Other( + "Pairing input length too long for OP Stack input size limitation".to_string(), + )); + } + precompile::bls12_381::pairing::pairing(input, gas_limit) + } +} + +#[cfg(test)] +mod tests { + use crate::precompiles::bls12_381::{ + run_g1_msm, run_g2_msm, ISTHMUS_G1_MSM_MAX_INPUT_SIZE, ISTHMUS_G2_MSM_MAX_INPUT_SIZE, + ISTHMUS_PAIRING_MAX_INPUT_SIZE, + }; + + use super::*; + use revm::{ + precompile::PrecompileError, + primitives::{hex, Bytes}, + }; + use std::vec; + + #[test] + fn test_bn128_pair() { + let input = hex::decode( + "\ + 1c76476f4def4bb94541d57ebba1193381ffa7aa76ada664dd31c16024c43f59\ + 3034dd2920f673e204fee2811c678745fc819b55d3e9d294e45c9b03a76aef41\ + 209dd15ebff5d46c4bd888e51a93cf99a7329636c63514396b4a452003a35bf7\ + 04bf11ca01483bfa8b34b43561848d28905960114c8ac04049af4b6315a41678\ + 2bb8324af6cfc93537a2ad1a445cfd0ca2a71acd7ac41fadbf933c2a51be344d\ + 120a2a4cf30c1bf9845f20c6fe39e07ea2cce61f0c9bb048165fe5e4de877550\ + 111e129f1cf1097710d41c4ac70fcdfa5ba2023c6ff1cbeac322de49d1b6df7c\ + 2032c61a830e3c17286de9462bf242fca2883585b93870a73853face6a6bf411\ + 198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2\ + 1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed\ + 090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b\ + 12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa", + ) + .unwrap(); + let expected = + hex::decode("0000000000000000000000000000000000000000000000000000000000000001") + .unwrap(); + let outcome = bn128_pair::run_pair(&input, 260_000).unwrap(); + assert_eq!(outcome.bytes, expected); + + // Invalid input length + let input = hex::decode( + "\ + 1111111111111111111111111111111111111111111111111111111111111111\ + 1111111111111111111111111111111111111111111111111111111111111111\ + 111111111111111111111111111111\ + ", + ) + .unwrap(); + + let res = bn128_pair::run_pair(&input, 260_000); + assert!(matches!(res, Err(PrecompileError::Bn128PairLength))); + + // Valid input length shorter than 112687 + let input = vec![1u8; 586 * bn128::PAIR_ELEMENT_LEN]; + let res = bn128_pair::run_pair(&input, 260_000); + assert!(matches!(res, Err(PrecompileError::OutOfGas))); + + // Input length longer than 112687 + let input = vec![1u8; 587 * bn128::PAIR_ELEMENT_LEN]; + let res = bn128_pair::run_pair(&input, 260_000); + assert!(matches!(res, Err(PrecompileError::Bn128PairLength))); + } + + #[test] + fn test_cancun_precompiles_in_fjord() { + // additional to cancun, fjord has p256verify + assert_eq!(fjord().difference(Precompiles::cancun()).len(), 1) + } + + #[test] + fn test_cancun_precompiles_in_granite() { + // granite has p256verify (fjord) + // granite has modification of cancun's bn128 pair (doesn't count as new precompile) + assert_eq!(granite().difference(Precompiles::cancun()).len(), 1) + } + + #[test] + fn test_prague_precompiles_in_isthmus() { + let new_prague_precompiles = Precompiles::prague().difference(Precompiles::cancun()); + + // isthmus contains all precompiles that were new in prague, without modifications + assert!(new_prague_precompiles.difference(isthmus()).is_empty()) + } + + #[test] + fn test_default_precompiles_is_latest() { + let latest = OpPrecompiles::new_with_spec(OpSpecId::default()) + .inner + .precompiles; + let default = OpPrecompiles::default().inner.precompiles; + assert_eq!(latest.len(), default.len()); + + let intersection = default.intersection(latest); + assert_eq!(intersection.len(), latest.len()) + } + + #[test] + fn test_g1_isthmus_max_size() { + let oversized_input = vec![0u8; ISTHMUS_G1_MSM_MAX_INPUT_SIZE + 1]; + let input = Bytes::from(oversized_input); + + let res = run_g1_msm(&input, 260_000); + + assert!( + matches!(res, Err(PrecompileError::Other(msg)) if msg.contains("input length too long")) + ); + } + #[test] + fn test_g2_isthmus_max_size() { + let oversized_input = vec![0u8; ISTHMUS_G2_MSM_MAX_INPUT_SIZE + 1]; + let input = Bytes::from(oversized_input); + + let res = run_g2_msm(&input, 260_000); + + assert!( + matches!(res, Err(PrecompileError::Other(msg)) if msg.contains("input length too long")) + ); + } + #[test] + fn test_pair_isthmus_max_size() { + let oversized_input = vec![0u8; ISTHMUS_PAIRING_MAX_INPUT_SIZE + 1]; + let input = Bytes::from(oversized_input); + + let res = bls12_381::run_pair(&input, 260_000); + + assert!( + matches!(res, Err(PrecompileError::Other(msg)) if msg.contains("input length too long")) + ); + } +} diff --git a/src/result.rs b/src/result.rs new file mode 100644 index 00000000000..449a7ccb548 --- /dev/null +++ b/src/result.rs @@ -0,0 +1,14 @@ +use revm::context_interface::result::HaltReason; + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum OpHaltReason { + Base(HaltReason), + FailedDeposit, +} + +impl From for OpHaltReason { + fn from(value: HaltReason) -> Self { + Self::Base(value) + } +} diff --git a/src/spec.rs b/src/spec.rs new file mode 100644 index 00000000000..747d10a9712 --- /dev/null +++ b/src/spec.rs @@ -0,0 +1,197 @@ +use core::str::FromStr; +use revm::primitives::hardfork::{name as eth_name, SpecId, UnknownHardfork}; + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[allow(non_camel_case_types)] +pub enum OpSpecId { + BEDROCK = 100, + REGOLITH, + CANYON, + ECOTONE, + FJORD, + GRANITE, + HOLOCENE, + #[default] + ISTHMUS, + INTEROP, + OSAKA, +} + +impl OpSpecId { + /// Converts the [`OpSpecId`] into a [`SpecId`]. + pub const fn into_eth_spec(self) -> SpecId { + match self { + Self::BEDROCK | Self::REGOLITH => SpecId::MERGE, + Self::CANYON => SpecId::SHANGHAI, + Self::ECOTONE | Self::FJORD | Self::GRANITE | Self::HOLOCENE => SpecId::CANCUN, + Self::ISTHMUS | Self::INTEROP => SpecId::PRAGUE, + Self::OSAKA => SpecId::OSAKA, + } + } + + pub const fn is_enabled_in(self, other: OpSpecId) -> bool { + other as u8 <= self as u8 + } +} + +impl From for SpecId { + fn from(spec: OpSpecId) -> Self { + spec.into_eth_spec() + } +} + +impl FromStr for OpSpecId { + type Err = UnknownHardfork; + + fn from_str(s: &str) -> Result { + match s { + name::BEDROCK => Ok(OpSpecId::BEDROCK), + name::REGOLITH => Ok(OpSpecId::REGOLITH), + name::CANYON => Ok(OpSpecId::CANYON), + name::ECOTONE => Ok(OpSpecId::ECOTONE), + name::FJORD => Ok(OpSpecId::FJORD), + name::GRANITE => Ok(OpSpecId::GRANITE), + name::HOLOCENE => Ok(OpSpecId::HOLOCENE), + name::ISTHMUS => Ok(OpSpecId::ISTHMUS), + name::INTEROP => Ok(OpSpecId::INTEROP), + eth_name::OSAKA => Ok(OpSpecId::OSAKA), + _ => Err(UnknownHardfork), + } + } +} + +impl From for &'static str { + fn from(spec_id: OpSpecId) -> Self { + match spec_id { + OpSpecId::BEDROCK => name::BEDROCK, + OpSpecId::REGOLITH => name::REGOLITH, + OpSpecId::CANYON => name::CANYON, + OpSpecId::ECOTONE => name::ECOTONE, + OpSpecId::FJORD => name::FJORD, + OpSpecId::GRANITE => name::GRANITE, + OpSpecId::HOLOCENE => name::HOLOCENE, + OpSpecId::ISTHMUS => name::ISTHMUS, + OpSpecId::INTEROP => name::INTEROP, + OpSpecId::OSAKA => eth_name::OSAKA, + } + } +} + +/// String identifiers for Optimism hardforks +pub mod name { + pub const BEDROCK: &str = "Bedrock"; + pub const REGOLITH: &str = "Regolith"; + pub const CANYON: &str = "Canyon"; + pub const ECOTONE: &str = "Ecotone"; + pub const FJORD: &str = "Fjord"; + pub const GRANITE: &str = "Granite"; + pub const HOLOCENE: &str = "Holocene"; + pub const ISTHMUS: &str = "Isthmus"; + pub const INTEROP: &str = "Interop"; +} + +#[cfg(test)] +mod tests { + use super::*; + use std::vec; + + #[test] + fn test_op_spec_id_eth_spec_compatibility() { + // Define test cases: (OpSpecId, enabled in ETH specs, enabled in OP specs) + let test_cases = [ + ( + OpSpecId::BEDROCK, + vec![ + (SpecId::MERGE, true), + (SpecId::SHANGHAI, false), + (SpecId::CANCUN, false), + (SpecId::default(), false), + ], + vec![(OpSpecId::BEDROCK, true), (OpSpecId::REGOLITH, false)], + ), + ( + OpSpecId::REGOLITH, + vec![ + (SpecId::MERGE, true), + (SpecId::SHANGHAI, false), + (SpecId::CANCUN, false), + (SpecId::default(), false), + ], + vec![(OpSpecId::BEDROCK, true), (OpSpecId::REGOLITH, true)], + ), + ( + OpSpecId::CANYON, + vec![ + (SpecId::MERGE, true), + (SpecId::SHANGHAI, true), + (SpecId::CANCUN, false), + (SpecId::default(), false), + ], + vec![ + (OpSpecId::BEDROCK, true), + (OpSpecId::REGOLITH, true), + (OpSpecId::CANYON, true), + ], + ), + ( + OpSpecId::ECOTONE, + vec![ + (SpecId::MERGE, true), + (SpecId::SHANGHAI, true), + (SpecId::CANCUN, true), + (SpecId::default(), false), + ], + vec![ + (OpSpecId::BEDROCK, true), + (OpSpecId::REGOLITH, true), + (OpSpecId::CANYON, true), + (OpSpecId::ECOTONE, true), + ], + ), + ( + OpSpecId::FJORD, + vec![ + (SpecId::MERGE, true), + (SpecId::SHANGHAI, true), + (SpecId::CANCUN, true), + (SpecId::default(), false), + ], + vec![ + (OpSpecId::BEDROCK, true), + (OpSpecId::REGOLITH, true), + (OpSpecId::CANYON, true), + (OpSpecId::ECOTONE, true), + (OpSpecId::FJORD, true), + ], + ), + ]; + + for (op_spec, eth_tests, op_tests) in test_cases { + // Test ETH spec compatibility + for (eth_spec, expected) in eth_tests { + assert_eq!( + op_spec.into_eth_spec().is_enabled_in(eth_spec), + expected, + "{:?} should {} be enabled in ETH {:?}", + op_spec, + if expected { "" } else { "not " }, + eth_spec + ); + } + + // Test OP spec compatibility + for (other_op_spec, expected) in op_tests { + assert_eq!( + op_spec.is_enabled_in(other_op_spec), + expected, + "{:?} should {} be enabled in OP {:?}", + op_spec, + if expected { "" } else { "not " }, + other_op_spec + ); + } + } + } +} diff --git a/src/transaction.rs b/src/transaction.rs new file mode 100644 index 00000000000..cbe16fb6240 --- /dev/null +++ b/src/transaction.rs @@ -0,0 +1,28 @@ +pub mod abstraction; +pub mod deposit; +pub mod error; + +pub use abstraction::{OpTransaction, OpTxTr}; +pub use error::OpTransactionError; + +use crate::fast_lz::flz_compress_len; + +/// +const L1_COST_FASTLZ_COEF: u64 = 836_500; + +/// +/// Inverted to be used with `saturating_sub`. +const L1_COST_INTERCEPT: u64 = 42_585_600; + +/// +const MIN_TX_SIZE_SCALED: u64 = 100 * 1_000_000; + +/// Estimates the compressed size of a transaction. +pub fn estimate_tx_compressed_size(input: &[u8]) -> u64 { + let fastlz_size = flz_compress_len(input) as u64; + + fastlz_size + .saturating_mul(L1_COST_FASTLZ_COEF) + .saturating_sub(L1_COST_INTERCEPT) + .max(MIN_TX_SIZE_SCALED) +} diff --git a/src/transaction/abstraction.rs b/src/transaction/abstraction.rs new file mode 100644 index 00000000000..7a978652ea3 --- /dev/null +++ b/src/transaction/abstraction.rs @@ -0,0 +1,196 @@ +use super::deposit::{DepositTransactionParts, DEPOSIT_TRANSACTION_TYPE}; +use auto_impl::auto_impl; +use revm::{ + context::TxEnv, + context_interface::transaction::Transaction, + handler::SystemCallTx, + primitives::{Address, Bytes, TxKind, B256, U256}, +}; +use std::vec; + +#[auto_impl(&, &mut, Box, Arc)] +pub trait OpTxTr: Transaction { + fn enveloped_tx(&self) -> Option<&Bytes>; + + /// Source hash of the deposit transaction + fn source_hash(&self) -> Option; + + /// Mint of the deposit transaction + fn mint(&self) -> Option; + + /// Whether the transaction is a system transaction + fn is_system_transaction(&self) -> bool; + + /// Returns `true` if transaction is of type [`DEPOSIT_TRANSACTION_TYPE`]. + fn is_deposit(&self) -> bool { + self.tx_type() == DEPOSIT_TRANSACTION_TYPE + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct OpTransaction { + pub base: T, + /// An enveloped EIP-2718 typed transaction + /// + /// This is used to compute the L1 tx cost using the L1 block info, as + /// opposed to requiring downstream apps to compute the cost + /// externally. + pub enveloped_tx: Option, + pub deposit: DepositTransactionParts, +} + +impl OpTransaction { + pub fn new(base: T) -> Self { + Self { + base, + enveloped_tx: None, + deposit: DepositTransactionParts::default(), + } + } +} + +impl Default for OpTransaction { + fn default() -> Self { + Self { + base: TxEnv::default(), + enveloped_tx: Some(vec![0x00].into()), + deposit: DepositTransactionParts::default(), + } + } +} + +impl SystemCallTx for OpTransaction { + fn new_system_tx(data: Bytes, system_contract_address: Address) -> Self { + OpTransaction::new(TX::new_system_tx(data, system_contract_address)) + } +} + +impl Transaction for OpTransaction { + type AccessListItem = T::AccessListItem; + type Authorization = T::Authorization; + + fn tx_type(&self) -> u8 { + self.base.tx_type() + } + + fn caller(&self) -> Address { + self.base.caller() + } + + fn gas_limit(&self) -> u64 { + self.base.gas_limit() + } + + fn value(&self) -> U256 { + self.base.value() + } + + fn input(&self) -> &Bytes { + self.base.input() + } + + fn nonce(&self) -> u64 { + self.base.nonce() + } + + fn kind(&self) -> TxKind { + self.base.kind() + } + + fn chain_id(&self) -> Option { + self.base.chain_id() + } + + fn access_list(&self) -> Option> { + self.base.access_list() + } + + fn max_priority_fee_per_gas(&self) -> Option { + self.base.max_priority_fee_per_gas() + } + + fn max_fee_per_gas(&self) -> u128 { + self.base.max_fee_per_gas() + } + + fn gas_price(&self) -> u128 { + self.base.gas_price() + } + + fn blob_versioned_hashes(&self) -> &[B256] { + self.base.blob_versioned_hashes() + } + + fn max_fee_per_blob_gas(&self) -> u128 { + self.base.max_fee_per_blob_gas() + } + + fn effective_gas_price(&self, base_fee: u128) -> u128 { + self.base.effective_gas_price(base_fee) + } + + fn authorization_list_len(&self) -> usize { + self.base.authorization_list_len() + } + + fn authorization_list(&self) -> impl Iterator { + self.base.authorization_list() + } +} + +impl OpTxTr for OpTransaction { + fn enveloped_tx(&self) -> Option<&Bytes> { + self.enveloped_tx.as_ref() + } + + fn source_hash(&self) -> Option { + if self.tx_type() != DEPOSIT_TRANSACTION_TYPE { + return None; + } + Some(self.deposit.source_hash) + } + + fn mint(&self) -> Option { + self.deposit.mint + } + + fn is_system_transaction(&self) -> bool { + self.deposit.is_system_transaction + } +} + +#[cfg(test)] +mod tests { + use crate::transaction::deposit::DEPOSIT_TRANSACTION_TYPE; + + use super::*; + use revm::primitives::{Address, B256}; + + #[test] + fn test_deposit_transaction_fields() { + let op_tx = OpTransaction { + base: TxEnv { + tx_type: DEPOSIT_TRANSACTION_TYPE, + gas_limit: 10, + gas_price: 100, + gas_priority_fee: Some(5), + ..Default::default() + }, + enveloped_tx: None, + deposit: DepositTransactionParts { + is_system_transaction: false, + mint: Some(0u128), + source_hash: B256::default(), + }, + }; + // Verify transaction type + assert_eq!(op_tx.tx_type(), DEPOSIT_TRANSACTION_TYPE); + // Verify common fields access + assert_eq!(op_tx.gas_limit(), 10); + assert_eq!(op_tx.kind(), revm::primitives::TxKind::Call(Address::ZERO)); + // Verify gas related calculations + assert_eq!(op_tx.effective_gas_price(90), 95); + assert_eq!(op_tx.max_fee_per_gas(), 100); + } +} diff --git a/src/transaction/deposit.rs b/src/transaction/deposit.rs new file mode 100644 index 00000000000..878b2ee14c9 --- /dev/null +++ b/src/transaction/deposit.rs @@ -0,0 +1,42 @@ +use revm::primitives::B256; + +pub const DEPOSIT_TRANSACTION_TYPE: u8 = 0x7E; + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct DepositTransactionParts { + pub source_hash: B256, + pub mint: Option, + pub is_system_transaction: bool, +} + +impl DepositTransactionParts { + pub fn new(source_hash: B256, mint: Option, is_system_transaction: bool) -> Self { + Self { + source_hash, + mint, + is_system_transaction, + } + } +} + +#[cfg(all(test, feature = "serde"))] +mod tests { + use super::*; + use revm::primitives::b256; + + #[test] + fn serialize_json_deposit_tx_parts() { + let response = r#"{"source_hash":"0xe927a1448525fb5d32cb50ee1408461a945ba6c39bd5cf5621407d500ecc8de9","mint":52,"is_system_transaction":false}"#; + + let deposit_tx_parts: DepositTransactionParts = serde_json::from_str(response).unwrap(); + assert_eq!( + deposit_tx_parts, + DepositTransactionParts::new( + b256!("0xe927a1448525fb5d32cb50ee1408461a945ba6c39bd5cf5621407d500ecc8de9"), + Some(0x34), + false, + ) + ); + } +} diff --git a/src/transaction/error.rs b/src/transaction/error.rs new file mode 100644 index 00000000000..a7e2301e86a --- /dev/null +++ b/src/transaction/error.rs @@ -0,0 +1,78 @@ +use core::fmt::Display; +use revm::context_interface::{ + result::{EVMError, InvalidTransaction}, + transaction::TransactionError, +}; + +/// Optimism transaction validation error. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum OpTransactionError { + Base(InvalidTransaction), + /// System transactions are not supported post-regolith hardfork. + /// + /// Before the Regolith hardfork, there was a special field in the `Deposit` transaction + /// type that differentiated between `system` and `user` deposit transactions. This field + /// was deprecated in the Regolith hardfork, and this error is thrown if a `Deposit` transaction + /// is found with this field set to `true` after the hardfork activation. + /// + /// In addition, this error is internal, and bubbles up into a [OpHaltReason::FailedDeposit][crate::OpHaltReason::FailedDeposit] error + /// in the `revm` handler for the consumer to easily handle. This is due to a state transition + /// rule on OP Stack chains where, if for any reason a deposit transaction fails, the transaction + /// must still be included in the block, the sender nonce is bumped, the `mint` value persists, and + /// special gas accounting rules are applied. Normally on L1, [EVMError::Transaction] errors + /// are cause for non-inclusion, so a special [OpHaltReason][crate::OpHaltReason] variant was introduced to handle this + /// case for failed deposit transactions. + DepositSystemTxPostRegolith, + /// Deposit transaction haults bubble up to the global main return handler, wiping state and + /// only increasing the nonce + persisting the mint value. + /// + /// This is a catch-all error for any deposit transaction that is results in a [OpHaltReason][crate::OpHaltReason] error + /// post-regolith hardfork. This allows for a consumer to easily handle special cases where + /// a deposit transaction fails during validation, but must still be included in the block. + /// + /// In addition, this error is internal, and bubbles up into a [OpHaltReason::FailedDeposit][crate::OpHaltReason::FailedDeposit] error + /// in the `revm` handler for the consumer to easily handle. This is due to a state transition + /// rule on OP Stack chains where, if for any reason a deposit transaction fails, the transaction + /// must still be included in the block, the sender nonce is bumped, the `mint` value persists, and + /// special gas accounting rules are applied. Normally on L1, [EVMError::Transaction] errors + /// are cause for non-inclusion, so a special [OpHaltReason][crate::OpHaltReason] variant was introduced to handle this + /// case for failed deposit transactions. + HaltedDepositPostRegolith, +} + +impl TransactionError for OpTransactionError {} + +impl Display for OpTransactionError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Base(error) => error.fmt(f), + Self::DepositSystemTxPostRegolith => { + write!( + f, + "deposit system transactions post regolith hardfork are not supported" + ) + } + Self::HaltedDepositPostRegolith => { + write!( + f, + "deposit transaction halted post-regolith; error will be bubbled up to main return handler" + ) + } + } + } +} + +impl core::error::Error for OpTransactionError {} + +impl From for OpTransactionError { + fn from(value: InvalidTransaction) -> Self { + Self::Base(value) + } +} + +impl From for EVMError { + fn from(value: OpTransactionError) -> Self { + Self::Transaction(value) + } +} From f84cb5e117f71bd2e680dab59a264767e9a1b1db Mon Sep 17 00:00:00 2001 From: rakita Date: Tue, 22 Apr 2025 14:30:20 +0200 Subject: [PATCH 002/225] feat(EOF): Changes needed for devnet-1 (bluealloy/revm#2377) * feat(eof): Change order of eofcreate items * new address * max_stack_size change to max_stack_increment * Introduce initcode caching, and tx type * use const * flush out txcreate * nit comment * nit * nits and comments * add missing checks * cleanup on validation * fix skipping of immediate * fix conflicts * fix eof validation * fix max increment * fix few things * fix new_eof_address fn * fix statetest for initcodes tx * add eest tests from main branch * added CI for eest tests * upgrade unit tests with new eof format * nits * fix max stack items * add initial gas calc for initcodes * bump eest tests and fix revme statetests * revert previous tests * cleanup * nits * saturation add for eof body decoding --- src/evm.rs | 14 +++++++------- src/transaction/abstraction.rs | 4 ++++ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/evm.rs b/src/evm.rs index 45c530c97d7..5f65dabd799 100644 --- a/src/evm.rs +++ b/src/evm.rs @@ -211,7 +211,7 @@ mod tests { const SPEC_ID: OpSpecId = OpSpecId::FJORD; let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &[], false, 0, 0, 0); + calculate_initial_tx_gas(SPEC_ID.into(), &[], false, 0, 0, 0, &[]); Context::op() .modify_tx_chained(|tx| { @@ -261,7 +261,7 @@ mod tests { > { let input = Bytes::from([1; GRANITE_MAX_INPUT_SIZE + 2]); let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(spec.into(), &input[..], false, 0, 0, 0); + calculate_initial_tx_gas(spec.into(), &input[..], false, 0, 0, 0, &[]); Context::op() .modify_tx_chained(|tx| { @@ -371,7 +371,7 @@ mod tests { let input = Bytes::from([1; bls12_381_const::G1_MSM_INPUT_LENGTH]); let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); + calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0, &[]); let gs1_msm_gas = bls12_381_utils::msm_required_gas( 1, &bls12_381_const::DISCOUNT_TABLE_G1_MSM, @@ -508,7 +508,7 @@ mod tests { let input = Bytes::from([1; bls12_381_const::G2_MSM_INPUT_LENGTH]); let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); + calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0, &[]); let gs2_msm_gas = bls12_381_utils::msm_required_gas( 1, &bls12_381_const::DISCOUNT_TABLE_G2_MSM, @@ -591,7 +591,7 @@ mod tests { let input = Bytes::from([1; bls12_381_const::PAIRING_INPUT_LENGTH]); let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); + calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0, &[]); let pairing_gas: u64 = bls12_381_const::PAIRING_MULTIPLIER_BASE + bls12_381_const::PAIRING_OFFSET_BASE; @@ -673,7 +673,7 @@ mod tests { let input = Bytes::from([1; bls12_381_const::PADDED_FP_LENGTH]); let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); + calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0, &[]); Context::op() .modify_tx_chained(|tx| { @@ -734,7 +734,7 @@ mod tests { let input = Bytes::from([1; bls12_381_const::PADDED_FP2_LENGTH]); let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); + calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0, &[]); Context::op() .modify_tx_chained(|tx| { diff --git a/src/transaction/abstraction.rs b/src/transaction/abstraction.rs index 7a978652ea3..763656aabee 100644 --- a/src/transaction/abstraction.rs +++ b/src/transaction/abstraction.rs @@ -137,6 +137,10 @@ impl Transaction for OpTransaction { fn authorization_list(&self) -> impl Iterator { self.base.authorization_list() } + + fn initcodes(&self) -> &[Bytes] { + self.base.initcodes() + } } impl OpTxTr for OpTransaction { From 46ca17e7a1a5942e6d38886d94bb7070974881c1 Mon Sep 17 00:00:00 2001 From: Federico Gimenez Date: Thu, 24 Apr 2025 13:10:16 +0200 Subject: [PATCH 003/225] feat: add precompiles getter to OpPrecompiles (bluealloy/revm#2444) --- src/precompiles.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/precompiles.rs b/src/precompiles.rs index 4c6481d062c..8dff6fc8a65 100644 --- a/src/precompiles.rs +++ b/src/precompiles.rs @@ -44,6 +44,12 @@ impl OpPrecompiles { spec, } } + + // Precompiles getter. + #[inline] + pub fn precompiles(&self) -> &'static Precompiles { + self.inner.precompiles + } } /// Returns precompiles for Fjord spec. From 1dc8c52f18da7f45ab3ddb525275e1da4b65156d Mon Sep 17 00:00:00 2001 From: rakita Date: Thu, 24 Apr 2025 17:36:12 +0200 Subject: [PATCH 004/225] feat(tx): Add Either RecoveredAuthorization (bluealloy/revm#2448) --- src/transaction/abstraction.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/transaction/abstraction.rs b/src/transaction/abstraction.rs index 763656aabee..775a7e02153 100644 --- a/src/transaction/abstraction.rs +++ b/src/transaction/abstraction.rs @@ -67,8 +67,14 @@ impl SystemCallTx for OpTransaction { } impl Transaction for OpTransaction { - type AccessListItem = T::AccessListItem; - type Authorization = T::Authorization; + type AccessListItem<'a> + = T::AccessListItem<'a> + where + T: 'a; + type Authorization<'a> + = T::Authorization<'a> + where + T: 'a; fn tx_type(&self) -> u8 { self.base.tx_type() @@ -102,7 +108,7 @@ impl Transaction for OpTransaction { self.base.chain_id() } - fn access_list(&self) -> Option> { + fn access_list(&self) -> Option>> { self.base.access_list() } @@ -134,7 +140,7 @@ impl Transaction for OpTransaction { self.base.authorization_list_len() } - fn authorization_list(&self) -> impl Iterator { + fn authorization_list(&self) -> impl Iterator> { self.base.authorization_list() } From 048795b1427ec3a2606770d69065e69610860bf3 Mon Sep 17 00:00:00 2001 From: rakita Date: Tue, 29 Apr 2025 11:59:48 +0200 Subject: [PATCH 005/225] feat(Handler): merge state validation with deduct_caller (bluealloy/revm#2460) * feat(Heandler): merge state validation with deduct_caller * compile * no_std fix, fix erc20 example * cleanup op handler * migration --- src/handler.rs | 181 +++++++++++++++++++++++++++---------------------- 1 file changed, 99 insertions(+), 82 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index f19f58d39f0..b03bf0ec0c7 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -6,21 +6,22 @@ use crate::{ L1BlockInfo, OpHaltReason, OpSpecId, }; use revm::{ + context::result::InvalidTransaction, context_interface::{ result::{EVMError, ExecutionResult, FromStringError, ResultAndState}, Block, Cfg, ContextTr, JournalTr, Transaction, }, handler::{ - handler::EvmTrError, validation::validate_tx_against_account, EvmTr, Frame, FrameResult, - Handler, MainnetHandler, + handler::EvmTrError, pre_execution::validate_account_nonce_and_code, EvmTr, Frame, + FrameResult, Handler, MainnetHandler, }, inspector::{Inspector, InspectorEvmTr, InspectorFrame, InspectorHandler}, interpreter::{interpreter::EthInterpreter, FrameInput, Gas}, - primitives::hardfork::SpecId, - primitives::{HashMap, U256}, + primitives::{hardfork::SpecId, HashMap, U256}, state::Account, Database, }; +use std::boxed::Box; pub struct OpHandler { pub mainnet: MainnetHandler, @@ -82,103 +83,111 @@ where self.mainnet.validate_env(evm) } - fn validate_tx_against_state(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> { - let context = evm.ctx(); - let spec = context.cfg().spec(); - let block_number = context.block().number(); - if context.tx().tx_type() == DEPOSIT_TRANSACTION_TYPE { - return Ok(()); - } else { - // The L1-cost fee is only computed for Optimism non-deposit transactions. - if context.chain().l2_block != block_number { - // L1 block info is stored in the context for later use. - // and it will be reloaded from the database if it is not for the current block. - *context.chain() = L1BlockInfo::try_fetch(context.db(), block_number, spec)?; - } - } + fn validate_against_state_and_deduct_caller( + &self, + evm: &mut Self::Evm, + ) -> Result<(), Self::Error> { + let ctx = evm.ctx(); - let enveloped_tx = context - .tx() - .enveloped_tx() - .expect("all not deposit tx have enveloped tx") - .clone(); + let basefee = ctx.block().basefee() as u128; + let blob_price = ctx.block().blob_gasprice().unwrap_or_default(); + let is_deposit = ctx.tx().tx_type() == DEPOSIT_TRANSACTION_TYPE; + let spec = ctx.cfg().spec(); + let block_number = ctx.block().number(); + let is_balance_check_disabled = ctx.cfg().is_balance_check_disabled(); + let is_eip3607_disabled = ctx.cfg().is_eip3607_disabled(); + let is_nonce_check_disabled = ctx.cfg().is_nonce_check_disabled(); + let mint = ctx.tx().mint(); - // compute L1 cost - let mut additional_cost = context.chain().calculate_tx_l1_cost(&enveloped_tx, spec); + let mut additional_cost = U256::ZERO; - if spec.is_enabled_in(OpSpecId::ISTHMUS) { - let gas_limit = U256::from(context.tx().gas_limit()); - let operator_fee_charge = context - .chain() - .operator_fee_charge(&enveloped_tx, gas_limit); + // The L1-cost fee is only computed for Optimism non-deposit transactions. + if !is_deposit { + // L1 block info is stored in the context for later use. + // and it will be reloaded from the database if it is not for the current block. + if ctx.chain().l2_block != block_number { + *ctx.chain() = L1BlockInfo::try_fetch(ctx.db(), block_number, spec)?; + } - additional_cost = additional_cost.saturating_add(operator_fee_charge); - } + // account for additional cost of l1 fee and operator fee + let enveloped_tx = ctx + .tx() + .enveloped_tx() + .expect("all not deposit tx have enveloped tx") + .clone(); - let tx_caller = context.tx().caller(); + // compute L1 cost + additional_cost = ctx.chain().calculate_tx_l1_cost(&enveloped_tx, spec); - // Load acc - let account = context.journal().load_account_code(tx_caller)?; - let account = account.data.info.clone(); + // compute operator fee + if spec.is_enabled_in(OpSpecId::ISTHMUS) { + let gas_limit = U256::from(ctx.tx().gas_limit()); + let operator_fee_charge = ctx.chain().operator_fee_charge(&enveloped_tx, gas_limit); + additional_cost = additional_cost.saturating_add(operator_fee_charge); + } + } - validate_tx_against_account(&account, context, additional_cost)?; - Ok(()) - } + let (tx, journal) = ctx.tx_journal(); - fn deduct_caller(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> { - let ctx = evm.ctx(); - let spec = ctx.cfg().spec(); - let caller = ctx.tx().caller(); - let is_deposit = ctx.tx().tx_type() == DEPOSIT_TRANSACTION_TYPE; + let caller_account = journal.load_account_code(tx.caller())?.data; // If the transaction is a deposit with a `mint` value, add the mint value // in wei to the caller's balance. This should be persisted to the database // prior to the rest of execution. - let mut tx_l1_cost = U256::ZERO; if is_deposit { - let tx = ctx.tx(); - if let Some(mint) = tx.mint() { - let mut caller_account = ctx.journal().load_account(caller)?; - caller_account.info.balance += U256::from(mint); + if let Some(mint) = mint { + caller_account.info.balance = + caller_account.info.balance.saturating_add(U256::from(mint)); } } else { - let enveloped_tx = ctx - .tx() - .enveloped_tx() - .expect("all not deposit tx have enveloped tx") - .clone(); - tx_l1_cost = ctx.chain().calculate_tx_l1_cost(&enveloped_tx, spec); + // validates account nonce and code + validate_account_nonce_and_code( + &mut caller_account.info, + tx.nonce(), + tx.kind().is_call(), + is_eip3607_disabled, + is_nonce_check_disabled, + )?; } - // We deduct caller max balance after minting and before deducing the - // L1 cost, max values is already checked in pre_validate but L1 cost wasn't. - self.mainnet.deduct_caller(evm)?; - - // If the transaction is not a deposit transaction, subtract the L1 data fee from the - // caller's balance directly after minting the requested amount of ETH. - // Additionally deduct the operator fee from the caller's account. - if !is_deposit { - let ctx = evm.ctx(); + let max_balance_spending = tx.max_balance_spending()?.saturating_add(additional_cost); + + // Check if account has enough balance for `gas_limit * max_fee`` and value transfer. + // Transfer will be done inside `*_inner` functions. + if is_balance_check_disabled { + // Make sure the caller's balance is at least the value of the transaction. + // this is not consensus critical, and it is used in testing. + caller_account.info.balance = caller_account.info.balance.max(tx.value()); + } else if !is_deposit && max_balance_spending > caller_account.info.balance { + // skip max balance check for deposit transactions. + // this check for deposit was skipped previously in `validate_tx_against_state` function + return Err(InvalidTransaction::LackOfFundForMaxFee { + fee: Box::new(max_balance_spending), + balance: Box::new(caller_account.info.balance), + } + .into()); + } else { + let effective_balance_spending = + tx.effective_balance_spending(basefee, blob_price).expect( + "effective balance is always smaller than max balance so it can't overflow", + ); - // Deduct the operator fee from the caller's account. - let gas_limit = U256::from(ctx.tx().gas_limit()); - let enveloped_tx = ctx - .tx() - .enveloped_tx() - .expect("all not deposit tx have enveloped tx") - .clone(); + // subtracting max balance spending with value that is going to be deducted later in the call. + let gas_balance_spending = effective_balance_spending - tx.value(); - let mut operator_fee_charge = U256::ZERO; - if spec.is_enabled_in(OpSpecId::ISTHMUS) { - operator_fee_charge = ctx.chain().operator_fee_charge(&enveloped_tx, gas_limit); - } + // If the transaction is not a deposit transaction, subtract the L1 data fee from the + // caller's balance directly after minting the requested amount of ETH. + // Additionally deduct the operator fee from the caller's account. + // + // In case of deposit additional cost will be zero. + let op_gas_balance_spending = gas_balance_spending.saturating_add(additional_cost); - let mut caller_account = ctx.journal().load_account(caller)?; caller_account.info.balance = caller_account .info .balance - .saturating_sub(tx_l1_cost.saturating_add(operator_fee_charge)); + .saturating_sub(op_gas_balance_spending); } + Ok(()) } @@ -622,7 +631,9 @@ mod tests { let mut evm = ctx.build_op(); let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new(); - handler.deduct_caller(&mut evm).unwrap(); + handler + .validate_against_state_and_deduct_caller(&mut evm) + .unwrap(); // Check the account balance is updated. let account = evm.ctx().journal().load_account(caller).unwrap(); @@ -660,7 +671,9 @@ mod tests { let mut evm = ctx.build_op(); let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new(); - handler.deduct_caller(&mut evm).unwrap(); + handler + .validate_against_state_and_deduct_caller(&mut evm) + .unwrap(); // Check the account balance is updated. let account = evm.ctx().journal().load_account(caller).unwrap(); @@ -697,7 +710,9 @@ mod tests { let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new(); // l1block cost is 1048 fee. - handler.deduct_caller(&mut evm).unwrap(); + handler + .validate_against_state_and_deduct_caller(&mut evm) + .unwrap(); // Check the account balance is updated. let account = evm.ctx().journal().load_account(caller).unwrap(); @@ -733,7 +748,9 @@ mod tests { // operator fee cost is operator_fee_scalar * gas_limit / 1e6 + operator_fee_constant // 10_000_000 * 10 / 1_000_000 + 50 = 150 - handler.deduct_caller(&mut evm).unwrap(); + handler + .validate_against_state_and_deduct_caller(&mut evm) + .unwrap(); // Check the account balance is updated. let account = evm.ctx().journal().load_account(caller).unwrap(); @@ -770,7 +787,7 @@ mod tests { // l1block cost is 1048 fee. assert_eq!( - handler.validate_tx_against_state(&mut evm), + handler.validate_against_state_and_deduct_caller(&mut evm), Err(EVMError::Transaction( InvalidTransaction::LackOfFundForMaxFee { fee: Box::new(U256::from(1048)), From 94ce4d50e37e79f995c3e06b02136dee040b5b8d Mon Sep 17 00:00:00 2001 From: rakita Date: Mon, 5 May 2025 17:54:53 +0200 Subject: [PATCH 006/225] feat: skip cloning of call input from shared memory (bluealloy/revm#2462) * feat: add buffer in CallInput * change precompile input to slice * add fetching from global buffer * fix bug * fix evm runner and switch to shared buffer * fix bugs * typos * add criterion * cleanup tests --- src/precompiles.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/precompiles.rs b/src/precompiles.rs index 8dff6fc8a65..3b873a12b92 100644 --- a/src/precompiles.rs +++ b/src/precompiles.rs @@ -160,10 +160,7 @@ pub mod bn128_pair { pub mod bls12_381 { use super::*; - use revm::{ - precompile::bls12_381_const::{G1_MSM_ADDRESS, G2_MSM_ADDRESS, PAIRING_ADDRESS}, - primitives::Bytes, - }; + use revm::precompile::bls12_381_const::{G1_MSM_ADDRESS, G2_MSM_ADDRESS, PAIRING_ADDRESS}; #[cfg(not(feature = "std"))] use crate::std::string::ToString; @@ -179,7 +176,7 @@ pub mod bls12_381 { pub const ISTHMUS_PAIRING: PrecompileWithAddress = PrecompileWithAddress(PAIRING_ADDRESS, run_pair); - pub fn run_g1_msm(input: &Bytes, gas_limit: u64) -> PrecompileResult { + pub fn run_g1_msm(input: &[u8], gas_limit: u64) -> PrecompileResult { if input.len() > ISTHMUS_G1_MSM_MAX_INPUT_SIZE { return Err(PrecompileError::Other( "G1MSM input length too long for OP Stack input size limitation".to_string(), @@ -188,7 +185,7 @@ pub mod bls12_381 { precompile::bls12_381::g1_msm::g1_msm(input, gas_limit) } - pub fn run_g2_msm(input: &Bytes, gas_limit: u64) -> PrecompileResult { + pub fn run_g2_msm(input: &[u8], gas_limit: u64) -> PrecompileResult { if input.len() > ISTHMUS_G2_MSM_MAX_INPUT_SIZE { return Err(PrecompileError::Other( "G2MSM input length too long for OP Stack input size limitation".to_string(), @@ -197,7 +194,7 @@ pub mod bls12_381 { precompile::bls12_381::g2_msm::g2_msm(input, gas_limit) } - pub fn run_pair(input: &Bytes, gas_limit: u64) -> PrecompileResult { + pub fn run_pair(input: &[u8], gas_limit: u64) -> PrecompileResult { if input.len() > ISTHMUS_PAIRING_MAX_INPUT_SIZE { return Err(PrecompileError::Other( "Pairing input length too long for OP Stack input size limitation".to_string(), From c4e917bb2a81ffafba5c4d3f73c4b2d5431fb55f Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Wed, 7 May 2025 14:41:57 +0200 Subject: [PATCH 007/225] test(op): Add test for verifying default OpSpecId update (bluealloy/revm#2478) --- src/spec.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/spec.rs b/src/spec.rs index 747d10a9712..bd5c09c2a05 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -194,4 +194,9 @@ mod tests { } } } + + #[test] + fn default_op_spec_id() { + assert_eq!(OpSpecId::default(), OpSpecId::ISTHMUS); + } } From 4130ef15338f8cd2f1c520f436e12f194972e897 Mon Sep 17 00:00:00 2001 From: rakita Date: Wed, 7 May 2025 14:56:38 +0200 Subject: [PATCH 008/225] feat(Osaka): disable EOF (bluealloy/revm#2480) * feat(Osaka): disable EOF * comment initcode tx --- src/evm.rs | 14 +++++++------- src/transaction/abstraction.rs | 7 ++++--- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/evm.rs b/src/evm.rs index 5f65dabd799..45c530c97d7 100644 --- a/src/evm.rs +++ b/src/evm.rs @@ -211,7 +211,7 @@ mod tests { const SPEC_ID: OpSpecId = OpSpecId::FJORD; let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &[], false, 0, 0, 0, &[]); + calculate_initial_tx_gas(SPEC_ID.into(), &[], false, 0, 0, 0); Context::op() .modify_tx_chained(|tx| { @@ -261,7 +261,7 @@ mod tests { > { let input = Bytes::from([1; GRANITE_MAX_INPUT_SIZE + 2]); let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(spec.into(), &input[..], false, 0, 0, 0, &[]); + calculate_initial_tx_gas(spec.into(), &input[..], false, 0, 0, 0); Context::op() .modify_tx_chained(|tx| { @@ -371,7 +371,7 @@ mod tests { let input = Bytes::from([1; bls12_381_const::G1_MSM_INPUT_LENGTH]); let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0, &[]); + calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); let gs1_msm_gas = bls12_381_utils::msm_required_gas( 1, &bls12_381_const::DISCOUNT_TABLE_G1_MSM, @@ -508,7 +508,7 @@ mod tests { let input = Bytes::from([1; bls12_381_const::G2_MSM_INPUT_LENGTH]); let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0, &[]); + calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); let gs2_msm_gas = bls12_381_utils::msm_required_gas( 1, &bls12_381_const::DISCOUNT_TABLE_G2_MSM, @@ -591,7 +591,7 @@ mod tests { let input = Bytes::from([1; bls12_381_const::PAIRING_INPUT_LENGTH]); let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0, &[]); + calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); let pairing_gas: u64 = bls12_381_const::PAIRING_MULTIPLIER_BASE + bls12_381_const::PAIRING_OFFSET_BASE; @@ -673,7 +673,7 @@ mod tests { let input = Bytes::from([1; bls12_381_const::PADDED_FP_LENGTH]); let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0, &[]); + calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); Context::op() .modify_tx_chained(|tx| { @@ -734,7 +734,7 @@ mod tests { let input = Bytes::from([1; bls12_381_const::PADDED_FP2_LENGTH]); let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0, &[]); + calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); Context::op() .modify_tx_chained(|tx| { diff --git a/src/transaction/abstraction.rs b/src/transaction/abstraction.rs index 775a7e02153..31714d96d14 100644 --- a/src/transaction/abstraction.rs +++ b/src/transaction/abstraction.rs @@ -144,9 +144,10 @@ impl Transaction for OpTransaction { self.base.authorization_list() } - fn initcodes(&self) -> &[Bytes] { - self.base.initcodes() - } + // TODO(EOF) + // fn initcodes(&self) -> &[Bytes] { + // self.base.initcodes() + // } } impl OpTxTr for OpTransaction { From da9a07ccc818fa34e2be121b705e5f1aca87e046 Mon Sep 17 00:00:00 2001 From: rakita Date: Wed, 7 May 2025 14:57:16 +0200 Subject: [PATCH 009/225] chore: Add clones to FrameData (bluealloy/revm#2482) Co-authored-by: Arsenii Kulikov --- src/handler.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index b03bf0ec0c7..3d8a36222ae 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -192,7 +192,7 @@ where } fn last_frame_result( - &self, + &mut self, evm: &mut Self::Evm, frame_result: &mut ::FrameResult, ) -> Result<(), Self::Error> { @@ -506,7 +506,7 @@ mod tests { 0..0, )); - let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new(); + let mut handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new(); handler .last_frame_result(&mut evm, &mut exec_result) From 9ae1aa44a41a535167b9fd7677a1d115a9941148 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Wed, 7 May 2025 15:30:10 +0200 Subject: [PATCH 010/225] test(op): Set l2 block num in reloaded isthmus l1 block info (bluealloy/revm#2465) * Add test for set l2 block num in reloaded isthmus l1 block info * Insert l1 fee test data * Fix lint --- Cargo.toml | 8 ++++- src/handler.rs | 90 ++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 95 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 27ba034738f..6b25107c9fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ rstest.workspace = true alloy-sol-types.workspace = true sha2.workspace = true serde_json = { workspace = true, features = ["alloc"] } +alloy-primitives.workspace = true [features] default = ["std", "c-kzg", "secp256k1", "portable", "blst"] @@ -48,9 +49,14 @@ std = [ "once_cell/std", "sha2/std", "serde_json/std", + "alloy-primitives/std" ] hashbrown = ["revm/hashbrown"] -serde = ["dep:serde", "revm/serde"] +serde = [ + "dep:serde", + "revm/serde", + "alloy-primitives/serde" +] portable = ["revm/portable"] dev = [ diff --git a/src/handler.rs b/src/handler.rs index 3d8a36222ae..0154fc801bd 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -475,9 +475,17 @@ where #[cfg(test)] mod tests { use super::*; - use crate::{api::default_ctx::OpContext, DefaultOp, OpBuilder}; + use crate::{ + api::default_ctx::OpContext, + constants::{ + BASE_FEE_SCALAR_OFFSET, ECOTONE_L1_BLOB_BASE_FEE_SLOT, ECOTONE_L1_FEE_SCALARS_SLOT, + L1_BASE_FEE_SLOT, L1_BLOCK_CONTRACT, OPERATOR_FEE_SCALARS_SLOT, + }, + DefaultOp, OpBuilder, + }; + use alloy_primitives::uint; use revm::{ - context::{Context, TransactionType}, + context::{BlockEnv, Context, TransactionType}, context_interface::result::InvalidTransaction, database::InMemoryDB, database_interface::EmptyDB, @@ -680,6 +688,84 @@ mod tests { assert_eq!(account.info.balance, U256::from(1010)); } + #[test] + fn test_reload_l1_block_info_isthmus() { + const BLOCK_NUM: u64 = 100; + const L1_BASE_FEE: U256 = uint!(1_U256); + const L1_BLOB_BASE_FEE: U256 = uint!(2_U256); + const L1_BASE_FEE_SCALAR: u64 = 3; + const L1_BLOB_BASE_FEE_SCALAR: u64 = 4; + const L1_FEE_SCALARS: U256 = U256::from_limbs([ + 0, + (L1_BASE_FEE_SCALAR << (64 - BASE_FEE_SCALAR_OFFSET * 2)) | L1_BLOB_BASE_FEE_SCALAR, + 0, + 0, + ]); + const OPERATOR_FEE_SCALAR: u64 = 5; + const OPERATOR_FEE_CONST: u64 = 6; + const OPERATOR_FEE: U256 = + U256::from_limbs([OPERATOR_FEE_CONST, OPERATOR_FEE_SCALAR, 0, 0]); + + let mut db = InMemoryDB::default(); + let l1_block_contract = db.load_account(L1_BLOCK_CONTRACT).unwrap(); + l1_block_contract + .storage + .insert(L1_BASE_FEE_SLOT, L1_BASE_FEE); + l1_block_contract + .storage + .insert(ECOTONE_L1_BLOB_BASE_FEE_SLOT, L1_BLOB_BASE_FEE); + l1_block_contract + .storage + .insert(ECOTONE_L1_FEE_SCALARS_SLOT, L1_FEE_SCALARS); + l1_block_contract + .storage + .insert(OPERATOR_FEE_SCALARS_SLOT, OPERATOR_FEE); + db.insert_account_info( + Address::ZERO, + AccountInfo { + balance: U256::from(1000), + ..Default::default() + }, + ); + + let ctx = Context::op() + .with_db(db) + .with_chain(L1BlockInfo { + l2_block: BLOCK_NUM + 1, // ahead by one block + ..Default::default() + }) + .with_block(BlockEnv { + number: BLOCK_NUM, + ..Default::default() + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); + + let mut evm = ctx.build_op(); + + assert_ne!(evm.ctx().chain().l2_block, BLOCK_NUM); + + let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new(); + handler + .validate_against_state_and_deduct_caller(&mut evm) + .unwrap(); + + assert_eq!( + *evm.ctx().chain(), + L1BlockInfo { + l2_block: BLOCK_NUM, + l1_base_fee: L1_BASE_FEE, + l1_base_fee_scalar: U256::from(L1_BASE_FEE_SCALAR), + l1_blob_base_fee: Some(L1_BLOB_BASE_FEE), + l1_blob_base_fee_scalar: Some(U256::from(L1_BLOB_BASE_FEE_SCALAR)), + empty_ecotone_scalars: false, + l1_fee_overhead: None, + operator_fee_scalar: Some(U256::from(OPERATOR_FEE_SCALAR)), + operator_fee_constant: Some(U256::from(OPERATOR_FEE_CONST)), + tx_l1_cost: Some(U256::ZERO), + } + ); + } + #[test] fn test_remove_l1_cost() { let caller = Address::ZERO; From a117bf3645a92c03d38923cb091d2be176505d33 Mon Sep 17 00:00:00 2001 From: rakita Date: Wed, 7 May 2025 16:01:03 +0200 Subject: [PATCH 011/225] feat: system_call switch order of inputs, address than bytes (bluealloy/revm#2485) --- src/api/exec.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/api/exec.rs b/src/api/exec.rs index e5c822492fe..be36a40282b 100644 --- a/src/api/exec.rs +++ b/src/api/exec.rs @@ -14,6 +14,7 @@ use revm::{ }, inspector::{InspectCommitEvm, InspectEvm, Inspector, InspectorHandler, JournalExt}, interpreter::{interpreter::EthInterpreter, InterpreterResult}, + primitives::{Address, Bytes}, DatabaseCommit, ExecuteCommitEvm, ExecuteEvm, }; @@ -125,8 +126,8 @@ where { fn transact_system_call( &mut self, - data: revm::primitives::Bytes, - system_contract_address: revm::primitives::Address, + system_contract_address: Address, + data: Bytes, ) -> Self::Output { self.set_tx(CTX::Tx::new_system_tx(data, system_contract_address)); let mut h = OpHandler::<_, _, EthFrame<_, _, _>>::new(); From cd6fc05e31a5e1c638a0e78ed4f4265ba2f537c1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 7 May 2025 16:20:15 +0200 Subject: [PATCH 012/225] chore: release (bluealloy/revm#2487) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 22 ++++++++++++++++++++++ Cargo.toml | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 464cc115270..1c24e2a9d52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [3.1.0](https://github.com/bluealloy/revm/compare/op-revm-v3.0.2...op-revm-v3.1.0) - 2025-05-07 + +### Added + +- system_call switch order of inputs, address than bytes ([#2485](https://github.com/bluealloy/revm/pull/2485)) +- *(Osaka)* disable EOF ([#2480](https://github.com/bluealloy/revm/pull/2480)) +- skip cloning of call input from shared memory ([#2462](https://github.com/bluealloy/revm/pull/2462)) +- *(Handler)* merge state validation with deduct_caller ([#2460](https://github.com/bluealloy/revm/pull/2460)) +- *(tx)* Add Either RecoveredAuthorization ([#2448](https://github.com/bluealloy/revm/pull/2448)) +- add precompiles getter to OpPrecompiles ([#2444](https://github.com/bluealloy/revm/pull/2444)) +- *(EOF)* Changes needed for devnet-1 ([#2377](https://github.com/bluealloy/revm/pull/2377)) + +### Other + +- *(op)* Set l2 block num in reloaded isthmus l1 block info ([#2465](https://github.com/bluealloy/revm/pull/2465)) +- Add clones to FrameData ([#2482](https://github.com/bluealloy/revm/pull/2482)) +- *(op)* Add test for verifying default OpSpecId update ([#2478](https://github.com/bluealloy/revm/pull/2478)) +- copy edit The Book ([#2463](https://github.com/bluealloy/revm/pull/2463)) +- bump dependency version ([#2431](https://github.com/bluealloy/revm/pull/2431)) +- fixed broken link ([#2421](https://github.com/bluealloy/revm/pull/2421)) +- backport from release branch ([#2415](https://github.com/bluealloy/revm/pull/2415)) ([#2416](https://github.com/bluealloy/revm/pull/2416)) + ## [3.0.2](https://github.com/bluealloy/revm/compare/op-revm-v3.0.1...op-revm-v3.0.2) - 2025-04-15 ### Other diff --git a/Cargo.toml b/Cargo.toml index 6b25107c9fc..24d349d89ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "3.0.2" +version = "3.1.0" authors.workspace = true edition.workspace = true keywords.workspace = true From 995bb70eba260cd5fbb3672ab2080892ebfe2025 Mon Sep 17 00:00:00 2001 From: rakita Date: Thu, 8 May 2025 00:41:27 +0200 Subject: [PATCH 013/225] bump: tag v71, revm v23.1.0 semver major bump (bluealloy/revm#2492) --- CHANGELOG.md | 4 ++++ Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c24e2a9d52..066dc7392d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [4.0.0](https://github.com/bluealloy/revm/compare/op-revm-v3.1.0...op-revm-v4.0.0) - 2025-05-07 + +Dependency bump + ## [3.1.0](https://github.com/bluealloy/revm/compare/op-revm-v3.0.2...op-revm-v3.1.0) - 2025-05-07 ### Added diff --git a/Cargo.toml b/Cargo.toml index 24d349d89ab..bb1df669735 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "3.1.0" +version = "4.0.0" authors.workspace = true edition.workspace = true keywords.workspace = true From 0bd1b8838fd1bb6c9503eb40259e0c7f41cf76aa Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Fri, 9 May 2025 12:25:30 +0200 Subject: [PATCH 014/225] test(op): Add test coverage to `OpTransactionError` (bluealloy/revm#2490) * Add test coverage to OpTransactionError * Fix imports --- src/transaction/error.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/transaction/error.rs b/src/transaction/error.rs index a7e2301e86a..b575c69eecb 100644 --- a/src/transaction/error.rs +++ b/src/transaction/error.rs @@ -76,3 +76,33 @@ impl From for EVMError Self::Transaction(value) } } + +#[cfg(test)] +mod test { + use super::*; + use std::string::ToString; + + #[test] + fn test_display_op_errors() { + assert_eq!( + OpTransactionError::DepositSystemTxPostRegolith.to_string(), + "deposit system transactions post regolith hardfork are not supported" + ); + assert_eq!( + OpTransactionError::HaltedDepositPostRegolith.to_string(), + "deposit transaction halted post-regolith; error will be bubbled up to main return handler" + ) + } + + #[cfg(feature = "serde")] + #[test] + fn test_serialize_json_op_transaction_error() { + let response = r#""DepositSystemTxPostRegolith""#; + + let op_transaction_error: OpTransactionError = serde_json::from_str(response).unwrap(); + assert_eq!( + op_transaction_error, + OpTransactionError::DepositSystemTxPostRegolith + ); + } +} From 4728fc18ed4e1bb8f7893cfe08ba92277d020097 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Fri, 9 May 2025 12:25:51 +0200 Subject: [PATCH 015/225] test(op): Add test coverage to OP result module (bluealloy/revm#2491) * Add test covergae to OP result module * Fix lint --- src/result.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/result.rs b/src/result.rs index 449a7ccb548..6250246ff8a 100644 --- a/src/result.rs +++ b/src/result.rs @@ -12,3 +12,20 @@ impl From for OpHaltReason { Self::Base(value) } } + +#[cfg(all(test, feature = "serde"))] +mod tests { + use super::*; + use revm::context_interface::result::OutOfGasError; + + #[test] + fn test_serialize_json_op_halt_reason() { + let response = r#"{"Base":{"OutOfGas":"Basic"}}"#; + + let op_halt_reason: OpHaltReason = serde_json::from_str(response).unwrap(); + assert_eq!( + op_halt_reason, + HaltReason::OutOfGas(OutOfGasError::Basic).into() + ); + } +} From 262101504858b4ad9425382db6b234eaacea7894 Mon Sep 17 00:00:00 2001 From: cz Date: Fri, 9 May 2025 18:27:45 +0800 Subject: [PATCH 016/225] fix(op): mark caller account as touched (bluealloy/revm#2495) --- src/handler.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/handler.rs b/src/handler.rs index 0154fc801bd..d7fe1bb185d 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -188,6 +188,8 @@ where .saturating_sub(op_gas_balance_spending); } + // Touch account so we know it is changed. + caller_account.mark_touch(); Ok(()) } From 4d68d7adc790c29335dfca53a11cbfefff0b7020 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 9 May 2025 12:32:03 +0200 Subject: [PATCH 017/225] chore(op-revm): release v4.0.1 (bluealloy/revm#2497) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 11 +++++++++++ Cargo.toml | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 066dc7392d9..4dd8eadd8f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [4.0.1](https://github.com/bluealloy/revm/compare/op-revm-v4.0.0...op-revm-v4.0.1) - 2025-05-09 + +### Fixed + +- *(op)* mark caller account as touched ([#2495](https://github.com/bluealloy/revm/pull/2495)) + +### Other + +- *(op)* Add test coverage to OP result module ([#2491](https://github.com/bluealloy/revm/pull/2491)) +- *(op)* Add test coverage to `OpTransactionError` ([#2490](https://github.com/bluealloy/revm/pull/2490)) + ## [4.0.0](https://github.com/bluealloy/revm/compare/op-revm-v3.1.0...op-revm-v4.0.0) - 2025-05-07 Dependency bump diff --git a/Cargo.toml b/Cargo.toml index bb1df669735..49dcc04d272 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "4.0.0" +version = "4.0.1" authors.workspace = true edition.workspace = true keywords.workspace = true From 9484695e19b54094a8709db1cd98a8240cb9eb53 Mon Sep 17 00:00:00 2001 From: rakita Date: Fri, 9 May 2025 16:31:12 +0200 Subject: [PATCH 018/225] fix(op): call cleanup on local context (bluealloy/revm#2499) --- src/handler.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index d7fe1bb185d..23ca42e76dc 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -6,7 +6,7 @@ use crate::{ L1BlockInfo, OpHaltReason, OpSpecId, }; use revm::{ - context::result::InvalidTransaction, + context::{result::InvalidTransaction, LocalContextTr}, context_interface::{ result::{EVMError, ExecutionResult, FromStringError, ResultAndState}, Block, Cfg, ContextTr, JournalTr, Transaction, @@ -446,9 +446,10 @@ where } else { Err(error) }; - // do cleanup + // do the cleanup evm.ctx().chain().clear_tx_l1_cost(); evm.ctx().journal().clear(); + evm.ctx().local().clear(); output } From 38c23527173a7743b5085599bdd786dfb88d58ad Mon Sep 17 00:00:00 2001 From: rakita Date: Fri, 9 May 2025 21:49:52 +0200 Subject: [PATCH 019/225] fix(op): bump nonce on deposit (bluealloy/revm#2503) --- src/handler.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/handler.rs b/src/handler.rs index 23ca42e76dc..b8de3a43b0a 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -144,12 +144,17 @@ where validate_account_nonce_and_code( &mut caller_account.info, tx.nonce(), - tx.kind().is_call(), is_eip3607_disabled, is_nonce_check_disabled, )?; } + // Bump the nonce for calls. Nonce for CREATE will be bumped in `handle_create`. + if tx.kind().is_call() { + // Nonce is already checked + caller_account.info.nonce = caller_account.info.nonce.saturating_add(1); + } + let max_balance_spending = tx.max_balance_spending()?.saturating_add(additional_cost); // Check if account has enough balance for `gas_limit * max_fee`` and value transfer. From 7e521dc5065a92faefcf9f27f2dfa9576894b1b4 Mon Sep 17 00:00:00 2001 From: rakita Date: Fri, 9 May 2025 22:02:09 +0200 Subject: [PATCH 020/225] chore(op): revert previous and localize fix (bluealloy/revm#2504) * Revert "fix(op): bump nonce on deposit (bluealloy/revm#2503)" This reverts commit 38c23527173a7743b5085599bdd786dfb88d58ad. * chore(op): revert previous and localize fix --- src/handler.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index b8de3a43b0a..155bebf4ef5 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -139,22 +139,20 @@ where caller_account.info.balance = caller_account.info.balance.saturating_add(U256::from(mint)); } + if tx.kind().is_call() { + caller_account.info.nonce = caller_account.info.nonce.saturating_add(1); + } } else { // validates account nonce and code validate_account_nonce_and_code( &mut caller_account.info, tx.nonce(), + tx.kind().is_call(), is_eip3607_disabled, is_nonce_check_disabled, )?; } - // Bump the nonce for calls. Nonce for CREATE will be bumped in `handle_create`. - if tx.kind().is_call() { - // Nonce is already checked - caller_account.info.nonce = caller_account.info.nonce.saturating_add(1); - } - let max_balance_spending = tx.max_balance_spending()?.saturating_add(additional_cost); // Check if account has enough balance for `gas_limit * max_fee`` and value transfer. From 18b8e85a07cad57627274d7b1dc625e0ca068965 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 9 May 2025 22:11:37 +0200 Subject: [PATCH 021/225] chore: release (bluealloy/revm#2500) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 11 +++++++++++ Cargo.toml | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dd8eadd8f2..3265e597f71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [4.0.2](https://github.com/bluealloy/revm/compare/op-revm-v4.0.1...op-revm-v4.0.2) - 2025-05-09 + +### Fixed + +- *(op)* bump nonce on deposit ([#2503](https://github.com/bluealloy/revm/pull/2503)) +- *(op)* call cleanup on local context ([#2499](https://github.com/bluealloy/revm/pull/2499)) + +### Other + +- *(op)* revert previous and localize fix ([#2504](https://github.com/bluealloy/revm/pull/2504)) + ## [4.0.1](https://github.com/bluealloy/revm/compare/op-revm-v4.0.0...op-revm-v4.0.1) - 2025-05-09 ### Fixed diff --git a/Cargo.toml b/Cargo.toml index 49dcc04d272..d5fb230393b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "4.0.1" +version = "4.0.2" authors.workspace = true edition.workspace = true keywords.workspace = true From 36f1ef17d13e1e439328fd59158402e6f262ab3c Mon Sep 17 00:00:00 2001 From: crStiv Date: Fri, 23 May 2025 00:41:31 +0300 Subject: [PATCH 022/225] feat(op-revm): add testdata comparison utility for EVM execution output (bluealloy/revm#2525) * Create test_utils.rs * Create basic_test.json * Create testdata_test.rs * Create migration_example.rs * Create fully_migrated_test.json * Create halt_test.json * Create revert_test.json * Create migration_example.json * Update Cargo.toml * Update test_utils.rs * Update Cargo.toml * Update migration_example.rs * Update Cargo.toml * Update test_utils.rs * Create README.md * feat: restructure op-test and cleanup test folder * self propagate feature to fix zepter --------- Co-authored-by: rakita --- Cargo.toml | 10 +- src/evm.rs | 715 ---------------- tests/common.rs | 103 +++ tests/integration.rs | 775 ++++++++++++++++++ tests/testdata/template_test.json | 14 + tests/testdata/test_deposit_tx.json | 39 + tests/testdata/test_halted_deposit_tx.json | 20 + ...all_bls12_381_g1_add_input_wrong_size.json | 132 +++ ...d_tx_call_bls12_381_g1_add_out_of_gas.json | 134 +++ ...all_bls12_381_g1_msm_input_wrong_size.json | 132 +++ ...d_tx_call_bls12_381_g1_msm_out_of_gas.json | 134 +++ ...l_bls12_381_g1_msm_wrong_input_layout.json | 132 +++ ...all_bls12_381_g2_add_input_wrong_size.json | 132 +++ ...d_tx_call_bls12_381_g2_add_out_of_gas.json | 134 +++ ...all_bls12_381_g2_msm_input_wrong_size.json | 132 +++ ...d_tx_call_bls12_381_g2_msm_out_of_gas.json | 134 +++ ...l_bls12_381_g2_msm_wrong_input_layout.json | 132 +++ ...12_381_map_fp2_to_g2_input_wrong_size.json | 132 +++ ...ll_bls12_381_map_fp2_to_g2_out_of_gas.json | 134 +++ ...s12_381_map_fp_to_g1_input_wrong_size.json | 132 +++ ...all_bls12_381_map_fp_to_g1_out_of_gas.json | 134 +++ ...ll_bls12_381_pairing_input_wrong_size.json | 132 +++ ..._tx_call_bls12_381_pairing_out_of_gas.json | 134 +++ ..._bls12_381_pairing_wrong_input_layout.json | 132 +++ .../test_halted_tx_call_bn128_pair_fjord.json | 134 +++ ...est_halted_tx_call_bn128_pair_granite.json | 132 +++ .../test_halted_tx_call_p256verify.json | 134 +++ tests/testdata/test_log_inspector.json | 167 ++++ tests/testdata/test_tx_call_p256verify.json | 135 +++ 29 files changed, 3918 insertions(+), 718 deletions(-) create mode 100644 tests/common.rs create mode 100644 tests/integration.rs create mode 100644 tests/testdata/template_test.json create mode 100644 tests/testdata/test_deposit_tx.json create mode 100644 tests/testdata/test_halted_deposit_tx.json create mode 100644 tests/testdata/test_halted_tx_call_bls12_381_g1_add_input_wrong_size.json create mode 100644 tests/testdata/test_halted_tx_call_bls12_381_g1_add_out_of_gas.json create mode 100644 tests/testdata/test_halted_tx_call_bls12_381_g1_msm_input_wrong_size.json create mode 100644 tests/testdata/test_halted_tx_call_bls12_381_g1_msm_out_of_gas.json create mode 100644 tests/testdata/test_halted_tx_call_bls12_381_g1_msm_wrong_input_layout.json create mode 100644 tests/testdata/test_halted_tx_call_bls12_381_g2_add_input_wrong_size.json create mode 100644 tests/testdata/test_halted_tx_call_bls12_381_g2_add_out_of_gas.json create mode 100644 tests/testdata/test_halted_tx_call_bls12_381_g2_msm_input_wrong_size.json create mode 100644 tests/testdata/test_halted_tx_call_bls12_381_g2_msm_out_of_gas.json create mode 100644 tests/testdata/test_halted_tx_call_bls12_381_g2_msm_wrong_input_layout.json create mode 100644 tests/testdata/test_halted_tx_call_bls12_381_map_fp2_to_g2_input_wrong_size.json create mode 100644 tests/testdata/test_halted_tx_call_bls12_381_map_fp2_to_g2_out_of_gas.json create mode 100644 tests/testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_input_wrong_size.json create mode 100644 tests/testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_out_of_gas.json create mode 100644 tests/testdata/test_halted_tx_call_bls12_381_pairing_input_wrong_size.json create mode 100644 tests/testdata/test_halted_tx_call_bls12_381_pairing_out_of_gas.json create mode 100644 tests/testdata/test_halted_tx_call_bls12_381_pairing_wrong_input_layout.json create mode 100644 tests/testdata/test_halted_tx_call_bn128_pair_fjord.json create mode 100644 tests/testdata/test_halted_tx_call_bn128_pair_granite.json create mode 100644 tests/testdata/test_halted_tx_call_p256verify.json create mode 100644 tests/testdata/test_log_inspector.json create mode 100644 tests/testdata/test_tx_call_p256verify.json diff --git a/Cargo.toml b/Cargo.toml index d5fb230393b..ec9baa457a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,12 +33,14 @@ once_cell = { workspace = true, features = ["alloc"] } serde = { workspace = true, features = ["derive", "rc"], optional = true } [dev-dependencies] -anyhow.workspace = true rstest.workspace = true alloy-sol-types.workspace = true sha2.workspace = true serde_json = { workspace = true, features = ["alloc"] } alloy-primitives.workspace = true +serde = { workspace = true, features = ["derive"] } +revm = { workspace = true, features = ["secp256r1", "serde"] } +op-revm = { workspace = true, features = ["serde"] } [features] default = ["std", "c-kzg", "secp256k1", "portable", "blst"] @@ -49,13 +51,15 @@ std = [ "once_cell/std", "sha2/std", "serde_json/std", - "alloy-primitives/std" + "alloy-primitives/std", + "op-revm/std" ] hashbrown = ["revm/hashbrown"] serde = [ "dep:serde", "revm/serde", - "alloy-primitives/serde" + "alloy-primitives/serde", + "op-revm/serde" ] portable = ["revm/portable"] diff --git a/src/evm.rs b/src/evm.rs index 45c530c97d7..bd1481f7826 100644 --- a/src/evm.rs +++ b/src/evm.rs @@ -115,718 +115,3 @@ where (&mut self.0.ctx, &mut self.0.precompiles) } } - -#[cfg(test)] -mod tests { - use crate::{ - precompiles::bn128_pair::GRANITE_MAX_INPUT_SIZE, - transaction::deposit::DEPOSIT_TRANSACTION_TYPE, DefaultOp, L1BlockInfo, OpBuilder, - OpHaltReason, OpSpecId, OpTransaction, - }; - use revm::{ - bytecode::opcode, - context::{ - result::{ExecutionResult, OutOfGasError}, - BlockEnv, CfgEnv, TxEnv, - }, - context_interface::result::HaltReason, - database::{BenchmarkDB, EmptyDB, BENCH_CALLER, BENCH_CALLER_BALANCE, BENCH_TARGET}, - interpreter::{ - gas::{calculate_initial_tx_gas, InitialAndFloorGas}, - Interpreter, InterpreterTypes, - }, - precompile::{bls12_381_const, bls12_381_utils, bn128, secp256r1, u64_to_address}, - primitives::{Address, Bytes, Log, TxKind, U256}, - state::Bytecode, - Context, ExecuteEvm, InspectEvm, Inspector, Journal, - }; - use std::vec::Vec; - - #[test] - fn test_deposit_tx() { - let ctx = Context::op() - .modify_tx_chained(|tx| { - tx.enveloped_tx = None; - tx.deposit.mint = Some(100); - tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE; - }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::HOLOCENE); - - let mut evm = ctx.build_op(); - - let output = evm.replay().unwrap(); - - // balance should be 100 - assert_eq!( - output - .state - .get(&Address::default()) - .map(|a| a.info.balance), - Some(U256::from(100)) - ); - } - - #[test] - fn test_halted_deposit_tx() { - let ctx = Context::op() - .modify_tx_chained(|tx| { - tx.enveloped_tx = None; - tx.deposit.mint = Some(100); - tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE; - tx.base.caller = BENCH_CALLER; - tx.base.kind = TxKind::Call(BENCH_TARGET); - }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::HOLOCENE) - .with_db(BenchmarkDB::new_bytecode(Bytecode::new_legacy( - [opcode::POP].into(), - ))); - - // POP would return a halt. - let mut evm = ctx.build_op(); - - let output = evm.replay().unwrap(); - - // balance should be 100 + previous balance - assert_eq!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::FailedDeposit, - gas_used: 30_000_000 - } - ); - assert_eq!( - output.state.get(&BENCH_CALLER).map(|a| a.info.balance), - Some(U256::from(100) + BENCH_CALLER_BALANCE) - ); - } - - fn p256verify_test_tx() -> Context< - BlockEnv, - OpTransaction, - CfgEnv, - EmptyDB, - Journal, - L1BlockInfo, - > { - const SPEC_ID: OpSpecId = OpSpecId::FJORD; - - let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &[], false, 0, 0, 0); - - Context::op() - .modify_tx_chained(|tx| { - tx.base.kind = TxKind::Call(u64_to_address(secp256r1::P256VERIFY_ADDRESS)); - tx.base.gas_limit = initial_gas + secp256r1::P256VERIFY_BASE_GAS_FEE; - }) - .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID) - } - - #[test] - fn test_tx_call_p256verify() { - let ctx = p256verify_test_tx(); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert successful call to P256VERIFY - assert!(output.result.is_success()); - } - - #[test] - fn test_halted_tx_call_p256verify() { - let ctx = p256verify_test_tx().modify_tx_chained(|tx| tx.base.gas_limit -= 1); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert out of gas for P256VERIFY - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), - .. - } - )); - } - - fn bn128_pair_test_tx( - spec: OpSpecId, - ) -> Context< - BlockEnv, - OpTransaction, - CfgEnv, - EmptyDB, - Journal, - L1BlockInfo, - > { - let input = Bytes::from([1; GRANITE_MAX_INPUT_SIZE + 2]); - let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(spec.into(), &input[..], false, 0, 0, 0); - - Context::op() - .modify_tx_chained(|tx| { - tx.base.kind = TxKind::Call(bn128::pair::ADDRESS); - tx.base.data = input; - tx.base.gas_limit = initial_gas; - }) - .modify_cfg_chained(|cfg| cfg.spec = spec) - } - - #[test] - fn test_halted_tx_call_bn128_pair_fjord() { - let ctx = bn128_pair_test_tx(OpSpecId::FJORD); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert out of gas - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), - .. - } - )); - } - - #[test] - fn test_halted_tx_call_bn128_pair_granite() { - let ctx = bn128_pair_test_tx(OpSpecId::GRANITE); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert bails early because input size too big - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::PrecompileError), - .. - } - )); - } - - #[test] - fn test_halted_tx_call_bls12_381_g1_add_out_of_gas() { - let ctx = Context::op() - .modify_tx_chained(|tx| { - tx.base.kind = TxKind::Call(bls12_381_const::G1_ADD_ADDRESS); - tx.base.gas_limit = 21_000 + bls12_381_const::G1_ADD_BASE_GAS_FEE - 1; - }) - .modify_chain_chained(|l1_block| { - l1_block.operator_fee_constant = Some(U256::ZERO); - l1_block.operator_fee_scalar = Some(U256::ZERO) - }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); - - let mut evm = ctx.build_op(); - - let output = evm.replay().unwrap(); - - // assert out of gas - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), - .. - } - )); - } - - #[test] - fn test_halted_tx_call_bls12_381_g1_add_input_wrong_size() { - let ctx = Context::op() - .modify_tx_chained(|tx| { - tx.base.kind = TxKind::Call(bls12_381_const::G1_ADD_ADDRESS); - tx.base.gas_limit = 21_000 + bls12_381_const::G1_ADD_BASE_GAS_FEE; - }) - .modify_chain_chained(|l1_block| { - l1_block.operator_fee_constant = Some(U256::ZERO); - l1_block.operator_fee_scalar = Some(U256::ZERO) - }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert fails post gas check, because input is wrong size - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::PrecompileError), - .. - } - )); - } - - fn g1_msm_test_tx() -> Context< - BlockEnv, - OpTransaction, - CfgEnv, - EmptyDB, - Journal, - L1BlockInfo, - > { - const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; - - let input = Bytes::from([1; bls12_381_const::G1_MSM_INPUT_LENGTH]); - let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); - let gs1_msm_gas = bls12_381_utils::msm_required_gas( - 1, - &bls12_381_const::DISCOUNT_TABLE_G1_MSM, - bls12_381_const::G1_MSM_BASE_GAS_FEE, - ); - - Context::op() - .modify_tx_chained(|tx| { - tx.base.kind = TxKind::Call(bls12_381_const::G1_MSM_ADDRESS); - tx.base.data = input; - tx.base.gas_limit = initial_gas + gs1_msm_gas; - }) - .modify_chain_chained(|l1_block| { - l1_block.operator_fee_constant = Some(U256::ZERO); - l1_block.operator_fee_scalar = Some(U256::ZERO) - }) - .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID) - } - - #[test] - fn test_halted_tx_call_bls12_381_g1_msm_input_wrong_size() { - let ctx = g1_msm_test_tx().modify_tx_chained(|tx| tx.base.data = tx.base.data.slice(1..)); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert fails pre gas check, because input is wrong size - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::PrecompileError), - .. - } - )); - } - - #[test] - fn test_halted_tx_call_bls12_381_g1_msm_out_of_gas() { - let ctx = g1_msm_test_tx().modify_tx_chained(|tx| tx.base.gas_limit -= 1); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert out of gas - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), - .. - } - )); - } - - #[test] - fn test_halted_tx_call_bls12_381_g1_msm_wrong_input_layout() { - let ctx = g1_msm_test_tx(); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert fails post gas check, because input is wrong layout - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::PrecompileError), - .. - } - )); - } - - #[test] - fn test_halted_tx_call_bls12_381_g2_add_out_of_gas() { - let ctx = Context::op() - .modify_tx_chained(|tx| { - tx.base.kind = TxKind::Call(bls12_381_const::G2_ADD_ADDRESS); - tx.base.gas_limit = 21_000 + bls12_381_const::G2_ADD_BASE_GAS_FEE - 1; - }) - .modify_chain_chained(|l1_block| { - l1_block.operator_fee_constant = Some(U256::ZERO); - l1_block.operator_fee_scalar = Some(U256::ZERO) - }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); - - let mut evm = ctx.build_op(); - - let output = evm.replay().unwrap(); - - // assert out of gas - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), - .. - } - )); - } - - #[test] - fn test_halted_tx_call_bls12_381_g2_add_input_wrong_size() { - let ctx = Context::op() - .modify_tx_chained(|tx| { - tx.base.kind = TxKind::Call(bls12_381_const::G2_ADD_ADDRESS); - tx.base.gas_limit = 21_000 + bls12_381_const::G2_ADD_BASE_GAS_FEE; - }) - .modify_chain_chained(|l1_block| { - l1_block.operator_fee_constant = Some(U256::ZERO); - l1_block.operator_fee_scalar = Some(U256::ZERO) - }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); - - let mut evm = ctx.build_op(); - - let output = evm.replay().unwrap(); - - // assert fails post gas check, because input is wrong size - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::PrecompileError), - .. - } - )); - } - - fn g2_msm_test_tx() -> Context< - BlockEnv, - OpTransaction, - CfgEnv, - EmptyDB, - Journal, - L1BlockInfo, - > { - const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; - - let input = Bytes::from([1; bls12_381_const::G2_MSM_INPUT_LENGTH]); - let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); - let gs2_msm_gas = bls12_381_utils::msm_required_gas( - 1, - &bls12_381_const::DISCOUNT_TABLE_G2_MSM, - bls12_381_const::G2_MSM_BASE_GAS_FEE, - ); - - Context::op() - .modify_tx_chained(|tx| { - tx.base.kind = TxKind::Call(bls12_381_const::G2_MSM_ADDRESS); - tx.base.data = input; - tx.base.gas_limit = initial_gas + gs2_msm_gas; - }) - .modify_chain_chained(|l1_block| { - l1_block.operator_fee_constant = Some(U256::ZERO); - l1_block.operator_fee_scalar = Some(U256::ZERO) - }) - .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID) - } - - #[test] - fn test_halted_tx_call_bls12_381_g2_msm_input_wrong_size() { - let ctx = g2_msm_test_tx().modify_tx_chained(|tx| tx.base.data = tx.base.data.slice(1..)); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert fails pre gas check, because input is wrong size - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::PrecompileError), - .. - } - )); - } - - #[test] - fn test_halted_tx_call_bls12_381_g2_msm_out_of_gas() { - let ctx = g2_msm_test_tx().modify_tx_chained(|tx| tx.base.gas_limit -= 1); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert out of gas - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), - .. - } - )); - } - - #[test] - fn test_halted_tx_call_bls12_381_g2_msm_wrong_input_layout() { - let ctx = g2_msm_test_tx(); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert fails post gas check, because input is wrong layout - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::PrecompileError), - .. - } - )); - } - - fn bl12_381_pairing_test_tx() -> Context< - BlockEnv, - OpTransaction, - CfgEnv, - EmptyDB, - Journal, - L1BlockInfo, - > { - const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; - - let input = Bytes::from([1; bls12_381_const::PAIRING_INPUT_LENGTH]); - let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); - - let pairing_gas: u64 = - bls12_381_const::PAIRING_MULTIPLIER_BASE + bls12_381_const::PAIRING_OFFSET_BASE; - - Context::op() - .modify_tx_chained(|tx| { - tx.base.kind = TxKind::Call(bls12_381_const::PAIRING_ADDRESS); - tx.base.data = input; - tx.base.gas_limit = initial_gas + pairing_gas; - }) - .modify_chain_chained(|l1_block| { - l1_block.operator_fee_constant = Some(U256::ZERO); - l1_block.operator_fee_scalar = Some(U256::ZERO) - }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS) - } - - #[test] - fn test_halted_tx_call_bls12_381_pairing_input_wrong_size() { - let ctx = bl12_381_pairing_test_tx() - .modify_tx_chained(|tx| tx.base.data = tx.base.data.slice(1..)); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert fails pre gas check, because input is wrong size - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::PrecompileError), - .. - } - )); - } - - #[test] - fn test_halted_tx_call_bls12_381_pairing_out_of_gas() { - let ctx = bl12_381_pairing_test_tx().modify_tx_chained(|tx| tx.base.gas_limit -= 1); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert out of gas - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), - .. - } - )); - } - - #[test] - fn test_tx_call_bls12_381_pairing_wrong_input_layout() { - let ctx = bl12_381_pairing_test_tx(); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert fails post gas check, because input is wrong layout - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::PrecompileError), - .. - } - )); - } - - fn fp_to_g1_test_tx() -> Context< - BlockEnv, - OpTransaction, - CfgEnv, - EmptyDB, - Journal, - L1BlockInfo, - > { - const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; - - let input = Bytes::from([1; bls12_381_const::PADDED_FP_LENGTH]); - let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); - - Context::op() - .modify_tx_chained(|tx| { - tx.base.kind = TxKind::Call(bls12_381_const::MAP_FP_TO_G1_ADDRESS); - tx.base.data = input; - tx.base.gas_limit = initial_gas + bls12_381_const::MAP_FP_TO_G1_BASE_GAS_FEE; - }) - .modify_chain_chained(|l1_block| { - l1_block.operator_fee_constant = Some(U256::ZERO); - l1_block.operator_fee_scalar = Some(U256::ZERO) - }) - .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID) - } - - #[test] - fn test_halted_tx_call_bls12_381_map_fp_to_g1_out_of_gas() { - let ctx = fp_to_g1_test_tx().modify_tx_chained(|tx| tx.base.gas_limit -= 1); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert out of gas - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), - .. - } - )); - } - - #[test] - fn test_halted_tx_call_bls12_381_map_fp_to_g1_input_wrong_size() { - let ctx = fp_to_g1_test_tx().modify_tx_chained(|tx| tx.base.data = tx.base.data.slice(1..)); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert fails post gas check, because input is wrong size - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::PrecompileError), - .. - } - )); - } - - fn fp2_to_g2_test_tx() -> Context< - BlockEnv, - OpTransaction, - CfgEnv, - EmptyDB, - Journal, - L1BlockInfo, - > { - const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; - - let input = Bytes::from([1; bls12_381_const::PADDED_FP2_LENGTH]); - let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); - - Context::op() - .modify_tx_chained(|tx| { - tx.base.kind = TxKind::Call(bls12_381_const::MAP_FP2_TO_G2_ADDRESS); - tx.base.data = input; - tx.base.gas_limit = initial_gas + bls12_381_const::MAP_FP2_TO_G2_BASE_GAS_FEE; - }) - .modify_chain_chained(|l1_block| { - l1_block.operator_fee_constant = Some(U256::ZERO); - l1_block.operator_fee_scalar = Some(U256::ZERO) - }) - .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID) - } - - #[test] - fn test_halted_tx_call_bls12_381_map_fp2_to_g2_out_of_gas() { - let ctx = fp2_to_g2_test_tx().modify_tx_chained(|tx| tx.base.gas_limit -= 1); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert out of gas - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), - .. - } - )); - } - - #[test] - fn test_halted_tx_call_bls12_381_map_fp2_to_g2_input_wrong_size() { - let ctx = - fp2_to_g2_test_tx().modify_tx_chained(|tx| tx.base.data = tx.base.data.slice(1..)); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert fails post gas check, because input is wrong size - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::PrecompileError), - .. - } - )); - } - - #[derive(Default, Debug)] - struct LogInspector { - logs: Vec, - } - - impl Inspector for LogInspector { - fn log(&mut self, _interp: &mut Interpreter, _context: &mut CTX, log: Log) { - self.logs.push(log) - } - } - - #[test] - fn test_log_inspector() { - // simple yul contract emits a log in constructor - - /*object "Contract" { - code { - log0(0, 0) - } - }*/ - - let contract_data: Bytes = Bytes::from([ - opcode::PUSH1, - 0x00, - opcode::DUP1, - opcode::LOG0, - opcode::STOP, - ]); - let bytecode = Bytecode::new_raw(contract_data); - - let ctx = Context::op() - .with_db(BenchmarkDB::new_bytecode(bytecode.clone())) - .modify_tx_chained(|tx| { - tx.base.caller = BENCH_CALLER; - tx.base.kind = TxKind::Call(BENCH_TARGET); - }); - - let mut evm = ctx.build_op_with_inspector(LogInspector::default()); - - // Run evm. - let _ = evm.inspect_replay().unwrap(); - - let inspector = &evm.0.inspector; - assert!(!inspector.logs.is_empty()); - } -} diff --git a/tests/common.rs b/tests/common.rs new file mode 100644 index 00000000000..1ce3ce52eb0 --- /dev/null +++ b/tests/common.rs @@ -0,0 +1,103 @@ +#![allow(dead_code)] + +use revm::{ + context_interface::result::{ + ExecutionResult, HaltReason, Output, ResultAndState, SuccessReason, + }, + primitives::Bytes, + state::EvmState, +}; +use serde::{de::DeserializeOwned, Serialize}; +use std::{fs, path::PathBuf}; + +// Constant for testdata directory path +pub(crate) const TESTS_TESTDATA: &str = "tests/testdata"; + +/// Compares or saves the execution output to a testdata file. +/// +/// This utility helps maintain consistent test behavior by comparing +/// execution results against known-good outputs stored in JSON files. +/// +/// # Arguments +/// +/// * `filename` - The name of the testdata file, relative to tests/testdata/ +/// * `output` - The execution output to compare or save +/// +/// # Returns +/// +/// `Ok(())` if the comparison or save was successful +/// `Err(anyhow::Error)` if there was an error +/// +/// # Note +/// +/// Tests using this function require the `serde` feature to be enabled: +/// ```bash +/// cargo test --features serde +/// ``` +pub(crate) fn compare_or_save_testdata( + filename: &str, + output: &ResultAndState, +) where + HaltReasonTy: Serialize + DeserializeOwned + PartialEq, +{ + let tests_dir = PathBuf::from(TESTS_TESTDATA); + let testdata_file = tests_dir.join(filename); + + // Create directory if it doesn't exist + if !tests_dir.exists() { + fs::create_dir_all(&tests_dir).unwrap(); + } + + // Serialize the output to JSON for saving + let output_json = serde_json::to_string_pretty(output).unwrap(); + + // If the testdata file doesn't exist, save the output + if !testdata_file.exists() { + fs::write(&testdata_file, &output_json).unwrap(); + println!("Saved testdata to {}", testdata_file.display()); + return; + } + + // Read the expected output from the testdata file + let expected_json = fs::read_to_string(&testdata_file).unwrap(); + + // Deserialize to actual ResultAndState object for proper comparison + let expected: ResultAndState = serde_json::from_str(&expected_json).unwrap(); + + // Compare the output objects directly + if output != &expected { + // If they don't match, generate a nicer error by pretty-printing both as JSON + // This helps with debugging by showing the exact differences + let expected_pretty = serde_json::to_string_pretty(&expected).unwrap(); + + panic!( + "Value does not match testdata.\nExpected:\n{}\n\nActual:\n{}", + expected_pretty, output_json + ); + } +} + +/// Example showing how to migrate an existing test to use the testdata comparison. +/// +/// This example consists of: +/// 1. The "original" test with standard assertions +/// 2. The migration approach - running assertions and saving testdata +/// 3. The final migrated test that only uses testdata comparison +#[test] +fn template_test() { + // Create a minimal result and state + let result: ResultAndState = ResultAndState { + result: ExecutionResult::Success { + reason: SuccessReason::Stop, + gas_used: 1000, + gas_refunded: 0, + logs: vec![], + output: Output::Call(Bytes::from(vec![4, 5, 6])), + }, + state: EvmState::default(), + }; + + // Simply use the testdata comparison utility + // No assertions needed - full validation is done by comparing with testdata + compare_or_save_testdata("template_test.json", &result); +} diff --git a/tests/integration.rs b/tests/integration.rs new file mode 100644 index 00000000000..5589b35c49e --- /dev/null +++ b/tests/integration.rs @@ -0,0 +1,775 @@ +mod common; + +use common::compare_or_save_testdata; +use op_revm::{ + precompiles::bn128_pair::GRANITE_MAX_INPUT_SIZE, + transaction::deposit::DEPOSIT_TRANSACTION_TYPE, DefaultOp, L1BlockInfo, OpBuilder, + OpHaltReason, OpSpecId, OpTransaction, +}; +use revm::{ + bytecode::opcode, + context::{ + result::{ExecutionResult, OutOfGasError}, + BlockEnv, CfgEnv, TxEnv, + }, + context_interface::result::HaltReason, + database::{BenchmarkDB, EmptyDB, BENCH_CALLER, BENCH_CALLER_BALANCE, BENCH_TARGET}, + interpreter::{ + gas::{calculate_initial_tx_gas, InitialAndFloorGas}, + Interpreter, InterpreterTypes, + }, + precompile::{bls12_381_const, bls12_381_utils, bn128, secp256r1, u64_to_address}, + primitives::{Address, Bytes, Log, TxKind, U256}, + state::Bytecode, + Context, ExecuteEvm, InspectEvm, Inspector, Journal, +}; +use std::vec::Vec; + +#[test] +fn test_deposit_tx() { + let ctx = Context::op() + .modify_tx_chained(|tx| { + tx.enveloped_tx = None; + tx.deposit.mint = Some(100); + tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE; + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::HOLOCENE); + + let mut evm = ctx.build_op(); + + let output = evm.replay().unwrap(); + + // balance should be 100 + assert_eq!( + output + .state + .get(&Address::default()) + .map(|a| a.info.balance), + Some(U256::from(100)) + ); + compare_or_save_testdata("test_deposit_tx.json", &output); +} + +#[test] +fn test_halted_deposit_tx() { + let ctx = Context::op() + .modify_tx_chained(|tx| { + tx.enveloped_tx = None; + tx.deposit.mint = Some(100); + tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE; + tx.base.caller = BENCH_CALLER; + tx.base.kind = TxKind::Call(BENCH_TARGET); + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::HOLOCENE) + .with_db(BenchmarkDB::new_bytecode(Bytecode::new_legacy( + [opcode::POP].into(), + ))); + + // POP would return a halt. + let mut evm = ctx.build_op(); + + let output = evm.replay().unwrap(); + + // balance should be 100 + previous balance + assert_eq!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::FailedDeposit, + gas_used: 30_000_000 + } + ); + assert_eq!( + output.state.get(&BENCH_CALLER).map(|a| a.info.balance), + Some(U256::from(100) + BENCH_CALLER_BALANCE) + ); + + compare_or_save_testdata("test_halted_deposit_tx.json", &output); +} + +fn p256verify_test_tx( +) -> Context, CfgEnv, EmptyDB, Journal, L1BlockInfo> +{ + const SPEC_ID: OpSpecId = OpSpecId::FJORD; + + let InitialAndFloorGas { initial_gas, .. } = + calculate_initial_tx_gas(SPEC_ID.into(), &[], false, 0, 0, 0); + + Context::op() + .modify_tx_chained(|tx| { + tx.base.kind = TxKind::Call(u64_to_address(secp256r1::P256VERIFY_ADDRESS)); + tx.base.gas_limit = initial_gas + secp256r1::P256VERIFY_BASE_GAS_FEE; + }) + .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID) +} + +#[test] +fn test_tx_call_p256verify() { + let ctx = p256verify_test_tx(); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert successful call to P256VERIFY + assert!(output.result.is_success()); + + compare_or_save_testdata("test_tx_call_p256verify.json", &output); +} + +#[test] +fn test_halted_tx_call_p256verify() { + let ctx = p256verify_test_tx().modify_tx_chained(|tx| tx.base.gas_limit -= 1); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert out of gas for P256VERIFY + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), + .. + } + )); + + compare_or_save_testdata("test_halted_tx_call_p256verify.json", &output); +} + +fn bn128_pair_test_tx( + spec: OpSpecId, +) -> Context, CfgEnv, EmptyDB, Journal, L1BlockInfo> +{ + let input = Bytes::from([1; GRANITE_MAX_INPUT_SIZE + 2]); + let InitialAndFloorGas { initial_gas, .. } = + calculate_initial_tx_gas(spec.into(), &input[..], false, 0, 0, 0); + + Context::op() + .modify_tx_chained(|tx| { + tx.base.kind = TxKind::Call(bn128::pair::ADDRESS); + tx.base.data = input; + tx.base.gas_limit = initial_gas; + }) + .modify_cfg_chained(|cfg| cfg.spec = spec) +} + +#[test] +fn test_halted_tx_call_bn128_pair_fjord() { + let ctx = bn128_pair_test_tx(OpSpecId::FJORD); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert out of gas + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), + .. + } + )); + + compare_or_save_testdata("test_halted_tx_call_bn128_pair_fjord.json", &output); +} + +#[test] +fn test_halted_tx_call_bn128_pair_granite() { + let ctx = bn128_pair_test_tx(OpSpecId::GRANITE); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert bails early because input size too big + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::PrecompileError), + .. + } + )); + + compare_or_save_testdata("test_halted_tx_call_bn128_pair_granite.json", &output); +} + +#[test] +fn test_halted_tx_call_bls12_381_g1_add_out_of_gas() { + let ctx = Context::op() + .modify_tx_chained(|tx| { + tx.base.kind = TxKind::Call(bls12_381_const::G1_ADD_ADDRESS); + tx.base.gas_limit = 21_000 + bls12_381_const::G1_ADD_BASE_GAS_FEE - 1; + }) + .modify_chain_chained(|l1_block| { + l1_block.operator_fee_constant = Some(U256::ZERO); + l1_block.operator_fee_scalar = Some(U256::ZERO) + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); + + let mut evm = ctx.build_op(); + + let output = evm.replay().unwrap(); + + // assert out of gas + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), + .. + } + )); + + compare_or_save_testdata( + "test_halted_tx_call_bls12_381_g1_add_out_of_gas.json", + &output, + ); +} + +#[test] +fn test_halted_tx_call_bls12_381_g1_add_input_wrong_size() { + let ctx = Context::op() + .modify_tx_chained(|tx| { + tx.base.kind = TxKind::Call(bls12_381_const::G1_ADD_ADDRESS); + tx.base.gas_limit = 21_000 + bls12_381_const::G1_ADD_BASE_GAS_FEE; + }) + .modify_chain_chained(|l1_block| { + l1_block.operator_fee_constant = Some(U256::ZERO); + l1_block.operator_fee_scalar = Some(U256::ZERO) + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert fails post gas check, because input is wrong size + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::PrecompileError), + .. + } + )); + + compare_or_save_testdata( + "test_halted_tx_call_bls12_381_g1_add_input_wrong_size.json", + &output, + ); +} + +fn g1_msm_test_tx( +) -> Context, CfgEnv, EmptyDB, Journal, L1BlockInfo> +{ + const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; + + let input = Bytes::from([1; bls12_381_const::G1_MSM_INPUT_LENGTH]); + let InitialAndFloorGas { initial_gas, .. } = + calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); + let gs1_msm_gas = bls12_381_utils::msm_required_gas( + 1, + &bls12_381_const::DISCOUNT_TABLE_G1_MSM, + bls12_381_const::G1_MSM_BASE_GAS_FEE, + ); + + Context::op() + .modify_tx_chained(|tx| { + tx.base.kind = TxKind::Call(bls12_381_const::G1_MSM_ADDRESS); + tx.base.data = input; + tx.base.gas_limit = initial_gas + gs1_msm_gas; + }) + .modify_chain_chained(|l1_block| { + l1_block.operator_fee_constant = Some(U256::ZERO); + l1_block.operator_fee_scalar = Some(U256::ZERO) + }) + .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID) +} + +#[test] +fn test_halted_tx_call_bls12_381_g1_msm_input_wrong_size() { + let ctx = g1_msm_test_tx().modify_tx_chained(|tx| tx.base.data = tx.base.data.slice(1..)); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert fails pre gas check, because input is wrong size + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::PrecompileError), + .. + } + )); + + compare_or_save_testdata( + "test_halted_tx_call_bls12_381_g1_msm_input_wrong_size.json", + &output, + ); +} + +#[test] +fn test_halted_tx_call_bls12_381_g1_msm_out_of_gas() { + let ctx = g1_msm_test_tx().modify_tx_chained(|tx| tx.base.gas_limit -= 1); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert out of gas + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), + .. + } + )); + + compare_or_save_testdata( + "test_halted_tx_call_bls12_381_g1_msm_out_of_gas.json", + &output, + ); +} + +#[test] +fn test_halted_tx_call_bls12_381_g1_msm_wrong_input_layout() { + let ctx = g1_msm_test_tx(); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert fails post gas check, because input is wrong layout + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::PrecompileError), + .. + } + )); + + compare_or_save_testdata( + "test_halted_tx_call_bls12_381_g1_msm_wrong_input_layout.json", + &output, + ); +} + +#[test] +fn test_halted_tx_call_bls12_381_g2_add_out_of_gas() { + let ctx = Context::op() + .modify_tx_chained(|tx| { + tx.base.kind = TxKind::Call(bls12_381_const::G2_ADD_ADDRESS); + tx.base.gas_limit = 21_000 + bls12_381_const::G2_ADD_BASE_GAS_FEE - 1; + }) + .modify_chain_chained(|l1_block| { + l1_block.operator_fee_constant = Some(U256::ZERO); + l1_block.operator_fee_scalar = Some(U256::ZERO) + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); + + let mut evm = ctx.build_op(); + + let output = evm.replay().unwrap(); + + // assert out of gas + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), + .. + } + )); + + compare_or_save_testdata( + "test_halted_tx_call_bls12_381_g2_add_out_of_gas.json", + &output, + ); +} + +#[test] +fn test_halted_tx_call_bls12_381_g2_add_input_wrong_size() { + let ctx = Context::op() + .modify_tx_chained(|tx| { + tx.base.kind = TxKind::Call(bls12_381_const::G2_ADD_ADDRESS); + tx.base.gas_limit = 21_000 + bls12_381_const::G2_ADD_BASE_GAS_FEE; + }) + .modify_chain_chained(|l1_block| { + l1_block.operator_fee_constant = Some(U256::ZERO); + l1_block.operator_fee_scalar = Some(U256::ZERO) + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); + + let mut evm = ctx.build_op(); + + let output = evm.replay().unwrap(); + + // assert fails post gas check, because input is wrong size + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::PrecompileError), + .. + } + )); + + compare_or_save_testdata( + "test_halted_tx_call_bls12_381_g2_add_input_wrong_size.json", + &output, + ); +} + +fn g2_msm_test_tx( +) -> Context, CfgEnv, EmptyDB, Journal, L1BlockInfo> +{ + const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; + + let input = Bytes::from([1; bls12_381_const::G2_MSM_INPUT_LENGTH]); + let InitialAndFloorGas { initial_gas, .. } = + calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); + let gs2_msm_gas = bls12_381_utils::msm_required_gas( + 1, + &bls12_381_const::DISCOUNT_TABLE_G2_MSM, + bls12_381_const::G2_MSM_BASE_GAS_FEE, + ); + + Context::op() + .modify_tx_chained(|tx| { + tx.base.kind = TxKind::Call(bls12_381_const::G2_MSM_ADDRESS); + tx.base.data = input; + tx.base.gas_limit = initial_gas + gs2_msm_gas; + }) + .modify_chain_chained(|l1_block| { + l1_block.operator_fee_constant = Some(U256::ZERO); + l1_block.operator_fee_scalar = Some(U256::ZERO) + }) + .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID) +} + +#[test] +fn test_halted_tx_call_bls12_381_g2_msm_input_wrong_size() { + let ctx = g2_msm_test_tx().modify_tx_chained(|tx| tx.base.data = tx.base.data.slice(1..)); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert fails pre gas check, because input is wrong size + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::PrecompileError), + .. + } + )); + + compare_or_save_testdata( + "test_halted_tx_call_bls12_381_g2_msm_input_wrong_size.json", + &output, + ); +} + +#[test] +fn test_halted_tx_call_bls12_381_g2_msm_out_of_gas() { + let ctx = g2_msm_test_tx().modify_tx_chained(|tx| tx.base.gas_limit -= 1); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert out of gas + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), + .. + } + )); + + compare_or_save_testdata( + "test_halted_tx_call_bls12_381_g2_msm_out_of_gas.json", + &output, + ); +} + +#[test] +fn test_halted_tx_call_bls12_381_g2_msm_wrong_input_layout() { + let ctx = g2_msm_test_tx(); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert fails post gas check, because input is wrong layout + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::PrecompileError), + .. + } + )); + + compare_or_save_testdata( + "test_halted_tx_call_bls12_381_g2_msm_wrong_input_layout.json", + &output, + ); +} + +fn bl12_381_pairing_test_tx( +) -> Context, CfgEnv, EmptyDB, Journal, L1BlockInfo> +{ + const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; + + let input = Bytes::from([1; bls12_381_const::PAIRING_INPUT_LENGTH]); + let InitialAndFloorGas { initial_gas, .. } = + calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); + + let pairing_gas: u64 = + bls12_381_const::PAIRING_MULTIPLIER_BASE + bls12_381_const::PAIRING_OFFSET_BASE; + + Context::op() + .modify_tx_chained(|tx| { + tx.base.kind = TxKind::Call(bls12_381_const::PAIRING_ADDRESS); + tx.base.data = input; + tx.base.gas_limit = initial_gas + pairing_gas; + }) + .modify_chain_chained(|l1_block| { + l1_block.operator_fee_constant = Some(U256::ZERO); + l1_block.operator_fee_scalar = Some(U256::ZERO) + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS) +} + +#[test] +fn test_halted_tx_call_bls12_381_pairing_input_wrong_size() { + let ctx = + bl12_381_pairing_test_tx().modify_tx_chained(|tx| tx.base.data = tx.base.data.slice(1..)); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert fails pre gas check, because input is wrong size + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::PrecompileError), + .. + } + )); + + compare_or_save_testdata( + "test_halted_tx_call_bls12_381_pairing_input_wrong_size.json", + &output, + ); +} + +#[test] +fn test_halted_tx_call_bls12_381_pairing_out_of_gas() { + let ctx = bl12_381_pairing_test_tx().modify_tx_chained(|tx| tx.base.gas_limit -= 1); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert out of gas + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), + .. + } + )); + + compare_or_save_testdata( + "test_halted_tx_call_bls12_381_pairing_out_of_gas.json", + &output, + ); +} + +#[test] +fn test_tx_call_bls12_381_pairing_wrong_input_layout() { + let ctx = bl12_381_pairing_test_tx(); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert fails post gas check, because input is wrong layout + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::PrecompileError), + .. + } + )); + + compare_or_save_testdata( + "test_halted_tx_call_bls12_381_pairing_wrong_input_layout.json", + &output, + ); +} + +fn fp_to_g1_test_tx( +) -> Context, CfgEnv, EmptyDB, Journal, L1BlockInfo> +{ + const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; + + let input = Bytes::from([1; bls12_381_const::PADDED_FP_LENGTH]); + let InitialAndFloorGas { initial_gas, .. } = + calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); + + Context::op() + .modify_tx_chained(|tx| { + tx.base.kind = TxKind::Call(bls12_381_const::MAP_FP_TO_G1_ADDRESS); + tx.base.data = input; + tx.base.gas_limit = initial_gas + bls12_381_const::MAP_FP_TO_G1_BASE_GAS_FEE; + }) + .modify_chain_chained(|l1_block| { + l1_block.operator_fee_constant = Some(U256::ZERO); + l1_block.operator_fee_scalar = Some(U256::ZERO) + }) + .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID) +} + +#[test] +fn test_halted_tx_call_bls12_381_map_fp_to_g1_out_of_gas() { + let ctx = fp_to_g1_test_tx().modify_tx_chained(|tx| tx.base.gas_limit -= 1); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert out of gas + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), + .. + } + )); + + compare_or_save_testdata( + "test_halted_tx_call_bls12_381_map_fp_to_g1_out_of_gas.json", + &output, + ); +} + +#[test] +fn test_halted_tx_call_bls12_381_map_fp_to_g1_input_wrong_size() { + let ctx = fp_to_g1_test_tx().modify_tx_chained(|tx| tx.base.data = tx.base.data.slice(1..)); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert fails post gas check, because input is wrong size + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::PrecompileError), + .. + } + )); + + compare_or_save_testdata( + "test_halted_tx_call_bls12_381_map_fp_to_g1_input_wrong_size.json", + &output, + ); +} + +fn fp2_to_g2_test_tx( +) -> Context, CfgEnv, EmptyDB, Journal, L1BlockInfo> +{ + const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; + + let input = Bytes::from([1; bls12_381_const::PADDED_FP2_LENGTH]); + let InitialAndFloorGas { initial_gas, .. } = + calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); + + Context::op() + .modify_tx_chained(|tx| { + tx.base.kind = TxKind::Call(bls12_381_const::MAP_FP2_TO_G2_ADDRESS); + tx.base.data = input; + tx.base.gas_limit = initial_gas + bls12_381_const::MAP_FP2_TO_G2_BASE_GAS_FEE; + }) + .modify_chain_chained(|l1_block| { + l1_block.operator_fee_constant = Some(U256::ZERO); + l1_block.operator_fee_scalar = Some(U256::ZERO) + }) + .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID) +} + +#[test] +fn test_halted_tx_call_bls12_381_map_fp2_to_g2_out_of_gas() { + let ctx = fp2_to_g2_test_tx().modify_tx_chained(|tx| tx.base.gas_limit -= 1); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert out of gas + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), + .. + } + )); + + compare_or_save_testdata( + "test_halted_tx_call_bls12_381_map_fp2_to_g2_out_of_gas.json", + &output, + ); +} + +#[test] +fn test_halted_tx_call_bls12_381_map_fp2_to_g2_input_wrong_size() { + let ctx = fp2_to_g2_test_tx().modify_tx_chained(|tx| tx.base.data = tx.base.data.slice(1..)); + + let mut evm = ctx.build_op(); + let output = evm.replay().unwrap(); + + // assert fails post gas check, because input is wrong size + assert!(matches!( + output.result, + ExecutionResult::Halt { + reason: OpHaltReason::Base(HaltReason::PrecompileError), + .. + } + )); + + compare_or_save_testdata( + "test_halted_tx_call_bls12_381_map_fp2_to_g2_input_wrong_size.json", + &output, + ); +} + +#[derive(Default, Debug)] +struct LogInspector { + logs: Vec, +} + +impl Inspector for LogInspector { + fn log(&mut self, _interp: &mut Interpreter, _context: &mut CTX, log: Log) { + self.logs.push(log) + } +} + +#[test] +fn test_log_inspector() { + // simple yul contract emits a log in constructor + + /*object "Contract" { + code { + log0(0, 0) + } + }*/ + + let contract_data: Bytes = Bytes::from([ + opcode::PUSH1, + 0x00, + opcode::DUP1, + opcode::LOG0, + opcode::STOP, + ]); + let bytecode = Bytecode::new_raw(contract_data); + + let ctx = Context::op() + .with_db(BenchmarkDB::new_bytecode(bytecode.clone())) + .modify_tx_chained(|tx| { + tx.base.caller = BENCH_CALLER; + tx.base.kind = TxKind::Call(BENCH_TARGET); + }); + + let mut evm = ctx.build_op_with_inspector(LogInspector::default()); + + // Run evm. + let output = evm.inspect_replay().unwrap(); + + let inspector = &evm.0.inspector; + assert!(!inspector.logs.is_empty()); + + compare_or_save_testdata("test_log_inspector.json", &output); +} diff --git a/tests/testdata/template_test.json b/tests/testdata/template_test.json new file mode 100644 index 00000000000..5c35711ddde --- /dev/null +++ b/tests/testdata/template_test.json @@ -0,0 +1,14 @@ +{ + "result": { + "Success": { + "reason": "Stop", + "gas_used": 1000, + "gas_refunded": 0, + "logs": [], + "output": { + "Call": "0x040506" + } + } + }, + "state": {} +} \ No newline at end of file diff --git a/tests/testdata/test_deposit_tx.json b/tests/testdata/test_deposit_tx.json new file mode 100644 index 00000000000..669672de561 --- /dev/null +++ b/tests/testdata/test_deposit_tx.json @@ -0,0 +1,39 @@ +{ + "result": { + "Success": { + "reason": "Stop", + "gas_used": 21000, + "gas_refunded": 0, + "logs": [], + "output": { + "Call": "0x" + } + } + }, + "state": { + "0x0000000000000000000000000000000000000000": { + "info": { + "balance": "0x64", + "nonce": 1, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + } + } +} \ No newline at end of file diff --git a/tests/testdata/test_halted_deposit_tx.json b/tests/testdata/test_halted_deposit_tx.json new file mode 100644 index 00000000000..85be26dfc21 --- /dev/null +++ b/tests/testdata/test_halted_deposit_tx.json @@ -0,0 +1,20 @@ +{ + "result": { + "Halt": { + "reason": "FailedDeposit", + "gas_used": 30000000 + } + }, + "state": { + "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee": { + "info": { + "balance": "0x9896e4", + "nonce": 1, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": null + }, + "storage": {}, + "status": "Touched" + } + } +} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g1_add_input_wrong_size.json b/tests/testdata/test_halted_tx_call_bls12_381_g1_add_input_wrong_size.json new file mode 100644 index 00000000000..6dbc5224716 --- /dev/null +++ b/tests/testdata/test_halted_tx_call_bls12_381_g1_add_input_wrong_size.json @@ -0,0 +1,132 @@ +{ + "result": { + "Halt": { + "reason": { + "Base": "PrecompileError" + }, + "gas_used": 21375 + } + }, + "state": { + "0x420000000000000000000000000000000000001a": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x0000000000000000000000000000000000000000": { + "info": { + "balance": "0x0", + "nonce": 1, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x4200000000000000000000000000000000000019": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x000000000000000000000000000000000000000b": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001b": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + } + } +} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g1_add_out_of_gas.json b/tests/testdata/test_halted_tx_call_bls12_381_g1_add_out_of_gas.json new file mode 100644 index 00000000000..bf40e83d4ca --- /dev/null +++ b/tests/testdata/test_halted_tx_call_bls12_381_g1_add_out_of_gas.json @@ -0,0 +1,134 @@ +{ + "result": { + "Halt": { + "reason": { + "Base": { + "OutOfGas": "Precompile" + } + }, + "gas_used": 21374 + } + }, + "state": { + "0x0000000000000000000000000000000000000000": { + "info": { + "balance": "0x0", + "nonce": 1, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x4200000000000000000000000000000000000019": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001a": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001b": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x000000000000000000000000000000000000000b": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "LoadedAsNotExisting" + } + } +} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_input_wrong_size.json b/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_input_wrong_size.json new file mode 100644 index 00000000000..8d767578dfd --- /dev/null +++ b/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_input_wrong_size.json @@ -0,0 +1,132 @@ +{ + "result": { + "Halt": { + "reason": { + "Base": "PrecompileError" + }, + "gas_used": 35560 + } + }, + "state": { + "0x4200000000000000000000000000000000000019": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001a": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x000000000000000000000000000000000000000c": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "LoadedAsNotExisting" + }, + "0x0000000000000000000000000000000000000000": { + "info": { + "balance": "0x0", + "nonce": 1, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001b": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + } + } +} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_out_of_gas.json b/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_out_of_gas.json new file mode 100644 index 00000000000..649ace7639e --- /dev/null +++ b/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_out_of_gas.json @@ -0,0 +1,134 @@ +{ + "result": { + "Halt": { + "reason": { + "Base": { + "OutOfGas": "Precompile" + } + }, + "gas_used": 35559 + } + }, + "state": { + "0x000000000000000000000000000000000000000c": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001a": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x4200000000000000000000000000000000000019": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001b": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x0000000000000000000000000000000000000000": { + "info": { + "balance": "0x0", + "nonce": 1, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + } + } +} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_wrong_input_layout.json b/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_wrong_input_layout.json new file mode 100644 index 00000000000..395d3c02188 --- /dev/null +++ b/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_wrong_input_layout.json @@ -0,0 +1,132 @@ +{ + "result": { + "Halt": { + "reason": { + "Base": "PrecompileError" + }, + "gas_used": 35560 + } + }, + "state": { + "0x420000000000000000000000000000000000001b": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x000000000000000000000000000000000000000c": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001a": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x0000000000000000000000000000000000000000": { + "info": { + "balance": "0x0", + "nonce": 1, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x4200000000000000000000000000000000000019": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + } + } +} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g2_add_input_wrong_size.json b/tests/testdata/test_halted_tx_call_bls12_381_g2_add_input_wrong_size.json new file mode 100644 index 00000000000..5b1765d3dd3 --- /dev/null +++ b/tests/testdata/test_halted_tx_call_bls12_381_g2_add_input_wrong_size.json @@ -0,0 +1,132 @@ +{ + "result": { + "Halt": { + "reason": { + "Base": "PrecompileError" + }, + "gas_used": 21600 + } + }, + "state": { + "0x420000000000000000000000000000000000001a": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x0000000000000000000000000000000000000000": { + "info": { + "balance": "0x0", + "nonce": 1, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x4200000000000000000000000000000000000019": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001b": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x000000000000000000000000000000000000000d": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "LoadedAsNotExisting" + } + } +} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g2_add_out_of_gas.json b/tests/testdata/test_halted_tx_call_bls12_381_g2_add_out_of_gas.json new file mode 100644 index 00000000000..8cd0f6db47e --- /dev/null +++ b/tests/testdata/test_halted_tx_call_bls12_381_g2_add_out_of_gas.json @@ -0,0 +1,134 @@ +{ + "result": { + "Halt": { + "reason": { + "Base": { + "OutOfGas": "Precompile" + } + }, + "gas_used": 21599 + } + }, + "state": { + "0x420000000000000000000000000000000000001a": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x0000000000000000000000000000000000000000": { + "info": { + "balance": "0x0", + "nonce": 1, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x4200000000000000000000000000000000000019": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x000000000000000000000000000000000000000d": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001b": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + } + } +} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_input_wrong_size.json b/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_input_wrong_size.json new file mode 100644 index 00000000000..3222d2cb2c5 --- /dev/null +++ b/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_input_wrong_size.json @@ -0,0 +1,132 @@ +{ + "result": { + "Halt": { + "reason": { + "Base": "PrecompileError" + }, + "gas_used": 48108 + } + }, + "state": { + "0x4200000000000000000000000000000000000019": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001a": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x000000000000000000000000000000000000000e": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001b": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x0000000000000000000000000000000000000000": { + "info": { + "balance": "0x0", + "nonce": 1, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + } + } +} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_out_of_gas.json b/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_out_of_gas.json new file mode 100644 index 00000000000..1941d7e171e --- /dev/null +++ b/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_out_of_gas.json @@ -0,0 +1,134 @@ +{ + "result": { + "Halt": { + "reason": { + "Base": { + "OutOfGas": "Precompile" + } + }, + "gas_used": 48107 + } + }, + "state": { + "0x4200000000000000000000000000000000000019": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001b": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x0000000000000000000000000000000000000000": { + "info": { + "balance": "0x0", + "nonce": 1, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x000000000000000000000000000000000000000e": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001a": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + } + } +} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_wrong_input_layout.json b/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_wrong_input_layout.json new file mode 100644 index 00000000000..977e40b2942 --- /dev/null +++ b/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_wrong_input_layout.json @@ -0,0 +1,132 @@ +{ + "result": { + "Halt": { + "reason": { + "Base": "PrecompileError" + }, + "gas_used": 48108 + } + }, + "state": { + "0x000000000000000000000000000000000000000e": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "LoadedAsNotExisting" + }, + "0x0000000000000000000000000000000000000000": { + "info": { + "balance": "0x0", + "nonce": 1, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x4200000000000000000000000000000000000019": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001b": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001a": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + } + } +} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_map_fp2_to_g2_input_wrong_size.json b/tests/testdata/test_halted_tx_call_bls12_381_map_fp2_to_g2_input_wrong_size.json new file mode 100644 index 00000000000..c8d19194e7a --- /dev/null +++ b/tests/testdata/test_halted_tx_call_bls12_381_map_fp2_to_g2_input_wrong_size.json @@ -0,0 +1,132 @@ +{ + "result": { + "Halt": { + "reason": { + "Base": "PrecompileError" + }, + "gas_used": 46848 + } + }, + "state": { + "0x420000000000000000000000000000000000001a": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001b": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x0000000000000000000000000000000000000000": { + "info": { + "balance": "0x0", + "nonce": 1, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x0000000000000000000000000000000000000011": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "LoadedAsNotExisting" + }, + "0x4200000000000000000000000000000000000019": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + } + } +} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_map_fp2_to_g2_out_of_gas.json b/tests/testdata/test_halted_tx_call_bls12_381_map_fp2_to_g2_out_of_gas.json new file mode 100644 index 00000000000..3447f1650ee --- /dev/null +++ b/tests/testdata/test_halted_tx_call_bls12_381_map_fp2_to_g2_out_of_gas.json @@ -0,0 +1,134 @@ +{ + "result": { + "Halt": { + "reason": { + "Base": { + "OutOfGas": "Precompile" + } + }, + "gas_used": 46847 + } + }, + "state": { + "0x420000000000000000000000000000000000001b": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001a": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x0000000000000000000000000000000000000011": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "LoadedAsNotExisting" + }, + "0x4200000000000000000000000000000000000019": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x0000000000000000000000000000000000000000": { + "info": { + "balance": "0x0", + "nonce": 1, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + } + } +} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_input_wrong_size.json b/tests/testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_input_wrong_size.json new file mode 100644 index 00000000000..3b2f0388a5d --- /dev/null +++ b/tests/testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_input_wrong_size.json @@ -0,0 +1,132 @@ +{ + "result": { + "Halt": { + "reason": { + "Base": "PrecompileError" + }, + "gas_used": 27524 + } + }, + "state": { + "0x0000000000000000000000000000000000000010": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001a": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x4200000000000000000000000000000000000019": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x0000000000000000000000000000000000000000": { + "info": { + "balance": "0x0", + "nonce": 1, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001b": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + } + } +} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_out_of_gas.json b/tests/testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_out_of_gas.json new file mode 100644 index 00000000000..de93cb3dd2c --- /dev/null +++ b/tests/testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_out_of_gas.json @@ -0,0 +1,134 @@ +{ + "result": { + "Halt": { + "reason": { + "Base": { + "OutOfGas": "Precompile" + } + }, + "gas_used": 27523 + } + }, + "state": { + "0x0000000000000000000000000000000000000010": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001b": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001a": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x0000000000000000000000000000000000000000": { + "info": { + "balance": "0x0", + "nonce": 1, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x4200000000000000000000000000000000000019": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + } + } +} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_pairing_input_wrong_size.json b/tests/testdata/test_halted_tx_call_bls12_381_pairing_input_wrong_size.json new file mode 100644 index 00000000000..1090b220fa3 --- /dev/null +++ b/tests/testdata/test_halted_tx_call_bls12_381_pairing_input_wrong_size.json @@ -0,0 +1,132 @@ +{ + "result": { + "Halt": { + "reason": { + "Base": "PrecompileError" + }, + "gas_used": 97444 + } + }, + "state": { + "0x0000000000000000000000000000000000000000": { + "info": { + "balance": "0x0", + "nonce": 1, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x000000000000000000000000000000000000000f": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001b": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x4200000000000000000000000000000000000019": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001a": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + } + } +} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_pairing_out_of_gas.json b/tests/testdata/test_halted_tx_call_bls12_381_pairing_out_of_gas.json new file mode 100644 index 00000000000..27b7bbe74a4 --- /dev/null +++ b/tests/testdata/test_halted_tx_call_bls12_381_pairing_out_of_gas.json @@ -0,0 +1,134 @@ +{ + "result": { + "Halt": { + "reason": { + "Base": { + "OutOfGas": "Precompile" + } + }, + "gas_used": 97443 + } + }, + "state": { + "0x4200000000000000000000000000000000000019": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001b": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001a": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x0000000000000000000000000000000000000000": { + "info": { + "balance": "0x0", + "nonce": 1, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x000000000000000000000000000000000000000f": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "LoadedAsNotExisting" + } + } +} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_pairing_wrong_input_layout.json b/tests/testdata/test_halted_tx_call_bls12_381_pairing_wrong_input_layout.json new file mode 100644 index 00000000000..dd43f6c387b --- /dev/null +++ b/tests/testdata/test_halted_tx_call_bls12_381_pairing_wrong_input_layout.json @@ -0,0 +1,132 @@ +{ + "result": { + "Halt": { + "reason": { + "Base": "PrecompileError" + }, + "gas_used": 97444 + } + }, + "state": { + "0x4200000000000000000000000000000000000019": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x000000000000000000000000000000000000000f": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001b": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001a": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x0000000000000000000000000000000000000000": { + "info": { + "balance": "0x0", + "nonce": 1, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + } + } +} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bn128_pair_fjord.json b/tests/testdata/test_halted_tx_call_bn128_pair_fjord.json new file mode 100644 index 00000000000..1caf6f8ed96 --- /dev/null +++ b/tests/testdata/test_halted_tx_call_bn128_pair_fjord.json @@ -0,0 +1,134 @@ +{ + "result": { + "Halt": { + "reason": { + "Base": { + "OutOfGas": "Precompile" + } + }, + "gas_used": 1824024 + } + }, + "state": { + "0x0000000000000000000000000000000000000008": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "LoadedAsNotExisting" + }, + "0x0000000000000000000000000000000000000000": { + "info": { + "balance": "0x0", + "nonce": 1, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001a": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001b": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x4200000000000000000000000000000000000019": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + } + } +} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bn128_pair_granite.json b/tests/testdata/test_halted_tx_call_bn128_pair_granite.json new file mode 100644 index 00000000000..3c3eb400966 --- /dev/null +++ b/tests/testdata/test_halted_tx_call_bn128_pair_granite.json @@ -0,0 +1,132 @@ +{ + "result": { + "Halt": { + "reason": { + "Base": "PrecompileError" + }, + "gas_used": 1824024 + } + }, + "state": { + "0x0000000000000000000000000000000000000000": { + "info": { + "balance": "0x0", + "nonce": 1, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x0000000000000000000000000000000000000008": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001a": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x4200000000000000000000000000000000000019": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001b": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + } + } +} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_p256verify.json b/tests/testdata/test_halted_tx_call_p256verify.json new file mode 100644 index 00000000000..4259f40720c --- /dev/null +++ b/tests/testdata/test_halted_tx_call_p256verify.json @@ -0,0 +1,134 @@ +{ + "result": { + "Halt": { + "reason": { + "Base": { + "OutOfGas": "Precompile" + } + }, + "gas_used": 24449 + } + }, + "state": { + "0x4200000000000000000000000000000000000019": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001b": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x0000000000000000000000000000000000000100": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "LoadedAsNotExisting" + }, + "0x0000000000000000000000000000000000000000": { + "info": { + "balance": "0x0", + "nonce": 1, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001a": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + } + } +} \ No newline at end of file diff --git a/tests/testdata/test_log_inspector.json b/tests/testdata/test_log_inspector.json new file mode 100644 index 00000000000..871989fad3f --- /dev/null +++ b/tests/testdata/test_log_inspector.json @@ -0,0 +1,167 @@ +{ + "result": { + "Success": { + "reason": "Stop", + "gas_used": 21381, + "gas_refunded": 0, + "logs": [ + { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "topics": [], + "data": "0x" + } + ], + "output": { + "Call": "0x" + } + } + }, + "state": { + "0x4200000000000000000000000000000000000019": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x0000000000000000000000000000000000000000": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001a": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001b": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0xffffffffffffffffffffffffffffffffffffffff": { + "info": { + "balance": "0x989680", + "nonce": 1, + "code_hash": "0x8013c386d5a03c3fa0a6ccbfefcf7d91471dedba2bb94eefef57f916e5929a8d", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x600080a000", + "original_len": 5, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 5, + "data": [ + 0 + ] + } + } + } + }, + "storage": {}, + "status": "Touched" + }, + "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee": { + "info": { + "balance": "0x989680", + "nonce": 1, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched" + } + } +} \ No newline at end of file diff --git a/tests/testdata/test_tx_call_p256verify.json b/tests/testdata/test_tx_call_p256verify.json new file mode 100644 index 00000000000..531842c82fe --- /dev/null +++ b/tests/testdata/test_tx_call_p256verify.json @@ -0,0 +1,135 @@ +{ + "result": { + "Success": { + "reason": "Return", + "gas_used": 24450, + "gas_refunded": 0, + "logs": [], + "output": { + "Call": "0x" + } + } + }, + "state": { + "0x4200000000000000000000000000000000000019": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x0000000000000000000000000000000000000000": { + "info": { + "balance": "0x0", + "nonce": 1, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001a": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001b": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x0000000000000000000000000000000000000100": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + } + } +} \ No newline at end of file From 1807104382ca0021419a4fc8568ddea34482bb9c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 23 May 2025 03:35:54 +0200 Subject: [PATCH 023/225] chore: release (bluealloy/revm#2527) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 10 ++++++++++ Cargo.toml | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3265e597f71..f6d9dfa1210 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [4.1.0](https://github.com/bluealloy/revm/compare/op-revm-v4.0.2...op-revm-v4.1.0) - 2025-05-22 + +### Added + +- *(op-revm)* add testdata comparison utility for EVM execution output ([#2525](https://github.com/bluealloy/revm/pull/2525)) + +### Other + +- make crates.io version badge clickable ([#2526](https://github.com/bluealloy/revm/pull/2526)) + ## [4.0.2](https://github.com/bluealloy/revm/compare/op-revm-v4.0.1...op-revm-v4.0.2) - 2025-05-09 ### Fixed diff --git a/Cargo.toml b/Cargo.toml index ec9baa457a4..fecca356ebe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "4.0.2" +version = "4.1.0" authors.workspace = true edition.workspace = true keywords.workspace = true From 033e50e9e7a0dc8e4a079d374d460f86801deeec Mon Sep 17 00:00:00 2001 From: rakita Date: Fri, 23 May 2025 03:55:55 +0200 Subject: [PATCH 024/225] bump: tag v74 revm v24.0.0 (bluealloy/revm#2539) --- CHANGELOG.md | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6d9dfa1210..891c9952cfa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [4.1.0](https://github.com/bluealloy/revm/compare/op-revm-v4.0.2...op-revm-v4.1.0) - 2025-05-22 +## [5.0.0](https://github.com/bluealloy/revm/compare/op-revm-v4.0.2...op-revm-v5.0.0) - 2025-05-22 ### Added diff --git a/Cargo.toml b/Cargo.toml index fecca356ebe..0d937207e13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "4.1.0" +version = "5.0.0" authors.workspace = true edition.workspace = true keywords.workspace = true From fb8d834d13298dcf12affa15c012e0f9d47b747d Mon Sep 17 00:00:00 2001 From: rakita Date: Fri, 23 May 2025 04:41:32 +0200 Subject: [PATCH 025/225] chore: op remove circular dep (bluealloy/revm#2540) --- Cargo.toml | 10 +--------- tests/common.rs | 14 +++++++++++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0d937207e13..430c55f6e46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,8 +39,6 @@ sha2.workspace = true serde_json = { workspace = true, features = ["alloc"] } alloy-primitives.workspace = true serde = { workspace = true, features = ["derive"] } -revm = { workspace = true, features = ["secp256r1", "serde"] } -op-revm = { workspace = true, features = ["serde"] } [features] default = ["std", "c-kzg", "secp256k1", "portable", "blst"] @@ -52,15 +50,9 @@ std = [ "sha2/std", "serde_json/std", "alloy-primitives/std", - "op-revm/std" ] hashbrown = ["revm/hashbrown"] -serde = [ - "dep:serde", - "revm/serde", - "alloy-primitives/serde", - "op-revm/serde" -] +serde = ["dep:serde", "revm/serde", "alloy-primitives/serde"] portable = ["revm/portable"] dev = [ diff --git a/tests/common.rs b/tests/common.rs index 1ce3ce52eb0..1baff3c6e91 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -7,12 +7,18 @@ use revm::{ primitives::Bytes, state::EvmState, }; -use serde::{de::DeserializeOwned, Serialize}; -use std::{fs, path::PathBuf}; // Constant for testdata directory path pub(crate) const TESTS_TESTDATA: &str = "tests/testdata"; +#[cfg(not(feature = "serde"))] +pub(crate) fn compare_or_save_testdata( + _filename: &str, + _output: &ResultAndState, +) { + // serde needs to be enabled to use this function +} + /// Compares or saves the execution output to a testdata file. /// /// This utility helps maintain consistent test behavior by comparing @@ -34,12 +40,14 @@ pub(crate) const TESTS_TESTDATA: &str = "tests/testdata"; /// ```bash /// cargo test --features serde /// ``` +#[cfg(feature = "serde")] pub(crate) fn compare_or_save_testdata( filename: &str, output: &ResultAndState, ) where - HaltReasonTy: Serialize + DeserializeOwned + PartialEq, + HaltReasonTy: serde::Serialize + serde::de::DeserializeOwned + PartialEq, { + use std::{fs, path::PathBuf}; let tests_dir = PathBuf::from(TESTS_TESTDATA); let testdata_file = tests_dir.join(filename); From a384b25a286a8a463c25402b7ed0e0d3d85eff0b Mon Sep 17 00:00:00 2001 From: rakita Date: Fri, 23 May 2025 18:31:01 +0200 Subject: [PATCH 026/225] chore(test): preserve order of fields in json fixtures (bluealloy/revm#2541) --- Cargo.toml | 2 +- ...all_bls12_381_g1_add_input_wrong_size.json | 18 ++++---- ...d_tx_call_bls12_381_g1_add_out_of_gas.json | 18 ++++---- ...all_bls12_381_g1_msm_input_wrong_size.json | 16 +++---- ...d_tx_call_bls12_381_g1_msm_out_of_gas.json | 18 ++++---- ...l_bls12_381_g1_msm_wrong_input_layout.json | 10 ++--- ...all_bls12_381_g2_add_input_wrong_size.json | 18 ++++---- ...d_tx_call_bls12_381_g2_add_out_of_gas.json | 16 +++---- ...all_bls12_381_g2_msm_input_wrong_size.json | 18 ++++---- ...d_tx_call_bls12_381_g2_msm_out_of_gas.json | 16 +++---- ...l_bls12_381_g2_msm_wrong_input_layout.json | 12 ++--- ...ll_bls12_381_map_fp2_to_g2_out_of_gas.json | 18 ++++---- ...s12_381_map_fp_to_g1_input_wrong_size.json | 10 ++--- ...all_bls12_381_map_fp_to_g1_out_of_gas.json | 18 ++++---- ...ll_bls12_381_pairing_input_wrong_size.json | 10 ++--- ..._tx_call_bls12_381_pairing_out_of_gas.json | 16 +++---- ..._bls12_381_pairing_wrong_input_layout.json | 16 +++---- .../test_halted_tx_call_bn128_pair_fjord.json | 10 ++--- ...est_halted_tx_call_bn128_pair_granite.json | 18 ++++---- .../test_halted_tx_call_p256verify.json | 12 ++--- tests/testdata/test_log_inspector.json | 44 +++++++++---------- tests/testdata/test_tx_call_p256verify.json | 14 +++--- 22 files changed, 174 insertions(+), 174 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 430c55f6e46..ccd8a3adceb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ serde = { workspace = true, features = ["derive", "rc"], optional = true } rstest.workspace = true alloy-sol-types.workspace = true sha2.workspace = true -serde_json = { workspace = true, features = ["alloc"] } +serde_json = { workspace = true, features = ["alloc", "preserve_order"] } alloy-primitives.workspace = true serde = { workspace = true, features = ["derive"] } diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g1_add_input_wrong_size.json b/tests/testdata/test_halted_tx_call_bls12_381_g1_add_input_wrong_size.json index 6dbc5224716..6a5a9e51632 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_g1_add_input_wrong_size.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_g1_add_input_wrong_size.json @@ -8,10 +8,10 @@ } }, "state": { - "0x420000000000000000000000000000000000001a": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -32,10 +32,10 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000000": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -56,7 +56,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x4200000000000000000000000000000000000019": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", "nonce": 0, @@ -80,7 +80,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x000000000000000000000000000000000000000b": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", "nonce": 0, @@ -102,9 +102,9 @@ } }, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001b": { + "0x000000000000000000000000000000000000000b": { "info": { "balance": "0x0", "nonce": 0, @@ -126,7 +126,7 @@ } }, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" } } } \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g1_add_out_of_gas.json b/tests/testdata/test_halted_tx_call_bls12_381_g1_add_out_of_gas.json index bf40e83d4ca..190c24787e4 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_g1_add_out_of_gas.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_g1_add_out_of_gas.json @@ -10,10 +10,10 @@ } }, "state": { - "0x0000000000000000000000000000000000000000": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -34,10 +34,10 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x4200000000000000000000000000000000000019": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -58,7 +58,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001a": { + "0x000000000000000000000000000000000000000b": { "info": { "balance": "0x0", "nonce": 0, @@ -80,9 +80,9 @@ } }, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001b": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", "nonce": 0, @@ -106,7 +106,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x000000000000000000000000000000000000000b": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", "nonce": 0, @@ -128,7 +128,7 @@ } }, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" } } } \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_input_wrong_size.json b/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_input_wrong_size.json index 8d767578dfd..00a43576026 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_input_wrong_size.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_input_wrong_size.json @@ -8,10 +8,10 @@ } }, "state": { - "0x4200000000000000000000000000000000000019": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -56,7 +56,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x000000000000000000000000000000000000000c": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", "nonce": 0, @@ -78,12 +78,12 @@ } }, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000000": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -104,7 +104,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001b": { + "0x000000000000000000000000000000000000000c": { "info": { "balance": "0x0", "nonce": 0, @@ -126,7 +126,7 @@ } }, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" } } } \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_out_of_gas.json b/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_out_of_gas.json index 649ace7639e..3542712059c 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_out_of_gas.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_out_of_gas.json @@ -10,7 +10,7 @@ } }, "state": { - "0x000000000000000000000000000000000000000c": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", "nonce": 0, @@ -32,9 +32,9 @@ } }, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001a": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", "nonce": 0, @@ -58,10 +58,10 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x4200000000000000000000000000000000000019": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -82,7 +82,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001b": { + "0x000000000000000000000000000000000000000c": { "info": { "balance": "0x0", "nonce": 0, @@ -104,12 +104,12 @@ } }, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000000": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_wrong_input_layout.json b/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_wrong_input_layout.json index 395d3c02188..21ebc4e4127 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_wrong_input_layout.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_wrong_input_layout.json @@ -8,7 +8,7 @@ } }, "state": { - "0x420000000000000000000000000000000000001b": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", "nonce": 0, @@ -80,10 +80,10 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000000": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -104,10 +104,10 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x4200000000000000000000000000000000000019": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g2_add_input_wrong_size.json b/tests/testdata/test_halted_tx_call_bls12_381_g2_add_input_wrong_size.json index 5b1765d3dd3..8eefe5faae7 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_g2_add_input_wrong_size.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_g2_add_input_wrong_size.json @@ -8,7 +8,7 @@ } }, "state": { - "0x420000000000000000000000000000000000001a": { + "0x000000000000000000000000000000000000000d": { "info": { "balance": "0x0", "nonce": 0, @@ -30,12 +30,12 @@ } }, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000000": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -56,10 +56,10 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x4200000000000000000000000000000000000019": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -80,7 +80,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001b": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", "nonce": 0, @@ -104,7 +104,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x000000000000000000000000000000000000000d": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", "nonce": 0, @@ -126,7 +126,7 @@ } }, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" } } } \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g2_add_out_of_gas.json b/tests/testdata/test_halted_tx_call_bls12_381_g2_add_out_of_gas.json index 8cd0f6db47e..1ef0993029a 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_g2_add_out_of_gas.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_g2_add_out_of_gas.json @@ -10,10 +10,10 @@ } }, "state": { - "0x420000000000000000000000000000000000001a": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -34,10 +34,10 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000000": { + "0x000000000000000000000000000000000000000d": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -56,7 +56,7 @@ } }, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" }, "0x4200000000000000000000000000000000000019": { "info": { @@ -82,7 +82,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x000000000000000000000000000000000000000d": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", "nonce": 0, @@ -104,9 +104,9 @@ } }, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001b": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", "nonce": 0, diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_input_wrong_size.json b/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_input_wrong_size.json index 3222d2cb2c5..0dbd565bd36 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_input_wrong_size.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_input_wrong_size.json @@ -8,7 +8,7 @@ } }, "state": { - "0x4200000000000000000000000000000000000019": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", "nonce": 0, @@ -32,7 +32,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001a": { + "0x000000000000000000000000000000000000000e": { "info": { "balance": "0x0", "nonce": 0, @@ -54,9 +54,9 @@ } }, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" }, - "0x000000000000000000000000000000000000000e": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", "nonce": 0, @@ -78,12 +78,12 @@ } }, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001b": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -104,10 +104,10 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000000": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_out_of_gas.json b/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_out_of_gas.json index 1941d7e171e..76216d0132a 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_out_of_gas.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_out_of_gas.json @@ -10,7 +10,7 @@ } }, "state": { - "0x4200000000000000000000000000000000000019": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", "nonce": 0, @@ -34,7 +34,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001b": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", "nonce": 0, @@ -58,10 +58,10 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000000": { + "0x000000000000000000000000000000000000000e": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -80,12 +80,12 @@ } }, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" }, - "0x000000000000000000000000000000000000000e": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -104,7 +104,7 @@ } }, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" }, "0x420000000000000000000000000000000000001a": { "info": { diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_wrong_input_layout.json b/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_wrong_input_layout.json index 977e40b2942..e467228e9ad 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_wrong_input_layout.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_wrong_input_layout.json @@ -32,10 +32,10 @@ "storage": {}, "status": "LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000000": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -56,10 +56,10 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x4200000000000000000000000000000000000019": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -80,7 +80,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001b": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", "nonce": 0, @@ -104,7 +104,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001a": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", "nonce": 0, diff --git a/tests/testdata/test_halted_tx_call_bls12_381_map_fp2_to_g2_out_of_gas.json b/tests/testdata/test_halted_tx_call_bls12_381_map_fp2_to_g2_out_of_gas.json index 3447f1650ee..0794b5db9b1 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_map_fp2_to_g2_out_of_gas.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_map_fp2_to_g2_out_of_gas.json @@ -10,7 +10,7 @@ } }, "state": { - "0x420000000000000000000000000000000000001b": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", "nonce": 0, @@ -34,10 +34,10 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001a": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -58,7 +58,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000011": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", "nonce": 0, @@ -80,9 +80,9 @@ } }, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" }, - "0x4200000000000000000000000000000000000019": { + "0x0000000000000000000000000000000000000011": { "info": { "balance": "0x0", "nonce": 0, @@ -104,12 +104,12 @@ } }, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000000": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { diff --git a/tests/testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_input_wrong_size.json b/tests/testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_input_wrong_size.json index 3b2f0388a5d..b3918b0f65b 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_input_wrong_size.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_input_wrong_size.json @@ -56,7 +56,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x4200000000000000000000000000000000000019": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", "nonce": 0, @@ -80,10 +80,10 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000000": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -104,10 +104,10 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001b": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { diff --git a/tests/testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_out_of_gas.json b/tests/testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_out_of_gas.json index de93cb3dd2c..7bfd1f92224 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_out_of_gas.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_out_of_gas.json @@ -10,7 +10,7 @@ } }, "state": { - "0x0000000000000000000000000000000000000010": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", "nonce": 0, @@ -32,9 +32,9 @@ } }, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001b": { + "0x0000000000000000000000000000000000000010": { "info": { "balance": "0x0", "nonce": 0, @@ -56,9 +56,9 @@ } }, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001a": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", "nonce": 0, @@ -82,10 +82,10 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000000": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -106,10 +106,10 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x4200000000000000000000000000000000000019": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { diff --git a/tests/testdata/test_halted_tx_call_bls12_381_pairing_input_wrong_size.json b/tests/testdata/test_halted_tx_call_bls12_381_pairing_input_wrong_size.json index 1090b220fa3..2195898c009 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_pairing_input_wrong_size.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_pairing_input_wrong_size.json @@ -32,7 +32,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x000000000000000000000000000000000000000f": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", "nonce": 0, @@ -54,9 +54,9 @@ } }, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001b": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", "nonce": 0, @@ -80,7 +80,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x4200000000000000000000000000000000000019": { + "0x000000000000000000000000000000000000000f": { "info": { "balance": "0x0", "nonce": 0, @@ -102,7 +102,7 @@ } }, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" }, "0x420000000000000000000000000000000000001a": { "info": { diff --git a/tests/testdata/test_halted_tx_call_bls12_381_pairing_out_of_gas.json b/tests/testdata/test_halted_tx_call_bls12_381_pairing_out_of_gas.json index 27b7bbe74a4..8a0ac761aa5 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_pairing_out_of_gas.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_pairing_out_of_gas.json @@ -10,7 +10,7 @@ } }, "state": { - "0x4200000000000000000000000000000000000019": { + "0x000000000000000000000000000000000000000f": { "info": { "balance": "0x0", "nonce": 0, @@ -32,7 +32,7 @@ } }, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" }, "0x420000000000000000000000000000000000001b": { "info": { @@ -58,10 +58,10 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001a": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -82,10 +82,10 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000000": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -106,7 +106,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x000000000000000000000000000000000000000f": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", "nonce": 0, @@ -128,7 +128,7 @@ } }, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" } } } \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_pairing_wrong_input_layout.json b/tests/testdata/test_halted_tx_call_bls12_381_pairing_wrong_input_layout.json index dd43f6c387b..90424e2d705 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_pairing_wrong_input_layout.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_pairing_wrong_input_layout.json @@ -8,10 +8,10 @@ } }, "state": { - "0x4200000000000000000000000000000000000019": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -32,7 +32,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x000000000000000000000000000000000000000f": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", "nonce": 0, @@ -54,7 +54,7 @@ } }, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" }, "0x420000000000000000000000000000000000001b": { "info": { @@ -80,7 +80,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001a": { + "0x000000000000000000000000000000000000000f": { "info": { "balance": "0x0", "nonce": 0, @@ -102,12 +102,12 @@ } }, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000000": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { diff --git a/tests/testdata/test_halted_tx_call_bn128_pair_fjord.json b/tests/testdata/test_halted_tx_call_bn128_pair_fjord.json index 1caf6f8ed96..99557e35e6f 100644 --- a/tests/testdata/test_halted_tx_call_bn128_pair_fjord.json +++ b/tests/testdata/test_halted_tx_call_bn128_pair_fjord.json @@ -10,7 +10,7 @@ } }, "state": { - "0x0000000000000000000000000000000000000008": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", "nonce": 0, @@ -32,7 +32,7 @@ } }, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" }, "0x0000000000000000000000000000000000000000": { "info": { @@ -58,7 +58,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001a": { + "0x0000000000000000000000000000000000000008": { "info": { "balance": "0x0", "nonce": 0, @@ -80,9 +80,9 @@ } }, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001b": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", "nonce": 0, diff --git a/tests/testdata/test_halted_tx_call_bn128_pair_granite.json b/tests/testdata/test_halted_tx_call_bn128_pair_granite.json index 3c3eb400966..ac36ef337c1 100644 --- a/tests/testdata/test_halted_tx_call_bn128_pair_granite.json +++ b/tests/testdata/test_halted_tx_call_bn128_pair_granite.json @@ -8,10 +8,10 @@ } }, "state": { - "0x0000000000000000000000000000000000000000": { + "0x0000000000000000000000000000000000000008": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -30,9 +30,9 @@ } }, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000008": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", "nonce": 0, @@ -54,9 +54,9 @@ } }, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001a": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", "nonce": 0, @@ -80,7 +80,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x4200000000000000000000000000000000000019": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", "nonce": 0, @@ -104,10 +104,10 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001b": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { diff --git a/tests/testdata/test_halted_tx_call_p256verify.json b/tests/testdata/test_halted_tx_call_p256verify.json index 4259f40720c..11144c7807d 100644 --- a/tests/testdata/test_halted_tx_call_p256verify.json +++ b/tests/testdata/test_halted_tx_call_p256verify.json @@ -10,7 +10,7 @@ } }, "state": { - "0x4200000000000000000000000000000000000019": { + "0x0000000000000000000000000000000000000100": { "info": { "balance": "0x0", "nonce": 0, @@ -32,9 +32,9 @@ } }, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001b": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", "nonce": 0, @@ -58,7 +58,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000100": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", "nonce": 0, @@ -80,7 +80,7 @@ } }, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" }, "0x0000000000000000000000000000000000000000": { "info": { @@ -106,7 +106,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001a": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", "nonce": 0, diff --git a/tests/testdata/test_log_inspector.json b/tests/testdata/test_log_inspector.json index 871989fad3f..a746c715b26 100644 --- a/tests/testdata/test_log_inspector.json +++ b/tests/testdata/test_log_inspector.json @@ -41,7 +41,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000000": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", "nonce": 0, @@ -65,7 +65,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001a": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", "nonce": 0, @@ -89,55 +89,55 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001b": { + "0xffffffffffffffffffffffffffffffffffffffff": { "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "balance": "0x989680", + "nonce": 1, + "code_hash": "0x8013c386d5a03c3fa0a6ccbfefcf7d91471dedba2bb94eefef57f916e5929a8d", "code": { "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, + "bytecode": "0x600080a000", + "original_len": 5, "jump_table": { "order": "bitvec::order::Lsb0", "head": { "width": 8, "index": 0 }, - "bits": 0, - "data": [] + "bits": 5, + "data": [ + 0 + ] } } } }, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "Touched" }, - "0xffffffffffffffffffffffffffffffffffffffff": { + "0x420000000000000000000000000000000000001a": { "info": { - "balance": "0x989680", - "nonce": 1, - "code_hash": "0x8013c386d5a03c3fa0a6ccbfefcf7d91471dedba2bb94eefef57f916e5929a8d", + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { - "bytecode": "0x600080a000", - "original_len": 5, + "bytecode": "0x00", + "original_len": 0, "jump_table": { "order": "bitvec::order::Lsb0", "head": { "width": 8, "index": 0 }, - "bits": 5, - "data": [ - 0 - ] + "bits": 0, + "data": [] } } } }, "storage": {}, - "status": "Touched" + "status": "Touched | LoadedAsNotExisting" }, "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee": { "info": { diff --git a/tests/testdata/test_tx_call_p256verify.json b/tests/testdata/test_tx_call_p256verify.json index 531842c82fe..1ada9b21873 100644 --- a/tests/testdata/test_tx_call_p256verify.json +++ b/tests/testdata/test_tx_call_p256verify.json @@ -11,10 +11,10 @@ } }, "state": { - "0x4200000000000000000000000000000000000019": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -35,10 +35,10 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000000": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -59,7 +59,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001a": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", "nonce": 0, @@ -83,7 +83,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001b": { + "0x0000000000000000000000000000000000000100": { "info": { "balance": "0x0", "nonce": 0, @@ -107,7 +107,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000100": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", "nonce": 0, From 0b68166d8061295bb9d50059f3e844c1a4ecc4f9 Mon Sep 17 00:00:00 2001 From: rakita Date: Sat, 24 May 2025 04:12:30 +0200 Subject: [PATCH 027/225] feat: transact multi tx (bluealloy/revm#2517) * chore: remove default capacity on journal reverts * wip multi tx exec support * wip * API changes * wip * fix compilation * some cleanup,introduce additional bench * cleanup and docs * fix op-revm, add transact-multi bench * bump criterion * add balance incr/decr and simplify calls to journal * add transact_multi bench * add journal entry for deduct_caller fn * cleanup * rm ResultAndState and fix tests * clippy * cheatcode example fix * rm cleanup after transact * cleaup on inner journal * cleanup on storage slot warm marking * cleanup unneded structs * fix discard bug * typo * fix benches * remove cloning of tx * rm with_capacity * clone journal * use mem::take * skip cloning * remove history entries * rm println * drain journal * bump testdata suite * use replay in bench * fix tests * cleanup and refactor, call discard_tx always --- src/api/builder.rs | 5 +- src/api/default_ctx.rs | 4 +- src/api/exec.rs | 62 ++--- src/fast_lz.rs | 21 +- src/handler.rs | 242 +++++++++--------- tests/common.rs | 24 +- tests/integration.rs | 18 +- tests/testdata/test_deposit_tx.json | 1 + tests/testdata/test_halted_deposit_tx.json | 46 +++- ...all_bls12_381_g1_add_input_wrong_size.json | 21 +- ...d_tx_call_bls12_381_g1_add_out_of_gas.json | 23 +- ...all_bls12_381_g1_msm_input_wrong_size.json | 19 +- ...d_tx_call_bls12_381_g1_msm_out_of_gas.json | 15 +- ...l_bls12_381_g1_msm_wrong_input_layout.json | 13 +- ...all_bls12_381_g2_add_input_wrong_size.json | 23 +- ...d_tx_call_bls12_381_g2_add_out_of_gas.json | 23 +- ...all_bls12_381_g2_msm_input_wrong_size.json | 23 +- ...d_tx_call_bls12_381_g2_msm_out_of_gas.json | 23 +- ...l_bls12_381_g2_msm_wrong_input_layout.json | 9 +- ...12_381_map_fp2_to_g2_input_wrong_size.json | 15 +- ...ll_bls12_381_map_fp2_to_g2_out_of_gas.json | 21 +- ...s12_381_map_fp_to_g1_input_wrong_size.json | 23 +- ...all_bls12_381_map_fp_to_g1_out_of_gas.json | 21 +- ...ll_bls12_381_pairing_input_wrong_size.json | 17 +- ..._tx_call_bls12_381_pairing_out_of_gas.json | 21 +- ..._bls12_381_pairing_wrong_input_layout.json | 19 +- .../test_halted_tx_call_bn128_pair_fjord.json | 23 +- ...est_halted_tx_call_bn128_pair_granite.json | 23 +- .../test_halted_tx_call_p256verify.json | 23 +- tests/testdata/test_log_inspector.json | 64 ++--- tests/testdata/test_tx_call_p256verify.json | 19 +- 31 files changed, 539 insertions(+), 365 deletions(-) diff --git a/src/api/builder.rs b/src/api/builder.rs index 20c980ee08a..08da6aafc37 100644 --- a/src/api/builder.rs +++ b/src/api/builder.rs @@ -1,9 +1,10 @@ use crate::{evm::OpEvm, transaction::OpTxTr, L1BlockInfo, OpSpecId}; use revm::{ - context::{Cfg, JournalOutput}, + context::Cfg, context_interface::{Block, JournalTr}, handler::instructions::EthInstructions, interpreter::interpreter::EthInterpreter, + state::EvmState, Context, Database, }; @@ -28,7 +29,7 @@ where TX: OpTxTr, CFG: Cfg, DB: Database, - JOURNAL: JournalTr, + JOURNAL: JournalTr, { type Context = Self; diff --git a/src/api/default_ctx.rs b/src/api/default_ctx.rs index 17fa5de7651..8d63f22c753 100644 --- a/src/api/default_ctx.rs +++ b/src/api/default_ctx.rs @@ -39,8 +39,8 @@ mod test { // convert to optimism context let mut evm = ctx.build_op_with_inspector(NoOpInspector {}); // execute - let _ = evm.replay(); + let _ = evm.transact_finalize(OpTransaction::default()); // inspect - let _ = evm.inspect_replay(); + let _ = evm.inspect_tx(OpTransaction::default()); } } diff --git a/src/api/exec.rs b/src/api/exec.rs index be36a40282b..47b764fa764 100644 --- a/src/api/exec.rs +++ b/src/api/exec.rs @@ -3,25 +3,26 @@ use crate::{ OpTransactionError, }; use revm::{ - context::{ContextSetters, JournalOutput}, + context::{result::ResultAndState, ContextSetters}, context_interface::{ - result::{EVMError, ExecutionResult, ResultAndState}, + result::{EVMError, ExecutionResult}, Cfg, ContextTr, Database, JournalTr, }, handler::{ - instructions::EthInstructions, system_call::SystemCallEvm, EthFrame, EvmTr, Handler, + instructions::EthInstructions, system_call::SystemCallEvm, EthFrame, Handler, PrecompileProvider, SystemCallTx, }, inspector::{InspectCommitEvm, InspectEvm, Inspector, InspectorHandler, JournalExt}, interpreter::{interpreter::EthInterpreter, InterpreterResult}, primitives::{Address, Bytes}, + state::EvmState, DatabaseCommit, ExecuteCommitEvm, ExecuteEvm, }; // Type alias for Optimism context pub trait OpContextTr: ContextTr< - Journal: JournalTr, + Journal: JournalTr, Tx: OpTxTr, Cfg: Cfg, Chain = L1BlockInfo, @@ -31,7 +32,7 @@ pub trait OpContextTr: impl OpContextTr for T where T: ContextTr< - Journal: JournalTr, + Journal: JournalTr, Tx: OpTxTr, Cfg: Cfg, Chain = L1BlockInfo, @@ -48,24 +49,35 @@ where CTX: OpContextTr + ContextSetters, PRECOMPILE: PrecompileProvider, { - type Output = Result, OpError>; - type Tx = ::Tx; - type Block = ::Block; - - fn set_tx(&mut self, tx: Self::Tx) { - self.0.ctx.set_tx(tx); - } + type State = EvmState; + type Error = OpError; + type ExecutionResult = ExecutionResult; fn set_block(&mut self, block: Self::Block) { self.0.ctx.set_block(block); } - fn replay(&mut self) -> Self::Output { + fn transact(&mut self, tx: Self::Tx) -> Result { + self.0.ctx.set_tx(tx); let mut h = OpHandler::<_, _, EthFrame<_, _, _>>::new(); h.run(self) } + + fn finalize(&mut self) -> Self::State { + self.0.ctx.journal().finalize() + } + + fn replay( + &mut self, + ) -> Result, Self::Error> { + let mut h = OpHandler::<_, _, EthFrame<_, _, _>>::new(); + h.run(self).map(|result| { + let state = self.finalize(); + ResultAndState::new(result, state) + }) + } } impl ExecuteCommitEvm @@ -74,13 +86,8 @@ where CTX: OpContextTr + ContextSetters, PRECOMPILE: PrecompileProvider, { - type CommitOutput = Result, OpError>; - - fn replay_commit(&mut self) -> Self::CommitOutput { - self.replay().map(|r| { - self.ctx().db().commit(r.state); - r.result - }) + fn commit(&mut self, state: Self::State) { + self.0.ctx.db().commit(state); } } @@ -97,7 +104,8 @@ where self.0.inspector = inspector; } - fn inspect_replay(&mut self) -> Self::Output { + fn inspect_tx(&mut self, tx: Self::Tx) -> Result { + self.0.ctx.set_tx(tx); let mut h = OpHandler::<_, _, EthFrame<_, _, _>>::new(); h.inspect_run(self) } @@ -110,12 +118,6 @@ where INSP: Inspector, PRECOMPILE: PrecompileProvider, { - fn inspect_replay_commit(&mut self) -> Self::CommitOutput { - self.inspect_replay().map(|r| { - self.ctx().db().commit(r.state); - r.result - }) - } } impl SystemCallEvm @@ -128,8 +130,10 @@ where &mut self, system_contract_address: Address, data: Bytes, - ) -> Self::Output { - self.set_tx(CTX::Tx::new_system_tx(data, system_contract_address)); + ) -> Result { + self.0 + .ctx + .set_tx(CTX::Tx::new_system_tx(data, system_contract_address)); let mut h = OpHandler::<_, _, EthFrame<_, _, _>>::new(); h.run_system_call(self) } diff --git a/src/fast_lz.rs b/src/fast_lz.rs index c3a21bb1538..3f0f460673a 100644 --- a/src/fast_lz.rs +++ b/src/fast_lz.rs @@ -156,6 +156,8 @@ mod tests { fn test_flz_native_evm_parity(#[case] input: Bytes) { // This bytecode and ABI is for a contract, which wraps the LibZip library for easier fuzz testing. // The source of this contract is here: https://github.com/danyalprout/fastlz/blob/main/src/FastLz.sol#L6-L10 + + use crate::OpTransaction; sol! { interface FastLz { function fastLz(bytes input) external view returns (uint256); @@ -168,18 +170,19 @@ mod tests { let mut evm = Context::op() .with_db(BenchmarkDB::new_bytecode(contract_bytecode.clone())) - .modify_tx_chained(|tx| { - tx.base.caller = EEADDRESS; - tx.base.kind = TxKind::Call(FFADDRESS); - tx.base.data = FastLz::fastLzCall::new((input,)).abi_encode().into(); - tx.base.gas_limit = 3_000_000; - tx.enveloped_tx = Some(Bytes::default()); - }) .build_op(); - let result_and_state = evm.replay().unwrap(); + let mut tx = OpTransaction::default(); + + tx.base.caller = EEADDRESS; + tx.base.kind = TxKind::Call(FFADDRESS); + tx.base.data = FastLz::fastLzCall::new((input,)).abi_encode().into(); + tx.base.gas_limit = 3_000_000; + tx.enveloped_tx = Some(Bytes::default()); + + let result = evm.transact(tx).unwrap(); - let output = result_and_state.result.output().unwrap(); + let output = result.output().unwrap(); let evm_val = FastLz::fastLzCall::abi_decode_returns(output).unwrap(); assert_eq!(U256::from(native_val), evm_val); diff --git a/src/handler.rs b/src/handler.rs index 155bebf4ef5..252f8658ef2 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -8,18 +8,19 @@ use crate::{ use revm::{ context::{result::InvalidTransaction, LocalContextTr}, context_interface::{ - result::{EVMError, ExecutionResult, FromStringError, ResultAndState}, + context::ContextError, + result::{EVMError, ExecutionResult, FromStringError}, Block, Cfg, ContextTr, JournalTr, Transaction, }, handler::{ - handler::EvmTrError, pre_execution::validate_account_nonce_and_code, EvmTr, Frame, - FrameResult, Handler, MainnetHandler, + handler::EvmTrError, + post_execution::{self, reimburse_caller}, + pre_execution::validate_account_nonce_and_code, + EvmTr, Frame, FrameResult, Handler, MainnetHandler, }, inspector::{Inspector, InspectorEvmTr, InspectorFrame, InspectorHandler}, interpreter::{interpreter::EthInterpreter, FrameInput, Gas}, - primitives::{hardfork::SpecId, HashMap, U256}, - state::Account, - Database, + primitives::{hardfork::SpecId, U256}, }; use std::boxed::Box; @@ -97,7 +98,12 @@ where let is_balance_check_disabled = ctx.cfg().is_balance_check_disabled(); let is_eip3607_disabled = ctx.cfg().is_eip3607_disabled(); let is_nonce_check_disabled = ctx.cfg().is_nonce_check_disabled(); - let mint = ctx.tx().mint(); + + let mint = if is_deposit { + ctx.tx().mint().unwrap_or_default() + } else { + 0 + }; let mut additional_cost = U256::ZERO; @@ -131,42 +137,43 @@ where let caller_account = journal.load_account_code(tx.caller())?.data; - // If the transaction is a deposit with a `mint` value, add the mint value - // in wei to the caller's balance. This should be persisted to the database - // prior to the rest of execution. - if is_deposit { - if let Some(mint) = mint { - caller_account.info.balance = - caller_account.info.balance.saturating_add(U256::from(mint)); - } - if tx.kind().is_call() { - caller_account.info.nonce = caller_account.info.nonce.saturating_add(1); - } - } else { + if !is_deposit { // validates account nonce and code validate_account_nonce_and_code( &mut caller_account.info, tx.nonce(), - tx.kind().is_call(), is_eip3607_disabled, is_nonce_check_disabled, )?; } + // Bump the nonce for calls. Nonce for CREATE will be bumped in `handle_create`. + if tx.kind().is_call() { + caller_account.info.nonce = caller_account.info.nonce.saturating_add(1); + } + let max_balance_spending = tx.max_balance_spending()?.saturating_add(additional_cost); + // old balance is journaled before mint is incremented. + let old_balance = caller_account.info.balance; + + // If the transaction is a deposit with a `mint` value, add the mint value + // in wei to the caller's balance. This should be persisted to the database + // prior to the rest of execution. + let mut new_balance = caller_account.info.balance.saturating_add(U256::from(mint)); + // Check if account has enough balance for `gas_limit * max_fee`` and value transfer. // Transfer will be done inside `*_inner` functions. if is_balance_check_disabled { // Make sure the caller's balance is at least the value of the transaction. // this is not consensus critical, and it is used in testing. - caller_account.info.balance = caller_account.info.balance.max(tx.value()); - } else if !is_deposit && max_balance_spending > caller_account.info.balance { + new_balance = caller_account.info.balance.max(tx.value()); + } else if !is_deposit && max_balance_spending > new_balance { // skip max balance check for deposit transactions. // this check for deposit was skipped previously in `validate_tx_against_state` function return Err(InvalidTransaction::LackOfFundForMaxFee { fee: Box::new(max_balance_spending), - balance: Box::new(caller_account.info.balance), + balance: Box::new(new_balance), } .into()); } else { @@ -185,14 +192,17 @@ where // In case of deposit additional cost will be zero. let op_gas_balance_spending = gas_balance_spending.saturating_add(additional_cost); - caller_account.info.balance = caller_account - .info - .balance - .saturating_sub(op_gas_balance_spending); + new_balance = new_balance.saturating_sub(op_gas_balance_spending); } // Touch account so we know it is changed. caller_account.mark_touch(); + caller_account.info.balance = new_balance; + + // NOTE: all changes to the caller account should journaled so in case of error + // we can revert the changes. + journal.caller_accounting_journal_entry(tx.caller(), old_balance, tx.kind().is_call()); + Ok(()) } @@ -267,26 +277,17 @@ where evm: &mut Self::Evm, exec_result: &mut ::FrameResult, ) -> Result<(), Self::Error> { - self.mainnet.reimburse_caller(evm, exec_result)?; - - let context = evm.ctx(); - if context.tx().tx_type() != DEPOSIT_TRANSACTION_TYPE { - let caller = context.tx().caller(); - let spec = context.cfg().spec(); - let operator_fee_refund = context.chain().operator_fee_refund(exec_result.gas(), spec); - - let caller_account = context.journal().load_account(caller)?; - - // In additional to the normal transaction fee, additionally refund the caller - // for the operator fee. - caller_account.data.info.balance = caller_account - .data - .info - .balance - .saturating_add(operator_fee_refund); + let mut additional_refund = U256::ZERO; + + if evm.ctx().tx().tx_type() != DEPOSIT_TRANSACTION_TYPE { + let spec = evm.ctx().cfg().spec(); + additional_refund = evm + .ctx() + .chain() + .operator_fee_refund(exec_result.gas(), spec); } - Ok(()) + reimburse_caller(evm.ctx(), exec_result.gas_mut(), additional_refund).map_err(From::from) } fn refund( @@ -321,61 +322,67 @@ where let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE; // Transfer fee to coinbase/beneficiary. - if !is_deposit { - self.mainnet.reward_beneficiary(evm, exec_result)?; - let basefee = evm.ctx().block().basefee() as u128; + if is_deposit { + return Ok(()); + } - // If the transaction is not a deposit transaction, fees are paid out - // to both the Base Fee Vault as well as the L1 Fee Vault. - let ctx = evm.ctx(); - let enveloped = ctx.tx().enveloped_tx().cloned(); - let spec = ctx.cfg().spec(); - let l1_block_info = ctx.chain(); + self.mainnet.reward_beneficiary(evm, exec_result)?; + let basefee = evm.ctx().block().basefee() as u128; - let Some(enveloped_tx) = &enveloped else { - return Err(ERROR::from_string( - "[OPTIMISM] Failed to load enveloped transaction.".into(), - )); - }; + // If the transaction is not a deposit transaction, fees are paid out + // to both the Base Fee Vault as well as the L1 Fee Vault. + let ctx = evm.ctx(); + let enveloped = ctx.tx().enveloped_tx().cloned(); + let spec = ctx.cfg().spec(); + let l1_block_info = ctx.chain(); - let l1_cost = l1_block_info.calculate_tx_l1_cost(enveloped_tx, spec); - let mut operator_fee_cost = U256::ZERO; - if spec.is_enabled_in(OpSpecId::ISTHMUS) { - operator_fee_cost = l1_block_info.operator_fee_charge( - enveloped_tx, - U256::from(exec_result.gas().spent() - exec_result.gas().refunded() as u64), - ); - } - // Send the L1 cost of the transaction to the L1 Fee Vault. - let mut l1_fee_vault_account = ctx.journal().load_account(L1_FEE_RECIPIENT)?; - l1_fee_vault_account.mark_touch(); - l1_fee_vault_account.info.balance += l1_cost; - - // Send the base fee of the transaction to the Base Fee Vault. - let mut base_fee_vault_account = - evm.ctx().journal().load_account(BASE_FEE_RECIPIENT)?; - base_fee_vault_account.mark_touch(); - base_fee_vault_account.info.balance += U256::from(basefee.saturating_mul( - (exec_result.gas().spent() - exec_result.gas().refunded() as u64) as u128, + let Some(enveloped_tx) = &enveloped else { + return Err(ERROR::from_string( + "[OPTIMISM] Failed to load enveloped transaction.".into(), )); + }; - // Send the operator fee of the transaction to the coinbase. - let mut operator_fee_vault_account = - evm.ctx().journal().load_account(OPERATOR_FEE_RECIPIENT)?; - operator_fee_vault_account.mark_touch(); - operator_fee_vault_account.data.info.balance += operator_fee_cost; + let l1_cost = l1_block_info.calculate_tx_l1_cost(enveloped_tx, spec); + let mut operator_fee_cost = U256::ZERO; + if spec.is_enabled_in(OpSpecId::ISTHMUS) { + operator_fee_cost = l1_block_info.operator_fee_charge( + enveloped_tx, + U256::from(exec_result.gas().spent() - exec_result.gas().refunded() as u64), + ); } + // Send the L1 cost of the transaction to the L1 Fee Vault. + ctx.journal().balance_incr(L1_FEE_RECIPIENT, l1_cost)?; + + // Send the base fee of the transaction to the Base Fee Vault. + ctx.journal().balance_incr( + BASE_FEE_RECIPIENT, + U256::from(basefee.saturating_mul( + (exec_result.gas().spent() - exec_result.gas().refunded() as u64) as u128, + )), + )?; + + // Send the operator fee of the transaction to the coinbase. + ctx.journal() + .balance_incr(OPERATOR_FEE_RECIPIENT, operator_fee_cost)?; + Ok(()) } - fn output( - &self, + fn execution_result( + &mut self, evm: &mut Self::Evm, result: ::FrameResult, - ) -> Result, Self::Error> { - let result = self.mainnet.output(evm, result)?; - let result = result.map_haltreason(OpHaltReason::Base); - if result.result.is_halt() { + ) -> Result, Self::Error> { + match core::mem::replace(evm.ctx().error(), Ok(())) { + Err(ContextError::Db(e)) => return Err(e.into()), + Err(ContextError::Custom(e)) => return Err(Self::Error::from_string(e)), + Ok(_) => (), + } + + let exec_result = + post_execution::output(evm.ctx(), result).map_haltreason(OpHaltReason::Base); + + if exec_result.is_halt() { // Post-regolith, if the transaction is a deposit transaction and it halts, // we bubble up to the global return handler. The mint value will be persisted // and the caller nonce will be incremented there. @@ -384,15 +391,18 @@ where return Err(ERROR::from(OpTransactionError::HaltedDepositPostRegolith)); } } + evm.ctx().journal().commit_tx(); evm.ctx().chain().clear_tx_l1_cost(); - Ok(result) + evm.ctx().local().clear(); + + Ok(exec_result) } fn catch_error( &self, evm: &mut Self::Evm, error: Self::Error, - ) -> Result, Self::Error> { + ) -> Result, Self::Error> { let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE; let output = if error.is_tx_error() && is_deposit { let ctx = evm.ctx(); @@ -402,6 +412,10 @@ where let mint = tx.mint(); let is_system_tx = tx.is_system_transaction(); let gas_limit = tx.gas_limit(); + + // discard all changes of this transaction + evm.ctx().journal().discard_tx(); + // If the transaction is a deposit transaction and it failed // for any reason, the caller nonce must be bumped, and the // gas reported must be altered depending on the Hardfork. This is @@ -411,23 +425,21 @@ where // Increment sender nonce and account balance for the mint amount. Deposits // always persist the mint amount, even if the transaction fails. - let account = { - let mut acc = Account::from( - evm.ctx() - .db() - .basic(caller) - .unwrap_or_default() - .unwrap_or_default(), - ); - acc.info.nonce = acc.info.nonce.saturating_add(1); - acc.info.balance = acc - .info - .balance - .saturating_add(U256::from(mint.unwrap_or_default())); - acc.mark_touch(); - acc - }; - let state = HashMap::from_iter([(caller, account)]); + let acc: &mut revm::state::Account = evm.ctx().journal().load_account(caller)?.data; + + let old_balance = acc.info.balance; + + acc.info.nonce = acc.info.nonce.saturating_add(1); + acc.info.balance = acc + .info + .balance + .saturating_add(U256::from(mint.unwrap_or_default())); + acc.mark_touch(); + + // add journal entry for accounts + evm.ctx() + .journal() + .caller_accounting_journal_entry(caller, old_balance, true); // The gas used of a failed deposit post-regolith is the gas // limit of the transaction. pre-regolith, it is the gas limit @@ -439,19 +451,15 @@ where 0 }; // clear the journal - Ok(ResultAndState { - result: ExecutionResult::Halt { - reason: OpHaltReason::FailedDeposit, - gas_used, - }, - state, + Ok(ExecutionResult::Halt { + reason: OpHaltReason::FailedDeposit, + gas_used, }) } else { Err(error) }; // do the cleanup evm.ctx().chain().clear_tx_l1_cost(); - evm.ctx().journal().clear(); evm.ctx().local().clear(); output @@ -958,10 +966,10 @@ mod tests { .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); let mut evm = ctx.build_op(); - let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new(); + let mut handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new(); assert_eq!( - handler.output( + handler.execution_result( &mut evm, FrameResult::Call(CallOutcome { result: InterpreterResult { diff --git a/tests/common.rs b/tests/common.rs index 1baff3c6e91..517d0925d89 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -1,9 +1,8 @@ #![allow(dead_code)] use revm::{ - context_interface::result::{ - ExecutionResult, HaltReason, Output, ResultAndState, SuccessReason, - }, + context::result::ResultAndState, + context_interface::result::{ExecutionResult, HaltReason, Output, SuccessReason}, primitives::Bytes, state::EvmState, }; @@ -14,7 +13,7 @@ pub(crate) const TESTS_TESTDATA: &str = "tests/testdata"; #[cfg(not(feature = "serde"))] pub(crate) fn compare_or_save_testdata( _filename: &str, - _output: &ResultAndState, + _output: &ResultAndState, EvmState>, ) { // serde needs to be enabled to use this function } @@ -43,11 +42,12 @@ pub(crate) fn compare_or_save_testdata( #[cfg(feature = "serde")] pub(crate) fn compare_or_save_testdata( filename: &str, - output: &ResultAndState, + output: &ResultAndState, EvmState>, ) where - HaltReasonTy: serde::Serialize + serde::de::DeserializeOwned + PartialEq, + HaltReasonTy: serde::Serialize + for<'a> serde::Deserialize<'a> + PartialEq, { use std::{fs, path::PathBuf}; + let tests_dir = PathBuf::from(TESTS_TESTDATA); let testdata_file = tests_dir.join(filename); @@ -70,7 +70,7 @@ pub(crate) fn compare_or_save_testdata( let expected_json = fs::read_to_string(&testdata_file).unwrap(); // Deserialize to actual ResultAndState object for proper comparison - let expected: ResultAndState = serde_json::from_str(&expected_json).unwrap(); + let expected = serde_json::from_str(&expected_json).unwrap(); // Compare the output objects directly if output != &expected { @@ -94,18 +94,18 @@ pub(crate) fn compare_or_save_testdata( #[test] fn template_test() { // Create a minimal result and state - let result: ResultAndState = ResultAndState { - result: ExecutionResult::Success { + let result = ResultAndState::new( + ExecutionResult::Success { reason: SuccessReason::Stop, gas_used: 1000, gas_refunded: 0, logs: vec![], output: Output::Call(Bytes::from(vec![4, 5, 6])), }, - state: EvmState::default(), - }; + EvmState::default(), + ); // Simply use the testdata comparison utility // No assertions needed - full validation is done by comparing with testdata - compare_or_save_testdata("template_test.json", &result); + compare_or_save_testdata::("template_test.json", &result); } diff --git a/tests/integration.rs b/tests/integration.rs index 5589b35c49e..a9c369c2e57 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -756,17 +756,21 @@ fn test_log_inspector() { ]); let bytecode = Bytecode::new_raw(contract_data); - let ctx = Context::op() - .with_db(BenchmarkDB::new_bytecode(bytecode.clone())) - .modify_tx_chained(|tx| { - tx.base.caller = BENCH_CALLER; - tx.base.kind = TxKind::Call(BENCH_TARGET); - }); + let ctx = Context::op().with_db(BenchmarkDB::new_bytecode(bytecode.clone())); let mut evm = ctx.build_op_with_inspector(LogInspector::default()); + let tx = OpTransaction { + base: TxEnv { + caller: BENCH_CALLER, + kind: TxKind::Call(BENCH_TARGET), + ..Default::default() + }, + ..Default::default() + }; + // Run evm. - let output = evm.inspect_replay().unwrap(); + let output = evm.inspect_tx_finalize(tx).unwrap(); let inspector = &evm.0.inspector; assert!(!inspector.logs.is_empty()); diff --git a/tests/testdata/test_deposit_tx.json b/tests/testdata/test_deposit_tx.json index 669672de561..c187d50833e 100644 --- a/tests/testdata/test_deposit_tx.json +++ b/tests/testdata/test_deposit_tx.json @@ -32,6 +32,7 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" } diff --git a/tests/testdata/test_halted_deposit_tx.json b/tests/testdata/test_halted_deposit_tx.json index 85be26dfc21..cca92e6161c 100644 --- a/tests/testdata/test_halted_deposit_tx.json +++ b/tests/testdata/test_halted_deposit_tx.json @@ -8,13 +8,55 @@ "state": { "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee": { "info": { - "balance": "0x9896e4", + "balance": "0x2386f26fc10064", "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": null + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } }, + "transaction_id": 0, "storage": {}, "status": "Touched" + }, + "0xffffffffffffffffffffffffffffffffffffffff": { + "info": { + "balance": "0x2386f26fc10000", + "nonce": 1, + "code_hash": "0x7b2ab94bb7d45041581aa3757ae020084674ccad6f75dc3750eb2ea8a92c4e9a", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x5000", + "original_len": 1, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 1, + "data": [ + 0 + ] + } + } + } + }, + "transaction_id": 0, + "storage": {}, + "status": "Cold" } } } \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g1_add_input_wrong_size.json b/tests/testdata/test_halted_tx_call_bls12_381_g1_add_input_wrong_size.json index 6a5a9e51632..5b5fc6861ba 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_g1_add_input_wrong_size.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_g1_add_input_wrong_size.json @@ -8,10 +8,10 @@ } }, "state": { - "0x0000000000000000000000000000000000000000": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -29,6 +29,7 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, @@ -53,10 +54,11 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001a": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", "nonce": 0, @@ -77,10 +79,11 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x4200000000000000000000000000000000000019": { + "0x000000000000000000000000000000000000000b": { "info": { "balance": "0x0", "nonce": 0, @@ -101,13 +104,14 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" }, - "0x000000000000000000000000000000000000000b": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -125,8 +129,9 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" } } } \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g1_add_out_of_gas.json b/tests/testdata/test_halted_tx_call_bls12_381_g1_add_out_of_gas.json index 190c24787e4..5690b09303b 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_g1_add_out_of_gas.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_g1_add_out_of_gas.json @@ -10,10 +10,10 @@ } }, "state": { - "0x420000000000000000000000000000000000001a": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -31,13 +31,14 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000000": { + "0x000000000000000000000000000000000000000b": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -55,10 +56,11 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" }, - "0x000000000000000000000000000000000000000b": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", "nonce": 0, @@ -79,10 +81,11 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" }, - "0x4200000000000000000000000000000000000019": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", "nonce": 0, @@ -103,10 +106,11 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001b": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", "nonce": 0, @@ -127,6 +131,7 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" } diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_input_wrong_size.json b/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_input_wrong_size.json index 00a43576026..109343782bd 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_input_wrong_size.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_input_wrong_size.json @@ -8,10 +8,10 @@ } }, "state": { - "0x0000000000000000000000000000000000000000": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -29,10 +29,11 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001a": { + "0x000000000000000000000000000000000000000c": { "info": { "balance": "0x0", "nonce": 0, @@ -53,8 +54,9 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" }, "0x4200000000000000000000000000000000000019": { "info": { @@ -77,6 +79,7 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, @@ -101,13 +104,14 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x000000000000000000000000000000000000000c": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -125,8 +129,9 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" } } } \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_out_of_gas.json b/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_out_of_gas.json index 3542712059c..38d133784c9 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_out_of_gas.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_out_of_gas.json @@ -10,7 +10,7 @@ } }, "state": { - "0x4200000000000000000000000000000000000019": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", "nonce": 0, @@ -31,6 +31,7 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, @@ -55,13 +56,14 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000000": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -79,6 +81,7 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, @@ -103,13 +106,14 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001a": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -127,6 +131,7 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" } diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_wrong_input_layout.json b/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_wrong_input_layout.json index 21ebc4e4127..83fd20ba23c 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_wrong_input_layout.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_wrong_input_layout.json @@ -29,6 +29,7 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, @@ -53,13 +54,14 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001a": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -77,6 +79,7 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, @@ -101,13 +104,14 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000000": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -125,6 +129,7 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" } diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g2_add_input_wrong_size.json b/tests/testdata/test_halted_tx_call_bls12_381_g2_add_input_wrong_size.json index 8eefe5faae7..9b58339aba7 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_g2_add_input_wrong_size.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_g2_add_input_wrong_size.json @@ -8,7 +8,7 @@ } }, "state": { - "0x000000000000000000000000000000000000000d": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", "nonce": 0, @@ -29,10 +29,11 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" }, - "0x4200000000000000000000000000000000000019": { + "0x000000000000000000000000000000000000000d": { "info": { "balance": "0x0", "nonce": 0, @@ -53,13 +54,14 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000000": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -77,13 +79,14 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001a": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -101,10 +104,11 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001b": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", "nonce": 0, @@ -125,6 +129,7 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" } diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g2_add_out_of_gas.json b/tests/testdata/test_halted_tx_call_bls12_381_g2_add_out_of_gas.json index 1ef0993029a..31d7f31de0b 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_g2_add_out_of_gas.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_g2_add_out_of_gas.json @@ -10,10 +10,10 @@ } }, "state": { - "0x0000000000000000000000000000000000000000": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -31,10 +31,11 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x000000000000000000000000000000000000000d": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", "nonce": 0, @@ -55,10 +56,11 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" }, - "0x4200000000000000000000000000000000000019": { + "0x000000000000000000000000000000000000000d": { "info": { "balance": "0x0", "nonce": 0, @@ -79,10 +81,11 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001b": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", "nonce": 0, @@ -103,13 +106,14 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001a": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -127,6 +131,7 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" } diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_input_wrong_size.json b/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_input_wrong_size.json index 0dbd565bd36..d22cb091810 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_input_wrong_size.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_input_wrong_size.json @@ -8,10 +8,10 @@ } }, "state": { - "0x420000000000000000000000000000000000001b": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -29,10 +29,11 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x000000000000000000000000000000000000000e": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", "nonce": 0, @@ -53,10 +54,11 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001a": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", "nonce": 0, @@ -77,13 +79,14 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000000": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -101,10 +104,11 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x4200000000000000000000000000000000000019": { + "0x000000000000000000000000000000000000000e": { "info": { "balance": "0x0", "nonce": 0, @@ -125,8 +129,9 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" } } } \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_out_of_gas.json b/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_out_of_gas.json index 76216d0132a..60ab39f58cd 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_out_of_gas.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_out_of_gas.json @@ -10,7 +10,7 @@ } }, "state": { - "0x420000000000000000000000000000000000001b": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", "nonce": 0, @@ -31,10 +31,11 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x4200000000000000000000000000000000000019": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", "nonce": 0, @@ -55,10 +56,11 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x000000000000000000000000000000000000000e": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", "nonce": 0, @@ -79,13 +81,14 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000000": { + "0x000000000000000000000000000000000000000e": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -103,13 +106,14 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001a": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -127,6 +131,7 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" } diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_wrong_input_layout.json b/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_wrong_input_layout.json index e467228e9ad..48c1006554f 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_wrong_input_layout.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_wrong_input_layout.json @@ -29,10 +29,11 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "LoadedAsNotExisting" }, - "0x4200000000000000000000000000000000000019": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", "nonce": 0, @@ -53,6 +54,7 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, @@ -77,10 +79,11 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001a": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", "nonce": 0, @@ -101,6 +104,7 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, @@ -125,6 +129,7 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" } diff --git a/tests/testdata/test_halted_tx_call_bls12_381_map_fp2_to_g2_input_wrong_size.json b/tests/testdata/test_halted_tx_call_bls12_381_map_fp2_to_g2_input_wrong_size.json index c8d19194e7a..3e00f925e8d 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_map_fp2_to_g2_input_wrong_size.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_map_fp2_to_g2_input_wrong_size.json @@ -8,10 +8,10 @@ } }, "state": { - "0x420000000000000000000000000000000000001a": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -29,6 +29,7 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, @@ -53,13 +54,14 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000000": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -77,6 +79,7 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, @@ -101,10 +104,11 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "LoadedAsNotExisting" }, - "0x4200000000000000000000000000000000000019": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", "nonce": 0, @@ -125,6 +129,7 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" } diff --git a/tests/testdata/test_halted_tx_call_bls12_381_map_fp2_to_g2_out_of_gas.json b/tests/testdata/test_halted_tx_call_bls12_381_map_fp2_to_g2_out_of_gas.json index 0794b5db9b1..9152806db61 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_map_fp2_to_g2_out_of_gas.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_map_fp2_to_g2_out_of_gas.json @@ -10,10 +10,10 @@ } }, "state": { - "0x420000000000000000000000000000000000001a": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -31,13 +31,14 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000000": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -55,10 +56,11 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x4200000000000000000000000000000000000019": { + "0x0000000000000000000000000000000000000011": { "info": { "balance": "0x0", "nonce": 0, @@ -79,10 +81,11 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000011": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", "nonce": 0, @@ -103,8 +106,9 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" }, "0x420000000000000000000000000000000000001b": { "info": { @@ -127,6 +131,7 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" } diff --git a/tests/testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_input_wrong_size.json b/tests/testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_input_wrong_size.json index b3918b0f65b..db93c13ab68 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_input_wrong_size.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_input_wrong_size.json @@ -8,7 +8,7 @@ } }, "state": { - "0x0000000000000000000000000000000000000010": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", "nonce": 0, @@ -29,13 +29,14 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001a": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -53,10 +54,11 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001b": { + "0x0000000000000000000000000000000000000010": { "info": { "balance": "0x0", "nonce": 0, @@ -77,10 +79,11 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" }, - "0x4200000000000000000000000000000000000019": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", "nonce": 0, @@ -101,13 +104,14 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000000": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -125,6 +129,7 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" } diff --git a/tests/testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_out_of_gas.json b/tests/testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_out_of_gas.json index 7bfd1f92224..11a64278d31 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_out_of_gas.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_out_of_gas.json @@ -10,10 +10,10 @@ } }, "state": { - "0x420000000000000000000000000000000000001a": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -31,10 +31,11 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000010": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", "nonce": 0, @@ -55,8 +56,9 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" }, "0x420000000000000000000000000000000000001b": { "info": { @@ -79,10 +81,11 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x4200000000000000000000000000000000000019": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", "nonce": 0, @@ -103,13 +106,14 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000000": { + "0x0000000000000000000000000000000000000010": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -127,8 +131,9 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" } } } \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_pairing_input_wrong_size.json b/tests/testdata/test_halted_tx_call_bls12_381_pairing_input_wrong_size.json index 2195898c009..001577b3a34 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_pairing_input_wrong_size.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_pairing_input_wrong_size.json @@ -29,10 +29,11 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001b": { + "0x000000000000000000000000000000000000000f": { "info": { "balance": "0x0", "nonce": 0, @@ -53,10 +54,11 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" }, - "0x4200000000000000000000000000000000000019": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", "nonce": 0, @@ -77,10 +79,11 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x000000000000000000000000000000000000000f": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", "nonce": 0, @@ -101,10 +104,11 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001a": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", "nonce": 0, @@ -125,6 +129,7 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" } diff --git a/tests/testdata/test_halted_tx_call_bls12_381_pairing_out_of_gas.json b/tests/testdata/test_halted_tx_call_bls12_381_pairing_out_of_gas.json index 8a0ac761aa5..628f248bd2b 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_pairing_out_of_gas.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_pairing_out_of_gas.json @@ -10,7 +10,7 @@ } }, "state": { - "0x000000000000000000000000000000000000000f": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", "nonce": 0, @@ -31,10 +31,11 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001b": { + "0x000000000000000000000000000000000000000f": { "info": { "balance": "0x0", "nonce": 0, @@ -55,13 +56,14 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000000": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -79,13 +81,14 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x4200000000000000000000000000000000000019": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -103,6 +106,7 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, @@ -127,6 +131,7 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" } diff --git a/tests/testdata/test_halted_tx_call_bls12_381_pairing_wrong_input_layout.json b/tests/testdata/test_halted_tx_call_bls12_381_pairing_wrong_input_layout.json index 90424e2d705..a7245cc2456 100644 --- a/tests/testdata/test_halted_tx_call_bls12_381_pairing_wrong_input_layout.json +++ b/tests/testdata/test_halted_tx_call_bls12_381_pairing_wrong_input_layout.json @@ -8,10 +8,10 @@ } }, "state": { - "0x0000000000000000000000000000000000000000": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -29,6 +29,7 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, @@ -53,6 +54,7 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, @@ -77,13 +79,14 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x000000000000000000000000000000000000000f": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -101,10 +104,11 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" }, - "0x4200000000000000000000000000000000000019": { + "0x000000000000000000000000000000000000000f": { "info": { "balance": "0x0", "nonce": 0, @@ -125,8 +129,9 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" } } } \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bn128_pair_fjord.json b/tests/testdata/test_halted_tx_call_bn128_pair_fjord.json index 99557e35e6f..eae5c631fa0 100644 --- a/tests/testdata/test_halted_tx_call_bn128_pair_fjord.json +++ b/tests/testdata/test_halted_tx_call_bn128_pair_fjord.json @@ -10,7 +10,7 @@ } }, "state": { - "0x420000000000000000000000000000000000001b": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", "nonce": 0, @@ -31,13 +31,14 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000000": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -55,13 +56,14 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000008": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -79,10 +81,11 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001a": { + "0x0000000000000000000000000000000000000008": { "info": { "balance": "0x0", "nonce": 0, @@ -103,10 +106,11 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" }, - "0x4200000000000000000000000000000000000019": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", "nonce": 0, @@ -127,6 +131,7 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" } diff --git a/tests/testdata/test_halted_tx_call_bn128_pair_granite.json b/tests/testdata/test_halted_tx_call_bn128_pair_granite.json index ac36ef337c1..ce92b110149 100644 --- a/tests/testdata/test_halted_tx_call_bn128_pair_granite.json +++ b/tests/testdata/test_halted_tx_call_bn128_pair_granite.json @@ -8,7 +8,7 @@ } }, "state": { - "0x0000000000000000000000000000000000000008": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", "nonce": 0, @@ -29,10 +29,11 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001a": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", "nonce": 0, @@ -53,10 +54,11 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x4200000000000000000000000000000000000019": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", "nonce": 0, @@ -77,13 +79,14 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001b": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -101,13 +104,14 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000000": { + "0x0000000000000000000000000000000000000008": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -125,8 +129,9 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" } } } \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_p256verify.json b/tests/testdata/test_halted_tx_call_p256verify.json index 11144c7807d..98a205f4831 100644 --- a/tests/testdata/test_halted_tx_call_p256verify.json +++ b/tests/testdata/test_halted_tx_call_p256verify.json @@ -10,10 +10,10 @@ } }, "state": { - "0x0000000000000000000000000000000000000100": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -31,10 +31,11 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001a": { + "0x0000000000000000000000000000000000000100": { "info": { "balance": "0x0", "nonce": 0, @@ -55,10 +56,11 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" }, - "0x4200000000000000000000000000000000000019": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", "nonce": 0, @@ -79,13 +81,14 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000000": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -103,10 +106,11 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001b": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", "nonce": 0, @@ -127,6 +131,7 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" } diff --git a/tests/testdata/test_log_inspector.json b/tests/testdata/test_log_inspector.json index a746c715b26..04fbaf3a18b 100644 --- a/tests/testdata/test_log_inspector.json +++ b/tests/testdata/test_log_inspector.json @@ -17,34 +17,37 @@ } }, "state": { - "0x4200000000000000000000000000000000000019": { + "0xffffffffffffffffffffffffffffffffffffffff": { "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "balance": "0x2386f26fc10000", + "nonce": 1, + "code_hash": "0x8013c386d5a03c3fa0a6ccbfefcf7d91471dedba2bb94eefef57f916e5929a8d", "code": { "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, + "bytecode": "0x600080a000", + "original_len": 5, "jump_table": { "order": "bitvec::order::Lsb0", "head": { "width": 8, "index": 0 }, - "bits": 0, - "data": [] + "bits": 5, + "data": [ + 0 + ] } } } }, + "transaction_id": 0, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "Touched" }, - "0x420000000000000000000000000000000000001b": { + "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee": { "info": { - "balance": "0x0", - "nonce": 0, + "balance": "0x2386f26fc10000", + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -62,8 +65,9 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "Touched" }, "0x0000000000000000000000000000000000000000": { "info": { @@ -86,36 +90,36 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0xffffffffffffffffffffffffffffffffffffffff": { + "0x420000000000000000000000000000000000001a": { "info": { - "balance": "0x989680", - "nonce": 1, - "code_hash": "0x8013c386d5a03c3fa0a6ccbfefcf7d91471dedba2bb94eefef57f916e5929a8d", + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { - "bytecode": "0x600080a000", - "original_len": 5, + "bytecode": "0x00", + "original_len": 0, "jump_table": { "order": "bitvec::order::Lsb0", "head": { "width": 8, "index": 0 }, - "bits": 5, - "data": [ - 0 - ] + "bits": 0, + "data": [] } } } }, + "transaction_id": 0, "storage": {}, - "status": "Touched" + "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001a": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", "nonce": 0, @@ -136,13 +140,14 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee": { + "0x4200000000000000000000000000000000000019": { "info": { - "balance": "0x989680", - "nonce": 1, + "balance": "0x0", + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -160,8 +165,9 @@ } } }, + "transaction_id": 0, "storage": {}, - "status": "Touched" + "status": "Touched | LoadedAsNotExisting" } } } \ No newline at end of file diff --git a/tests/testdata/test_tx_call_p256verify.json b/tests/testdata/test_tx_call_p256verify.json index 1ada9b21873..edca703816e 100644 --- a/tests/testdata/test_tx_call_p256verify.json +++ b/tests/testdata/test_tx_call_p256verify.json @@ -11,10 +11,10 @@ } }, "state": { - "0x0000000000000000000000000000000000000000": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -32,10 +32,11 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001a": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", "nonce": 0, @@ -56,10 +57,11 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x4200000000000000000000000000000000000019": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", "nonce": 0, @@ -80,13 +82,14 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000100": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -104,10 +107,11 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001b": { + "0x0000000000000000000000000000000000000100": { "info": { "balance": "0x0", "nonce": 0, @@ -128,6 +132,7 @@ } } }, + "transaction_id": 0, "storage": {}, "status": "Touched | LoadedAsNotExisting" } From d7a5a30af88192cd35c4946171e26ca5e0a45fac Mon Sep 17 00:00:00 2001 From: rakita Date: Tue, 27 May 2025 13:21:29 +0200 Subject: [PATCH 028/225] feat: expand timestamp/block_number to u256 (bluealloy/revm#2546) --- src/handler.rs | 4 ++-- src/l1block.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index 252f8658ef2..ee98cbd820d 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -704,7 +704,7 @@ mod tests { #[test] fn test_reload_l1_block_info_isthmus() { - const BLOCK_NUM: u64 = 100; + const BLOCK_NUM: U256 = uint!(100_U256); const L1_BASE_FEE: U256 = uint!(1_U256); const L1_BLOB_BASE_FEE: U256 = uint!(2_U256); const L1_BASE_FEE_SCALAR: u64 = 3; @@ -745,7 +745,7 @@ mod tests { let ctx = Context::op() .with_db(db) .with_chain(L1BlockInfo { - l2_block: BLOCK_NUM + 1, // ahead by one block + l2_block: BLOCK_NUM + U256::from(1), // ahead by one block ..Default::default() }) .with_block(BlockEnv { diff --git a/src/l1block.rs b/src/l1block.rs index fdb1983d02c..6a03da28352 100644 --- a/src/l1block.rs +++ b/src/l1block.rs @@ -29,7 +29,7 @@ use revm::{ pub struct L1BlockInfo { /// The L2 block number. If not same as the one in the context, /// L1BlockInfo is not valid and will be reloaded from the database. - pub l2_block: u64, + pub l2_block: U256, /// The base fee of the L1 origin block. pub l1_base_fee: U256, /// The current L1 fee overhead. None if Ecotone is activated. @@ -54,7 +54,7 @@ impl L1BlockInfo { /// Try to fetch the L1 block info from the database. pub fn try_fetch( db: &mut DB, - l2_block: u64, + l2_block: U256, spec_id: OpSpecId, ) -> Result { // Ensure the L1 Block account is loaded into the cache after Ecotone. With EIP-4788, it is no longer the case From 89640cf2fe0a51771c5704ce8354eb56ac7e9db9 Mon Sep 17 00:00:00 2001 From: rakita Date: Fri, 30 May 2025 18:46:46 +0200 Subject: [PATCH 029/225] chore: ContextTr rm *_ref, and add *_mut fn (bluealloy/revm#2560) --- src/api/exec.rs | 4 ++-- src/handler.rs | 40 ++++++++++++++++++++-------------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/api/exec.rs b/src/api/exec.rs index 47b764fa764..fcffd2e0cd8 100644 --- a/src/api/exec.rs +++ b/src/api/exec.rs @@ -66,7 +66,7 @@ where } fn finalize(&mut self) -> Self::State { - self.0.ctx.journal().finalize() + self.0.ctx.journal_mut().finalize() } fn replay( @@ -87,7 +87,7 @@ where PRECOMPILE: PrecompileProvider, { fn commit(&mut self, state: Self::State) { - self.0.ctx.db().commit(state); + self.0.ctx.db_mut().commit(state); } } diff --git a/src/handler.rs b/src/handler.rs index ee98cbd820d..1a3ce0c1f4b 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -112,7 +112,7 @@ where // L1 block info is stored in the context for later use. // and it will be reloaded from the database if it is not for the current block. if ctx.chain().l2_block != block_number { - *ctx.chain() = L1BlockInfo::try_fetch(ctx.db(), block_number, spec)?; + *ctx.chain_mut() = L1BlockInfo::try_fetch(ctx.db_mut(), block_number, spec)?; } // account for additional cost of l1 fee and operator fee @@ -123,7 +123,7 @@ where .clone(); // compute L1 cost - additional_cost = ctx.chain().calculate_tx_l1_cost(&enveloped_tx, spec); + additional_cost = ctx.chain_mut().calculate_tx_l1_cost(&enveloped_tx, spec); // compute operator fee if spec.is_enabled_in(OpSpecId::ISTHMUS) { @@ -133,7 +133,7 @@ where } } - let (tx, journal) = ctx.tx_journal(); + let (tx, journal) = ctx.tx_journal_mut(); let caller_account = journal.load_account_code(tx.caller())?.data; @@ -334,7 +334,7 @@ where let ctx = evm.ctx(); let enveloped = ctx.tx().enveloped_tx().cloned(); let spec = ctx.cfg().spec(); - let l1_block_info = ctx.chain(); + let l1_block_info = ctx.chain_mut(); let Some(enveloped_tx) = &enveloped else { return Err(ERROR::from_string( @@ -351,10 +351,10 @@ where ); } // Send the L1 cost of the transaction to the L1 Fee Vault. - ctx.journal().balance_incr(L1_FEE_RECIPIENT, l1_cost)?; + ctx.journal_mut().balance_incr(L1_FEE_RECIPIENT, l1_cost)?; // Send the base fee of the transaction to the Base Fee Vault. - ctx.journal().balance_incr( + ctx.journal_mut().balance_incr( BASE_FEE_RECIPIENT, U256::from(basefee.saturating_mul( (exec_result.gas().spent() - exec_result.gas().refunded() as u64) as u128, @@ -362,7 +362,7 @@ where )?; // Send the operator fee of the transaction to the coinbase. - ctx.journal() + ctx.journal_mut() .balance_incr(OPERATOR_FEE_RECIPIENT, operator_fee_cost)?; Ok(()) @@ -391,9 +391,9 @@ where return Err(ERROR::from(OpTransactionError::HaltedDepositPostRegolith)); } } - evm.ctx().journal().commit_tx(); - evm.ctx().chain().clear_tx_l1_cost(); - evm.ctx().local().clear(); + evm.ctx().journal_mut().commit_tx(); + evm.ctx().chain_mut().clear_tx_l1_cost(); + evm.ctx().local_mut().clear(); Ok(exec_result) } @@ -414,7 +414,7 @@ where let gas_limit = tx.gas_limit(); // discard all changes of this transaction - evm.ctx().journal().discard_tx(); + evm.ctx().journal_mut().discard_tx(); // If the transaction is a deposit transaction and it failed // for any reason, the caller nonce must be bumped, and the @@ -425,7 +425,7 @@ where // Increment sender nonce and account balance for the mint amount. Deposits // always persist the mint amount, even if the transaction fails. - let acc: &mut revm::state::Account = evm.ctx().journal().load_account(caller)?.data; + let acc: &mut revm::state::Account = evm.ctx().journal_mut().load_account(caller)?.data; let old_balance = acc.info.balance; @@ -438,7 +438,7 @@ where // add journal entry for accounts evm.ctx() - .journal() + .journal_mut() .caller_accounting_journal_entry(caller, old_balance, true); // The gas used of a failed deposit post-regolith is the gas @@ -459,8 +459,8 @@ where Err(error) }; // do the cleanup - evm.ctx().chain().clear_tx_l1_cost(); - evm.ctx().local().clear(); + evm.ctx().chain_mut().clear_tx_l1_cost(); + evm.ctx().local_mut().clear(); output } @@ -658,7 +658,7 @@ mod tests { .unwrap(); // Check the account balance is updated. - let account = evm.ctx().journal().load_account(caller).unwrap(); + let account = evm.ctx().journal_mut().load_account(caller).unwrap(); assert_eq!(account.info.balance, U256::from(1010)); } @@ -698,7 +698,7 @@ mod tests { .unwrap(); // Check the account balance is updated. - let account = evm.ctx().journal().load_account(caller).unwrap(); + let account = evm.ctx().journal_mut().load_account(caller).unwrap(); assert_eq!(account.info.balance, U256::from(1010)); } @@ -815,7 +815,7 @@ mod tests { .unwrap(); // Check the account balance is updated. - let account = evm.ctx().journal().load_account(caller).unwrap(); + let account = evm.ctx().journal_mut().load_account(caller).unwrap(); assert_eq!(account.info.balance, U256::from(1)); } @@ -853,7 +853,7 @@ mod tests { .unwrap(); // Check the account balance is updated. - let account = evm.ctx().journal().load_account(caller).unwrap(); + let account = evm.ctx().journal_mut().load_account(caller).unwrap(); assert_eq!(account.info.balance, U256::from(1)); } @@ -1045,7 +1045,7 @@ mod tests { } // Check that the caller was reimbursed the correct amount of ETH. - let account = evm.ctx().journal().load_account(SENDER).unwrap(); + let account = evm.ctx().journal_mut().load_account(SENDER).unwrap(); assert_eq!(account.info.balance, expected_refund); } } From ba37a7c38965f3b04b2461e567b028289f68d0d5 Mon Sep 17 00:00:00 2001 From: rakita Date: Mon, 2 Jun 2025 20:03:58 +0200 Subject: [PATCH 030/225] chore(docs): add lints to database-interface and op-revm crates (bluealloy/revm#2568) --- Cargo.toml | 9 ++------- src/api.rs | 6 ++++++ src/api/builder.rs | 1 + src/api/default_ctx.rs | 1 + src/api/exec.rs | 5 +++-- src/constants.rs | 6 +++++- src/evm.rs | 5 +++++ src/fast_lz.rs | 2 ++ src/handler.rs | 10 ++++++++++ src/l1block.rs | 23 +++++++++++------------ src/precompiles.rs | 20 ++++++++++++++++++-- src/result.rs | 4 ++++ src/spec.rs | 22 ++++++++++++++++++++++ src/transaction.rs | 1 + src/transaction/abstraction.rs | 9 ++++++++- src/transaction/deposit.rs | 7 +++++++ src/transaction/error.rs | 2 ++ tests/common.rs | 1 + tests/integration.rs | 1 + 19 files changed, 110 insertions(+), 25 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ccd8a3adceb..b91f7b90389 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,13 +13,8 @@ readme.workspace = true all-features = true rustdoc-args = ["--cfg", "docsrs"] -[lints.rust] -unreachable_pub = "warn" -unused_must_use = "deny" -rust_2018_idioms = "deny" - -[lints.rustdoc] -all = "warn" +[lints] +workspace = true [dependencies] # revm diff --git a/src/api.rs b/src/api.rs index 101ee66c477..b3d5771dc5b 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,3 +1,9 @@ +//! Optimism API types. + pub mod builder; pub mod default_ctx; pub mod exec; + +pub use builder::OpBuilder; +pub use default_ctx::DefaultOp; +pub use exec::{OpContextTr, OpError}; diff --git a/src/api/builder.rs b/src/api/builder.rs index 08da6aafc37..f97c08373cc 100644 --- a/src/api/builder.rs +++ b/src/api/builder.rs @@ -1,3 +1,4 @@ +//! Optimism builder trait [`OpBuilder`] used to build [`OpEvm`]. use crate::{evm::OpEvm, transaction::OpTxTr, L1BlockInfo, OpSpecId}; use revm::{ context::Cfg, diff --git a/src/api/default_ctx.rs b/src/api/default_ctx.rs index 8d63f22c753..b7626364675 100644 --- a/src/api/default_ctx.rs +++ b/src/api/default_ctx.rs @@ -1,3 +1,4 @@ +//! Contains trait [`DefaultOp`] used to create a default context. use crate::{L1BlockInfo, OpSpecId, OpTransaction}; use revm::{ context::{BlockEnv, CfgEnv, TxEnv}, diff --git a/src/api/exec.rs b/src/api/exec.rs index fcffd2e0cd8..55536346a52 100644 --- a/src/api/exec.rs +++ b/src/api/exec.rs @@ -1,3 +1,4 @@ +//! Implementation of the [`ExecuteEvm`] trait for the [`OpEvm`]. use crate::{ evm::OpEvm, handler::OpHandler, transaction::OpTxTr, L1BlockInfo, OpHaltReason, OpSpecId, OpTransactionError, @@ -19,7 +20,7 @@ use revm::{ DatabaseCommit, ExecuteCommitEvm, ExecuteEvm, }; -// Type alias for Optimism context +/// Type alias for Optimism context pub trait OpContextTr: ContextTr< Journal: JournalTr, @@ -41,7 +42,7 @@ impl OpContextTr for T where } /// Type alias for the error type of the OpEvm. -type OpError = EVMError<<::Db as Database>::Error, OpTransactionError>; +pub type OpError = EVMError<<::Db as Database>::Error, OpTransactionError>; impl ExecuteEvm for OpEvm, PRECOMPILE> diff --git a/src/constants.rs b/src/constants.rs index 3d6c79c6dce..c639e8573c7 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,6 +1,7 @@ +//! Optimism constants used in the Optimism EVM. use revm::primitives::{address, Address, U256}; -pub const ZERO_BYTE_COST: u64 = 4; +/// The cost of a non-zero byte in the EVM. pub const NON_ZERO_BYTE_COST: u64 = 16; /// The two 4-byte Ecotone fee scalar values are packed into the same storage slot as the 8-byte sequence number. @@ -22,8 +23,11 @@ pub const OPERATOR_FEE_CONSTANT_OFFSET: usize = 24; /// Allows users to use 6 decimal points of precision when specifying the operator_fee_scalar. pub const OPERATOR_FEE_SCALAR_DECIMAL: u64 = 1_000_000; +/// The L1 base fee slot. pub const L1_BASE_FEE_SLOT: U256 = U256::from_limbs([1u64, 0, 0, 0]); +/// The L1 overhead slot. pub const L1_OVERHEAD_SLOT: U256 = U256::from_limbs([5u64, 0, 0, 0]); +/// The L1 scalar slot. pub const L1_SCALAR_SLOT: U256 = U256::from_limbs([6u64, 0, 0, 0]); /// [ECOTONE_L1_BLOB_BASE_FEE_SLOT] was added in the Ecotone upgrade and stores the L1 blobBaseFee attribute. diff --git a/src/evm.rs b/src/evm.rs index bd1481f7826..b742dc84979 100644 --- a/src/evm.rs +++ b/src/evm.rs @@ -1,3 +1,4 @@ +//! Contains the `[OpEvm]` type and its implementation of the execution EVM traits. use crate::precompiles::OpPrecompiles; use revm::{ context::{ContextSetters, Evm}, @@ -11,11 +12,15 @@ use revm::{ Inspector, }; +/// Optimism EVM extends the [`Evm`] type with Optimism specific types and logic. +#[derive(Debug, Clone)] pub struct OpEvm, P = OpPrecompiles>( + /// Inner EVM type. pub Evm, ); impl OpEvm, OpPrecompiles> { + /// Create a new Optimism EVM. pub fn new(ctx: CTX, inspector: INSP) -> Self { Self(Evm { ctx, diff --git a/src/fast_lz.rs b/src/fast_lz.rs index 3f0f460673a..feea6521dd4 100644 --- a/src/fast_lz.rs +++ b/src/fast_lz.rs @@ -1,3 +1,5 @@ +//! Contains the `[flz_compress_len]` function. + /// Returns the length of the data after compression through FastLZ, based on /// /// diff --git a/src/handler.rs b/src/handler.rs index 1a3ce0c1f4b..649a5bca5e2 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -24,12 +24,18 @@ use revm::{ }; use std::boxed::Box; +/// Optimism handler extends the [`Handler`] with Optimism specific logic. +#[derive(Debug, Clone)] pub struct OpHandler { + /// Mainnet handler allows us to use functions from the mainnet handler inside optimism handler. + /// So we dont duplicate the logic pub mainnet: MainnetHandler, + /// Phantom data to avoid type inference issues. pub _phantom: core::marker::PhantomData<(EVM, ERROR, FRAME)>, } impl OpHandler { + /// Create a new Optimism handler. pub fn new() -> Self { Self { mainnet: MainnetHandler::default(), @@ -44,7 +50,11 @@ impl Default for OpHandler { } } +/// Trait to check if the error is a transaction error. +/// +/// Used in cache_error handler to catch deposit transaction that was halted. pub trait IsTxError { + /// Check if the error is a transaction error. fn is_tx_error(&self) -> bool; } diff --git a/src/l1block.rs b/src/l1block.rs index 6a03da28352..cc4ec830003 100644 --- a/src/l1block.rs +++ b/src/l1block.rs @@ -1,17 +1,21 @@ +//! Contains the `[L1BlockInfo]` type and its implementation. use crate::{ constants::{ BASE_FEE_SCALAR_OFFSET, BLOB_BASE_FEE_SCALAR_OFFSET, ECOTONE_L1_BLOB_BASE_FEE_SLOT, ECOTONE_L1_FEE_SCALARS_SLOT, EMPTY_SCALARS, L1_BASE_FEE_SLOT, L1_BLOCK_CONTRACT, L1_OVERHEAD_SLOT, L1_SCALAR_SLOT, NON_ZERO_BYTE_COST, OPERATOR_FEE_CONSTANT_OFFSET, OPERATOR_FEE_SCALARS_SLOT, OPERATOR_FEE_SCALAR_DECIMAL, OPERATOR_FEE_SCALAR_OFFSET, - ZERO_BYTE_COST, }, transaction::estimate_tx_compressed_size, OpSpecId, }; -use core::ops::Mul; use revm::{ - database_interface::Database, interpreter::Gas, primitives::hardfork::SpecId, primitives::U256, + database_interface::Database, + interpreter::{ + gas::{get_tokens_in_calldata, NON_ZERO_BYTE_MULTIPLIER_ISTANBUL, STANDARD_TOKEN_COST}, + Gas, + }, + primitives::{hardfork::SpecId, U256}, }; /// L1 block info @@ -204,20 +208,15 @@ impl L1BlockInfo { .wrapping_div(U256::from(1_000_000)); }; - let mut rollup_data_gas_cost = U256::from(input.iter().fold(0, |acc, byte| { - acc + if *byte == 0x00 { - ZERO_BYTE_COST - } else { - NON_ZERO_BYTE_COST - } - })); + // tokens in calldata where non-zero bytes are priced 4 times higher than zero bytes (Same as in Istanbul). + let mut tokens_in_transaction_data = get_tokens_in_calldata(input, true); // Prior to regolith, an extra 68 non zero bytes were included in the rollup data costs. if !spec_id.is_enabled_in(OpSpecId::REGOLITH) { - rollup_data_gas_cost += U256::from(NON_ZERO_BYTE_COST).mul(U256::from(68)); + tokens_in_transaction_data += 68 * NON_ZERO_BYTE_MULTIPLIER_ISTANBUL; } - rollup_data_gas_cost + U256::from(tokens_in_transaction_data.saturating_mul(STANDARD_TOKEN_COST)) } // Calculate the estimated compressed transaction size in bytes, scaled by 1e6. diff --git a/src/precompiles.rs b/src/precompiles.rs index 3b873a12b92..d29e5667653 100644 --- a/src/precompiles.rs +++ b/src/precompiles.rs @@ -1,3 +1,4 @@ +//! Contains Optimism specific precompiles. use crate::OpSpecId; use once_cell::race::OnceBox; use revm::{ @@ -14,11 +15,12 @@ use revm::{ use std::boxed::Box; use std::string::String; -// Optimism precompile provider +/// Optimism precompile provider #[derive(Debug, Clone)] pub struct OpPrecompiles { /// Inner precompile provider is same as Ethereums. inner: EthPrecompiles, + /// Spec id of the precompile provider. spec: OpSpecId, } @@ -45,7 +47,7 @@ impl OpPrecompiles { } } - // Precompiles getter. + /// Precompiles getter. #[inline] pub fn precompiles(&self) -> &'static Precompiles { self.inner.precompiles @@ -136,15 +138,19 @@ impl Default for OpPrecompiles { } } +/// Bn128 pair precompile. pub mod bn128_pair { use super::*; + /// Max input size for the bn128 pair precompile. pub const GRANITE_MAX_INPUT_SIZE: usize = 112687; + /// Bn128 pair precompile. pub const GRANITE: PrecompileWithAddress = PrecompileWithAddress(bn128::pair::ADDRESS, |input, gas_limit| { run_pair(input, gas_limit) }); + /// Run the bn128 pair precompile with Optimism input limit. pub fn run_pair(input: &[u8], gas_limit: u64) -> PrecompileResult { if input.len() > GRANITE_MAX_INPUT_SIZE { return Err(PrecompileError::Bn128PairLength); @@ -158,6 +164,7 @@ pub mod bn128_pair { } } +/// Bls12_381 precompile. pub mod bls12_381 { use super::*; use revm::precompile::bls12_381_const::{G1_MSM_ADDRESS, G2_MSM_ADDRESS, PAIRING_ADDRESS}; @@ -165,17 +172,24 @@ pub mod bls12_381 { #[cfg(not(feature = "std"))] use crate::std::string::ToString; + /// Max input size for the g1 msm precompile. pub const ISTHMUS_G1_MSM_MAX_INPUT_SIZE: usize = 513760; + /// Max input size for the g2 msm precompile. pub const ISTHMUS_G2_MSM_MAX_INPUT_SIZE: usize = 488448; + /// Max input size for the pairing precompile. pub const ISTHMUS_PAIRING_MAX_INPUT_SIZE: usize = 235008; + /// G1 msm precompile. pub const ISTHMUS_G1_MSM: PrecompileWithAddress = PrecompileWithAddress(G1_MSM_ADDRESS, run_g1_msm); + /// G2 msm precompile. pub const ISTHMUS_G2_MSM: PrecompileWithAddress = PrecompileWithAddress(G2_MSM_ADDRESS, run_g2_msm); + /// Pairing precompile. pub const ISTHMUS_PAIRING: PrecompileWithAddress = PrecompileWithAddress(PAIRING_ADDRESS, run_pair); + /// Run the g1 msm precompile with Optimism input limit. pub fn run_g1_msm(input: &[u8], gas_limit: u64) -> PrecompileResult { if input.len() > ISTHMUS_G1_MSM_MAX_INPUT_SIZE { return Err(PrecompileError::Other( @@ -185,6 +199,7 @@ pub mod bls12_381 { precompile::bls12_381::g1_msm::g1_msm(input, gas_limit) } + /// Run the g2 msm precompile with Optimism input limit. pub fn run_g2_msm(input: &[u8], gas_limit: u64) -> PrecompileResult { if input.len() > ISTHMUS_G2_MSM_MAX_INPUT_SIZE { return Err(PrecompileError::Other( @@ -194,6 +209,7 @@ pub mod bls12_381 { precompile::bls12_381::g2_msm::g2_msm(input, gas_limit) } + /// Run the pairing precompile with Optimism input limit. pub fn run_pair(input: &[u8], gas_limit: u64) -> PrecompileResult { if input.len() > ISTHMUS_PAIRING_MAX_INPUT_SIZE { return Err(PrecompileError::Other( diff --git a/src/result.rs b/src/result.rs index 6250246ff8a..2542a204607 100644 --- a/src/result.rs +++ b/src/result.rs @@ -1,9 +1,13 @@ +//! Contains the `[OpHaltReason]` type. use revm::context_interface::result::HaltReason; +/// Optimism halt reason. #[derive(Clone, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum OpHaltReason { + /// Base halt reason. Base(HaltReason), + /// Failed deposit halt reason. FailedDeposit, } diff --git a/src/spec.rs b/src/spec.rs index bd5c09c2a05..c0e764c598a 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -1,21 +1,33 @@ +//! Contains the `[OpSpecId]` type and its implementation. use core::str::FromStr; use revm::primitives::hardfork::{name as eth_name, SpecId, UnknownHardfork}; +/// Optimism spec id. #[repr(u8)] #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Default)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[allow(non_camel_case_types)] pub enum OpSpecId { + /// Bedrock spec id. BEDROCK = 100, + /// Regolith spec id. REGOLITH, + /// Canyon spec id. CANYON, + /// Ecotone spec id. ECOTONE, + /// Fjord spec id. FJORD, + /// Granite spec id. GRANITE, + /// Holocene spec id. HOLOCENE, + /// Isthmus spec id. #[default] ISTHMUS, + /// Interop spec id. INTEROP, + /// Osaka spec id. OSAKA, } @@ -31,6 +43,7 @@ impl OpSpecId { } } + /// Checks if the [`OpSpecId`] is enabled in the other [`OpSpecId`]. pub const fn is_enabled_in(self, other: OpSpecId) -> bool { other as u8 <= self as u8 } @@ -81,14 +94,23 @@ impl From for &'static str { /// String identifiers for Optimism hardforks pub mod name { + /// Bedrock spec name. pub const BEDROCK: &str = "Bedrock"; + /// Regolith spec name. pub const REGOLITH: &str = "Regolith"; + /// Canyon spec name. pub const CANYON: &str = "Canyon"; + /// Ecotone spec name. pub const ECOTONE: &str = "Ecotone"; + /// Fjord spec name. pub const FJORD: &str = "Fjord"; + /// Granite spec name. pub const GRANITE: &str = "Granite"; + /// Holocene spec name. pub const HOLOCENE: &str = "Holocene"; + /// Isthmus spec name. pub const ISTHMUS: &str = "Isthmus"; + /// Interop spec name. pub const INTEROP: &str = "Interop"; } diff --git a/src/transaction.rs b/src/transaction.rs index cbe16fb6240..702de9dfecd 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -1,3 +1,4 @@ +//! Contains the `[OpTransaction]` type and its implementation. pub mod abstraction; pub mod deposit; pub mod error; diff --git a/src/transaction/abstraction.rs b/src/transaction/abstraction.rs index 31714d96d14..23a61bd6206 100644 --- a/src/transaction/abstraction.rs +++ b/src/transaction/abstraction.rs @@ -1,3 +1,4 @@ +//! Optimism transaction abstraction containing the `[OpTxTr]` trait and corresponding `[OpTransaction]` type. use super::deposit::{DepositTransactionParts, DEPOSIT_TRANSACTION_TYPE}; use auto_impl::auto_impl; use revm::{ @@ -8,11 +9,13 @@ use revm::{ }; use std::vec; +/// Optimism Transaction trait. #[auto_impl(&, &mut, Box, Arc)] pub trait OpTxTr: Transaction { + /// Enveloped transaction bytes. fn enveloped_tx(&self) -> Option<&Bytes>; - /// Source hash of the deposit transaction + /// Source hash of the deposit transaction. fn source_hash(&self) -> Option; /// Mint of the deposit transaction @@ -27,9 +30,11 @@ pub trait OpTxTr: Transaction { } } +/// Optimism transaction. #[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct OpTransaction { + /// Base transaction fields. pub base: T, /// An enveloped EIP-2718 typed transaction /// @@ -37,10 +42,12 @@ pub struct OpTransaction { /// opposed to requiring downstream apps to compute the cost /// externally. pub enveloped_tx: Option, + /// Deposit transaction parts. pub deposit: DepositTransactionParts, } impl OpTransaction { + /// Create a new Optimism transaction. pub fn new(base: T) -> Self { Self { base, diff --git a/src/transaction/deposit.rs b/src/transaction/deposit.rs index 878b2ee14c9..1d51fb12246 100644 --- a/src/transaction/deposit.rs +++ b/src/transaction/deposit.rs @@ -1,16 +1,23 @@ +//! Contains Deposit transaction parts. use revm::primitives::B256; +/// Deposit transaction type. pub const DEPOSIT_TRANSACTION_TYPE: u8 = 0x7E; +/// Deposit transaction parts. #[derive(Clone, Debug, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct DepositTransactionParts { + /// Source hash of the deposit transaction. pub source_hash: B256, + /// Minted value of the deposit transaction. pub mint: Option, + /// Whether the transaction is a system transaction. pub is_system_transaction: bool, } impl DepositTransactionParts { + /// Create a new deposit transaction parts. pub fn new(source_hash: B256, mint: Option, is_system_transaction: bool) -> Self { Self { source_hash, diff --git a/src/transaction/error.rs b/src/transaction/error.rs index b575c69eecb..76169a3dd85 100644 --- a/src/transaction/error.rs +++ b/src/transaction/error.rs @@ -1,3 +1,4 @@ +//! Contains the `[OpTransactionError]` type. use core::fmt::Display; use revm::context_interface::{ result::{EVMError, InvalidTransaction}, @@ -8,6 +9,7 @@ use revm::context_interface::{ #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum OpTransactionError { + /// Base transaction error. Base(InvalidTransaction), /// System transactions are not supported post-regolith hardfork. /// diff --git a/tests/common.rs b/tests/common.rs index 517d0925d89..1c83223ea7b 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -1,3 +1,4 @@ +//! Common test utilities used to compare execution results against testdata. #![allow(dead_code)] use revm::{ diff --git a/tests/integration.rs b/tests/integration.rs index a9c369c2e57..cc12aef3aa3 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1,3 +1,4 @@ +//! Integration tests for the `op-revm` crate. mod common; use common::compare_or_save_testdata; From ee028a12b3d50d2da36d42a6310e6fe245db5f89 Mon Sep 17 00:00:00 2001 From: rakita Date: Tue, 3 Jun 2025 18:45:58 +0200 Subject: [PATCH 031/225] feat(Osaka): EIP-7825 tx limit cap (bluealloy/revm#2575) * feat(Osaka): EIP-7825 tx limit cap * use const value --- tests/integration.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration.rs b/tests/integration.rs index cc12aef3aa3..29473b68e75 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -20,7 +20,7 @@ use revm::{ Interpreter, InterpreterTypes, }, precompile::{bls12_381_const, bls12_381_utils, bn128, secp256r1, u64_to_address}, - primitives::{Address, Bytes, Log, TxKind, U256}, + primitives::{eip7825, Address, Bytes, Log, TxKind, U256}, state::Bytecode, Context, ExecuteEvm, InspectEvm, Inspector, Journal, }; @@ -76,7 +76,7 @@ fn test_halted_deposit_tx() { output.result, ExecutionResult::Halt { reason: OpHaltReason::FailedDeposit, - gas_used: 30_000_000 + gas_used: eip7825::TX_GAS_LIMIT_CAP, } ); assert_eq!( From a9ac4d298e7c363b467fe7c8a58361f4d6f66859 Mon Sep 17 00:00:00 2001 From: 0xDmtri <0xDmtri@protonmail.com> Date: Wed, 4 Jun 2025 12:13:45 +0200 Subject: [PATCH 032/225] chore(op-revm): impl type alias for Default OpEvm (bluealloy/revm#2576) --- src/api/builder.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/api/builder.rs b/src/api/builder.rs index f97c08373cc..d36708516e8 100644 --- a/src/api/builder.rs +++ b/src/api/builder.rs @@ -1,5 +1,5 @@ //! Optimism builder trait [`OpBuilder`] used to build [`OpEvm`]. -use crate::{evm::OpEvm, transaction::OpTxTr, L1BlockInfo, OpSpecId}; +use crate::{evm::OpEvm, precompiles::OpPrecompiles, transaction::OpTxTr, L1BlockInfo, OpSpecId}; use revm::{ context::Cfg, context_interface::{Block, JournalTr}, @@ -9,19 +9,20 @@ use revm::{ Context, Database, }; +/// Type alias for default OpEvm +pub type DefaultOpEvm = + OpEvm, OpPrecompiles>; + /// Trait that allows for optimism OpEvm to be built. pub trait OpBuilder: Sized { /// Type of the context. type Context; /// Build the op. - fn build_op(self) -> OpEvm>; + fn build_op(self) -> DefaultOpEvm; /// Build the op with an inspector. - fn build_op_with_inspector( - self, - inspector: INSP, - ) -> OpEvm>; + fn build_op_with_inspector(self, inspector: INSP) -> DefaultOpEvm; } impl OpBuilder for Context @@ -34,14 +35,11 @@ where { type Context = Self; - fn build_op(self) -> OpEvm> { + fn build_op(self) -> DefaultOpEvm { OpEvm::new(self, ()) } - fn build_op_with_inspector( - self, - inspector: INSP, - ) -> OpEvm> { + fn build_op_with_inspector(self, inspector: INSP) -> DefaultOpEvm { OpEvm::new(self, inspector) } } From c03ca908c804a9ae7cbdd5aa7dbf2f985c868d64 Mon Sep 17 00:00:00 2001 From: rakita Date: Fri, 6 Jun 2025 11:01:27 +0200 Subject: [PATCH 033/225] fix(multitx): Add local flags for create and selfdestruct (bluealloy/revm#2581) * fix(multitx): Add local flags for create and selfdestruct * fix tests * add test for selfdestruct * add selfdestruct crate test * fix zepter --- src/handler.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/handler.rs b/src/handler.rs index 649a5bca5e2..fa856b3a915 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -439,6 +439,8 @@ where let old_balance = acc.info.balance; + // decrement transaction id as it was incremented when we discarded the tx. + acc.transaction_id -= acc.transaction_id; acc.info.nonce = acc.info.nonce.saturating_add(1); acc.info.balance = acc .info From 5aba389a84023e49295bce347254e6dcd7ce98d9 Mon Sep 17 00:00:00 2001 From: rakita Date: Fri, 6 Jun 2025 12:18:38 +0200 Subject: [PATCH 034/225] feat: add with_caller for system_transact (bluealloy/revm#2587) --- src/api/exec.rs | 11 +++++++---- src/transaction/abstraction.rs | 12 ++++++++++-- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/api/exec.rs b/src/api/exec.rs index 55536346a52..07bcab0dc69 100644 --- a/src/api/exec.rs +++ b/src/api/exec.rs @@ -127,14 +127,17 @@ where CTX: OpContextTr + ContextSetters, PRECOMPILE: PrecompileProvider, { - fn transact_system_call( + fn transact_system_call_with_caller( &mut self, + caller: Address, system_contract_address: Address, data: Bytes, ) -> Result { - self.0 - .ctx - .set_tx(CTX::Tx::new_system_tx(data, system_contract_address)); + self.0.ctx.set_tx(CTX::Tx::new_system_tx_with_caller( + caller, + system_contract_address, + data, + )); let mut h = OpHandler::<_, _, EthFrame<_, _, _>>::new(); h.run_system_call(self) } diff --git a/src/transaction/abstraction.rs b/src/transaction/abstraction.rs index 23a61bd6206..a0b66663e26 100644 --- a/src/transaction/abstraction.rs +++ b/src/transaction/abstraction.rs @@ -68,8 +68,16 @@ impl Default for OpTransaction { } impl SystemCallTx for OpTransaction { - fn new_system_tx(data: Bytes, system_contract_address: Address) -> Self { - OpTransaction::new(TX::new_system_tx(data, system_contract_address)) + fn new_system_tx_with_caller( + caller: Address, + system_contract_address: Address, + data: Bytes, + ) -> Self { + OpTransaction::new(TX::new_system_tx_with_caller( + caller, + system_contract_address, + data, + )) } } From 6e560d4d426bef499e16fcf608ec63af7590b0d0 Mon Sep 17 00:00:00 2001 From: rakita Date: Fri, 6 Jun 2025 15:57:47 +0200 Subject: [PATCH 035/225] bump: tag v75 revm v24.0.1 (bluealloy/revm#2563) (bluealloy/revm#2589) * refactor: unify calling of journal account loading * bump: tag v75 revm v24.0.1 --- CHANGELOG.md | 6 ++++++ Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 891c9952cfa..dde5979898e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [5.0.1](https://github.com/bluealloy/revm/compare/op-revm-v5.0.0...op-revm-v5.0.1) - 2025-05-31 + +### Other + +- updated the following local packages: revm + ## [5.0.0](https://github.com/bluealloy/revm/compare/op-revm-v4.0.2...op-revm-v5.0.0) - 2025-05-22 ### Added diff --git a/Cargo.toml b/Cargo.toml index b91f7b90389..7e33a1012b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "5.0.0" +version = "5.0.1" authors.workspace = true edition.workspace = true keywords.workspace = true From 29d574e76f8205f7477a39ba8f037fd6a9f5c391 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 6 Jun 2025 16:12:13 +0200 Subject: [PATCH 036/225] chore: release (bluealloy/revm#2577) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 21 +++++++++++++++++++++ Cargo.toml | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dde5979898e..c7b6c286d2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [6.0.0](https://github.com/bluealloy/revm/compare/op-revm-v5.0.1...op-revm-v6.0.0) - 2025-06-06 + +### Added + +- add with_caller for system_transact ([#2587](https://github.com/bluealloy/revm/pull/2587)) +- *(Osaka)* EIP-7825 tx limit cap ([#2575](https://github.com/bluealloy/revm/pull/2575)) +- expand timestamp/block_number to u256 ([#2546](https://github.com/bluealloy/revm/pull/2546)) +- transact multi tx ([#2517](https://github.com/bluealloy/revm/pull/2517)) + +### Fixed + +- *(multitx)* Add local flags for create and selfdestruct ([#2581](https://github.com/bluealloy/revm/pull/2581)) + +### Other + +- tag v75 revm v24.0.1 ([#2563](https://github.com/bluealloy/revm/pull/2563)) ([#2589](https://github.com/bluealloy/revm/pull/2589)) +- *(op-revm)* impl type alias for Default OpEvm ([#2576](https://github.com/bluealloy/revm/pull/2576)) +- *(docs)* add lints to database-interface and op-revm crates ([#2568](https://github.com/bluealloy/revm/pull/2568)) +- ContextTr rm *_ref, and add *_mut fn ([#2560](https://github.com/bluealloy/revm/pull/2560)) +- *(test)* preserve order of fields in json fixtures ([#2541](https://github.com/bluealloy/revm/pull/2541)) + ## [5.0.1](https://github.com/bluealloy/revm/compare/op-revm-v5.0.0...op-revm-v5.0.1) - 2025-05-31 ### Other diff --git a/Cargo.toml b/Cargo.toml index 7e33a1012b4..6f39b991acb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "5.0.1" +version = "6.0.0" authors.workspace = true edition.workspace = true keywords.workspace = true From 3a971c1602b0a6a44021711f5c6973d1a2471a1f Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Mon, 9 Jun 2025 11:59:29 +0200 Subject: [PATCH 037/225] feat: enable P256 in Osaka (bluealloy/revm#2601) --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 6f39b991acb..20ede9f2f3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ workspace = true [dependencies] # revm -revm = { workspace = true, features = ["secp256r1"] } +revm.workspace = true auto_impl.workspace = true # static precompile sets. From 9ddadb665c07e1831307b7bbd0acde28e17315a6 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Tue, 10 Jun 2025 17:13:17 +0200 Subject: [PATCH 038/225] chore: rename `transact` methods (bluealloy/revm#2616) * refactor: rename transact -> transact_one, transact_finalize -> transact * renames * default generic * inspect_tx * ResultAndState * fix --- src/api/default_ctx.rs | 4 ++-- src/api/exec.rs | 10 +++++----- src/fast_lz.rs | 2 +- tests/common.rs | 4 ++-- tests/integration.rs | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/api/default_ctx.rs b/src/api/default_ctx.rs index b7626364675..5506deed8b5 100644 --- a/src/api/default_ctx.rs +++ b/src/api/default_ctx.rs @@ -40,8 +40,8 @@ mod test { // convert to optimism context let mut evm = ctx.build_op_with_inspector(NoOpInspector {}); // execute - let _ = evm.transact_finalize(OpTransaction::default()); + let _ = evm.transact(OpTransaction::default()); // inspect - let _ = evm.inspect_tx(OpTransaction::default()); + let _ = evm.inspect_one_tx(OpTransaction::default()); } } diff --git a/src/api/exec.rs b/src/api/exec.rs index 07bcab0dc69..7c36dd94759 100644 --- a/src/api/exec.rs +++ b/src/api/exec.rs @@ -4,7 +4,7 @@ use crate::{ OpTransactionError, }; use revm::{ - context::{result::ResultAndState, ContextSetters}, + context::{result::ExecResultAndState, ContextSetters}, context_interface::{ result::{EVMError, ExecutionResult}, Cfg, ContextTr, Database, JournalTr, @@ -60,7 +60,7 @@ where self.0.ctx.set_block(block); } - fn transact(&mut self, tx: Self::Tx) -> Result { + fn transact_one(&mut self, tx: Self::Tx) -> Result { self.0.ctx.set_tx(tx); let mut h = OpHandler::<_, _, EthFrame<_, _, _>>::new(); h.run(self) @@ -72,11 +72,11 @@ where fn replay( &mut self, - ) -> Result, Self::Error> { + ) -> Result, Self::Error> { let mut h = OpHandler::<_, _, EthFrame<_, _, _>>::new(); h.run(self).map(|result| { let state = self.finalize(); - ResultAndState::new(result, state) + ExecResultAndState::new(result, state) }) } } @@ -105,7 +105,7 @@ where self.0.inspector = inspector; } - fn inspect_tx(&mut self, tx: Self::Tx) -> Result { + fn inspect_one_tx(&mut self, tx: Self::Tx) -> Result { self.0.ctx.set_tx(tx); let mut h = OpHandler::<_, _, EthFrame<_, _, _>>::new(); h.inspect_run(self) diff --git a/src/fast_lz.rs b/src/fast_lz.rs index feea6521dd4..bb8ccc17a33 100644 --- a/src/fast_lz.rs +++ b/src/fast_lz.rs @@ -182,7 +182,7 @@ mod tests { tx.base.gas_limit = 3_000_000; tx.enveloped_tx = Some(Bytes::default()); - let result = evm.transact(tx).unwrap(); + let result = evm.transact_one(tx).unwrap(); let output = result.output().unwrap(); let evm_val = FastLz::fastLzCall::abi_decode_returns(output).unwrap(); diff --git a/tests/common.rs b/tests/common.rs index 1c83223ea7b..d1d55599945 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -14,7 +14,7 @@ pub(crate) const TESTS_TESTDATA: &str = "tests/testdata"; #[cfg(not(feature = "serde"))] pub(crate) fn compare_or_save_testdata( _filename: &str, - _output: &ResultAndState, EvmState>, + _output: &ResultAndState, ) { // serde needs to be enabled to use this function } @@ -43,7 +43,7 @@ pub(crate) fn compare_or_save_testdata( #[cfg(feature = "serde")] pub(crate) fn compare_or_save_testdata( filename: &str, - output: &ResultAndState, EvmState>, + output: &ResultAndState, ) where HaltReasonTy: serde::Serialize + for<'a> serde::Deserialize<'a> + PartialEq, { diff --git a/tests/integration.rs b/tests/integration.rs index 29473b68e75..95833a21251 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -771,7 +771,7 @@ fn test_log_inspector() { }; // Run evm. - let output = evm.inspect_tx_finalize(tx).unwrap(); + let output = evm.inspect_tx(tx).unwrap(); let inspector = &evm.0.inspector; assert!(!inspector.logs.is_empty()); From 8ca9a72e3ea19c0f1f3260ac4a48e33980655345 Mon Sep 17 00:00:00 2001 From: rakita Date: Tue, 17 Jun 2025 15:14:18 +0200 Subject: [PATCH 039/225] perf: re-use frame allocation (bluealloy/revm#2636) * wip * fix * fix: use Default instead of uninit * fix * chore: clippy * perf: try removing allocs from Default * chore: clippy * new: OutFrame abstraction over a maybe-uninit slot * perf: make SharedMemory::empty not allocate * inline * perf: do the stack thing * rename * perf: share the stack in the evm context * perf: initialize first frame the same way * rm inlines * jump_inner inline always * simplify frame init * cleanup move frame stack outside of run_exec_loop fun * cleanup inspector trait * add box around frame_stack items so we dont allocate a lot more of it * box included for no_std * unbox the init closure * WIP * move frame to evmtr * wip * clippy * fix inspection * some cleanup * cleanup * fix inspector test * cleaup, renames and moving of structs * renames --------- Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> --- src/api/exec.rs | 8 ++-- src/evm.rs | 114 +++++++++++++++++++++++++++++++++--------------- src/handler.rs | 86 +++++++++++++++++++----------------- 3 files changed, 127 insertions(+), 81 deletions(-) diff --git a/src/api/exec.rs b/src/api/exec.rs index 7c36dd94759..a5ddec95b68 100644 --- a/src/api/exec.rs +++ b/src/api/exec.rs @@ -62,7 +62,7 @@ where fn transact_one(&mut self, tx: Self::Tx) -> Result { self.0.ctx.set_tx(tx); - let mut h = OpHandler::<_, _, EthFrame<_, _, _>>::new(); + let mut h = OpHandler::<_, _, EthFrame>::new(); h.run(self) } @@ -73,7 +73,7 @@ where fn replay( &mut self, ) -> Result, Self::Error> { - let mut h = OpHandler::<_, _, EthFrame<_, _, _>>::new(); + let mut h = OpHandler::<_, _, EthFrame>::new(); h.run(self).map(|result| { let state = self.finalize(); ExecResultAndState::new(result, state) @@ -107,7 +107,7 @@ where fn inspect_one_tx(&mut self, tx: Self::Tx) -> Result { self.0.ctx.set_tx(tx); - let mut h = OpHandler::<_, _, EthFrame<_, _, _>>::new(); + let mut h = OpHandler::<_, _, EthFrame>::new(); h.inspect_run(self) } } @@ -138,7 +138,7 @@ where system_contract_address, data, )); - let mut h = OpHandler::<_, _, EthFrame<_, _, _>>::new(); + let mut h = OpHandler::<_, _, EthFrame>::new(); h.run_system_call(self) } } diff --git a/src/evm.rs b/src/evm.rs index b742dc84979..8f13b36c667 100644 --- a/src/evm.rs +++ b/src/evm.rs @@ -1,22 +1,29 @@ //! Contains the `[OpEvm]` type and its implementation of the execution EVM traits. use crate::precompiles::OpPrecompiles; use revm::{ - context::{ContextSetters, Evm}, + context::{ContextError, ContextSetters, Evm, FrameStack}, context_interface::ContextTr, handler::{ + evm::FrameTr, instructions::{EthInstructions, InstructionProvider}, - EvmTr, PrecompileProvider, + EthFrame, EvmTr, FrameInitOrResult, ItemOrResult, PrecompileProvider, }, inspector::{InspectorEvmTr, JournalExt}, - interpreter::{interpreter::EthInterpreter, Interpreter, InterpreterAction, InterpreterTypes}, - Inspector, + interpreter::{interpreter::EthInterpreter, InterpreterResult}, + Database, Inspector, }; /// Optimism EVM extends the [`Evm`] type with Optimism specific types and logic. #[derive(Debug, Clone)] -pub struct OpEvm, P = OpPrecompiles>( +pub struct OpEvm< + CTX, + INSP, + I = EthInstructions, + P = OpPrecompiles, + F = EthFrame, +>( /// Inner EVM type. - pub Evm, + pub Evm, ); impl OpEvm, OpPrecompiles> { @@ -27,6 +34,7 @@ impl OpEvm inspector, instruction: EthInstructions::new_mainnet(), precompiles: OpPrecompiles::default(), + frame_stack: FrameStack::new(), }) } } @@ -51,11 +59,8 @@ impl OpEvm { impl InspectorEvmTr for OpEvm where CTX: ContextTr + ContextSetters, - I: InstructionProvider< - Context = CTX, - InterpreterTypes: InterpreterTypes, - >, - P: PrecompileProvider, + I: InstructionProvider, + P: PrecompileProvider, INSP: Inspector, { type Inspector = INSP; @@ -68,41 +73,43 @@ where (&mut self.0.ctx, &mut self.0.inspector) } - fn run_inspect_interpreter( + fn ctx_inspector_frame( &mut self, - interpreter: &mut Interpreter< - ::InterpreterTypes, - >, - ) -> <::InterpreterTypes as InterpreterTypes>::Output - { - self.0.run_inspect_interpreter(interpreter) + ) -> (&mut Self::Context, &mut Self::Inspector, &mut Self::Frame) { + ( + &mut self.0.ctx, + &mut self.0.inspector, + self.0.frame_stack.get(), + ) + } + + fn ctx_inspector_frame_instructions( + &mut self, + ) -> ( + &mut Self::Context, + &mut Self::Inspector, + &mut Self::Frame, + &mut Self::Instructions, + ) { + ( + &mut self.0.ctx, + &mut self.0.inspector, + self.0.frame_stack.get(), + &mut self.0.instruction, + ) } } -impl EvmTr for OpEvm +impl EvmTr for OpEvm> where CTX: ContextTr, - I: InstructionProvider< - Context = CTX, - InterpreterTypes: InterpreterTypes, - >, - P: PrecompileProvider, + I: InstructionProvider, + P: PrecompileProvider, { type Context = CTX; type Instructions = I; type Precompiles = P; - - fn run_interpreter( - &mut self, - interpreter: &mut Interpreter< - ::InterpreterTypes, - >, - ) -> <::InterpreterTypes as InterpreterTypes>::Output - { - let context = &mut self.0.ctx; - let instructions = &mut self.0.instruction; - interpreter.run_plain(instructions.instruction_table(), context) - } + type Frame = EthFrame; fn ctx(&mut self) -> &mut Self::Context { &mut self.0.ctx @@ -119,4 +126,39 @@ where fn ctx_precompiles(&mut self) -> (&mut Self::Context, &mut Self::Precompiles) { (&mut self.0.ctx, &mut self.0.precompiles) } + + fn frame_stack(&mut self) -> &mut FrameStack { + &mut self.0.frame_stack + } + + fn frame_init( + &mut self, + frame_input: ::FrameInit, + ) -> Result< + ItemOrResult<&mut Self::Frame, ::FrameResult>, + ContextError<<::Db as Database>::Error>, + > { + self.0.frame_init(frame_input) + } + + fn frame_run( + &mut self, + ) -> Result< + FrameInitOrResult, + ContextError<<::Db as Database>::Error>, + > { + self.0.frame_run() + } + + #[doc = " Returns the result of the frame to the caller. Frame is popped from the frame stack."] + #[doc = " Consumes the frame result or returns it if there is more frames to run."] + fn frame_return_result( + &mut self, + result: ::FrameResult, + ) -> Result< + Option<::FrameResult>, + ContextError<<::Db as Database>::Error>, + > { + self.0.frame_return_result(result) + } } diff --git a/src/handler.rs b/src/handler.rs index fa856b3a915..2e4b0aed71a 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -13,13 +13,14 @@ use revm::{ Block, Cfg, ContextTr, JournalTr, Transaction, }, handler::{ + evm::FrameTr, handler::EvmTrError, post_execution::{self, reimburse_caller}, pre_execution::validate_account_nonce_and_code, - EvmTr, Frame, FrameResult, Handler, MainnetHandler, + EthFrame, EvmTr, FrameResult, Handler, MainnetHandler, }, - inspector::{Inspector, InspectorEvmTr, InspectorFrame, InspectorHandler}, - interpreter::{interpreter::EthInterpreter, FrameInput, Gas}, + inspector::{Inspector, InspectorEvmTr, InspectorHandler}, + interpreter::{interpreter::EthInterpreter, interpreter_action::FrameInit, Gas}, primitives::{hardfork::SpecId, U256}, }; use std::boxed::Box; @@ -66,15 +67,14 @@ impl IsTxError for EVMError { impl Handler for OpHandler where - EVM: EvmTr, + EVM: EvmTr, ERROR: EvmTrError + From + FromStringError + IsTxError, // TODO `FrameResult` should be a generic trait. // TODO `FrameInit` should be a generic. - FRAME: Frame, + FRAME: FrameTr, { type Evm = EVM; type Error = ERROR; - type Frame = FRAME; type HaltReason = OpHaltReason; fn validate_env(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> { @@ -219,7 +219,7 @@ where fn last_frame_result( &mut self, evm: &mut Self::Evm, - frame_result: &mut ::FrameResult, + frame_result: &mut <::Frame as FrameTr>::FrameResult, ) -> Result<(), Self::Error> { let ctx = evm.ctx(); let tx = ctx.tx(); @@ -285,7 +285,7 @@ where fn reimburse_caller( &self, evm: &mut Self::Evm, - exec_result: &mut ::FrameResult, + frame_result: &mut <::Frame as FrameTr>::FrameResult, ) -> Result<(), Self::Error> { let mut additional_refund = U256::ZERO; @@ -294,19 +294,19 @@ where additional_refund = evm .ctx() .chain() - .operator_fee_refund(exec_result.gas(), spec); + .operator_fee_refund(frame_result.gas(), spec); } - reimburse_caller(evm.ctx(), exec_result.gas_mut(), additional_refund).map_err(From::from) + reimburse_caller(evm.ctx(), frame_result.gas_mut(), additional_refund).map_err(From::from) } fn refund( &self, evm: &mut Self::Evm, - exec_result: &mut ::FrameResult, + frame_result: &mut <::Frame as FrameTr>::FrameResult, eip7702_refund: i64, ) { - exec_result.gas_mut().record_refund(eip7702_refund); + frame_result.gas_mut().record_refund(eip7702_refund); let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE; let is_regolith = evm.ctx().cfg().spec().is_enabled_in(OpSpecId::REGOLITH); @@ -314,7 +314,7 @@ where // Prior to Regolith, deposit transactions did not receive gas refunds. let is_gas_refund_disabled = is_deposit && !is_regolith; if !is_gas_refund_disabled { - exec_result.gas_mut().set_final_refund( + frame_result.gas_mut().set_final_refund( evm.ctx() .cfg() .spec() @@ -327,7 +327,7 @@ where fn reward_beneficiary( &self, evm: &mut Self::Evm, - exec_result: &mut ::FrameResult, + frame_result: &mut <::Frame as FrameTr>::FrameResult, ) -> Result<(), Self::Error> { let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE; @@ -336,7 +336,7 @@ where return Ok(()); } - self.mainnet.reward_beneficiary(evm, exec_result)?; + self.mainnet.reward_beneficiary(evm, frame_result)?; let basefee = evm.ctx().block().basefee() as u128; // If the transaction is not a deposit transaction, fees are paid out @@ -357,7 +357,7 @@ where if spec.is_enabled_in(OpSpecId::ISTHMUS) { operator_fee_cost = l1_block_info.operator_fee_charge( enveloped_tx, - U256::from(exec_result.gas().spent() - exec_result.gas().refunded() as u64), + U256::from(frame_result.gas().spent() - frame_result.gas().refunded() as u64), ); } // Send the L1 cost of the transaction to the L1 Fee Vault. @@ -367,7 +367,7 @@ where ctx.journal_mut().balance_incr( BASE_FEE_RECIPIENT, U256::from(basefee.saturating_mul( - (exec_result.gas().spent() - exec_result.gas().refunded() as u64) as u128, + (frame_result.gas().spent() - frame_result.gas().refunded() as u64) as u128, )), )?; @@ -381,7 +381,7 @@ where fn execution_result( &mut self, evm: &mut Self::Evm, - result: ::FrameResult, + frame_result: <::Frame as FrameTr>::FrameResult, ) -> Result, Self::Error> { match core::mem::replace(evm.ctx().error(), Ok(())) { Err(ContextError::Db(e)) => return Err(e.into()), @@ -390,7 +390,7 @@ where } let exec_result = - post_execution::output(evm.ctx(), result).map_haltreason(OpHaltReason::Base); + post_execution::output(evm.ctx(), frame_result).map_haltreason(OpHaltReason::Base); if exec_result.is_halt() { // Post-regolith, if the transaction is a deposit transaction and it halts, @@ -478,22 +478,14 @@ where } } -impl InspectorHandler for OpHandler +impl InspectorHandler for OpHandler> where EVM: InspectorEvmTr< Context: OpContextTr, + Frame = EthFrame, Inspector: Inspector<<::Evm as EvmTr>::Context, EthInterpreter>, >, ERROR: EvmTrError + From + FromStringError + IsTxError, - // TODO `FrameResult` should be a generic trait. - // TODO `FrameInit` should be a generic. - FRAME: InspectorFrame< - Evm = EVM, - Error = ERROR, - FrameResult = FrameResult, - FrameInit = FrameInput, - IT = EthInterpreter, - >, { type IT = EthInterpreter; } @@ -540,7 +532,8 @@ mod tests { 0..0, )); - let mut handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new(); + let mut handler = + OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame>::new(); handler .last_frame_result(&mut evm, &mut exec_result) @@ -664,7 +657,8 @@ mod tests { let mut evm = ctx.build_op(); - let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new(); + let handler = + OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame>::new(); handler .validate_against_state_and_deduct_caller(&mut evm) .unwrap(); @@ -704,7 +698,8 @@ mod tests { let mut evm = ctx.build_op(); - let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new(); + let handler = + OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame>::new(); handler .validate_against_state_and_deduct_caller(&mut evm) .unwrap(); @@ -770,7 +765,8 @@ mod tests { assert_ne!(evm.ctx().chain().l2_block, BLOCK_NUM); - let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new(); + let handler = + OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame>::new(); handler .validate_against_state_and_deduct_caller(&mut evm) .unwrap(); @@ -819,7 +815,8 @@ mod tests { }); let mut evm = ctx.build_op(); - let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new(); + let handler = + OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame>::new(); // l1block cost is 1048 fee. handler @@ -856,7 +853,8 @@ mod tests { }); let mut evm = ctx.build_op(); - let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new(); + let handler = + OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame>::new(); // operator fee cost is operator_fee_scalar * gas_limit / 1e6 + operator_fee_constant // 10_000_000 * 10 / 1_000_000 + 50 = 150 @@ -895,7 +893,8 @@ mod tests { // l1block cost is 1048 fee. let mut evm = ctx.build_op(); - let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new(); + let handler = + OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame>::new(); // l1block cost is 1048 fee. assert_eq!( @@ -921,7 +920,8 @@ mod tests { .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); let mut evm = ctx.build_op(); - let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new(); + let handler = + OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame>::new(); assert_eq!( handler.validate_env(&mut evm), @@ -947,7 +947,8 @@ mod tests { .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); let mut evm = ctx.build_op(); - let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new(); + let handler = + OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame>::new(); assert!(handler.validate_env(&mut evm).is_ok()); } @@ -963,7 +964,8 @@ mod tests { .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); let mut evm = ctx.build_op(); - let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new(); + let handler = + OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame>::new(); // Nonce and balance checks should be skipped for deposit transactions. assert!(handler.validate_env(&mut evm).is_ok()); @@ -978,7 +980,8 @@ mod tests { .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); let mut evm = ctx.build_op(); - let mut handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new(); + let mut handler = + OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame>::new(); assert_eq!( handler.execution_result( @@ -1020,7 +1023,8 @@ mod tests { .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); let mut evm = ctx.build_op(); - let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new(); + let handler = + OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame>::new(); // Set the operator fee scalar & constant to non-zero values in the L1 block info. evm.ctx().chain.operator_fee_scalar = Some(U256::from(OP_FEE_MOCK_PARAM)); From 18d0e1b1d07fb821cb97e1e086047a99e441f5de Mon Sep 17 00:00:00 2001 From: rakita Date: Wed, 18 Jun 2025 15:37:12 +0200 Subject: [PATCH 040/225] feat: remove EOF (bluealloy/revm#2644) * feat: remove EOF * remove more of eof code * fix test * remove tracer section and fn depth * docs * inline functions * clippy doc --- src/transaction/abstraction.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/transaction/abstraction.rs b/src/transaction/abstraction.rs index a0b66663e26..705d6491a7b 100644 --- a/src/transaction/abstraction.rs +++ b/src/transaction/abstraction.rs @@ -158,11 +158,6 @@ impl Transaction for OpTransaction { fn authorization_list(&self) -> impl Iterator> { self.base.authorization_list() } - - // TODO(EOF) - // fn initcodes(&self) -> &[Bytes] { - // self.base.initcodes() - // } } impl OpTxTr for OpTransaction { From 934b9a80a7f221cf10e67b9d91a7e6f528bb7f0b Mon Sep 17 00:00:00 2001 From: Wodann Date: Wed, 18 Jun 2025 17:19:27 -0500 Subject: [PATCH 041/225] feat: add fallible conversion from OpHaltReason to HaltReason (bluealloy/revm#2649) --- src/result.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/result.rs b/src/result.rs index 2542a204607..1607bc6e8f2 100644 --- a/src/result.rs +++ b/src/result.rs @@ -17,6 +17,17 @@ impl From for OpHaltReason { } } +impl TryFrom for HaltReason { + type Error = OpHaltReason; + + fn try_from(value: OpHaltReason) -> Result { + match value { + OpHaltReason::Base(reason) => Ok(reason), + OpHaltReason::FailedDeposit => Err(value), + } + } +} + #[cfg(all(test, feature = "serde"))] mod tests { use super::*; From b784e910dd0979c597e46be3812beb5c19ca049e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 19 Jun 2025 13:43:45 +0200 Subject: [PATCH 042/225] chore: release (bluealloy/revm#2641) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 14 ++++++++++++++ Cargo.toml | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7b6c286d2d..cb6b1647c06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [6.1.0](https://github.com/bluealloy/revm/compare/op-revm-v6.0.0...op-revm-v6.1.0) - 2025-06-19 + +### Added + +- add fallible conversion from OpHaltReason to HaltReason ([#2649](https://github.com/bluealloy/revm/pull/2649)) +- remove EOF ([#2644](https://github.com/bluealloy/revm/pull/2644)) +- *(precompile)* rug/gmp-based modexp ([#2596](https://github.com/bluealloy/revm/pull/2596)) +- enable P256 in Osaka ([#2601](https://github.com/bluealloy/revm/pull/2601)) + +### Other + +- re-use frame allocation ([#2636](https://github.com/bluealloy/revm/pull/2636)) +- rename `transact` methods ([#2616](https://github.com/bluealloy/revm/pull/2616)) + ## [6.0.0](https://github.com/bluealloy/revm/compare/op-revm-v5.0.1...op-revm-v6.0.0) - 2025-06-06 ### Added diff --git a/Cargo.toml b/Cargo.toml index 20ede9f2f3b..8d763eafc41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "6.0.0" +version = "6.1.0" authors.workspace = true edition.workspace = true keywords.workspace = true From 0b7f47486cb7e25704084b5c68643f888494d6be Mon Sep 17 00:00:00 2001 From: rakita Date: Thu, 19 Jun 2025 14:02:08 +0200 Subject: [PATCH 043/225] chore: bump v77 (bluealloy/revm#2651) --- CHANGELOG.md | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb6b1647c06..5fba6baba2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [6.1.0](https://github.com/bluealloy/revm/compare/op-revm-v6.0.0...op-revm-v6.1.0) - 2025-06-19 +## [7.0.0](https://github.com/bluealloy/revm/compare/op-revm-v6.0.0...op-revm-v7.0.0) - 2025-06-19 ### Added diff --git a/Cargo.toml b/Cargo.toml index 8d763eafc41..d987640e547 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "6.1.0" +version = "7.0.0" authors.workspace = true edition.workspace = true keywords.workspace = true From b8aec7b3ee478f673571a87776d318bc4b26470b Mon Sep 17 00:00:00 2001 From: rakita Date: Fri, 20 Jun 2025 19:41:22 +0200 Subject: [PATCH 044/225] fix: call stack_frame.clear() at end (bluealloy/revm#2656) --- src/handler.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/handler.rs b/src/handler.rs index 2e4b0aed71a..688c84d2dae 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -404,6 +404,7 @@ where evm.ctx().journal_mut().commit_tx(); evm.ctx().chain_mut().clear_tx_l1_cost(); evm.ctx().local_mut().clear(); + evm.frame_stack().clear(); Ok(exec_result) } @@ -473,6 +474,7 @@ where // do the cleanup evm.ctx().chain_mut().clear_tx_l1_cost(); evm.ctx().local_mut().clear(); + evm.frame_stack().clear(); output } From 085c4408f8f9a7a3bc1c9ef7c360f8c9a165b768 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 20 Jun 2025 19:50:07 +0200 Subject: [PATCH 045/225] chore: release (bluealloy/revm#2657) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 6 ++++++ Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fba6baba2e..cde06557fca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [7.0.1](https://github.com/bluealloy/revm/compare/op-revm-v7.0.0...op-revm-v7.0.1) - 2025-06-20 + +### Fixed + +- call stack_frame.clear() at end ([#2656](https://github.com/bluealloy/revm/pull/2656)) + ## [7.0.0](https://github.com/bluealloy/revm/compare/op-revm-v6.0.0...op-revm-v7.0.0) - 2025-06-19 ### Added diff --git a/Cargo.toml b/Cargo.toml index d987640e547..a0aa9949fc7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "7.0.0" +version = "7.0.1" authors.workspace = true edition.workspace = true keywords.workspace = true From 109dd642fd62396ca8a2d8c6fea8e5ef078f49ec Mon Sep 17 00:00:00 2001 From: bernard-wagner Date: Mon, 23 Jun 2025 15:42:41 +0200 Subject: [PATCH 046/225] feat: optional_eip3541 (bluealloy/revm#2661) * feat: optional_eip3541 * fmt --- Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index a0aa9949fc7..0c81fb4d291 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,12 +54,14 @@ dev = [ "memory_limit", "optional_balance_check", "optional_block_gas_limit", + "optional_eip3541", "optional_eip3607", "optional_no_base_fee", ] memory_limit = ["revm/memory_limit"] optional_balance_check = ["revm/optional_balance_check"] optional_block_gas_limit = ["revm/optional_block_gas_limit"] +optional_eip3541 = ["revm/optional_eip3541"] optional_eip3607 = ["revm/optional_eip3607"] optional_no_base_fee = ["revm/optional_no_base_fee"] From 1b7588767ad6491296fbf6ac7280c89de5708c29 Mon Sep 17 00:00:00 2001 From: rakita Date: Tue, 24 Jun 2025 14:06:46 +0200 Subject: [PATCH 047/225] refactor: use TxEnv::builder (bluealloy/revm#2652) * chore: lints for examples * feat: Use TxEnv builder * wip optx builder * builder for OpTx * compile * fix parts of it * revert runner changes * fix clippy --- src/api/default_ctx.rs | 6 +- src/fast_lz.rs | 19 +- src/handler.rs | 144 ++++++----- src/transaction/abstraction.rs | 191 ++++++++++++-- tests/integration.rs | 452 +++++++++++++++++++++++++-------- 5 files changed, 608 insertions(+), 204 deletions(-) diff --git a/src/api/default_ctx.rs b/src/api/default_ctx.rs index 5506deed8b5..1b215073791 100644 --- a/src/api/default_ctx.rs +++ b/src/api/default_ctx.rs @@ -19,7 +19,7 @@ pub trait DefaultOp { impl DefaultOp for OpContext { fn op() -> Self { Context::mainnet() - .with_tx(OpTransaction::default()) + .with_tx(OpTransaction::builder().build_fill()) .with_cfg(CfgEnv::new_with_spec(OpSpecId::BEDROCK)) .with_chain(L1BlockInfo::default()) } @@ -40,8 +40,8 @@ mod test { // convert to optimism context let mut evm = ctx.build_op_with_inspector(NoOpInspector {}); // execute - let _ = evm.transact(OpTransaction::default()); + let _ = evm.transact(OpTransaction::builder().build_fill()); // inspect - let _ = evm.inspect_one_tx(OpTransaction::default()); + let _ = evm.inspect_one_tx(OpTransaction::builder().build_fill()); } } diff --git a/src/fast_lz.rs b/src/fast_lz.rs index bb8ccc17a33..a1a262a079b 100644 --- a/src/fast_lz.rs +++ b/src/fast_lz.rs @@ -159,6 +159,8 @@ mod tests { // This bytecode and ABI is for a contract, which wraps the LibZip library for easier fuzz testing. // The source of this contract is here: https://github.com/danyalprout/fastlz/blob/main/src/FastLz.sol#L6-L10 + use revm::context::TxEnv; + use crate::OpTransaction; sol! { interface FastLz { @@ -174,13 +176,16 @@ mod tests { .with_db(BenchmarkDB::new_bytecode(contract_bytecode.clone())) .build_op(); - let mut tx = OpTransaction::default(); - - tx.base.caller = EEADDRESS; - tx.base.kind = TxKind::Call(FFADDRESS); - tx.base.data = FastLz::fastLzCall::new((input,)).abi_encode().into(); - tx.base.gas_limit = 3_000_000; - tx.enveloped_tx = Some(Bytes::default()); + let tx = OpTransaction::builder() + .base( + TxEnv::builder() + .caller(EEADDRESS) + .kind(TxKind::Call(FFADDRESS)) + .data(FastLz::fastLzCall::new((input,)).abi_encode().into()) + .gas_limit(3_000_000), + ) + .enveloped_tx(Some(Bytes::default())) + .build_fill(); let result = evm.transact_one(tx).unwrap(); diff --git a/src/handler.rs b/src/handler.rs index 688c84d2dae..248be8c6e63 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -501,11 +501,11 @@ mod tests { BASE_FEE_SCALAR_OFFSET, ECOTONE_L1_BLOB_BASE_FEE_SLOT, ECOTONE_L1_FEE_SCALARS_SLOT, L1_BASE_FEE_SLOT, L1_BLOCK_CONTRACT, OPERATOR_FEE_SCALARS_SLOT, }, - DefaultOp, OpBuilder, + DefaultOp, OpBuilder, OpTransaction, }; use alloy_primitives::uint; use revm::{ - context::{BlockEnv, Context, TransactionType}, + context::{BlockEnv, Context, TxEnv}, context_interface::result::InvalidTransaction, database::InMemoryDB, database_interface::EmptyDB, @@ -547,10 +547,11 @@ mod tests { #[test] fn test_revert_gas() { let ctx = Context::op() - .modify_tx_chained(|tx| { - tx.base.gas_limit = 100; - tx.enveloped_tx = None; - }) + .with_tx( + OpTransaction::builder() + .base(TxEnv::builder().gas_limit(100)) + .build_fill(), + ) .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::BEDROCK); let gas = call_last_frame_return(ctx, InstructionResult::Revert, Gas::new(90)); @@ -562,11 +563,11 @@ mod tests { #[test] fn test_consume_gas() { let ctx = Context::op() - .modify_tx_chained(|tx| { - tx.base.gas_limit = 100; - tx.deposit.source_hash = B256::ZERO; - tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE; - }) + .with_tx( + OpTransaction::builder() + .base(TxEnv::builder().gas_limit(100)) + .build_fill(), + ) .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); let gas = call_last_frame_return(ctx, InstructionResult::Stop, Gas::new(90)); @@ -578,11 +579,12 @@ mod tests { #[test] fn test_consume_gas_with_refund() { let ctx = Context::op() - .modify_tx_chained(|tx| { - tx.base.gas_limit = 100; - tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE; - tx.deposit.source_hash = B256::ZERO; - }) + .with_tx( + OpTransaction::builder() + .base(TxEnv::builder().gas_limit(100)) + .source_hash(B256::from([1u8; 32])) + .build_fill(), + ) .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); let mut ret_gas = Gas::new(90); @@ -602,11 +604,12 @@ mod tests { #[test] fn test_consume_gas_deposit_tx() { let ctx = Context::op() - .modify_tx_chained(|tx| { - tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE; - tx.base.gas_limit = 100; - tx.deposit.source_hash = B256::ZERO; - }) + .with_tx( + OpTransaction::builder() + .base(TxEnv::builder().gas_limit(100)) + .source_hash(B256::from([1u8; 32])) + .build_fill(), + ) .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::BEDROCK); let gas = call_last_frame_return(ctx, InstructionResult::Stop, Gas::new(90)); assert_eq!(gas.remaining(), 0); @@ -617,12 +620,13 @@ mod tests { #[test] fn test_consume_gas_sys_deposit_tx() { let ctx = Context::op() - .modify_tx_chained(|tx| { - tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE; - tx.base.gas_limit = 100; - tx.deposit.source_hash = B256::ZERO; - tx.deposit.is_system_transaction = true; - }) + .with_tx( + OpTransaction::builder() + .base(TxEnv::builder().gas_limit(100)) + .source_hash(B256::from([1u8; 32])) + .is_system_transaction() + .build_fill(), + ) .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::BEDROCK); let gas = call_last_frame_return(ctx, InstructionResult::Stop, Gas::new(90)); assert_eq!(gas.remaining(), 100); @@ -652,8 +656,7 @@ mod tests { }) .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); ctx.modify_tx(|tx| { - tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE; - tx.deposit.source_hash = B256::ZERO; + tx.deposit.source_hash = B256::from([1u8; 32]); tx.deposit.mint = Some(10); }); @@ -677,7 +680,7 @@ mod tests { db.insert_account_info( caller, AccountInfo { - balance: U256::from(1000), + balance: U256::from(1058), // Increased to cover L1 fees (1048) + base fees ..Default::default() }, ); @@ -690,13 +693,14 @@ mod tests { ..Default::default() }) .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH) - .modify_tx_chained(|tx| { - tx.base.gas_limit = 100; - tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE; - tx.deposit.mint = Some(10); - tx.enveloped_tx = Some(bytes!("FACADE")); - tx.deposit.source_hash = B256::ZERO; - }); + .with_tx( + OpTransaction::builder() + .base(TxEnv::builder().gas_limit(100)) + .enveloped_tx(Some(bytes!("FACADE"))) + .source_hash(B256::ZERO) + .build() + .unwrap(), + ); let mut evm = ctx.build_op(); @@ -708,7 +712,7 @@ mod tests { // Check the account balance is updated. let account = evm.ctx().journal_mut().load_account(caller).unwrap(); - assert_eq!(account.info.balance, U256::from(1010)); + assert_eq!(account.info.balance, U256::from(10)); // 1058 - 1048 = 10 } #[test] @@ -810,11 +814,14 @@ mod tests { ..Default::default() }) .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH) - .modify_tx_chained(|tx| { - tx.base.gas_limit = 100; - tx.deposit.source_hash = B256::ZERO; - tx.enveloped_tx = Some(bytes!("FACADE")); - }); + .with_tx( + OpTransaction::builder() + .base(TxEnv::builder().gas_limit(100)) + .source_hash(B256::ZERO) + .enveloped_tx(Some(bytes!("FACADE"))) + .build() + .unwrap(), + ); let mut evm = ctx.build_op(); let handler = @@ -849,10 +856,12 @@ mod tests { ..Default::default() }) .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS) - .modify_tx_chained(|tx| { - tx.base.gas_limit = 10; - tx.enveloped_tx = Some(bytes!("FACADE")); - }); + .with_tx( + OpTransaction::builder() + .base(TxEnv::builder().gas_limit(10)) + .enveloped_tx(Some(bytes!("FACADE"))) + .build_fill(), + ); let mut evm = ctx.build_op(); let handler = @@ -916,7 +925,7 @@ mod tests { // mark the tx as a system transaction. let ctx = Context::op() .modify_tx_chained(|tx| { - tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE; + tx.deposit.source_hash = B256::from([1u8; 32]); tx.deposit.is_system_transaction = true; }) .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); @@ -943,8 +952,7 @@ mod tests { // Set source hash. let ctx = Context::op() .modify_tx_chained(|tx| { - tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE; - tx.deposit.source_hash = B256::ZERO; + tx.deposit.source_hash = B256::from([1u8; 32]); }) .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); @@ -960,8 +968,7 @@ mod tests { // Set source hash. let ctx = Context::op() .modify_tx_chained(|tx| { - tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE; - tx.deposit.source_hash = B256::ZERO; + tx.deposit.source_hash = B256::from([1u8; 32]); }) .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); @@ -977,7 +984,8 @@ mod tests { fn test_halted_deposit_tx_post_regolith() { let ctx = Context::op() .modify_tx_chained(|tx| { - tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE; + // Set up as deposit transaction by having a deposit with source_hash + tx.deposit.source_hash = B256::from([1u8; 32]); }) .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); @@ -1012,16 +1020,26 @@ mod tests { const OP_FEE_MOCK_PARAM: u128 = 0xFFFF; let ctx = Context::op() - .modify_tx_chained(|tx| { - tx.base.tx_type = if is_deposit { - DEPOSIT_TRANSACTION_TYPE - } else { - TransactionType::Eip1559 as u8 - }; - tx.base.gas_price = GAS_PRICE; - tx.base.gas_priority_fee = None; - tx.base.caller = SENDER; - }) + .with_tx( + OpTransaction::builder() + .base( + TxEnv::builder() + .gas_price(GAS_PRICE) + .gas_priority_fee(None) + .caller(SENDER), + ) + .enveloped_tx(if is_deposit { + None + } else { + Some(bytes!("FACADE")) + }) + .source_hash(if is_deposit { + B256::from([1u8; 32]) + } else { + B256::ZERO + }) + .build_fill(), + ) .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); let mut evm = ctx.build_op(); diff --git a/src/transaction/abstraction.rs b/src/transaction/abstraction.rs index 705d6491a7b..fcbf8d294b1 100644 --- a/src/transaction/abstraction.rs +++ b/src/transaction/abstraction.rs @@ -2,7 +2,10 @@ use super::deposit::{DepositTransactionParts, DEPOSIT_TRANSACTION_TYPE}; use auto_impl::auto_impl; use revm::{ - context::TxEnv, + context::{ + tx::{TxEnvBuildError, TxEnvBuilder}, + TxEnv, + }, context_interface::transaction::Transaction, handler::SystemCallTx, primitives::{Address, Bytes, TxKind, B256, U256}, @@ -35,15 +38,21 @@ pub trait OpTxTr: Transaction { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct OpTransaction { /// Base transaction fields. - pub base: T, + pub(crate) base: T, /// An enveloped EIP-2718 typed transaction /// /// This is used to compute the L1 tx cost using the L1 block info, as /// opposed to requiring downstream apps to compute the cost /// externally. - pub enveloped_tx: Option, + pub(crate) enveloped_tx: Option, /// Deposit transaction parts. - pub deposit: DepositTransactionParts, + pub(crate) deposit: DepositTransactionParts, +} + +impl AsRef for OpTransaction { + fn as_ref(&self) -> &T { + &self.base + } } impl OpTransaction { @@ -57,6 +66,13 @@ impl OpTransaction { } } +impl OpTransaction { + /// Create a new Optimism transaction. + pub fn builder() -> OpTransactionBuilder { + OpTransactionBuilder::new() + } +} + impl Default for OpTransaction { fn default() -> Self { Self { @@ -92,7 +108,12 @@ impl Transaction for OpTransaction { T: 'a; fn tx_type(&self) -> u8 { - self.base.tx_type() + // If this is a deposit transaction (has source_hash set), return deposit type + if self.deposit.source_hash != B256::ZERO { + DEPOSIT_TRANSACTION_TYPE + } else { + self.base.tx_type() + } } fn caller(&self) -> Address { @@ -148,6 +169,10 @@ impl Transaction for OpTransaction { } fn effective_gas_price(&self, base_fee: u128) -> u128 { + // Deposit transactions use gas_price directly + if self.tx_type() == DEPOSIT_TRANSACTION_TYPE { + return self.gas_price(); + } self.base.effective_gas_price(base_fee) } @@ -181,37 +206,149 @@ impl OpTxTr for OpTransaction { } } +/// Builder for constructing [`OpTransaction`] instances +#[derive(Default, Debug)] +pub struct OpTransactionBuilder { + base: TxEnvBuilder, + enveloped_tx: Option, + deposit: DepositTransactionParts, +} + +impl OpTransactionBuilder { + /// Create a new builder with default values + pub fn new() -> Self { + Self { + base: TxEnvBuilder::new(), + enveloped_tx: None, + deposit: DepositTransactionParts::default(), + } + } + + /// Set the base transaction builder based for TxEnvBuilder. + pub fn base(mut self, base: TxEnvBuilder) -> Self { + self.base = base; + self + } + + /// Set the enveloped transaction bytes. + pub fn enveloped_tx(mut self, enveloped_tx: Option) -> Self { + self.enveloped_tx = enveloped_tx; + self + } + + /// Set the source hash of the deposit transaction. + pub fn source_hash(mut self, source_hash: B256) -> Self { + self.deposit.source_hash = source_hash; + self + } + + /// Set the mint of the deposit transaction. + pub fn mint(mut self, mint: u128) -> Self { + self.deposit.mint = Some(mint); + self + } + + /// Set the deposit transaction to be a system transaction. + pub fn is_system_transaction(mut self) -> Self { + self.deposit.is_system_transaction = true; + self + } + + /// Set the deposit transaction to not be a system transaction. + pub fn not_system_transaction(mut self) -> Self { + self.deposit.is_system_transaction = false; + self + } + + /// Set the deposit transaction to be a deposit transaction. + pub fn is_deposit_tx(mut self) -> Self { + self.base = self.base.tx_type(Some(DEPOSIT_TRANSACTION_TYPE)); + self + } + + /// Build the [`OpTransaction`] with default values for missing fields. + /// + /// This is useful for testing and debugging where it is not necessary to + /// have full [`OpTransaction`] instance. + pub fn build_fill(mut self) -> OpTransaction { + let base = self.base.build_fill(); + if base.tx_type() != DEPOSIT_TRANSACTION_TYPE { + self.enveloped_tx = Some(vec![0x00].into()); + } + + OpTransaction { + base, + enveloped_tx: self.enveloped_tx, + deposit: self.deposit, + } + } + + /// Build the [`OpTransaction`] instance, return error if the transaction is not valid. + /// + pub fn build(self) -> Result, OpBuildError> { + let base = self.base.build()?; + // Check if this will be a deposit transaction based on source_hash + let is_deposit = self.deposit.source_hash != B256::ZERO; + if !is_deposit && self.enveloped_tx.is_none() { + return Err(OpBuildError::MissingEnvelopedTxBytes); + } + + Ok(OpTransaction { + base, + enveloped_tx: self.enveloped_tx, + deposit: self.deposit, + }) + } +} + +/// Error type for building [`TxEnv`] +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum OpBuildError { + /// Base transaction build error + Base(TxEnvBuildError), + /// Missing enveloped transaction bytes + MissingEnvelopedTxBytes, +} + +impl From for OpBuildError { + fn from(error: TxEnvBuildError) -> Self { + OpBuildError::Base(error) + } +} + #[cfg(test)] mod tests { - use crate::transaction::deposit::DEPOSIT_TRANSACTION_TYPE; - use super::*; - use revm::primitives::{Address, B256}; + use revm::{ + context_interface::Transaction, + primitives::{Address, B256}, + }; #[test] fn test_deposit_transaction_fields() { - let op_tx = OpTransaction { - base: TxEnv { - tx_type: DEPOSIT_TRANSACTION_TYPE, - gas_limit: 10, - gas_price: 100, - gas_priority_fee: Some(5), - ..Default::default() - }, - enveloped_tx: None, - deposit: DepositTransactionParts { - is_system_transaction: false, - mint: Some(0u128), - source_hash: B256::default(), - }, - }; - // Verify transaction type - assert_eq!(op_tx.tx_type(), DEPOSIT_TRANSACTION_TYPE); + let base_tx = TxEnv::builder() + .gas_limit(10) + .gas_price(100) + .gas_priority_fee(Some(5)) + .build() + .unwrap(); + + let op_tx = OpTransaction::builder() + .base(base_tx.modify()) + .enveloped_tx(None) + .not_system_transaction() + .mint(0u128) + .source_hash(B256::from([1u8; 32])) + .build() + .unwrap(); + // Verify transaction type (deposit transactions should have tx_type based on OpSpecId) + // The tx_type is derived from the transaction structure, not set manually // Verify common fields access assert_eq!(op_tx.gas_limit(), 10); assert_eq!(op_tx.kind(), revm::primitives::TxKind::Call(Address::ZERO)); - // Verify gas related calculations - assert_eq!(op_tx.effective_gas_price(90), 95); + // Verify gas related calculations - deposit transactions use gas_price for effective gas price + assert_eq!(op_tx.effective_gas_price(90), 100); assert_eq!(op_tx.max_fee_per_gas(), 100); } } diff --git a/tests/integration.rs b/tests/integration.rs index 95833a21251..4cb184d421a 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -3,8 +3,7 @@ mod common; use common::compare_or_save_testdata; use op_revm::{ - precompiles::bn128_pair::GRANITE_MAX_INPUT_SIZE, - transaction::deposit::DEPOSIT_TRANSACTION_TYPE, DefaultOp, L1BlockInfo, OpBuilder, + precompiles::bn128_pair::GRANITE_MAX_INPUT_SIZE, DefaultOp, L1BlockInfo, OpBuilder, OpHaltReason, OpSpecId, OpTransaction, }; use revm::{ @@ -29,11 +28,13 @@ use std::vec::Vec; #[test] fn test_deposit_tx() { let ctx = Context::op() - .modify_tx_chained(|tx| { - tx.enveloped_tx = None; - tx.deposit.mint = Some(100); - tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE; - }) + .with_tx( + OpTransaction::builder() + .enveloped_tx(None) + .mint(100) + .source_hash(revm::primitives::B256::from([1u8; 32])) + .build_fill(), + ) .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::HOLOCENE); let mut evm = ctx.build_op(); @@ -54,13 +55,18 @@ fn test_deposit_tx() { #[test] fn test_halted_deposit_tx() { let ctx = Context::op() - .modify_tx_chained(|tx| { - tx.enveloped_tx = None; - tx.deposit.mint = Some(100); - tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE; - tx.base.caller = BENCH_CALLER; - tx.base.kind = TxKind::Call(BENCH_TARGET); - }) + .with_tx( + OpTransaction::builder() + .base( + TxEnv::builder() + .caller(BENCH_CALLER) + .kind(TxKind::Call(BENCH_TARGET)), + ) + .enveloped_tx(None) + .mint(100) + .source_hash(revm::primitives::B256::from([1u8; 32])) + .build_fill(), + ) .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::HOLOCENE) .with_db(BenchmarkDB::new_bytecode(Bytecode::new_legacy( [opcode::POP].into(), @@ -96,10 +102,15 @@ fn p256verify_test_tx( calculate_initial_tx_gas(SPEC_ID.into(), &[], false, 0, 0, 0); Context::op() - .modify_tx_chained(|tx| { - tx.base.kind = TxKind::Call(u64_to_address(secp256r1::P256VERIFY_ADDRESS)); - tx.base.gas_limit = initial_gas + secp256r1::P256VERIFY_BASE_GAS_FEE; - }) + .with_tx( + OpTransaction::builder() + .base( + TxEnv::builder() + .kind(TxKind::Call(u64_to_address(secp256r1::P256VERIFY_ADDRESS))) + .gas_limit(initial_gas + secp256r1::P256VERIFY_BASE_GAS_FEE), + ) + .build_fill(), + ) .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID) } @@ -118,7 +129,22 @@ fn test_tx_call_p256verify() { #[test] fn test_halted_tx_call_p256verify() { - let ctx = p256verify_test_tx().modify_tx_chained(|tx| tx.base.gas_limit -= 1); + const SPEC_ID: OpSpecId = OpSpecId::FJORD; + let InitialAndFloorGas { initial_gas, .. } = + calculate_initial_tx_gas(SPEC_ID.into(), &[], false, 0, 0, 0); + let original_gas_limit = initial_gas + secp256r1::P256VERIFY_BASE_GAS_FEE; + + let ctx = Context::op() + .with_tx( + OpTransaction::builder() + .base( + TxEnv::builder() + .kind(TxKind::Call(u64_to_address(secp256r1::P256VERIFY_ADDRESS))) + .gas_limit(original_gas_limit - 1), + ) + .build_fill(), + ) + .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -144,11 +170,16 @@ fn bn128_pair_test_tx( calculate_initial_tx_gas(spec.into(), &input[..], false, 0, 0, 0); Context::op() - .modify_tx_chained(|tx| { - tx.base.kind = TxKind::Call(bn128::pair::ADDRESS); - tx.base.data = input; - tx.base.gas_limit = initial_gas; - }) + .with_tx( + OpTransaction::builder() + .base( + TxEnv::builder() + .kind(TxKind::Call(bn128::pair::ADDRESS)) + .data(input) + .gas_limit(initial_gas), + ) + .build_fill(), + ) .modify_cfg_chained(|cfg| cfg.spec = spec) } @@ -193,10 +224,15 @@ fn test_halted_tx_call_bn128_pair_granite() { #[test] fn test_halted_tx_call_bls12_381_g1_add_out_of_gas() { let ctx = Context::op() - .modify_tx_chained(|tx| { - tx.base.kind = TxKind::Call(bls12_381_const::G1_ADD_ADDRESS); - tx.base.gas_limit = 21_000 + bls12_381_const::G1_ADD_BASE_GAS_FEE - 1; - }) + .with_tx( + OpTransaction::builder() + .base( + TxEnv::builder() + .kind(TxKind::Call(bls12_381_const::G1_ADD_ADDRESS)) + .gas_limit(21_000 + bls12_381_const::G1_ADD_BASE_GAS_FEE - 1), + ) + .build_fill(), + ) .modify_chain_chained(|l1_block| { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) @@ -225,10 +261,15 @@ fn test_halted_tx_call_bls12_381_g1_add_out_of_gas() { #[test] fn test_halted_tx_call_bls12_381_g1_add_input_wrong_size() { let ctx = Context::op() - .modify_tx_chained(|tx| { - tx.base.kind = TxKind::Call(bls12_381_const::G1_ADD_ADDRESS); - tx.base.gas_limit = 21_000 + bls12_381_const::G1_ADD_BASE_GAS_FEE; - }) + .with_tx( + OpTransaction::builder() + .base( + TxEnv::builder() + .kind(TxKind::Call(bls12_381_const::G1_ADD_ADDRESS)) + .gas_limit(21_000 + bls12_381_const::G1_ADD_BASE_GAS_FEE), + ) + .build_fill(), + ) .modify_chain_chained(|l1_block| { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) @@ -268,11 +309,16 @@ fn g1_msm_test_tx( ); Context::op() - .modify_tx_chained(|tx| { - tx.base.kind = TxKind::Call(bls12_381_const::G1_MSM_ADDRESS); - tx.base.data = input; - tx.base.gas_limit = initial_gas + gs1_msm_gas; - }) + .with_tx( + OpTransaction::builder() + .base( + TxEnv::builder() + .kind(TxKind::Call(bls12_381_const::G1_MSM_ADDRESS)) + .data(input) + .gas_limit(initial_gas + gs1_msm_gas), + ) + .build_fill(), + ) .modify_chain_chained(|l1_block| { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) @@ -282,7 +328,32 @@ fn g1_msm_test_tx( #[test] fn test_halted_tx_call_bls12_381_g1_msm_input_wrong_size() { - let ctx = g1_msm_test_tx().modify_tx_chained(|tx| tx.base.data = tx.base.data.slice(1..)); + const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; + let input = Bytes::from([1; bls12_381_const::G1_MSM_INPUT_LENGTH]); + let InitialAndFloorGas { initial_gas, .. } = + calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); + let gs1_msm_gas = bls12_381_utils::msm_required_gas( + 1, + &bls12_381_const::DISCOUNT_TABLE_G1_MSM, + bls12_381_const::G1_MSM_BASE_GAS_FEE, + ); + + let ctx = Context::op() + .with_tx( + OpTransaction::builder() + .base( + TxEnv::builder() + .kind(TxKind::Call(bls12_381_const::G1_MSM_ADDRESS)) + .data(input.slice(1..)) + .gas_limit(initial_gas + gs1_msm_gas), + ) + .build_fill(), + ) + .modify_chain_chained(|l1_block| { + l1_block.operator_fee_constant = Some(U256::ZERO); + l1_block.operator_fee_scalar = Some(U256::ZERO) + }) + .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -304,7 +375,32 @@ fn test_halted_tx_call_bls12_381_g1_msm_input_wrong_size() { #[test] fn test_halted_tx_call_bls12_381_g1_msm_out_of_gas() { - let ctx = g1_msm_test_tx().modify_tx_chained(|tx| tx.base.gas_limit -= 1); + const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; + let input = Bytes::from([1; bls12_381_const::G1_MSM_INPUT_LENGTH]); + let InitialAndFloorGas { initial_gas, .. } = + calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); + let gs1_msm_gas = bls12_381_utils::msm_required_gas( + 1, + &bls12_381_const::DISCOUNT_TABLE_G1_MSM, + bls12_381_const::G1_MSM_BASE_GAS_FEE, + ); + + let ctx = Context::op() + .with_tx( + OpTransaction::builder() + .base( + TxEnv::builder() + .kind(TxKind::Call(bls12_381_const::G1_MSM_ADDRESS)) + .data(input) + .gas_limit(initial_gas + gs1_msm_gas - 1), + ) + .build_fill(), + ) + .modify_chain_chained(|l1_block| { + l1_block.operator_fee_constant = Some(U256::ZERO); + l1_block.operator_fee_scalar = Some(U256::ZERO) + }) + .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -349,10 +445,15 @@ fn test_halted_tx_call_bls12_381_g1_msm_wrong_input_layout() { #[test] fn test_halted_tx_call_bls12_381_g2_add_out_of_gas() { let ctx = Context::op() - .modify_tx_chained(|tx| { - tx.base.kind = TxKind::Call(bls12_381_const::G2_ADD_ADDRESS); - tx.base.gas_limit = 21_000 + bls12_381_const::G2_ADD_BASE_GAS_FEE - 1; - }) + .with_tx( + OpTransaction::builder() + .base( + TxEnv::builder() + .kind(TxKind::Call(bls12_381_const::G2_ADD_ADDRESS)) + .gas_limit(21_000 + bls12_381_const::G2_ADD_BASE_GAS_FEE - 1), + ) + .build_fill(), + ) .modify_chain_chained(|l1_block| { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) @@ -381,10 +482,15 @@ fn test_halted_tx_call_bls12_381_g2_add_out_of_gas() { #[test] fn test_halted_tx_call_bls12_381_g2_add_input_wrong_size() { let ctx = Context::op() - .modify_tx_chained(|tx| { - tx.base.kind = TxKind::Call(bls12_381_const::G2_ADD_ADDRESS); - tx.base.gas_limit = 21_000 + bls12_381_const::G2_ADD_BASE_GAS_FEE; - }) + .with_tx( + OpTransaction::builder() + .base( + TxEnv::builder() + .kind(TxKind::Call(bls12_381_const::G2_ADD_ADDRESS)) + .gas_limit(21_000 + bls12_381_const::G2_ADD_BASE_GAS_FEE), + ) + .build_fill(), + ) .modify_chain_chained(|l1_block| { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) @@ -425,11 +531,16 @@ fn g2_msm_test_tx( ); Context::op() - .modify_tx_chained(|tx| { - tx.base.kind = TxKind::Call(bls12_381_const::G2_MSM_ADDRESS); - tx.base.data = input; - tx.base.gas_limit = initial_gas + gs2_msm_gas; - }) + .with_tx( + OpTransaction::builder() + .base( + TxEnv::builder() + .kind(TxKind::Call(bls12_381_const::G2_MSM_ADDRESS)) + .data(input) + .gas_limit(initial_gas + gs2_msm_gas), + ) + .build_fill(), + ) .modify_chain_chained(|l1_block| { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) @@ -439,7 +550,32 @@ fn g2_msm_test_tx( #[test] fn test_halted_tx_call_bls12_381_g2_msm_input_wrong_size() { - let ctx = g2_msm_test_tx().modify_tx_chained(|tx| tx.base.data = tx.base.data.slice(1..)); + const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; + let input = Bytes::from([1; bls12_381_const::G2_MSM_INPUT_LENGTH]); + let InitialAndFloorGas { initial_gas, .. } = + calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); + let gs2_msm_gas = bls12_381_utils::msm_required_gas( + 1, + &bls12_381_const::DISCOUNT_TABLE_G2_MSM, + bls12_381_const::G2_MSM_BASE_GAS_FEE, + ); + + let ctx = Context::op() + .with_tx( + OpTransaction::builder() + .base( + TxEnv::builder() + .kind(TxKind::Call(bls12_381_const::G2_MSM_ADDRESS)) + .data(input.slice(1..)) + .gas_limit(initial_gas + gs2_msm_gas), + ) + .build_fill(), + ) + .modify_chain_chained(|l1_block| { + l1_block.operator_fee_constant = Some(U256::ZERO); + l1_block.operator_fee_scalar = Some(U256::ZERO) + }) + .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -461,7 +597,32 @@ fn test_halted_tx_call_bls12_381_g2_msm_input_wrong_size() { #[test] fn test_halted_tx_call_bls12_381_g2_msm_out_of_gas() { - let ctx = g2_msm_test_tx().modify_tx_chained(|tx| tx.base.gas_limit -= 1); + const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; + let input = Bytes::from([1; bls12_381_const::G2_MSM_INPUT_LENGTH]); + let InitialAndFloorGas { initial_gas, .. } = + calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); + let gs2_msm_gas = bls12_381_utils::msm_required_gas( + 1, + &bls12_381_const::DISCOUNT_TABLE_G2_MSM, + bls12_381_const::G2_MSM_BASE_GAS_FEE, + ); + + let ctx = Context::op() + .with_tx( + OpTransaction::builder() + .base( + TxEnv::builder() + .kind(TxKind::Call(bls12_381_const::G2_MSM_ADDRESS)) + .data(input) + .gas_limit(initial_gas + gs2_msm_gas - 1), + ) + .build_fill(), + ) + .modify_chain_chained(|l1_block| { + l1_block.operator_fee_constant = Some(U256::ZERO); + l1_block.operator_fee_scalar = Some(U256::ZERO) + }) + .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -516,11 +677,16 @@ fn bl12_381_pairing_test_tx( bls12_381_const::PAIRING_MULTIPLIER_BASE + bls12_381_const::PAIRING_OFFSET_BASE; Context::op() - .modify_tx_chained(|tx| { - tx.base.kind = TxKind::Call(bls12_381_const::PAIRING_ADDRESS); - tx.base.data = input; - tx.base.gas_limit = initial_gas + pairing_gas; - }) + .with_tx( + OpTransaction::builder() + .base( + TxEnv::builder() + .kind(TxKind::Call(bls12_381_const::PAIRING_ADDRESS)) + .data(input) + .gas_limit(initial_gas + pairing_gas), + ) + .build_fill(), + ) .modify_chain_chained(|l1_block| { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) @@ -530,8 +696,29 @@ fn bl12_381_pairing_test_tx( #[test] fn test_halted_tx_call_bls12_381_pairing_input_wrong_size() { - let ctx = - bl12_381_pairing_test_tx().modify_tx_chained(|tx| tx.base.data = tx.base.data.slice(1..)); + const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; + let input = Bytes::from([1; bls12_381_const::PAIRING_INPUT_LENGTH]); + let InitialAndFloorGas { initial_gas, .. } = + calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); + let pairing_gas: u64 = + bls12_381_const::PAIRING_MULTIPLIER_BASE + bls12_381_const::PAIRING_OFFSET_BASE; + + let ctx = Context::op() + .with_tx( + OpTransaction::builder() + .base( + TxEnv::builder() + .kind(TxKind::Call(bls12_381_const::PAIRING_ADDRESS)) + .data(input.slice(1..)) + .gas_limit(initial_gas + pairing_gas), + ) + .build_fill(), + ) + .modify_chain_chained(|l1_block| { + l1_block.operator_fee_constant = Some(U256::ZERO); + l1_block.operator_fee_scalar = Some(U256::ZERO) + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -553,7 +740,29 @@ fn test_halted_tx_call_bls12_381_pairing_input_wrong_size() { #[test] fn test_halted_tx_call_bls12_381_pairing_out_of_gas() { - let ctx = bl12_381_pairing_test_tx().modify_tx_chained(|tx| tx.base.gas_limit -= 1); + const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; + let input = Bytes::from([1; bls12_381_const::PAIRING_INPUT_LENGTH]); + let InitialAndFloorGas { initial_gas, .. } = + calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); + let pairing_gas: u64 = + bls12_381_const::PAIRING_MULTIPLIER_BASE + bls12_381_const::PAIRING_OFFSET_BASE; + + let ctx = Context::op() + .with_tx( + OpTransaction::builder() + .base( + TxEnv::builder() + .kind(TxKind::Call(bls12_381_const::PAIRING_ADDRESS)) + .data(input) + .gas_limit(initial_gas + pairing_gas - 1), + ) + .build_fill(), + ) + .modify_chain_chained(|l1_block| { + l1_block.operator_fee_constant = Some(U256::ZERO); + l1_block.operator_fee_scalar = Some(U256::ZERO) + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -595,31 +804,29 @@ fn test_tx_call_bls12_381_pairing_wrong_input_layout() { ); } -fn fp_to_g1_test_tx( -) -> Context, CfgEnv, EmptyDB, Journal, L1BlockInfo> -{ +#[test] +fn test_halted_tx_call_bls12_381_map_fp_to_g1_out_of_gas() { const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; - let input = Bytes::from([1; bls12_381_const::PADDED_FP_LENGTH]); let InitialAndFloorGas { initial_gas, .. } = calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); - Context::op() - .modify_tx_chained(|tx| { - tx.base.kind = TxKind::Call(bls12_381_const::MAP_FP_TO_G1_ADDRESS); - tx.base.data = input; - tx.base.gas_limit = initial_gas + bls12_381_const::MAP_FP_TO_G1_BASE_GAS_FEE; - }) + let ctx = Context::op() + .with_tx( + OpTransaction::builder() + .base( + TxEnv::builder() + .kind(TxKind::Call(bls12_381_const::MAP_FP_TO_G1_ADDRESS)) + .data(input) + .gas_limit(initial_gas + bls12_381_const::MAP_FP_TO_G1_BASE_GAS_FEE - 1), + ) + .build_fill(), + ) .modify_chain_chained(|l1_block| { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID) -} - -#[test] -fn test_halted_tx_call_bls12_381_map_fp_to_g1_out_of_gas() { - let ctx = fp_to_g1_test_tx().modify_tx_chained(|tx| tx.base.gas_limit -= 1); + .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -641,7 +848,27 @@ fn test_halted_tx_call_bls12_381_map_fp_to_g1_out_of_gas() { #[test] fn test_halted_tx_call_bls12_381_map_fp_to_g1_input_wrong_size() { - let ctx = fp_to_g1_test_tx().modify_tx_chained(|tx| tx.base.data = tx.base.data.slice(1..)); + const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; + let input = Bytes::from([1; bls12_381_const::PADDED_FP_LENGTH]); + let InitialAndFloorGas { initial_gas, .. } = + calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); + + let ctx = Context::op() + .with_tx( + OpTransaction::builder() + .base( + TxEnv::builder() + .kind(TxKind::Call(bls12_381_const::MAP_FP_TO_G1_ADDRESS)) + .data(input.slice(1..)) + .gas_limit(initial_gas + bls12_381_const::MAP_FP_TO_G1_BASE_GAS_FEE), + ) + .build_fill(), + ) + .modify_chain_chained(|l1_block| { + l1_block.operator_fee_constant = Some(U256::ZERO); + l1_block.operator_fee_scalar = Some(U256::ZERO) + }) + .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -661,31 +888,29 @@ fn test_halted_tx_call_bls12_381_map_fp_to_g1_input_wrong_size() { ); } -fn fp2_to_g2_test_tx( -) -> Context, CfgEnv, EmptyDB, Journal, L1BlockInfo> -{ +#[test] +fn test_halted_tx_call_bls12_381_map_fp2_to_g2_out_of_gas() { const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; - let input = Bytes::from([1; bls12_381_const::PADDED_FP2_LENGTH]); let InitialAndFloorGas { initial_gas, .. } = calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); - Context::op() - .modify_tx_chained(|tx| { - tx.base.kind = TxKind::Call(bls12_381_const::MAP_FP2_TO_G2_ADDRESS); - tx.base.data = input; - tx.base.gas_limit = initial_gas + bls12_381_const::MAP_FP2_TO_G2_BASE_GAS_FEE; - }) + let ctx = Context::op() + .with_tx( + OpTransaction::builder() + .base( + TxEnv::builder() + .kind(TxKind::Call(bls12_381_const::MAP_FP2_TO_G2_ADDRESS)) + .data(input) + .gas_limit(initial_gas + bls12_381_const::MAP_FP2_TO_G2_BASE_GAS_FEE - 1), + ) + .build_fill(), + ) .modify_chain_chained(|l1_block| { l1_block.operator_fee_constant = Some(U256::ZERO); l1_block.operator_fee_scalar = Some(U256::ZERO) }) - .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID) -} - -#[test] -fn test_halted_tx_call_bls12_381_map_fp2_to_g2_out_of_gas() { - let ctx = fp2_to_g2_test_tx().modify_tx_chained(|tx| tx.base.gas_limit -= 1); + .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -707,7 +932,27 @@ fn test_halted_tx_call_bls12_381_map_fp2_to_g2_out_of_gas() { #[test] fn test_halted_tx_call_bls12_381_map_fp2_to_g2_input_wrong_size() { - let ctx = fp2_to_g2_test_tx().modify_tx_chained(|tx| tx.base.data = tx.base.data.slice(1..)); + const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; + let input = Bytes::from([1; bls12_381_const::PADDED_FP2_LENGTH]); + let InitialAndFloorGas { initial_gas, .. } = + calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); + + let ctx = Context::op() + .with_tx( + OpTransaction::builder() + .base( + TxEnv::builder() + .kind(TxKind::Call(bls12_381_const::MAP_FP2_TO_G2_ADDRESS)) + .data(input.slice(1..)) + .gas_limit(initial_gas + bls12_381_const::MAP_FP2_TO_G2_BASE_GAS_FEE), + ) + .build_fill(), + ) + .modify_chain_chained(|l1_block| { + l1_block.operator_fee_constant = Some(U256::ZERO); + l1_block.operator_fee_scalar = Some(U256::ZERO) + }) + .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -761,14 +1006,13 @@ fn test_log_inspector() { let mut evm = ctx.build_op_with_inspector(LogInspector::default()); - let tx = OpTransaction { - base: TxEnv { - caller: BENCH_CALLER, - kind: TxKind::Call(BENCH_TARGET), - ..Default::default() - }, - ..Default::default() - }; + let tx = OpTransaction::builder() + .base( + TxEnv::builder() + .caller(BENCH_CALLER) + .kind(TxKind::Call(BENCH_TARGET)), + ) + .build_fill(); // Run evm. let output = evm.inspect_tx(tx).unwrap(); From 9ded277c6aeb9e6d534ca2c7ab135eb2ba6d013b Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Thu, 26 Jun 2025 13:42:43 +0200 Subject: [PATCH 048/225] test(op/handler): verify caller account is touched by zero value transfer (bluealloy/revm#2669) --- src/handler.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/handler.rs b/src/handler.rs index 248be8c6e63..3f238673c83 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1011,6 +1011,36 @@ mod tests { ) } + #[test] + fn test_tx_zero_value_touch_caller() { + let ctx = Context::op(); + + let mut evm = ctx.build_op(); + + assert!(!evm + .0 + .ctx + .journal_mut() + .load_account(Address::ZERO) + .unwrap() + .is_touched()); + + let handler = + OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame>::new(); + + handler + .validate_against_state_and_deduct_caller(&mut evm) + .unwrap(); + + assert!(evm + .0 + .ctx + .journal_mut() + .load_account(Address::ZERO) + .unwrap() + .is_touched()); + } + #[rstest] #[case::deposit(true)] #[case::dyn_fee(false)] From 1e0b29f036109a181563887911036edc63f2dcb5 Mon Sep 17 00:00:00 2001 From: rakita Date: Fri, 27 Jun 2025 22:24:08 +0200 Subject: [PATCH 049/225] chore: cargo clippy --fix --all (bluealloy/revm#2671) * chore: cargo +nightly clippy --fix --all * clippy second try * clippy in tests --- tests/common.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/common.rs b/tests/common.rs index d1d55599945..bca6e97ec5b 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -80,8 +80,7 @@ pub(crate) fn compare_or_save_testdata( let expected_pretty = serde_json::to_string_pretty(&expected).unwrap(); panic!( - "Value does not match testdata.\nExpected:\n{}\n\nActual:\n{}", - expected_pretty, output_json + "Value does not match testdata.\nExpected:\n{expected_pretty}\n\nActual:\n{output_json}", ); } } From 3da1617c3c728219f9ea77719ff61af9e5bd91ab Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 1 Jul 2025 00:17:39 +0200 Subject: [PATCH 050/225] chore: release (bluealloy/revm#2659) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 12 ++++++++++++ Cargo.toml | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cde06557fca..ed79b8e0c9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [8.0.0](https://github.com/bluealloy/revm/compare/op-revm-v7.0.1...op-revm-v8.0.0) - 2025-06-30 + +### Added + +- optional_eip3541 ([#2661](https://github.com/bluealloy/revm/pull/2661)) + +### Other + +- cargo clippy --fix --all ([#2671](https://github.com/bluealloy/revm/pull/2671)) +- *(op/handler)* verify caller account is touched by zero value transfer ([#2669](https://github.com/bluealloy/revm/pull/2669)) +- use TxEnv::builder ([#2652](https://github.com/bluealloy/revm/pull/2652)) + ## [7.0.1](https://github.com/bluealloy/revm/compare/op-revm-v7.0.0...op-revm-v7.0.1) - 2025-06-20 ### Fixed diff --git a/Cargo.toml b/Cargo.toml index 0c81fb4d291..3fee322061f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "7.0.1" +version = "8.0.0" authors.workspace = true edition.workspace = true keywords.workspace = true From add8c49237d7322d1c829f9b0c29ac5d6e3b8e6a Mon Sep 17 00:00:00 2001 From: rakita Date: Tue, 1 Jul 2025 02:15:59 +0200 Subject: [PATCH 051/225] fix: OpTransactionBuilder dont override envelope (bluealloy/revm#2681) * fix: OpTransactionBuilder dont override envelope * relax builder for known tx type --- src/transaction/abstraction.rs | 60 ++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/src/transaction/abstraction.rs b/src/transaction/abstraction.rs index fcbf8d294b1..65b50e3ddd9 100644 --- a/src/transaction/abstraction.rs +++ b/src/transaction/abstraction.rs @@ -38,15 +38,15 @@ pub trait OpTxTr: Transaction { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct OpTransaction { /// Base transaction fields. - pub(crate) base: T, + pub base: T, /// An enveloped EIP-2718 typed transaction /// /// This is used to compute the L1 tx cost using the L1 block info, as /// opposed to requiring downstream apps to compute the cost /// externally. - pub(crate) enveloped_tx: Option, + pub enveloped_tx: Option, /// Deposit transaction parts. - pub(crate) deposit: DepositTransactionParts, + pub deposit: DepositTransactionParts, } impl AsRef for OpTransaction { @@ -270,12 +270,30 @@ impl OpTransactionBuilder { /// /// This is useful for testing and debugging where it is not necessary to /// have full [`OpTransaction`] instance. + /// + /// If the source hash is not [`B256::ZERO`], set the transaction type to deposit and remove the enveloped transaction. pub fn build_fill(mut self) -> OpTransaction { - let base = self.base.build_fill(); - if base.tx_type() != DEPOSIT_TRANSACTION_TYPE { + let tx_type = self.base.get_tx_type(); + if tx_type.is_some() { + if tx_type == Some(DEPOSIT_TRANSACTION_TYPE) { + // source hash is required for deposit transactions + if self.deposit.source_hash == B256::ZERO { + self.deposit.source_hash = B256::from([1u8; 32]); + } + } else { + // enveloped is required for non-deposit transactions + self.enveloped_tx = Some(vec![0x00].into()); + } + } else if self.deposit.source_hash != B256::ZERO { + // if type is not set and source hash is set, set the transaction type to deposit + self.base = self.base.tx_type(Some(DEPOSIT_TRANSACTION_TYPE)); + } else if self.enveloped_tx.is_none() { + // if type is not set and source hash is not set, set the enveloped transaction to something. self.enveloped_tx = Some(vec![0x00].into()); } + let base = self.base.build_fill(); + OpTransaction { base, enveloped_tx: self.enveloped_tx, @@ -285,14 +303,28 @@ impl OpTransactionBuilder { /// Build the [`OpTransaction`] instance, return error if the transaction is not valid. /// - pub fn build(self) -> Result, OpBuildError> { - let base = self.base.build()?; - // Check if this will be a deposit transaction based on source_hash - let is_deposit = self.deposit.source_hash != B256::ZERO; - if !is_deposit && self.enveloped_tx.is_none() { + pub fn build(mut self) -> Result, OpBuildError> { + let tx_type = self.base.get_tx_type(); + if tx_type.is_some() { + if Some(DEPOSIT_TRANSACTION_TYPE) == tx_type { + // if tx type is deposit, check if source hash is set + if self.deposit.source_hash == B256::ZERO { + return Err(OpBuildError::MissingSourceHashForDeposit); + } + } else if self.enveloped_tx.is_none() { + // enveloped is required for non-deposit transactions + return Err(OpBuildError::MissingEnvelopedTxBytes); + } + } else if self.deposit.source_hash != B256::ZERO { + // if type is not set and source hash is set, set the transaction type to deposit + self.base = self.base.tx_type(Some(DEPOSIT_TRANSACTION_TYPE)); + } else if self.enveloped_tx.is_none() { + // tx is not deposit and enveloped is required return Err(OpBuildError::MissingEnvelopedTxBytes); } + let base = self.base.build()?; + Ok(OpTransaction { base, enveloped_tx: self.enveloped_tx, @@ -309,6 +341,8 @@ pub enum OpBuildError { Base(TxEnvBuildError), /// Missing enveloped transaction bytes MissingEnvelopedTxBytes, + /// Missing source hash for deposit transaction + MissingSourceHashForDeposit, } impl From for OpBuildError { @@ -330,12 +364,10 @@ mod tests { let base_tx = TxEnv::builder() .gas_limit(10) .gas_price(100) - .gas_priority_fee(Some(5)) - .build() - .unwrap(); + .gas_priority_fee(Some(5)); let op_tx = OpTransaction::builder() - .base(base_tx.modify()) + .base(base_tx) .enveloped_tx(None) .not_system_transaction() .mint(0u128) From 4a66f59221f7c4648b389f8d71d2f61f1db58d68 Mon Sep 17 00:00:00 2001 From: rakita Date: Tue, 1 Jul 2025 02:32:09 +0200 Subject: [PATCH 052/225] bump: v80 revm v27.0.1 (bluealloy/revm#2683) * bump: v80 revm v27.0.1 * main changelog --- CHANGELOG.md | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed79b8e0c9d..003b9a8e6f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [8.0.0](https://github.com/bluealloy/revm/compare/op-revm-v7.0.1...op-revm-v8.0.0) - 2025-06-30 +## [8.0.1](https://github.com/bluealloy/revm/compare/op-revm-v7.0.1...op-revm-v8.0.1) - 2025-06-30 ### Added diff --git a/Cargo.toml b/Cargo.toml index 3fee322061f..fd2e08d614a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "8.0.0" +version = "8.0.1" authors.workspace = true edition.workspace = true keywords.workspace = true From 983efdf4037f36513aebec711c003e24d9aeb9a4 Mon Sep 17 00:00:00 2001 From: rakita Date: Thu, 3 Jul 2025 15:19:41 +0200 Subject: [PATCH 053/225] bump: tag v81 revm v27.0.1 (bluealloy/revm#2689) --- CHANGELOG.md | 6 ++++++ Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 003b9a8e6f2..e1335ad20fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [8.0.2](https://github.com/bluealloy/revm/compare/op-revm-v8.0.1...op-revm-v8.0.2) - 2025-07-03 + +### Other + +- updated the following local packages: revm + ## [8.0.1](https://github.com/bluealloy/revm/compare/op-revm-v7.0.1...op-revm-v8.0.1) - 2025-06-30 ### Added diff --git a/Cargo.toml b/Cargo.toml index fd2e08d614a..eba33fba829 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "8.0.1" +version = "8.0.2" authors.workspace = true edition.workspace = true keywords.workspace = true From 7da8e7fe702343d4c9680091f9a45a0ae71e98b9 Mon Sep 17 00:00:00 2001 From: jakevin Date: Mon, 14 Jul 2025 19:46:20 +0800 Subject: [PATCH 054/225] refactor: simplify gas calculations by introducing a used() method (bluealloy/revm#2703) * refactor: simplify gas calculations by introducing a used() method * fix(gas): update used() method to prevent underflow by using saturating_sub --------- Co-authored-by: megakabi --- src/handler.rs | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index 3f238673c83..69a471f98bd 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -353,27 +353,21 @@ where }; let l1_cost = l1_block_info.calculate_tx_l1_cost(enveloped_tx, spec); - let mut operator_fee_cost = U256::ZERO; - if spec.is_enabled_in(OpSpecId::ISTHMUS) { - operator_fee_cost = l1_block_info.operator_fee_charge( - enveloped_tx, - U256::from(frame_result.gas().spent() - frame_result.gas().refunded() as u64), - ); + let operator_fee_cost = if spec.is_enabled_in(OpSpecId::ISTHMUS) { + l1_block_info.operator_fee_charge(enveloped_tx, U256::from(frame_result.gas().used())) + } else { + U256::ZERO + }; + let base_fee_amount = U256::from(basefee.saturating_mul(frame_result.gas().used() as u128)); + + // Send fees to their respective recipients + for (recipient, amount) in [ + (L1_FEE_RECIPIENT, l1_cost), + (BASE_FEE_RECIPIENT, base_fee_amount), + (OPERATOR_FEE_RECIPIENT, operator_fee_cost), + ] { + ctx.journal_mut().balance_incr(recipient, amount)?; } - // Send the L1 cost of the transaction to the L1 Fee Vault. - ctx.journal_mut().balance_incr(L1_FEE_RECIPIENT, l1_cost)?; - - // Send the base fee of the transaction to the Base Fee Vault. - ctx.journal_mut().balance_incr( - BASE_FEE_RECIPIENT, - U256::from(basefee.saturating_mul( - (frame_result.gas().spent() - frame_result.gas().refunded() as u64) as u128, - )), - )?; - - // Send the operator fee of the transaction to the coinbase. - ctx.journal_mut() - .balance_incr(OPERATOR_FEE_RECIPIENT, operator_fee_cost)?; Ok(()) } From c947f19a0d56e15170f7f2e138511f8af8dd6c25 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 14:36:23 +0200 Subject: [PATCH 055/225] chore: release (bluealloy/revm#2682) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 6 ++++++ Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1335ad20fc..4d85723ecc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [8.0.3](https://github.com/bluealloy/revm/compare/op-revm-v8.0.2...op-revm-v8.0.3) - 2025-07-14 + +### Other + +- simplify gas calculations by introducing a used() method ([#2703](https://github.com/bluealloy/revm/pull/2703)) + ## [8.0.2](https://github.com/bluealloy/revm/compare/op-revm-v8.0.1...op-revm-v8.0.2) - 2025-07-03 ### Other diff --git a/Cargo.toml b/Cargo.toml index eba33fba829..cbb058e8edc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "8.0.2" +version = "8.0.3" authors.workspace = true edition.workspace = true keywords.workspace = true From ed87c91b65673818076ea6c63f3eec66df5b001e Mon Sep 17 00:00:00 2001 From: jakevin Date: Tue, 15 Jul 2025 18:18:43 +0800 Subject: [PATCH 056/225] chore: change gas parameter to immutable reference (bluealloy/revm#2702) * chore: change gas parameter to immutable reference * chore: update gas parameter to use immutable reference in handler functions --------- Co-authored-by: megakabi --- src/handler.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handler.rs b/src/handler.rs index 69a471f98bd..911a1c8f82d 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -297,7 +297,7 @@ where .operator_fee_refund(frame_result.gas(), spec); } - reimburse_caller(evm.ctx(), frame_result.gas_mut(), additional_refund).map_err(From::from) + reimburse_caller(evm.ctx(), frame_result.gas(), additional_refund).map_err(From::from) } fn refund( From 9bcc3b7f93a3571f9c387d78c6b006b4c306bdb6 Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Mon, 21 Jul 2025 10:16:53 -0300 Subject: [PATCH 057/225] fix: gas deduction with `disable_balance_check` (bluealloy/revm#2699) * add failing test * fix gas deduction * fix minimum tx.value balance * fmt * improve test to cover tx value handling * apply fix to op-revm --- src/handler.rs | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index 911a1c8f82d..e696aba0558 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -174,11 +174,7 @@ where // Check if account has enough balance for `gas_limit * max_fee`` and value transfer. // Transfer will be done inside `*_inner` functions. - if is_balance_check_disabled { - // Make sure the caller's balance is at least the value of the transaction. - // this is not consensus critical, and it is used in testing. - new_balance = caller_account.info.balance.max(tx.value()); - } else if !is_deposit && max_balance_spending > new_balance { + if !is_deposit && max_balance_spending > new_balance && !is_balance_check_disabled { // skip max balance check for deposit transactions. // this check for deposit was skipped previously in `validate_tx_against_state` function return Err(InvalidTransaction::LackOfFundForMaxFee { @@ -186,23 +182,28 @@ where balance: Box::new(new_balance), } .into()); - } else { - let effective_balance_spending = - tx.effective_balance_spending(basefee, blob_price).expect( - "effective balance is always smaller than max balance so it can't overflow", - ); + } - // subtracting max balance spending with value that is going to be deducted later in the call. - let gas_balance_spending = effective_balance_spending - tx.value(); + let effective_balance_spending = tx + .effective_balance_spending(basefee, blob_price) + .expect("effective balance is always smaller than max balance so it can't overflow"); - // If the transaction is not a deposit transaction, subtract the L1 data fee from the - // caller's balance directly after minting the requested amount of ETH. - // Additionally deduct the operator fee from the caller's account. - // - // In case of deposit additional cost will be zero. - let op_gas_balance_spending = gas_balance_spending.saturating_add(additional_cost); + // subtracting max balance spending with value that is going to be deducted later in the call. + let gas_balance_spending = effective_balance_spending - tx.value(); - new_balance = new_balance.saturating_sub(op_gas_balance_spending); + // If the transaction is not a deposit transaction, subtract the L1 data fee from the + // caller's balance directly after minting the requested amount of ETH. + // Additionally deduct the operator fee from the caller's account. + // + // In case of deposit additional cost will be zero. + let op_gas_balance_spending = gas_balance_spending.saturating_add(additional_cost); + + new_balance = new_balance.saturating_sub(op_gas_balance_spending); + + if is_balance_check_disabled { + // Make sure the caller's balance is at least the value of the transaction. + // this is not consensus critical, and it is used in testing. + new_balance = new_balance.max(tx.value()); } // Touch account so we know it is changed. From a9f9e08f0e9a86036923228bae11adddd7cd72ac Mon Sep 17 00:00:00 2001 From: rakita Date: Mon, 21 Jul 2025 16:40:30 +0200 Subject: [PATCH 058/225] test(op-revm): test for optional balance check (bluealloy/revm#2746) --- tests/integration.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tests/integration.rs b/tests/integration.rs index 4cb184d421a..2f7f2fcf96f 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -972,6 +972,56 @@ fn test_halted_tx_call_bls12_381_map_fp2_to_g2_input_wrong_size() { ); } +#[test] +#[cfg(feature = "optional_balance_check")] +fn test_disable_balance_check() { + const RETURN_CALLER_BALANCE_BYTECODE: &[u8] = &[ + opcode::CALLER, + opcode::BALANCE, + opcode::PUSH1, + 0x00, + opcode::MSTORE, + opcode::PUSH1, + 0x20, + opcode::PUSH1, + 0x00, + opcode::RETURN, + ]; + + let mut evm = Context::op() + .modify_cfg_chained(|cfg| cfg.disable_balance_check = true) + .with_db(BenchmarkDB::new_bytecode(Bytecode::new_legacy( + RETURN_CALLER_BALANCE_BYTECODE.into(), + ))) + .build_op(); + + // Construct tx so that effective cost is more than caller balance. + let gas_price = 1; + let gas_limit = 100_000; + // Make sure value doesn't consume all balance since we want to validate that all effective + // cost is deducted. + let tx_value = BENCH_CALLER_BALANCE - U256::from(1); + + let result = evm + .transact_one( + OpTransaction::builder() + .base( + TxEnv::builder_for_bench() + .gas_price(gas_price) + .gas_limit(gas_limit) + .value(tx_value), + ) + .build_fill(), + ) + .unwrap(); + + assert!(result.is_success()); + + let returned_balance = U256::from_be_slice(result.output().unwrap().as_ref()); + let expected_balance = U256::ZERO; + assert_eq!(returned_balance, expected_balance); +} + #[derive(Default, Debug)] struct LogInspector { logs: Vec, From dbc0c35a853b2dab95ee411a1f2dd1271adf8ba0 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Tue, 22 Jul 2025 15:16:27 +0200 Subject: [PATCH 059/225] feat(osaka): update EIP-7825 constant (bluealloy/revm#2753) * feat(osaka): update EIP-7825 constant * patch test --- tests/testdata/test_halted_deposit_tx.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testdata/test_halted_deposit_tx.json b/tests/testdata/test_halted_deposit_tx.json index cca92e6161c..8aac2ceb885 100644 --- a/tests/testdata/test_halted_deposit_tx.json +++ b/tests/testdata/test_halted_deposit_tx.json @@ -2,7 +2,7 @@ "result": { "Halt": { "reason": "FailedDeposit", - "gas_used": 30000000 + "gas_used": 16777216 } }, "state": { From d51aad217041638ac6b5561c2b8bf98c5c70f241 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 23 Jul 2025 17:29:12 +0200 Subject: [PATCH 060/225] chore: release (bluealloy/revm#2771) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 15 +++++++++++++++ Cargo.toml | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d85723ecc5..bc9d37965e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [8.1.0](https://github.com/bluealloy/revm/compare/op-revm-v8.0.3...op-revm-v8.1.0) - 2025-07-23 + +### Added + +- *(osaka)* update EIP-7825 constant ([#2753](https://github.com/bluealloy/revm/pull/2753)) + +### Fixed + +- gas deduction with `disable_balance_check` ([#2699](https://github.com/bluealloy/revm/pull/2699)) + +### Other + +- *(op-revm)* test for optional balance check ([#2746](https://github.com/bluealloy/revm/pull/2746)) +- change gas parameter to immutable reference ([#2702](https://github.com/bluealloy/revm/pull/2702)) + ## [8.0.3](https://github.com/bluealloy/revm/compare/op-revm-v8.0.2...op-revm-v8.0.3) - 2025-07-14 ### Other diff --git a/Cargo.toml b/Cargo.toml index cbb058e8edc..5b8f39ae75a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "8.0.3" +version = "8.1.0" authors.workspace = true edition.workspace = true keywords.workspace = true From 6b12b57d20ca6a3e439131c9d9814ddd2a32c54c Mon Sep 17 00:00:00 2001 From: kevaundray Date: Wed, 23 Jul 2025 17:10:17 +0100 Subject: [PATCH 061/225] chore: Add dyn Crypto trait to PrecompileFn (bluealloy/revm#2772) * add trait to PrecompilesStruct and refactor * impl sha * add execute method and execute using Crypto trait * refactor tests * fmt * reduce diff * fix * fmt * add box * benchmarks * fmt * add ripemd --- src/precompiles.rs | 49 +++++++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/src/precompiles.rs b/src/precompiles.rs index d29e5667653..cef2029f943 100644 --- a/src/precompiles.rs +++ b/src/precompiles.rs @@ -146,12 +146,16 @@ pub mod bn128_pair { pub const GRANITE_MAX_INPUT_SIZE: usize = 112687; /// Bn128 pair precompile. pub const GRANITE: PrecompileWithAddress = - PrecompileWithAddress(bn128::pair::ADDRESS, |input, gas_limit| { - run_pair(input, gas_limit) + PrecompileWithAddress(bn128::pair::ADDRESS, |input, gas_limit, crypto| { + run_pair(input, gas_limit, crypto) }); /// Run the bn128 pair precompile with Optimism input limit. - pub fn run_pair(input: &[u8], gas_limit: u64) -> PrecompileResult { + pub fn run_pair( + input: &[u8], + gas_limit: u64, + crypto: &dyn precompile::Crypto, + ) -> PrecompileResult { if input.len() > GRANITE_MAX_INPUT_SIZE { return Err(PrecompileError::Bn128PairLength); } @@ -160,6 +164,7 @@ pub mod bn128_pair { bn128::pair::ISTANBUL_PAIR_PER_POINT, bn128::pair::ISTANBUL_PAIR_BASE, gas_limit, + crypto, ) } } @@ -190,33 +195,45 @@ pub mod bls12_381 { PrecompileWithAddress(PAIRING_ADDRESS, run_pair); /// Run the g1 msm precompile with Optimism input limit. - pub fn run_g1_msm(input: &[u8], gas_limit: u64) -> PrecompileResult { + pub fn run_g1_msm( + input: &[u8], + gas_limit: u64, + crypto: &dyn precompile::Crypto, + ) -> PrecompileResult { if input.len() > ISTHMUS_G1_MSM_MAX_INPUT_SIZE { return Err(PrecompileError::Other( "G1MSM input length too long for OP Stack input size limitation".to_string(), )); } - precompile::bls12_381::g1_msm::g1_msm(input, gas_limit) + precompile::bls12_381::g1_msm::g1_msm(input, gas_limit, crypto) } /// Run the g2 msm precompile with Optimism input limit. - pub fn run_g2_msm(input: &[u8], gas_limit: u64) -> PrecompileResult { + pub fn run_g2_msm( + input: &[u8], + gas_limit: u64, + crypto: &dyn precompile::Crypto, + ) -> PrecompileResult { if input.len() > ISTHMUS_G2_MSM_MAX_INPUT_SIZE { return Err(PrecompileError::Other( "G2MSM input length too long for OP Stack input size limitation".to_string(), )); } - precompile::bls12_381::g2_msm::g2_msm(input, gas_limit) + precompile::bls12_381::g2_msm::g2_msm(input, gas_limit, crypto) } /// Run the pairing precompile with Optimism input limit. - pub fn run_pair(input: &[u8], gas_limit: u64) -> PrecompileResult { + pub fn run_pair( + input: &[u8], + gas_limit: u64, + crypto: &dyn precompile::Crypto, + ) -> PrecompileResult { if input.len() > ISTHMUS_PAIRING_MAX_INPUT_SIZE { return Err(PrecompileError::Other( "Pairing input length too long for OP Stack input size limitation".to_string(), )); } - precompile::bls12_381::pairing::pairing(input, gas_limit) + precompile::bls12_381::pairing::pairing(input, gas_limit, crypto) } } @@ -255,7 +272,7 @@ mod tests { let expected = hex::decode("0000000000000000000000000000000000000000000000000000000000000001") .unwrap(); - let outcome = bn128_pair::run_pair(&input, 260_000).unwrap(); + let outcome = bn128_pair::run_pair(&input, 260_000, &precompile::DefaultCrypto).unwrap(); assert_eq!(outcome.bytes, expected); // Invalid input length @@ -268,17 +285,17 @@ mod tests { ) .unwrap(); - let res = bn128_pair::run_pair(&input, 260_000); + let res = bn128_pair::run_pair(&input, 260_000, &precompile::DefaultCrypto); assert!(matches!(res, Err(PrecompileError::Bn128PairLength))); // Valid input length shorter than 112687 let input = vec![1u8; 586 * bn128::PAIR_ELEMENT_LEN]; - let res = bn128_pair::run_pair(&input, 260_000); + let res = bn128_pair::run_pair(&input, 260_000, &precompile::DefaultCrypto); assert!(matches!(res, Err(PrecompileError::OutOfGas))); // Input length longer than 112687 let input = vec![1u8; 587 * bn128::PAIR_ELEMENT_LEN]; - let res = bn128_pair::run_pair(&input, 260_000); + let res = bn128_pair::run_pair(&input, 260_000, &precompile::DefaultCrypto); assert!(matches!(res, Err(PrecompileError::Bn128PairLength))); } @@ -320,7 +337,7 @@ mod tests { let oversized_input = vec![0u8; ISTHMUS_G1_MSM_MAX_INPUT_SIZE + 1]; let input = Bytes::from(oversized_input); - let res = run_g1_msm(&input, 260_000); + let res = run_g1_msm(&input, 260_000, &precompile::DefaultCrypto); assert!( matches!(res, Err(PrecompileError::Other(msg)) if msg.contains("input length too long")) @@ -331,7 +348,7 @@ mod tests { let oversized_input = vec![0u8; ISTHMUS_G2_MSM_MAX_INPUT_SIZE + 1]; let input = Bytes::from(oversized_input); - let res = run_g2_msm(&input, 260_000); + let res = run_g2_msm(&input, 260_000, &precompile::DefaultCrypto); assert!( matches!(res, Err(PrecompileError::Other(msg)) if msg.contains("input length too long")) @@ -342,7 +359,7 @@ mod tests { let oversized_input = vec![0u8; ISTHMUS_PAIRING_MAX_INPUT_SIZE + 1]; let input = Bytes::from(oversized_input); - let res = bls12_381::run_pair(&input, 260_000); + let res = bls12_381::run_pair(&input, 260_000, &precompile::DefaultCrypto); assert!( matches!(res, Err(PrecompileError::Other(msg)) if msg.contains("input length too long")) From 013b1d21c22b0c4c4fadd2b6865468b9938f465b Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Thu, 24 Jul 2025 14:04:25 +0200 Subject: [PATCH 062/225] chore: add OnceLock re-export with no_std support (bluealloy/revm#2787) --- Cargo.toml | 4 ---- src/precompiles.rs | 15 +++++++-------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5b8f39ae75a..c4a93b05169 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,9 +21,6 @@ workspace = true revm.workspace = true auto_impl.workspace = true -# static precompile sets. -once_cell = { workspace = true, features = ["alloc"] } - # Optional serde = { workspace = true, features = ["derive", "rc"], optional = true } @@ -41,7 +38,6 @@ std = [ "serde?/std", "revm/std", "alloy-sol-types/std", - "once_cell/std", "sha2/std", "serde_json/std", "alloy-primitives/std", diff --git a/src/precompiles.rs b/src/precompiles.rs index cef2029f943..1d38296d003 100644 --- a/src/precompiles.rs +++ b/src/precompiles.rs @@ -1,6 +1,5 @@ //! Contains Optimism specific precompiles. use crate::OpSpecId; -use once_cell::race::OnceBox; use revm::{ context::Cfg, context_interface::ContextTr, @@ -10,7 +9,7 @@ use revm::{ self, bn128, secp256r1, PrecompileError, PrecompileResult, PrecompileWithAddress, Precompiles, }, - primitives::{hardfork::SpecId, Address}, + primitives::{hardfork::SpecId, Address, OnceLock}, }; use std::boxed::Box; use std::string::String; @@ -56,29 +55,29 @@ impl OpPrecompiles { /// Returns precompiles for Fjord spec. pub fn fjord() -> &'static Precompiles { - static INSTANCE: OnceBox = OnceBox::new(); + static INSTANCE: OnceLock = OnceLock::new(); INSTANCE.get_or_init(|| { let mut precompiles = Precompiles::cancun().clone(); // RIP-7212: secp256r1 P256verify precompiles.extend([secp256r1::P256VERIFY]); - Box::new(precompiles) + precompiles }) } /// Returns precompiles for Granite spec. pub fn granite() -> &'static Precompiles { - static INSTANCE: OnceBox = OnceBox::new(); + static INSTANCE: OnceLock = OnceLock::new(); INSTANCE.get_or_init(|| { let mut precompiles = fjord().clone(); // Restrict bn256Pairing input size precompiles.extend([bn128_pair::GRANITE]); - Box::new(precompiles) + precompiles }) } /// Returns precompiles for isthumus spec. pub fn isthmus() -> &'static Precompiles { - static INSTANCE: OnceBox = OnceBox::new(); + static INSTANCE: OnceLock = OnceLock::new(); INSTANCE.get_or_init(|| { let mut precompiles = granite().clone(); // Prague bls12 precompiles @@ -89,7 +88,7 @@ pub fn isthmus() -> &'static Precompiles { bls12_381::ISTHMUS_G2_MSM, bls12_381::ISTHMUS_PAIRING, ]); - Box::new(precompiles) + precompiles }) } From 5d805b7908d363f05cf0ad2a988dffd60509cb2d Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Thu, 24 Jul 2025 16:16:37 +0200 Subject: [PATCH 063/225] chore: add rust-version and note about MSRV (bluealloy/revm#2789) Having rust-version set helps Cargo produce better error messages if the installed version is lower than the minimum specified. --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index c4a93b05169..cd359e6f6f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ keywords.workspace = true license.workspace = true repository.workspace = true readme.workspace = true +rust-version.workspace = true [package.metadata.docs.rs] all-features = true From d33fd9098aed86d6a2428bb395074bc10886d4d4 Mon Sep 17 00:00:00 2001 From: rakita Date: Fri, 25 Jul 2025 11:48:19 +0200 Subject: [PATCH 064/225] chore: reuse global crypto provide idea (bluealloy/revm#2786) * add bn128 * add rest of impls * add cfg_if * uniform reexport * refactor * nit: function names * remove refs from blake * type alias + inlines * use trait for version hash * remove Result * test dynamic dispatch * revert static call * chore: reuse global crypto provide idea * fix compilation * add default impl to Crypto trait * nit rm alloc * nits * fix lock compilation * return bool for p256 verify fn --------- Co-authored-by: Kevaundray Wedderburn --- src/precompiles.rs | 49 +++++++++++++++------------------------------- 1 file changed, 16 insertions(+), 33 deletions(-) diff --git a/src/precompiles.rs b/src/precompiles.rs index 1d38296d003..d7e2c8e4e35 100644 --- a/src/precompiles.rs +++ b/src/precompiles.rs @@ -145,16 +145,12 @@ pub mod bn128_pair { pub const GRANITE_MAX_INPUT_SIZE: usize = 112687; /// Bn128 pair precompile. pub const GRANITE: PrecompileWithAddress = - PrecompileWithAddress(bn128::pair::ADDRESS, |input, gas_limit, crypto| { - run_pair(input, gas_limit, crypto) + PrecompileWithAddress(bn128::pair::ADDRESS, |input, gas_limit| { + run_pair(input, gas_limit) }); /// Run the bn128 pair precompile with Optimism input limit. - pub fn run_pair( - input: &[u8], - gas_limit: u64, - crypto: &dyn precompile::Crypto, - ) -> PrecompileResult { + pub fn run_pair(input: &[u8], gas_limit: u64) -> PrecompileResult { if input.len() > GRANITE_MAX_INPUT_SIZE { return Err(PrecompileError::Bn128PairLength); } @@ -163,7 +159,6 @@ pub mod bn128_pair { bn128::pair::ISTANBUL_PAIR_PER_POINT, bn128::pair::ISTANBUL_PAIR_BASE, gas_limit, - crypto, ) } } @@ -194,45 +189,33 @@ pub mod bls12_381 { PrecompileWithAddress(PAIRING_ADDRESS, run_pair); /// Run the g1 msm precompile with Optimism input limit. - pub fn run_g1_msm( - input: &[u8], - gas_limit: u64, - crypto: &dyn precompile::Crypto, - ) -> PrecompileResult { + pub fn run_g1_msm(input: &[u8], gas_limit: u64) -> PrecompileResult { if input.len() > ISTHMUS_G1_MSM_MAX_INPUT_SIZE { return Err(PrecompileError::Other( "G1MSM input length too long for OP Stack input size limitation".to_string(), )); } - precompile::bls12_381::g1_msm::g1_msm(input, gas_limit, crypto) + precompile::bls12_381::g1_msm::g1_msm(input, gas_limit) } /// Run the g2 msm precompile with Optimism input limit. - pub fn run_g2_msm( - input: &[u8], - gas_limit: u64, - crypto: &dyn precompile::Crypto, - ) -> PrecompileResult { + pub fn run_g2_msm(input: &[u8], gas_limit: u64) -> PrecompileResult { if input.len() > ISTHMUS_G2_MSM_MAX_INPUT_SIZE { return Err(PrecompileError::Other( "G2MSM input length too long for OP Stack input size limitation".to_string(), )); } - precompile::bls12_381::g2_msm::g2_msm(input, gas_limit, crypto) + precompile::bls12_381::g2_msm::g2_msm(input, gas_limit) } /// Run the pairing precompile with Optimism input limit. - pub fn run_pair( - input: &[u8], - gas_limit: u64, - crypto: &dyn precompile::Crypto, - ) -> PrecompileResult { + pub fn run_pair(input: &[u8], gas_limit: u64) -> PrecompileResult { if input.len() > ISTHMUS_PAIRING_MAX_INPUT_SIZE { return Err(PrecompileError::Other( "Pairing input length too long for OP Stack input size limitation".to_string(), )); } - precompile::bls12_381::pairing::pairing(input, gas_limit, crypto) + precompile::bls12_381::pairing::pairing(input, gas_limit) } } @@ -271,7 +254,7 @@ mod tests { let expected = hex::decode("0000000000000000000000000000000000000000000000000000000000000001") .unwrap(); - let outcome = bn128_pair::run_pair(&input, 260_000, &precompile::DefaultCrypto).unwrap(); + let outcome = bn128_pair::run_pair(&input, 260_000).unwrap(); assert_eq!(outcome.bytes, expected); // Invalid input length @@ -284,17 +267,17 @@ mod tests { ) .unwrap(); - let res = bn128_pair::run_pair(&input, 260_000, &precompile::DefaultCrypto); + let res = bn128_pair::run_pair(&input, 260_000); assert!(matches!(res, Err(PrecompileError::Bn128PairLength))); // Valid input length shorter than 112687 let input = vec![1u8; 586 * bn128::PAIR_ELEMENT_LEN]; - let res = bn128_pair::run_pair(&input, 260_000, &precompile::DefaultCrypto); + let res = bn128_pair::run_pair(&input, 260_000); assert!(matches!(res, Err(PrecompileError::OutOfGas))); // Input length longer than 112687 let input = vec![1u8; 587 * bn128::PAIR_ELEMENT_LEN]; - let res = bn128_pair::run_pair(&input, 260_000, &precompile::DefaultCrypto); + let res = bn128_pair::run_pair(&input, 260_000); assert!(matches!(res, Err(PrecompileError::Bn128PairLength))); } @@ -336,7 +319,7 @@ mod tests { let oversized_input = vec![0u8; ISTHMUS_G1_MSM_MAX_INPUT_SIZE + 1]; let input = Bytes::from(oversized_input); - let res = run_g1_msm(&input, 260_000, &precompile::DefaultCrypto); + let res = run_g1_msm(&input, 260_000); assert!( matches!(res, Err(PrecompileError::Other(msg)) if msg.contains("input length too long")) @@ -347,7 +330,7 @@ mod tests { let oversized_input = vec![0u8; ISTHMUS_G2_MSM_MAX_INPUT_SIZE + 1]; let input = Bytes::from(oversized_input); - let res = run_g2_msm(&input, 260_000, &precompile::DefaultCrypto); + let res = run_g2_msm(&input, 260_000); assert!( matches!(res, Err(PrecompileError::Other(msg)) if msg.contains("input length too long")) @@ -358,7 +341,7 @@ mod tests { let oversized_input = vec![0u8; ISTHMUS_PAIRING_MAX_INPUT_SIZE + 1]; let input = Bytes::from(oversized_input); - let res = bls12_381::run_pair(&input, 260_000, &precompile::DefaultCrypto); + let res = bls12_381::run_pair(&input, 260_000); assert!( matches!(res, Err(PrecompileError::Other(msg)) if msg.contains("input length too long")) From a06ea3ae79b3b32aba9429cc884319036f28a49d Mon Sep 17 00:00:00 2001 From: rakita Date: Fri, 25 Jul 2025 12:44:28 +0200 Subject: [PATCH 065/225] fix: nonce changed is not reverted in journal if fail due to insufficient balance (bluealloy/revm#2805) --- src/handler.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index e696aba0558..4d4a6d00ec5 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -157,11 +157,6 @@ where )?; } - // Bump the nonce for calls. Nonce for CREATE will be bumped in `handle_create`. - if tx.kind().is_call() { - caller_account.info.nonce = caller_account.info.nonce.saturating_add(1); - } - let max_balance_spending = tx.max_balance_spending()?.saturating_add(additional_cost); // old balance is journaled before mint is incremented. @@ -210,6 +205,11 @@ where caller_account.mark_touch(); caller_account.info.balance = new_balance; + // Bump the nonce for calls. Nonce for CREATE will be bumped in `handle_create`. + if tx.kind().is_call() { + caller_account.info.nonce = caller_account.info.nonce.saturating_add(1); + } + // NOTE: all changes to the caller account should journaled so in case of error // we can revert the changes. journal.caller_accounting_journal_entry(tx.caller(), old_balance, tx.kind().is_call()); From f58325e0e70d807f31d05e89f385c49cae3afc85 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Mon, 28 Jul 2025 10:53:53 +0200 Subject: [PATCH 066/225] fix(op-revm): system tx not enveloped (bluealloy/revm#2807) * Fix bug, system tx not enveloped and add test * Fix lint --- src/transaction/abstraction.rs | 8 +- tests/integration.rs | 17 +++ tests/testdata/test_system_call.json | 165 +++++++++++++++++++++++++++ 3 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 tests/testdata/test_system_call.json diff --git a/src/transaction/abstraction.rs b/src/transaction/abstraction.rs index 65b50e3ddd9..2043f1ca0aa 100644 --- a/src/transaction/abstraction.rs +++ b/src/transaction/abstraction.rs @@ -89,11 +89,15 @@ impl SystemCallTx for OpTransaction { system_contract_address: Address, data: Bytes, ) -> Self { - OpTransaction::new(TX::new_system_tx_with_caller( + let mut tx = OpTransaction::new(TX::new_system_tx_with_caller( caller, system_contract_address, data, - )) + )); + + tx.enveloped_tx = Some(Bytes::default()); + + tx } } diff --git a/tests/integration.rs b/tests/integration.rs index 2f7f2fcf96f..cc7647b7f2c 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1,11 +1,13 @@ //! Integration tests for the `op-revm` crate. mod common; +use alloy_primitives::bytes; use common::compare_or_save_testdata; use op_revm::{ precompiles::bn128_pair::GRANITE_MAX_INPUT_SIZE, DefaultOp, L1BlockInfo, OpBuilder, OpHaltReason, OpSpecId, OpTransaction, }; +use revm::SystemCallEvm; use revm::{ bytecode::opcode, context::{ @@ -1072,3 +1074,18 @@ fn test_log_inspector() { compare_or_save_testdata("test_log_inspector.json", &output); } + +#[test] +fn test_system_call() { + let ctx = Context::op(); + + let mut evm = ctx.build_op(); + + evm.transact_system_call(BENCH_TARGET, bytes!("0x0001")) + .unwrap(); + + // Run evm. + let output = evm.replay().unwrap(); + + compare_or_save_testdata("test_system_call.json", &output); +} diff --git a/tests/testdata/test_system_call.json b/tests/testdata/test_system_call.json new file mode 100644 index 00000000000..c45ac37cc01 --- /dev/null +++ b/tests/testdata/test_system_call.json @@ -0,0 +1,165 @@ +{ + "result": { + "Success": { + "reason": "Stop", + "gas_used": 21020, + "gas_refunded": 0, + "logs": [], + "output": { + "Call": "0x" + } + } + }, + "state": { + "0x420000000000000000000000000000000000001b": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "transaction_id": 1, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x4200000000000000000000000000000000000019": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "transaction_id": 1, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0xfffffffffffffffffffffffffffffffffffffffe": { + "info": { + "balance": "0x0", + "nonce": 1, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "transaction_id": 1, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x0000000000000000000000000000000000000000": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "transaction_id": 1, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0xffffffffffffffffffffffffffffffffffffffff": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "transaction_id": 1, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + }, + "0x420000000000000000000000000000000000001a": { + "info": { + "balance": "0x0", + "nonce": 0, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 0, + "data": [] + } + } + } + }, + "transaction_id": 1, + "storage": {}, + "status": "Touched | LoadedAsNotExisting" + } + } +} \ No newline at end of file From 440dcc68ac048b8824c56a43b9cd1c770e259079 Mon Sep 17 00:00:00 2001 From: Pratham Agarwal <32198580+Pratham1812@users.noreply.github.com> Date: Mon, 28 Jul 2025 16:07:00 +0530 Subject: [PATCH 067/225] feat: rename bn128 to bn254 for Ethereum standard consistency (bluealloy/revm#2810) * feat: rename bn128 to bn254 for Ethereum standard consistency * fix: renamed more occurences * fix: addresed nits --- src/precompiles.rs | 46 ++++++++++++++++++++++---------------------- tests/integration.rs | 20 +++++++++---------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/precompiles.rs b/src/precompiles.rs index d7e2c8e4e35..e4e14b90828 100644 --- a/src/precompiles.rs +++ b/src/precompiles.rs @@ -6,7 +6,7 @@ use revm::{ handler::{EthPrecompiles, PrecompileProvider}, interpreter::{InputsImpl, InterpreterResult}, precompile::{ - self, bn128, secp256r1, PrecompileError, PrecompileResult, PrecompileWithAddress, + self, bn254, secp256r1, PrecompileError, PrecompileResult, PrecompileWithAddress, Precompiles, }, primitives::{hardfork::SpecId, Address, OnceLock}, @@ -69,8 +69,8 @@ pub fn granite() -> &'static Precompiles { static INSTANCE: OnceLock = OnceLock::new(); INSTANCE.get_or_init(|| { let mut precompiles = fjord().clone(); - // Restrict bn256Pairing input size - precompiles.extend([bn128_pair::GRANITE]); + // Restrict bn254Pairing input size + precompiles.extend([bn254_pair::GRANITE]); precompiles }) } @@ -137,27 +137,27 @@ impl Default for OpPrecompiles { } } -/// Bn128 pair precompile. -pub mod bn128_pair { +/// Bn254 pair precompile. +pub mod bn254_pair { use super::*; - /// Max input size for the bn128 pair precompile. + /// Max input size for the bn254 pair precompile. pub const GRANITE_MAX_INPUT_SIZE: usize = 112687; - /// Bn128 pair precompile. + /// Bn254 pair precompile. pub const GRANITE: PrecompileWithAddress = - PrecompileWithAddress(bn128::pair::ADDRESS, |input, gas_limit| { + PrecompileWithAddress(bn254::pair::ADDRESS, |input, gas_limit| { run_pair(input, gas_limit) }); - /// Run the bn128 pair precompile with Optimism input limit. + /// Run the bn254 pair precompile with Optimism input limit. pub fn run_pair(input: &[u8], gas_limit: u64) -> PrecompileResult { if input.len() > GRANITE_MAX_INPUT_SIZE { - return Err(PrecompileError::Bn128PairLength); + return Err(PrecompileError::Bn254PairLength); } - bn128::run_pair( + bn254::run_pair( input, - bn128::pair::ISTANBUL_PAIR_PER_POINT, - bn128::pair::ISTANBUL_PAIR_BASE, + bn254::pair::ISTANBUL_PAIR_PER_POINT, + bn254::pair::ISTANBUL_PAIR_BASE, gas_limit, ) } @@ -234,7 +234,7 @@ mod tests { use std::vec; #[test] - fn test_bn128_pair() { + fn test_bn254_pair() { let input = hex::decode( "\ 1c76476f4def4bb94541d57ebba1193381ffa7aa76ada664dd31c16024c43f59\ @@ -254,7 +254,7 @@ mod tests { let expected = hex::decode("0000000000000000000000000000000000000000000000000000000000000001") .unwrap(); - let outcome = bn128_pair::run_pair(&input, 260_000).unwrap(); + let outcome = bn254_pair::run_pair(&input, 260_000).unwrap(); assert_eq!(outcome.bytes, expected); // Invalid input length @@ -267,18 +267,18 @@ mod tests { ) .unwrap(); - let res = bn128_pair::run_pair(&input, 260_000); - assert!(matches!(res, Err(PrecompileError::Bn128PairLength))); + let res = bn254_pair::run_pair(&input, 260_000); + assert!(matches!(res, Err(PrecompileError::Bn254PairLength))); // Valid input length shorter than 112687 - let input = vec![1u8; 586 * bn128::PAIR_ELEMENT_LEN]; - let res = bn128_pair::run_pair(&input, 260_000); + let input = vec![1u8; 586 * bn254::PAIR_ELEMENT_LEN]; + let res = bn254_pair::run_pair(&input, 260_000); assert!(matches!(res, Err(PrecompileError::OutOfGas))); // Input length longer than 112687 - let input = vec![1u8; 587 * bn128::PAIR_ELEMENT_LEN]; - let res = bn128_pair::run_pair(&input, 260_000); - assert!(matches!(res, Err(PrecompileError::Bn128PairLength))); + let input = vec![1u8; 587 * bn254::PAIR_ELEMENT_LEN]; + let res = bn254_pair::run_pair(&input, 260_000); + assert!(matches!(res, Err(PrecompileError::Bn254PairLength))); } #[test] @@ -290,7 +290,7 @@ mod tests { #[test] fn test_cancun_precompiles_in_granite() { // granite has p256verify (fjord) - // granite has modification of cancun's bn128 pair (doesn't count as new precompile) + // granite has modification of cancun's bn254 pair (doesn't count as new precompile) assert_eq!(granite().difference(Precompiles::cancun()).len(), 1) } diff --git a/tests/integration.rs b/tests/integration.rs index cc7647b7f2c..c20993daffd 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -4,7 +4,7 @@ mod common; use alloy_primitives::bytes; use common::compare_or_save_testdata; use op_revm::{ - precompiles::bn128_pair::GRANITE_MAX_INPUT_SIZE, DefaultOp, L1BlockInfo, OpBuilder, + precompiles::bn254_pair::GRANITE_MAX_INPUT_SIZE, DefaultOp, L1BlockInfo, OpBuilder, OpHaltReason, OpSpecId, OpTransaction, }; use revm::SystemCallEvm; @@ -20,7 +20,7 @@ use revm::{ gas::{calculate_initial_tx_gas, InitialAndFloorGas}, Interpreter, InterpreterTypes, }, - precompile::{bls12_381_const, bls12_381_utils, bn128, secp256r1, u64_to_address}, + precompile::{bls12_381_const, bls12_381_utils, bn254, secp256r1, u64_to_address}, primitives::{eip7825, Address, Bytes, Log, TxKind, U256}, state::Bytecode, Context, ExecuteEvm, InspectEvm, Inspector, Journal, @@ -163,7 +163,7 @@ fn test_halted_tx_call_p256verify() { compare_or_save_testdata("test_halted_tx_call_p256verify.json", &output); } -fn bn128_pair_test_tx( +fn bn254_pair_test_tx( spec: OpSpecId, ) -> Context, CfgEnv, EmptyDB, Journal, L1BlockInfo> { @@ -176,7 +176,7 @@ fn bn128_pair_test_tx( OpTransaction::builder() .base( TxEnv::builder() - .kind(TxKind::Call(bn128::pair::ADDRESS)) + .kind(TxKind::Call(bn254::pair::ADDRESS)) .data(input) .gas_limit(initial_gas), ) @@ -186,8 +186,8 @@ fn bn128_pair_test_tx( } #[test] -fn test_halted_tx_call_bn128_pair_fjord() { - let ctx = bn128_pair_test_tx(OpSpecId::FJORD); +fn test_halted_tx_call_bn254_pair_fjord() { + let ctx = bn254_pair_test_tx(OpSpecId::FJORD); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -201,12 +201,12 @@ fn test_halted_tx_call_bn128_pair_fjord() { } )); - compare_or_save_testdata("test_halted_tx_call_bn128_pair_fjord.json", &output); + compare_or_save_testdata("test_halted_tx_call_bn254_pair_fjord.json", &output); } #[test] -fn test_halted_tx_call_bn128_pair_granite() { - let ctx = bn128_pair_test_tx(OpSpecId::GRANITE); +fn test_halted_tx_call_bn254_pair_granite() { + let ctx = bn254_pair_test_tx(OpSpecId::GRANITE); let mut evm = ctx.build_op(); let output = evm.replay().unwrap(); @@ -220,7 +220,7 @@ fn test_halted_tx_call_bn128_pair_granite() { } )); - compare_or_save_testdata("test_halted_tx_call_bn128_pair_granite.json", &output); + compare_or_save_testdata("test_halted_tx_call_bn254_pair_granite.json", &output); } #[test] From e7dbad4601a3e171bc0aa4aedf3077c7a0d0fed4 Mon Sep 17 00:00:00 2001 From: rakita Date: Mon, 28 Jul 2025 17:09:07 +0200 Subject: [PATCH 068/225] feat: Align naming of SystemCallEvm function to ExecuteEvm (bluealloy/revm#2814) --- src/api/exec.rs | 2 +- tests/integration.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/api/exec.rs b/src/api/exec.rs index a5ddec95b68..2e8f24b2329 100644 --- a/src/api/exec.rs +++ b/src/api/exec.rs @@ -127,7 +127,7 @@ where CTX: OpContextTr + ContextSetters, PRECOMPILE: PrecompileProvider, { - fn transact_system_call_with_caller( + fn system_call_one( &mut self, caller: Address, system_contract_address: Address, diff --git a/tests/integration.rs b/tests/integration.rs index c20993daffd..088bf5a8ccf 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -7,7 +7,6 @@ use op_revm::{ precompiles::bn254_pair::GRANITE_MAX_INPUT_SIZE, DefaultOp, L1BlockInfo, OpBuilder, OpHaltReason, OpSpecId, OpTransaction, }; -use revm::SystemCallEvm; use revm::{ bytecode::opcode, context::{ @@ -25,6 +24,7 @@ use revm::{ state::Bytecode, Context, ExecuteEvm, InspectEvm, Inspector, Journal, }; +use revm::{handler::system_call::SYSTEM_ADDRESS, SystemCallEvm}; use std::vec::Vec; #[test] @@ -1081,7 +1081,7 @@ fn test_system_call() { let mut evm = ctx.build_op(); - evm.transact_system_call(BENCH_TARGET, bytes!("0x0001")) + evm.system_call_one(SYSTEM_ADDRESS, BENCH_TARGET, bytes!("0x0001")) .unwrap(); // Run evm. From 3bf47afb694e58f502c1f1987fe2786d2c2bb85d Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Mon, 28 Jul 2025 17:11:19 +0200 Subject: [PATCH 069/225] Update test data for renamed tests (bluealloy/revm#2817) --- ... test_halted_tx_call_bn254_pair_fjord.json} | 18 +++++++++--------- ...est_halted_tx_call_bn254_pair_granite.json} | 18 +++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) rename tests/testdata/{test_halted_tx_call_bn128_pair_fjord.json => test_halted_tx_call_bn254_pair_fjord.json} (100%) rename tests/testdata/{test_halted_tx_call_bn128_pair_granite.json => test_halted_tx_call_bn254_pair_granite.json} (100%) diff --git a/tests/testdata/test_halted_tx_call_bn128_pair_fjord.json b/tests/testdata/test_halted_tx_call_bn254_pair_fjord.json similarity index 100% rename from tests/testdata/test_halted_tx_call_bn128_pair_fjord.json rename to tests/testdata/test_halted_tx_call_bn254_pair_fjord.json index eae5c631fa0..03f0c8e0fc8 100644 --- a/tests/testdata/test_halted_tx_call_bn128_pair_fjord.json +++ b/tests/testdata/test_halted_tx_call_bn254_pair_fjord.json @@ -10,10 +10,10 @@ } }, "state": { - "0x4200000000000000000000000000000000000019": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -35,7 +35,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001b": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", "nonce": 0, @@ -60,10 +60,10 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000000": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -85,7 +85,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000008": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", "nonce": 0, @@ -108,9 +108,9 @@ }, "transaction_id": 0, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001a": { + "0x0000000000000000000000000000000000000008": { "info": { "balance": "0x0", "nonce": 0, @@ -133,7 +133,7 @@ }, "transaction_id": 0, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" } } } \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bn128_pair_granite.json b/tests/testdata/test_halted_tx_call_bn254_pair_granite.json similarity index 100% rename from tests/testdata/test_halted_tx_call_bn128_pair_granite.json rename to tests/testdata/test_halted_tx_call_bn254_pair_granite.json index ce92b110149..f05d9a82695 100644 --- a/tests/testdata/test_halted_tx_call_bn128_pair_granite.json +++ b/tests/testdata/test_halted_tx_call_bn254_pair_granite.json @@ -8,7 +8,7 @@ } }, "state": { - "0x4200000000000000000000000000000000000019": { + "0x420000000000000000000000000000000000001a": { "info": { "balance": "0x0", "nonce": 0, @@ -33,7 +33,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001b": { + "0x0000000000000000000000000000000000000008": { "info": { "balance": "0x0", "nonce": 0, @@ -56,12 +56,12 @@ }, "transaction_id": 0, "storage": {}, - "status": "Touched | LoadedAsNotExisting" + "status": "LoadedAsNotExisting" }, - "0x420000000000000000000000000000000000001a": { + "0x0000000000000000000000000000000000000000": { "info": { "balance": "0x0", - "nonce": 0, + "nonce": 1, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -83,10 +83,10 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000000": { + "0x420000000000000000000000000000000000001b": { "info": { "balance": "0x0", - "nonce": 1, + "nonce": 0, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { "LegacyAnalyzed": { @@ -108,7 +108,7 @@ "storage": {}, "status": "Touched | LoadedAsNotExisting" }, - "0x0000000000000000000000000000000000000008": { + "0x4200000000000000000000000000000000000019": { "info": { "balance": "0x0", "nonce": 0, @@ -131,7 +131,7 @@ }, "transaction_id": 0, "storage": {}, - "status": "LoadedAsNotExisting" + "status": "Touched | LoadedAsNotExisting" } } } \ No newline at end of file From 526bb0b6896df1447982c24fa066838f8c39b764 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Mon, 28 Jul 2025 17:12:00 +0200 Subject: [PATCH 070/225] test(op-revm): Full test coverage `OpTransactionError` (bluealloy/revm#2818) * Fix typos * Cover OpTransactionError::Base in test display * fixup! Fix typos --- src/transaction/error.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/transaction/error.rs b/src/transaction/error.rs index 76169a3dd85..ba13cab90eb 100644 --- a/src/transaction/error.rs +++ b/src/transaction/error.rs @@ -18,7 +18,7 @@ pub enum OpTransactionError { /// was deprecated in the Regolith hardfork, and this error is thrown if a `Deposit` transaction /// is found with this field set to `true` after the hardfork activation. /// - /// In addition, this error is internal, and bubbles up into a [OpHaltReason::FailedDeposit][crate::OpHaltReason::FailedDeposit] error + /// In addition, this error is internal, and bubbles up into an [OpHaltReason::FailedDeposit][crate::OpHaltReason::FailedDeposit] error /// in the `revm` handler for the consumer to easily handle. This is due to a state transition /// rule on OP Stack chains where, if for any reason a deposit transaction fails, the transaction /// must still be included in the block, the sender nonce is bumped, the `mint` value persists, and @@ -26,14 +26,14 @@ pub enum OpTransactionError { /// are cause for non-inclusion, so a special [OpHaltReason][crate::OpHaltReason] variant was introduced to handle this /// case for failed deposit transactions. DepositSystemTxPostRegolith, - /// Deposit transaction haults bubble up to the global main return handler, wiping state and + /// Deposit transaction halts bubble up to the global main return handler, wiping state and /// only increasing the nonce + persisting the mint value. /// - /// This is a catch-all error for any deposit transaction that is results in a [OpHaltReason][crate::OpHaltReason] error + /// This is a catch-all error for any deposit transaction that results in an [OpHaltReason][crate::OpHaltReason] error /// post-regolith hardfork. This allows for a consumer to easily handle special cases where /// a deposit transaction fails during validation, but must still be included in the block. /// - /// In addition, this error is internal, and bubbles up into a [OpHaltReason::FailedDeposit][crate::OpHaltReason::FailedDeposit] error + /// In addition, this error is internal, and bubbles up into an [OpHaltReason::FailedDeposit][crate::OpHaltReason::FailedDeposit] error /// in the `revm` handler for the consumer to easily handle. This is due to a state transition /// rule on OP Stack chains where, if for any reason a deposit transaction fails, the transaction /// must still be included in the block, the sender nonce is bumped, the `mint` value persists, and @@ -86,6 +86,11 @@ mod test { #[test] fn test_display_op_errors() { + assert_eq!( + OpTransactionError::Base(InvalidTransaction::NonceTooHigh { tx: 2, state: 1 }) + .to_string(), + "nonce 2 too high, expected 1" + ); assert_eq!( OpTransactionError::DepositSystemTxPostRegolith.to_string(), "deposit system transactions post regolith hardfork are not supported" From 2f8d8a0098fa78e3dc072c6dabba75c67d621fcd Mon Sep 17 00:00:00 2001 From: Rez Date: Tue, 29 Jul 2025 20:01:42 +1000 Subject: [PATCH 071/225] feat: add system transaction inspection support (bluealloy/revm#2808) * att1 * cargo fmt * reduce test bloat * remove InspectSystemCallCommitEvm * fix: rename system call inspection methods to match inspect_one_tx pattern - inspect_system_call_one -> inspect_one_system_call - inspect_system_call_with_caller_one -> inspect_one_system_call_with_caller - inspect_system_call_with_inspector_one -> inspect_one_system_call_with_inspector * feat: add system call inspection support to op-revm - Implement InspectSystemCallEvm trait for OpEvm - Add inspect_one_system_call_with_caller method - Add comprehensive test for system call inspection functionality * fix: simplify system call inspection test - Remove unnecessary custom bytecode setup - Use default context for cleaner test * style: fix code formatting for CI - Format import statements in exec.rs - Fix whitespace formatting in test functions - Ensure consistent code style across files * trigger ci * Revert "trigger ci" This reverts commit fba9ceb361ea25c0564b92ed1bef992de7f6a7a3. --- src/api/exec.rs | 27 ++++++++++++++++++++++++++- tests/integration.rs | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/api/exec.rs b/src/api/exec.rs index 2e8f24b2329..204973509ee 100644 --- a/src/api/exec.rs +++ b/src/api/exec.rs @@ -13,7 +13,9 @@ use revm::{ instructions::EthInstructions, system_call::SystemCallEvm, EthFrame, Handler, PrecompileProvider, SystemCallTx, }, - inspector::{InspectCommitEvm, InspectEvm, Inspector, InspectorHandler, JournalExt}, + inspector::{ + InspectCommitEvm, InspectEvm, InspectSystemCallEvm, Inspector, InspectorHandler, JournalExt, + }, interpreter::{interpreter::EthInterpreter, InterpreterResult}, primitives::{Address, Bytes}, state::EvmState, @@ -142,3 +144,26 @@ where h.run_system_call(self) } } + +impl InspectSystemCallEvm + for OpEvm, PRECOMPILE> +where + CTX: OpContextTr + ContextSetters, + INSP: Inspector, + PRECOMPILE: PrecompileProvider, +{ + fn inspect_one_system_call_with_caller( + &mut self, + caller: Address, + system_contract_address: Address, + data: Bytes, + ) -> Result { + self.0.ctx.set_tx(CTX::Tx::new_system_tx_with_caller( + caller, + system_contract_address, + data, + )); + let mut h = OpHandler::<_, _, EthFrame>::new(); + h.inspect_run_system_call(self) + } +} diff --git a/tests/integration.rs b/tests/integration.rs index 088bf5a8ccf..52bd9a976ad 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1075,6 +1075,44 @@ fn test_log_inspector() { compare_or_save_testdata("test_log_inspector.json", &output); } +#[test] +fn test_system_call_inspection() { + use revm::InspectSystemCallEvm; + + let ctx = Context::op(); + + let mut evm = ctx.build_op_with_inspector(LogInspector::default()); + + // Test system call inspection + let result = evm + .inspect_one_system_call(BENCH_TARGET, Bytes::default()) + .unwrap(); + + // Should succeed + assert!(result.is_success()); + + // Test system call inspection with caller + let custom_caller = Address::from([0x12; 20]); + let result = evm + .inspect_one_system_call_with_caller(custom_caller, BENCH_TARGET, Bytes::default()) + .unwrap(); + + // Should also succeed + assert!(result.is_success()); + + // Test system call inspection with inspector + let result = evm + .inspect_one_system_call_with_inspector( + BENCH_TARGET, + Bytes::default(), + LogInspector::default(), + ) + .unwrap(); + + // Should succeed + assert!(result.is_success()); +} + #[test] fn test_system_call() { let ctx = Context::op(); From 6c621850978a55511966d7ae4a7207679a7a8afb Mon Sep 17 00:00:00 2001 From: rakita Date: Tue, 29 Jul 2025 12:36:35 +0200 Subject: [PATCH 072/225] feat: refactor test utils (bluealloy/revm#2813) * feat: refactor test utils * fix compilation * feat: add ee-test to unify ee tests * sort all json test fixtures * fmt * typo --- Cargo.toml | 3 +- tests/common.rs | 111 -- tests/integration.rs | 1129 ----------------- tests/testdata/template_test.json | 14 - tests/testdata/test_deposit_tx.json | 40 - tests/testdata/test_halted_deposit_tx.json | 62 - ...all_bls12_381_g1_add_input_wrong_size.json | 137 -- ...d_tx_call_bls12_381_g1_add_out_of_gas.json | 139 -- ...all_bls12_381_g1_msm_input_wrong_size.json | 137 -- ...d_tx_call_bls12_381_g1_msm_out_of_gas.json | 139 -- ...l_bls12_381_g1_msm_wrong_input_layout.json | 137 -- ...all_bls12_381_g2_add_input_wrong_size.json | 137 -- ...d_tx_call_bls12_381_g2_add_out_of_gas.json | 139 -- ...all_bls12_381_g2_msm_input_wrong_size.json | 137 -- ...d_tx_call_bls12_381_g2_msm_out_of_gas.json | 139 -- ...l_bls12_381_g2_msm_wrong_input_layout.json | 137 -- ...12_381_map_fp2_to_g2_input_wrong_size.json | 137 -- ...ll_bls12_381_map_fp2_to_g2_out_of_gas.json | 139 -- ...s12_381_map_fp_to_g1_input_wrong_size.json | 137 -- ...all_bls12_381_map_fp_to_g1_out_of_gas.json | 139 -- ...ll_bls12_381_pairing_input_wrong_size.json | 137 -- ..._tx_call_bls12_381_pairing_out_of_gas.json | 139 -- ..._bls12_381_pairing_wrong_input_layout.json | 137 -- .../test_halted_tx_call_bn254_pair_fjord.json | 139 -- ...est_halted_tx_call_bn254_pair_granite.json | 137 -- .../test_halted_tx_call_p256verify.json | 139 -- tests/testdata/test_log_inspector.json | 173 --- tests/testdata/test_system_call.json | 165 --- tests/testdata/test_tx_call_p256verify.json | 140 -- 29 files changed, 2 insertions(+), 4593 deletions(-) delete mode 100644 tests/common.rs delete mode 100644 tests/integration.rs delete mode 100644 tests/testdata/template_test.json delete mode 100644 tests/testdata/test_deposit_tx.json delete mode 100644 tests/testdata/test_halted_deposit_tx.json delete mode 100644 tests/testdata/test_halted_tx_call_bls12_381_g1_add_input_wrong_size.json delete mode 100644 tests/testdata/test_halted_tx_call_bls12_381_g1_add_out_of_gas.json delete mode 100644 tests/testdata/test_halted_tx_call_bls12_381_g1_msm_input_wrong_size.json delete mode 100644 tests/testdata/test_halted_tx_call_bls12_381_g1_msm_out_of_gas.json delete mode 100644 tests/testdata/test_halted_tx_call_bls12_381_g1_msm_wrong_input_layout.json delete mode 100644 tests/testdata/test_halted_tx_call_bls12_381_g2_add_input_wrong_size.json delete mode 100644 tests/testdata/test_halted_tx_call_bls12_381_g2_add_out_of_gas.json delete mode 100644 tests/testdata/test_halted_tx_call_bls12_381_g2_msm_input_wrong_size.json delete mode 100644 tests/testdata/test_halted_tx_call_bls12_381_g2_msm_out_of_gas.json delete mode 100644 tests/testdata/test_halted_tx_call_bls12_381_g2_msm_wrong_input_layout.json delete mode 100644 tests/testdata/test_halted_tx_call_bls12_381_map_fp2_to_g2_input_wrong_size.json delete mode 100644 tests/testdata/test_halted_tx_call_bls12_381_map_fp2_to_g2_out_of_gas.json delete mode 100644 tests/testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_input_wrong_size.json delete mode 100644 tests/testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_out_of_gas.json delete mode 100644 tests/testdata/test_halted_tx_call_bls12_381_pairing_input_wrong_size.json delete mode 100644 tests/testdata/test_halted_tx_call_bls12_381_pairing_out_of_gas.json delete mode 100644 tests/testdata/test_halted_tx_call_bls12_381_pairing_wrong_input_layout.json delete mode 100644 tests/testdata/test_halted_tx_call_bn254_pair_fjord.json delete mode 100644 tests/testdata/test_halted_tx_call_bn254_pair_granite.json delete mode 100644 tests/testdata/test_halted_tx_call_p256verify.json delete mode 100644 tests/testdata/test_log_inspector.json delete mode 100644 tests/testdata/test_system_call.json delete mode 100644 tests/testdata/test_tx_call_p256verify.json diff --git a/Cargo.toml b/Cargo.toml index cd359e6f6f2..5035af78be1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,8 +30,9 @@ rstest.workspace = true alloy-sol-types.workspace = true sha2.workspace = true serde_json = { workspace = true, features = ["alloc", "preserve_order"] } -alloy-primitives.workspace = true serde = { workspace = true, features = ["derive"] } +alloy-primitives.workspace = true +ee-tests.workspace = true [features] default = ["std", "c-kzg", "secp256k1", "portable", "blst"] diff --git a/tests/common.rs b/tests/common.rs deleted file mode 100644 index bca6e97ec5b..00000000000 --- a/tests/common.rs +++ /dev/null @@ -1,111 +0,0 @@ -//! Common test utilities used to compare execution results against testdata. -#![allow(dead_code)] - -use revm::{ - context::result::ResultAndState, - context_interface::result::{ExecutionResult, HaltReason, Output, SuccessReason}, - primitives::Bytes, - state::EvmState, -}; - -// Constant for testdata directory path -pub(crate) const TESTS_TESTDATA: &str = "tests/testdata"; - -#[cfg(not(feature = "serde"))] -pub(crate) fn compare_or_save_testdata( - _filename: &str, - _output: &ResultAndState, -) { - // serde needs to be enabled to use this function -} - -/// Compares or saves the execution output to a testdata file. -/// -/// This utility helps maintain consistent test behavior by comparing -/// execution results against known-good outputs stored in JSON files. -/// -/// # Arguments -/// -/// * `filename` - The name of the testdata file, relative to tests/testdata/ -/// * `output` - The execution output to compare or save -/// -/// # Returns -/// -/// `Ok(())` if the comparison or save was successful -/// `Err(anyhow::Error)` if there was an error -/// -/// # Note -/// -/// Tests using this function require the `serde` feature to be enabled: -/// ```bash -/// cargo test --features serde -/// ``` -#[cfg(feature = "serde")] -pub(crate) fn compare_or_save_testdata( - filename: &str, - output: &ResultAndState, -) where - HaltReasonTy: serde::Serialize + for<'a> serde::Deserialize<'a> + PartialEq, -{ - use std::{fs, path::PathBuf}; - - let tests_dir = PathBuf::from(TESTS_TESTDATA); - let testdata_file = tests_dir.join(filename); - - // Create directory if it doesn't exist - if !tests_dir.exists() { - fs::create_dir_all(&tests_dir).unwrap(); - } - - // Serialize the output to JSON for saving - let output_json = serde_json::to_string_pretty(output).unwrap(); - - // If the testdata file doesn't exist, save the output - if !testdata_file.exists() { - fs::write(&testdata_file, &output_json).unwrap(); - println!("Saved testdata to {}", testdata_file.display()); - return; - } - - // Read the expected output from the testdata file - let expected_json = fs::read_to_string(&testdata_file).unwrap(); - - // Deserialize to actual ResultAndState object for proper comparison - let expected = serde_json::from_str(&expected_json).unwrap(); - - // Compare the output objects directly - if output != &expected { - // If they don't match, generate a nicer error by pretty-printing both as JSON - // This helps with debugging by showing the exact differences - let expected_pretty = serde_json::to_string_pretty(&expected).unwrap(); - - panic!( - "Value does not match testdata.\nExpected:\n{expected_pretty}\n\nActual:\n{output_json}", - ); - } -} - -/// Example showing how to migrate an existing test to use the testdata comparison. -/// -/// This example consists of: -/// 1. The "original" test with standard assertions -/// 2. The migration approach - running assertions and saving testdata -/// 3. The final migrated test that only uses testdata comparison -#[test] -fn template_test() { - // Create a minimal result and state - let result = ResultAndState::new( - ExecutionResult::Success { - reason: SuccessReason::Stop, - gas_used: 1000, - gas_refunded: 0, - logs: vec![], - output: Output::Call(Bytes::from(vec![4, 5, 6])), - }, - EvmState::default(), - ); - - // Simply use the testdata comparison utility - // No assertions needed - full validation is done by comparing with testdata - compare_or_save_testdata::("template_test.json", &result); -} diff --git a/tests/integration.rs b/tests/integration.rs deleted file mode 100644 index 52bd9a976ad..00000000000 --- a/tests/integration.rs +++ /dev/null @@ -1,1129 +0,0 @@ -//! Integration tests for the `op-revm` crate. -mod common; - -use alloy_primitives::bytes; -use common::compare_or_save_testdata; -use op_revm::{ - precompiles::bn254_pair::GRANITE_MAX_INPUT_SIZE, DefaultOp, L1BlockInfo, OpBuilder, - OpHaltReason, OpSpecId, OpTransaction, -}; -use revm::{ - bytecode::opcode, - context::{ - result::{ExecutionResult, OutOfGasError}, - BlockEnv, CfgEnv, TxEnv, - }, - context_interface::result::HaltReason, - database::{BenchmarkDB, EmptyDB, BENCH_CALLER, BENCH_CALLER_BALANCE, BENCH_TARGET}, - interpreter::{ - gas::{calculate_initial_tx_gas, InitialAndFloorGas}, - Interpreter, InterpreterTypes, - }, - precompile::{bls12_381_const, bls12_381_utils, bn254, secp256r1, u64_to_address}, - primitives::{eip7825, Address, Bytes, Log, TxKind, U256}, - state::Bytecode, - Context, ExecuteEvm, InspectEvm, Inspector, Journal, -}; -use revm::{handler::system_call::SYSTEM_ADDRESS, SystemCallEvm}; -use std::vec::Vec; - -#[test] -fn test_deposit_tx() { - let ctx = Context::op() - .with_tx( - OpTransaction::builder() - .enveloped_tx(None) - .mint(100) - .source_hash(revm::primitives::B256::from([1u8; 32])) - .build_fill(), - ) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::HOLOCENE); - - let mut evm = ctx.build_op(); - - let output = evm.replay().unwrap(); - - // balance should be 100 - assert_eq!( - output - .state - .get(&Address::default()) - .map(|a| a.info.balance), - Some(U256::from(100)) - ); - compare_or_save_testdata("test_deposit_tx.json", &output); -} - -#[test] -fn test_halted_deposit_tx() { - let ctx = Context::op() - .with_tx( - OpTransaction::builder() - .base( - TxEnv::builder() - .caller(BENCH_CALLER) - .kind(TxKind::Call(BENCH_TARGET)), - ) - .enveloped_tx(None) - .mint(100) - .source_hash(revm::primitives::B256::from([1u8; 32])) - .build_fill(), - ) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::HOLOCENE) - .with_db(BenchmarkDB::new_bytecode(Bytecode::new_legacy( - [opcode::POP].into(), - ))); - - // POP would return a halt. - let mut evm = ctx.build_op(); - - let output = evm.replay().unwrap(); - - // balance should be 100 + previous balance - assert_eq!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::FailedDeposit, - gas_used: eip7825::TX_GAS_LIMIT_CAP, - } - ); - assert_eq!( - output.state.get(&BENCH_CALLER).map(|a| a.info.balance), - Some(U256::from(100) + BENCH_CALLER_BALANCE) - ); - - compare_or_save_testdata("test_halted_deposit_tx.json", &output); -} - -fn p256verify_test_tx( -) -> Context, CfgEnv, EmptyDB, Journal, L1BlockInfo> -{ - const SPEC_ID: OpSpecId = OpSpecId::FJORD; - - let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &[], false, 0, 0, 0); - - Context::op() - .with_tx( - OpTransaction::builder() - .base( - TxEnv::builder() - .kind(TxKind::Call(u64_to_address(secp256r1::P256VERIFY_ADDRESS))) - .gas_limit(initial_gas + secp256r1::P256VERIFY_BASE_GAS_FEE), - ) - .build_fill(), - ) - .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID) -} - -#[test] -fn test_tx_call_p256verify() { - let ctx = p256verify_test_tx(); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert successful call to P256VERIFY - assert!(output.result.is_success()); - - compare_or_save_testdata("test_tx_call_p256verify.json", &output); -} - -#[test] -fn test_halted_tx_call_p256verify() { - const SPEC_ID: OpSpecId = OpSpecId::FJORD; - let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &[], false, 0, 0, 0); - let original_gas_limit = initial_gas + secp256r1::P256VERIFY_BASE_GAS_FEE; - - let ctx = Context::op() - .with_tx( - OpTransaction::builder() - .base( - TxEnv::builder() - .kind(TxKind::Call(u64_to_address(secp256r1::P256VERIFY_ADDRESS))) - .gas_limit(original_gas_limit - 1), - ) - .build_fill(), - ) - .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert out of gas for P256VERIFY - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), - .. - } - )); - - compare_or_save_testdata("test_halted_tx_call_p256verify.json", &output); -} - -fn bn254_pair_test_tx( - spec: OpSpecId, -) -> Context, CfgEnv, EmptyDB, Journal, L1BlockInfo> -{ - let input = Bytes::from([1; GRANITE_MAX_INPUT_SIZE + 2]); - let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(spec.into(), &input[..], false, 0, 0, 0); - - Context::op() - .with_tx( - OpTransaction::builder() - .base( - TxEnv::builder() - .kind(TxKind::Call(bn254::pair::ADDRESS)) - .data(input) - .gas_limit(initial_gas), - ) - .build_fill(), - ) - .modify_cfg_chained(|cfg| cfg.spec = spec) -} - -#[test] -fn test_halted_tx_call_bn254_pair_fjord() { - let ctx = bn254_pair_test_tx(OpSpecId::FJORD); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert out of gas - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), - .. - } - )); - - compare_or_save_testdata("test_halted_tx_call_bn254_pair_fjord.json", &output); -} - -#[test] -fn test_halted_tx_call_bn254_pair_granite() { - let ctx = bn254_pair_test_tx(OpSpecId::GRANITE); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert bails early because input size too big - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::PrecompileError), - .. - } - )); - - compare_or_save_testdata("test_halted_tx_call_bn254_pair_granite.json", &output); -} - -#[test] -fn test_halted_tx_call_bls12_381_g1_add_out_of_gas() { - let ctx = Context::op() - .with_tx( - OpTransaction::builder() - .base( - TxEnv::builder() - .kind(TxKind::Call(bls12_381_const::G1_ADD_ADDRESS)) - .gas_limit(21_000 + bls12_381_const::G1_ADD_BASE_GAS_FEE - 1), - ) - .build_fill(), - ) - .modify_chain_chained(|l1_block| { - l1_block.operator_fee_constant = Some(U256::ZERO); - l1_block.operator_fee_scalar = Some(U256::ZERO) - }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); - - let mut evm = ctx.build_op(); - - let output = evm.replay().unwrap(); - - // assert out of gas - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), - .. - } - )); - - compare_or_save_testdata( - "test_halted_tx_call_bls12_381_g1_add_out_of_gas.json", - &output, - ); -} - -#[test] -fn test_halted_tx_call_bls12_381_g1_add_input_wrong_size() { - let ctx = Context::op() - .with_tx( - OpTransaction::builder() - .base( - TxEnv::builder() - .kind(TxKind::Call(bls12_381_const::G1_ADD_ADDRESS)) - .gas_limit(21_000 + bls12_381_const::G1_ADD_BASE_GAS_FEE), - ) - .build_fill(), - ) - .modify_chain_chained(|l1_block| { - l1_block.operator_fee_constant = Some(U256::ZERO); - l1_block.operator_fee_scalar = Some(U256::ZERO) - }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert fails post gas check, because input is wrong size - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::PrecompileError), - .. - } - )); - - compare_or_save_testdata( - "test_halted_tx_call_bls12_381_g1_add_input_wrong_size.json", - &output, - ); -} - -fn g1_msm_test_tx( -) -> Context, CfgEnv, EmptyDB, Journal, L1BlockInfo> -{ - const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; - - let input = Bytes::from([1; bls12_381_const::G1_MSM_INPUT_LENGTH]); - let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); - let gs1_msm_gas = bls12_381_utils::msm_required_gas( - 1, - &bls12_381_const::DISCOUNT_TABLE_G1_MSM, - bls12_381_const::G1_MSM_BASE_GAS_FEE, - ); - - Context::op() - .with_tx( - OpTransaction::builder() - .base( - TxEnv::builder() - .kind(TxKind::Call(bls12_381_const::G1_MSM_ADDRESS)) - .data(input) - .gas_limit(initial_gas + gs1_msm_gas), - ) - .build_fill(), - ) - .modify_chain_chained(|l1_block| { - l1_block.operator_fee_constant = Some(U256::ZERO); - l1_block.operator_fee_scalar = Some(U256::ZERO) - }) - .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID) -} - -#[test] -fn test_halted_tx_call_bls12_381_g1_msm_input_wrong_size() { - const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; - let input = Bytes::from([1; bls12_381_const::G1_MSM_INPUT_LENGTH]); - let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); - let gs1_msm_gas = bls12_381_utils::msm_required_gas( - 1, - &bls12_381_const::DISCOUNT_TABLE_G1_MSM, - bls12_381_const::G1_MSM_BASE_GAS_FEE, - ); - - let ctx = Context::op() - .with_tx( - OpTransaction::builder() - .base( - TxEnv::builder() - .kind(TxKind::Call(bls12_381_const::G1_MSM_ADDRESS)) - .data(input.slice(1..)) - .gas_limit(initial_gas + gs1_msm_gas), - ) - .build_fill(), - ) - .modify_chain_chained(|l1_block| { - l1_block.operator_fee_constant = Some(U256::ZERO); - l1_block.operator_fee_scalar = Some(U256::ZERO) - }) - .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert fails pre gas check, because input is wrong size - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::PrecompileError), - .. - } - )); - - compare_or_save_testdata( - "test_halted_tx_call_bls12_381_g1_msm_input_wrong_size.json", - &output, - ); -} - -#[test] -fn test_halted_tx_call_bls12_381_g1_msm_out_of_gas() { - const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; - let input = Bytes::from([1; bls12_381_const::G1_MSM_INPUT_LENGTH]); - let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); - let gs1_msm_gas = bls12_381_utils::msm_required_gas( - 1, - &bls12_381_const::DISCOUNT_TABLE_G1_MSM, - bls12_381_const::G1_MSM_BASE_GAS_FEE, - ); - - let ctx = Context::op() - .with_tx( - OpTransaction::builder() - .base( - TxEnv::builder() - .kind(TxKind::Call(bls12_381_const::G1_MSM_ADDRESS)) - .data(input) - .gas_limit(initial_gas + gs1_msm_gas - 1), - ) - .build_fill(), - ) - .modify_chain_chained(|l1_block| { - l1_block.operator_fee_constant = Some(U256::ZERO); - l1_block.operator_fee_scalar = Some(U256::ZERO) - }) - .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert out of gas - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), - .. - } - )); - - compare_or_save_testdata( - "test_halted_tx_call_bls12_381_g1_msm_out_of_gas.json", - &output, - ); -} - -#[test] -fn test_halted_tx_call_bls12_381_g1_msm_wrong_input_layout() { - let ctx = g1_msm_test_tx(); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert fails post gas check, because input is wrong layout - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::PrecompileError), - .. - } - )); - - compare_or_save_testdata( - "test_halted_tx_call_bls12_381_g1_msm_wrong_input_layout.json", - &output, - ); -} - -#[test] -fn test_halted_tx_call_bls12_381_g2_add_out_of_gas() { - let ctx = Context::op() - .with_tx( - OpTransaction::builder() - .base( - TxEnv::builder() - .kind(TxKind::Call(bls12_381_const::G2_ADD_ADDRESS)) - .gas_limit(21_000 + bls12_381_const::G2_ADD_BASE_GAS_FEE - 1), - ) - .build_fill(), - ) - .modify_chain_chained(|l1_block| { - l1_block.operator_fee_constant = Some(U256::ZERO); - l1_block.operator_fee_scalar = Some(U256::ZERO) - }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); - - let mut evm = ctx.build_op(); - - let output = evm.replay().unwrap(); - - // assert out of gas - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), - .. - } - )); - - compare_or_save_testdata( - "test_halted_tx_call_bls12_381_g2_add_out_of_gas.json", - &output, - ); -} - -#[test] -fn test_halted_tx_call_bls12_381_g2_add_input_wrong_size() { - let ctx = Context::op() - .with_tx( - OpTransaction::builder() - .base( - TxEnv::builder() - .kind(TxKind::Call(bls12_381_const::G2_ADD_ADDRESS)) - .gas_limit(21_000 + bls12_381_const::G2_ADD_BASE_GAS_FEE), - ) - .build_fill(), - ) - .modify_chain_chained(|l1_block| { - l1_block.operator_fee_constant = Some(U256::ZERO); - l1_block.operator_fee_scalar = Some(U256::ZERO) - }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); - - let mut evm = ctx.build_op(); - - let output = evm.replay().unwrap(); - - // assert fails post gas check, because input is wrong size - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::PrecompileError), - .. - } - )); - - compare_or_save_testdata( - "test_halted_tx_call_bls12_381_g2_add_input_wrong_size.json", - &output, - ); -} - -fn g2_msm_test_tx( -) -> Context, CfgEnv, EmptyDB, Journal, L1BlockInfo> -{ - const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; - - let input = Bytes::from([1; bls12_381_const::G2_MSM_INPUT_LENGTH]); - let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); - let gs2_msm_gas = bls12_381_utils::msm_required_gas( - 1, - &bls12_381_const::DISCOUNT_TABLE_G2_MSM, - bls12_381_const::G2_MSM_BASE_GAS_FEE, - ); - - Context::op() - .with_tx( - OpTransaction::builder() - .base( - TxEnv::builder() - .kind(TxKind::Call(bls12_381_const::G2_MSM_ADDRESS)) - .data(input) - .gas_limit(initial_gas + gs2_msm_gas), - ) - .build_fill(), - ) - .modify_chain_chained(|l1_block| { - l1_block.operator_fee_constant = Some(U256::ZERO); - l1_block.operator_fee_scalar = Some(U256::ZERO) - }) - .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID) -} - -#[test] -fn test_halted_tx_call_bls12_381_g2_msm_input_wrong_size() { - const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; - let input = Bytes::from([1; bls12_381_const::G2_MSM_INPUT_LENGTH]); - let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); - let gs2_msm_gas = bls12_381_utils::msm_required_gas( - 1, - &bls12_381_const::DISCOUNT_TABLE_G2_MSM, - bls12_381_const::G2_MSM_BASE_GAS_FEE, - ); - - let ctx = Context::op() - .with_tx( - OpTransaction::builder() - .base( - TxEnv::builder() - .kind(TxKind::Call(bls12_381_const::G2_MSM_ADDRESS)) - .data(input.slice(1..)) - .gas_limit(initial_gas + gs2_msm_gas), - ) - .build_fill(), - ) - .modify_chain_chained(|l1_block| { - l1_block.operator_fee_constant = Some(U256::ZERO); - l1_block.operator_fee_scalar = Some(U256::ZERO) - }) - .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert fails pre gas check, because input is wrong size - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::PrecompileError), - .. - } - )); - - compare_or_save_testdata( - "test_halted_tx_call_bls12_381_g2_msm_input_wrong_size.json", - &output, - ); -} - -#[test] -fn test_halted_tx_call_bls12_381_g2_msm_out_of_gas() { - const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; - let input = Bytes::from([1; bls12_381_const::G2_MSM_INPUT_LENGTH]); - let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); - let gs2_msm_gas = bls12_381_utils::msm_required_gas( - 1, - &bls12_381_const::DISCOUNT_TABLE_G2_MSM, - bls12_381_const::G2_MSM_BASE_GAS_FEE, - ); - - let ctx = Context::op() - .with_tx( - OpTransaction::builder() - .base( - TxEnv::builder() - .kind(TxKind::Call(bls12_381_const::G2_MSM_ADDRESS)) - .data(input) - .gas_limit(initial_gas + gs2_msm_gas - 1), - ) - .build_fill(), - ) - .modify_chain_chained(|l1_block| { - l1_block.operator_fee_constant = Some(U256::ZERO); - l1_block.operator_fee_scalar = Some(U256::ZERO) - }) - .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert out of gas - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), - .. - } - )); - - compare_or_save_testdata( - "test_halted_tx_call_bls12_381_g2_msm_out_of_gas.json", - &output, - ); -} - -#[test] -fn test_halted_tx_call_bls12_381_g2_msm_wrong_input_layout() { - let ctx = g2_msm_test_tx(); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert fails post gas check, because input is wrong layout - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::PrecompileError), - .. - } - )); - - compare_or_save_testdata( - "test_halted_tx_call_bls12_381_g2_msm_wrong_input_layout.json", - &output, - ); -} - -fn bl12_381_pairing_test_tx( -) -> Context, CfgEnv, EmptyDB, Journal, L1BlockInfo> -{ - const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; - - let input = Bytes::from([1; bls12_381_const::PAIRING_INPUT_LENGTH]); - let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); - - let pairing_gas: u64 = - bls12_381_const::PAIRING_MULTIPLIER_BASE + bls12_381_const::PAIRING_OFFSET_BASE; - - Context::op() - .with_tx( - OpTransaction::builder() - .base( - TxEnv::builder() - .kind(TxKind::Call(bls12_381_const::PAIRING_ADDRESS)) - .data(input) - .gas_limit(initial_gas + pairing_gas), - ) - .build_fill(), - ) - .modify_chain_chained(|l1_block| { - l1_block.operator_fee_constant = Some(U256::ZERO); - l1_block.operator_fee_scalar = Some(U256::ZERO) - }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS) -} - -#[test] -fn test_halted_tx_call_bls12_381_pairing_input_wrong_size() { - const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; - let input = Bytes::from([1; bls12_381_const::PAIRING_INPUT_LENGTH]); - let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); - let pairing_gas: u64 = - bls12_381_const::PAIRING_MULTIPLIER_BASE + bls12_381_const::PAIRING_OFFSET_BASE; - - let ctx = Context::op() - .with_tx( - OpTransaction::builder() - .base( - TxEnv::builder() - .kind(TxKind::Call(bls12_381_const::PAIRING_ADDRESS)) - .data(input.slice(1..)) - .gas_limit(initial_gas + pairing_gas), - ) - .build_fill(), - ) - .modify_chain_chained(|l1_block| { - l1_block.operator_fee_constant = Some(U256::ZERO); - l1_block.operator_fee_scalar = Some(U256::ZERO) - }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert fails pre gas check, because input is wrong size - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::PrecompileError), - .. - } - )); - - compare_or_save_testdata( - "test_halted_tx_call_bls12_381_pairing_input_wrong_size.json", - &output, - ); -} - -#[test] -fn test_halted_tx_call_bls12_381_pairing_out_of_gas() { - const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; - let input = Bytes::from([1; bls12_381_const::PAIRING_INPUT_LENGTH]); - let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); - let pairing_gas: u64 = - bls12_381_const::PAIRING_MULTIPLIER_BASE + bls12_381_const::PAIRING_OFFSET_BASE; - - let ctx = Context::op() - .with_tx( - OpTransaction::builder() - .base( - TxEnv::builder() - .kind(TxKind::Call(bls12_381_const::PAIRING_ADDRESS)) - .data(input) - .gas_limit(initial_gas + pairing_gas - 1), - ) - .build_fill(), - ) - .modify_chain_chained(|l1_block| { - l1_block.operator_fee_constant = Some(U256::ZERO); - l1_block.operator_fee_scalar = Some(U256::ZERO) - }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert out of gas - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), - .. - } - )); - - compare_or_save_testdata( - "test_halted_tx_call_bls12_381_pairing_out_of_gas.json", - &output, - ); -} - -#[test] -fn test_tx_call_bls12_381_pairing_wrong_input_layout() { - let ctx = bl12_381_pairing_test_tx(); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert fails post gas check, because input is wrong layout - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::PrecompileError), - .. - } - )); - - compare_or_save_testdata( - "test_halted_tx_call_bls12_381_pairing_wrong_input_layout.json", - &output, - ); -} - -#[test] -fn test_halted_tx_call_bls12_381_map_fp_to_g1_out_of_gas() { - const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; - let input = Bytes::from([1; bls12_381_const::PADDED_FP_LENGTH]); - let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); - - let ctx = Context::op() - .with_tx( - OpTransaction::builder() - .base( - TxEnv::builder() - .kind(TxKind::Call(bls12_381_const::MAP_FP_TO_G1_ADDRESS)) - .data(input) - .gas_limit(initial_gas + bls12_381_const::MAP_FP_TO_G1_BASE_GAS_FEE - 1), - ) - .build_fill(), - ) - .modify_chain_chained(|l1_block| { - l1_block.operator_fee_constant = Some(U256::ZERO); - l1_block.operator_fee_scalar = Some(U256::ZERO) - }) - .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert out of gas - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), - .. - } - )); - - compare_or_save_testdata( - "test_halted_tx_call_bls12_381_map_fp_to_g1_out_of_gas.json", - &output, - ); -} - -#[test] -fn test_halted_tx_call_bls12_381_map_fp_to_g1_input_wrong_size() { - const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; - let input = Bytes::from([1; bls12_381_const::PADDED_FP_LENGTH]); - let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); - - let ctx = Context::op() - .with_tx( - OpTransaction::builder() - .base( - TxEnv::builder() - .kind(TxKind::Call(bls12_381_const::MAP_FP_TO_G1_ADDRESS)) - .data(input.slice(1..)) - .gas_limit(initial_gas + bls12_381_const::MAP_FP_TO_G1_BASE_GAS_FEE), - ) - .build_fill(), - ) - .modify_chain_chained(|l1_block| { - l1_block.operator_fee_constant = Some(U256::ZERO); - l1_block.operator_fee_scalar = Some(U256::ZERO) - }) - .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert fails post gas check, because input is wrong size - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::PrecompileError), - .. - } - )); - - compare_or_save_testdata( - "test_halted_tx_call_bls12_381_map_fp_to_g1_input_wrong_size.json", - &output, - ); -} - -#[test] -fn test_halted_tx_call_bls12_381_map_fp2_to_g2_out_of_gas() { - const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; - let input = Bytes::from([1; bls12_381_const::PADDED_FP2_LENGTH]); - let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); - - let ctx = Context::op() - .with_tx( - OpTransaction::builder() - .base( - TxEnv::builder() - .kind(TxKind::Call(bls12_381_const::MAP_FP2_TO_G2_ADDRESS)) - .data(input) - .gas_limit(initial_gas + bls12_381_const::MAP_FP2_TO_G2_BASE_GAS_FEE - 1), - ) - .build_fill(), - ) - .modify_chain_chained(|l1_block| { - l1_block.operator_fee_constant = Some(U256::ZERO); - l1_block.operator_fee_scalar = Some(U256::ZERO) - }) - .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert out of gas - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::OutOfGas(OutOfGasError::Precompile)), - .. - } - )); - - compare_or_save_testdata( - "test_halted_tx_call_bls12_381_map_fp2_to_g2_out_of_gas.json", - &output, - ); -} - -#[test] -fn test_halted_tx_call_bls12_381_map_fp2_to_g2_input_wrong_size() { - const SPEC_ID: OpSpecId = OpSpecId::ISTHMUS; - let input = Bytes::from([1; bls12_381_const::PADDED_FP2_LENGTH]); - let InitialAndFloorGas { initial_gas, .. } = - calculate_initial_tx_gas(SPEC_ID.into(), &input[..], false, 0, 0, 0); - - let ctx = Context::op() - .with_tx( - OpTransaction::builder() - .base( - TxEnv::builder() - .kind(TxKind::Call(bls12_381_const::MAP_FP2_TO_G2_ADDRESS)) - .data(input.slice(1..)) - .gas_limit(initial_gas + bls12_381_const::MAP_FP2_TO_G2_BASE_GAS_FEE), - ) - .build_fill(), - ) - .modify_chain_chained(|l1_block| { - l1_block.operator_fee_constant = Some(U256::ZERO); - l1_block.operator_fee_scalar = Some(U256::ZERO) - }) - .modify_cfg_chained(|cfg| cfg.spec = SPEC_ID); - - let mut evm = ctx.build_op(); - let output = evm.replay().unwrap(); - - // assert fails post gas check, because input is wrong size - assert!(matches!( - output.result, - ExecutionResult::Halt { - reason: OpHaltReason::Base(HaltReason::PrecompileError), - .. - } - )); - - compare_or_save_testdata( - "test_halted_tx_call_bls12_381_map_fp2_to_g2_input_wrong_size.json", - &output, - ); -} - -#[test] -#[cfg(feature = "optional_balance_check")] -fn test_disable_balance_check() { - const RETURN_CALLER_BALANCE_BYTECODE: &[u8] = &[ - opcode::CALLER, - opcode::BALANCE, - opcode::PUSH1, - 0x00, - opcode::MSTORE, - opcode::PUSH1, - 0x20, - opcode::PUSH1, - 0x00, - opcode::RETURN, - ]; - - let mut evm = Context::op() - .modify_cfg_chained(|cfg| cfg.disable_balance_check = true) - .with_db(BenchmarkDB::new_bytecode(Bytecode::new_legacy( - RETURN_CALLER_BALANCE_BYTECODE.into(), - ))) - .build_op(); - - // Construct tx so that effective cost is more than caller balance. - let gas_price = 1; - let gas_limit = 100_000; - // Make sure value doesn't consume all balance since we want to validate that all effective - // cost is deducted. - let tx_value = BENCH_CALLER_BALANCE - U256::from(1); - - let result = evm - .transact_one( - OpTransaction::builder() - .base( - TxEnv::builder_for_bench() - .gas_price(gas_price) - .gas_limit(gas_limit) - .value(tx_value), - ) - .build_fill(), - ) - .unwrap(); - - assert!(result.is_success()); - - let returned_balance = U256::from_be_slice(result.output().unwrap().as_ref()); - let expected_balance = U256::ZERO; - assert_eq!(returned_balance, expected_balance); -} - -#[derive(Default, Debug)] -struct LogInspector { - logs: Vec, -} - -impl Inspector for LogInspector { - fn log(&mut self, _interp: &mut Interpreter, _context: &mut CTX, log: Log) { - self.logs.push(log) - } -} - -#[test] -fn test_log_inspector() { - // simple yul contract emits a log in constructor - - /*object "Contract" { - code { - log0(0, 0) - } - }*/ - - let contract_data: Bytes = Bytes::from([ - opcode::PUSH1, - 0x00, - opcode::DUP1, - opcode::LOG0, - opcode::STOP, - ]); - let bytecode = Bytecode::new_raw(contract_data); - - let ctx = Context::op().with_db(BenchmarkDB::new_bytecode(bytecode.clone())); - - let mut evm = ctx.build_op_with_inspector(LogInspector::default()); - - let tx = OpTransaction::builder() - .base( - TxEnv::builder() - .caller(BENCH_CALLER) - .kind(TxKind::Call(BENCH_TARGET)), - ) - .build_fill(); - - // Run evm. - let output = evm.inspect_tx(tx).unwrap(); - - let inspector = &evm.0.inspector; - assert!(!inspector.logs.is_empty()); - - compare_or_save_testdata("test_log_inspector.json", &output); -} - -#[test] -fn test_system_call_inspection() { - use revm::InspectSystemCallEvm; - - let ctx = Context::op(); - - let mut evm = ctx.build_op_with_inspector(LogInspector::default()); - - // Test system call inspection - let result = evm - .inspect_one_system_call(BENCH_TARGET, Bytes::default()) - .unwrap(); - - // Should succeed - assert!(result.is_success()); - - // Test system call inspection with caller - let custom_caller = Address::from([0x12; 20]); - let result = evm - .inspect_one_system_call_with_caller(custom_caller, BENCH_TARGET, Bytes::default()) - .unwrap(); - - // Should also succeed - assert!(result.is_success()); - - // Test system call inspection with inspector - let result = evm - .inspect_one_system_call_with_inspector( - BENCH_TARGET, - Bytes::default(), - LogInspector::default(), - ) - .unwrap(); - - // Should succeed - assert!(result.is_success()); -} - -#[test] -fn test_system_call() { - let ctx = Context::op(); - - let mut evm = ctx.build_op(); - - evm.system_call_one(SYSTEM_ADDRESS, BENCH_TARGET, bytes!("0x0001")) - .unwrap(); - - // Run evm. - let output = evm.replay().unwrap(); - - compare_or_save_testdata("test_system_call.json", &output); -} diff --git a/tests/testdata/template_test.json b/tests/testdata/template_test.json deleted file mode 100644 index 5c35711ddde..00000000000 --- a/tests/testdata/template_test.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "result": { - "Success": { - "reason": "Stop", - "gas_used": 1000, - "gas_refunded": 0, - "logs": [], - "output": { - "Call": "0x040506" - } - } - }, - "state": {} -} \ No newline at end of file diff --git a/tests/testdata/test_deposit_tx.json b/tests/testdata/test_deposit_tx.json deleted file mode 100644 index c187d50833e..00000000000 --- a/tests/testdata/test_deposit_tx.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "result": { - "Success": { - "reason": "Stop", - "gas_used": 21000, - "gas_refunded": 0, - "logs": [], - "output": { - "Call": "0x" - } - } - }, - "state": { - "0x0000000000000000000000000000000000000000": { - "info": { - "balance": "0x64", - "nonce": 1, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - } - } -} \ No newline at end of file diff --git a/tests/testdata/test_halted_deposit_tx.json b/tests/testdata/test_halted_deposit_tx.json deleted file mode 100644 index 8aac2ceb885..00000000000 --- a/tests/testdata/test_halted_deposit_tx.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "result": { - "Halt": { - "reason": "FailedDeposit", - "gas_used": 16777216 - } - }, - "state": { - "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee": { - "info": { - "balance": "0x2386f26fc10064", - "nonce": 1, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched" - }, - "0xffffffffffffffffffffffffffffffffffffffff": { - "info": { - "balance": "0x2386f26fc10000", - "nonce": 1, - "code_hash": "0x7b2ab94bb7d45041581aa3757ae020084674ccad6f75dc3750eb2ea8a92c4e9a", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x5000", - "original_len": 1, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 1, - "data": [ - 0 - ] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Cold" - } - } -} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g1_add_input_wrong_size.json b/tests/testdata/test_halted_tx_call_bls12_381_g1_add_input_wrong_size.json deleted file mode 100644 index 5b5fc6861ba..00000000000 --- a/tests/testdata/test_halted_tx_call_bls12_381_g1_add_input_wrong_size.json +++ /dev/null @@ -1,137 +0,0 @@ -{ - "result": { - "Halt": { - "reason": { - "Base": "PrecompileError" - }, - "gas_used": 21375 - } - }, - "state": { - "0x420000000000000000000000000000000000001a": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001b": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x4200000000000000000000000000000000000019": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x000000000000000000000000000000000000000b": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "LoadedAsNotExisting" - }, - "0x0000000000000000000000000000000000000000": { - "info": { - "balance": "0x0", - "nonce": 1, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - } - } -} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g1_add_out_of_gas.json b/tests/testdata/test_halted_tx_call_bls12_381_g1_add_out_of_gas.json deleted file mode 100644 index 5690b09303b..00000000000 --- a/tests/testdata/test_halted_tx_call_bls12_381_g1_add_out_of_gas.json +++ /dev/null @@ -1,139 +0,0 @@ -{ - "result": { - "Halt": { - "reason": { - "Base": { - "OutOfGas": "Precompile" - } - }, - "gas_used": 21374 - } - }, - "state": { - "0x0000000000000000000000000000000000000000": { - "info": { - "balance": "0x0", - "nonce": 1, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x000000000000000000000000000000000000000b": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001b": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001a": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x4200000000000000000000000000000000000019": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - } - } -} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_input_wrong_size.json b/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_input_wrong_size.json deleted file mode 100644 index 109343782bd..00000000000 --- a/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_input_wrong_size.json +++ /dev/null @@ -1,137 +0,0 @@ -{ - "result": { - "Halt": { - "reason": { - "Base": "PrecompileError" - }, - "gas_used": 35560 - } - }, - "state": { - "0x420000000000000000000000000000000000001a": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x000000000000000000000000000000000000000c": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "LoadedAsNotExisting" - }, - "0x4200000000000000000000000000000000000019": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001b": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x0000000000000000000000000000000000000000": { - "info": { - "balance": "0x0", - "nonce": 1, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - } - } -} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_out_of_gas.json b/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_out_of_gas.json deleted file mode 100644 index 38d133784c9..00000000000 --- a/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_out_of_gas.json +++ /dev/null @@ -1,139 +0,0 @@ -{ - "result": { - "Halt": { - "reason": { - "Base": { - "OutOfGas": "Precompile" - } - }, - "gas_used": 35559 - } - }, - "state": { - "0x420000000000000000000000000000000000001a": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001b": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x4200000000000000000000000000000000000019": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x000000000000000000000000000000000000000c": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "LoadedAsNotExisting" - }, - "0x0000000000000000000000000000000000000000": { - "info": { - "balance": "0x0", - "nonce": 1, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - } - } -} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_wrong_input_layout.json b/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_wrong_input_layout.json deleted file mode 100644 index 83fd20ba23c..00000000000 --- a/tests/testdata/test_halted_tx_call_bls12_381_g1_msm_wrong_input_layout.json +++ /dev/null @@ -1,137 +0,0 @@ -{ - "result": { - "Halt": { - "reason": { - "Base": "PrecompileError" - }, - "gas_used": 35560 - } - }, - "state": { - "0x4200000000000000000000000000000000000019": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x000000000000000000000000000000000000000c": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "LoadedAsNotExisting" - }, - "0x0000000000000000000000000000000000000000": { - "info": { - "balance": "0x0", - "nonce": 1, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001b": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001a": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - } - } -} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g2_add_input_wrong_size.json b/tests/testdata/test_halted_tx_call_bls12_381_g2_add_input_wrong_size.json deleted file mode 100644 index 9b58339aba7..00000000000 --- a/tests/testdata/test_halted_tx_call_bls12_381_g2_add_input_wrong_size.json +++ /dev/null @@ -1,137 +0,0 @@ -{ - "result": { - "Halt": { - "reason": { - "Base": "PrecompileError" - }, - "gas_used": 21600 - } - }, - "state": { - "0x420000000000000000000000000000000000001b": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x000000000000000000000000000000000000000d": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001a": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x0000000000000000000000000000000000000000": { - "info": { - "balance": "0x0", - "nonce": 1, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x4200000000000000000000000000000000000019": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - } - } -} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g2_add_out_of_gas.json b/tests/testdata/test_halted_tx_call_bls12_381_g2_add_out_of_gas.json deleted file mode 100644 index 31d7f31de0b..00000000000 --- a/tests/testdata/test_halted_tx_call_bls12_381_g2_add_out_of_gas.json +++ /dev/null @@ -1,139 +0,0 @@ -{ - "result": { - "Halt": { - "reason": { - "Base": { - "OutOfGas": "Precompile" - } - }, - "gas_used": 21599 - } - }, - "state": { - "0x420000000000000000000000000000000000001a": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001b": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x000000000000000000000000000000000000000d": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "LoadedAsNotExisting" - }, - "0x4200000000000000000000000000000000000019": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x0000000000000000000000000000000000000000": { - "info": { - "balance": "0x0", - "nonce": 1, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - } - } -} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_input_wrong_size.json b/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_input_wrong_size.json deleted file mode 100644 index d22cb091810..00000000000 --- a/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_input_wrong_size.json +++ /dev/null @@ -1,137 +0,0 @@ -{ - "result": { - "Halt": { - "reason": { - "Base": "PrecompileError" - }, - "gas_used": 48108 - } - }, - "state": { - "0x0000000000000000000000000000000000000000": { - "info": { - "balance": "0x0", - "nonce": 1, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001b": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x4200000000000000000000000000000000000019": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001a": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x000000000000000000000000000000000000000e": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "LoadedAsNotExisting" - } - } -} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_out_of_gas.json b/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_out_of_gas.json deleted file mode 100644 index 60ab39f58cd..00000000000 --- a/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_out_of_gas.json +++ /dev/null @@ -1,139 +0,0 @@ -{ - "result": { - "Halt": { - "reason": { - "Base": { - "OutOfGas": "Precompile" - } - }, - "gas_used": 48107 - } - }, - "state": { - "0x4200000000000000000000000000000000000019": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001b": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001a": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x000000000000000000000000000000000000000e": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "LoadedAsNotExisting" - }, - "0x0000000000000000000000000000000000000000": { - "info": { - "balance": "0x0", - "nonce": 1, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - } - } -} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_wrong_input_layout.json b/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_wrong_input_layout.json deleted file mode 100644 index 48c1006554f..00000000000 --- a/tests/testdata/test_halted_tx_call_bls12_381_g2_msm_wrong_input_layout.json +++ /dev/null @@ -1,137 +0,0 @@ -{ - "result": { - "Halt": { - "reason": { - "Base": "PrecompileError" - }, - "gas_used": 48108 - } - }, - "state": { - "0x000000000000000000000000000000000000000e": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001a": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x0000000000000000000000000000000000000000": { - "info": { - "balance": "0x0", - "nonce": 1, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x4200000000000000000000000000000000000019": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001b": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - } - } -} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_map_fp2_to_g2_input_wrong_size.json b/tests/testdata/test_halted_tx_call_bls12_381_map_fp2_to_g2_input_wrong_size.json deleted file mode 100644 index 3e00f925e8d..00000000000 --- a/tests/testdata/test_halted_tx_call_bls12_381_map_fp2_to_g2_input_wrong_size.json +++ /dev/null @@ -1,137 +0,0 @@ -{ - "result": { - "Halt": { - "reason": { - "Base": "PrecompileError" - }, - "gas_used": 46848 - } - }, - "state": { - "0x0000000000000000000000000000000000000000": { - "info": { - "balance": "0x0", - "nonce": 1, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001b": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x4200000000000000000000000000000000000019": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x0000000000000000000000000000000000000011": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001a": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - } - } -} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_map_fp2_to_g2_out_of_gas.json b/tests/testdata/test_halted_tx_call_bls12_381_map_fp2_to_g2_out_of_gas.json deleted file mode 100644 index 9152806db61..00000000000 --- a/tests/testdata/test_halted_tx_call_bls12_381_map_fp2_to_g2_out_of_gas.json +++ /dev/null @@ -1,139 +0,0 @@ -{ - "result": { - "Halt": { - "reason": { - "Base": { - "OutOfGas": "Precompile" - } - }, - "gas_used": 46847 - } - }, - "state": { - "0x0000000000000000000000000000000000000000": { - "info": { - "balance": "0x0", - "nonce": 1, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001a": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x0000000000000000000000000000000000000011": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "LoadedAsNotExisting" - }, - "0x4200000000000000000000000000000000000019": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001b": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - } - } -} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_input_wrong_size.json b/tests/testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_input_wrong_size.json deleted file mode 100644 index db93c13ab68..00000000000 --- a/tests/testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_input_wrong_size.json +++ /dev/null @@ -1,137 +0,0 @@ -{ - "result": { - "Halt": { - "reason": { - "Base": "PrecompileError" - }, - "gas_used": 27524 - } - }, - "state": { - "0x420000000000000000000000000000000000001b": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x0000000000000000000000000000000000000000": { - "info": { - "balance": "0x0", - "nonce": 1, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x0000000000000000000000000000000000000010": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001a": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x4200000000000000000000000000000000000019": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - } - } -} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_out_of_gas.json b/tests/testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_out_of_gas.json deleted file mode 100644 index 11a64278d31..00000000000 --- a/tests/testdata/test_halted_tx_call_bls12_381_map_fp_to_g1_out_of_gas.json +++ /dev/null @@ -1,139 +0,0 @@ -{ - "result": { - "Halt": { - "reason": { - "Base": { - "OutOfGas": "Precompile" - } - }, - "gas_used": 27523 - } - }, - "state": { - "0x0000000000000000000000000000000000000000": { - "info": { - "balance": "0x0", - "nonce": 1, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x4200000000000000000000000000000000000019": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001b": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001a": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x0000000000000000000000000000000000000010": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "LoadedAsNotExisting" - } - } -} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_pairing_input_wrong_size.json b/tests/testdata/test_halted_tx_call_bls12_381_pairing_input_wrong_size.json deleted file mode 100644 index 001577b3a34..00000000000 --- a/tests/testdata/test_halted_tx_call_bls12_381_pairing_input_wrong_size.json +++ /dev/null @@ -1,137 +0,0 @@ -{ - "result": { - "Halt": { - "reason": { - "Base": "PrecompileError" - }, - "gas_used": 97444 - } - }, - "state": { - "0x0000000000000000000000000000000000000000": { - "info": { - "balance": "0x0", - "nonce": 1, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x000000000000000000000000000000000000000f": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001a": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001b": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x4200000000000000000000000000000000000019": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - } - } -} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_pairing_out_of_gas.json b/tests/testdata/test_halted_tx_call_bls12_381_pairing_out_of_gas.json deleted file mode 100644 index 628f248bd2b..00000000000 --- a/tests/testdata/test_halted_tx_call_bls12_381_pairing_out_of_gas.json +++ /dev/null @@ -1,139 +0,0 @@ -{ - "result": { - "Halt": { - "reason": { - "Base": { - "OutOfGas": "Precompile" - } - }, - "gas_used": 97443 - } - }, - "state": { - "0x420000000000000000000000000000000000001b": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x000000000000000000000000000000000000000f": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "LoadedAsNotExisting" - }, - "0x4200000000000000000000000000000000000019": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x0000000000000000000000000000000000000000": { - "info": { - "balance": "0x0", - "nonce": 1, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001a": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - } - } -} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bls12_381_pairing_wrong_input_layout.json b/tests/testdata/test_halted_tx_call_bls12_381_pairing_wrong_input_layout.json deleted file mode 100644 index a7245cc2456..00000000000 --- a/tests/testdata/test_halted_tx_call_bls12_381_pairing_wrong_input_layout.json +++ /dev/null @@ -1,137 +0,0 @@ -{ - "result": { - "Halt": { - "reason": { - "Base": "PrecompileError" - }, - "gas_used": 97444 - } - }, - "state": { - "0x4200000000000000000000000000000000000019": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001a": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001b": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x0000000000000000000000000000000000000000": { - "info": { - "balance": "0x0", - "nonce": 1, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x000000000000000000000000000000000000000f": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "LoadedAsNotExisting" - } - } -} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bn254_pair_fjord.json b/tests/testdata/test_halted_tx_call_bn254_pair_fjord.json deleted file mode 100644 index 03f0c8e0fc8..00000000000 --- a/tests/testdata/test_halted_tx_call_bn254_pair_fjord.json +++ /dev/null @@ -1,139 +0,0 @@ -{ - "result": { - "Halt": { - "reason": { - "Base": { - "OutOfGas": "Precompile" - } - }, - "gas_used": 1824024 - } - }, - "state": { - "0x0000000000000000000000000000000000000000": { - "info": { - "balance": "0x0", - "nonce": 1, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x4200000000000000000000000000000000000019": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001a": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001b": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x0000000000000000000000000000000000000008": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "LoadedAsNotExisting" - } - } -} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_bn254_pair_granite.json b/tests/testdata/test_halted_tx_call_bn254_pair_granite.json deleted file mode 100644 index f05d9a82695..00000000000 --- a/tests/testdata/test_halted_tx_call_bn254_pair_granite.json +++ /dev/null @@ -1,137 +0,0 @@ -{ - "result": { - "Halt": { - "reason": { - "Base": "PrecompileError" - }, - "gas_used": 1824024 - } - }, - "state": { - "0x420000000000000000000000000000000000001a": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x0000000000000000000000000000000000000008": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "LoadedAsNotExisting" - }, - "0x0000000000000000000000000000000000000000": { - "info": { - "balance": "0x0", - "nonce": 1, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001b": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x4200000000000000000000000000000000000019": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - } - } -} \ No newline at end of file diff --git a/tests/testdata/test_halted_tx_call_p256verify.json b/tests/testdata/test_halted_tx_call_p256verify.json deleted file mode 100644 index 98a205f4831..00000000000 --- a/tests/testdata/test_halted_tx_call_p256verify.json +++ /dev/null @@ -1,139 +0,0 @@ -{ - "result": { - "Halt": { - "reason": { - "Base": { - "OutOfGas": "Precompile" - } - }, - "gas_used": 24449 - } - }, - "state": { - "0x0000000000000000000000000000000000000000": { - "info": { - "balance": "0x0", - "nonce": 1, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x0000000000000000000000000000000000000100": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001b": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x4200000000000000000000000000000000000019": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001a": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - } - } -} \ No newline at end of file diff --git a/tests/testdata/test_log_inspector.json b/tests/testdata/test_log_inspector.json deleted file mode 100644 index 04fbaf3a18b..00000000000 --- a/tests/testdata/test_log_inspector.json +++ /dev/null @@ -1,173 +0,0 @@ -{ - "result": { - "Success": { - "reason": "Stop", - "gas_used": 21381, - "gas_refunded": 0, - "logs": [ - { - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "topics": [], - "data": "0x" - } - ], - "output": { - "Call": "0x" - } - } - }, - "state": { - "0xffffffffffffffffffffffffffffffffffffffff": { - "info": { - "balance": "0x2386f26fc10000", - "nonce": 1, - "code_hash": "0x8013c386d5a03c3fa0a6ccbfefcf7d91471dedba2bb94eefef57f916e5929a8d", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x600080a000", - "original_len": 5, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 5, - "data": [ - 0 - ] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched" - }, - "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee": { - "info": { - "balance": "0x2386f26fc10000", - "nonce": 1, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched" - }, - "0x0000000000000000000000000000000000000000": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001a": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001b": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x4200000000000000000000000000000000000019": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - } - } -} \ No newline at end of file diff --git a/tests/testdata/test_system_call.json b/tests/testdata/test_system_call.json deleted file mode 100644 index c45ac37cc01..00000000000 --- a/tests/testdata/test_system_call.json +++ /dev/null @@ -1,165 +0,0 @@ -{ - "result": { - "Success": { - "reason": "Stop", - "gas_used": 21020, - "gas_refunded": 0, - "logs": [], - "output": { - "Call": "0x" - } - } - }, - "state": { - "0x420000000000000000000000000000000000001b": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 1, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x4200000000000000000000000000000000000019": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 1, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0xfffffffffffffffffffffffffffffffffffffffe": { - "info": { - "balance": "0x0", - "nonce": 1, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 1, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x0000000000000000000000000000000000000000": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 1, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0xffffffffffffffffffffffffffffffffffffffff": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 1, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001a": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 1, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - } - } -} \ No newline at end of file diff --git a/tests/testdata/test_tx_call_p256verify.json b/tests/testdata/test_tx_call_p256verify.json deleted file mode 100644 index edca703816e..00000000000 --- a/tests/testdata/test_tx_call_p256verify.json +++ /dev/null @@ -1,140 +0,0 @@ -{ - "result": { - "Success": { - "reason": "Return", - "gas_used": 24450, - "gas_refunded": 0, - "logs": [], - "output": { - "Call": "0x" - } - } - }, - "state": { - "0x420000000000000000000000000000000000001b": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x4200000000000000000000000000000000000019": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x420000000000000000000000000000000000001a": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x0000000000000000000000000000000000000000": { - "info": { - "balance": "0x0", - "nonce": 1, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - }, - "0x0000000000000000000000000000000000000100": { - "info": { - "balance": "0x0", - "nonce": 0, - "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "code": { - "LegacyAnalyzed": { - "bytecode": "0x00", - "original_len": 0, - "jump_table": { - "order": "bitvec::order::Lsb0", - "head": { - "width": 8, - "index": 0 - }, - "bits": 0, - "data": [] - } - } - } - }, - "transaction_id": 0, - "storage": {}, - "status": "Touched | LoadedAsNotExisting" - } - } -} \ No newline at end of file From 34345b24e63a285056949f34845870e2622c4784 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Tue, 29 Jul 2025 17:44:23 +0200 Subject: [PATCH 073/225] test(op-revm): Adds caller nonce assertion to op-revm intergation tests (bluealloy/revm#2815) * Adds handler test for verifying unchanged nonce on balance too low revert * Fix system call test * fixup! Fix merge conflicts --- src/handler.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/handler.rs b/src/handler.rs index 4d4a6d00ec5..f00414ec509 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1109,4 +1109,37 @@ mod tests { let account = evm.ctx().journal_mut().load_account(SENDER).unwrap(); assert_eq!(account.info.balance, expected_refund); } + + #[test] + fn test_tx_low_balance_nonce_unchanged() { + let ctx = Context::op().with_tx( + OpTransaction::builder() + .base(TxEnv::builder().value(U256::from(1000))) + .build_fill(), + ); + + let mut evm = ctx.build_op(); + + let handler = + OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame>::new(); + + let result = handler.validate_against_state_and_deduct_caller(&mut evm); + + assert!(matches!( + result.err().unwrap(), + EVMError::Transaction(OpTransactionError::Base( + InvalidTransaction::LackOfFundForMaxFee { .. } + )) + )); + assert_eq!( + evm.0 + .ctx + .journal_mut() + .load_account(Address::ZERO) + .unwrap() + .info + .nonce, + 0 + ); + } } From 32a3ce30f3e4b627361113e49fc5b85a394aeb63 Mon Sep 17 00:00:00 2001 From: rakita Date: Wed, 30 Jul 2025 13:46:37 +0200 Subject: [PATCH 074/225] feat: fix renamed functions for system_call (bluealloy/revm#2824) --- src/api/exec.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/exec.rs b/src/api/exec.rs index 204973509ee..9c460bdcac6 100644 --- a/src/api/exec.rs +++ b/src/api/exec.rs @@ -129,7 +129,7 @@ where CTX: OpContextTr + ContextSetters, PRECOMPILE: PrecompileProvider, { - fn system_call_one( + fn system_call_one_with_caller( &mut self, caller: Address, system_contract_address: Address, From 905547fe93410d6a4267268cc8c8ee0809722b24 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 7 Aug 2025 01:45:32 +0200 Subject: [PATCH 075/225] chore: release (bluealloy/revm#2854) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ Cargo.toml | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc9d37965e2..110d7f1fbd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [9.0.0](https://github.com/bluealloy/revm/compare/op-revm-v8.1.0...op-revm-v9.0.0) - 2025-08-06 + +### Added + +- fix renamed functions for system_call ([#2824](https://github.com/bluealloy/revm/pull/2824)) +- refactor test utils ([#2813](https://github.com/bluealloy/revm/pull/2813)) +- add system transaction inspection support ([#2808](https://github.com/bluealloy/revm/pull/2808)) +- Align naming of SystemCallEvm function to ExecuteEvm ([#2814](https://github.com/bluealloy/revm/pull/2814)) +- rename bn128 to bn254 for Ethereum standard consistency ([#2810](https://github.com/bluealloy/revm/pull/2810)) + +### Fixed + +- *(op-revm)* system tx not enveloped ([#2807](https://github.com/bluealloy/revm/pull/2807)) +- nonce changed is not reverted in journal if fail due to insufficient balance ([#2805](https://github.com/bluealloy/revm/pull/2805)) + +### Other + +- update README.md ([#2842](https://github.com/bluealloy/revm/pull/2842)) +- *(op-revm)* Adds caller nonce assertion to op-revm intergation tests ([#2815](https://github.com/bluealloy/revm/pull/2815)) +- *(op-revm)* Full test coverage `OpTransactionError` ([#2818](https://github.com/bluealloy/revm/pull/2818)) +- Update test data for renamed tests ([#2817](https://github.com/bluealloy/revm/pull/2817)) +- reuse global crypto provide idea ([#2786](https://github.com/bluealloy/revm/pull/2786)) +- add rust-version and note about MSRV ([#2789](https://github.com/bluealloy/revm/pull/2789)) +- add OnceLock re-export with no_std support ([#2787](https://github.com/bluealloy/revm/pull/2787)) +- Add dyn Crypto trait to PrecompileFn ([#2772](https://github.com/bluealloy/revm/pull/2772)) + ## [8.1.0](https://github.com/bluealloy/revm/compare/op-revm-v8.0.3...op-revm-v8.1.0) - 2025-07-23 ### Added diff --git a/Cargo.toml b/Cargo.toml index 5035af78be1..4706ab61f3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "8.1.0" +version = "9.0.0" authors.workspace = true edition.workspace = true keywords.workspace = true From 4f55fa318a8fa438bcfcf164ea62c008875613e3 Mon Sep 17 00:00:00 2001 From: rakita Date: Thu, 7 Aug 2025 02:30:59 +0200 Subject: [PATCH 076/225] chore: rm ee-test from revm/op-revm (bluealloy/revm#2857) --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4706ab61f3f..9c5b5d930c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,6 @@ sha2.workspace = true serde_json = { workspace = true, features = ["alloc", "preserve_order"] } serde = { workspace = true, features = ["derive"] } alloy-primitives.workspace = true -ee-tests.workspace = true [features] default = ["std", "c-kzg", "secp256k1", "portable", "blst"] From e8a40471fc5e9d78cd3ca33bcf41b50e44c7c7f0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 23:56:59 +0200 Subject: [PATCH 077/225] chore: release (bluealloy/revm#2873) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 6 ++++++ Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 110d7f1fbd3..06394174118 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [9.0.1](https://github.com/bluealloy/revm/compare/op-revm-v9.0.0...op-revm-v9.0.1) - 2025-08-12 + +### Other + +- updated the following local packages: revm + ## [9.0.0](https://github.com/bluealloy/revm/compare/op-revm-v8.1.0...op-revm-v9.0.0) - 2025-08-06 ### Added diff --git a/Cargo.toml b/Cargo.toml index 9c5b5d930c4..39557efcd19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "9.0.0" +version = "9.0.1" authors.workspace = true edition.workspace = true keywords.workspace = true From d23c4f0684b3123a6895a6e8a333f8dffc506005 Mon Sep 17 00:00:00 2001 From: jakevin Date: Mon, 18 Aug 2025 07:49:51 +0800 Subject: [PATCH 078/225] fix(handler): correct transaction ID decrement logic (bluealloy/revm#2892) Co-authored-by: megakabi --- src/handler.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handler.rs b/src/handler.rs index f00414ec509..f70421fc723 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -436,7 +436,7 @@ where let old_balance = acc.info.balance; // decrement transaction id as it was incremented when we discarded the tx. - acc.transaction_id -= acc.transaction_id; + acc.transaction_id -= 1; acc.info.nonce = acc.info.nonce.saturating_add(1); acc.info.balance = acc .info From 3ed244da66f7de4b70f05629697ce518d714213a Mon Sep 17 00:00:00 2001 From: rakita Date: Thu, 21 Aug 2025 13:44:13 +0200 Subject: [PATCH 079/225] feat(fusaka): Add PrecompileId (bluealloy/revm#2904) --- src/precompiles.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/precompiles.rs b/src/precompiles.rs index e4e14b90828..579ceb30c89 100644 --- a/src/precompiles.rs +++ b/src/precompiles.rs @@ -6,7 +6,7 @@ use revm::{ handler::{EthPrecompiles, PrecompileProvider}, interpreter::{InputsImpl, InterpreterResult}, precompile::{ - self, bn254, secp256r1, PrecompileError, PrecompileResult, PrecompileWithAddress, + self, bn254, secp256r1, Precompile, PrecompileError, PrecompileId, PrecompileResult, Precompiles, }, primitives::{hardfork::SpecId, Address, OnceLock}, @@ -144,10 +144,8 @@ pub mod bn254_pair { /// Max input size for the bn254 pair precompile. pub const GRANITE_MAX_INPUT_SIZE: usize = 112687; /// Bn254 pair precompile. - pub const GRANITE: PrecompileWithAddress = - PrecompileWithAddress(bn254::pair::ADDRESS, |input, gas_limit| { - run_pair(input, gas_limit) - }); + pub const GRANITE: Precompile = + Precompile::new(PrecompileId::Bn254Pairing, bn254::pair::ADDRESS, run_pair); /// Run the bn254 pair precompile with Optimism input limit. pub fn run_pair(input: &[u8], gas_limit: u64) -> PrecompileResult { @@ -179,14 +177,14 @@ pub mod bls12_381 { pub const ISTHMUS_PAIRING_MAX_INPUT_SIZE: usize = 235008; /// G1 msm precompile. - pub const ISTHMUS_G1_MSM: PrecompileWithAddress = - PrecompileWithAddress(G1_MSM_ADDRESS, run_g1_msm); + pub const ISTHMUS_G1_MSM: Precompile = + Precompile::new(PrecompileId::Bls12G1Msm, G1_MSM_ADDRESS, run_g1_msm); /// G2 msm precompile. - pub const ISTHMUS_G2_MSM: PrecompileWithAddress = - PrecompileWithAddress(G2_MSM_ADDRESS, run_g2_msm); + pub const ISTHMUS_G2_MSM: Precompile = + Precompile::new(PrecompileId::Bls12G2Msm, G2_MSM_ADDRESS, run_g2_msm); /// Pairing precompile. - pub const ISTHMUS_PAIRING: PrecompileWithAddress = - PrecompileWithAddress(PAIRING_ADDRESS, run_pair); + pub const ISTHMUS_PAIRING: Precompile = + Precompile::new(PrecompileId::Bls12Pairing, PAIRING_ADDRESS, run_pair); /// Run the g1 msm precompile with Optimism input limit. pub fn run_g1_msm(input: &[u8], gas_limit: u64) -> PrecompileResult { From 3c49ecbd7bdcbdf3664aaf0e8534b17647024729 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 24 Aug 2025 17:34:43 +0200 Subject: [PATCH 080/225] chore: release (bluealloy/revm#2899) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 10 ++++++++++ Cargo.toml | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06394174118..48cf6060ea1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [9.1.0](https://github.com/bluealloy/revm/compare/op-revm-v9.0.1...op-revm-v9.1.0) - 2025-08-23 + +### Added + +- *(fusaka)* Add PrecompileId ([#2904](https://github.com/bluealloy/revm/pull/2904)) + +### Fixed + +- *(handler)* correct transaction ID decrement logic ([#2892](https://github.com/bluealloy/revm/pull/2892)) + ## [9.0.1](https://github.com/bluealloy/revm/compare/op-revm-v9.0.0...op-revm-v9.0.1) - 2025-08-12 ### Other diff --git a/Cargo.toml b/Cargo.toml index 39557efcd19..c92eb4b4660 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "9.0.1" +version = "9.1.0" authors.workspace = true edition.workspace = true keywords.workspace = true From a58bd215536f9c55633f53efb229bbc2b738bd7f Mon Sep 17 00:00:00 2001 From: rakita Date: Sun, 24 Aug 2025 17:59:20 +0200 Subject: [PATCH 081/225] bump: tag v86 revm v29.0.0 (bluealloy/revm#2912) --- CHANGELOG.md | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48cf6060ea1..3486bf1404f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [9.1.0](https://github.com/bluealloy/revm/compare/op-revm-v9.0.1...op-revm-v9.1.0) - 2025-08-23 +## [10.0.0](https://github.com/bluealloy/revm/compare/op-revm-v9.0.1...op-revm-v10.0.0) - 2025-08-23 ### Added diff --git a/Cargo.toml b/Cargo.toml index c92eb4b4660..9565a3beca7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "9.1.0" +version = "10.0.0" authors.workspace = true edition.workspace = true keywords.workspace = true From 4ae87dc09f62282dca0944149f13ff723f1708eb Mon Sep 17 00:00:00 2001 From: Mablr <59505383+mablr@users.noreply.github.com> Date: Tue, 26 Aug 2025 12:11:08 +0200 Subject: [PATCH 082/225] refactor(handler): provide `&CallInputs`to`PrecompileProvider::run` (bluealloy/revm#2921) --- src/precompiles.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/precompiles.rs b/src/precompiles.rs index 579ceb30c89..033b2f3a00c 100644 --- a/src/precompiles.rs +++ b/src/precompiles.rs @@ -4,7 +4,7 @@ use revm::{ context::Cfg, context_interface::ContextTr, handler::{EthPrecompiles, PrecompileProvider}, - interpreter::{InputsImpl, InterpreterResult}, + interpreter::{CallInputs, InterpreterResult}, precompile::{ self, bn254, secp256r1, Precompile, PrecompileError, PrecompileId, PrecompileResult, Precompiles, @@ -111,13 +111,9 @@ where fn run( &mut self, context: &mut CTX, - address: &Address, - inputs: &InputsImpl, - is_static: bool, - gas_limit: u64, + inputs: &CallInputs, ) -> Result, String> { - self.inner - .run(context, address, inputs, is_static, gas_limit) + self.inner.run(context, inputs) } #[inline] From 1511eac56bd7f3bb5dfccd7086c20f1c2738adf0 Mon Sep 17 00:00:00 2001 From: kevaundray Date: Wed, 27 Aug 2025 21:07:24 +0100 Subject: [PATCH 083/225] feat!: Remove kzg-rs (bluealloy/revm#2909) --- Cargo.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9565a3beca7..298c2775908 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,7 +65,5 @@ optional_no_base_fee = ["revm/optional_no_base_fee"] # See comments in `revm-precompile` secp256k1 = ["revm/secp256k1"] c-kzg = ["revm/c-kzg"] -# `kzg-rs` is not audited but useful for `no_std` environment, use it with causing and default to `c-kzg` if possible. -kzg-rs = ["revm/kzg-rs"] blst = ["revm/blst"] bn = ["revm/bn"] From 8cc8e1f4486c2bcbb12836021e67570f62f3ca0a Mon Sep 17 00:00:00 2001 From: nk_ysg Date: Mon, 8 Sep 2025 13:59:20 +0800 Subject: [PATCH 084/225] test(op-revm): add serialize DepositTransactionParts test (bluealloy/revm#2942) --- src/transaction/deposit.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/transaction/deposit.rs b/src/transaction/deposit.rs index 1d51fb12246..509a0fea6ac 100644 --- a/src/transaction/deposit.rs +++ b/src/transaction/deposit.rs @@ -33,17 +33,20 @@ mod tests { use revm::primitives::b256; #[test] - fn serialize_json_deposit_tx_parts() { + fn serialize_deserialize_json_deposit_tx_parts() { + let parts = DepositTransactionParts::new( + b256!("0xe927a1448525fb5d32cb50ee1408461a945ba6c39bd5cf5621407d500ecc8de9"), + Some(0x34), + false, + ); let response = r#"{"source_hash":"0xe927a1448525fb5d32cb50ee1408461a945ba6c39bd5cf5621407d500ecc8de9","mint":52,"is_system_transaction":false}"#; + // serialize + let json = serde_json::to_string(&parts).unwrap(); + assert_eq!(json.as_str(), response); + + // deserialize let deposit_tx_parts: DepositTransactionParts = serde_json::from_str(response).unwrap(); - assert_eq!( - deposit_tx_parts, - DepositTransactionParts::new( - b256!("0xe927a1448525fb5d32cb50ee1408461a945ba6c39bd5cf5621407d500ecc8de9"), - Some(0x34), - false, - ) - ); + assert_eq!(deposit_tx_parts, parts); } } From 3bde9245199f2249622162b59506da36c5140cec Mon Sep 17 00:00:00 2001 From: nk_ysg Date: Mon, 8 Sep 2025 13:59:38 +0800 Subject: [PATCH 085/225] chore(op-revm): rm redundant phantom (bluealloy/revm#2943) --- src/handler.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index f70421fc723..5e25ff738c7 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -31,8 +31,6 @@ pub struct OpHandler { /// Mainnet handler allows us to use functions from the mainnet handler inside optimism handler. /// So we dont duplicate the logic pub mainnet: MainnetHandler, - /// Phantom data to avoid type inference issues. - pub _phantom: core::marker::PhantomData<(EVM, ERROR, FRAME)>, } impl OpHandler { @@ -40,7 +38,6 @@ impl OpHandler { pub fn new() -> Self { Self { mainnet: MainnetHandler::default(), - _phantom: core::marker::PhantomData, } } } From d1a6b3255c0874148c7d6a5b07cd8746c5d3bd59 Mon Sep 17 00:00:00 2001 From: GarmashAlex Date: Wed, 10 Sep 2025 15:30:45 +0300 Subject: [PATCH 086/225] fix(op-revm): clear enveloped_tx for deposit txs in build_fill and align docs (bluealloy/revm#2957) --- src/transaction/abstraction.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/transaction/abstraction.rs b/src/transaction/abstraction.rs index 2043f1ca0aa..d0179a78079 100644 --- a/src/transaction/abstraction.rs +++ b/src/transaction/abstraction.rs @@ -275,7 +275,10 @@ impl OpTransactionBuilder { /// This is useful for testing and debugging where it is not necessary to /// have full [`OpTransaction`] instance. /// - /// If the source hash is not [`B256::ZERO`], set the transaction type to deposit and remove the enveloped transaction. + /// If the transaction is a deposit (either `tx_type == DEPOSIT_TRANSACTION_TYPE` or + /// `source_hash != B256::ZERO`), set the transaction type accordingly and ensure the + /// `enveloped_tx` is removed (`None`). For non-deposit transactions, ensure + /// `enveloped_tx` is set. pub fn build_fill(mut self) -> OpTransaction { let tx_type = self.base.get_tx_type(); if tx_type.is_some() { @@ -284,6 +287,8 @@ impl OpTransactionBuilder { if self.deposit.source_hash == B256::ZERO { self.deposit.source_hash = B256::from([1u8; 32]); } + // deposit transactions should not carry enveloped bytes + self.enveloped_tx = None; } else { // enveloped is required for non-deposit transactions self.enveloped_tx = Some(vec![0x00].into()); @@ -291,6 +296,8 @@ impl OpTransactionBuilder { } else if self.deposit.source_hash != B256::ZERO { // if type is not set and source hash is set, set the transaction type to deposit self.base = self.base.tx_type(Some(DEPOSIT_TRANSACTION_TYPE)); + // deposit transactions should not carry enveloped bytes + self.enveloped_tx = None; } else if self.enveloped_tx.is_none() { // if type is not set and source hash is not set, set the enveloped transaction to something. self.enveloped_tx = Some(vec![0x00].into()); From 7f24b62934bb0142d00b432715e6d39dcf0e0ba3 Mon Sep 17 00:00:00 2001 From: FT <140458077+zeevick10@users.noreply.github.com> Date: Tue, 16 Sep 2025 10:58:42 +0200 Subject: [PATCH 087/225] op-revm: treat empty input as zero operator fee in operator_fee_charge (bluealloy/revm#2973) --- src/l1block.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/l1block.rs b/src/l1block.rs index cc4ec830003..1a0750465e1 100644 --- a/src/l1block.rs +++ b/src/l1block.rs @@ -154,7 +154,7 @@ impl L1BlockInfo { /// Introduced in isthmus. Prior to isthmus, the operator fee is always zero. pub fn operator_fee_charge(&self, input: &[u8], gas_limit: U256) -> U256 { // If the input is a deposit transaction or empty, the default value is zero. - if input.first() == Some(&0x7E) { + if input.is_empty() || input.first() == Some(&0x7E) { return U256::ZERO; } From d4f9a105be8e5bbb96c7b50bca2b5fb85ac7d3d8 Mon Sep 17 00:00:00 2001 From: rakita Date: Tue, 16 Sep 2025 10:59:14 +0200 Subject: [PATCH 088/225] chore: prealloc few frames (bluealloy/revm#2965) * prealloc few frames * use vec::with_capacity * reserve capacity on get_next, and set len in push --- src/evm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evm.rs b/src/evm.rs index 8f13b36c667..ad9e965783d 100644 --- a/src/evm.rs +++ b/src/evm.rs @@ -34,7 +34,7 @@ impl OpEvm inspector, instruction: EthInstructions::new_mainnet(), precompiles: OpPrecompiles::default(), - frame_stack: FrameStack::new(), + frame_stack: FrameStack::new_prealloc(8), }) } } From ec057328a5586f8b41df97b0e00f59ecaffa2929 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Wed, 17 Sep 2025 10:39:52 +0200 Subject: [PATCH 089/225] feat(op-revm): Add an option to disable "fee-charge" on `op-revm` (bluealloy/revm#2980) An option to disable fee charges, only meaningful on `op-revm` is added, which doesn't charge additional fees, in addition from the default fee from the gas limit and gas price. This is useful for `eth_call`s which should disable all fees. See https://github.com/paradigmxyz/reth/issues/18470 --- src/handler.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handler.rs b/src/handler.rs index 5e25ff738c7..64a91db80c6 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -115,7 +115,7 @@ where let mut additional_cost = U256::ZERO; // The L1-cost fee is only computed for Optimism non-deposit transactions. - if !is_deposit { + if !is_deposit && !ctx.cfg().is_fee_charge_disabled() { // L1 block info is stored in the context for later use. // and it will be reloaded from the database if it is not for the current block. if ctx.chain().l2_block != block_number { From a7a7c24523dd99136e9b2bdee7d22be30828eba4 Mon Sep 17 00:00:00 2001 From: Snezhkko Date: Thu, 18 Sep 2025 15:36:00 +0300 Subject: [PATCH 090/225] Set l2_block in try_fetch for pre-Isthmus forks; add reload tests (bluealloy/revm#2994) * Update l1block.rs * Update handler.rs --- src/handler.rs | 118 +++++++++++++++++++++++++++++++++++++++++++++++++ src/l1block.rs | 2 + 2 files changed, 120 insertions(+) diff --git a/src/handler.rs b/src/handler.rs index 64a91db80c6..e28150a7efe 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -786,6 +786,124 @@ mod tests { ); } + #[test] + fn test_reload_l1_block_info_regolith() { + const BLOCK_NUM: U256 = uint!(200_U256); + const L1_BASE_FEE: U256 = uint!(7_U256); + const L1_FEE_OVERHEAD: U256 = uint!(9_U256); + const L1_BASE_FEE_SCALAR: u64 = 11; + + let mut db = InMemoryDB::default(); + let l1_block_contract = db.load_account(L1_BLOCK_CONTRACT).unwrap(); + l1_block_contract + .storage + .insert(L1_BASE_FEE_SLOT, L1_BASE_FEE); + // Pre-ecotone bedrock/regolith slots + use crate::constants::{L1_OVERHEAD_SLOT, L1_SCALAR_SLOT}; + l1_block_contract + .storage + .insert(L1_OVERHEAD_SLOT, L1_FEE_OVERHEAD); + l1_block_contract + .storage + .insert(L1_SCALAR_SLOT, U256::from(L1_BASE_FEE_SCALAR)); + + let ctx = Context::op() + .with_db(db) + .with_chain(L1BlockInfo { + l2_block: BLOCK_NUM + U256::from(1), + ..Default::default() + }) + .with_block(BlockEnv { + number: BLOCK_NUM, + ..Default::default() + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); + + let mut evm = ctx.build_op(); + assert_ne!(evm.ctx().chain().l2_block, BLOCK_NUM); + + let handler = + OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame>::new(); + handler + .validate_against_state_and_deduct_caller(&mut evm) + .unwrap(); + + assert_eq!( + *evm.ctx().chain(), + L1BlockInfo { + l2_block: BLOCK_NUM, + l1_base_fee: L1_BASE_FEE, + l1_fee_overhead: Some(L1_FEE_OVERHEAD), + l1_base_fee_scalar: U256::from(L1_BASE_FEE_SCALAR), + tx_l1_cost: Some(U256::ZERO), + ..Default::default() + } + ); + } + + #[test] + fn test_reload_l1_block_info_ecotone_pre_isthmus() { + const BLOCK_NUM: U256 = uint!(300_U256); + const L1_BASE_FEE: U256 = uint!(13_U256); + const L1_BLOB_BASE_FEE: U256 = uint!(17_U256); + const L1_BASE_FEE_SCALAR: u64 = 19; + const L1_BLOB_BASE_FEE_SCALAR: u64 = 23; + const L1_FEE_SCALARS: U256 = U256::from_limbs([ + 0, + (L1_BASE_FEE_SCALAR << (64 - BASE_FEE_SCALAR_OFFSET * 2)) | L1_BLOB_BASE_FEE_SCALAR, + 0, + 0, + ]); + + let mut db = InMemoryDB::default(); + let l1_block_contract = db.load_account(L1_BLOCK_CONTRACT).unwrap(); + l1_block_contract + .storage + .insert(L1_BASE_FEE_SLOT, L1_BASE_FEE); + l1_block_contract + .storage + .insert(ECOTONE_L1_BLOB_BASE_FEE_SLOT, L1_BLOB_BASE_FEE); + l1_block_contract + .storage + .insert(ECOTONE_L1_FEE_SCALARS_SLOT, L1_FEE_SCALARS); + + let ctx = Context::op() + .with_db(db) + .with_chain(L1BlockInfo { + l2_block: BLOCK_NUM + U256::from(1), + ..Default::default() + }) + .with_block(BlockEnv { + number: BLOCK_NUM, + ..Default::default() + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ECOTONE); + + let mut evm = ctx.build_op(); + assert_ne!(evm.ctx().chain().l2_block, BLOCK_NUM); + + let handler = + OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame>::new(); + handler + .validate_against_state_and_deduct_caller(&mut evm) + .unwrap(); + + assert_eq!( + *evm.ctx().chain(), + L1BlockInfo { + l2_block: BLOCK_NUM, + l1_base_fee: L1_BASE_FEE, + l1_base_fee_scalar: U256::from(L1_BASE_FEE_SCALAR), + l1_blob_base_fee: Some(L1_BLOB_BASE_FEE), + l1_blob_base_fee_scalar: Some(U256::from(L1_BLOB_BASE_FEE_SCALAR)), + empty_ecotone_scalars: false, + l1_fee_overhead: None, + tx_l1_cost: Some(U256::ZERO), + ..Default::default() + } + ); + } + #[test] fn test_remove_l1_cost() { let caller = Address::ZERO; diff --git a/src/l1block.rs b/src/l1block.rs index 1a0750465e1..8c4e5b936f8 100644 --- a/src/l1block.rs +++ b/src/l1block.rs @@ -74,6 +74,7 @@ impl L1BlockInfo { let l1_fee_scalar = db.storage(L1_BLOCK_CONTRACT, L1_SCALAR_SLOT)?; Ok(L1BlockInfo { + l2_block, l1_base_fee, l1_fee_overhead: Some(l1_fee_overhead), l1_base_fee_scalar: l1_fee_scalar, @@ -137,6 +138,7 @@ impl L1BlockInfo { } else { // Pre-isthmus L1 block info Ok(L1BlockInfo { + l2_block, l1_base_fee, l1_base_fee_scalar, l1_blob_base_fee: Some(l1_blob_base_fee), From fb3728c58c19dbba70fd034c4cbb67bb9f58a8ab Mon Sep 17 00:00:00 2001 From: Taehoon Kim Date: Fri, 19 Sep 2025 06:18:45 -0700 Subject: [PATCH 091/225] feat(op-revm): implement jovian operator fee fix (bluealloy/revm#2996) --- src/constants.rs | 3 +++ src/handler.rs | 54 ++++++++++++++++++++++++++++++++++++++++++---- src/l1block.rs | 46 ++++++++++++++++++++++++++++++--------- src/precompiles.rs | 2 +- src/spec.rs | 8 ++++++- 5 files changed, 97 insertions(+), 16 deletions(-) diff --git a/src/constants.rs b/src/constants.rs index c639e8573c7..40e9b00092f 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -23,6 +23,9 @@ pub const OPERATOR_FEE_CONSTANT_OFFSET: usize = 24; /// Allows users to use 6 decimal points of precision when specifying the operator_fee_scalar. pub const OPERATOR_FEE_SCALAR_DECIMAL: u64 = 1_000_000; +/// The Jovian multiplier applied to the operator fee scalar component. +pub const OPERATOR_FEE_JOVIAN_MULTIPLIER: u64 = 100; + /// The L1 base fee slot. pub const L1_BASE_FEE_SLOT: U256 = U256::from_limbs([1u64, 0, 0, 0]); /// The L1 overhead slot. diff --git a/src/handler.rs b/src/handler.rs index e28150a7efe..c23c34ac7c4 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -135,7 +135,9 @@ where // compute operator fee if spec.is_enabled_in(OpSpecId::ISTHMUS) { let gas_limit = U256::from(ctx.tx().gas_limit()); - let operator_fee_charge = ctx.chain().operator_fee_charge(&enveloped_tx, gas_limit); + let operator_fee_charge = + ctx.chain() + .operator_fee_charge(&enveloped_tx, gas_limit, spec); additional_cost = additional_cost.saturating_add(operator_fee_charge); } } @@ -352,7 +354,11 @@ where let l1_cost = l1_block_info.calculate_tx_l1_cost(enveloped_tx, spec); let operator_fee_cost = if spec.is_enabled_in(OpSpecId::ISTHMUS) { - l1_block_info.operator_fee_charge(enveloped_tx, U256::from(frame_result.gas().used())) + l1_block_info.operator_fee_charge( + enveloped_tx, + U256::from(frame_result.gas().used()), + spec, + ) } else { U256::ZERO }; @@ -948,7 +954,7 @@ mod tests { } #[test] - fn test_remove_operator_cost() { + fn test_remove_operator_cost_isthmus() { let caller = Address::ZERO; let mut db = InMemoryDB::default(); db.insert_account_info( @@ -977,7 +983,7 @@ mod tests { let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame>::new(); - // operator fee cost is operator_fee_scalar * gas_limit / 1e6 + operator_fee_constant + // Under Isthmus the operator fee cost is operator_fee_scalar * gas_limit / 1e6 + operator_fee_constant // 10_000_000 * 10 / 1_000_000 + 50 = 150 handler .validate_against_state_and_deduct_caller(&mut evm) @@ -988,6 +994,46 @@ mod tests { assert_eq!(account.info.balance, U256::from(1)); } + #[test] + fn test_remove_operator_cost_jovian() { + let caller = Address::ZERO; + let mut db = InMemoryDB::default(); + db.insert_account_info( + caller, + AccountInfo { + balance: U256::from(2_051), + ..Default::default() + }, + ); + let ctx = Context::op() + .with_db(db) + .with_chain(L1BlockInfo { + operator_fee_scalar: Some(U256::from(2)), + operator_fee_constant: Some(U256::from(50)), + ..Default::default() + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::JOVIAN) + .with_tx( + OpTransaction::builder() + .base(TxEnv::builder().gas_limit(10)) + .enveloped_tx(Some(bytes!("FACADE"))) + .build_fill(), + ); + + let mut evm = ctx.build_op(); + let handler = + OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame>::new(); + + // Under Jovian the operator fee cost is operator_fee_scalar * gas_limit * 100 + operator_fee_constant + // 2 * 10 * 100 + 50 = 2_050 + handler + .validate_against_state_and_deduct_caller(&mut evm) + .unwrap(); + + let account = evm.ctx().journal_mut().load_account(caller).unwrap(); + assert_eq!(account.info.balance, U256::from(1)); + } + #[test] fn test_remove_l1_cost_lack_of_funds() { let caller = Address::ZERO; diff --git a/src/l1block.rs b/src/l1block.rs index 8c4e5b936f8..70553ede722 100644 --- a/src/l1block.rs +++ b/src/l1block.rs @@ -4,7 +4,8 @@ use crate::{ BASE_FEE_SCALAR_OFFSET, BLOB_BASE_FEE_SCALAR_OFFSET, ECOTONE_L1_BLOB_BASE_FEE_SLOT, ECOTONE_L1_FEE_SCALARS_SLOT, EMPTY_SCALARS, L1_BASE_FEE_SLOT, L1_BLOCK_CONTRACT, L1_OVERHEAD_SLOT, L1_SCALAR_SLOT, NON_ZERO_BYTE_COST, OPERATOR_FEE_CONSTANT_OFFSET, - OPERATOR_FEE_SCALARS_SLOT, OPERATOR_FEE_SCALAR_DECIMAL, OPERATOR_FEE_SCALAR_OFFSET, + OPERATOR_FEE_JOVIAN_MULTIPLIER, OPERATOR_FEE_SCALARS_SLOT, OPERATOR_FEE_SCALAR_DECIMAL, + OPERATOR_FEE_SCALAR_OFFSET, }, transaction::estimate_tx_compressed_size, OpSpecId, @@ -154,17 +155,17 @@ impl L1BlockInfo { /// Calculate the operator fee for executing this transaction. /// /// Introduced in isthmus. Prior to isthmus, the operator fee is always zero. - pub fn operator_fee_charge(&self, input: &[u8], gas_limit: U256) -> U256 { + pub fn operator_fee_charge(&self, input: &[u8], gas_limit: U256, spec_id: OpSpecId) -> U256 { // If the input is a deposit transaction or empty, the default value is zero. if input.is_empty() || input.first() == Some(&0x7E) { return U256::ZERO; } - self.operator_fee_charge_inner(gas_limit) + self.operator_fee_charge_inner(gas_limit, spec_id) } /// Calculate the operator fee for the given `gas`. - fn operator_fee_charge_inner(&self, gas: U256) -> U256 { + fn operator_fee_charge_inner(&self, gas: U256, spec_id: OpSpecId) -> U256 { let operator_fee_scalar = self .operator_fee_scalar .expect("Missing operator fee scalar for isthmus L1 Block"); @@ -172,8 +173,12 @@ impl L1BlockInfo { .operator_fee_constant .expect("Missing operator fee constant for isthmus L1 Block"); - let product = - gas.saturating_mul(operator_fee_scalar) / (U256::from(OPERATOR_FEE_SCALAR_DECIMAL)); + let product = if spec_id.is_enabled_in(OpSpecId::JOVIAN) { + gas.saturating_mul(operator_fee_scalar) + .saturating_mul(U256::from(OPERATOR_FEE_JOVIAN_MULTIPLIER)) + } else { + gas.saturating_mul(operator_fee_scalar) / U256::from(OPERATOR_FEE_SCALAR_DECIMAL) + }; product.saturating_add(operator_fee_constant) } @@ -186,10 +191,12 @@ impl L1BlockInfo { return U256::ZERO; } - let operator_cost_gas_limit = self.operator_fee_charge_inner(U256::from(gas.limit())); - let operator_cost_gas_used = self.operator_fee_charge_inner(U256::from( - gas.limit() - (gas.remaining() + gas.refunded() as u64), - )); + let operator_cost_gas_limit = + self.operator_fee_charge_inner(U256::from(gas.limit()), spec_id); + let operator_cost_gas_used = self.operator_fee_charge_inner( + U256::from(gas.limit() - (gas.remaining() + gas.refunded() as u64)), + spec_id, + ); operator_cost_gas_limit.saturating_sub(operator_cost_gas_used) } @@ -575,6 +582,25 @@ mod tests { assert_eq!(l1_fee, expected_l1_fee) } + #[test] + fn test_operator_fee_charge_formulas() { + let l1_block_info = L1BlockInfo { + operator_fee_scalar: Some(U256::from(1_000u64)), + operator_fee_constant: Some(U256::from(10u64)), + ..Default::default() + }; + + let input = [0x01u8]; + + let isthmus_fee = + l1_block_info.operator_fee_charge(&input, U256::from(1_000u64), OpSpecId::ISTHMUS); + assert_eq!(isthmus_fee, U256::from(11u64)); + + let jovian_fee = + l1_block_info.operator_fee_charge(&input, U256::from(1_000u64), OpSpecId::JOVIAN); + assert_eq!(jovian_fee, U256::from(100_000_010u64)); + } + #[test] fn test_operator_fee_refund() { let gas = Gas::new(50000); diff --git a/src/precompiles.rs b/src/precompiles.rs index 033b2f3a00c..d0f67bddb4b 100644 --- a/src/precompiles.rs +++ b/src/precompiles.rs @@ -34,7 +34,7 @@ impl OpPrecompiles { | OpSpecId::ECOTONE) => Precompiles::new(spec.into_eth_spec().into()), OpSpecId::FJORD => fjord(), OpSpecId::GRANITE | OpSpecId::HOLOCENE => granite(), - OpSpecId::ISTHMUS | OpSpecId::INTEROP | OpSpecId::OSAKA => isthmus(), + OpSpecId::ISTHMUS | OpSpecId::JOVIAN | OpSpecId::INTEROP | OpSpecId::OSAKA => isthmus(), }; Self { diff --git a/src/spec.rs b/src/spec.rs index c0e764c598a..c36ff8b2932 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -25,6 +25,8 @@ pub enum OpSpecId { /// Isthmus spec id. #[default] ISTHMUS, + /// Jovian spec id. + JOVIAN, /// Interop spec id. INTEROP, /// Osaka spec id. @@ -38,7 +40,7 @@ impl OpSpecId { Self::BEDROCK | Self::REGOLITH => SpecId::MERGE, Self::CANYON => SpecId::SHANGHAI, Self::ECOTONE | Self::FJORD | Self::GRANITE | Self::HOLOCENE => SpecId::CANCUN, - Self::ISTHMUS | Self::INTEROP => SpecId::PRAGUE, + Self::ISTHMUS | Self::JOVIAN | Self::INTEROP => SpecId::PRAGUE, Self::OSAKA => SpecId::OSAKA, } } @@ -68,6 +70,7 @@ impl FromStr for OpSpecId { name::GRANITE => Ok(OpSpecId::GRANITE), name::HOLOCENE => Ok(OpSpecId::HOLOCENE), name::ISTHMUS => Ok(OpSpecId::ISTHMUS), + name::JOVIAN => Ok(OpSpecId::JOVIAN), name::INTEROP => Ok(OpSpecId::INTEROP), eth_name::OSAKA => Ok(OpSpecId::OSAKA), _ => Err(UnknownHardfork), @@ -86,6 +89,7 @@ impl From for &'static str { OpSpecId::GRANITE => name::GRANITE, OpSpecId::HOLOCENE => name::HOLOCENE, OpSpecId::ISTHMUS => name::ISTHMUS, + OpSpecId::JOVIAN => name::JOVIAN, OpSpecId::INTEROP => name::INTEROP, OpSpecId::OSAKA => eth_name::OSAKA, } @@ -110,6 +114,8 @@ pub mod name { pub const HOLOCENE: &str = "Holocene"; /// Isthmus spec name. pub const ISTHMUS: &str = "Isthmus"; + /// Jovian spec name. + pub const JOVIAN: &str = "Jovian"; /// Interop spec name. pub const INTEROP: &str = "Interop"; } From 8d5cb0ac924a41c440e0fd2848972dd4e6d0e61a Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 24 Sep 2025 12:08:25 +0200 Subject: [PATCH 092/225] fix: add missing is_fee_charge_disabled check (bluealloy/revm#3007) --- src/handler.rs | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index c23c34ac7c4..fc3fd7eac67 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -115,30 +115,32 @@ where let mut additional_cost = U256::ZERO; // The L1-cost fee is only computed for Optimism non-deposit transactions. - if !is_deposit && !ctx.cfg().is_fee_charge_disabled() { + if !is_deposit { // L1 block info is stored in the context for later use. // and it will be reloaded from the database if it is not for the current block. if ctx.chain().l2_block != block_number { *ctx.chain_mut() = L1BlockInfo::try_fetch(ctx.db_mut(), block_number, spec)?; } - // account for additional cost of l1 fee and operator fee - let enveloped_tx = ctx - .tx() - .enveloped_tx() - .expect("all not deposit tx have enveloped tx") - .clone(); - - // compute L1 cost - additional_cost = ctx.chain_mut().calculate_tx_l1_cost(&enveloped_tx, spec); - - // compute operator fee - if spec.is_enabled_in(OpSpecId::ISTHMUS) { - let gas_limit = U256::from(ctx.tx().gas_limit()); - let operator_fee_charge = - ctx.chain() - .operator_fee_charge(&enveloped_tx, gas_limit, spec); - additional_cost = additional_cost.saturating_add(operator_fee_charge); + if !ctx.cfg().is_fee_charge_disabled() { + // account for additional cost of l1 fee and operator fee + let enveloped_tx = ctx + .tx() + .enveloped_tx() + .expect("all not deposit tx have enveloped tx") + .clone(); + + // compute L1 cost + additional_cost = ctx.chain_mut().calculate_tx_l1_cost(&enveloped_tx, spec); + + // compute operator fee + if spec.is_enabled_in(OpSpecId::ISTHMUS) { + let gas_limit = U256::from(ctx.tx().gas_limit()); + let operator_fee_charge = + ctx.chain() + .operator_fee_charge(&enveloped_tx, gas_limit, spec); + additional_cost = additional_cost.saturating_add(operator_fee_charge); + } } } @@ -289,7 +291,9 @@ where ) -> Result<(), Self::Error> { let mut additional_refund = U256::ZERO; - if evm.ctx().tx().tx_type() != DEPOSIT_TRANSACTION_TYPE { + if evm.ctx().tx().tx_type() != DEPOSIT_TRANSACTION_TYPE + && !evm.ctx().cfg().is_fee_charge_disabled() + { let spec = evm.ctx().cfg().spec(); additional_refund = evm .ctx() From 4430a849c51ca03ced6dc608abd6124014efe2a3 Mon Sep 17 00:00:00 2001 From: theo <80177219+theochap@users.noreply.github.com> Date: Fri, 26 Sep 2025 20:07:55 -0400 Subject: [PATCH 093/225] feat(jovian): add da footprint block limit. (bluealloy/revm#3003) --- src/constants.rs | 8 ++ src/handler.rs | 104 ++++++++++++++++++++++++- src/l1block.rs | 186 ++++++++++++++++++++++++--------------------- src/precompiles.rs | 2 +- src/spec.rs | 23 +++++- 5 files changed, 233 insertions(+), 90 deletions(-) diff --git a/src/constants.rs b/src/constants.rs index 40e9b00092f..243343e8044 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -18,6 +18,10 @@ pub const OPERATOR_FEE_SCALAR_OFFSET: usize = 20; /// the storage slot of the 8-byte operatorFeeConstant attribute. pub const OPERATOR_FEE_CONSTANT_OFFSET: usize = 24; +/// The Jovian daFootprintGasScalar value is packed into a single storage slot. Byte offset within +/// the storage slot of the 16-byte daFootprintGasScalar attribute. +pub const DA_FOOTPRINT_GAS_SCALAR_OFFSET: usize = 0; + /// The fixed point decimal scaling factor associated with the operator fee scalar. /// /// Allows users to use 6 decimal points of precision when specifying the operator_fee_scalar. @@ -44,6 +48,10 @@ pub const ECOTONE_L1_FEE_SCALARS_SLOT: U256 = U256::from_limbs([3u64, 0, 0, 0]); /// offsets [OPERATOR_FEE_SCALAR_OFFSET] and [OPERATOR_FEE_CONSTANT_OFFSET] respectively. pub const OPERATOR_FEE_SCALARS_SLOT: U256 = U256::from_limbs([8u64, 0, 0, 0]); +/// As of the Jovian upgrade, this storage slot stores the 32-bit daFootprintGasScalar attribute at +/// offset [DA_FOOTPRINT_GAS_SCALAR_OFFSET]. +pub const DA_FOOTPRINT_GAS_SCALAR_SLOT: U256 = U256::from_limbs([9u64, 0, 0, 0]); + /// An empty 64-bit set of scalar values. pub const EMPTY_SCALARS: [u8; 8] = [0u8; 8]; diff --git a/src/handler.rs b/src/handler.rs index fc3fd7eac67..4f24c5fdb25 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -500,8 +500,9 @@ mod tests { use crate::{ api::default_ctx::OpContext, constants::{ - BASE_FEE_SCALAR_OFFSET, ECOTONE_L1_BLOB_BASE_FEE_SLOT, ECOTONE_L1_FEE_SCALARS_SLOT, - L1_BASE_FEE_SLOT, L1_BLOCK_CONTRACT, OPERATOR_FEE_SCALARS_SLOT, + BASE_FEE_SCALAR_OFFSET, DA_FOOTPRINT_GAS_SCALAR_SLOT, ECOTONE_L1_BLOB_BASE_FEE_SLOT, + ECOTONE_L1_FEE_SCALARS_SLOT, L1_BASE_FEE_SLOT, L1_BLOCK_CONTRACT, + OPERATOR_FEE_SCALARS_SLOT, }, DefaultOp, OpBuilder, OpTransaction, }; @@ -792,6 +793,105 @@ mod tests { operator_fee_scalar: Some(U256::from(OPERATOR_FEE_SCALAR)), operator_fee_constant: Some(U256::from(OPERATOR_FEE_CONST)), tx_l1_cost: Some(U256::ZERO), + da_footprint_gas_scalar: None + } + ); + } + + #[test] + fn test_parse_da_footprint_gas_scalar_jovian() { + const BLOCK_NUM: U256 = uint!(100_U256); + const L1_BASE_FEE: U256 = uint!(1_U256); + const L1_BLOB_BASE_FEE: U256 = uint!(2_U256); + const L1_BASE_FEE_SCALAR: u64 = 3; + const L1_BLOB_BASE_FEE_SCALAR: u64 = 4; + const L1_FEE_SCALARS: U256 = U256::from_limbs([ + 0, + (L1_BASE_FEE_SCALAR << (64 - BASE_FEE_SCALAR_OFFSET * 2)) | L1_BLOB_BASE_FEE_SCALAR, + 0, + 0, + ]); + const OPERATOR_FEE_SCALAR: u64 = 5; + const OPERATOR_FEE_CONST: u64 = 6; + const OPERATOR_FEE: U256 = + U256::from_limbs([OPERATOR_FEE_CONST, OPERATOR_FEE_SCALAR, 0, 0]); + const DA_FOOTPRINT_GAS_SCALAR: u16 = 7; + const DA_FOOTPRINT_GAS_SCALAR_U64: u64 = + u64::from_be_bytes([0, DA_FOOTPRINT_GAS_SCALAR as u8, 0, 0, 0, 0, 0, 0]); + const DA_FOOTPRINT_GAS_SCALAR_SLOT_VALUE: U256 = + U256::from_limbs([0, 0, 0, DA_FOOTPRINT_GAS_SCALAR_U64]); + + let mut db = InMemoryDB::default(); + let l1_block_contract = db.load_account(L1_BLOCK_CONTRACT).unwrap(); + l1_block_contract + .storage + .insert(L1_BASE_FEE_SLOT, L1_BASE_FEE); + l1_block_contract + .storage + .insert(ECOTONE_L1_BLOB_BASE_FEE_SLOT, L1_BLOB_BASE_FEE); + l1_block_contract + .storage + .insert(ECOTONE_L1_FEE_SCALARS_SLOT, L1_FEE_SCALARS); + l1_block_contract + .storage + .insert(OPERATOR_FEE_SCALARS_SLOT, OPERATOR_FEE); + l1_block_contract.storage.insert( + DA_FOOTPRINT_GAS_SCALAR_SLOT, + DA_FOOTPRINT_GAS_SCALAR_SLOT_VALUE, + ); + db.insert_account_info( + Address::ZERO, + AccountInfo { + balance: U256::from(6000), + ..Default::default() + }, + ); + + let ctx = Context::op() + .with_db(db) + .with_chain(L1BlockInfo { + l2_block: BLOCK_NUM + U256::from(1), // ahead by one block + operator_fee_scalar: Some(U256::from(2)), + operator_fee_constant: Some(U256::from(50)), + ..Default::default() + }) + .with_block(BlockEnv { + number: BLOCK_NUM, + ..Default::default() + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::JOVIAN) + // set the operator fee to a low value + .with_tx( + OpTransaction::builder() + .base(TxEnv::builder().gas_limit(10)) + .enveloped_tx(Some(bytes!("FACADE"))) + .build_fill(), + ); + + let mut evm = ctx.build_op(); + + assert_ne!(evm.ctx().chain().l2_block, BLOCK_NUM); + + let handler = + OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame>::new(); + handler + .validate_against_state_and_deduct_caller(&mut evm) + .unwrap(); + + assert_eq!( + *evm.ctx().chain(), + L1BlockInfo { + l2_block: BLOCK_NUM, + l1_base_fee: L1_BASE_FEE, + l1_base_fee_scalar: U256::from(L1_BASE_FEE_SCALAR), + l1_blob_base_fee: Some(L1_BLOB_BASE_FEE), + l1_blob_base_fee_scalar: Some(U256::from(L1_BLOB_BASE_FEE_SCALAR)), + empty_ecotone_scalars: false, + l1_fee_overhead: None, + operator_fee_scalar: Some(U256::from(OPERATOR_FEE_SCALAR)), + operator_fee_constant: Some(U256::from(OPERATOR_FEE_CONST)), + tx_l1_cost: Some(U256::ZERO), + da_footprint_gas_scalar: Some(DA_FOOTPRINT_GAS_SCALAR), } ); } diff --git a/src/l1block.rs b/src/l1block.rs index 70553ede722..788b47f7df1 100644 --- a/src/l1block.rs +++ b/src/l1block.rs @@ -1,11 +1,11 @@ //! Contains the `[L1BlockInfo]` type and its implementation. use crate::{ constants::{ - BASE_FEE_SCALAR_OFFSET, BLOB_BASE_FEE_SCALAR_OFFSET, ECOTONE_L1_BLOB_BASE_FEE_SLOT, - ECOTONE_L1_FEE_SCALARS_SLOT, EMPTY_SCALARS, L1_BASE_FEE_SLOT, L1_BLOCK_CONTRACT, - L1_OVERHEAD_SLOT, L1_SCALAR_SLOT, NON_ZERO_BYTE_COST, OPERATOR_FEE_CONSTANT_OFFSET, - OPERATOR_FEE_JOVIAN_MULTIPLIER, OPERATOR_FEE_SCALARS_SLOT, OPERATOR_FEE_SCALAR_DECIMAL, - OPERATOR_FEE_SCALAR_OFFSET, + BASE_FEE_SCALAR_OFFSET, BLOB_BASE_FEE_SCALAR_OFFSET, DA_FOOTPRINT_GAS_SCALAR_OFFSET, + DA_FOOTPRINT_GAS_SCALAR_SLOT, ECOTONE_L1_BLOB_BASE_FEE_SLOT, ECOTONE_L1_FEE_SCALARS_SLOT, + EMPTY_SCALARS, L1_BASE_FEE_SLOT, L1_BLOCK_CONTRACT, L1_OVERHEAD_SLOT, L1_SCALAR_SLOT, + NON_ZERO_BYTE_COST, OPERATOR_FEE_CONSTANT_OFFSET, OPERATOR_FEE_JOVIAN_MULTIPLIER, + OPERATOR_FEE_SCALARS_SLOT, OPERATOR_FEE_SCALAR_DECIMAL, OPERATOR_FEE_SCALAR_OFFSET, }, transaction::estimate_tx_compressed_size, OpSpecId, @@ -49,13 +49,84 @@ pub struct L1BlockInfo { pub operator_fee_scalar: Option, /// The current L1 blob base fee scalar. None if Isthmus is not activated. pub operator_fee_constant: Option, + /// Da footprint gas scalar. Used to set the DA footprint block limit on the L2. Always null prior to the Jovian hardfork. + pub da_footprint_gas_scalar: Option, /// True if Ecotone is activated, but the L1 fee scalars have not yet been set. - pub(crate) empty_ecotone_scalars: bool, + pub empty_ecotone_scalars: bool, /// Last calculated l1 fee cost. Uses as a cache between validation and pre execution stages. pub tx_l1_cost: Option, } impl L1BlockInfo { + /// Try to fetch the L1 block info from the database, post-Jovian. + fn try_fetch_jovian(&mut self, db: &mut DB) -> Result<(), DB::Error> { + let da_footprint_gas_scalar_slot = db + .storage(L1_BLOCK_CONTRACT, DA_FOOTPRINT_GAS_SCALAR_SLOT)? + .to_be_bytes::<32>(); + + // Extract the first 2 bytes directly as a u16 in big-endian format + let bytes = [ + da_footprint_gas_scalar_slot[DA_FOOTPRINT_GAS_SCALAR_OFFSET], + da_footprint_gas_scalar_slot[DA_FOOTPRINT_GAS_SCALAR_OFFSET + 1], + ]; + self.da_footprint_gas_scalar = Some(u16::from_be_bytes(bytes)); + + Ok(()) + } + + /// Try to fetch the L1 block info from the database, post-Isthmus. + fn try_fetch_isthmus(&mut self, db: &mut DB) -> Result<(), DB::Error> { + // Post-isthmus L1 block info + let operator_fee_scalars = db + .storage(L1_BLOCK_CONTRACT, OPERATOR_FEE_SCALARS_SLOT)? + .to_be_bytes::<32>(); + + // The `operator_fee_scalar` is stored as a big endian u32 at + // OPERATOR_FEE_SCALAR_OFFSET. + self.operator_fee_scalar = Some(U256::from_be_slice( + operator_fee_scalars[OPERATOR_FEE_SCALAR_OFFSET..OPERATOR_FEE_SCALAR_OFFSET + 4] + .as_ref(), + )); + // The `operator_fee_constant` is stored as a big endian u64 at + // OPERATOR_FEE_CONSTANT_OFFSET. + self.operator_fee_constant = Some(U256::from_be_slice( + operator_fee_scalars[OPERATOR_FEE_CONSTANT_OFFSET..OPERATOR_FEE_CONSTANT_OFFSET + 8] + .as_ref(), + )); + + Ok(()) + } + + /// Try to fetch the L1 block info from the database, post-Ecotone. + fn try_fetch_ecotone(&mut self, db: &mut DB) -> Result<(), DB::Error> { + self.l1_blob_base_fee = Some(db.storage(L1_BLOCK_CONTRACT, ECOTONE_L1_BLOB_BASE_FEE_SLOT)?); + + let l1_fee_scalars = db + .storage(L1_BLOCK_CONTRACT, ECOTONE_L1_FEE_SCALARS_SLOT)? + .to_be_bytes::<32>(); + + self.l1_base_fee_scalar = U256::from_be_slice( + l1_fee_scalars[BASE_FEE_SCALAR_OFFSET..BASE_FEE_SCALAR_OFFSET + 4].as_ref(), + ); + + let l1_blob_base_fee = U256::from_be_slice( + l1_fee_scalars[BLOB_BASE_FEE_SCALAR_OFFSET..BLOB_BASE_FEE_SCALAR_OFFSET + 4].as_ref(), + ); + self.l1_blob_base_fee_scalar = Some(l1_blob_base_fee); + + // Check if the L1 fee scalars are empty. If so, we use the Bedrock cost function. + // The L1 fee overhead is only necessary if `empty_ecotone_scalars` is true, as it was deprecated in Ecotone. + self.empty_ecotone_scalars = l1_blob_base_fee.is_zero() + && l1_fee_scalars[BASE_FEE_SCALAR_OFFSET..BLOB_BASE_FEE_SCALAR_OFFSET + 4] + == EMPTY_SCALARS; + self.l1_fee_overhead = self + .empty_ecotone_scalars + .then(|| db.storage(L1_BLOCK_CONTRACT, L1_OVERHEAD_SLOT)) + .transpose()?; + + Ok(()) + } + /// Try to fetch the L1 block info from the database. pub fn try_fetch( db: &mut DB, @@ -68,88 +139,33 @@ impl L1BlockInfo { let _ = db.basic(L1_BLOCK_CONTRACT)?; } - let l1_base_fee = db.storage(L1_BLOCK_CONTRACT, L1_BASE_FEE_SLOT)?; + let mut out = L1BlockInfo { + l2_block, + l1_base_fee: db.storage(L1_BLOCK_CONTRACT, L1_BASE_FEE_SLOT)?, + ..Default::default() + }; + // Post-Ecotone if !spec_id.is_enabled_in(OpSpecId::ECOTONE) { - let l1_fee_overhead = db.storage(L1_BLOCK_CONTRACT, L1_OVERHEAD_SLOT)?; - let l1_fee_scalar = db.storage(L1_BLOCK_CONTRACT, L1_SCALAR_SLOT)?; - - Ok(L1BlockInfo { - l2_block, - l1_base_fee, - l1_fee_overhead: Some(l1_fee_overhead), - l1_base_fee_scalar: l1_fee_scalar, - ..Default::default() - }) - } else { - let l1_blob_base_fee = db.storage(L1_BLOCK_CONTRACT, ECOTONE_L1_BLOB_BASE_FEE_SLOT)?; - let l1_fee_scalars = db - .storage(L1_BLOCK_CONTRACT, ECOTONE_L1_FEE_SCALARS_SLOT)? - .to_be_bytes::<32>(); - - let l1_base_fee_scalar = U256::from_be_slice( - l1_fee_scalars[BASE_FEE_SCALAR_OFFSET..BASE_FEE_SCALAR_OFFSET + 4].as_ref(), - ); - let l1_blob_base_fee_scalar = U256::from_be_slice( - l1_fee_scalars[BLOB_BASE_FEE_SCALAR_OFFSET..BLOB_BASE_FEE_SCALAR_OFFSET + 4] - .as_ref(), - ); - - // Check if the L1 fee scalars are empty. If so, we use the Bedrock cost function. - // The L1 fee overhead is only necessary if `empty_ecotone_scalars` is true, as it was deprecated in Ecotone. - let empty_ecotone_scalars = l1_blob_base_fee.is_zero() - && l1_fee_scalars[BASE_FEE_SCALAR_OFFSET..BLOB_BASE_FEE_SCALAR_OFFSET + 4] - == EMPTY_SCALARS; - let l1_fee_overhead = empty_ecotone_scalars - .then(|| db.storage(L1_BLOCK_CONTRACT, L1_OVERHEAD_SLOT)) - .transpose()?; - - if spec_id.is_enabled_in(OpSpecId::ISTHMUS) { - let operator_fee_scalars = db - .storage(L1_BLOCK_CONTRACT, OPERATOR_FEE_SCALARS_SLOT)? - .to_be_bytes::<32>(); - - // Post-isthmus L1 block info - // The `operator_fee_scalar` is stored as a big endian u32 at - // OPERATOR_FEE_SCALAR_OFFSET. - let operator_fee_scalar = U256::from_be_slice( - operator_fee_scalars - [OPERATOR_FEE_SCALAR_OFFSET..OPERATOR_FEE_SCALAR_OFFSET + 4] - .as_ref(), - ); - // The `operator_fee_constant` is stored as a big endian u64 at - // OPERATOR_FEE_CONSTANT_OFFSET. - let operator_fee_constant = U256::from_be_slice( - operator_fee_scalars - [OPERATOR_FEE_CONSTANT_OFFSET..OPERATOR_FEE_CONSTANT_OFFSET + 8] - .as_ref(), - ); - Ok(L1BlockInfo { - l2_block, - l1_base_fee, - l1_base_fee_scalar, - l1_blob_base_fee: Some(l1_blob_base_fee), - l1_blob_base_fee_scalar: Some(l1_blob_base_fee_scalar), - empty_ecotone_scalars, - l1_fee_overhead, - operator_fee_scalar: Some(operator_fee_scalar), - operator_fee_constant: Some(operator_fee_constant), - tx_l1_cost: None, - }) - } else { - // Pre-isthmus L1 block info - Ok(L1BlockInfo { - l2_block, - l1_base_fee, - l1_base_fee_scalar, - l1_blob_base_fee: Some(l1_blob_base_fee), - l1_blob_base_fee_scalar: Some(l1_blob_base_fee_scalar), - empty_ecotone_scalars, - l1_fee_overhead, - ..Default::default() - }) - } + out.l1_base_fee_scalar = db.storage(L1_BLOCK_CONTRACT, L1_SCALAR_SLOT)?; + out.l1_fee_overhead = Some(db.storage(L1_BLOCK_CONTRACT, L1_OVERHEAD_SLOT)?); + + return Ok(out); } + + out.try_fetch_ecotone(db)?; + + // Post-Isthmus L1 block info + if spec_id.is_enabled_in(OpSpecId::ISTHMUS) { + out.try_fetch_isthmus(db)?; + } + + // Pre-Jovian + if spec_id.is_enabled_in(OpSpecId::JOVIAN) { + out.try_fetch_jovian(db)?; + } + + Ok(out) } /// Calculate the operator fee for executing this transaction. diff --git a/src/precompiles.rs b/src/precompiles.rs index d0f67bddb4b..f27bf339b5f 100644 --- a/src/precompiles.rs +++ b/src/precompiles.rs @@ -34,7 +34,7 @@ impl OpPrecompiles { | OpSpecId::ECOTONE) => Precompiles::new(spec.into_eth_spec().into()), OpSpecId::FJORD => fjord(), OpSpecId::GRANITE | OpSpecId::HOLOCENE => granite(), - OpSpecId::ISTHMUS | OpSpecId::JOVIAN | OpSpecId::INTEROP | OpSpecId::OSAKA => isthmus(), + OpSpecId::ISTHMUS | OpSpecId::INTEROP | OpSpecId::OSAKA | OpSpecId::JOVIAN => isthmus(), }; Self { diff --git a/src/spec.rs b/src/spec.rs index c36ff8b2932..35b15c3c9a9 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -40,8 +40,8 @@ impl OpSpecId { Self::BEDROCK | Self::REGOLITH => SpecId::MERGE, Self::CANYON => SpecId::SHANGHAI, Self::ECOTONE | Self::FJORD | Self::GRANITE | Self::HOLOCENE => SpecId::CANCUN, - Self::ISTHMUS | Self::JOVIAN | Self::INTEROP => SpecId::PRAGUE, - Self::OSAKA => SpecId::OSAKA, + Self::ISTHMUS | Self::INTEROP => SpecId::PRAGUE, + Self::JOVIAN | Self::OSAKA => SpecId::OSAKA, } } @@ -194,6 +194,25 @@ mod tests { (OpSpecId::FJORD, true), ], ), + ( + OpSpecId::JOVIAN, + vec![ + (SpecId::PRAGUE, true), + (SpecId::SHANGHAI, true), + (SpecId::CANCUN, true), + (SpecId::MERGE, true), + (SpecId::OSAKA, true), + ], + vec![ + (OpSpecId::BEDROCK, true), + (OpSpecId::REGOLITH, true), + (OpSpecId::CANYON, true), + (OpSpecId::ECOTONE, true), + (OpSpecId::FJORD, true), + (OpSpecId::HOLOCENE, true), + (OpSpecId::ISTHMUS, true), + ], + ), ]; for (op_spec, eth_tests, op_tests) in test_cases { From e4e3e5065354de4dcbfe2a529f998ebc3f0ff1d1 Mon Sep 17 00:00:00 2001 From: rakita Date: Sun, 28 Sep 2025 13:24:25 +0200 Subject: [PATCH 094/225] chore(op-revm): propagate optional_fee_charge feature (bluealloy/revm#3020) --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 298c2775908..e507af218f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,6 +61,7 @@ optional_block_gas_limit = ["revm/optional_block_gas_limit"] optional_eip3541 = ["revm/optional_eip3541"] optional_eip3607 = ["revm/optional_eip3607"] optional_no_base_fee = ["revm/optional_no_base_fee"] +optional_fee_charge = ["revm/optional_fee_charge"] # See comments in `revm-precompile` secp256k1 = ["revm/secp256k1"] From 601c169eeefc4be5cb305ad421593baf05fcd4c2 Mon Sep 17 00:00:00 2001 From: rakita Date: Wed, 1 Oct 2025 20:41:44 +0200 Subject: [PATCH 095/225] chore: add ensure_enough_balance helper (bluealloy/revm#3033) --- src/handler.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index 4f24c5fdb25..dea8e11c79e 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -158,8 +158,6 @@ where )?; } - let max_balance_spending = tx.max_balance_spending()?.saturating_add(additional_cost); - // old balance is journaled before mint is incremented. let old_balance = caller_account.info.balance; @@ -170,14 +168,16 @@ where // Check if account has enough balance for `gas_limit * max_fee`` and value transfer. // Transfer will be done inside `*_inner` functions. - if !is_deposit && max_balance_spending > new_balance && !is_balance_check_disabled { - // skip max balance check for deposit transactions. - // this check for deposit was skipped previously in `validate_tx_against_state` function - return Err(InvalidTransaction::LackOfFundForMaxFee { - fee: Box::new(max_balance_spending), - balance: Box::new(new_balance), - } - .into()); + if !is_deposit && !is_balance_check_disabled { + // check additional cost and deduct it from the caller's balances + let Some(balance) = new_balance.checked_sub(additional_cost) else { + return Err(InvalidTransaction::LackOfFundForMaxFee { + fee: Box::new(additional_cost), + balance: Box::new(new_balance), + } + .into()); + }; + tx.ensure_enough_balance(balance)?; } let effective_balance_spending = tx From c1813d72ee0373029e8408eb23ea0997b93a3242 Mon Sep 17 00:00:00 2001 From: rakita Date: Thu, 2 Oct 2025 12:19:26 +0200 Subject: [PATCH 096/225] chore: EvmTr and InspectorEvmTr receive all/all_mut fn (bluealloy/revm#3037) --- src/evm.rs | 78 ++++++++++++++++++++++++++---------------------------- 1 file changed, 37 insertions(+), 41 deletions(-) diff --git a/src/evm.rs b/src/evm.rs index ad9e965783d..a93a172756c 100644 --- a/src/evm.rs +++ b/src/evm.rs @@ -65,38 +65,30 @@ where { type Inspector = INSP; - fn inspector(&mut self) -> &mut Self::Inspector { - &mut self.0.inspector - } - - fn ctx_inspector(&mut self) -> (&mut Self::Context, &mut Self::Inspector) { - (&mut self.0.ctx, &mut self.0.inspector) - } - - fn ctx_inspector_frame( - &mut self, - ) -> (&mut Self::Context, &mut Self::Inspector, &mut Self::Frame) { - ( - &mut self.0.ctx, - &mut self.0.inspector, - self.0.frame_stack.get(), - ) + #[inline] + fn all_inspector( + &self, + ) -> ( + &Self::Context, + &Self::Instructions, + &Self::Precompiles, + &FrameStack, + &Self::Inspector, + ) { + self.0.all_inspector() } - fn ctx_inspector_frame_instructions( + #[inline] + fn all_mut_inspector( &mut self, ) -> ( &mut Self::Context, - &mut Self::Inspector, - &mut Self::Frame, &mut Self::Instructions, + &mut Self::Precompiles, + &mut FrameStack, + &mut Self::Inspector, ) { - ( - &mut self.0.ctx, - &mut self.0.inspector, - self.0.frame_stack.get(), - &mut self.0.instruction, - ) + self.0.all_mut_inspector() } } @@ -111,24 +103,28 @@ where type Precompiles = P; type Frame = EthFrame; - fn ctx(&mut self) -> &mut Self::Context { - &mut self.0.ctx - } - - fn ctx_ref(&self) -> &Self::Context { - &self.0.ctx - } - - fn ctx_instructions(&mut self) -> (&mut Self::Context, &mut Self::Instructions) { - (&mut self.0.ctx, &mut self.0.instruction) - } - - fn ctx_precompiles(&mut self) -> (&mut Self::Context, &mut Self::Precompiles) { - (&mut self.0.ctx, &mut self.0.precompiles) + #[inline] + fn all( + &self, + ) -> ( + &Self::Context, + &Self::Instructions, + &Self::Precompiles, + &FrameStack, + ) { + self.0.all() } - fn frame_stack(&mut self) -> &mut FrameStack { - &mut self.0.frame_stack + #[inline] + fn all_mut( + &mut self, + ) -> ( + &mut Self::Context, + &mut Self::Instructions, + &mut Self::Precompiles, + &mut FrameStack, + ) { + self.0.all_mut() } fn frame_init( From e2344632b2d5083a74e5ea149b90ad5623a92226 Mon Sep 17 00:00:00 2001 From: rakita Date: Thu, 2 Oct 2025 15:21:57 +0200 Subject: [PATCH 097/225] chore: helper caller_initial_modification added (bluealloy/revm#3032) * chore: helper caller_touch_and_change added * move caller_touch_and_change to Account * rn artifact from merge * rename to caller_initial_modification --- src/handler.rs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index dea8e11c79e..862f14e99a0 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -158,9 +158,6 @@ where )?; } - // old balance is journaled before mint is incremented. - let old_balance = caller_account.info.balance; - // If the transaction is a deposit with a `mint` value, add the mint value // in wei to the caller's balance. This should be persisted to the database // prior to the rest of execution. @@ -202,14 +199,8 @@ where new_balance = new_balance.max(tx.value()); } - // Touch account so we know it is changed. - caller_account.mark_touch(); - caller_account.info.balance = new_balance; - - // Bump the nonce for calls. Nonce for CREATE will be bumped in `handle_create`. - if tx.kind().is_call() { - caller_account.info.nonce = caller_account.info.nonce.saturating_add(1); - } + let old_balance = + caller_account.caller_initial_modification(new_balance, tx.kind().is_call()); // NOTE: all changes to the caller account should journaled so in case of error // we can revert the changes. From 33672d5b75b499ef7707eabeefc6d025b91d55f4 Mon Sep 17 00:00:00 2001 From: rakita Date: Thu, 2 Oct 2025 15:48:23 +0200 Subject: [PATCH 098/225] chore: helper function gas_balance_spending (bluealloy/revm#3030) * chore: helper function effective_balance_spending_without_value * add box * rm function that is part of different PR * nits and cleanup * gas_balance_spending --- src/handler.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index 862f14e99a0..c429a5dbf4f 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -177,12 +177,10 @@ where tx.ensure_enough_balance(balance)?; } - let effective_balance_spending = tx - .effective_balance_spending(basefee, blob_price) - .expect("effective balance is always smaller than max balance so it can't overflow"); - // subtracting max balance spending with value that is going to be deducted later in the call. - let gas_balance_spending = effective_balance_spending - tx.value(); + let gas_balance_spending = tx + .gas_balance_spending(basefee, blob_price) + .expect("effective balance is always smaller than max balance so it can't overflow"); // If the transaction is not a deposit transaction, subtract the L1 data fee from the // caller's balance directly after minting the requested amount of ETH. From 968c3c83766d34a3e6a044ff1a917b68ccfc4b17 Mon Sep 17 00:00:00 2001 From: rakita Date: Tue, 7 Oct 2025 00:34:30 +0200 Subject: [PATCH 099/225] chore: changelog update for v87 (bluealloy/revm#3056) * bump: tag v87 revm v29.0.1 * chore: changelog update v87 --- CHANGELOG.md | 6 ++++++ Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3486bf1404f..05e6874cf9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [10.1.0](https://github.com/bluealloy/revm/compare/op-revm-v10.0.0...op-revm-v10.1.0) - 2025-09-23 + +### Added + +- *(op-revm)* Add an option to disable "fee-charge" on `op-revm` ([#2980](https://github.com/bluealloy/revm/pull/2980)) + ## [10.0.0](https://github.com/bluealloy/revm/compare/op-revm-v9.0.1...op-revm-v10.0.0) - 2025-08-23 ### Added diff --git a/Cargo.toml b/Cargo.toml index e507af218f1..2bd7247379c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "10.0.0" +version = "10.1.0" authors.workspace = true edition.workspace = true keywords.workspace = true From 14cc438e6223d734613302a2d38e8876a065ebf9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 7 Oct 2025 03:49:11 +0200 Subject: [PATCH 100/225] chore: release (bluealloy/revm#2958) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 32 ++++++++++++++++++++++++++++++++ Cargo.toml | 2 +- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05e6874cf9c..17646f69c0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [11.0.0](https://github.com/bluealloy/revm/compare/op-revm-v10.1.0...op-revm-v11.0.0) - 2025-10-07 + +### Added + +- *(jovian)* add da footprint block limit. ([#3003](https://github.com/bluealloy/revm/pull/3003)) +- *(op-revm)* implement jovian operator fee fix ([#2996](https://github.com/bluealloy/revm/pull/2996)) +- *(op-revm)* Add an option to disable "fee-charge" on `op-revm` ([#2980](https://github.com/bluealloy/revm/pull/2980)) +- [**breaking**] Remove kzg-rs ([#2909](https://github.com/bluealloy/revm/pull/2909)) + +### Fixed + +- add missing is_fee_charge_disabled check ([#3007](https://github.com/bluealloy/revm/pull/3007)) +- Apply spelling corrections from PRs #2926, #2915, #2908 ([#2978](https://github.com/bluealloy/revm/pull/2978)) +- *(op-revm)* clear enveloped_tx for deposit txs in build_fill and align docs ([#2957](https://github.com/bluealloy/revm/pull/2957)) + +### Other + +- changelog update for v87 ([#3056](https://github.com/bluealloy/revm/pull/3056)) +- add boundless ([#3043](https://github.com/bluealloy/revm/pull/3043)) +- helper function gas_balance_spending ([#3030](https://github.com/bluealloy/revm/pull/3030)) +- helper caller_initial_modification added ([#3032](https://github.com/bluealloy/revm/pull/3032)) +- EvmTr and InspectorEvmTr receive all/all_mut fn ([#3037](https://github.com/bluealloy/revm/pull/3037)) +- add ensure_enough_balance helper ([#3033](https://github.com/bluealloy/revm/pull/3033)) +- *(op-revm)* propagate optional_fee_charge feature ([#3020](https://github.com/bluealloy/revm/pull/3020)) +- Set l2_block in try_fetch for pre-Isthmus forks; add reload tests ([#2994](https://github.com/bluealloy/revm/pull/2994)) +- prealloc few frames ([#2965](https://github.com/bluealloy/revm/pull/2965)) +- treat empty input as zero operator fee in operator_fee_charge ([#2973](https://github.com/bluealloy/revm/pull/2973)) +- add SECURITY.md ([#2956](https://github.com/bluealloy/revm/pull/2956)) +- *(op-revm)* rm redundant phantom ([#2943](https://github.com/bluealloy/revm/pull/2943)) +- *(op-revm)* add serialize DepositTransactionParts test ([#2942](https://github.com/bluealloy/revm/pull/2942)) +- *(handler)* provide `&CallInputs`to`PrecompileProvider::run` ([#2921](https://github.com/bluealloy/revm/pull/2921)) + ## [10.1.0](https://github.com/bluealloy/revm/compare/op-revm-v10.0.0...op-revm-v10.1.0) - 2025-09-23 ### Added diff --git a/Cargo.toml b/Cargo.toml index 2bd7247379c..b32dc81b606 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "10.1.0" +version = "11.0.0" authors.workspace = true edition.workspace = true keywords.workspace = true From 727ab9add6a7a4212a156846d6da01ef2249016f Mon Sep 17 00:00:00 2001 From: sashass1315 Date: Tue, 7 Oct 2025 14:35:32 +0300 Subject: [PATCH 101/225] fix(op-revm): return error instead of panic when enveloped_tx is missing (bluealloy/revm#3055) * fix(op-revm): return error instead of panic when enveloped_tx is missing * Update crates/op-revm/src/handler.rs Co-authored-by: rakita * fmt --------- Co-authored-by: rakita --- src/handler.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index c429a5dbf4f..5121c8f96f8 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -124,11 +124,11 @@ where if !ctx.cfg().is_fee_charge_disabled() { // account for additional cost of l1 fee and operator fee - let enveloped_tx = ctx - .tx() - .enveloped_tx() - .expect("all not deposit tx have enveloped tx") - .clone(); + let Some(enveloped_tx) = ctx.tx().enveloped_tx().cloned() else { + return Err(ERROR::from_string( + "[OPTIMISM] Failed to load enveloped transaction.".into(), + )); + }; // compute L1 cost additional_cost = ctx.chain_mut().calculate_tx_l1_cost(&enveloped_tx, spec); From 1a1d914170bacc2ebc51aae0fd7922c4c53b942d Mon Sep 17 00:00:00 2001 From: rakita Date: Thu, 9 Oct 2025 13:42:46 +0200 Subject: [PATCH 102/225] chore(op): split paths for deposit tx in caller deduction (bluealloy/revm#3041) --- src/handler.rs | 113 +++++++++++++++++++++++++++++-------------------- 1 file changed, 68 insertions(+), 45 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index 5121c8f96f8..b3f67d9fb63 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -106,41 +106,69 @@ where let is_eip3607_disabled = ctx.cfg().is_eip3607_disabled(); let is_nonce_check_disabled = ctx.cfg().is_nonce_check_disabled(); - let mint = if is_deposit { - ctx.tx().mint().unwrap_or_default() - } else { - 0 - }; + if is_deposit { + let (block, tx, cfg, journal, _, _) = evm.ctx().all_mut(); + let basefee = block.basefee() as u128; + let blob_price = block.blob_gasprice().unwrap_or_default(); + // deposit skips max fee check and just deducts the effective balance spending. - let mut additional_cost = U256::ZERO; + let caller_account = journal.load_account_code(tx.caller())?.data; - // The L1-cost fee is only computed for Optimism non-deposit transactions. - if !is_deposit { - // L1 block info is stored in the context for later use. - // and it will be reloaded from the database if it is not for the current block. - if ctx.chain().l2_block != block_number { - *ctx.chain_mut() = L1BlockInfo::try_fetch(ctx.db_mut(), block_number, spec)?; + let effective_balance_spending = tx + .effective_balance_spending(basefee, blob_price) + .expect("Deposit transaction effective balance spending overflow") + - tx.value(); + + // Mind value should be added first before subtracting the effective balance spending. + let mut new_balance = caller_account + .info + .balance + .saturating_add(U256::from(tx.mint().unwrap_or_default())) + .saturating_sub(effective_balance_spending); + + if cfg.is_balance_check_disabled() { + // Make sure the caller's balance is at least the value of the transaction. + // this is not consensus critical, and it is used in testing. + new_balance = new_balance.max(tx.value()); } - if !ctx.cfg().is_fee_charge_disabled() { - // account for additional cost of l1 fee and operator fee - let Some(enveloped_tx) = ctx.tx().enveloped_tx().cloned() else { - return Err(ERROR::from_string( - "[OPTIMISM] Failed to load enveloped transaction.".into(), - )); - }; - - // compute L1 cost - additional_cost = ctx.chain_mut().calculate_tx_l1_cost(&enveloped_tx, spec); - - // compute operator fee - if spec.is_enabled_in(OpSpecId::ISTHMUS) { - let gas_limit = U256::from(ctx.tx().gas_limit()); - let operator_fee_charge = - ctx.chain() - .operator_fee_charge(&enveloped_tx, gas_limit, spec); - additional_cost = additional_cost.saturating_add(operator_fee_charge); - } + let old_balance = + caller_account.caller_initial_modification(new_balance, tx.kind().is_call()); + + // NOTE: all changes to the caller account should journaled so in case of error + // we can revert the changes. + journal.caller_accounting_journal_entry(tx.caller(), old_balance, tx.kind().is_call()); + + return Ok(()); + } + + let mut additional_cost = U256::ZERO; + + // L1 block info is stored in the context for later use. + // and it will be reloaded from the database if it is not for the current block. + if ctx.chain().l2_block != block_number { + *ctx.chain_mut() = L1BlockInfo::try_fetch(ctx.db_mut(), block_number, spec)?; + } + + if !ctx.cfg().is_fee_charge_disabled() { + // account for additional cost of l1 fee and operator fee + // account for additional cost of l1 fee and operator fee + let Some(enveloped_tx) = ctx.tx().enveloped_tx().cloned() else { + return Err(ERROR::from_string( + "[OPTIMISM] Failed to load enveloped transaction.".into(), + )); + }; + + // compute L1 cost + additional_cost = ctx.chain_mut().calculate_tx_l1_cost(&enveloped_tx, spec); + + // compute operator fee + if spec.is_enabled_in(OpSpecId::ISTHMUS) { + let gas_limit = U256::from(ctx.tx().gas_limit()); + let operator_fee_charge = + ctx.chain() + .operator_fee_charge(&enveloped_tx, gas_limit, spec); + additional_cost = additional_cost.saturating_add(operator_fee_charge); } } @@ -148,24 +176,19 @@ where let caller_account = journal.load_account_code(tx.caller())?.data; - if !is_deposit { - // validates account nonce and code - validate_account_nonce_and_code( - &mut caller_account.info, - tx.nonce(), - is_eip3607_disabled, - is_nonce_check_disabled, - )?; - } + // validates account nonce and code + validate_account_nonce_and_code( + &mut caller_account.info, + tx.nonce(), + is_eip3607_disabled, + is_nonce_check_disabled, + )?; - // If the transaction is a deposit with a `mint` value, add the mint value - // in wei to the caller's balance. This should be persisted to the database - // prior to the rest of execution. - let mut new_balance = caller_account.info.balance.saturating_add(U256::from(mint)); + let mut new_balance = caller_account.info.balance; // Check if account has enough balance for `gas_limit * max_fee`` and value transfer. // Transfer will be done inside `*_inner` functions. - if !is_deposit && !is_balance_check_disabled { + if !is_balance_check_disabled { // check additional cost and deduct it from the caller's balances let Some(balance) = new_balance.checked_sub(additional_cost) else { return Err(InvalidTransaction::LackOfFundForMaxFee { From ac287353ba7f2436c0dafff876249b36ac550ead Mon Sep 17 00:00:00 2001 From: rakita Date: Thu, 9 Oct 2025 23:43:08 +0200 Subject: [PATCH 103/225] chore: backport v89 changelog (bluealloy/revm#3075) --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17646f69c0b..2b1f110ee7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [10.1.1](https://github.com/bluealloy/revm/compare/op-revm-v10.0.0...op-revm-v10.1.1) - 2025-09-23 + ## [11.0.0](https://github.com/bluealloy/revm/compare/op-revm-v10.1.0...op-revm-v11.0.0) - 2025-10-07 ### Added From 54bf6b282f94f3047bb639e8fbb35007c5c22ca6 Mon Sep 17 00:00:00 2001 From: rakita Date: Thu, 9 Oct 2025 23:56:48 +0200 Subject: [PATCH 104/225] chore(op): backport of #3073 fix for l1block info (bluealloy/revm#3076) --- src/handler.rs | 107 +++++++++++++++++++++++++++++++++++++++++++------ src/l1block.rs | 4 +- 2 files changed, 96 insertions(+), 15 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index b3f67d9fb63..98c054f4672 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -146,7 +146,7 @@ where // L1 block info is stored in the context for later use. // and it will be reloaded from the database if it is not for the current block. - if ctx.chain().l2_block != block_number { + if ctx.chain().l2_block != Some(block_number) { *ctx.chain_mut() = L1BlockInfo::try_fetch(ctx.db_mut(), block_number, spec)?; } @@ -705,6 +705,7 @@ mod tests { l1_base_fee: U256::from(1_000), l1_fee_overhead: Some(U256::from(1_000)), l1_base_fee_scalar: U256::from(1_000), + l2_block: Some(U256::from(0)), ..Default::default() }) .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH) @@ -773,7 +774,7 @@ mod tests { let ctx = Context::op() .with_db(db) .with_chain(L1BlockInfo { - l2_block: BLOCK_NUM + U256::from(1), // ahead by one block + l2_block: Some(BLOCK_NUM + U256::from(1)), // ahead by one block ..Default::default() }) .with_block(BlockEnv { @@ -784,7 +785,7 @@ mod tests { let mut evm = ctx.build_op(); - assert_ne!(evm.ctx().chain().l2_block, BLOCK_NUM); + assert_ne!(evm.ctx().chain().l2_block, Some(BLOCK_NUM)); let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame>::new(); @@ -795,7 +796,7 @@ mod tests { assert_eq!( *evm.ctx().chain(), L1BlockInfo { - l2_block: BLOCK_NUM, + l2_block: Some(BLOCK_NUM), l1_base_fee: L1_BASE_FEE, l1_base_fee_scalar: U256::from(L1_BASE_FEE_SCALAR), l1_blob_base_fee: Some(L1_BLOB_BASE_FEE), @@ -862,7 +863,7 @@ mod tests { let ctx = Context::op() .with_db(db) .with_chain(L1BlockInfo { - l2_block: BLOCK_NUM + U256::from(1), // ahead by one block + l2_block: Some(BLOCK_NUM + U256::from(1)), // ahead by one block operator_fee_scalar: Some(U256::from(2)), operator_fee_constant: Some(U256::from(50)), ..Default::default() @@ -882,7 +883,7 @@ mod tests { let mut evm = ctx.build_op(); - assert_ne!(evm.ctx().chain().l2_block, BLOCK_NUM); + assert_ne!(evm.ctx().chain().l2_block, Some(BLOCK_NUM)); let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame>::new(); @@ -893,7 +894,7 @@ mod tests { assert_eq!( *evm.ctx().chain(), L1BlockInfo { - l2_block: BLOCK_NUM, + l2_block: Some(BLOCK_NUM), l1_base_fee: L1_BASE_FEE, l1_base_fee_scalar: U256::from(L1_BASE_FEE_SCALAR), l1_blob_base_fee: Some(L1_BLOB_BASE_FEE), @@ -932,7 +933,7 @@ mod tests { let ctx = Context::op() .with_db(db) .with_chain(L1BlockInfo { - l2_block: BLOCK_NUM + U256::from(1), + l2_block: Some(BLOCK_NUM + U256::from(1)), ..Default::default() }) .with_block(BlockEnv { @@ -942,7 +943,7 @@ mod tests { .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); let mut evm = ctx.build_op(); - assert_ne!(evm.ctx().chain().l2_block, BLOCK_NUM); + assert_ne!(evm.ctx().chain().l2_block, Some(BLOCK_NUM)); let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame>::new(); @@ -953,7 +954,7 @@ mod tests { assert_eq!( *evm.ctx().chain(), L1BlockInfo { - l2_block: BLOCK_NUM, + l2_block: Some(BLOCK_NUM), l1_base_fee: L1_BASE_FEE, l1_fee_overhead: Some(L1_FEE_OVERHEAD), l1_base_fee_scalar: U256::from(L1_BASE_FEE_SCALAR), @@ -992,7 +993,7 @@ mod tests { let ctx = Context::op() .with_db(db) .with_chain(L1BlockInfo { - l2_block: BLOCK_NUM + U256::from(1), + l2_block: Some(BLOCK_NUM + U256::from(1)), ..Default::default() }) .with_block(BlockEnv { @@ -1002,7 +1003,7 @@ mod tests { .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ECOTONE); let mut evm = ctx.build_op(); - assert_ne!(evm.ctx().chain().l2_block, BLOCK_NUM); + assert_ne!(evm.ctx().chain().l2_block, Some(BLOCK_NUM)); let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame>::new(); @@ -1013,7 +1014,7 @@ mod tests { assert_eq!( *evm.ctx().chain(), L1BlockInfo { - l2_block: BLOCK_NUM, + l2_block: Some(BLOCK_NUM), l1_base_fee: L1_BASE_FEE, l1_base_fee_scalar: U256::from(L1_BASE_FEE_SCALAR), l1_blob_base_fee: Some(L1_BLOB_BASE_FEE), @@ -1026,6 +1027,82 @@ mod tests { ); } + #[test] + fn test_load_l1_block_info_isthmus_none() { + const BLOCK_NUM: U256 = uint!(100_U256); + const L1_BASE_FEE: U256 = uint!(1_U256); + const L1_BLOB_BASE_FEE: U256 = uint!(2_U256); + const L1_BASE_FEE_SCALAR: u64 = 3; + const L1_BLOB_BASE_FEE_SCALAR: u64 = 4; + const L1_FEE_SCALARS: U256 = U256::from_limbs([ + 0, + (L1_BASE_FEE_SCALAR << (64 - BASE_FEE_SCALAR_OFFSET * 2)) | L1_BLOB_BASE_FEE_SCALAR, + 0, + 0, + ]); + const OPERATOR_FEE_SCALAR: u64 = 5; + const OPERATOR_FEE_CONST: u64 = 6; + const OPERATOR_FEE: U256 = + U256::from_limbs([OPERATOR_FEE_CONST, OPERATOR_FEE_SCALAR, 0, 0]); + + let mut db = InMemoryDB::default(); + let l1_block_contract = db.load_account(L1_BLOCK_CONTRACT).unwrap(); + l1_block_contract + .storage + .insert(L1_BASE_FEE_SLOT, L1_BASE_FEE); + l1_block_contract + .storage + .insert(ECOTONE_L1_BLOB_BASE_FEE_SLOT, L1_BLOB_BASE_FEE); + l1_block_contract + .storage + .insert(ECOTONE_L1_FEE_SCALARS_SLOT, L1_FEE_SCALARS); + l1_block_contract + .storage + .insert(OPERATOR_FEE_SCALARS_SLOT, OPERATOR_FEE); + db.insert_account_info( + Address::ZERO, + AccountInfo { + balance: U256::from(1000), + ..Default::default() + }, + ); + + let ctx = Context::op() + .with_db(db) + .with_block(BlockEnv { + number: BLOCK_NUM, + ..Default::default() + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); + + let mut evm = ctx.build_op(); + + assert_ne!(evm.ctx().chain().l2_block, Some(BLOCK_NUM)); + + let handler = + OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame>::new(); + handler + .validate_against_state_and_deduct_caller(&mut evm) + .unwrap(); + + assert_eq!( + *evm.ctx().chain(), + L1BlockInfo { + l2_block: Some(BLOCK_NUM), + l1_base_fee: L1_BASE_FEE, + l1_base_fee_scalar: U256::from(L1_BASE_FEE_SCALAR), + l1_blob_base_fee: Some(L1_BLOB_BASE_FEE), + l1_blob_base_fee_scalar: Some(U256::from(L1_BLOB_BASE_FEE_SCALAR)), + empty_ecotone_scalars: false, + l1_fee_overhead: None, + operator_fee_scalar: Some(U256::from(OPERATOR_FEE_SCALAR)), + operator_fee_constant: Some(U256::from(OPERATOR_FEE_CONST)), + tx_l1_cost: Some(U256::ZERO), + ..Default::default() + } + ); + } + #[test] fn test_remove_l1_cost() { let caller = Address::ZERO; @@ -1043,6 +1120,7 @@ mod tests { l1_base_fee: U256::from(1_000), l1_fee_overhead: Some(U256::from(1_000)), l1_base_fee_scalar: U256::from(1_000), + l2_block: Some(U256::from(0)), ..Default::default() }) .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH) @@ -1085,6 +1163,7 @@ mod tests { .with_chain(L1BlockInfo { operator_fee_scalar: Some(U256::from(10_000_000)), operator_fee_constant: Some(U256::from(50)), + l2_block: Some(U256::from(0)), ..Default::default() }) .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS) @@ -1126,6 +1205,7 @@ mod tests { .with_chain(L1BlockInfo { operator_fee_scalar: Some(U256::from(2)), operator_fee_constant: Some(U256::from(50)), + l2_block: Some(U256::from(0)), ..Default::default() }) .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::JOVIAN) @@ -1167,6 +1247,7 @@ mod tests { l1_base_fee: U256::from(1_000), l1_fee_overhead: Some(U256::from(1_000)), l1_base_fee_scalar: U256::from(1_000), + l2_block: Some(U256::from(0)), ..Default::default() }) .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH) diff --git a/src/l1block.rs b/src/l1block.rs index 788b47f7df1..fd92a32c461 100644 --- a/src/l1block.rs +++ b/src/l1block.rs @@ -34,7 +34,7 @@ use revm::{ pub struct L1BlockInfo { /// The L2 block number. If not same as the one in the context, /// L1BlockInfo is not valid and will be reloaded from the database. - pub l2_block: U256, + pub l2_block: Option, /// The base fee of the L1 origin block. pub l1_base_fee: U256, /// The current L1 fee overhead. None if Ecotone is activated. @@ -140,7 +140,7 @@ impl L1BlockInfo { } let mut out = L1BlockInfo { - l2_block, + l2_block: Some(l2_block), l1_base_fee: db.storage(L1_BLOCK_CONTRACT, L1_BASE_FEE_SLOT)?, ..Default::default() }; From 0ed8f0d2585deb015c5f46c39a0cc5ce6d97bc07 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 10 Oct 2025 00:35:05 +0200 Subject: [PATCH 105/225] chore: release (bluealloy/revm#3061) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 12 ++++++++++++ Cargo.toml | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b1f110ee7c..bcccd1dcc8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [11.0.1](https://github.com/bluealloy/revm/compare/op-revm-v11.0.0...op-revm-v11.0.1) - 2025-10-09 + +### Fixed + +- *(op-revm)* return error instead of panic when enveloped_tx is missing ([#3055](https://github.com/bluealloy/revm/pull/3055)) + +### Other + +- *(op)* backport of #3073 fix for l1block info ([#3076](https://github.com/bluealloy/revm/pull/3076)) +- backport v89 changelog ([#3075](https://github.com/bluealloy/revm/pull/3075)) +- *(op)* split paths for deposit tx in caller deduction ([#3041](https://github.com/bluealloy/revm/pull/3041)) + ## [10.1.1](https://github.com/bluealloy/revm/compare/op-revm-v10.0.0...op-revm-v10.1.1) - 2025-09-23 ## [11.0.0](https://github.com/bluealloy/revm/compare/op-revm-v10.1.0...op-revm-v11.0.0) - 2025-10-07 diff --git a/Cargo.toml b/Cargo.toml index b32dc81b606..365f5e115ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "11.0.0" +version = "11.0.1" authors.workspace = true edition.workspace = true keywords.workspace = true From 763f5134d75f23d333f92db1e3b315586379b195 Mon Sep 17 00:00:00 2001 From: rakita Date: Fri, 10 Oct 2025 02:23:30 +0200 Subject: [PATCH 106/225] chore: bump minor versions (bluealloy/revm#3078) --- CHANGELOG.md | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bcccd1dcc8b..6b5ddb75224 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [11.0.1](https://github.com/bluealloy/revm/compare/op-revm-v11.0.0...op-revm-v11.0.1) - 2025-10-09 +## [11.1.0](https://github.com/bluealloy/revm/compare/op-revm-v11.0.0...op-revm-v11.1.0) - 2025-10-09 ### Fixed diff --git a/Cargo.toml b/Cargo.toml index 365f5e115ad..b9d010ce310 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "11.0.1" +version = "11.1.0" authors.workspace = true edition.workspace = true keywords.workspace = true From 5185aa2b273ee9f97b9dd3bfa8ba33ba7014d934 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 02:18:33 +0200 Subject: [PATCH 107/225] chore: release (bluealloy/revm#3079) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 6 ++++++ Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b5ddb75224..1e2b533343b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [11.1.1](https://github.com/bluealloy/revm/compare/op-revm-v11.1.0...op-revm-v11.1.1) - 2025-10-15 + +### Other + +- updated the following local packages: revm + ## [11.1.0](https://github.com/bluealloy/revm/compare/op-revm-v11.0.0...op-revm-v11.1.0) - 2025-10-09 ### Fixed diff --git a/Cargo.toml b/Cargo.toml index b9d010ce310..90b3b6ae91b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "11.1.0" +version = "11.1.1" authors.workspace = true edition.workspace = true keywords.workspace = true From 66417851df11a58b299c2e81850efca00e8edefb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 14:39:04 +0200 Subject: [PATCH 108/225] chore: release (bluealloy/revm#3102) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 6 ++++++ Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e2b533343b..07b0d791371 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [11.1.2](https://github.com/bluealloy/revm/compare/op-revm-v11.1.1...op-revm-v11.1.2) - 2025-10-15 + +### Other + +- updated the following local packages: revm + ## [11.1.1](https://github.com/bluealloy/revm/compare/op-revm-v11.1.0...op-revm-v11.1.1) - 2025-10-15 ### Other diff --git a/Cargo.toml b/Cargo.toml index 90b3b6ae91b..10ac2976924 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "11.1.1" +version = "11.1.2" authors.workspace = true edition.workspace = true keywords.workspace = true From 9b3af09eaaa7953911382635b112bfae0230cd76 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 17 Oct 2025 16:50:11 +0200 Subject: [PATCH 109/225] chore: release (bluealloy/revm#3108) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 6 ++++++ Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07b0d791371..3d2a7f661f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [11.1.3](https://github.com/bluealloy/revm/compare/op-revm-v11.1.2...op-revm-v11.1.3) - 2025-10-17 + +### Other + +- updated the following local packages: revm + ## [11.1.2](https://github.com/bluealloy/revm/compare/op-revm-v11.1.1...op-revm-v11.1.2) - 2025-10-15 ### Other diff --git a/Cargo.toml b/Cargo.toml index 10ac2976924..7a2d00a7927 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "11.1.2" +version = "11.1.3" authors.workspace = true edition.workspace = true keywords.workspace = true From d51243ae4d99a6ae1984644cbda321d38dc2ac98 Mon Sep 17 00:00:00 2001 From: rakita Date: Fri, 17 Oct 2025 19:49:48 +0200 Subject: [PATCH 110/225] bump: tag v93 revm v30.1.0 (bluealloy/revm#3112) --- CHANGELOG.md | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d2a7f661f7..75a997848c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [11.1.3](https://github.com/bluealloy/revm/compare/op-revm-v11.1.2...op-revm-v11.1.3) - 2025-10-17 +## [11.2.0](https://github.com/bluealloy/revm/compare/op-revm-v11.1.2...op-revm-v11.2.0) - 2025-10-17 ### Other diff --git a/Cargo.toml b/Cargo.toml index 7a2d00a7927..c952cb4e571 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "11.1.3" +version = "11.2.0" authors.workspace = true edition.workspace = true keywords.workspace = true From 7ff54a35805470ec1bc17c685b1bbaba5d4a514e Mon Sep 17 00:00:00 2001 From: Skylar Ray <137945430+sky-coderay@users.noreply.github.com> Date: Fri, 17 Oct 2025 21:04:38 +0300 Subject: [PATCH 111/225] fix(op-revm): add missing enveloped_tx validation in validate_env (bluealloy/revm#3094) * fix(op-revm): add missing enveloped_tx validation in validate_env * cargo fmt --- src/handler.rs | 29 +++++++++++++++++++++++++++++ src/transaction/error.rs | 17 ++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/handler.rs b/src/handler.rs index 98c054f4672..e94f3049c92 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -88,6 +88,12 @@ where } return Ok(()); } + + // Check that non-deposit transactions have enveloped_tx set + if tx.enveloped_tx().is_none() { + return Err(OpTransactionError::MissingEnvelopedTx.into()); + } + self.mainnet.validate_env(evm) } @@ -1500,4 +1506,27 @@ mod tests { 0 ); } + + #[test] + fn test_validate_missing_enveloped_tx() { + use crate::transaction::deposit::DepositTransactionParts; + + // Create a non-deposit transaction without enveloped_tx + let ctx = Context::op().with_tx(OpTransaction { + base: TxEnv::builder().build_fill(), + enveloped_tx: None, // Missing enveloped_tx for non-deposit transaction + deposit: DepositTransactionParts::default(), // No source_hash means non-deposit + }); + + let mut evm = ctx.build_op(); + let handler = + OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame>::new(); + + assert_eq!( + handler.validate_env(&mut evm), + Err(EVMError::Transaction( + OpTransactionError::MissingEnvelopedTx + )) + ); + } } diff --git a/src/transaction/error.rs b/src/transaction/error.rs index ba13cab90eb..ad28175ee2a 100644 --- a/src/transaction/error.rs +++ b/src/transaction/error.rs @@ -41,6 +41,11 @@ pub enum OpTransactionError { /// are cause for non-inclusion, so a special [OpHaltReason][crate::OpHaltReason] variant was introduced to handle this /// case for failed deposit transactions. HaltedDepositPostRegolith, + /// Missing enveloped transaction bytes for non-deposit transaction. + /// + /// Non-deposit transactions on Optimism must have `enveloped_tx` field set + /// to properly calculate L1 costs. + MissingEnvelopedTx, } impl TransactionError for OpTransactionError {} @@ -61,6 +66,12 @@ impl Display for OpTransactionError { "deposit transaction halted post-regolith; error will be bubbled up to main return handler" ) } + Self::MissingEnvelopedTx => { + write!( + f, + "missing enveloped transaction bytes for non-deposit transaction" + ) + } } } } @@ -98,7 +109,11 @@ mod test { assert_eq!( OpTransactionError::HaltedDepositPostRegolith.to_string(), "deposit transaction halted post-regolith; error will be bubbled up to main return handler" - ) + ); + assert_eq!( + OpTransactionError::MissingEnvelopedTx.to_string(), + "missing enveloped transaction bytes for non-deposit transaction" + ); } #[cfg(feature = "serde")] From a40b37acdaaffe8408646350bd2f618ebe453cd3 Mon Sep 17 00:00:00 2001 From: rakita Date: Mon, 20 Oct 2025 15:55:24 +0200 Subject: [PATCH 112/225] chore(op): use helper function in validate against state (bluealloy/revm#3069) --- src/handler.rs | 97 ++++++++++---------------------------------------- src/l1block.rs | 31 +++++++++++++++- 2 files changed, 48 insertions(+), 80 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index e94f3049c92..6aeaaed1587 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -16,7 +16,7 @@ use revm::{ evm::FrameTr, handler::EvmTrError, post_execution::{self, reimburse_caller}, - pre_execution::validate_account_nonce_and_code, + pre_execution::{calculate_caller_fee, validate_account_nonce_and_code_with_components}, EthFrame, EvmTr, FrameResult, Handler, MainnetHandler, }, inspector::{Inspector, InspectorEvmTr, InspectorHandler}, @@ -101,19 +101,10 @@ where &self, evm: &mut Self::Evm, ) -> Result<(), Self::Error> { - let ctx = evm.ctx(); - - let basefee = ctx.block().basefee() as u128; - let blob_price = ctx.block().blob_gasprice().unwrap_or_default(); - let is_deposit = ctx.tx().tx_type() == DEPOSIT_TRANSACTION_TYPE; - let spec = ctx.cfg().spec(); - let block_number = ctx.block().number(); - let is_balance_check_disabled = ctx.cfg().is_balance_check_disabled(); - let is_eip3607_disabled = ctx.cfg().is_eip3607_disabled(); - let is_nonce_check_disabled = ctx.cfg().is_nonce_check_disabled(); + let (block, tx, cfg, journal, chain, _) = evm.ctx().all_mut(); + let spec = cfg.spec(); - if is_deposit { - let (block, tx, cfg, journal, _, _) = evm.ctx().all_mut(); + if tx.tx_type() == DEPOSIT_TRANSACTION_TYPE { let basefee = block.basefee() as u128; let blob_price = block.blob_gasprice().unwrap_or_default(); // deposit skips max fee check and just deducts the effective balance spending. @@ -148,87 +139,35 @@ where return Ok(()); } - let mut additional_cost = U256::ZERO; - // L1 block info is stored in the context for later use. // and it will be reloaded from the database if it is not for the current block. - if ctx.chain().l2_block != Some(block_number) { - *ctx.chain_mut() = L1BlockInfo::try_fetch(ctx.db_mut(), block_number, spec)?; + if chain.l2_block != Some(block.number()) { + *chain = L1BlockInfo::try_fetch(journal.db_mut(), block.number(), spec)?; } - if !ctx.cfg().is_fee_charge_disabled() { - // account for additional cost of l1 fee and operator fee - // account for additional cost of l1 fee and operator fee - let Some(enveloped_tx) = ctx.tx().enveloped_tx().cloned() else { - return Err(ERROR::from_string( - "[OPTIMISM] Failed to load enveloped transaction.".into(), - )); - }; - - // compute L1 cost - additional_cost = ctx.chain_mut().calculate_tx_l1_cost(&enveloped_tx, spec); - - // compute operator fee - if spec.is_enabled_in(OpSpecId::ISTHMUS) { - let gas_limit = U256::from(ctx.tx().gas_limit()); - let operator_fee_charge = - ctx.chain() - .operator_fee_charge(&enveloped_tx, gas_limit, spec); - additional_cost = additional_cost.saturating_add(operator_fee_charge); - } - } - - let (tx, journal) = ctx.tx_journal_mut(); - let caller_account = journal.load_account_code(tx.caller())?.data; // validates account nonce and code - validate_account_nonce_and_code( - &mut caller_account.info, - tx.nonce(), - is_eip3607_disabled, - is_nonce_check_disabled, - )?; - - let mut new_balance = caller_account.info.balance; - - // Check if account has enough balance for `gas_limit * max_fee`` and value transfer. - // Transfer will be done inside `*_inner` functions. - if !is_balance_check_disabled { - // check additional cost and deduct it from the caller's balances - let Some(balance) = new_balance.checked_sub(additional_cost) else { + validate_account_nonce_and_code_with_components(&mut caller_account.info, tx, cfg)?; + + // check additional cost and deduct it from the caller's balances + let mut balance = caller_account.info.balance; + + if !cfg.is_fee_charge_disabled() { + let additional_cost = chain.tx_cost_with_tx(tx, spec); + let Some(new_balance) = balance.checked_sub(additional_cost) else { return Err(InvalidTransaction::LackOfFundForMaxFee { fee: Box::new(additional_cost), - balance: Box::new(new_balance), + balance: Box::new(balance), } .into()); }; - tx.ensure_enough_balance(balance)?; - } - - // subtracting max balance spending with value that is going to be deducted later in the call. - let gas_balance_spending = tx - .gas_balance_spending(basefee, blob_price) - .expect("effective balance is always smaller than max balance so it can't overflow"); - - // If the transaction is not a deposit transaction, subtract the L1 data fee from the - // caller's balance directly after minting the requested amount of ETH. - // Additionally deduct the operator fee from the caller's account. - // - // In case of deposit additional cost will be zero. - let op_gas_balance_spending = gas_balance_spending.saturating_add(additional_cost); - - new_balance = new_balance.saturating_sub(op_gas_balance_spending); - - if is_balance_check_disabled { - // Make sure the caller's balance is at least the value of the transaction. - // this is not consensus critical, and it is used in testing. - new_balance = new_balance.max(tx.value()); + balance = new_balance } - let old_balance = - caller_account.caller_initial_modification(new_balance, tx.kind().is_call()); + let balance = calculate_caller_fee(balance, tx, block, cfg)?; + let old_balance = caller_account.caller_initial_modification(balance, tx.kind().is_call()); // NOTE: all changes to the caller account should journaled so in case of error // we can revert the changes. journal.caller_accounting_journal_entry(tx.caller(), old_balance, tx.kind().is_call()); diff --git a/src/l1block.rs b/src/l1block.rs index fd92a32c461..c3a6dc2172d 100644 --- a/src/l1block.rs +++ b/src/l1block.rs @@ -7,7 +7,7 @@ use crate::{ NON_ZERO_BYTE_COST, OPERATOR_FEE_CONSTANT_OFFSET, OPERATOR_FEE_JOVIAN_MULTIPLIER, OPERATOR_FEE_SCALARS_SLOT, OPERATOR_FEE_SCALAR_DECIMAL, OPERATOR_FEE_SCALAR_OFFSET, }, - transaction::estimate_tx_compressed_size, + transaction::{estimate_tx_compressed_size, OpTxTr}, OpSpecId, }; use revm::{ @@ -256,6 +256,35 @@ impl L1BlockInfo { self.tx_l1_cost = None; } + /// Calculate additional transaction cost with OpTxTr. + /// + /// Internally calls [`L1BlockInfo::tx_cost`]. + #[track_caller] + pub fn tx_cost_with_tx(&mut self, tx: impl OpTxTr, spec: OpSpecId) -> U256 { + // account for additional cost of l1 fee and operator fee + let enveloped_tx = tx + .enveloped_tx() + .expect("all not deposit tx have enveloped tx") + .clone(); + let gas_limit = U256::from(tx.gas_limit()); + self.tx_cost(&enveloped_tx, gas_limit, spec) + } + + /// Calculate additional transaction cost. + #[inline] + pub fn tx_cost(&mut self, enveloped_tx: &[u8], gas_limit: U256, spec: OpSpecId) -> U256 { + // compute L1 cost + let mut additional_cost = self.calculate_tx_l1_cost(enveloped_tx, spec); + + // compute operator fee + if spec.is_enabled_in(OpSpecId::ISTHMUS) { + let operator_fee_charge = self.operator_fee_charge(enveloped_tx, gas_limit, spec); + additional_cost = additional_cost.saturating_add(operator_fee_charge); + } + + additional_cost + } + /// Calculate the gas cost of a transaction based on L1 block data posted on L2, depending on the [OpSpecId] passed. pub fn calculate_tx_l1_cost(&mut self, input: &[u8], spec_id: OpSpecId) -> U256 { if let Some(tx_l1_cost) = self.tx_l1_cost { From c130c3fc7c06923d762ca88991eba56125b60b7e Mon Sep 17 00:00:00 2001 From: theo <80177219+theochap@users.noreply.github.com> Date: Wed, 22 Oct 2025 05:43:13 -0400 Subject: [PATCH 113/225] fix(jovian): fixes the DA footprint update storage slot. fix l1 fork associated with Jovian. (bluealloy/revm#3120) * fix(jovian/storage): fix jovian's storage slot for da footprint gas scalar * fix(jovian): fix l1 hardfork matching jovian. expose da footprint gas scalar getter --- src/constants.rs | 6 +++--- src/handler.rs | 31 +++++++++++++------------------ src/l1block.rs | 11 ++++++++--- src/spec.rs | 5 ++--- 4 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/constants.rs b/src/constants.rs index 243343e8044..2c93bff13b0 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -20,7 +20,7 @@ pub const OPERATOR_FEE_CONSTANT_OFFSET: usize = 24; /// The Jovian daFootprintGasScalar value is packed into a single storage slot. Byte offset within /// the storage slot of the 16-byte daFootprintGasScalar attribute. -pub const DA_FOOTPRINT_GAS_SCALAR_OFFSET: usize = 0; +pub const DA_FOOTPRINT_GAS_SCALAR_OFFSET: usize = 18; /// The fixed point decimal scaling factor associated with the operator fee scalar. /// @@ -48,9 +48,9 @@ pub const ECOTONE_L1_FEE_SCALARS_SLOT: U256 = U256::from_limbs([3u64, 0, 0, 0]); /// offsets [OPERATOR_FEE_SCALAR_OFFSET] and [OPERATOR_FEE_CONSTANT_OFFSET] respectively. pub const OPERATOR_FEE_SCALARS_SLOT: U256 = U256::from_limbs([8u64, 0, 0, 0]); -/// As of the Jovian upgrade, this storage slot stores the 32-bit daFootprintGasScalar attribute at +/// As of the Jovian upgrade, this storage slot stores the 16-bit daFootprintGasScalar attribute at /// offset [DA_FOOTPRINT_GAS_SCALAR_OFFSET]. -pub const DA_FOOTPRINT_GAS_SCALAR_SLOT: U256 = U256::from_limbs([9u64, 0, 0, 0]); +pub const DA_FOOTPRINT_GAS_SCALAR_SLOT: U256 = U256::from_limbs([8u64, 0, 0, 0]); /// An empty 64-bit set of scalar values. pub const EMPTY_SCALARS: [u8; 8] = [0u8; 8]; diff --git a/src/handler.rs b/src/handler.rs index 6aeaaed1587..cd9a3462cd8 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -457,9 +457,8 @@ mod tests { use crate::{ api::default_ctx::OpContext, constants::{ - BASE_FEE_SCALAR_OFFSET, DA_FOOTPRINT_GAS_SCALAR_SLOT, ECOTONE_L1_BLOB_BASE_FEE_SLOT, - ECOTONE_L1_FEE_SCALARS_SLOT, L1_BASE_FEE_SLOT, L1_BLOCK_CONTRACT, - OPERATOR_FEE_SCALARS_SLOT, + BASE_FEE_SCALAR_OFFSET, ECOTONE_L1_BLOB_BASE_FEE_SLOT, ECOTONE_L1_FEE_SCALARS_SLOT, + L1_BASE_FEE_SLOT, L1_BLOCK_CONTRACT, OPERATOR_FEE_SCALARS_SLOT, }, DefaultOp, OpBuilder, OpTransaction, }; @@ -769,15 +768,14 @@ mod tests { 0, 0, ]); - const OPERATOR_FEE_SCALAR: u64 = 5; - const OPERATOR_FEE_CONST: u64 = 6; - const OPERATOR_FEE: U256 = - U256::from_limbs([OPERATOR_FEE_CONST, OPERATOR_FEE_SCALAR, 0, 0]); - const DA_FOOTPRINT_GAS_SCALAR: u16 = 7; - const DA_FOOTPRINT_GAS_SCALAR_U64: u64 = - u64::from_be_bytes([0, DA_FOOTPRINT_GAS_SCALAR as u8, 0, 0, 0, 0, 0, 0]); - const DA_FOOTPRINT_GAS_SCALAR_SLOT_VALUE: U256 = - U256::from_limbs([0, 0, 0, DA_FOOTPRINT_GAS_SCALAR_U64]); + const OPERATOR_FEE_SCALAR: u8 = 5; + const OPERATOR_FEE_CONST: u8 = 6; + const DA_FOOTPRINT_GAS_SCALAR: u8 = 7; + let mut operator_fee_and_da_footprint = [0u8; 32]; + operator_fee_and_da_footprint[31] = OPERATOR_FEE_CONST; + operator_fee_and_da_footprint[23] = OPERATOR_FEE_SCALAR; + operator_fee_and_da_footprint[19] = DA_FOOTPRINT_GAS_SCALAR; + let operator_fee_and_da_footprint_u256 = U256::from_be_bytes(operator_fee_and_da_footprint); let mut db = InMemoryDB::default(); let l1_block_contract = db.load_account(L1_BLOCK_CONTRACT).unwrap(); @@ -790,12 +788,9 @@ mod tests { l1_block_contract .storage .insert(ECOTONE_L1_FEE_SCALARS_SLOT, L1_FEE_SCALARS); - l1_block_contract - .storage - .insert(OPERATOR_FEE_SCALARS_SLOT, OPERATOR_FEE); l1_block_contract.storage.insert( - DA_FOOTPRINT_GAS_SCALAR_SLOT, - DA_FOOTPRINT_GAS_SCALAR_SLOT_VALUE, + OPERATOR_FEE_SCALARS_SLOT, + operator_fee_and_da_footprint_u256, ); db.insert_account_info( Address::ZERO, @@ -849,7 +844,7 @@ mod tests { operator_fee_scalar: Some(U256::from(OPERATOR_FEE_SCALAR)), operator_fee_constant: Some(U256::from(OPERATOR_FEE_CONST)), tx_l1_cost: Some(U256::ZERO), - da_footprint_gas_scalar: Some(DA_FOOTPRINT_GAS_SCALAR), + da_footprint_gas_scalar: Some(DA_FOOTPRINT_GAS_SCALAR as u16), } ); } diff --git a/src/l1block.rs b/src/l1block.rs index c3a6dc2172d..67f52b6b601 100644 --- a/src/l1block.rs +++ b/src/l1block.rs @@ -58,8 +58,8 @@ pub struct L1BlockInfo { } impl L1BlockInfo { - /// Try to fetch the L1 block info from the database, post-Jovian. - fn try_fetch_jovian(&mut self, db: &mut DB) -> Result<(), DB::Error> { + /// Fetch the DA footprint gas scalar from the database. + pub fn fetch_da_footprint_gas_scalar(db: &mut DB) -> Result { let da_footprint_gas_scalar_slot = db .storage(L1_BLOCK_CONTRACT, DA_FOOTPRINT_GAS_SCALAR_SLOT)? .to_be_bytes::<32>(); @@ -69,7 +69,12 @@ impl L1BlockInfo { da_footprint_gas_scalar_slot[DA_FOOTPRINT_GAS_SCALAR_OFFSET], da_footprint_gas_scalar_slot[DA_FOOTPRINT_GAS_SCALAR_OFFSET + 1], ]; - self.da_footprint_gas_scalar = Some(u16::from_be_bytes(bytes)); + Ok(u16::from_be_bytes(bytes)) + } + + /// Try to fetch the L1 block info from the database, post-Jovian. + fn try_fetch_jovian(&mut self, db: &mut DB) -> Result<(), DB::Error> { + self.da_footprint_gas_scalar = Some(Self::fetch_da_footprint_gas_scalar(db)?); Ok(()) } diff --git a/src/spec.rs b/src/spec.rs index 35b15c3c9a9..e192847a0f2 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -40,8 +40,8 @@ impl OpSpecId { Self::BEDROCK | Self::REGOLITH => SpecId::MERGE, Self::CANYON => SpecId::SHANGHAI, Self::ECOTONE | Self::FJORD | Self::GRANITE | Self::HOLOCENE => SpecId::CANCUN, - Self::ISTHMUS | Self::INTEROP => SpecId::PRAGUE, - Self::JOVIAN | Self::OSAKA => SpecId::OSAKA, + Self::ISTHMUS | Self::JOVIAN | Self::INTEROP => SpecId::PRAGUE, + Self::OSAKA => SpecId::OSAKA, } } @@ -201,7 +201,6 @@ mod tests { (SpecId::SHANGHAI, true), (SpecId::CANCUN, true), (SpecId::MERGE, true), - (SpecId::OSAKA, true), ], vec![ (OpSpecId::BEDROCK, true), From 0feb43a4705b2b6157452e33d860bdd2d3cac4ed Mon Sep 17 00:00:00 2001 From: rakita Date: Thu, 23 Oct 2025 14:40:04 +0200 Subject: [PATCH 114/225] feat: JournaledAccount, a nice way to update and track changes (bluealloy/revm#3086) * JournaledAcccount * PoC: JournaledAccount, a nice way to update and track changes * cleanup * rm artifact * inline some of host fn * beneficiary refactor * nit * nits * fmt * rename and make old fn deprecated --- src/handler.rs | 58 +++++++++++++++++++++----------------------------- 1 file changed, 24 insertions(+), 34 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index cd9a3462cd8..5f4f4ed16d8 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -6,7 +6,7 @@ use crate::{ L1BlockInfo, OpHaltReason, OpSpecId, }; use revm::{ - context::{result::InvalidTransaction, LocalContextTr}, + context::{journaled_state::JournalCheckpoint, result::InvalidTransaction, LocalContextTr}, context_interface::{ context::ContextError, result::{EVMError, ExecutionResult, FromStringError}, @@ -109,7 +109,7 @@ where let blob_price = block.blob_gasprice().unwrap_or_default(); // deposit skips max fee check and just deducts the effective balance spending. - let caller_account = journal.load_account_code(tx.caller())?.data; + let mut caller = journal.load_account_with_code_mut(tx.caller())?.data; let effective_balance_spending = tx .effective_balance_spending(basefee, blob_price) @@ -117,9 +117,8 @@ where - tx.value(); // Mind value should be added first before subtracting the effective balance spending. - let mut new_balance = caller_account - .info - .balance + let mut new_balance = caller + .balance() .saturating_add(U256::from(tx.mint().unwrap_or_default())) .saturating_sub(effective_balance_spending); @@ -129,12 +128,11 @@ where new_balance = new_balance.max(tx.value()); } - let old_balance = - caller_account.caller_initial_modification(new_balance, tx.kind().is_call()); - - // NOTE: all changes to the caller account should journaled so in case of error - // we can revert the changes. - journal.caller_accounting_journal_entry(tx.caller(), old_balance, tx.kind().is_call()); + // set the new balance and bump the nonce if it is a call + caller.set_balance(new_balance); + if tx.kind().is_call() { + caller.bump_nonce(); + } return Ok(()); } @@ -145,10 +143,10 @@ where *chain = L1BlockInfo::try_fetch(journal.db_mut(), block.number(), spec)?; } - let caller_account = journal.load_account_code(tx.caller())?.data; + let mut caller_account = journal.load_account_with_code_mut(tx.caller())?.data; // validates account nonce and code - validate_account_nonce_and_code_with_components(&mut caller_account.info, tx, cfg)?; + validate_account_nonce_and_code_with_components(&caller_account.info, tx, cfg)?; // check additional cost and deduct it from the caller's balances let mut balance = caller_account.info.balance; @@ -167,10 +165,11 @@ where let balance = calculate_caller_fee(balance, tx, block, cfg)?; - let old_balance = caller_account.caller_initial_modification(balance, tx.kind().is_call()); - // NOTE: all changes to the caller account should journaled so in case of error - // we can revert the changes. - journal.caller_accounting_journal_entry(tx.caller(), old_balance, tx.kind().is_call()); + // make changes to the account + caller_account.set_balance(balance); + if tx.kind().is_call() { + caller_account.bump_nonce(); + } Ok(()) } @@ -382,9 +381,11 @@ where let mint = tx.mint(); let is_system_tx = tx.is_system_transaction(); let gas_limit = tx.gas_limit(); + let journal = evm.ctx().journal_mut(); // discard all changes of this transaction - evm.ctx().journal_mut().discard_tx(); + // Default JournalCheckpoint is the first checkpoint and will wipe all changes. + journal.checkpoint_revert(JournalCheckpoint::default()); // If the transaction is a deposit transaction and it failed // for any reason, the caller nonce must be bumped, and the @@ -395,23 +396,12 @@ where // Increment sender nonce and account balance for the mint amount. Deposits // always persist the mint amount, even if the transaction fails. - let acc: &mut revm::state::Account = evm.ctx().journal_mut().load_account(caller)?.data; + let mut acc = journal.load_account_mut(caller)?; + acc.bump_nonce(); + acc.incr_balance(U256::from(mint.unwrap_or_default())); - let old_balance = acc.info.balance; - - // decrement transaction id as it was incremented when we discarded the tx. - acc.transaction_id -= 1; - acc.info.nonce = acc.info.nonce.saturating_add(1); - acc.info.balance = acc - .info - .balance - .saturating_add(U256::from(mint.unwrap_or_default())); - acc.mark_touch(); - - // add journal entry for accounts - evm.ctx() - .journal_mut() - .caller_accounting_journal_entry(caller, old_balance, true); + // We can now commit the changes. + journal.commit_tx(); // The gas used of a failed deposit post-regolith is the gas // limit of the transaction. pre-regolith, it is the gas limit From c2fed9fe27f9d8c392e31dbe35b6629bb7aa9f35 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 30 Oct 2025 22:06:15 +0100 Subject: [PATCH 115/225] chore: release (bluealloy/revm#3113) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 15 +++++++++++++++ Cargo.toml | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75a997848c0..fbef6c87365 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [12.0.0](https://github.com/bluealloy/revm/compare/op-revm-v11.2.0...op-revm-v12.0.0) - 2025-10-30 + +### Added + +- JournaledAccount, a nice way to update and track changes ([#3086](https://github.com/bluealloy/revm/pull/3086)) + +### Fixed + +- *(jovian)* fixes the DA footprint update storage slot. fix l1 fork associated with Jovian. ([#3120](https://github.com/bluealloy/revm/pull/3120)) +- *(op-revm)* add missing enveloped_tx validation in validate_env ([#3094](https://github.com/bluealloy/revm/pull/3094)) + +### Other + +- *(op)* use helper function in validate against state ([#3069](https://github.com/bluealloy/revm/pull/3069)) + ## [11.2.0](https://github.com/bluealloy/revm/compare/op-revm-v11.1.2...op-revm-v11.2.0) - 2025-10-17 ### Other diff --git a/Cargo.toml b/Cargo.toml index c952cb4e571..231bfe077a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "11.2.0" +version = "12.0.0" authors.workspace = true edition.workspace = true keywords.workspace = true From 05eeb489fa9c88d7042004ca27e133617fc8d303 Mon Sep 17 00:00:00 2001 From: rakita Date: Thu, 30 Oct 2025 22:49:26 +0100 Subject: [PATCH 116/225] feat(precompiles/jovian): add jovian precompiles to revm (bluealloy/revm#3128) (bluealloy/revm#3134) * feat(precompiles/jovian): add jovian precompiles to revm (bluealloy/revm#3128) * clippy fix --------- Co-authored-by: theo <80177219+theochap@users.noreply.github.com> --- src/precompiles.rs | 278 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 255 insertions(+), 23 deletions(-) diff --git a/src/precompiles.rs b/src/precompiles.rs index f27bf339b5f..025a6afbadc 100644 --- a/src/precompiles.rs +++ b/src/precompiles.rs @@ -34,7 +34,8 @@ impl OpPrecompiles { | OpSpecId::ECOTONE) => Precompiles::new(spec.into_eth_spec().into()), OpSpecId::FJORD => fjord(), OpSpecId::GRANITE | OpSpecId::HOLOCENE => granite(), - OpSpecId::ISTHMUS | OpSpecId::INTEROP | OpSpecId::OSAKA | OpSpecId::JOVIAN => isthmus(), + OpSpecId::ISTHMUS => isthmus(), + OpSpecId::INTEROP | OpSpecId::OSAKA | OpSpecId::JOVIAN => jovian(), }; Self { @@ -92,6 +93,34 @@ pub fn isthmus() -> &'static Precompiles { }) } +/// Returns precompiles for jovian spec. +pub fn jovian() -> &'static Precompiles { + static INSTANCE: OnceLock = OnceLock::new(); + INSTANCE.get_or_init(|| { + let mut precompiles = isthmus().clone(); + + let mut to_remove = Precompiles::default(); + to_remove.extend([ + bn254::pair::ISTANBUL, + bls12_381::ISTHMUS_G1_MSM, + bls12_381::ISTHMUS_G2_MSM, + bls12_381::ISTHMUS_PAIRING, + ]); + + // Replace the 4 variable-input precompiles with Jovian versions (reduced limits) + precompiles.difference(&to_remove); + + precompiles.extend([ + bn254_pair::JOVIAN, + bls12_381::JOVIAN_G1_MSM, + bls12_381::JOVIAN_G2_MSM, + bls12_381::JOVIAN_PAIRING, + ]); + + precompiles + }) +} + impl PrecompileProvider for OpPrecompiles where CTX: ContextTr>, @@ -129,7 +158,7 @@ where impl Default for OpPrecompiles { fn default() -> Self { - Self::new_with_spec(OpSpecId::ISTHMUS) + Self::new_with_spec(OpSpecId::JOVIAN) } } @@ -140,11 +169,14 @@ pub mod bn254_pair { /// Max input size for the bn254 pair precompile. pub const GRANITE_MAX_INPUT_SIZE: usize = 112687; /// Bn254 pair precompile. - pub const GRANITE: Precompile = - Precompile::new(PrecompileId::Bn254Pairing, bn254::pair::ADDRESS, run_pair); + pub const GRANITE: Precompile = Precompile::new( + PrecompileId::Bn254Pairing, + bn254::pair::ADDRESS, + run_pair_granite, + ); /// Run the bn254 pair precompile with Optimism input limit. - pub fn run_pair(input: &[u8], gas_limit: u64) -> PrecompileResult { + pub fn run_pair_granite(input: &[u8], gas_limit: u64) -> PrecompileResult { if input.len() > GRANITE_MAX_INPUT_SIZE { return Err(PrecompileError::Bn254PairLength); } @@ -155,6 +187,28 @@ pub mod bn254_pair { gas_limit, ) } + + /// Max input size for the bn254 pair precompile. + pub const JOVIAN_MAX_INPUT_SIZE: usize = 81_984; + /// Bn254 pair precompile. + pub const JOVIAN: Precompile = Precompile::new( + PrecompileId::Bn254Pairing, + bn254::pair::ADDRESS, + run_pair_jovian, + ); + + /// Run the bn254 pair precompile with Optimism input limit. + pub fn run_pair_jovian(input: &[u8], gas_limit: u64) -> PrecompileResult { + if input.len() > JOVIAN_MAX_INPUT_SIZE { + return Err(PrecompileError::Bn254PairLength); + } + bn254::run_pair( + input, + bn254::pair::ISTANBUL_PAIR_PER_POINT, + bn254::pair::ISTANBUL_PAIR_BASE, + gas_limit, + ) + } } /// Bls12_381 precompile. @@ -167,33 +221,67 @@ pub mod bls12_381 { /// Max input size for the g1 msm precompile. pub const ISTHMUS_G1_MSM_MAX_INPUT_SIZE: usize = 513760; + + /// The maximum input size for the BLS12-381 g1 msm operation after the Jovian Hardfork. + pub const JOVIAN_G1_MSM_MAX_INPUT_SIZE: usize = 288_960; + /// Max input size for the g2 msm precompile. pub const ISTHMUS_G2_MSM_MAX_INPUT_SIZE: usize = 488448; + + /// Max input size for the g2 msm precompile after the Jovian Hardfork. + pub const JOVIAN_G2_MSM_MAX_INPUT_SIZE: usize = 278_784; + /// Max input size for the pairing precompile. pub const ISTHMUS_PAIRING_MAX_INPUT_SIZE: usize = 235008; + /// Max input size for the pairing precompile after the Jovian Hardfork. + pub const JOVIAN_PAIRING_MAX_INPUT_SIZE: usize = 156_672; + /// G1 msm precompile. pub const ISTHMUS_G1_MSM: Precompile = - Precompile::new(PrecompileId::Bls12G1Msm, G1_MSM_ADDRESS, run_g1_msm); + Precompile::new(PrecompileId::Bls12G1Msm, G1_MSM_ADDRESS, run_g1_msm_isthmus); /// G2 msm precompile. pub const ISTHMUS_G2_MSM: Precompile = - Precompile::new(PrecompileId::Bls12G2Msm, G2_MSM_ADDRESS, run_g2_msm); + Precompile::new(PrecompileId::Bls12G2Msm, G2_MSM_ADDRESS, run_g2_msm_isthmus); /// Pairing precompile. - pub const ISTHMUS_PAIRING: Precompile = - Precompile::new(PrecompileId::Bls12Pairing, PAIRING_ADDRESS, run_pair); + pub const ISTHMUS_PAIRING: Precompile = Precompile::new( + PrecompileId::Bls12Pairing, + PAIRING_ADDRESS, + run_pair_isthmus, + ); + + /// G1 msm precompile after the Jovian Hardfork. + pub const JOVIAN_G1_MSM: Precompile = + Precompile::new(PrecompileId::Bls12G1Msm, G1_MSM_ADDRESS, run_g1_msm_jovian); + /// G2 msm precompile after the Jovian Hardfork. + pub const JOVIAN_G2_MSM: Precompile = + Precompile::new(PrecompileId::Bls12G2Msm, G2_MSM_ADDRESS, run_g2_msm_jovian); + /// Pairing precompile after the Jovian Hardfork. + pub const JOVIAN_PAIRING: Precompile = + Precompile::new(PrecompileId::Bls12Pairing, PAIRING_ADDRESS, run_pair_jovian); /// Run the g1 msm precompile with Optimism input limit. - pub fn run_g1_msm(input: &[u8], gas_limit: u64) -> PrecompileResult { + pub fn run_g1_msm_isthmus(input: &[u8], gas_limit: u64) -> PrecompileResult { if input.len() > ISTHMUS_G1_MSM_MAX_INPUT_SIZE { return Err(PrecompileError::Other( - "G1MSM input length too long for OP Stack input size limitation".to_string(), + "G1MSM input length too long for OP Stack input size limitation after the Isthmus Hardfork".to_string(), + )); + } + precompile::bls12_381::g1_msm::g1_msm(input, gas_limit) + } + + /// Run the g1 msm precompile with Optimism input limit. + pub fn run_g1_msm_jovian(input: &[u8], gas_limit: u64) -> PrecompileResult { + if input.len() > JOVIAN_G1_MSM_MAX_INPUT_SIZE { + return Err(PrecompileError::Other( + "G1MSM input length too long for OP Stack input size limitation after the Jovian Hardfork".to_string(), )); } precompile::bls12_381::g1_msm::g1_msm(input, gas_limit) } /// Run the g2 msm precompile with Optimism input limit. - pub fn run_g2_msm(input: &[u8], gas_limit: u64) -> PrecompileResult { + pub fn run_g2_msm_isthmus(input: &[u8], gas_limit: u64) -> PrecompileResult { if input.len() > ISTHMUS_G2_MSM_MAX_INPUT_SIZE { return Err(PrecompileError::Other( "G2MSM input length too long for OP Stack input size limitation".to_string(), @@ -202,8 +290,18 @@ pub mod bls12_381 { precompile::bls12_381::g2_msm::g2_msm(input, gas_limit) } + /// Run the g2 msm precompile with Optimism input limit after the Jovian Hardfork. + pub fn run_g2_msm_jovian(input: &[u8], gas_limit: u64) -> PrecompileResult { + if input.len() > JOVIAN_G2_MSM_MAX_INPUT_SIZE { + return Err(PrecompileError::Other( + "G2MSM input length too long for OP Stack input size limitation after the Jovian Hardfork".to_string(), + )); + } + precompile::bls12_381::g2_msm::g2_msm(input, gas_limit) + } + /// Run the pairing precompile with Optimism input limit. - pub fn run_pair(input: &[u8], gas_limit: u64) -> PrecompileResult { + pub fn run_pair_isthmus(input: &[u8], gas_limit: u64) -> PrecompileResult { if input.len() > ISTHMUS_PAIRING_MAX_INPUT_SIZE { return Err(PrecompileError::Other( "Pairing input length too long for OP Stack input size limitation".to_string(), @@ -211,18 +309,30 @@ pub mod bls12_381 { } precompile::bls12_381::pairing::pairing(input, gas_limit) } + + /// Run the pairing precompile with Optimism input limit after the Jovian Hardfork. + pub fn run_pair_jovian(input: &[u8], gas_limit: u64) -> PrecompileResult { + if input.len() > JOVIAN_PAIRING_MAX_INPUT_SIZE { + return Err(PrecompileError::Other( + "Pairing input length too long for OP Stack input size limitation after the Jovian Hardfork".to_string(), + )); + } + precompile::bls12_381::pairing::pairing(input, gas_limit) + } } #[cfg(test)] mod tests { use crate::precompiles::bls12_381::{ - run_g1_msm, run_g2_msm, ISTHMUS_G1_MSM_MAX_INPUT_SIZE, ISTHMUS_G2_MSM_MAX_INPUT_SIZE, - ISTHMUS_PAIRING_MAX_INPUT_SIZE, + run_g1_msm_isthmus, run_g1_msm_jovian, run_g2_msm_isthmus, run_g2_msm_jovian, + ISTHMUS_G1_MSM_MAX_INPUT_SIZE, ISTHMUS_G2_MSM_MAX_INPUT_SIZE, + ISTHMUS_PAIRING_MAX_INPUT_SIZE, JOVIAN_G1_MSM_MAX_INPUT_SIZE, JOVIAN_G2_MSM_MAX_INPUT_SIZE, + JOVIAN_PAIRING_MAX_INPUT_SIZE, }; use super::*; use revm::{ - precompile::PrecompileError, + precompile::{bls12_381_const, PrecompileError}, primitives::{hex, Bytes}, }; use std::vec; @@ -248,7 +358,7 @@ mod tests { let expected = hex::decode("0000000000000000000000000000000000000000000000000000000000000001") .unwrap(); - let outcome = bn254_pair::run_pair(&input, 260_000).unwrap(); + let outcome = bn254_pair::run_pair_granite(&input, 260_000).unwrap(); assert_eq!(outcome.bytes, expected); // Invalid input length @@ -261,20 +371,91 @@ mod tests { ) .unwrap(); - let res = bn254_pair::run_pair(&input, 260_000); + let res = bn254_pair::run_pair_granite(&input, 260_000); assert!(matches!(res, Err(PrecompileError::Bn254PairLength))); // Valid input length shorter than 112687 let input = vec![1u8; 586 * bn254::PAIR_ELEMENT_LEN]; - let res = bn254_pair::run_pair(&input, 260_000); + let res = bn254_pair::run_pair_granite(&input, 260_000); assert!(matches!(res, Err(PrecompileError::OutOfGas))); // Input length longer than 112687 let input = vec![1u8; 587 * bn254::PAIR_ELEMENT_LEN]; - let res = bn254_pair::run_pair(&input, 260_000); + let res = bn254_pair::run_pair_granite(&input, 260_000); assert!(matches!(res, Err(PrecompileError::Bn254PairLength))); } + #[test] + fn test_accelerated_bn254_pairing_jovian() { + const TEST_INPUT: [u8; 384] = hex!( + "2cf44499d5d27bb186308b7af7af02ac5bc9eeb6a3d147c186b21fb1b76e18da2c0f001f52110ccfe69108924926e45f0b0c868df0e7bde1fe16d3242dc715f61fb19bb476f6b9e44e2a32234da8212f61cd63919354bc06aef31e3cfaff3ebc22606845ff186793914e03e21df544c34ffe2f2f3504de8a79d9159eca2d98d92bd368e28381e8eccb5fa81fc26cf3f048eea9abfdd85d7ed3ab3698d63e4f902fe02e47887507adf0ff1743cbac6ba291e66f59be6bd763950bb16041a0a85e000000000000000000000000000000000000000000000000000000000000000130644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd451971ff0471b09fa93caaf13cbf443c1aede09cc4328f5a62aad45f40ec133eb4091058a3141822985733cbdddfed0fd8d6c104e9e9eff40bf5abfef9ab163bc72a23af9a5ce2ba2796c1f4e453a370eb0af8c212d9dc9acd8fc02c2e907baea223a8eb0b0996252cb548a4487da97b02422ebc0e834613f954de6c7e0afdc1fc" + ); + const EXPECTED_OUTPUT: [u8; 32] = + hex!("0000000000000000000000000000000000000000000000000000000000000001"); + + let res = bn254_pair::run_pair_jovian(TEST_INPUT.as_ref(), u64::MAX); + assert!(matches!(res, Ok(outcome) if **outcome.bytes == EXPECTED_OUTPUT)); + } + + #[test] + fn test_accelerated_bn254_pairing_bad_input_len_jovian() { + let input = [0u8; bn254_pair::JOVIAN_MAX_INPUT_SIZE + 1]; + let res = bn254_pair::run_pair_jovian(&input, u64::MAX); + assert!(matches!(res, Err(PrecompileError::Bn254PairLength))); + } + + #[test] + fn test_get_jovian_precompile_with_bad_input_len() { + let precompiles = OpPrecompiles::new_with_spec(OpSpecId::JOVIAN); + let bn254_pair_precompile = precompiles + .precompiles() + .get(&bn254::pair::ADDRESS) + .unwrap(); + + let mut bad_input_len = bn254_pair::JOVIAN_MAX_INPUT_SIZE + 1; + assert!(bad_input_len < bn254_pair::GRANITE_MAX_INPUT_SIZE); + let input = vec![0u8; bad_input_len]; + + let res = bn254_pair_precompile.execute(&input, u64::MAX); + assert!(matches!(res, Err(PrecompileError::Bn254PairLength))); + + let bls12_381_g1_msm_precompile = precompiles + .precompiles() + .get(&bls12_381_const::G1_MSM_ADDRESS) + .unwrap(); + bad_input_len = bls12_381::JOVIAN_G1_MSM_MAX_INPUT_SIZE + 1; + assert!(bad_input_len < bls12_381::ISTHMUS_G1_MSM_MAX_INPUT_SIZE); + let input = vec![0u8; bad_input_len]; + let res = bls12_381_g1_msm_precompile.execute(&input, u64::MAX); + assert!( + matches!(res, Err(PrecompileError::Other(msg)) if msg.contains("input length too long")) + ); + + let bls12_381_g2_msm_precompile = precompiles + .precompiles() + .get(&bls12_381_const::G2_MSM_ADDRESS) + .unwrap(); + bad_input_len = bls12_381::JOVIAN_G2_MSM_MAX_INPUT_SIZE + 1; + assert!(bad_input_len < bls12_381::ISTHMUS_G2_MSM_MAX_INPUT_SIZE); + let input = vec![0u8; bad_input_len]; + let res = bls12_381_g2_msm_precompile.execute(&input, u64::MAX); + assert!( + matches!(res, Err(PrecompileError::Other(msg)) if msg.contains("input length too long")) + ); + + let bls12_381_pairing_precompile = precompiles + .precompiles() + .get(&bls12_381_const::PAIRING_ADDRESS) + .unwrap(); + bad_input_len = bls12_381::JOVIAN_PAIRING_MAX_INPUT_SIZE + 1; + assert!(bad_input_len < bls12_381::ISTHMUS_PAIRING_MAX_INPUT_SIZE); + let input = vec![0u8; bad_input_len]; + let res = bls12_381_pairing_precompile.execute(&input, u64::MAX); + assert!( + matches!(res, Err(PrecompileError::Other(msg)) if msg.contains("input length too long")) + ); + } + #[test] fn test_cancun_precompiles_in_fjord() { // additional to cancun, fjord has p256verify @@ -296,6 +477,23 @@ mod tests { assert!(new_prague_precompiles.difference(isthmus()).is_empty()) } + #[test] + fn test_prague_precompiles_in_jovian() { + let new_prague_precompiles = Precompiles::prague().difference(Precompiles::cancun()); + + // jovian contains all precompiles that were new in prague, without modifications + assert!(new_prague_precompiles.difference(jovian()).is_empty()) + } + + /// All the addresses of the precompiles in isthmus should be in jovian + #[test] + fn test_isthmus_precompiles_in_jovian() { + let new_isthmus_precompiles = isthmus().difference(Precompiles::cancun()); + + // jovian contains all precompiles that were new in isthmus, without modifications + assert!(new_isthmus_precompiles.difference(jovian()).is_empty()) + } + #[test] fn test_default_precompiles_is_latest() { let latest = OpPrecompiles::new_with_spec(OpSpecId::default()) @@ -313,7 +511,19 @@ mod tests { let oversized_input = vec![0u8; ISTHMUS_G1_MSM_MAX_INPUT_SIZE + 1]; let input = Bytes::from(oversized_input); - let res = run_g1_msm(&input, 260_000); + let res = run_g1_msm_isthmus(&input, 260_000); + + assert!( + matches!(res, Err(PrecompileError::Other(msg)) if msg.contains("input length too long")) + ); + } + + #[test] + fn test_g1_jovian_max_size() { + let oversized_input = vec![0u8; JOVIAN_G1_MSM_MAX_INPUT_SIZE + 1]; + let input = Bytes::from(oversized_input); + + let res = run_g1_msm_jovian(&input, u64::MAX); assert!( matches!(res, Err(PrecompileError::Other(msg)) if msg.contains("input length too long")) @@ -324,7 +534,18 @@ mod tests { let oversized_input = vec![0u8; ISTHMUS_G2_MSM_MAX_INPUT_SIZE + 1]; let input = Bytes::from(oversized_input); - let res = run_g2_msm(&input, 260_000); + let res = run_g2_msm_isthmus(&input, 260_000); + + assert!( + matches!(res, Err(PrecompileError::Other(msg)) if msg.contains("input length too long")) + ); + } + #[test] + fn test_g2_jovian_max_size() { + let oversized_input = vec![0u8; JOVIAN_G2_MSM_MAX_INPUT_SIZE + 1]; + let input = Bytes::from(oversized_input); + + let res = run_g2_msm_jovian(&input, u64::MAX); assert!( matches!(res, Err(PrecompileError::Other(msg)) if msg.contains("input length too long")) @@ -335,7 +556,18 @@ mod tests { let oversized_input = vec![0u8; ISTHMUS_PAIRING_MAX_INPUT_SIZE + 1]; let input = Bytes::from(oversized_input); - let res = bls12_381::run_pair(&input, 260_000); + let res = bls12_381::run_pair_isthmus(&input, 260_000); + + assert!( + matches!(res, Err(PrecompileError::Other(msg)) if msg.contains("input length too long")) + ); + } + #[test] + fn test_pair_jovian_max_size() { + let oversized_input = vec![0u8; JOVIAN_PAIRING_MAX_INPUT_SIZE + 1]; + let input = Bytes::from(oversized_input); + + let res = bls12_381::run_pair_jovian(&input, u64::MAX); assert!( matches!(res, Err(PrecompileError::Other(msg)) if msg.contains("input length too long")) From 37379946c8a606e4c4731cf033ed0376a801c779 Mon Sep 17 00:00:00 2001 From: rakita Date: Thu, 30 Oct 2025 23:33:05 +0100 Subject: [PATCH 117/225] bump: tag v96 revm v31.0.0 (bluealloy/revm#3135) --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fbef6c87365..25462fe9e01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - *(op)* use helper function in validate against state ([#3069](https://github.com/bluealloy/revm/pull/3069)) + +## [11.3.0](https://github.com/bluealloy/revm/compare/op-revm-v11.2.0...op-revm-v11.3.0) - 2025-10-28 + +### Added + +- *(precompiles/jovian)* add jovian precompiles to revm ([#3128](https://github.com/bluealloy/revm/pull/3128)) + + ## [11.2.0](https://github.com/bluealloy/revm/compare/op-revm-v11.1.2...op-revm-v11.2.0) - 2025-10-17 ### Other From c2bf49fbbd09c86d75df58f296f31bbcdd83b402 Mon Sep 17 00:00:00 2001 From: rakita Date: Mon, 10 Nov 2025 15:44:37 +0100 Subject: [PATCH 118/225] fix(op): Ensure L1Block account is always loaded (bluealloy/revm#3150) (bluealloy/revm#3154) * fix(op): Ensure L1Block account is always loaded * dont panic on State::storage fn * add test for pre regolit tx --- src/l1block.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/l1block.rs b/src/l1block.rs index 67f52b6b601..d39dc52ffef 100644 --- a/src/l1block.rs +++ b/src/l1block.rs @@ -16,7 +16,7 @@ use revm::{ gas::{get_tokens_in_calldata, NON_ZERO_BYTE_MULTIPLIER_ISTANBUL, STANDARD_TOKEN_COST}, Gas, }, - primitives::{hardfork::SpecId, U256}, + primitives::U256, }; /// L1 block info @@ -138,11 +138,8 @@ impl L1BlockInfo { l2_block: U256, spec_id: OpSpecId, ) -> Result { - // Ensure the L1 Block account is loaded into the cache after Ecotone. With EIP-4788, it is no longer the case - // that the L1 block account is loaded into the cache prior to the first inquiry for the L1 block info. - if spec_id.into_eth_spec().is_enabled_in(SpecId::CANCUN) { - let _ = db.basic(L1_BLOCK_CONTRACT)?; - } + // Ensure the L1 Block account is loaded into the cache. + let _ = db.basic(L1_BLOCK_CONTRACT)?; let mut out = L1BlockInfo { l2_block: Some(l2_block), From 97713d6ee5495e3a29a10d60cde5ce8f50092157 Mon Sep 17 00:00:00 2001 From: rakita Date: Mon, 10 Nov 2025 15:51:22 +0100 Subject: [PATCH 119/225] chore: merge v98 versions bumps (bluealloy/revm#3155) --- CHANGELOG.md | 12 ++++++++++++ Cargo.toml | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25462fe9e01..80d735ec38d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [12.0.2](https://github.com/bluealloy/revm/compare/op-revm-v12.0.1...op-revm-v12.0.2) - 2025-11-10 + +### Fixed + +- *(op)* Ensure L1Block account is always loaded ([#3150](https://github.com/bluealloy/revm/pull/3150)) + +## [12.0.1](https://github.com/bluealloy/revm/compare/op-revm-v12.0.0...op-revm-v12.0.1) - 2025-11-07 + +### Other + +- updated the following local packages: revm + ## [12.0.0](https://github.com/bluealloy/revm/compare/op-revm-v11.2.0...op-revm-v12.0.0) - 2025-10-30 ### Added diff --git a/Cargo.toml b/Cargo.toml index 231bfe077a5..4ad21a2eee1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "12.0.0" +version = "12.0.2" authors.workspace = true edition.workspace = true keywords.workspace = true From 4dbffd2f816cfe640f702a073057726a589dbb2e Mon Sep 17 00:00:00 2001 From: rakita Date: Mon, 10 Nov 2025 19:22:25 +0100 Subject: [PATCH 120/225] feat: process precompile logs to inspector (bluealloy/revm#3148) * 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 --- src/handler.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index 5f4f4ed16d8..925d9dc663a 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1279,14 +1279,14 @@ mod tests { assert_eq!( handler.execution_result( &mut evm, - FrameResult::Call(CallOutcome { - result: InterpreterResult { + FrameResult::Call(CallOutcome::new( + InterpreterResult { result: InstructionResult::OutOfGas, output: Default::default(), gas: Default::default(), }, - memory_offset: Default::default(), - }) + Default::default() + )) ), Err(EVMError::Transaction( OpTransactionError::HaltedDepositPostRegolith From d9694c1de77e4148f49974d8df554af41c0b78f4 Mon Sep 17 00:00:00 2001 From: rakita Date: Mon, 10 Nov 2025 19:38:33 +0100 Subject: [PATCH 121/225] feat(precompiles): add performant PrecompileError::OtherCowStr variant (bluealloy/revm#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 --- src/precompiles.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/precompiles.rs b/src/precompiles.rs index 025a6afbadc..3de2d0813d7 100644 --- a/src/precompiles.rs +++ b/src/precompiles.rs @@ -216,9 +216,6 @@ pub mod bls12_381 { use super::*; use revm::precompile::bls12_381_const::{G1_MSM_ADDRESS, G2_MSM_ADDRESS, PAIRING_ADDRESS}; - #[cfg(not(feature = "std"))] - use crate::std::string::ToString; - /// Max input size for the g1 msm precompile. pub const ISTHMUS_G1_MSM_MAX_INPUT_SIZE: usize = 513760; @@ -264,7 +261,7 @@ pub mod bls12_381 { pub fn run_g1_msm_isthmus(input: &[u8], gas_limit: u64) -> PrecompileResult { if input.len() > ISTHMUS_G1_MSM_MAX_INPUT_SIZE { return Err(PrecompileError::Other( - "G1MSM input length too long for OP Stack input size limitation after the Isthmus Hardfork".to_string(), + "G1MSM input length too long for OP Stack input size limitation after the Isthmus Hardfork".into(), )); } precompile::bls12_381::g1_msm::g1_msm(input, gas_limit) @@ -274,7 +271,7 @@ pub mod bls12_381 { pub fn run_g1_msm_jovian(input: &[u8], gas_limit: u64) -> PrecompileResult { if input.len() > JOVIAN_G1_MSM_MAX_INPUT_SIZE { return Err(PrecompileError::Other( - "G1MSM input length too long for OP Stack input size limitation after the Jovian Hardfork".to_string(), + "G1MSM input length too long for OP Stack input size limitation after the Jovian Hardfork".into(), )); } precompile::bls12_381::g1_msm::g1_msm(input, gas_limit) @@ -284,7 +281,7 @@ pub mod bls12_381 { pub fn run_g2_msm_isthmus(input: &[u8], gas_limit: u64) -> PrecompileResult { if input.len() > ISTHMUS_G2_MSM_MAX_INPUT_SIZE { return Err(PrecompileError::Other( - "G2MSM input length too long for OP Stack input size limitation".to_string(), + "G2MSM input length too long for OP Stack input size limitation".into(), )); } precompile::bls12_381::g2_msm::g2_msm(input, gas_limit) @@ -294,7 +291,7 @@ pub mod bls12_381 { pub fn run_g2_msm_jovian(input: &[u8], gas_limit: u64) -> PrecompileResult { if input.len() > JOVIAN_G2_MSM_MAX_INPUT_SIZE { return Err(PrecompileError::Other( - "G2MSM input length too long for OP Stack input size limitation after the Jovian Hardfork".to_string(), + "G2MSM input length too long for OP Stack input size limitation after the Jovian Hardfork".into(), )); } precompile::bls12_381::g2_msm::g2_msm(input, gas_limit) @@ -304,7 +301,7 @@ pub mod bls12_381 { pub fn run_pair_isthmus(input: &[u8], gas_limit: u64) -> PrecompileResult { if input.len() > ISTHMUS_PAIRING_MAX_INPUT_SIZE { return Err(PrecompileError::Other( - "Pairing input length too long for OP Stack input size limitation".to_string(), + "Pairing input length too long for OP Stack input size limitation".into(), )); } precompile::bls12_381::pairing::pairing(input, gas_limit) @@ -314,7 +311,7 @@ pub mod bls12_381 { pub fn run_pair_jovian(input: &[u8], gas_limit: u64) -> PrecompileResult { if input.len() > JOVIAN_PAIRING_MAX_INPUT_SIZE { return Err(PrecompileError::Other( - "Pairing input length too long for OP Stack input size limitation after the Jovian Hardfork".to_string(), + "Pairing input length too long for OP Stack input size limitation after the Jovian Hardfork".into(), )); } precompile::bls12_381::pairing::pairing(input, gas_limit) From 87447eb699d66053194dbd675f7aac790a071797 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 23:38:11 +0100 Subject: [PATCH 122/225] chore: release (bluealloy/revm#3136) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 7 +++++++ Cargo.toml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80d735ec38d..ded8d99fa58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [12.1.0](https://github.com/bluealloy/revm/compare/op-revm-v12.0.2...op-revm-v12.1.0) - 2025-11-10 + +### Added + +- *(precompiles)* add performant PrecompileError::OtherCowStr variant ([#3144](https://github.com/bluealloy/revm/pull/3144)) +- process precompile logs to inspector ([#3148](https://github.com/bluealloy/revm/pull/3148)) + ## [12.0.2](https://github.com/bluealloy/revm/compare/op-revm-v12.0.1...op-revm-v12.0.2) - 2025-11-10 ### Fixed diff --git a/Cargo.toml b/Cargo.toml index 4ad21a2eee1..fbd4915d3bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "12.0.2" +version = "12.1.0" authors.workspace = true edition.workspace = true keywords.workspace = true From 1975a0717ca2c7c7c43b85538fdc8840cb29a6be Mon Sep 17 00:00:00 2001 From: rakita Date: Tue, 11 Nov 2025 00:07:13 +0100 Subject: [PATCH 123/225] bump: v99 revm v32.0.0 (bluealloy/revm#3157) * bump: v99 revm v32.0.0 * typo: --- CHANGELOG.md | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ded8d99fa58..45a1140d916 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [12.1.0](https://github.com/bluealloy/revm/compare/op-revm-v12.0.2...op-revm-v12.1.0) - 2025-11-10 +## [13.0.0](https://github.com/bluealloy/revm/compare/op-revm-v12.0.2...op-revm-v13.0.0) - 2025-11-10 ### Added diff --git a/Cargo.toml b/Cargo.toml index fbd4915d3bd..0596160be2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "12.1.0" +version = "13.0.0" authors.workspace = true edition.workspace = true keywords.workspace = true From 116fc61a00182e4dbe6ca77a6851a31567027e08 Mon Sep 17 00:00:00 2001 From: rakita Date: Wed, 12 Nov 2025 01:55:05 +0100 Subject: [PATCH 124/225] bump: tag v100 revm v33.0.0 (bluealloy/revm#3161) --- CHANGELOG.md | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45a1140d916..c9f8dcdf653 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [13.0.0](https://github.com/bluealloy/revm/compare/op-revm-v12.0.2...op-revm-v13.0.0) - 2025-11-10 +## [14.0.0](https://github.com/bluealloy/revm/compare/op-revm-v12.0.2...op-revm-v14.0.0) - 2025-11-10 ### Added diff --git a/Cargo.toml b/Cargo.toml index 0596160be2f..36c89567438 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "13.0.0" +version = "14.0.0" authors.workspace = true edition.workspace = true keywords.workspace = true From dc737d233ef2139420d879b30f6c6715956e6845 Mon Sep 17 00:00:00 2001 From: Keemo Styler Date: Thu, 13 Nov 2025 09:20:44 +0700 Subject: [PATCH 125/225] fix(op-revm): return error when enveloped_tx is missing (bluealloy/revm#3143) * fix(op-revm): return error when enveloped_tx is missing * fmt * Remove unnecessary clone --- src/handler.rs | 6 +++++- src/l1block.rs | 10 +++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index 925d9dc663a..f444e6f420f 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -152,7 +152,11 @@ where let mut balance = caller_account.info.balance; if !cfg.is_fee_charge_disabled() { - let additional_cost = chain.tx_cost_with_tx(tx, spec); + let Some(additional_cost) = chain.tx_cost_with_tx(tx, spec) else { + return Err(ERROR::from_string( + "[OPTIMISM] Failed to load enveloped transaction.".into(), + )); + }; let Some(new_balance) = balance.checked_sub(additional_cost) else { return Err(InvalidTransaction::LackOfFundForMaxFee { fee: Box::new(additional_cost), diff --git a/src/l1block.rs b/src/l1block.rs index d39dc52ffef..cf61db76974 100644 --- a/src/l1block.rs +++ b/src/l1block.rs @@ -261,15 +261,11 @@ impl L1BlockInfo { /// Calculate additional transaction cost with OpTxTr. /// /// Internally calls [`L1BlockInfo::tx_cost`]. - #[track_caller] - pub fn tx_cost_with_tx(&mut self, tx: impl OpTxTr, spec: OpSpecId) -> U256 { + pub fn tx_cost_with_tx(&mut self, tx: impl OpTxTr, spec: OpSpecId) -> Option { // account for additional cost of l1 fee and operator fee - let enveloped_tx = tx - .enveloped_tx() - .expect("all not deposit tx have enveloped tx") - .clone(); + let enveloped_tx = tx.enveloped_tx()?; let gas_limit = U256::from(tx.gas_limit()); - self.tx_cost(&enveloped_tx, gas_limit, spec) + Some(self.tx_cost(enveloped_tx, gas_limit, spec)) } /// Calculate additional transaction cost. From 9402bb946ba40ed3768f0d72ab34a90e8492d9ef Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 14 Nov 2025 14:17:38 +0100 Subject: [PATCH 126/225] chore: release (bluealloy/revm#3162) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 6 ++++++ Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9f8dcdf653..30b342cd5f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [14.0.1](https://github.com/bluealloy/revm/compare/op-revm-v14.0.0...op-revm-v14.0.1) - 2025-11-14 + +### Fixed + +- *(op-revm)* return error when enveloped_tx is missing ([#3143](https://github.com/bluealloy/revm/pull/3143)) + ## [14.0.0](https://github.com/bluealloy/revm/compare/op-revm-v12.0.2...op-revm-v14.0.0) - 2025-11-10 ### Added diff --git a/Cargo.toml b/Cargo.toml index 36c89567438..9649e0ecaea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "14.0.0" +version = "14.0.1" authors.workspace = true edition.workspace = true keywords.workspace = true From 48a49edfde73ce30f0493f01ace7f72e1a09dd1a Mon Sep 17 00:00:00 2001 From: rakita Date: Fri, 14 Nov 2025 15:05:53 +0100 Subject: [PATCH 127/225] bump: tag v102 revm v33.1.0 (bluealloy/revm#3177) --- CHANGELOG.md | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30b342cd5f9..e06472495de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [14.0.1](https://github.com/bluealloy/revm/compare/op-revm-v14.0.0...op-revm-v14.0.1) - 2025-11-14 +## [14.1.0](https://github.com/bluealloy/revm/compare/op-revm-v14.0.0...op-revm-v14.1.0) - 2025-11-14 ### Fixed diff --git a/Cargo.toml b/Cargo.toml index 9649e0ecaea..767d0f7cf2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "14.0.1" +version = "14.1.0" authors.workspace = true edition.workspace = true keywords.workspace = true From 7863014380dec3ca893d7f86de5946eae2af210b Mon Sep 17 00:00:00 2001 From: rakita Date: Thu, 20 Nov 2025 16:36:35 +0100 Subject: [PATCH 128/225] chore(fmt): merge all imports (bluealloy/revm#3184) --- src/fast_lz.rs | 5 ++--- src/precompiles.rs | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/fast_lz.rs b/src/fast_lz.rs index a1a262a079b..7d1f1c57a60 100644 --- a/src/fast_lz.rs +++ b/src/fast_lz.rs @@ -112,14 +112,13 @@ fn u24(input: &[u8], idx: u32) -> u32 { mod tests { use super::*; use crate::api::{builder::OpBuilder, default_ctx::DefaultOp}; - use alloy_sol_types::sol; - use alloy_sol_types::SolCall; + use alloy_sol_types::{sol, SolCall}; use revm::{ bytecode::Bytecode, database::{BenchmarkDB, EEADDRESS, FFADDRESS}, primitives::{bytes, Bytes, TxKind, U256}, + Context, ExecuteEvm, }; - use revm::{Context, ExecuteEvm}; use rstest::rstest; use std::vec::Vec; diff --git a/src/precompiles.rs b/src/precompiles.rs index 3de2d0813d7..d2349801644 100644 --- a/src/precompiles.rs +++ b/src/precompiles.rs @@ -11,8 +11,7 @@ use revm::{ }, primitives::{hardfork::SpecId, Address, OnceLock}, }; -use std::boxed::Box; -use std::string::String; +use std::{boxed::Box, string::String}; /// Optimism precompile provider #[derive(Debug, Clone)] From fc76d305524b56b7e95fa367cca7a847fb6388c9 Mon Sep 17 00:00:00 2001 From: rakita Date: Wed, 3 Dec 2025 02:43:09 +0100 Subject: [PATCH 129/225] feat: Restrict Database::Error. JournaledAccountTr (bluealloy/revm#3199) * feat: Restrict Database::Error. JournaledAccountTr * nits * nits std --- src/handler.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index f444e6f420f..ca86a3b72e7 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -6,7 +6,11 @@ use crate::{ L1BlockInfo, OpHaltReason, OpSpecId, }; use revm::{ - context::{journaled_state::JournalCheckpoint, result::InvalidTransaction, LocalContextTr}, + context::{ + journaled_state::{account::JournaledAccountTr, JournalCheckpoint}, + result::InvalidTransaction, + LocalContextTr, + }, context_interface::{ context::ContextError, result::{EVMError, ExecutionResult, FromStringError}, @@ -146,10 +150,10 @@ where let mut caller_account = journal.load_account_with_code_mut(tx.caller())?.data; // validates account nonce and code - validate_account_nonce_and_code_with_components(&caller_account.info, tx, cfg)?; + validate_account_nonce_and_code_with_components(&caller_account.account().info, tx, cfg)?; // check additional cost and deduct it from the caller's balances - let mut balance = caller_account.info.balance; + let mut balance = caller_account.account().info.balance; if !cfg.is_fee_charge_disabled() { let Some(additional_cost) = chain.tx_cost_with_tx(tx, spec) else { @@ -404,6 +408,8 @@ where acc.bump_nonce(); acc.incr_balance(U256::from(mint.unwrap_or_default())); + drop(acc); // Drop acc to avoid borrow checker issues. + // We can now commit the changes. journal.commit_tx(); From a7750cab67d242127ffa2bad60e092b0072e6203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=90=E1=BA=A1t=20Nguy=E1=BB=85n?= Date: Tue, 9 Dec 2025 00:01:16 +0700 Subject: [PATCH 130/225] feat: early return if the l1 fee scalar is zero (bluealloy/revm#3213) * feat: early return if the l1 fee scalar is zero * update code format --- src/l1block.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/l1block.rs b/src/l1block.rs index cf61db76974..3c86904b472 100644 --- a/src/l1block.rs +++ b/src/l1block.rs @@ -345,6 +345,10 @@ impl L1BlockInfo { /// `estimatedSize*(baseFeeScalar*l1BaseFee*16 + blobFeeScalar*l1BlobBaseFee)/1e12` fn calculate_tx_l1_cost_fjord(&self, input: &[u8]) -> U256 { let l1_fee_scaled = self.calculate_l1_fee_scaled_ecotone(); + if l1_fee_scaled.is_zero() { + return U256::ZERO; + } + let estimated_size = self.tx_estimated_size_fjord(input); estimated_size From 708c42311cbe655a273dc1146074ba57f1b0711b Mon Sep 17 00:00:00 2001 From: anim001k <140460766+anim001k@users.noreply.github.com> Date: Mon, 15 Dec 2025 14:18:42 +0100 Subject: [PATCH 131/225] chore: Remove redundant tx fetch in Optimism handler gas accounting (bluealloy/revm#3220) * Update handler.rs * Update handler.rs * Update contract.rs * Update memory.rs * Update system.rs * Update instructions.rs --- src/handler.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index ca86a3b72e7..13ee5c4f92e 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -220,13 +220,10 @@ where // Regolith, gas is reported as normal. gas.erase_cost(remaining); gas.record_refund(refunded); - } else if is_deposit { - let tx = ctx.tx(); - if tx.is_system_transaction() { - // System transactions were a special type of deposit transaction in - // the Bedrock hardfork that did not incur any gas costs. - gas.erase_cost(tx_gas_limit); - } + } else if is_deposit && tx.is_system_transaction() { + // System transactions were a special type of deposit transaction in + // the Bedrock hardfork that did not incur any gas costs. + gas.erase_cost(tx_gas_limit); } } else if instruction_result.is_revert() { // On Optimism, deposit transactions report gas usage uniquely to other From 0215bf592b4bb424cf1b946c485b47fa693f6cd6 Mon Sep 17 00:00:00 2001 From: rakita Date: Thu, 18 Dec 2025 11:35:28 +0100 Subject: [PATCH 132/225] feat: BAL EIP-7928 (bluealloy/revm#3070) * BAL * WIP * bal wip * bal followup * Database::bal and Bal IndexMap for accounts * bal builder and integration with databases * chore: bump eest tests v5.3.0 * bump bal for caller/beneficiary * bal builder from state on commit, alloy included * cleanup * bal integration in btests * wip sys call * fix few bugs, propagate error * remove bal panic from btest * error handling * cleanup bal tests * skip touching beneficiary if reward is 0 * handle local selfdestruct * feat: dont load access list immediatly * nits fmt * bump output as accounts now have original account info * BalDatabase * nit, rm clone * bump tests, add missing imports, cleanup * reause indexmap from alloy with default hasher * typos * add missing serde propagation * dont skip test * Create BalState and add it inside State so that we dont need to use BalDatabase * nits, and deserialization for Account without original info * propagate feature * fix: add bal_builder.commit to state, small cleanup * fix: bal binary search cases (bluealloy/revm#3139) * fix: bal binary search cases * nit(test): generalize BAL binary search test for any threshold * code cleanup --------- Co-authored-by: rakita * compile tests * Rename BalDatabaseError to EvmDatabaseError * throw error if bal exist but account/storage not * rename storage_id to account_id * rm println * use alloy main, clippy/typo fixes * ark the bytecode * typo * add statis default for Bytecode * use oncelock * try with oncelock * box original acc info --- src/handler.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index 13ee5c4f92e..ef51d2eddc0 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -378,7 +378,11 @@ where error: Self::Error, ) -> Result, Self::Error> { let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE; - let output = if error.is_tx_error() && is_deposit { + let is_tx_error = error.is_tx_error(); + let mut output = Err(error); + + // Deposit transaction can't fail so we manually handle it here. + if is_tx_error && is_deposit { let ctx = evm.ctx(); let spec = ctx.cfg().spec(); let tx = ctx.tx(); @@ -420,13 +424,12 @@ where 0 }; // clear the journal - Ok(ExecutionResult::Halt { + output = Ok(ExecutionResult::Halt { reason: OpHaltReason::FailedDeposit, gas_used, }) - } else { - Err(error) - }; + } + // do the cleanup evm.ctx().chain_mut().clear_tx_l1_cost(); evm.ctx().local_mut().clear(); From 38e9f269b8c885d2db360060639fd13f29449a01 Mon Sep 17 00:00:00 2001 From: rakita Date: Mon, 5 Jan 2026 13:59:16 +0100 Subject: [PATCH 133/225] chore: happy new year, 2026 licence (bluealloy/revm#3272) --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index be6d350ebe4..7edb0405ff4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021-2025 draganrakita +Copyright (c) 2021-2026 draganrakita Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From f3cd62f64ae86918e12db610fcf1de949fbea91d Mon Sep 17 00:00:00 2001 From: rakita Date: Thu, 8 Jan 2026 11:36:36 +0100 Subject: [PATCH 134/225] feat: move GasParams to Cfg (bluealloy/revm#3229) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: move GasParams to Cfg * propagate spec id * use cfg spec when evm is created * nits * nit * doc tests * use derive-where to skip fn * Move SetSpecTr and use it as main tr * remove SetSpecTr, introduce is_custom_gas_param flag * simplification * nits and tests fix * morf spec type * dont compare ptr * import fix * typo * ref alloy-eips * add core::error::Error impl to BalError 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * send/sync on GasParams * relex generic for some Cfg functions --------- Co-authored-by: Claude Opus 4.5 --- src/evm.rs | 18 +++++++++++++----- src/handler.rs | 50 +++++++++++++++++++++++++++----------------------- src/l1block.rs | 6 ++---- 3 files changed, 42 insertions(+), 32 deletions(-) diff --git a/src/evm.rs b/src/evm.rs index a93a172756c..64f28a3e354 100644 --- a/src/evm.rs +++ b/src/evm.rs @@ -1,7 +1,7 @@ //! Contains the `[OpEvm]` type and its implementation of the execution EVM traits. -use crate::precompiles::OpPrecompiles; +use crate::{precompiles::OpPrecompiles, OpSpecId}; use revm::{ - context::{ContextError, ContextSetters, Evm, FrameStack}, + context::{Cfg, ContextError, ContextSetters, Evm, FrameStack}, context_interface::ContextTr, handler::{ evm::FrameTr, @@ -26,17 +26,25 @@ pub struct OpEvm< pub Evm, ); -impl OpEvm, OpPrecompiles> { +impl + Clone>>, INSP> + OpEvm, OpPrecompiles> +{ /// Create a new Optimism EVM. pub fn new(ctx: CTX, inspector: INSP) -> Self { + let spec: OpSpecId = ctx.cfg().spec().into(); Self(Evm { ctx, inspector, - instruction: EthInstructions::new_mainnet(), - precompiles: OpPrecompiles::default(), + instruction: EthInstructions::new_mainnet_with_spec(spec.into()), + precompiles: OpPrecompiles::new_with_spec(spec), frame_stack: FrameStack::new_prealloc(8), }) } + + /// Consumes self and returns the inner context. + pub fn into_context(self) -> CTX { + self.0.ctx + } } impl OpEvm { diff --git a/src/handler.rs b/src/handler.rs index ef51d2eddc0..ada5a49a0fc 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -464,7 +464,7 @@ mod tests { }; use alloy_primitives::uint; use revm::{ - context::{BlockEnv, Context, TxEnv}, + context::{BlockEnv, CfgEnv, Context, TxEnv}, context_interface::result::InvalidTransaction, database::InMemoryDB, database_interface::EmptyDB, @@ -511,7 +511,7 @@ mod tests { .base(TxEnv::builder().gas_limit(100)) .build_fill(), ) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::BEDROCK); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::BEDROCK)); let gas = call_last_frame_return(ctx, InstructionResult::Revert, Gas::new(90)); assert_eq!(gas.remaining(), 90); @@ -527,7 +527,7 @@ mod tests { .base(TxEnv::builder().gas_limit(100)) .build_fill(), ) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::REGOLITH)); let gas = call_last_frame_return(ctx, InstructionResult::Stop, Gas::new(90)); assert_eq!(gas.remaining(), 90); @@ -544,7 +544,7 @@ mod tests { .source_hash(B256::from([1u8; 32])) .build_fill(), ) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::REGOLITH)); let mut ret_gas = Gas::new(90); ret_gas.record_refund(20); @@ -569,7 +569,7 @@ mod tests { .source_hash(B256::from([1u8; 32])) .build_fill(), ) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::BEDROCK); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::BEDROCK)); let gas = call_last_frame_return(ctx, InstructionResult::Stop, Gas::new(90)); assert_eq!(gas.remaining(), 0); assert_eq!(gas.spent(), 100); @@ -586,7 +586,7 @@ mod tests { .is_system_transaction() .build_fill(), ) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::BEDROCK); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::BEDROCK)); let gas = call_last_frame_return(ctx, InstructionResult::Stop, Gas::new(90)); assert_eq!(gas.remaining(), 100); assert_eq!(gas.spent(), 0); @@ -613,7 +613,7 @@ mod tests { l1_base_fee_scalar: U256::from(1_000), ..Default::default() }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::REGOLITH)); ctx.modify_tx(|tx| { tx.deposit.source_hash = B256::from([1u8; 32]); tx.deposit.mint = Some(10); @@ -652,7 +652,7 @@ mod tests { l2_block: Some(U256::from(0)), ..Default::default() }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH) + .with_cfg(CfgEnv::new_with_spec(OpSpecId::REGOLITH)) .with_tx( OpTransaction::builder() .base(TxEnv::builder().gas_limit(100)) @@ -725,7 +725,7 @@ mod tests { number: BLOCK_NUM, ..Default::default() }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::ISTHMUS)); let mut evm = ctx.build_op(); @@ -812,7 +812,7 @@ mod tests { number: BLOCK_NUM, ..Default::default() }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::JOVIAN) + .with_cfg(CfgEnv::new_with_spec(OpSpecId::JOVIAN)) // set the operator fee to a low value .with_tx( OpTransaction::builder() @@ -880,7 +880,7 @@ mod tests { number: BLOCK_NUM, ..Default::default() }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::REGOLITH)); let mut evm = ctx.build_op(); assert_ne!(evm.ctx().chain().l2_block, Some(BLOCK_NUM)); @@ -940,7 +940,7 @@ mod tests { number: BLOCK_NUM, ..Default::default() }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ECOTONE); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::ECOTONE)); let mut evm = ctx.build_op(); assert_ne!(evm.ctx().chain().l2_block, Some(BLOCK_NUM)); @@ -1013,7 +1013,7 @@ mod tests { number: BLOCK_NUM, ..Default::default() }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::ISTHMUS)); let mut evm = ctx.build_op(); @@ -1063,7 +1063,7 @@ mod tests { l2_block: Some(U256::from(0)), ..Default::default() }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH) + .with_cfg(CfgEnv::new_with_spec(OpSpecId::REGOLITH)) .with_tx( OpTransaction::builder() .base(TxEnv::builder().gas_limit(100)) @@ -1106,7 +1106,7 @@ mod tests { l2_block: Some(U256::from(0)), ..Default::default() }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS) + .with_cfg(CfgEnv::new_with_spec(OpSpecId::ISTHMUS)) .with_tx( OpTransaction::builder() .base(TxEnv::builder().gas_limit(10)) @@ -1148,7 +1148,7 @@ mod tests { l2_block: Some(U256::from(0)), ..Default::default() }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::JOVIAN) + .with_cfg(CfgEnv::new_with_spec(OpSpecId::JOVIAN)) .with_tx( OpTransaction::builder() .base(TxEnv::builder().gas_limit(10)) @@ -1190,7 +1190,7 @@ mod tests { l2_block: Some(U256::from(0)), ..Default::default() }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH) + .with_cfg(CfgEnv::new_with_spec(OpSpecId::REGOLITH)) .modify_tx_chained(|tx| { tx.enveloped_tx = Some(bytes!("FACADE")); }); @@ -1221,7 +1221,7 @@ mod tests { tx.deposit.source_hash = B256::from([1u8; 32]); tx.deposit.is_system_transaction = true; }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::REGOLITH)); let mut evm = ctx.build_op(); let handler = @@ -1234,7 +1234,11 @@ mod tests { )) ); - evm.ctx().modify_cfg(|cfg| cfg.spec = OpSpecId::BEDROCK); + // With BEDROCK spec. + let ctx = evm.into_context(); + let mut evm = ctx + .with_cfg(CfgEnv::new_with_spec(OpSpecId::BEDROCK)) + .build_op(); // Pre-regolith system transactions should be allowed. assert!(handler.validate_env(&mut evm).is_ok()); @@ -1247,7 +1251,7 @@ mod tests { .modify_tx_chained(|tx| { tx.deposit.source_hash = B256::from([1u8; 32]); }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::REGOLITH)); let mut evm = ctx.build_op(); let handler = @@ -1263,7 +1267,7 @@ mod tests { .modify_tx_chained(|tx| { tx.deposit.source_hash = B256::from([1u8; 32]); }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::REGOLITH)); let mut evm = ctx.build_op(); let handler = @@ -1280,7 +1284,7 @@ mod tests { // Set up as deposit transaction by having a deposit with source_hash tx.deposit.source_hash = B256::from([1u8; 32]); }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::REGOLITH)); let mut evm = ctx.build_op(); let mut handler = @@ -1363,7 +1367,7 @@ mod tests { }) .build_fill(), ) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS); + .with_cfg(CfgEnv::new_with_spec(OpSpecId::ISTHMUS)); let mut evm = ctx.build_op(); let handler = diff --git a/src/l1block.rs b/src/l1block.rs index 3c86904b472..325796f4da0 100644 --- a/src/l1block.rs +++ b/src/l1block.rs @@ -11,11 +11,9 @@ use crate::{ OpSpecId, }; use revm::{ + context_interface::cfg::gas::{NON_ZERO_BYTE_MULTIPLIER_ISTANBUL, STANDARD_TOKEN_COST}, database_interface::Database, - interpreter::{ - gas::{get_tokens_in_calldata, NON_ZERO_BYTE_MULTIPLIER_ISTANBUL, STANDARD_TOKEN_COST}, - Gas, - }, + interpreter::{gas::get_tokens_in_calldata, Gas}, primitives::U256, }; From 3f51ad1191c331ba102ca7cd15d2d62e7522e477 Mon Sep 17 00:00:00 2001 From: rakita Date: Thu, 8 Jan 2026 11:45:58 +0100 Subject: [PATCH 135/225] feat: new gas params, tx initial gas and codedeposit (bluealloy/revm#3260) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: move GasParams to Cfg * propagate spec id * use cfg spec when evm is created * nits * nit * doc tests * use derive-where to skip fn * Move SetSpecTr and use it as main tr * feat: add codedeposit price in GasParams * remove SetSpecTr, introduce is_custom_gas_param flag * simplification * nits and tests fix * morf spec type * dont compare ptr * import fix * typo * ref alloy-eips * add core::error::Error impl to BalError 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * send/sync on GasParams * relex generic for some Cfg functions * load_account_mut_skip_cold * WIP * move initial tx gas calc to GasParams * fixes --------- Co-authored-by: Claude Opus 4.5 --- src/l1block.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/l1block.rs b/src/l1block.rs index 325796f4da0..eff2ab0a532 100644 --- a/src/l1block.rs +++ b/src/l1block.rs @@ -13,7 +13,7 @@ use crate::{ use revm::{ context_interface::cfg::gas::{NON_ZERO_BYTE_MULTIPLIER_ISTANBUL, STANDARD_TOKEN_COST}, database_interface::Database, - interpreter::{gas::get_tokens_in_calldata, Gas}, + interpreter::{gas::get_tokens_in_calldata_istanbul, Gas}, primitives::U256, }; @@ -234,7 +234,7 @@ impl L1BlockInfo { }; // tokens in calldata where non-zero bytes are priced 4 times higher than zero bytes (Same as in Istanbul). - let mut tokens_in_transaction_data = get_tokens_in_calldata(input, true); + let mut tokens_in_transaction_data = get_tokens_in_calldata_istanbul(input); // Prior to regolith, an extra 68 non zero bytes were included in the rollup data costs. if !spec_id.is_enabled_in(OpSpecId::REGOLITH) { From 74b42d0950b6d6af8ff72910d3a8d5c749db75ec Mon Sep 17 00:00:00 2001 From: leopardracer <136604165+leopardracer@users.noreply.github.com> Date: Fri, 9 Jan 2026 15:06:09 +0200 Subject: [PATCH 136/225] chore: fix typos, grammar errors, and improve documentation consistency (bluealloy/revm#3294) * Update README.md * Fix typo in MIGRATION_GUIDE.md Corrected a typo in the description of `PrecompileError`. * Update inspector.md * Update revme.md * Update contact.md * Update documentation comment for isthmus function * Update awesome.md * Update architecture.md * Update README.md * Update README.md --- src/precompiles.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/precompiles.rs b/src/precompiles.rs index d2349801644..14df489c578 100644 --- a/src/precompiles.rs +++ b/src/precompiles.rs @@ -75,7 +75,7 @@ pub fn granite() -> &'static Precompiles { }) } -/// Returns precompiles for isthumus spec. +/// Returns precompiles for isthmus spec. pub fn isthmus() -> &'static Precompiles { static INSTANCE: OnceLock = OnceLock::new(); INSTANCE.get_or_init(|| { From cf41c936eb74b6fb31494e25b95596bc274363c0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 11:27:23 +0100 Subject: [PATCH 137/225] chore: release (bluealloy/revm#3175) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 17 +++++++++++++++++ Cargo.toml | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e06472495de..e9061aa4c20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [14.2.0](https://github.com/bluealloy/revm/compare/op-revm-v14.1.0...op-revm-v14.2.0) - 2026-01-15 + +### Added + +- new gas params, tx initial gas and codedeposit ([#3260](https://github.com/bluealloy/revm/pull/3260)) +- move GasParams to Cfg ([#3229](https://github.com/bluealloy/revm/pull/3229)) +- BAL EIP-7928 ([#3070](https://github.com/bluealloy/revm/pull/3070)) +- early return if the l1 fee scalar is zero ([#3213](https://github.com/bluealloy/revm/pull/3213)) +- Restrict Database::Error. JournaledAccountTr ([#3199](https://github.com/bluealloy/revm/pull/3199)) + +### Other + +- fix typos, grammar errors, and improve documentation consistency ([#3294](https://github.com/bluealloy/revm/pull/3294)) +- happy new year, 2026 licence ([#3272](https://github.com/bluealloy/revm/pull/3272)) +- Remove redundant tx fetch in Optimism handler gas accounting ([#3220](https://github.com/bluealloy/revm/pull/3220)) +- *(fmt)* merge all imports ([#3184](https://github.com/bluealloy/revm/pull/3184)) + ## [14.1.0](https://github.com/bluealloy/revm/compare/op-revm-v14.0.0...op-revm-v14.1.0) - 2025-11-14 ### Fixed diff --git a/Cargo.toml b/Cargo.toml index 767d0f7cf2f..5564f315da7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "14.1.0" +version = "14.2.0" authors.workspace = true edition.workspace = true keywords.workspace = true From 1b39bf626812657d0c9d48f17e4d6b10d44a267f Mon Sep 17 00:00:00 2001 From: rakita Date: Thu, 15 Jan 2026 12:08:11 +0100 Subject: [PATCH 138/225] bump: revm v34.0.0 (bluealloy/revm#3313) --- CHANGELOG.md | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9061aa4c20..e7731b8828f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [14.2.0](https://github.com/bluealloy/revm/compare/op-revm-v14.1.0...op-revm-v14.2.0) - 2026-01-15 +## [15.0.0](https://github.com/bluealloy/revm/compare/op-revm-v14.1.0...op-revm-v15.0.0) - 2026-01-15 ### Added diff --git a/Cargo.toml b/Cargo.toml index 5564f315da7..59e1c7d0931 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "14.2.0" +version = "15.0.0" authors.workspace = true edition.workspace = true keywords.workspace = true From 51f931c819232132fd69d486e403c7865574760b Mon Sep 17 00:00:00 2001 From: zerosnacks <95942363+zerosnacks@users.noreply.github.com> Date: Mon, 19 Jan 2026 11:34:08 +0000 Subject: [PATCH 139/225] chore: update default hardfork to Osaka (Ethereum) and Jovian (Optimism) (bluealloy/revm#3326) - Update SpecId default from PRAGUE to OSAKA - Update OpSpecId default from ISTHMUS to JOVIAN - Add Osaka activation details: slot 13164544, timestamp 1764798551 (Dec 3, 2025) - Update default_op_spec_id test assertion - Disable tx gas limit cap for benchmarks (EIP-7825 enforcement) --- src/spec.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/spec.rs b/src/spec.rs index e192847a0f2..373ed11da58 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -23,9 +23,9 @@ pub enum OpSpecId { /// Holocene spec id. HOLOCENE, /// Isthmus spec id. - #[default] ISTHMUS, /// Jovian spec id. + #[default] JOVIAN, /// Interop spec id. INTEROP, @@ -243,6 +243,6 @@ mod tests { #[test] fn default_op_spec_id() { - assert_eq!(OpSpecId::default(), OpSpecId::ISTHMUS); + assert_eq!(OpSpecId::default(), OpSpecId::JOVIAN); } } From 786cf364db74acfa62b625fb17452af6b8a1c521 Mon Sep 17 00:00:00 2001 From: andrewshab <152420261+andrewshab3@users.noreply.github.com> Date: Mon, 26 Jan 2026 22:38:23 +0100 Subject: [PATCH 140/225] refactor(handler): extract duplicate ContextError handling (bluealloy/revm#3312) * Update context.rs * Update handler.rs * Update frame.rs * Update handler.rs * Update context.rs --- src/handler.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index ada5a49a0fc..128fcf8fcfa 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -12,7 +12,7 @@ use revm::{ LocalContextTr, }, context_interface::{ - context::ContextError, + context::take_error, result::{EVMError, ExecutionResult, FromStringError}, Block, Cfg, ContextTr, JournalTr, Transaction, }, @@ -346,11 +346,7 @@ where evm: &mut Self::Evm, frame_result: <::Frame as FrameTr>::FrameResult, ) -> Result, Self::Error> { - match core::mem::replace(evm.ctx().error(), Ok(())) { - Err(ContextError::Db(e)) => return Err(e.into()), - Err(ContextError::Custom(e)) => return Err(Self::Error::from_string(e)), - Ok(_) => (), - } + take_error::(evm.ctx().error())?; let exec_result = post_execution::output(evm.ctx(), frame_result).map_haltreason(OpHaltReason::Base); From 91611e218a44313687f6f4ecbdfce8b4c9ee0571 Mon Sep 17 00:00:00 2001 From: rakita Date: Thu, 12 Feb 2026 14:16:05 +0100 Subject: [PATCH 141/225] refactor!: add ResultGas struct to ExecutionResult (bluealloy/revm#3413) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor!: add `ResultGas` struct to `ExecutionResult` Replace loose `gas_used` and `gas_refunded` fields in all three `ExecutionResult` variants with a single `gas: ResultGas` struct that exposes richer gas accounting: `gas_used`, `gas_refunded`, `gas_spent` (pre-refund), and `floor_gas` (EIP-7623). * refactor: simplify ResultGas to store only independent fields Remove the redundant `gas_used` field from `ResultGas` since it can always be derived as `gas_spent - gas_refunded`. The struct now stores only three independent values (gas_spent, gas_refunded, floor_gas) and exposes `gas_used()` as a method. * refactor: refine ResultGas field naming and add Display impl - Rename fields to align with Gas struct methods: gas_spent → spent, gas_refunded → refunded, gas_used() → used() Eliminates redundant gas.gas_spent pattern (now gas.spent) - Add #[serde(rename)] to preserve JSON key stability - Add Display impl for ResultGas (conditionally shows refunded/floor) - Simplify ExecutionResult Display to delegate to ResultGas Display - Improve documentation with source mapping table - Add tests for ResultGas Display and used() method * feat: add gas limit to ResultGas for self-contained gas accounting Add `limit` field to `ResultGas` so it contains all gas information without requiring external context. Also add `remaining()` derived method (limit - spent) to complement `used()` (spent - refunded). ResultGas now mirrors the full Gas struct snapshot: - limit: transaction gas limit - spent: gas consumed before refund - refunded: gas refund amount - floor_gas: EIP-7623 floor gas - used(): spent - refunded (derived) - remaining(): limit - spent (derived) * feat: add `intrinsic_gas` to `ResultGas` and return it from `post_execution` Add intrinsic_gas field to ResultGas so it carries the initial tx overhead gas. Change post_execution to return ResultGas directly, and have execution_result receive a pre-built ResultGas instead of InitialAndFloorGas. * chore: fix formatting * feat: encapsulate ResultGas fields with getters, setters, and builders - Make all ResultGas fields private - Add getter methods: limit(), spent(), refunded(), floor_gas(), intrinsic_gas() - Add builder methods: with_limit(), with_spent(), with_refunded(), with_floor_gas(), with_intrinsic_gas() - Add setter methods: set_limit(), set_spent(), set_refunded(), set_floor_gas(), set_intrinsic_gas() - Add derived methods: final_used(), inner_refunded(), final_refunded() * docs: update ResultGas comments to reflect encapsulated API - Rewrite struct doc table to reference getter methods instead of fields - Fix used() doc formula to include floor_gas: max(spent - refunded, floor_gas) - Fix incomplete refunded field doc, point to final_refunded() - Remove stale final_used() reference, remove refunded() getter (replaced by inner_refunded) * chore: fix typo in handler comment * Update crates/context/interface/src/result.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix: correct grammar in handler.rs comment (bluealloy/revm#3422) * Initial plan * fix: correct grammar in handler.rs comment --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> --- src/handler.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index 128fcf8fcfa..a174a2b48b1 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -13,7 +13,7 @@ use revm::{ }, context_interface::{ context::take_error, - result::{EVMError, ExecutionResult, FromStringError}, + result::{EVMError, ExecutionResult, FromStringError, ResultGas}, Block, Cfg, ContextTr, JournalTr, Transaction, }, handler::{ @@ -345,11 +345,12 @@ where &mut self, evm: &mut Self::Evm, frame_result: <::Frame as FrameTr>::FrameResult, + result_gas: ResultGas, ) -> Result, Self::Error> { take_error::(evm.ctx().error())?; - let exec_result = - post_execution::output(evm.ctx(), frame_result).map_haltreason(OpHaltReason::Base); + let exec_result = post_execution::output(evm.ctx(), frame_result, result_gas) + .map_haltreason(OpHaltReason::Base); if exec_result.is_halt() { // Post-regolith, if the transaction is a deposit transaction and it halts, @@ -422,7 +423,7 @@ where // clear the journal output = Ok(ExecutionResult::Halt { reason: OpHaltReason::FailedDeposit, - gas_used, + gas: ResultGas::new(gas_limit, gas_used, 0, 0, 0), }) } @@ -1296,7 +1297,8 @@ mod tests { gas: Default::default(), }, Default::default() - )) + )), + ResultGas::default(), ), Err(EVMError::Transaction( OpTransactionError::HaltedDepositPostRegolith From d5b86865b411778bc7607b96e7176a7d84645356 Mon Sep 17 00:00:00 2001 From: rakita Date: Fri, 13 Feb 2026 12:22:18 +0100 Subject: [PATCH 142/225] refactor!: add logs to Revert and Halt variants of ExecutionResult (bluealloy/revm#3424) * refactor!: add `logs` field to `Revert` and `Halt` variants of `ExecutionResult` Logs emitted before a revert or halt were previously discarded. This preserves them by adding `logs: Vec` to both variants so that downstream consumers can inspect pre-failure logs. * fix: import Vec in op-revm handler for no_std compatibility * fix: formatting in ExecutionResult logs() and into_logs() methods --- src/handler.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/handler.rs b/src/handler.rs index a174a2b48b1..f04d88897b6 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -27,7 +27,7 @@ use revm::{ interpreter::{interpreter::EthInterpreter, interpreter_action::FrameInit, Gas}, primitives::{hardfork::SpecId, U256}, }; -use std::boxed::Box; +use std::{boxed::Box, vec::Vec}; /// Optimism handler extends the [`Handler`] with Optimism specific logic. #[derive(Debug, Clone)] @@ -424,6 +424,7 @@ where output = Ok(ExecutionResult::Halt { reason: OpHaltReason::FailedDeposit, gas: ResultGas::new(gas_limit, gas_used, 0, 0, 0), + logs: Vec::new(), }) } From abd29c80960e367ba3140369123ff7879245f5fe Mon Sep 17 00:00:00 2001 From: jakevin Date: Mon, 2 Mar 2026 10:59:43 +0800 Subject: [PATCH 143/225] docs(op-revm): fix operator fee field doc comments (bluealloy/revm#3457) --- src/l1block.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/l1block.rs b/src/l1block.rs index eff2ab0a532..b9772b02061 100644 --- a/src/l1block.rs +++ b/src/l1block.rs @@ -43,9 +43,9 @@ pub struct L1BlockInfo { pub l1_blob_base_fee: Option, /// The current L1 blob base fee scalar. None if Ecotone is not activated. pub l1_blob_base_fee_scalar: Option, - /// The current L1 blob base fee. None if Isthmus is not activated, except if `empty_ecotone_scalars` is `true`. + /// The operator fee scalar. None if Isthmus is not activated. pub operator_fee_scalar: Option, - /// The current L1 blob base fee scalar. None if Isthmus is not activated. + /// The operator fee constant. None if Isthmus is not activated. pub operator_fee_constant: Option, /// Da footprint gas scalar. Used to set the DA footprint block limit on the L2. Always null prior to the Jovian hardfork. pub da_footprint_gas_scalar: Option, From 4b8b1a6a84785b12ab0455ecd754f77793b1d484 Mon Sep 17 00:00:00 2001 From: jakevin Date: Mon, 2 Mar 2026 11:01:07 +0800 Subject: [PATCH 144/225] perf(op-revm): remove unnecessary enveloped_tx clone in reward_beneficiary (bluealloy/revm#3455) Use all_mut() to simultaneously borrow tx (immutable) and chain/journal (mutable), avoiding an unnecessary deep copy of the enveloped transaction bytes on every non-deposit transaction. --- src/handler.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index f04d88897b6..c611e1985fa 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -306,12 +306,12 @@ where // If the transaction is not a deposit transaction, fees are paid out // to both the Base Fee Vault as well as the L1 Fee Vault. - let ctx = evm.ctx(); - let enveloped = ctx.tx().enveloped_tx().cloned(); - let spec = ctx.cfg().spec(); - let l1_block_info = ctx.chain_mut(); + // Use all_mut() to simultaneously borrow tx (immutable) and chain (mutable), + // avoiding an unnecessary clone of the enveloped transaction bytes. + let (_, tx, cfg, journal, l1_block_info, _) = evm.ctx().all_mut(); + let spec = cfg.spec(); - let Some(enveloped_tx) = &enveloped else { + let Some(enveloped_tx) = tx.enveloped_tx() else { return Err(ERROR::from_string( "[OPTIMISM] Failed to load enveloped transaction.".into(), )); @@ -335,7 +335,7 @@ where (BASE_FEE_RECIPIENT, base_fee_amount), (OPERATOR_FEE_RECIPIENT, operator_fee_cost), ] { - ctx.journal_mut().balance_incr(recipient, amount)?; + journal.balance_incr(recipient, amount)?; } Ok(()) From bf19a2541e35487a4aec9f7c1b4b0f1c5ad0ce0f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2026 15:18:14 +0100 Subject: [PATCH 145/225] chore: release (bluealloy/revm#3316) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 12 ++++++++++++ Cargo.toml | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7731b8828f..3330aea8b50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [16.0.0](https://github.com/bluealloy/revm/compare/op-revm-v15.0.0...op-revm-v16.0.0) - 2026-03-02 + +### Other + +- *(op-revm)* remove unnecessary enveloped_tx clone in reward_beneficiary ([#3455](https://github.com/bluealloy/revm/pull/3455)) +- *(op-revm)* fix operator fee field doc comments ([#3457](https://github.com/bluealloy/revm/pull/3457)) +- [**breaking**] add logs to Revert and Halt variants of ExecutionResult ([#3424](https://github.com/bluealloy/revm/pull/3424)) +- [**breaking**] add ResultGas struct to ExecutionResult ([#3413](https://github.com/bluealloy/revm/pull/3413)) +- remove GPL mention and update gmp feature comments ([#3383](https://github.com/bluealloy/revm/pull/3383)) +- *(handler)* extract duplicate ContextError handling ([#3312](https://github.com/bluealloy/revm/pull/3312)) +- update default hardfork to Osaka (Ethereum) and Jovian (Optimism) ([#3326](https://github.com/bluealloy/revm/pull/3326)) + ## [15.0.0](https://github.com/bluealloy/revm/compare/op-revm-v14.1.0...op-revm-v15.0.0) - 2026-01-15 ### Added diff --git a/Cargo.toml b/Cargo.toml index 59e1c7d0931..838307d6a20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "15.0.0" +version = "16.0.0" authors.workspace = true edition.workspace = true keywords.workspace = true From c4138ee366cfa45a51c14c65676eb69eff274b74 Mon Sep 17 00:00:00 2001 From: rakita Date: Wed, 4 Mar 2026 12:02:25 +0100 Subject: [PATCH 146/225] chore: bump revm-database-interface to v10.0.0 and all dependents (v107) (bluealloy/revm#3474) --- CHANGELOG.md | 6 ++++++ Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3330aea8b50..8f155079fbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [17.0.0](https://github.com/bluealloy/revm/compare/op-revm-v16.0.0...op-revm-v17.0.0) - 2026-03-04 + +### Other + +- bump revm-database-interface to v10.0.0 + ## [16.0.0](https://github.com/bluealloy/revm/compare/op-revm-v15.0.0...op-revm-v16.0.0) - 2026-03-02 ### Other diff --git a/Cargo.toml b/Cargo.toml index 838307d6a20..30a523c56bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-revm" description = "Optimism variant of Revm" -version = "16.0.0" +version = "17.0.0" authors.workspace = true edition.workspace = true keywords.workspace = true From ba62bcd8f0d9ebc046c628b0626f0f9ebc140cef Mon Sep 17 00:00:00 2001 From: rakita Date: Mon, 16 Mar 2026 17:43:38 +0100 Subject: [PATCH 147/225] feat: add crate-level re-exports for all revm-* dependencies (bluealloy/revm#3507) * feat: add crate-level re-exports for all revm-* dependencies Re-export all revm-* dependencies as modules via `pub use crate_name;` in each crate's lib.rs, making them transitively accessible (e.g., `handler::context::...`, `interpreter::primitives::...`). Note: in inspector, `handler` crate is re-exported as `evm_handler` to avoid conflict with the local `pub mod handler`. * style: fix formatting in bytecode lib.rs --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 485d5adc173..a6ad9df08c1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,8 @@ pub mod result; pub mod spec; pub mod transaction; +pub use revm; + pub use api::{ builder::OpBuilder, default_ctx::{DefaultOp, OpContext}, From c79483d5d04949e8a99dc3c11c79a28825236396 Mon Sep 17 00:00:00 2001 From: Cooper Mosawi Date: Mon, 23 Mar 2026 20:40:28 +1200 Subject: [PATCH 148/225] chore(op-revm): use typed error for missing enveloped tx in all paths (bluealloy/revm#3509) --- src/handler.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index c611e1985fa..ce3abd051a0 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -157,9 +157,7 @@ where if !cfg.is_fee_charge_disabled() { let Some(additional_cost) = chain.tx_cost_with_tx(tx, spec) else { - return Err(ERROR::from_string( - "[OPTIMISM] Failed to load enveloped transaction.".into(), - )); + return Err(OpTransactionError::MissingEnvelopedTx.into()); }; let Some(new_balance) = balance.checked_sub(additional_cost) else { return Err(InvalidTransaction::LackOfFundForMaxFee { @@ -312,9 +310,7 @@ where let spec = cfg.spec(); let Some(enveloped_tx) = tx.enveloped_tx() else { - return Err(ERROR::from_string( - "[OPTIMISM] Failed to load enveloped transaction.".into(), - )); + return Err(OpTransactionError::MissingEnvelopedTx.into()); }; let l1_cost = l1_block_info.calculate_tx_l1_cost(enveloped_tx, spec); From 7bc2cdf8cf0089edbf4c7a5809d8fc56dec4400c Mon Sep 17 00:00:00 2001 From: Arun Dhyani Date: Sat, 4 Apr 2026 19:13:59 +0530 Subject: [PATCH 149/225] feat(op-reth): Proof store v2 implementation --- op-devstack/sysgo/mixed_runtime.go | 12 +- op-devstack/sysgo/singlechain_build.go | 2 +- .../crates/cli/src/commands/op_proofs/init.rs | 113 +- .../cli/src/commands/op_proofs/prune.rs | 74 +- .../cli/src/commands/op_proofs/unwind.rs | 71 +- rust/op-reth/crates/node/src/args.rs | 25 + rust/op-reth/crates/node/src/proof_history.rs | 114 +- rust/op-reth/crates/trie/Cargo.toml | 1 + rust/op-reth/crates/trie/src/db/cursor_v2.rs | 3580 ++++++++++++++++ rust/op-reth/crates/trie/src/db/mod.rs | 9 + rust/op-reth/crates/trie/src/db/models/key.rs | 265 ++ rust/op-reth/crates/trie/src/db/models/kv.rs | 2 + rust/op-reth/crates/trie/src/db/models/mod.rs | 159 +- .../crates/trie/src/db/models/value.rs | 135 + rust/op-reth/crates/trie/src/db/store_v2.rs | 3810 +++++++++++++++++ rust/op-reth/crates/trie/src/lib.rs | 4 +- rust/op-reth/crates/trie/tests/lib.rs | 69 +- rust/op-reth/crates/trie/tests/live.rs | 103 +- rust/op-reth/tests/proofs/utils/preset.go | 2 + 19 files changed, 8369 insertions(+), 181 deletions(-) create mode 100644 rust/op-reth/crates/trie/src/db/cursor_v2.rs create mode 100644 rust/op-reth/crates/trie/src/db/models/key.rs create mode 100644 rust/op-reth/crates/trie/src/db/models/value.rs create mode 100644 rust/op-reth/crates/trie/src/db/store_v2.rs diff --git a/op-devstack/sysgo/mixed_runtime.go b/op-devstack/sysgo/mixed_runtime.go index d3de1b786fb..f87bddeeb31 100644 --- a/op-devstack/sysgo/mixed_runtime.go +++ b/op-devstack/sysgo/mixed_runtime.go @@ -45,8 +45,9 @@ type MixedL2ELKind string const DevstackL2ELKindEnvVar = "DEVSTACK_L2EL_KIND" const ( - MixedL2ELOpGeth MixedL2ELKind = "op-geth" - MixedL2ELOpReth MixedL2ELKind = "op-reth" + MixedL2ELOpGeth MixedL2ELKind = "op-geth" + MixedL2ELOpReth MixedL2ELKind = "op-reth" + MixedL2ELOpRethV2 MixedL2ELKind = "op-reth-with-proof-v2" ) // SkipUnlessOpGeth skips the test when the L2 execution layer is op-reth @@ -144,7 +145,9 @@ func NewMixedSingleChainRuntime(t devtest.T, cfg MixedSingleChainPresetConfig) * case MixedL2ELOpGeth: el = startL2ELNode(t, l2Net, jwtPath, jwtSecret, spec.ELKey, identity) case MixedL2ELOpReth: - el = startMixedOpRethNode(t, l2Net, spec.ELKey, jwtPath, jwtSecret, metricsRegistrar) + el = startMixedOpRethNode(t, l2Net, spec.ELKey, jwtPath, jwtSecret, metricsRegistrar, "v1") + case MixedL2ELOpRethV2: + el = startMixedOpRethNode(t, l2Net, spec.ELKey, jwtPath, jwtSecret, metricsRegistrar, "v2") default: require.FailNowf("unsupported EL kind", "unsupported mixed EL kind %q", spec.ELKind) } @@ -255,6 +258,7 @@ func startMixedOpRethNode( jwtPath string, jwtSecret [32]byte, metricsRegistrar L2MetricsRegistrar, + storageVersion string, ) *OpReth { tempDir := t.TempDir() @@ -330,6 +334,7 @@ func startMixedOpRethNode( "--datadir=" + dataDirPath, "--chain=" + chainConfigPath, "--proofs-history.storage-path=" + proofHistoryDir, + "--proofs-history.storage-version=" + storageVersion, } err = exec.Command(execPath, initProofsArgs...).Run() t.Require().NoError(err, "must init op-reth proof history") @@ -340,6 +345,7 @@ func startMixedOpRethNode( "--proofs-history.window=10000", "--proofs-history.prune-interval=1m", "--proofs-history.storage-path="+proofHistoryDir, + "--proofs-history.storage-version="+storageVersion, ) l2EL := &OpReth{ diff --git a/op-devstack/sysgo/singlechain_build.go b/op-devstack/sysgo/singlechain_build.go index 23c03735a4e..2bf0c536bd1 100644 --- a/op-devstack/sysgo/singlechain_build.go +++ b/op-devstack/sysgo/singlechain_build.go @@ -157,7 +157,7 @@ func startL2ELForKey(t devtest.T, l2Net *L2Network, jwtPath string, jwtSecret [3 case MixedL2ELOpGeth: return startL2ELNode(t, l2Net, jwtPath, jwtSecret, key, identity) default: // op-reth - return startMixedOpRethNode(t, l2Net, key, jwtPath, jwtSecret, nil) + return startMixedOpRethNode(t, l2Net, key, jwtPath, jwtSecret, nil, "v1") } } diff --git a/rust/op-reth/crates/cli/src/commands/op_proofs/init.rs b/rust/op-reth/crates/cli/src/commands/op_proofs/init.rs index 0c05fcd0471..cc474316eb7 100644 --- a/rust/op-reth/crates/cli/src/commands/op_proofs/init.rs +++ b/rust/op-reth/crates/cli/src/commands/op_proofs/init.rs @@ -7,7 +7,11 @@ use reth_cli_commands::common::{AccessRights, CliNodeTypes, Environment, Environ use reth_node_core::version::version_metadata; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_primitives::OpPrimitives; -use reth_optimism_trie::{InitializationJob, OpProofsStore, OpProofsProviderRO, db::MdbxProofsStorage}; +use reth_optimism_node::args::ProofsStorageVersion; +use reth_optimism_trie::{ + InitializationJob, OpProofsProviderRO, OpProofsStore, + db::{MdbxProofsStorage, MdbxProofsStorageV2}, +}; use reth_provider::{BlockNumReader, DBProvider, DatabaseProviderFactory}; use std::{path::PathBuf, sync::Arc}; use tracing::info; @@ -31,6 +35,14 @@ pub struct InitCommand { required = true )] pub storage_path: PathBuf, + + /// Storage schema version. Must match the version used when starting the node. + #[arg( + long = "proofs-history.storage-version", + value_name = "PROOFS_HISTORY_STORAGE_VERSION", + default_value = "v1" + )] + pub storage_version: ProofsStorageVersion, } impl> InitCommand { @@ -44,51 +56,72 @@ impl> InitCommand { // Initialize the environment with read-only access let Environment { provider_factory, .. } = self.env.init::(AccessRights::RO)?; - // Create the proofs storage without the metrics wrapper. - // During initialization we write billions of entries; the metrics layer's - // `AtomicBucket::push` (used by `Histogram::record_many`) is append-only and - // would accumulate ~19 bytes per observation, causing OOM on large chains. - let storage: Arc = Arc::new( - MdbxProofsStorage::new(&self.storage_path) - .map_err(|e| eyre::eyre!("Failed to create MdbxProofsStorage: {e}"))?, - ); - - // Check if already initialized - if let Some((block_number, block_hash)) = storage.provider_ro()?.get_earliest_block_number()? { - info!( - target: "reth::cli", - block_number = block_number, - block_hash = ?block_hash, - "Proofs storage already initialized" - ); - return Ok(()); - } + macro_rules! run_init { + ($storage:expr) => {{ + let storage = $storage; - // Get the current chain state - let ChainInfo { best_number, best_hash, .. } = provider_factory.chain_info()?; + // Check if already initialized + if let Some((block_number, block_hash)) = + storage.provider_ro()?.get_earliest_block_number()? + { + info!( + target: "reth::cli", + block_number = block_number, + block_hash = ?block_hash, + "Proofs storage already initialized" + ); + return Ok(()); + } - info!( - target: "reth::cli", - best_number = best_number, - best_hash = ?best_hash, - "Starting backfill job for current chain state" - ); + // Get the current chain state + let ChainInfo { best_number, best_hash, .. } = provider_factory.chain_info()?; - // Run the backfill job - { - let db_provider = - provider_factory.database_provider_ro()?.disable_long_read_transaction_safety(); - let db_tx = db_provider.into_tx(); + info!( + target: "reth::cli", + best_number = best_number, + best_hash = ?best_hash, + "Starting backfill job for current chain state" + ); - InitializationJob::new(storage, db_tx).run(best_number, best_hash)?; + // Run the backfill job + { + let db_provider = provider_factory + .database_provider_ro()? + .disable_long_read_transaction_safety(); + let db_tx = db_provider.into_tx(); + + InitializationJob::new(storage, db_tx).run(best_number, best_hash)?; + } + + info!( + target: "reth::cli", + best_number = best_number, + best_hash = ?best_hash, + "Proofs storage initialized successfully" + ); + }}; } - info!( - target: "reth::cli", - best_number = best_number, - best_hash = ?best_hash, - "Proofs storage initialized successfully" - ); + // Create the proofs storage without the metrics wrapper. + // During initialization we write billions of entries; the metrics layer's + // `AtomicBucket::push` (used by `Histogram::record_many`) is append-only and + // would accumulate ~19 bytes per observation, causing OOM on large chains. + match self.storage_version { + ProofsStorageVersion::V1 => { + let storage: Arc = Arc::new( + MdbxProofsStorage::new(&self.storage_path) + .map_err(|e| eyre::eyre!("Failed to create MdbxProofsStorage: {e}"))?, + ); + run_init!(storage); + } + ProofsStorageVersion::V2 => { + let storage: Arc = Arc::new( + MdbxProofsStorageV2::new(&self.storage_path) + .map_err(|e| eyre::eyre!("Failed to create MdbxProofsStorageV2: {e}"))?, + ); + run_init!(storage); + } + } Ok(()) } diff --git a/rust/op-reth/crates/cli/src/commands/op_proofs/prune.rs b/rust/op-reth/crates/cli/src/commands/op_proofs/prune.rs index d324f0d157d..986a600ebfa 100644 --- a/rust/op-reth/crates/cli/src/commands/op_proofs/prune.rs +++ b/rust/op-reth/crates/cli/src/commands/op_proofs/prune.rs @@ -6,7 +6,11 @@ use reth_cli_commands::common::{AccessRights, CliNodeTypes, Environment, Environ use reth_node_core::version::version_metadata; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_primitives::OpPrimitives; -use reth_optimism_trie::{OpProofStoragePruner, OpProofsStore, OpProofsProviderRO, db::MdbxProofsStorage}; +use reth_optimism_node::args::ProofsStorageVersion; +use reth_optimism_trie::{ + OpProofStoragePruner, OpProofsProviderRO, OpProofsStore, + db::{MdbxProofsStorage, MdbxProofsStorageV2}, +}; use std::{path::PathBuf, sync::Arc}; use tracing::info; @@ -41,6 +45,14 @@ pub struct PruneCommand { value_name = "PROOFS_HISTORY_PRUNE_BATCH_SIZE" )] pub proofs_history_prune_batch_size: u64, + + /// Storage schema version. Must match the version used when starting the node. + #[arg( + long = "proofs-history.storage-version", + value_name = "PROOFS_HISTORY_STORAGE_VERSION", + default_value = "v1" + )] + pub storage_version: ProofsStorageVersion, } impl> PruneCommand { @@ -54,28 +66,48 @@ impl> PruneCommand { // Initialize the environment with read-only access let Environment { provider_factory, .. } = self.env.init::(AccessRights::RO)?; - let storage: Arc = Arc::new( - MdbxProofsStorage::new(&self.storage_path) - .map_err(|e| eyre::eyre!("Failed to create MdbxProofsStorage: {e}"))?, - ); + macro_rules! run_prune { + ($storage:expr) => {{ + let storage = $storage; + + let provider_ro = storage.provider_ro()?; + let earliest_block = provider_ro.get_earliest_block_number()?; + let latest_block = provider_ro.get_latest_block_number()?; + info!( + target: "reth::cli", + ?earliest_block, + ?latest_block, + "Current proofs storage block range" + ); + drop(provider_ro); + + let pruner = OpProofStoragePruner::new( + storage, + provider_factory, + self.proofs_history_window, + self.proofs_history_prune_batch_size, + ); + pruner.run(); + }}; + } - let provider_ro = storage.provider_ro()?; - let earliest_block = provider_ro.get_earliest_block_number()?; - let latest_block = provider_ro.get_latest_block_number()?; - info!( - target: "reth::cli", - ?earliest_block, - ?latest_block, - "Current proofs storage block range" - ); + match self.storage_version { + ProofsStorageVersion::V1 => { + let storage: Arc = Arc::new( + MdbxProofsStorage::new(&self.storage_path) + .map_err(|e| eyre::eyre!("Failed to create MdbxProofsStorage: {e}"))?, + ); + run_prune!(storage); + } + ProofsStorageVersion::V2 => { + let storage: Arc = Arc::new( + MdbxProofsStorageV2::new(&self.storage_path) + .map_err(|e| eyre::eyre!("Failed to create MdbxProofsStorageV2: {e}"))?, + ); + run_prune!(storage); + } + } - let pruner = OpProofStoragePruner::new( - storage, - provider_factory, - self.proofs_history_window, - self.proofs_history_prune_batch_size, - ); - pruner.run(); Ok(()) } } diff --git a/rust/op-reth/crates/cli/src/commands/op_proofs/unwind.rs b/rust/op-reth/crates/cli/src/commands/op_proofs/unwind.rs index 35d4d7ad63d..d0c117e8d27 100644 --- a/rust/op-reth/crates/cli/src/commands/op_proofs/unwind.rs +++ b/rust/op-reth/crates/cli/src/commands/op_proofs/unwind.rs @@ -6,7 +6,11 @@ use reth_cli_commands::common::{AccessRights, CliNodeTypes, Environment, Environ use reth_node_core::version::version_metadata; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_primitives::OpPrimitives; -use reth_optimism_trie::{OpProofsStore, OpProofsProviderRO, OpProofsProviderRw, db::MdbxProofsStorage}; +use reth_optimism_node::args::ProofsStorageVersion; +use reth_optimism_trie::{ + OpProofsProviderRO, OpProofsProviderRw, OpProofsStore, + db::{MdbxProofsStorage, MdbxProofsStorageV2}, +}; use reth_provider::{BlockReader, TransactionVariant}; use std::{path::PathBuf, sync::Arc}; use tracing::{info, warn}; @@ -32,6 +36,14 @@ pub struct UnwindCommand { /// All history *after* this block will be removed. #[arg(long, value_name = "TARGET_BLOCK")] pub target: u64, + + /// Storage schema version. Must match the version used when starting the node. + #[arg( + long = "proofs-history.storage-version", + value_name = "PROOFS_HISTORY_STORAGE_VERSION", + default_value = "v1" + )] + pub storage_version: ProofsStorageVersion, } impl UnwindCommand { @@ -70,28 +82,45 @@ impl> UnwindCommand { // Initialize the environment with read-only access let Environment { provider_factory, .. } = self.env.init::(AccessRights::RO)?; - // Create the proofs storage - let storage: Arc = Arc::new( - MdbxProofsStorage::new(&self.storage_path) - .map_err(|e| eyre::eyre!("Failed to create MdbxProofsStorage: {e}"))?, - ); - - // Validate that the target block is within a valid range for unwinding - if !self.validate_unwind_range(storage.clone())? { - return Ok(()); + macro_rules! run_unwind { + ($storage:expr) => {{ + let storage = $storage; + + // Validate that the target block is within a valid range for unwinding + if !self.validate_unwind_range(storage.clone())? { + return Ok(()); + } + + // Get the target block from the main database + let block = provider_factory + .recovered_block(self.target.into(), TransactionVariant::NoHash)? + .ok_or_else(|| { + eyre::eyre!("Target block {} not found in the main database", self.target) + })?; + + info!(target: "reth::cli", block_number = block.number, block_hash = %block.hash(), "Unwinding to target block"); + let provider_rw = storage.provider_rw()?; + provider_rw.unwind_history(block.block_with_parent())?; + provider_rw.commit()?; + }}; } - // Get the target block from the main database - let block = provider_factory - .recovered_block(self.target.into(), TransactionVariant::NoHash)? - .ok_or_else(|| { - eyre::eyre!("Target block {} not found in the main database", self.target) - })?; - - info!(target: "reth::cli", block_number = block.number, block_hash = %block.hash(), "Unwinding to target block"); - let provider_rw = storage.provider_rw()?; - provider_rw.unwind_history(block.block_with_parent())?; - provider_rw.commit()?; + match self.storage_version { + ProofsStorageVersion::V1 => { + let storage: Arc = Arc::new( + MdbxProofsStorage::new(&self.storage_path) + .map_err(|e| eyre::eyre!("Failed to create MdbxProofsStorage: {e}"))?, + ); + run_unwind!(storage); + } + ProofsStorageVersion::V2 => { + let storage: Arc = Arc::new( + MdbxProofsStorageV2::new(&self.storage_path) + .map_err(|e| eyre::eyre!("Failed to create MdbxProofsStorageV2: {e}"))?, + ); + run_unwind!(storage); + } + } Ok(()) } diff --git a/rust/op-reth/crates/node/src/args.rs b/rust/op-reth/crates/node/src/args.rs index d4d8748fa82..6b411dc29e7 100644 --- a/rust/op-reth/crates/node/src/args.rs +++ b/rust/op-reth/crates/node/src/args.rs @@ -7,6 +7,17 @@ use op_alloy_consensus::interop::SafetyLevel; use std::{path::PathBuf, time::Duration}; use url::Url; +/// Storage schema version for the proofs-history database. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, clap::ValueEnum)] +pub enum ProofsStorageVersion { + /// V1 storage schema (original single-table-per-domain layout). Default. + #[default] + V1, + /// V2 storage schema with changeset and history-bitmap tables, enabling + /// history-aware reads at any block number within the proof window. + V2, +} + /// Parameters for rollup configuration #[derive(Debug, Clone, PartialEq, Eq, clap::Args)] #[command(next_help_heading = "Rollup")] @@ -139,6 +150,19 @@ pub struct RollupArgs { default_value_t = 0 )] pub proofs_history_verification_interval: u64, + + /// Storage schema version for proofs history. Defaults to v1. + /// + /// Use `v2` to enable the changeset + history-bitmap schema, which supports + /// history-aware reads at any block number within the proof window. + /// + /// CLI: `--proofs-history.storage-version v2` + #[arg( + long = "proofs-history.storage-version", + value_name = "PROOFS_HISTORY_STORAGE_VERSION", + default_value = "v1" + )] + pub proofs_history_storage_version: ProofsStorageVersion, } impl Default for RollupArgs { @@ -161,6 +185,7 @@ impl Default for RollupArgs { proofs_history_window: 1_296_000, proofs_history_prune_interval: Duration::from_secs(15), proofs_history_verification_interval: 0, + proofs_history_storage_version: ProofsStorageVersion::V1, } } } diff --git a/rust/op-reth/crates/node/src/proof_history.rs b/rust/op-reth/crates/node/src/proof_history.rs index 21a0265f366..2f1461e9a46 100644 --- a/rust/op-reth/crates/node/src/proof_history.rs +++ b/rust/op-reth/crates/node/src/proof_history.rs @@ -1,6 +1,6 @@ //! Node luncher with proof history support. -use crate::{OpNode, args::RollupArgs}; +use crate::{OpNode, args::{ProofsStorageVersion, RollupArgs}}; use eyre::ErrReport; use futures_util::FutureExt; use reth_db::DatabaseEnv; @@ -12,7 +12,7 @@ use reth_optimism_rpc::{ debug::{DebugApiExt, DebugApiOverrideServer}, eth::proofs::{EthApiExt, EthApiOverrideServer}, }; -use reth_optimism_trie::{OpProofsStorage, db::MdbxProofsStorage}; +use reth_optimism_trie::{OpProofsStorage, db::{MdbxProofsStorage, MdbxProofsStorageV2}}; use reth_tasks::TaskExecutor; use std::{sync::Arc, time::Duration}; use tokio::time::sleep; @@ -30,6 +30,7 @@ pub async fn launch_node_with_proof_history( proofs_history_window, proofs_history_prune_interval, proofs_history_verification_interval, + proofs_history_storage_version, .. } = args; @@ -41,49 +42,74 @@ pub async fn launch_node_with_proof_history( .proofs_history_storage_path .clone() .expect("Path must be provided if not using in-memory storage"); - info!(target: "reth::cli", "Using on-disk storage for proofs history"); - let mdbx = Arc::new( - MdbxProofsStorage::new(&path) - .map_err(|e| eyre::eyre!("Failed to create MdbxProofsStorage: {e}"))?, - ); - let storage: OpProofsStorage> = mdbx.clone().into(); + // Installs the ExEx and RPC overrides for the given `mdbx` handle and `storage`. + // Defined as a macro because the builder chain must be monomorphized for each concrete + // storage type; a generic function cannot be used due to the type-state builder pattern. + macro_rules! install_proof_history { + ($mdbx:expr, $storage:expr) => {{ + let mdbx = $mdbx; + let storage = $storage; + let storage_exec = storage.clone(); - let storage_exec = storage.clone(); + node_builder = node_builder + .on_node_started(move |node| { + spawn_proofs_db_metrics( + node.task_executor, + mdbx, + node.config.metrics.push_gateway_interval, + ); + Ok(()) + }) + .install_exex("proofs-history", async move |exex_context| { + Ok(OpProofsExEx::builder(exex_context, storage_exec) + .with_proofs_history_window(proofs_history_window) + .with_proofs_history_prune_interval(proofs_history_prune_interval) + .with_verification_interval(proofs_history_verification_interval) + .build() + .run() + .boxed()) + }) + .extend_rpc_modules(move |ctx| { + info!(target: "reth::cli", "Installing proofs-history RPC overrides (eth_getProof, debug_executePayload)"); + let api_ext = + EthApiExt::new(ctx.registry.eth_api().clone(), storage.clone()); + let debug_ext = DebugApiExt::new( + ctx.node().provider().clone(), + ctx.registry.eth_api().clone(), + storage, + Box::new(ctx.node().task_executor().clone()), + ctx.node().evm_config().clone(), + ); + let eth_replaced = ctx.modules.replace_configured(api_ext.into_rpc())?; + let debug_replaced = + ctx.modules.replace_configured(debug_ext.into_rpc())?; + info!(target: "reth::cli", eth_replaced, debug_replaced, "Proofs-history RPC overrides installed"); + Ok(()) + }); + }}; + } - node_builder = node_builder - .on_node_started(move |node| { - spawn_proofs_db_metrics( - node.task_executor, - mdbx, - node.config.metrics.push_gateway_interval, + match proofs_history_storage_version { + ProofsStorageVersion::V1 => { + info!(target: "reth::cli", "Using on-disk storage for proofs history (v1)"); + let mdbx = Arc::new( + MdbxProofsStorage::new(&path) + .map_err(|e| eyre::eyre!("Failed to create MdbxProofsStorage: {e}"))?, ); - Ok(()) - }) - .install_exex("proofs-history", async move |exex_context| { - Ok(OpProofsExEx::builder(exex_context, storage_exec) - .with_proofs_history_window(proofs_history_window) - .with_proofs_history_prune_interval(proofs_history_prune_interval) - .with_verification_interval(proofs_history_verification_interval) - .build() - .run() - .boxed()) - }) - .extend_rpc_modules(move |ctx| { - info!(target: "reth::cli", "Installing proofs-history RPC overrides (eth_getProof, debug_executePayload)"); - let api_ext = EthApiExt::new(ctx.registry.eth_api().clone(), storage.clone()); - let debug_ext = DebugApiExt::new( - ctx.node().provider().clone(), - ctx.registry.eth_api().clone(), - storage, - Box::new(ctx.node().task_executor().clone()), - ctx.node().evm_config().clone(), + let storage: OpProofsStorage> = mdbx.clone().into(); + install_proof_history!(mdbx, storage); + } + ProofsStorageVersion::V2 => { + info!(target: "reth::cli", "Using on-disk storage for proofs history (v2)"); + let mdbx = Arc::new( + MdbxProofsStorageV2::new(&path) + .map_err(|e| eyre::eyre!("Failed to create MdbxProofsStorageV2: {e}"))?, ); - let eth_replaced = ctx.modules.replace_configured(api_ext.into_rpc())?; - let debug_replaced = ctx.modules.replace_configured(debug_ext.into_rpc())?; - info!(target: "reth::cli", eth_replaced, debug_replaced, "Proofs-history RPC overrides installed"); - Ok(()) - }); + let storage: OpProofsStorage> = mdbx.clone().into(); + install_proof_history!(mdbx, storage); + } + } } // In all cases (with or without proofs), launch the node. @@ -91,11 +117,13 @@ pub async fn launch_node_with_proof_history( handle.node_exit_future.await } /// Spawns a task that periodically reports metrics for the proofs DB. -fn spawn_proofs_db_metrics( +fn spawn_proofs_db_metrics( executor: TaskExecutor, - storage: Arc, + storage: Arc, metrics_report_interval: Duration, -) { +) where + S: DatabaseMetrics + Send + Sync + 'static, +{ executor.spawn_critical_task("op-proofs-storage-metrics", async move { info!( target: "reth::cli", diff --git a/rust/op-reth/crates/trie/Cargo.toml b/rust/op-reth/crates/trie/Cargo.toml index 9d768751027..402fe690d49 100644 --- a/rust/op-reth/crates/trie/Cargo.toml +++ b/rust/op-reth/crates/trie/Cargo.toml @@ -21,6 +21,7 @@ reth-provider.workspace = true reth-revm.workspace = true reth-trie = { workspace = true, features = ["serde"] } reth-trie-common = { workspace = true, features = ["serde"] } +reth-codecs.workspace = true reth-tasks.workspace = true # workaround: reth-trie/serde-bincode-compat activates serde-bincode-compat on diff --git a/rust/op-reth/crates/trie/src/db/cursor_v2.rs b/rust/op-reth/crates/trie/src/db/cursor_v2.rs new file mode 100644 index 00000000000..de88090072c --- /dev/null +++ b/rust/op-reth/crates/trie/src/db/cursor_v2.rs @@ -0,0 +1,3580 @@ +//! V2 cursor implementations for the v2 table schema. +//! +//! These cursors implement **history-aware reads** using the v2 3-table-per-data-type pattern: +//! +//! | Purpose | Accounts | Storages | Account Trie | Storage Trie | +//! |---------|----------|----------|-------------|-------------| +//! | Current state | [`HashedAccounts`] | [`HashedStorages`] | [`AccountsTrie`] | [`StoragesTrie`] | +//! | ChangeSets | [`HashedAccountChangeSets`] | [`HashedStorageChangeSets`] | [`AccountTrieChangeSets`] | [`StorageTrieChangeSets`] | +//! | History | [`HashedAccountsHistory`] | [`HashedStoragesHistory`] | [`AccountsTrieHistory`] | [`StoragesTrieHistory`] | +//! +//! # Historical Lookup Strategy +//! +//! Each cursor accepts a `max_block_number` parameter. For each key encountered: +//! +//! 1. **History bitmap lookup**: Seek `ShardedKey(key, max_block_number)` in the history table. +//! The bitmap tells us which blocks modified this key. +//! 2. **Find the first modification *after* `max_block_number`**: Using `rank` + `select` on +//! the bitmap. `rank(max_block_number)` counts entries ≤ the target block; +//! `select(rank)` returns the first entry strictly greater. +//! 3. **Determine where the value lives**: +//! - If a block `> max_block_number` modified this key → read the **changeset** at that +//! block. The changeset stores the value *before* that block's execution, which is +//! the value at the end of `max_block_number`. +//! - If no block after `max_block_number` modified this key → the **current state** table +//! already has the correct value. +//! + +use alloy_primitives::{B256, U256}; +use reth_db::{ + cursor::{DbCursorRO, DbDupCursorRO}, + models::sharded_key::ShardedKey, + table::Table, + BlockNumberList, DatabaseError, +}; +use reth_primitives_traits::Account; +use reth_trie::{ + hashed_cursor::{HashedCursor, HashedStorageCursor}, + trie_cursor::{TrieCursor, TrieStorageCursor}, + BranchNodeCompact, Nibbles, StorageTrieEntry, StoredNibbles, StoredNibblesSubKey, +}; + +use crate::db::{ + models::{ + AccountTrieShardedKey, AccountsTrie, AccountTrieChangeSets, AccountsTrieHistory, + BlockNumberHashedAddress, HashedAccountChangeSets, HashedAccountShardedKey, + HashedAccounts, HashedAccountsHistory, HashedStorageChangeSets, + HashedStorageShardedKey, HashedStorages, HashedStoragesHistory, StorageTrieShardedKey, + StoragesTrie, StorageTrieChangeSets, StoragesTrieHistory, + }, +}; + +/// Enum to define where to read the value for a given key at a specific block. +#[derive(Debug, Eq, PartialEq)] +enum ResolvedSource { + /// Read the "before" value from the changeset at this block. + /// The changeset stores the value *before* this block's execution, + /// which equals the value at the end of `max_block_number`. + FromChangeset(u64), + /// No modification after the target block → current state has the value. + FromCurrentState, +} + +/// Search history bitmaps to determine where to read the value for a key +/// at a given `max_block_number`. +/// +/// The algorithm: +/// 1. Seek the first history shard with `highest_block_number >= max_block_number`. +/// 2. Within that shard, find the first block strictly `> max_block_number`. +/// 3. If found → `FromChangeset(block)`. +/// 4. If the shard boundary was hit (all entries ≤ `max_block_number`), advance +/// to the next shard for the same key. If found → use its first entry. +/// 5. Otherwise → `FromCurrentState`. +fn find_source( + cursor: &mut C, + seek_key: T::Key, + max_block_number: u64, + key_filter: impl Fn(&T::Key) -> bool, +) -> Result +where + T: Table, + C: DbCursorRO, +{ + // 1. Seek the first shard with highest_block_number >= max_block_number. + let shard = cursor.seek(seek_key)?.filter(|(k, _)| key_filter(k)); + + let Some((_, chunk)) = shard else { + // No history shard found for this key (or all shards have + // highest < max_block_number). Current state is authoritative. + return Ok(ResolvedSource::FromCurrentState); + }; + + // 2. rank(n) = count of entries ≤ n. select(rank) = first entry > n. + let rank = chunk.rank(max_block_number); + if let Some(block) = chunk.select(rank) { + return Ok(ResolvedSource::FromChangeset(block)); + } + + // 3. All entries in this shard are ≤ max_block_number (shard boundary hit). + // The next shard (if it exists for the same key) starts after this one. + if let Some((_, next_chunk)) = cursor.next()?.filter(|(k, _)| key_filter(k)) { + if let Some(block) = next_chunk.select(0) { + return Ok(ResolvedSource::FromChangeset(block)); + } + } + + Ok(ResolvedSource::FromCurrentState) +} + +/// History-aware cursor over the [`HashedAccounts`] v2 tables. +/// +/// Uses a **dual-cursor merge** to discover all account keys that existed at +/// `max_block_number`. This is necessary because an account deleted *after* +/// the target block no longer exists in the current-state table and would be +/// missed by a walk of current state alone. The merge walks both the +/// current-state cursor and the history-bitmap cursor in sorted order, +/// yielding the minimum key from each, resolving its value at the target +/// block, and skipping keys that did not exist at that block. +#[derive(Debug)] +pub struct V2AccountCursor { + /// Current state walk cursor. + cursor: C, + /// History bitmap cursor for resolving individual keys. + history_cursor: HC, + /// History bitmap cursor for merge-walking deleted keys. + history_walk_cursor: HC, + /// Changeset cursor. + changeset_cursor: CC, + /// Target block number for historical reads. + max_block_number: u64, + /// Pre-fetched next entry from the current state walk. + cs_next: Option<(B256, Account)>, + /// Pre-fetched next unique key from the history walk. + hist_next_key: Option, + /// Whether `seek` has been called to initialize the merge cursors. + seeked: bool, + /// Fast path: when `true`, skip all history/changeset lookups and + /// read directly from the current-state table. + is_latest: bool, +} + +impl V2AccountCursor { + /// Create a new [`V2AccountCursor`]. + pub const fn new( + cursor: C, + history_cursor: HC, + history_walk_cursor: HC, + changeset_cursor: CC, + max_block_number: u64, + is_latest: bool, + ) -> Self { + Self { + cursor, + history_cursor, + history_walk_cursor, + changeset_cursor, + max_block_number, + cs_next: None, + hist_next_key: None, + seeked: false, + is_latest, + } + } +} + +impl V2AccountCursor +where + C: DbCursorRO, + HC: DbCursorRO, + CC: DbCursorRO + DbDupCursorRO, +{ + /// Resolve an account using a pre-fetched current-state value. + /// + /// Does **not** touch the walk cursor, so it is safe to call from the + /// merge loop (`find_next_live`). + fn resolve_account_merge( + &mut self, + hashed_address: B256, + cs_value: Option<&Account>, + ) -> Result, DatabaseError> { + let history_key = HashedAccountShardedKey::new(hashed_address, self.max_block_number); + let source = find_source::( + &mut self.history_cursor, + history_key, + self.max_block_number, + |k| k.0.key == hashed_address, + )?; + + match source { + ResolvedSource::FromChangeset(changeset_block) => { + let entry = self + .changeset_cursor + .seek_by_key_subkey(changeset_block, hashed_address)? + .filter(|e| e.hashed_address == hashed_address); + Ok(entry.and_then(|e| e.info)) + } + ResolvedSource::FromCurrentState => Ok(cs_value.cloned()), + } + } + + /// Advance the history walk cursor past all shards of `key` and return + /// the next distinct key, if any. + fn advance_history_past( + &mut self, + key: &B256, + ) -> Result, DatabaseError> { + let entry = + self.history_walk_cursor.seek(HashedAccountShardedKey::new(*key, u64::MAX))?; + match entry { + Some((k, _)) if k.0.key == *key => { + // On the last shard of this key — one more step. + Ok(self.history_walk_cursor.next()?.map(|(k, _)| k.0.key)) + } + Some((k, _)) => Ok(Some(k.0.key)), + None => Ok(None), + } + } + + /// Merge-walk both the current-state cursor and the history-bitmap cursor, + /// yielding the next key (in ascending order) whose account is live at + /// `max_block_number`. + fn find_next_live( + &mut self, + ) -> Result, DatabaseError> { + loop { + let (min_key, cs_value) = match (&self.cs_next, &self.hist_next_key) { + (Some((cs_k, cs_v)), Some(h_k)) => { + if cs_k <= h_k { + (*cs_k, Some(*cs_v)) + } else { + (*h_k, None) + } + } + (Some((cs_k, cs_v)), None) => (*cs_k, Some(*cs_v)), + (None, Some(h_k)) => (*h_k, None), + (None, None) => return Ok(None), + }; + + // Advance whichever cursor(s) produced this key. + if self.cs_next.as_ref().is_some_and(|(k, _)| *k == min_key) { + self.cs_next = self.cursor.next()?; + } + if self.hist_next_key.as_ref().is_some_and(|k| *k == min_key) { + self.hist_next_key = self.advance_history_past(&min_key)?; + } + + // Resolve the value at max_block_number. + if let Some(account) = self.resolve_account_merge(min_key, cs_value.as_ref())? { + return Ok(Some((min_key, account))); + } + // Key doesn't exist at max_block_number — continue to next. + } + } +} + +impl HashedCursor for V2AccountCursor +where + C: DbCursorRO + Send, + HC: DbCursorRO + Send, + CC: DbCursorRO + DbDupCursorRO + Send, +{ + type Value = Account; + + fn seek(&mut self, key: B256) -> Result, DatabaseError> { + self.seeked = true; + + if self.is_latest { + // Fast path: current state is authoritative, no history needed. + return self.cursor.seek(key); + } + + // Initialize both merge cursors at the target key. + self.cs_next = self.cursor.seek(key)?; + self.hist_next_key = self + .history_walk_cursor + .seek(HashedAccountShardedKey::new(key, 0))? + .map(|(k, _)| k.0.key); + self.find_next_live() + } + + fn next(&mut self) -> Result, DatabaseError> { + if !self.seeked { + return self.seek(B256::ZERO); + } + + if self.is_latest { + return self.cursor.next(); + } + + self.find_next_live() + } + + fn reset(&mut self) { + self.cs_next = None; + self.hist_next_key = None; + self.seeked = false; + } +} + +/// History-aware cursor over the [`HashedStorages`] v2 DupSort table. +/// +/// Uses the same dual-cursor merge strategy as [`V2AccountCursor`] but +/// scoped to a single `hashed_address`. Both the current-state DupSort +/// entries and the history-bitmap entries are walked in parallel to discover +/// storage slots that may have been deleted after `max_block_number`. +#[derive(Debug)] +pub struct V2StorageCursor { + /// Current state cursor (DupSort). + cursor: C, + /// History bitmap cursor for resolving individual keys. + history_cursor: HC, + /// History bitmap cursor for merge-walking deleted keys. + history_walk_cursor: HC, + /// Changeset cursor (DupSort). + changeset_cursor: CC, + /// Target hashed address. + hashed_address: B256, + /// Target block number for historical reads. + max_block_number: u64, + /// Pre-fetched next entry from the current state walk (within address). + cs_next: Option, + /// Pre-fetched next unique storage key from the history walk. + hist_next_key: Option, + /// Whether `seek` has been called to initialize the merge cursors. + seeked: bool, + /// Fast path: when `true`, skip all history/changeset lookups. + is_latest: bool, +} + +impl V2StorageCursor { + /// Create a new [`V2StorageCursor`]. + pub const fn new( + cursor: C, + history_cursor: HC, + history_walk_cursor: HC, + changeset_cursor: CC, + hashed_address: B256, + max_block_number: u64, + is_latest: bool, + ) -> Self { + Self { + cursor, + history_cursor, + history_walk_cursor, + changeset_cursor, + hashed_address, + max_block_number, + cs_next: None, + hist_next_key: None, + seeked: false, + is_latest, + } + } +} + +impl V2StorageCursor +where + C: DbCursorRO + DbDupCursorRO, + HC: DbCursorRO, + CC: DbCursorRO + DbDupCursorRO, +{ + /// Resolve a storage slot using a pre-fetched current-state value. + /// + /// Does **not** touch the walk cursor, so it is safe to call from the + /// merge loop (`find_next_live`). + fn resolve_storage_merge( + &mut self, + storage_key: B256, + cs_value: Option<&U256>, + ) -> Result, DatabaseError> { + let history_key = HashedStorageShardedKey { + hashed_address: self.hashed_address, + sharded_key: ShardedKey::new(storage_key, self.max_block_number), + }; + + let addr = self.hashed_address; + let source = find_source::( + &mut self.history_cursor, + history_key, + self.max_block_number, + |k| k.hashed_address == addr && k.sharded_key.key == storage_key, + )?; + + match source { + ResolvedSource::FromChangeset(changeset_block) => { + let cs_key = BlockNumberHashedAddress((changeset_block, self.hashed_address)); + let entry = self + .changeset_cursor + .seek_by_key_subkey(cs_key, storage_key)? + .filter(|e| e.key == storage_key); + match entry { + Some(e) if e.value.is_zero() => Ok(None), + Some(e) => Ok(Some(e.value)), + None => Ok(None), + } + } + ResolvedSource::FromCurrentState => { + Ok(cs_value.copied().filter(|v| !v.is_zero())) + } + } + } + + /// Advance the history walk cursor past all shards of `key` (for this + /// address) and return the next distinct storage key, if any. + fn advance_history_past( + &mut self, + key: &B256, + ) -> Result, DatabaseError> { + let seek = HashedStorageShardedKey { + hashed_address: self.hashed_address, + sharded_key: ShardedKey::new(*key, u64::MAX), + }; + let entry = self + .history_walk_cursor + .seek(seek)? + .filter(|(k, _)| k.hashed_address == self.hashed_address); + match entry { + Some((k, _)) if k.sharded_key.key == *key => { + // On the last shard of this key — advance once more. + Ok(self + .history_walk_cursor + .next()? + .filter(|(k, _)| k.hashed_address == self.hashed_address) + .map(|(k, _)| k.sharded_key.key)) + } + Some((k, _)) => Ok(Some(k.sharded_key.key)), + None => Ok(None), + } + } + + /// Merge-walk both the current-state DupSort cursor and the history-bitmap + /// cursor, yielding the next storage slot whose value is live at + /// `max_block_number`. + fn find_next_live( + &mut self, + ) -> Result, DatabaseError> { + loop { + let (min_key, cs_value) = match (&self.cs_next, &self.hist_next_key) { + (Some(cs_entry), Some(h_k)) => { + if cs_entry.key <= *h_k { + (cs_entry.key, Some(cs_entry.value)) + } else { + (*h_k, None) + } + } + (Some(cs_entry), None) => (cs_entry.key, Some(cs_entry.value)), + (None, Some(h_k)) => (*h_k, None), + (None, None) => return Ok(None), + }; + + // Advance whichever cursor(s) produced this key. + if self.cs_next.as_ref().is_some_and(|e| e.key == min_key) { + self.cs_next = self.cursor.next_dup_val()?; + } + if self.hist_next_key.as_ref().is_some_and(|k| *k == min_key) { + self.hist_next_key = self.advance_history_past(&min_key)?; + } + + // Resolve the value at max_block_number. + if let Some(value) = self.resolve_storage_merge(min_key, cs_value.as_ref())? { + return Ok(Some((min_key, value))); + } + // Key doesn't exist at max_block_number — continue to next. + } + } +} + +impl HashedCursor for V2StorageCursor +where + C: DbCursorRO + DbDupCursorRO + Send, + HC: DbCursorRO + Send, + CC: DbCursorRO + + DbDupCursorRO + + Send, +{ + type Value = U256; + + fn seek(&mut self, subkey: B256) -> Result, DatabaseError> { + self.seeked = true; + + if self.is_latest { + // Fast path: current state is authoritative. + // Loop to skip zero-valued entries (tombstones). + let mut entry = + self.cursor.seek_by_key_subkey(self.hashed_address, subkey)?; + while let Some(ref e) = entry { + if !e.value.is_zero() { + return Ok(Some((e.key, e.value))); + } + entry = self.cursor.next_dup_val()?; + } + return Ok(None); + } + + // Initialize both merge cursors at the target key. + self.cs_next = self.cursor.seek_by_key_subkey(self.hashed_address, subkey)?; + let hist_seek = HashedStorageShardedKey { + hashed_address: self.hashed_address, + sharded_key: ShardedKey::new(subkey, 0), + }; + self.hist_next_key = self + .history_walk_cursor + .seek(hist_seek)? + .filter(|(k, _)| k.hashed_address == self.hashed_address) + .map(|(k, _)| k.sharded_key.key); + self.find_next_live() + } + + fn next(&mut self) -> Result, DatabaseError> { + if !self.seeked { + return self.seek(B256::ZERO); + } + + if self.is_latest { + // Loop to skip zero-valued entries (tombstones). + while let Some(e) = self.cursor.next_dup_val()? { + if !e.value.is_zero() { + return Ok(Some((e.key, e.value))); + } + } + return Ok(None); + } + + self.find_next_live() + } + + fn reset(&mut self) { + self.cs_next = None; + self.hist_next_key = None; + self.seeked = false; + } +} + +impl HashedStorageCursor for V2StorageCursor +where + C: DbCursorRO + DbDupCursorRO + Send, + HC: DbCursorRO + Send, + CC: DbCursorRO + + DbDupCursorRO + + Send, +{ + fn is_storage_empty(&mut self) -> Result { + Ok(self.seek(B256::ZERO)?.is_none()) + } + + fn set_hashed_address(&mut self, hashed_address: B256) { + self.hashed_address = hashed_address; + self.cs_next = None; + self.hist_next_key = None; + self.seeked = false; + } +} + +/// History-aware cursor over the [`AccountsTrie`] v2 tables. +/// +/// Uses a **dual-cursor merge** to discover all trie paths that existed at +/// `max_block_number`. This is necessary because a key deleted *after* the +/// target block no longer exists in the current-state table and would be +/// missed by a walk of current state alone. The merge walks both the +/// current-state cursor and the history-bitmap cursor in sorted order, +/// yielding the minimum key from each, resolving its value at the target +/// block, and skipping keys that did not exist at that block. +#[derive(Debug)] +pub struct V2AccountTrieCursor { + /// Current state walk cursor. + cursor: C, + /// History bitmap cursor for resolving individual keys. + history_cursor: HC, + /// History bitmap cursor for merge-walking deleted keys. + history_walk_cursor: HC, + /// Changeset cursor. + changeset_cursor: CC, + /// Target block number. + max_block_number: u64, + /// Pre-fetched next entry from the current state walk. + cs_next: Option<(StoredNibbles, BranchNodeCompact)>, + /// Pre-fetched next unique key from the history walk. + hist_next_key: Option, + /// Last key yielded by `seek`/`next` (for `current()`). + last_key: Option, + /// Whether `seek` or `seek_exact` has been called to initialize the merge cursors. + seeked: bool, + /// Fast path: when `true`, skip all history/changeset lookups. + is_latest: bool, +} + +impl V2AccountTrieCursor { + /// Create a new [`V2AccountTrieCursor`]. + pub const fn new( + cursor: C, + history_cursor: HC, + history_walk_cursor: HC, + changeset_cursor: CC, + max_block_number: u64, + is_latest: bool, + ) -> Self { + Self { + cursor, + history_cursor, + history_walk_cursor, + changeset_cursor, + max_block_number, + cs_next: None, + hist_next_key: None, + last_key: None, + seeked: false, + is_latest, + } + } +} + +impl V2AccountTrieCursor +where + C: DbCursorRO, + HC: DbCursorRO, + CC: DbCursorRO + DbDupCursorRO, +{ + /// Resolve a key using the walk cursor for the `FromCurrentState` case. + /// + /// May disrupt the walk cursor position — only call when the walk state + /// will be re-synced immediately afterward (e.g. in `seek_exact`). + fn resolve_node_standalone( + &mut self, + path: &StoredNibbles, + ) -> Result, DatabaseError> { + let seek_key = AccountTrieShardedKey::new(path.clone(), self.max_block_number); + let target = path.clone(); + let source = find_source::( + &mut self.history_cursor, + seek_key, + self.max_block_number, + |k| k.key == target, + )?; + + match source { + ResolvedSource::FromChangeset(changeset_block) => { + let entry = self + .changeset_cursor + .seek_by_key_subkey(changeset_block, StoredNibblesSubKey(path.0))? + .filter(|e| e.nibbles == StoredNibblesSubKey(path.0)); + Ok(entry.and_then(|e| e.node)) + } + ResolvedSource::FromCurrentState => { + Ok(self.cursor.seek_exact(path.clone())?.map(|(_, node)| node)) + } + } + } + + /// Resolve a key using a pre-fetched current-state value. + /// + /// Does **not** touch the walk cursor, so it is safe to call from the + /// merge loop (`find_next_live`). + fn resolve_node_merge( + &mut self, + path: &StoredNibbles, + cs_value: Option<&BranchNodeCompact>, + ) -> Result, DatabaseError> { + let seek_key = AccountTrieShardedKey::new(path.clone(), self.max_block_number); + let target = path.clone(); + let source = find_source::( + &mut self.history_cursor, + seek_key, + self.max_block_number, + |k| k.key == target, + )?; + + match source { + ResolvedSource::FromChangeset(changeset_block) => { + let entry = self + .changeset_cursor + .seek_by_key_subkey(changeset_block, StoredNibblesSubKey(path.0))? + .filter(|e| e.nibbles == StoredNibblesSubKey(path.0)); + Ok(entry.and_then(|e| e.node)) + } + ResolvedSource::FromCurrentState => Ok(cs_value.cloned()), + } + } + + /// Advance the history walk cursor past all shards of `key` and return + /// the next distinct key, if any. + fn advance_history_past( + &mut self, + key: &StoredNibbles, + ) -> Result, DatabaseError> { + // Jump to the last shard of this key (or past it entirely). + let entry = + self.history_walk_cursor.seek(AccountTrieShardedKey::new(key.clone(), u64::MAX))?; + match entry { + Some((k, _)) if k.key == *key => { + // On the last shard of this key — one more step to reach the + // next distinct key. + Ok(self.history_walk_cursor.next()?.map(|(k, _)| k.key)) + } + Some((k, _)) => Ok(Some(k.key)), + None => Ok(None), + } + } + + /// Merge-walk both the current-state cursor and the history-bitmap cursor, + /// yielding the next key (in ascending order) whose value is live at + /// `max_block_number`. + fn find_next_live( + &mut self, + ) -> Result, DatabaseError> { + loop { + // Pick the minimum key from the two sources. + let (min_key, cs_value) = match (&self.cs_next, &self.hist_next_key) { + (Some((cs_k, cs_v)), Some(h_k)) => { + if cs_k <= h_k { + (cs_k.clone(), Some(cs_v.clone())) + } else { + (h_k.clone(), None) + } + } + (Some((cs_k, cs_v)), None) => (cs_k.clone(), Some(cs_v.clone())), + (None, Some(h_k)) => (h_k.clone(), None), + (None, None) => return Ok(None), + }; + + // Advance whichever cursor(s) produced this key. + if self.cs_next.as_ref().is_some_and(|(k, _)| *k == min_key) { + self.cs_next = self.cursor.next()?; + } + if self.hist_next_key.as_ref().is_some_and(|k| *k == min_key) { + self.hist_next_key = self.advance_history_past(&min_key)?; + } + + // Resolve the value at max_block_number. + if let Some(node) = self.resolve_node_merge(&min_key, cs_value.as_ref())? { + self.last_key = Some(min_key.clone()); + return Ok(Some((min_key.0, node))); + } + // Key doesn't exist at max_block_number — continue to next. + } + } +} + +impl TrieCursor for V2AccountTrieCursor +where + C: DbCursorRO + Send, + HC: DbCursorRO + Send, + CC: DbCursorRO + + DbDupCursorRO + + Send, +{ + fn seek_exact( + &mut self, + key: Nibbles, + ) -> Result, DatabaseError> { + self.seeked = true; + + if self.is_latest { + // Fast path: direct current-state lookup. + let result = self.cursor.seek_exact(StoredNibbles(key))?; + if result.is_some() { + self.last_key = Some(StoredNibbles(key)); + } + return Ok(result.map(|(_, node)| (key, node))); + } + + let path = StoredNibbles(key); + let node = self.resolve_node_standalone(&path)?; + + // Re-sync the walk state so a subsequent next() starts after `path`. + let cs_at_key = self.cursor.seek(path.clone())?; + self.cs_next = match cs_at_key { + Some((k, _)) if k == path => self.cursor.next()?, + other => other, + }; + self.hist_next_key = self.advance_history_past(&path)?; + + if node.is_some() { + self.last_key = Some(path); + } + Ok(node.map(|n| (key, n))) + } + + fn seek( + &mut self, + key: Nibbles, + ) -> Result, DatabaseError> { + self.seeked = true; + + if self.is_latest { + // Fast path: direct current-state walk. + let result = self.cursor.seek(StoredNibbles(key))?; + if let Some((ref k, _)) = result { + self.last_key = Some(k.clone()); + } + return Ok(result.map(|(k, node)| (k.0, node))); + } + + // Initialize both merge cursors at the target key. + self.cs_next = self.cursor.seek(StoredNibbles(key))?; + self.hist_next_key = self + .history_walk_cursor + .seek(AccountTrieShardedKey::new(StoredNibbles(key), 0))? + .map(|(k, _)| k.key); + self.find_next_live() + } + + fn next(&mut self) -> Result, DatabaseError> { + if !self.seeked { + return self.seek(Nibbles::default()); + } + + if self.is_latest { + let result = self.cursor.next()?; + if let Some((ref k, _)) = result { + self.last_key = Some(k.clone()); + } + return Ok(result.map(|(k, node)| (k.0, node))); + } + + self.find_next_live() + } + + fn current(&mut self) -> Result, DatabaseError> { + Ok(self.last_key.as_ref().map(|k| k.0)) + } + + fn reset(&mut self) {} +} + +/// History-aware cursor over the [`StoragesTrie`] v2 DupSort table. +/// +/// Uses the same dual-cursor merge strategy as [`V2AccountTrieCursor`] but +/// scoped to a single `hashed_address`. Both the current-state DupSort +/// entries and the history-bitmap entries are walked in parallel to discover +/// keys that may have been deleted after `max_block_number`. +#[derive(Debug)] +pub struct V2StorageTrieCursor { + /// Current state cursor (DupSort). + cursor: C, + /// History bitmap cursor for resolving individual keys. + history_cursor: HC, + /// History bitmap cursor for merge-walking deleted keys. + history_walk_cursor: HC, + /// Changeset cursor (DupSort). + changeset_cursor: CC, + /// Target hashed address. + hashed_address: B256, + /// Target block number. + max_block_number: u64, + /// Pre-fetched next entry from the current state walk (within address). + cs_next: Option, + /// Pre-fetched next unique nibbles key from the history walk. + hist_next_key: Option, + /// Last key yielded by `seek`/`next` (for `current()`). + last_key: Option, + /// Whether `seek` or `seek_exact` has been called to initialize the merge cursors. + seeked: bool, + /// Fast path: when `true`, skip all history/changeset lookups. + is_latest: bool, +} + +impl V2StorageTrieCursor { + /// Create a new [`V2StorageTrieCursor`]. + pub const fn new( + cursor: C, + history_cursor: HC, + history_walk_cursor: HC, + changeset_cursor: CC, + hashed_address: B256, + max_block_number: u64, + is_latest: bool, + ) -> Self { + Self { + cursor, + history_cursor, + history_walk_cursor, + changeset_cursor, + hashed_address, + max_block_number, + cs_next: None, + hist_next_key: None, + last_key: None, + seeked: false, + is_latest, + } + } +} + +impl V2StorageTrieCursor +where + C: DbCursorRO + DbDupCursorRO, + HC: DbCursorRO, + CC: DbCursorRO + DbDupCursorRO, +{ + /// Resolve a key using the walk cursor for the `FromCurrentState` case. + /// + /// May disrupt the walk cursor position — only call from `seek_exact`. + fn resolve_node_standalone( + &mut self, + path: Nibbles, + ) -> Result, DatabaseError> { + let nibbles = StoredNibbles(path); + let seek_key = StorageTrieShardedKey::new( + self.hashed_address, + nibbles.clone(), + self.max_block_number, + ); + + let addr = self.hashed_address; + let nibbles_cmp = nibbles.clone(); + let source = find_source::( + &mut self.history_cursor, + seek_key, + self.max_block_number, + |k| k.hashed_address == addr && k.key == nibbles_cmp, + )?; + + match source { + ResolvedSource::FromChangeset(changeset_block) => { + let cs_key = BlockNumberHashedAddress((changeset_block, self.hashed_address)); + let entry = self + .changeset_cursor + .seek_by_key_subkey(cs_key, StoredNibblesSubKey(path))? + .filter(|e| e.nibbles == StoredNibblesSubKey(path)); + Ok(entry.and_then(|e| e.node)) + } + ResolvedSource::FromCurrentState => Ok(self + .cursor + .seek_by_key_subkey(self.hashed_address, StoredNibblesSubKey(path))? + .filter(|e| e.nibbles == StoredNibblesSubKey(path)) + .map(|e| e.node)), + } + } + + /// Resolve a key using a pre-fetched current-state value. + /// + /// Does **not** touch the walk cursor. + fn resolve_node_merge( + &mut self, + path: Nibbles, + cs_value: Option<&BranchNodeCompact>, + ) -> Result, DatabaseError> { + let nibbles = StoredNibbles(path); + let seek_key = StorageTrieShardedKey::new( + self.hashed_address, + nibbles.clone(), + self.max_block_number, + ); + + let addr = self.hashed_address; + let nibbles_cmp = nibbles.clone(); + let source = find_source::( + &mut self.history_cursor, + seek_key, + self.max_block_number, + |k| k.hashed_address == addr && k.key == nibbles_cmp, + )?; + + match source { + ResolvedSource::FromChangeset(changeset_block) => { + let cs_key = BlockNumberHashedAddress((changeset_block, self.hashed_address)); + let entry = self + .changeset_cursor + .seek_by_key_subkey(cs_key, StoredNibblesSubKey(path))? + .filter(|e| e.nibbles == StoredNibblesSubKey(path)); + Ok(entry.and_then(|e| e.node)) + } + ResolvedSource::FromCurrentState => Ok(cs_value.cloned()), + } + } + + /// Advance the history walk cursor past all shards of `key` (for this + /// address) and return the next distinct nibbles key, if any. + fn advance_history_past( + &mut self, + key: &StoredNibbles, + ) -> Result, DatabaseError> { + let seek = StorageTrieShardedKey::new( + self.hashed_address, + key.clone(), + u64::MAX, + ); + let entry = self + .history_walk_cursor + .seek(seek)? + .filter(|(k, _)| k.hashed_address == self.hashed_address); + match entry { + Some((k, _)) if k.key == *key => { + // On the last shard of this key — advance once more. + Ok(self + .history_walk_cursor + .next()? + .filter(|(k, _)| k.hashed_address == self.hashed_address) + .map(|(k, _)| k.key)) + } + Some((k, _)) => Ok(Some(k.key)), + None => Ok(None), + } + } + + /// Merge-walk both the current-state DupSort cursor and the history-bitmap + /// cursor, yielding the next path whose node is live at `max_block_number`. + fn find_next_live( + &mut self, + ) -> Result, DatabaseError> { + loop { + let (min_nibbles, cs_node) = match (&self.cs_next, &self.hist_next_key) { + (Some(cs_entry), Some(h_k)) => { + let cs_stored = StoredNibbles(cs_entry.nibbles.0); + if cs_stored <= *h_k { + (cs_stored, Some(cs_entry.node.clone())) + } else { + (h_k.clone(), None) + } + } + (Some(cs_entry), None) => { + (StoredNibbles(cs_entry.nibbles.0), Some(cs_entry.node.clone())) + } + (None, Some(h_k)) => (h_k.clone(), None), + (None, None) => return Ok(None), + }; + + // Advance whichever cursor(s) produced this key. + if self + .cs_next + .as_ref() + .is_some_and(|e| StoredNibbles(e.nibbles.0) == min_nibbles) + { + self.cs_next = self.cursor.next_dup()?.map(|(_, v)| v); + } + if self.hist_next_key.as_ref().is_some_and(|k| *k == min_nibbles) { + self.hist_next_key = self.advance_history_past(&min_nibbles)?; + } + + // Resolve the value at max_block_number. + if let Some(node) = + self.resolve_node_merge(min_nibbles.0, cs_node.as_ref())? + { + self.last_key = Some(StoredNibbles(min_nibbles.0)); + return Ok(Some((min_nibbles.0, node))); + } + } + } +} + +impl TrieCursor for V2StorageTrieCursor +where + C: DbCursorRO + DbDupCursorRO + Send, + HC: DbCursorRO + Send, + CC: DbCursorRO + + DbDupCursorRO + + Send, +{ + fn seek_exact( + &mut self, + key: Nibbles, + ) -> Result, DatabaseError> { + self.seeked = true; + + if self.is_latest { + // Fast path: direct DupSort lookup. + let entry = self + .cursor + .seek_by_key_subkey(self.hashed_address, StoredNibblesSubKey(key))? + .filter(|e| e.nibbles == StoredNibblesSubKey(key)); + if entry.is_some() { + self.last_key = Some(StoredNibbles(key)); + } + return Ok(entry.map(|e| (key, e.node))); + } + + let node = self.resolve_node_standalone(key)?; + + // Re-sync walk state so a subsequent next() starts after `key`. + let cs_at_key = + self.cursor.seek_by_key_subkey(self.hashed_address, StoredNibblesSubKey(key))?; + self.cs_next = match cs_at_key { + Some(e) if e.nibbles == StoredNibblesSubKey(key) => { + self.cursor.next_dup()?.map(|(_, v)| v) + } + other => other, + }; + let path = StoredNibbles(key); + self.hist_next_key = self.advance_history_past(&path)?; + + if node.is_some() { + self.last_key = Some(path); + } + Ok(node.map(|n| (key, n))) + } + + fn seek( + &mut self, + key: Nibbles, + ) -> Result, DatabaseError> { + self.seeked = true; + + if self.is_latest { + // Fast path: direct DupSort walk. + let entry = + self.cursor.seek_by_key_subkey(self.hashed_address, StoredNibblesSubKey(key))?; + if let Some(ref e) = entry { + self.last_key = Some(StoredNibbles(e.nibbles.0)); + } + return Ok(entry.map(|e| (e.nibbles.0, e.node))); + } + + // Initialize both merge cursors at the target key. + self.cs_next = + self.cursor.seek_by_key_subkey(self.hashed_address, StoredNibblesSubKey(key))?; + let hist_seek = StorageTrieShardedKey::new( + self.hashed_address, + StoredNibbles(key), + 0, + ); + self.hist_next_key = self + .history_walk_cursor + .seek(hist_seek)? + .filter(|(k, _)| k.hashed_address == self.hashed_address) + .map(|(k, _)| k.key); + self.find_next_live() + } + + fn next(&mut self) -> Result, DatabaseError> { + if !self.seeked { + return self.seek(Nibbles::default()); + } + + if self.is_latest { + let entry = self.cursor.next_dup()?.map(|(_, v)| v); + if let Some(ref e) = entry { + self.last_key = Some(StoredNibbles(e.nibbles.0)); + } + return Ok(entry.map(|e| (e.nibbles.0, e.node))); + } + + self.find_next_live() + } + + fn current(&mut self) -> Result, DatabaseError> { + Ok(self.last_key.as_ref().map(|k| k.0)) + } + + fn reset(&mut self) {} +} + +impl TrieStorageCursor for V2StorageTrieCursor +where + C: DbCursorRO + DbDupCursorRO + Send, + HC: DbCursorRO + Send, + CC: DbCursorRO + + DbDupCursorRO + + Send, +{ + fn set_hashed_address(&mut self, hashed_address: B256) { + self.hashed_address = hashed_address; + self.cs_next = None; + self.hist_next_key = None; + self.last_key = None; + self.seeked = false; + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::db::models; + use crate::db::models::{HashedAccountBeforeTx, TrieChangeSetsEntry}; + use reth_db::{ + cursor::{DbCursorRW, DbDupCursorRW}, + mdbx::{init_db_for, DatabaseArguments}, + Database, DatabaseEnv, + }; + use reth_db_api::transaction::{DbTx, DbTxMut}; + use reth_primitives_traits::StorageEntry; + use reth_trie::{ + BranchNodeCompact, Nibbles, StoredNibbles, StoredNibblesSubKey, + }; + use tempfile::TempDir; + + fn setup_db() -> DatabaseEnv { + let tmp = TempDir::new().expect("create tmpdir"); + init_db_for::<_, models::Tables>(tmp, DatabaseArguments::default()).expect("init db") + } + + fn node() -> BranchNodeCompact { + BranchNodeCompact::new(0b11, 0, 0, vec![], Some(B256::repeat_byte(0xAB))) + } + + fn node2() -> BranchNodeCompact { + BranchNodeCompact::new(0b101, 0, 0, vec![], Some(B256::repeat_byte(0xCD))) + } + + fn sample_account(nonce: u64) -> Account { + Account { nonce, ..Default::default() } + } + + // ====================== find_source unit tests ====================== + + #[test] + fn find_source_returns_current_state_when_no_history() { + let db = setup_db(); + let addr = B256::from([0xAA; 32]); + + let tx = db.tx().expect("ro tx"); + let mut cursor = tx.cursor_read::().expect("c"); + + let result = find_source::( + &mut cursor, + HashedAccountShardedKey::new(addr, 10), + 10, + |k| k.0.key == addr, + ) + .expect("ok"); + + assert_eq!(result, ResolvedSource::FromCurrentState); + } + + #[test] + fn find_source_returns_changeset_when_modification_after_target() { + let db = setup_db(); + let addr = B256::from([0xBB; 32]); + + { + let wtx = db.tx_mut().expect("rw tx"); + wtx.cursor_write::() + .expect("c") + .upsert( + HashedAccountShardedKey::new(addr, u64::MAX), + &BlockNumberList::new_pre_sorted([5, 10, 15]), + ) + .expect("upsert"); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut cursor = tx.cursor_read::().expect("c"); + + // Target block 7 → first block > 7 in [5, 10, 15] is 10 + let result = find_source::( + &mut cursor, + HashedAccountShardedKey::new(addr, 7), + 7, + |k| k.0.key == addr, + ) + .expect("ok"); + + assert_eq!(result, ResolvedSource::FromChangeset(10)); + } + + #[test] + fn find_source_returns_current_state_when_no_modification_after_target() { + let db = setup_db(); + let addr = B256::from([0xCC; 32]); + + { + let wtx = db.tx_mut().expect("rw tx"); + wtx.cursor_write::() + .expect("c") + .upsert( + HashedAccountShardedKey::new(addr, u64::MAX), + &BlockNumberList::new_pre_sorted([3, 7]), + ) + .expect("upsert"); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut cursor = tx.cursor_read::().expect("c"); + + // Target block 10 → no block > 10 in [3, 7] + let result = find_source::( + &mut cursor, + HashedAccountShardedKey::new(addr, 10), + 10, + |k| k.0.key == addr, + ) + .expect("ok"); + + assert_eq!(result, ResolvedSource::FromCurrentState); + } + + #[test] + fn find_source_handles_exact_match_block() { + let db = setup_db(); + let addr = B256::from([0xDD; 32]); + + { + let wtx = db.tx_mut().expect("rw tx"); + wtx.cursor_write::() + .expect("c") + .upsert( + HashedAccountShardedKey::new(addr, u64::MAX), + &BlockNumberList::new_pre_sorted([5, 10, 15]), + ) + .expect("upsert"); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut cursor = tx.cursor_read::().expect("c"); + + // Target block 10 (exactly in the bitmap) → first block > 10 is 15 + let result = find_source::( + &mut cursor, + HashedAccountShardedKey::new(addr, 10), + 10, + |k| k.0.key == addr, + ) + .expect("ok"); + + assert_eq!(result, ResolvedSource::FromChangeset(15)); + } + + // ====================== find_source with AccountTrieShardedKey tests ====================== + + #[test] + fn find_source_resolves_root_path_despite_child_history() { + // Regression test: the root trie path [] has history at blocks [10, 15]. + // A child path [0] also has history. With the old `ShardedKey` + // encoding (no length prefix), `cursor.seek` would land on the wrong path. + // With `AccountTrieShardedKey`'s length-prefixed encoding, `find_source` works + // correctly: all shards of [] sort before all shards of [0]. + let db = setup_db(); + let root_path = StoredNibbles(Nibbles::default()); + let child_path = StoredNibbles(Nibbles::from_nibbles([0])); + + { + let wtx = db.tx_mut().expect("rw tx"); + let mut cursor = wtx.cursor_write::().expect("c"); + // Root path history: modified at blocks 10, 15 + cursor + .upsert( + AccountTrieShardedKey::new(root_path.clone(), u64::MAX), + &BlockNumberList::new_pre_sorted([10, 15]), + ) + .expect("upsert root"); + // Child path [0] history: modified at blocks 10, 15 + cursor + .upsert( + AccountTrieShardedKey::new(child_path.clone(), u64::MAX), + &BlockNumberList::new_pre_sorted([10, 15]), + ) + .expect("upsert child"); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut cursor = tx.cursor_read::().expect("c"); + + // Query at block 12 — should find changeset at block 15 + // (the first modification after block 12). + let result = find_source::( + &mut cursor, + AccountTrieShardedKey::new(root_path.clone(), 12), + 12, + |k| k.key == root_path, + ) + .expect("ok"); + + assert_eq!(result, ResolvedSource::FromChangeset(15)); + } + + #[test] + fn find_source_trie_returns_current_state_when_no_history() { + let db = setup_db(); + let root_path = StoredNibbles(Nibbles::default()); + + let tx = db.tx().expect("ro tx"); + let mut cursor = tx.cursor_read::().expect("c"); + + let result = find_source::( + &mut cursor, + AccountTrieShardedKey::new(root_path.clone(), 10), + 10, + |k| k.key == root_path, + ) + .expect("ok"); + + assert_eq!(result, ResolvedSource::FromCurrentState); + } + + #[test] + fn find_source_trie_returns_current_state_when_all_modifications_before_target() { + let db = setup_db(); + let root_path = StoredNibbles(Nibbles::default()); + + { + let wtx = db.tx_mut().expect("rw tx"); + wtx.cursor_write::() + .expect("c") + .upsert( + AccountTrieShardedKey::new(root_path.clone(), u64::MAX), + &BlockNumberList::new_pre_sorted([5, 8]), + ) + .expect("upsert"); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut cursor = tx.cursor_read::().expect("c"); + + // Target block 10 — all modifications (5, 8) are ≤ 10 + let result = find_source::( + &mut cursor, + AccountTrieShardedKey::new(root_path.clone(), 10), + 10, + |k| k.key == root_path, + ) + .expect("ok"); + + assert_eq!(result, ResolvedSource::FromCurrentState); + } + + #[test] + fn find_source_handles_root_path_with_child_history() { + // Verifies the encoding fix: `find_source` with `AccountTrieShardedKey` + // correctly resolves the root path even when child path [0] has history. + // Before the length-prefix fix, this would return `FromCurrentState` + // due to encoding ambiguity. Now it correctly returns `FromChangeset(15)`. + let db = setup_db(); + let root_path = StoredNibbles(Nibbles::default()); + let child_path = StoredNibbles(Nibbles::from_nibbles([0])); + + { + let wtx = db.tx_mut().expect("rw tx"); + let mut cursor = wtx.cursor_write::().expect("c"); + cursor + .upsert( + AccountTrieShardedKey::new(root_path.clone(), u64::MAX), + &BlockNumberList::new_pre_sorted([10, 15]), + ) + .expect("upsert root"); + cursor + .upsert( + AccountTrieShardedKey::new(child_path.clone(), u64::MAX), + &BlockNumberList::new_pre_sorted([10, 15]), + ) + .expect("upsert child"); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut cursor = tx.cursor_read::().expect("c"); + + // find_source with AccountTrieShardedKey correctly returns FromChangeset(15) + let result = find_source::( + &mut cursor, + AccountTrieShardedKey::new(root_path.clone(), 12), + 12, + |k| k.key == root_path, + ) + .expect("ok"); + + assert_eq!( + result, + ResolvedSource::FromChangeset(15), + "find_source with AccountTrieShardedKey should correctly resolve root path" + ); + } + + // ====================== Account Cursor tests ====================== + + #[test] + fn account_cursor_reads_current_state_when_no_history() { + let db = setup_db(); + let addr = B256::from([0xAA; 32]); + let acc = sample_account(42); + + { + let wtx = db.tx_mut().expect("rw tx"); + wtx.cursor_write::() + .expect("c") + .upsert(addr, &acc) + .expect("upsert"); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut cur = V2AccountCursor::new( + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + u64::MAX, + true, + ); + + let result = cur.seek(addr).expect("ok").expect("should find"); + assert_eq!(result.0, addr); + assert_eq!(result.1.nonce, 42); + } + + #[test] + fn account_cursor_resolves_from_changeset_when_modified_after_target() { + let db = setup_db(); + let addr = B256::from([0xBB; 32]); + + { + let wtx = db.tx_mut().expect("rw tx"); + + // Current state: nonce=10 (applied at block 5) + wtx.cursor_write::() + .expect("c") + .upsert(addr, &sample_account(10)) + .expect("upsert"); + + // History bitmap: block 5 modified this account + wtx.cursor_write::() + .expect("c") + .upsert( + HashedAccountShardedKey::new(addr, u64::MAX), + &BlockNumberList::new_pre_sorted([5]), + ) + .expect("upsert"); + + // Changeset: before block 5, account had nonce=3 + wtx.cursor_dup_write::() + .expect("c") + .append_dup(5u64, HashedAccountBeforeTx::new(addr, Some(sample_account(3)))) + .expect("append"); + + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + + // Query at block 4 (before the modification at block 5) + let mut cur = V2AccountCursor::new( + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + 4, + false, + ); + + let result = cur.seek(addr).expect("ok").expect("should find"); + assert_eq!(result.0, addr); + assert_eq!(result.1.nonce, 3, "should get changeset value (before block 5)"); + } + + #[test] + fn account_cursor_returns_current_state_when_at_or_after_last_modification() { + let db = setup_db(); + let addr = B256::from([0xCC; 32]); + + { + let wtx = db.tx_mut().expect("rw tx"); + + // Current state: nonce=20 + wtx.cursor_write::() + .expect("c") + .upsert(addr, &sample_account(20)) + .expect("upsert"); + + // History bitmap: [3, 7] + wtx.cursor_write::() + .expect("c") + .upsert( + HashedAccountShardedKey::new(addr, u64::MAX), + &BlockNumberList::new_pre_sorted([3, 7]), + ) + .expect("upsert"); + + // Changeset at 3 + wtx.cursor_dup_write::() + .expect("c") + .append_dup(3u64, HashedAccountBeforeTx::new(addr, Some(sample_account(1)))) + .expect("append"); + + // Changeset at 7 + wtx.cursor_dup_write::() + .expect("c") + .append_dup(7u64, HashedAccountBeforeTx::new(addr, Some(sample_account(5)))) + .expect("append"); + + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + + // Query at block 10 (after last modification at block 7) + let mut cur = V2AccountCursor::new( + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + 10, + true, + ); + + let result = cur.seek(addr).expect("ok").expect("should find"); + assert_eq!(result.0, addr); + assert_eq!(result.1.nonce, 20, "current state (no modification after block 10)"); + } + + #[test] + fn account_cursor_returns_none_when_not_yet_created() { + let db = setup_db(); + let addr = B256::from([0xDD; 32]); + + { + let wtx = db.tx_mut().expect("rw tx"); + + // Current state: account exists (created at block 5) + wtx.cursor_write::() + .expect("c") + .upsert(addr, &sample_account(1)) + .expect("upsert"); + + // History: first write at block 5 + wtx.cursor_write::() + .expect("c") + .upsert( + HashedAccountShardedKey::new(addr, u64::MAX), + &BlockNumberList::new_pre_sorted([5]), + ) + .expect("upsert"); + + // Changeset at 5: didn't exist before (info = None) + wtx.cursor_dup_write::() + .expect("c") + .append_dup(5u64, HashedAccountBeforeTx::new(addr, None)) + .expect("append"); + + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + + // Query at block 4 (before first write at 5) + // The changeset at block 5 says info=None → the cursor's resolve returns None + // → next_live_from skips this entry → seek returns None + let mut cur = V2AccountCursor::new( + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + 4, + false, + ); + + let result = cur.seek(addr).expect("ok"); + assert!(result.is_none(), "account should not exist at block 4"); + } + + #[test] + fn account_cursor_seek_and_next_skip_dead_entries() { + let db = setup_db(); + let k1 = B256::from([0x01; 32]); + let k2 = B256::from([0x02; 32]); + let k3 = B256::from([0x03; 32]); + + { + let wtx = db.tx_mut().expect("rw tx"); + let mut c = wtx.cursor_write::().expect("c"); + c.upsert(k1, &sample_account(1)).expect("upsert"); + c.upsert(k2, &sample_account(2)).expect("upsert"); + c.upsert(k3, &sample_account(3)).expect("upsert"); + + // k2 was created at block 10 + wtx.cursor_write::() + .expect("c") + .upsert( + HashedAccountShardedKey::new(k2, u64::MAX), + &BlockNumberList::new_pre_sorted([10]), + ) + .expect("upsert"); + + // Changeset at 10: k2 didn't exist before + wtx.cursor_dup_write::() + .expect("c") + .append_dup(10u64, HashedAccountBeforeTx::new(k2, None)) + .expect("append"); + + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + + // Query at block 5 (before k2 was created) + let mut cur = V2AccountCursor::new( + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + 5, + false, + ); + + // Seek k1 → should find k1 (no history = current state) + let result = cur.seek(k1).expect("ok").expect("should find k1"); + assert_eq!(result.0, k1); + assert_eq!(result.1.nonce, 1); + + // Next → should skip k2 (doesn't exist at block 5) and find k3 + let result = cur.next().expect("ok").expect("should skip k2, find k3"); + assert_eq!(result.0, k3); + assert_eq!(result.1.nonce, 3); + } + + /// Account was deleted (SELFDESTRUCT) after the target block, so it's not + /// in the current-state table. The history walk must discover it. + #[test] + fn account_cursor_discovers_key_deleted_after_target_block() { + let db = setup_db(); + let k1 = B256::from([0x01; 32]); + let k2 = B256::from([0x02; 32]); // deleted after target + let k3 = B256::from([0x03; 32]); + + { + let wtx = db.tx_mut().expect("rw tx"); + let mut c = wtx.cursor_write::().expect("c"); + // k1 and k3 exist in current state; k2 was deleted at block 10 + c.upsert(k1, &sample_account(1)).expect("upsert"); + c.upsert(k3, &sample_account(3)).expect("upsert"); + + // k2 history: modified at blocks [5, 10] + wtx.cursor_write::() + .expect("c") + .upsert( + HashedAccountShardedKey::new(k2, u64::MAX), + &BlockNumberList::new_pre_sorted([5, 10]), + ) + .expect("upsert"); + + // Changeset at block 10: value before block 10 = nonce 7 + wtx.cursor_dup_write::() + .expect("c") + .append_dup(10u64, HashedAccountBeforeTx::new(k2, Some(sample_account(7)))) + .expect("append"); + + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + // Query at block 9: k2 existed with nonce=7 + let mut cur = V2AccountCursor::new( + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + 9, + false, + ); + + let r1 = cur.seek(B256::ZERO).expect("ok").expect("k1"); + assert_eq!(r1.0, k1); + assert_eq!(r1.1.nonce, 1); + + let r2 = cur.next().expect("ok").expect("k2 from history"); + assert_eq!(r2.0, k2); + assert_eq!(r2.1.nonce, 7); + + let r3 = cur.next().expect("ok").expect("k3"); + assert_eq!(r3.0, k3); + assert_eq!(r3.1.nonce, 3); + + assert!(cur.next().expect("ok").is_none()); + } + + /// All accounts are deleted after the target block — only the history + /// walk can find them. + #[test] + fn account_cursor_all_keys_from_history() { + let db = setup_db(); + let k1 = B256::from([0x10; 32]); + let k2 = B256::from([0x20; 32]); + + { + let wtx = db.tx_mut().expect("rw tx"); + // Nothing in current state. + + // k1 modified at block 5 + wtx.cursor_write::() + .expect("c") + .upsert( + HashedAccountShardedKey::new(k1, u64::MAX), + &BlockNumberList::new_pre_sorted([5]), + ) + .expect("upsert"); + wtx.cursor_dup_write::() + .expect("c") + .append_dup(5u64, HashedAccountBeforeTx::new(k1, Some(sample_account(11)))) + .expect("append"); + + // k2 modified at block 8 + wtx.cursor_write::() + .expect("c") + .upsert( + HashedAccountShardedKey::new(k2, u64::MAX), + &BlockNumberList::new_pre_sorted([8]), + ) + .expect("upsert"); + wtx.cursor_dup_write::() + .expect("c") + .append_dup(8u64, HashedAccountBeforeTx::new(k2, Some(sample_account(22)))) + .expect("append"); + + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut cur = V2AccountCursor::new( + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + 4, + false, + ); + + let r1 = cur.seek(B256::ZERO).expect("ok").expect("k1"); + assert_eq!(r1.0, k1); + assert_eq!(r1.1.nonce, 11); + + let r2 = cur.next().expect("ok").expect("k2"); + assert_eq!(r2.0, k2); + assert_eq!(r2.1.nonce, 22); + + assert!(cur.next().expect("ok").is_none()); + } + + /// Duplicate key in both current state and history — the merge should + /// yield it exactly once. + #[test] + fn account_cursor_deduplicates_key_in_both_cursors() { + let db = setup_db(); + let k = B256::from([0x55; 32]); + + { + let wtx = db.tx_mut().expect("rw tx"); + wtx.cursor_write::() + .expect("c") + .upsert(k, &sample_account(99)) + .expect("upsert"); + + wtx.cursor_write::() + .expect("c") + .upsert( + HashedAccountShardedKey::new(k, u64::MAX), + &BlockNumberList::new_pre_sorted([5]), + ) + .expect("upsert"); + wtx.cursor_dup_write::() + .expect("c") + .append_dup(5u64, HashedAccountBeforeTx::new(k, Some(sample_account(50)))) + .expect("append"); + + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + // At block 4 → changeset at 5 gives nonce=50 + let mut cur = V2AccountCursor::new( + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + 4, + false, + ); + + let r = cur.seek(B256::ZERO).expect("ok").expect("one result"); + assert_eq!(r.0, k); + assert_eq!(r.1.nonce, 50); + assert!(cur.next().expect("ok").is_none(), "no duplicates"); + } + + // ====================== Account Trie Cursor tests ====================== + + #[test] + fn account_trie_cursor_reads_current_state_when_no_history() { + let db = setup_db(); + let path = Nibbles::from_nibbles([0x0A]); + let n = node(); + + { + let wtx = db.tx_mut().expect("rw tx"); + wtx.cursor_write::() + .expect("c") + .upsert(StoredNibbles(path), &n) + .expect("upsert"); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut cur = V2AccountTrieCursor::new( + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + u64::MAX, + true, + ); + + let out = TrieCursor::seek_exact(&mut cur, path).expect("ok").expect("some"); + assert_eq!(out.0, path); + assert_eq!(out.1, n); + } + + #[test] + fn account_trie_cursor_resolves_old_node_from_changeset() { + let db = setup_db(); + let path = Nibbles::from_nibbles([0x0B]); + let old_node = node(); + let new_node = node2(); + + { + let wtx = db.tx_mut().expect("rw tx"); + + // Current state has new_node (applied at block 10) + wtx.cursor_write::() + .expect("c") + .upsert(StoredNibbles(path), &new_node) + .expect("upsert"); + + // History: modified at block 10 + wtx.cursor_write::() + .expect("c") + .upsert( + AccountTrieShardedKey::new(StoredNibbles(path), u64::MAX), + &BlockNumberList::new_pre_sorted([10]), + ) + .expect("upsert"); + + // Changeset at block 10: old_node was the value before + let cs_entry = TrieChangeSetsEntry { + nibbles: StoredNibblesSubKey(path), + node: Some(old_node.clone()), + }; + wtx.cursor_dup_write::() + .expect("c") + .append_dup(10u64, cs_entry) + .expect("append"); + + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + + // Query at block 9 (before modification at 10) + let mut cur = V2AccountTrieCursor::new( + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + 9, + false, + ); + + let out = TrieCursor::seek_exact(&mut cur, path).expect("ok").expect("some"); + assert_eq!(out.0, path); + assert_eq!(out.1, old_node, "should get old node from changeset"); + } + + #[test] + fn account_trie_cursor_seek_and_next_skip_dead_nodes() { + let db = setup_db(); + let p1 = Nibbles::from_nibbles([0x01]); + let p2 = Nibbles::from_nibbles([0x02]); + let p3 = Nibbles::from_nibbles([0x03]); + + { + let wtx = db.tx_mut().expect("rw tx"); + let mut c = wtx.cursor_write::().expect("c"); + c.upsert(StoredNibbles(p1), &node()).expect("upsert"); + c.upsert(StoredNibbles(p2), &node()).expect("upsert"); + c.upsert(StoredNibbles(p3), &node()).expect("upsert"); + + // p2 was created at block 5, didn't exist before + wtx.cursor_write::() + .expect("c") + .upsert( + AccountTrieShardedKey::new(StoredNibbles(p2), u64::MAX), + &BlockNumberList::new_pre_sorted([5]), + ) + .expect("upsert"); + + let cs_entry = TrieChangeSetsEntry { + nibbles: StoredNibblesSubKey(p2), + node: None, + }; + wtx.cursor_dup_write::() + .expect("c") + .append_dup(5u64, cs_entry) + .expect("append"); + + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + + // Query at block 3 (before p2 was created) + let mut cur = V2AccountTrieCursor::new( + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + 3, + false, + ); + + // Seek p1 → should find p1 + let out = TrieCursor::seek(&mut cur, p1).expect("ok").expect("some"); + assert_eq!(out.0, p1); + + // Next → should skip p2 (didn't exist at block 3) and find p3 + let out = TrieCursor::next(&mut cur).expect("ok").expect("some"); + assert_eq!(out.0, p3, "should skip p2 which didn't exist at block 3"); + } + + #[test] + fn account_trie_cursor_seek_returns_gte() { + let db = setup_db(); + let p_a = Nibbles::from_nibbles([0x0A]); + let p_c = Nibbles::from_nibbles([0x0C]); + let p_b = Nibbles::from_nibbles([0x0B]); + + { + let wtx = db.tx_mut().expect("rw tx"); + let mut c = wtx.cursor_write::().expect("c"); + c.upsert(StoredNibbles(p_a), &node()).expect("upsert"); + c.upsert(StoredNibbles(p_c), &node()).expect("upsert"); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut cur = V2AccountTrieCursor::new( + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + u64::MAX, + true, + ); + + // Seek to 0x0B → should land on 0x0C (first ≥ 0x0B) + let out = TrieCursor::seek(&mut cur, p_b).expect("ok").expect("some"); + assert_eq!(out.0, p_c); + } + + #[test] + fn account_trie_cursor_empty_returns_none() { + let db = setup_db(); + let tx = db.tx().expect("ro tx"); + let mut cur = V2AccountTrieCursor::new( + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + u64::MAX, + true, + ); + + assert!(TrieCursor::seek(&mut cur, Nibbles::default()).expect("ok").is_none()); + assert!(TrieCursor::next(&mut cur).expect("ok").is_none()); + } + + #[test] + fn account_trie_cursor_discovers_key_deleted_after_target_block() { + // Scenario from the design discussion: + // block 2 adds [a1, b1, c1] + // block 3 adds [d1, z1] and deletes a1 + // Query at block 2 → should see a1, b1, c1 (not d1 or z1) + let db = setup_db(); + let a1 = Nibbles::from_nibbles([0x0A, 0x01]); + let b1 = Nibbles::from_nibbles([0x0B, 0x01]); + let c1 = Nibbles::from_nibbles([0x0C, 0x01]); + let d1 = Nibbles::from_nibbles([0x0D, 0x01]); + let z1 = Nibbles::from_nibbles([0x0F, 0x01]); + let n = node(); + + { + let wtx = db.tx_mut().expect("rw tx"); + let mut c = wtx.cursor_write::().expect("c"); + + // Current state after block 3: {b1, c1, d1, z1} (a1 deleted) + c.upsert(StoredNibbles(b1), &n).expect("upsert"); + c.upsert(StoredNibbles(c1), &n).expect("upsert"); + c.upsert(StoredNibbles(d1), &n).expect("upsert"); + c.upsert(StoredNibbles(z1), &n).expect("upsert"); + + // History bitmaps + let mut hc = wtx.cursor_write::().expect("c"); + // a1 modified at blocks 2 and 3 + hc.upsert( + AccountTrieShardedKey::new(StoredNibbles(a1), u64::MAX), + &BlockNumberList::new_pre_sorted([2, 3]), + ) + .expect("upsert"); + // b1 modified at block 2 + hc.upsert( + AccountTrieShardedKey::new(StoredNibbles(b1), u64::MAX), + &BlockNumberList::new_pre_sorted([2]), + ) + .expect("upsert"); + // c1 modified at block 2 + hc.upsert( + AccountTrieShardedKey::new(StoredNibbles(c1), u64::MAX), + &BlockNumberList::new_pre_sorted([2]), + ) + .expect("upsert"); + // d1 modified at block 3 + hc.upsert( + AccountTrieShardedKey::new(StoredNibbles(d1), u64::MAX), + &BlockNumberList::new_pre_sorted([3]), + ) + .expect("upsert"); + // z1 modified at block 3 + hc.upsert( + AccountTrieShardedKey::new(StoredNibbles(z1), u64::MAX), + &BlockNumberList::new_pre_sorted([3]), + ) + .expect("upsert"); + + // Changesets + let mut csc = wtx.cursor_dup_write::().expect("c"); + + // Block 2 changesets: a1, b1, c1 didn't exist before + csc.append_dup( + 2u64, + TrieChangeSetsEntry { nibbles: StoredNibblesSubKey(a1), node: None }, + ) + .expect("append"); + csc.append_dup( + 2u64, + TrieChangeSetsEntry { nibbles: StoredNibblesSubKey(b1), node: None }, + ) + .expect("append"); + csc.append_dup( + 2u64, + TrieChangeSetsEntry { nibbles: StoredNibblesSubKey(c1), node: None }, + ) + .expect("append"); + + // Block 3 changesets: a1 existed (deleted), d1 and z1 didn't exist + csc.append_dup( + 3u64, + TrieChangeSetsEntry { nibbles: StoredNibblesSubKey(a1), node: Some(n.clone()) }, + ) + .expect("append"); + csc.append_dup( + 3u64, + TrieChangeSetsEntry { nibbles: StoredNibblesSubKey(d1), node: None }, + ) + .expect("append"); + csc.append_dup( + 3u64, + TrieChangeSetsEntry { nibbles: StoredNibblesSubKey(z1), node: None }, + ) + .expect("append"); + + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + + // Query at block 2: a1 should be visible even though it's deleted + // from current state (deleted at block 3). + let mut cur = V2AccountTrieCursor::new( + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + 2, + false, + ); + + // seek(default) → a1 (discovered via history walk, resolved from changeset) + let out = TrieCursor::seek(&mut cur, Nibbles::default()) + .expect("ok") + .expect("should find a1"); + assert_eq!(out.0, a1, "a1 must be visible at block 2"); + assert_eq!(out.1, n); + + // next → b1 + let out = TrieCursor::next(&mut cur).expect("ok").expect("should find b1"); + assert_eq!(out.0, b1); + + // next → c1 + let out = TrieCursor::next(&mut cur).expect("ok").expect("should find c1"); + assert_eq!(out.0, c1); + + // next → None (d1 and z1 didn't exist at block 2) + let out = TrieCursor::next(&mut cur).expect("ok"); + assert!(out.is_none(), "d1 and z1 must NOT be visible at block 2"); + } + + #[test] + fn account_trie_cursor_deleted_key_only_in_history() { + // Key exists ONLY in history (not in current state), no other keys at all. + // Ensures the history-walk alone can produce results when current state is empty. + let db = setup_db(); + let p = Nibbles::from_nibbles([0x05]); + let n = node(); + + { + let wtx = db.tx_mut().expect("rw tx"); + // Current state: empty (p was deleted at block 4) + + // History: [2, 4] + wtx.cursor_write::() + .expect("c") + .upsert( + AccountTrieShardedKey::new(StoredNibbles(p), u64::MAX), + &BlockNumberList::new_pre_sorted([2, 4]), + ) + .expect("upsert"); + + // Changeset block 2: p didn't exist before + let mut csc = wtx.cursor_dup_write::().expect("c"); + csc.append_dup( + 2u64, + TrieChangeSetsEntry { nibbles: StoredNibblesSubKey(p), node: None }, + ) + .expect("append"); + // Changeset block 4: p had value n before deletion + csc.append_dup( + 4u64, + TrieChangeSetsEntry { nibbles: StoredNibblesSubKey(p), node: Some(n.clone()) }, + ) + .expect("append"); + + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + + // Query at block 3: p should be visible (created at 2, deleted at 4) + let mut cur = V2AccountTrieCursor::new( + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + 3, + false, + ); + + let out = TrieCursor::seek(&mut cur, Nibbles::default()) + .expect("ok") + .expect("should find p at block 3"); + assert_eq!(out.0, p); + assert_eq!(out.1, n, "should resolve from changeset at block 4"); + + // next → None + assert!(TrieCursor::next(&mut cur).expect("ok").is_none()); + + // Also: query at block 1 → p didn't exist yet + let mut cur2 = V2AccountTrieCursor::new( + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + 1, + false, + ); + assert!( + TrieCursor::seek(&mut cur2, Nibbles::default()).expect("ok").is_none(), + "p should not exist at block 1" + ); + } + + #[test] + fn account_trie_cursor_seek_exact_on_deleted_key() { + // seek_exact on a key that is deleted from current state but alive at + // the target block. + let db = setup_db(); + let p = Nibbles::from_nibbles([0x0A]); + let n = node(); + + { + let wtx = db.tx_mut().expect("rw tx"); + // Current state: empty (p deleted at block 10) + + wtx.cursor_write::() + .expect("c") + .upsert( + AccountTrieShardedKey::new(StoredNibbles(p), u64::MAX), + &BlockNumberList::new_pre_sorted([5, 10]), + ) + .expect("upsert"); + + let mut csc = wtx.cursor_dup_write::().expect("c"); + // Block 5: created (old = None) + csc.append_dup( + 5u64, + TrieChangeSetsEntry { nibbles: StoredNibblesSubKey(p), node: None }, + ) + .expect("append"); + // Block 10: deleted (old = n) + csc.append_dup( + 10u64, + TrieChangeSetsEntry { nibbles: StoredNibblesSubKey(p), node: Some(n.clone()) }, + ) + .expect("append"); + + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + + // seek_exact at block 8 → should find p + let mut cur = V2AccountTrieCursor::new( + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + 8, + false, + ); + let out = TrieCursor::seek_exact(&mut cur, p).expect("ok").expect("should find"); + assert_eq!(out.0, p); + assert_eq!(out.1, n); + + // seek_exact at block 3 → should NOT find p (created at 5) + let mut cur2 = V2AccountTrieCursor::new( + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + 3, + false, + ); + assert!(TrieCursor::seek_exact(&mut cur2, p).expect("ok").is_none()); + } + + #[test] + fn account_trie_cursor_current_tracks_last_yielded() { + // current() should return the last key yielded by seek/next. + let db = setup_db(); + let p1 = Nibbles::from_nibbles([0x01]); + let p2 = Nibbles::from_nibbles([0x02]); + let n = node(); + + { + let wtx = db.tx_mut().expect("rw tx"); + let mut c = wtx.cursor_write::().expect("c"); + c.upsert(StoredNibbles(p1), &n).expect("upsert"); + c.upsert(StoredNibbles(p2), &n).expect("upsert"); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut cur = V2AccountTrieCursor::new( + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + u64::MAX, + true, + ); + + // Before any seek, current is None + assert!(TrieCursor::current(&mut cur).expect("ok").is_none()); + + TrieCursor::seek(&mut cur, p1).expect("ok"); + assert_eq!(TrieCursor::current(&mut cur).expect("ok"), Some(p1)); + + TrieCursor::next(&mut cur).expect("ok"); + assert_eq!(TrieCursor::current(&mut cur).expect("ok"), Some(p2)); + } + + #[test] + fn account_trie_cursor_seek_exact_then_next() { + // After seek_exact, next() should return the key after the sought key. + let db = setup_db(); + let p1 = Nibbles::from_nibbles([0x01]); + let p2 = Nibbles::from_nibbles([0x02]); + let p3 = Nibbles::from_nibbles([0x03]); + let n = node(); + + { + let wtx = db.tx_mut().expect("rw tx"); + let mut c = wtx.cursor_write::().expect("c"); + c.upsert(StoredNibbles(p1), &n).expect("upsert"); + c.upsert(StoredNibbles(p2), &n).expect("upsert"); + c.upsert(StoredNibbles(p3), &n).expect("upsert"); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut cur = V2AccountTrieCursor::new( + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + u64::MAX, + true, + ); + + // seek_exact p2 + let out = TrieCursor::seek_exact(&mut cur, p2).expect("ok").expect("some"); + assert_eq!(out.0, p2); + + // next → p3 + let out = TrieCursor::next(&mut cur).expect("ok").expect("some"); + assert_eq!(out.0, p3); + + // next → None + assert!(TrieCursor::next(&mut cur).expect("ok").is_none()); + } + + #[test] + fn account_trie_cursor_seek_gte_skips_dead_landing() { + // seek() lands on a dead key (in current state but not alive at target + // block) and must skip forward to the next live key. + let db = setup_db(); + let p_a = Nibbles::from_nibbles([0x0A]); + let p_b = Nibbles::from_nibbles([0x0B]); + let p_c = Nibbles::from_nibbles([0x0C]); + + { + let wtx = db.tx_mut().expect("rw tx"); + let mut c = wtx.cursor_write::().expect("c"); + c.upsert(StoredNibbles(p_a), &node()).expect("upsert"); + c.upsert(StoredNibbles(p_b), &node()).expect("upsert"); + c.upsert(StoredNibbles(p_c), &node()).expect("upsert"); + + // p_b was created at block 10 + wtx.cursor_write::() + .expect("c") + .upsert( + AccountTrieShardedKey::new(StoredNibbles(p_b), u64::MAX), + &BlockNumberList::new_pre_sorted([10]), + ) + .expect("upsert"); + + wtx.cursor_dup_write::() + .expect("c") + .append_dup( + 10u64, + TrieChangeSetsEntry { nibbles: StoredNibblesSubKey(p_b), node: None }, + ) + .expect("append"); + + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + + // At block 5, seek(p_b) → p_b is dead → should skip to p_c + let mut cur = V2AccountTrieCursor::new( + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + 5, + false, + ); + + let out = TrieCursor::seek(&mut cur, p_b).expect("ok").expect("some"); + assert_eq!(out.0, p_c, "should skip dead p_b and land on p_c"); + } + + #[test] + fn account_trie_cursor_all_keys_dead() { + // Every key in current state is dead at the target block, and no + // history-only keys exist → None. + let db = setup_db(); + let p1 = Nibbles::from_nibbles([0x01]); + let p2 = Nibbles::from_nibbles([0x02]); + + { + let wtx = db.tx_mut().expect("rw tx"); + let mut c = wtx.cursor_write::().expect("c"); + c.upsert(StoredNibbles(p1), &node()).expect("upsert"); + c.upsert(StoredNibbles(p2), &node()).expect("upsert"); + + let mut hc = wtx.cursor_write::().expect("c"); + // Both created at block 5 + hc.upsert( + AccountTrieShardedKey::new(StoredNibbles(p1), u64::MAX), + &BlockNumberList::new_pre_sorted([5]), + ) + .expect("upsert"); + hc.upsert( + AccountTrieShardedKey::new(StoredNibbles(p2), u64::MAX), + &BlockNumberList::new_pre_sorted([5]), + ) + .expect("upsert"); + + let mut csc = wtx.cursor_dup_write::().expect("c"); + csc.append_dup( + 5u64, + TrieChangeSetsEntry { nibbles: StoredNibblesSubKey(p1), node: None }, + ) + .expect("append"); + csc.append_dup( + 5u64, + TrieChangeSetsEntry { nibbles: StoredNibblesSubKey(p2), node: None }, + ) + .expect("append"); + + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + + // At block 3 → both dead + let mut cur = V2AccountTrieCursor::new( + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + 3, + false, + ); + + assert!(TrieCursor::seek(&mut cur, Nibbles::default()).expect("ok").is_none()); + } + + #[test] + fn account_trie_cursor_interleaved_current_and_history_keys() { + // Current state has {b, d}. History has {a, c, e} (all deleted after + // target block). The merge should yield a, b, c, d, e in order. + let db = setup_db(); + let a = Nibbles::from_nibbles([0x01]); + let b = Nibbles::from_nibbles([0x02]); + let c = Nibbles::from_nibbles([0x03]); + let d = Nibbles::from_nibbles([0x04]); + let e = Nibbles::from_nibbles([0x05]); + let n = node(); + + { + let wtx = db.tx_mut().expect("rw tx"); + let mut cs = wtx.cursor_write::().expect("c"); + // Current state: {b, d} + cs.upsert(StoredNibbles(b), &n).expect("upsert"); + cs.upsert(StoredNibbles(d), &n).expect("upsert"); + + let mut hc = wtx.cursor_write::().expect("c"); + // a: created block 2, deleted block 10 + hc.upsert( + AccountTrieShardedKey::new(StoredNibbles(a), u64::MAX), + &BlockNumberList::new_pre_sorted([2, 10]), + ) + .expect("upsert"); + // b: created block 2 (stays in current state) + hc.upsert( + AccountTrieShardedKey::new(StoredNibbles(b), u64::MAX), + &BlockNumberList::new_pre_sorted([2]), + ) + .expect("upsert"); + // c: created block 2, deleted block 10 + hc.upsert( + AccountTrieShardedKey::new(StoredNibbles(c), u64::MAX), + &BlockNumberList::new_pre_sorted([2, 10]), + ) + .expect("upsert"); + // d: created block 2 (stays) + hc.upsert( + AccountTrieShardedKey::new(StoredNibbles(d), u64::MAX), + &BlockNumberList::new_pre_sorted([2]), + ) + .expect("upsert"); + // e: created block 2, deleted block 10 + hc.upsert( + AccountTrieShardedKey::new(StoredNibbles(e), u64::MAX), + &BlockNumberList::new_pre_sorted([2, 10]), + ) + .expect("upsert"); + + let mut csc = wtx.cursor_dup_write::().expect("c"); + // Block 2: all created (old = None) + csc.append_dup( + 2u64, + TrieChangeSetsEntry { nibbles: StoredNibblesSubKey(a), node: None }, + ) + .expect("append"); + csc.append_dup( + 2u64, + TrieChangeSetsEntry { nibbles: StoredNibblesSubKey(b), node: None }, + ) + .expect("append"); + csc.append_dup( + 2u64, + TrieChangeSetsEntry { nibbles: StoredNibblesSubKey(c), node: None }, + ) + .expect("append"); + csc.append_dup( + 2u64, + TrieChangeSetsEntry { nibbles: StoredNibblesSubKey(d), node: None }, + ) + .expect("append"); + csc.append_dup( + 2u64, + TrieChangeSetsEntry { nibbles: StoredNibblesSubKey(e), node: None }, + ) + .expect("append"); + // Block 10: a, c, e deleted (old = n) + csc.append_dup( + 10u64, + TrieChangeSetsEntry { nibbles: StoredNibblesSubKey(a), node: Some(n.clone()) }, + ) + .expect("append"); + csc.append_dup( + 10u64, + TrieChangeSetsEntry { nibbles: StoredNibblesSubKey(c), node: Some(n.clone()) }, + ) + .expect("append"); + csc.append_dup( + 10u64, + TrieChangeSetsEntry { nibbles: StoredNibblesSubKey(e), node: Some(n.clone()) }, + ) + .expect("append"); + + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + + // At block 5: a, b, c, d, e all alive (a, c, e via changeset at 10) + let mut cur = V2AccountTrieCursor::new( + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + 5, + false, + ); + + let out = TrieCursor::seek(&mut cur, Nibbles::default()).expect("ok").expect("a"); + assert_eq!(out.0, a, "first key should be a (from history)"); + + let out = TrieCursor::next(&mut cur).expect("ok").expect("b"); + assert_eq!(out.0, b, "second key should be b (from current state)"); + + let out = TrieCursor::next(&mut cur).expect("ok").expect("c"); + assert_eq!(out.0, c, "third key should be c (from history)"); + + let out = TrieCursor::next(&mut cur).expect("ok").expect("d"); + assert_eq!(out.0, d, "fourth key should be d (from current state)"); + + let out = TrieCursor::next(&mut cur).expect("ok").expect("e"); + assert_eq!(out.0, e, "fifth key should be e (from history)"); + + assert!(TrieCursor::next(&mut cur).expect("ok").is_none()); + } + + #[test] + fn account_trie_cursor_duplicate_key_in_both_cursors() { + // Key exists in BOTH current state and history. The merge should NOT + // yield it twice. + let db = setup_db(); + let p = Nibbles::from_nibbles([0x0A]); + let n = node(); + let n2 = node2(); + + { + let wtx = db.tx_mut().expect("rw tx"); + + // Current state: p -> n2 (updated at block 10) + wtx.cursor_write::() + .expect("c") + .upsert(StoredNibbles(p), &n2) + .expect("upsert"); + + // History: modified at block 10 + wtx.cursor_write::() + .expect("c") + .upsert( + AccountTrieShardedKey::new(StoredNibbles(p), u64::MAX), + &BlockNumberList::new_pre_sorted([10]), + ) + .expect("upsert"); + + wtx.cursor_dup_write::() + .expect("c") + .append_dup( + 10u64, + TrieChangeSetsEntry { + nibbles: StoredNibblesSubKey(p), + node: Some(n.clone()), + }, + ) + .expect("append"); + + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + + // At block 8: resolve from changeset at 10 → old value n + let mut cur = V2AccountTrieCursor::new( + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + 8, + false, + ); + + let out = TrieCursor::seek(&mut cur, p).expect("ok").expect("should find"); + assert_eq!(out.0, p); + assert_eq!(out.1, n, "should get old value from changeset"); + + // next → None (should NOT yield p again) + assert!(TrieCursor::next(&mut cur).expect("ok").is_none()); + } + + #[test] + fn account_trie_cursor_query_at_latest_block() { + // When max_block_number == u64::MAX, everything reads from current + // state — even keys with history. This exercises the + // FromCurrentState path in find_source. + let db = setup_db(); + let p1 = Nibbles::from_nibbles([0x01]); + let p2 = Nibbles::from_nibbles([0x02]); + let n2 = node2(); + + { + let wtx = db.tx_mut().expect("rw tx"); + let mut c = wtx.cursor_write::().expect("c"); + c.upsert(StoredNibbles(p1), &node()).expect("upsert"); + c.upsert(StoredNibbles(p2), &n2).expect("upsert"); + + // p2 has history at block 5 + wtx.cursor_write::() + .expect("c") + .upsert( + AccountTrieShardedKey::new(StoredNibbles(p2), u64::MAX), + &BlockNumberList::new_pre_sorted([5]), + ) + .expect("upsert"); + + wtx.cursor_dup_write::() + .expect("c") + .append_dup( + 5u64, + TrieChangeSetsEntry { nibbles: StoredNibblesSubKey(p2), node: None }, + ) + .expect("append"); + + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut cur = V2AccountTrieCursor::new( + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + u64::MAX, + true, + ); + + let out = TrieCursor::seek(&mut cur, Nibbles::default()).expect("ok").expect("p1"); + assert_eq!(out.0, p1); + + let out = TrieCursor::next(&mut cur).expect("ok").expect("p2"); + assert_eq!(out.0, p2); + assert_eq!(out.1, n2, "should read current state value"); + + assert!(TrieCursor::next(&mut cur).expect("ok").is_none()); + } + + // ——— Storage Trie Cursor: dual-cursor merge tests ——— + + #[test] + fn storage_trie_cursor_discovers_deleted_key() { + // Same scenario as account trie deleted-key test, but for storage trie. + let db = setup_db(); + let addr = B256::from([0xAA; 32]); + let a1 = Nibbles::from_nibbles([0x0A, 0x01]); + let b1 = Nibbles::from_nibbles([0x0B, 0x01]); + let c1 = Nibbles::from_nibbles([0x0C, 0x01]); + let n = node(); + + { + let wtx = db.tx_mut().expect("rw tx"); + + // Current state: {b1, c1} (a1 deleted at block 5) + let mut sc = wtx.cursor_dup_write::().expect("c"); + sc.upsert( + addr, + &StorageTrieEntry { nibbles: StoredNibblesSubKey(b1), node: n.clone() }, + ) + .expect("upsert"); + sc.upsert( + addr, + &StorageTrieEntry { nibbles: StoredNibblesSubKey(c1), node: n.clone() }, + ) + .expect("upsert"); + + let mut hc = wtx.cursor_write::().expect("c"); + // a1: created at block 2, deleted at block 5 + hc.upsert( + StorageTrieShardedKey::new(addr, StoredNibbles(a1), u64::MAX), + &BlockNumberList::new_pre_sorted([2, 5]), + ) + .expect("upsert"); + // b1: created at block 2 + hc.upsert( + StorageTrieShardedKey::new(addr, StoredNibbles(b1), u64::MAX), + &BlockNumberList::new_pre_sorted([2]), + ) + .expect("upsert"); + // c1: created at block 2 + hc.upsert( + StorageTrieShardedKey::new(addr, StoredNibbles(c1), u64::MAX), + &BlockNumberList::new_pre_sorted([2]), + ) + .expect("upsert"); + + let mut csc = wtx.cursor_dup_write::().expect("c"); + // Block 2: all created + let cs_key2 = BlockNumberHashedAddress((2u64, addr)); + csc.append_dup( + cs_key2.clone(), + TrieChangeSetsEntry { nibbles: StoredNibblesSubKey(a1), node: None }, + ) + .expect("append"); + csc.append_dup( + cs_key2.clone(), + TrieChangeSetsEntry { nibbles: StoredNibblesSubKey(b1), node: None }, + ) + .expect("append"); + csc.append_dup( + cs_key2, + TrieChangeSetsEntry { nibbles: StoredNibblesSubKey(c1), node: None }, + ) + .expect("append"); + // Block 5: a1 deleted (old = n) + let cs_key5 = BlockNumberHashedAddress((5u64, addr)); + csc.append_dup( + cs_key5, + TrieChangeSetsEntry { nibbles: StoredNibblesSubKey(a1), node: Some(n.clone()) }, + ) + .expect("append"); + + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + + // Query at block 3: a1 should be visible + let mut cur = V2StorageTrieCursor::new( + tx.cursor_dup_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + addr, + 3, + false, + ); + + let out = TrieCursor::seek(&mut cur, Nibbles::default()) + .expect("ok") + .expect("should find a1"); + assert_eq!(out.0, a1, "a1 must be visible at block 3"); + + let out = TrieCursor::next(&mut cur).expect("ok").expect("b1"); + assert_eq!(out.0, b1); + + let out = TrieCursor::next(&mut cur).expect("ok").expect("c1"); + assert_eq!(out.0, c1); + + assert!(TrieCursor::next(&mut cur).expect("ok").is_none()); + } + + #[test] + fn storage_trie_cursor_deleted_key_does_not_cross_address() { + // Deleted history key from addr_b must NOT appear when walking addr_a. + let db = setup_db(); + let addr_a = B256::from([0x11; 32]); + let addr_b = B256::from([0x22; 32]); + let p1 = Nibbles::from_nibbles([0x01]); + let p2 = Nibbles::from_nibbles([0x02]); + let n = node(); + + { + let wtx = db.tx_mut().expect("rw tx"); + + // Current state: addr_a has {p1}, addr_b is empty (p2 deleted) + let mut sc = wtx.cursor_dup_write::().expect("c"); + sc.upsert( + addr_a, + &StorageTrieEntry { nibbles: StoredNibblesSubKey(p1), node: n.clone() }, + ) + .expect("upsert"); + + // addr_b: p2 history (created block 2, deleted block 5) + wtx.cursor_write::() + .expect("c") + .upsert( + StorageTrieShardedKey::new(addr_b, StoredNibbles(p2), u64::MAX), + &BlockNumberList::new_pre_sorted([2, 5]), + ) + .expect("upsert"); + + let mut csc = wtx.cursor_dup_write::().expect("c"); + csc.append_dup( + BlockNumberHashedAddress((2u64, addr_b)), + TrieChangeSetsEntry { nibbles: StoredNibblesSubKey(p2), node: None }, + ) + .expect("append"); + csc.append_dup( + BlockNumberHashedAddress((5u64, addr_b)), + TrieChangeSetsEntry { + nibbles: StoredNibblesSubKey(p2), + node: Some(n.clone()), + }, + ) + .expect("append"); + + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + + // Walk addr_a at block 3 → only p1 + let mut cur = V2StorageTrieCursor::new( + tx.cursor_dup_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + addr_a, + 3, + true, + ); + + let out = TrieCursor::seek(&mut cur, Nibbles::default()).expect("ok").expect("p1"); + assert_eq!(out.0, p1); + assert!( + TrieCursor::next(&mut cur).expect("ok").is_none(), + "must not leak addr_b's history into addr_a" + ); + } + + #[test] + fn storage_trie_cursor_set_hashed_address_resets_merge_state() { + // After set_hashed_address, the merge state must be reset so seek/next + // operate correctly on the new address. + let db = setup_db(); + let addr_a = B256::from([0x55; 32]); + let addr_b = B256::from([0x66; 32]); + let p1 = Nibbles::from_nibbles([0x01]); + let p2 = Nibbles::from_nibbles([0x02]); + let n = node(); + + { + let wtx = db.tx_mut().expect("rw tx"); + let mut c = wtx.cursor_dup_write::().expect("c"); + c.upsert( + addr_a, + &StorageTrieEntry { nibbles: StoredNibblesSubKey(p1), node: n.clone() }, + ) + .expect("upsert"); + c.upsert( + addr_b, + &StorageTrieEntry { nibbles: StoredNibblesSubKey(p2), node: n.clone() }, + ) + .expect("upsert"); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut cur = V2StorageTrieCursor::new( + tx.cursor_dup_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + addr_a, + u64::MAX, + true, + ); + + // Seek on addr_a + let out = TrieCursor::seek(&mut cur, p1).expect("ok").expect("p1"); + assert_eq!(out.0, p1); + + // Switch to addr_b + cur.set_hashed_address(addr_b); + let out = TrieCursor::seek(&mut cur, p2).expect("ok").expect("p2"); + assert_eq!(out.0, p2); + + assert!(TrieCursor::next(&mut cur).expect("ok").is_none()); + } + + // ====================== Storage Cursor tests ====================== + + #[test] + fn storage_cursor_reads_current_state_when_no_history() { + let db = setup_db(); + let addr = B256::from([0xAA; 32]); + let slot = B256::from([0x01; 32]); + + { + let wtx = db.tx_mut().expect("rw tx"); + wtx.cursor_dup_write::() + .expect("c") + .upsert(addr, &StorageEntry { key: slot, value: U256::from(42u64) }) + .expect("upsert"); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut cur = V2StorageCursor::new( + tx.cursor_dup_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + addr, + u64::MAX, + true, + ); + + let result = cur.seek(slot).expect("ok").expect("should find"); + assert_eq!(result, (slot, U256::from(42u64))); + } + + #[test] + fn storage_cursor_resolves_from_changeset() { + let db = setup_db(); + let addr = B256::from([0xAA; 32]); + let slot = B256::from([0x01; 32]); + + { + let wtx = db.tx_mut().expect("rw tx"); + + // Current state: value=1000 + wtx.cursor_dup_write::() + .expect("c") + .upsert(addr, &StorageEntry { key: slot, value: U256::from(1000u64) }) + .expect("upsert"); + + // History: modified at block 8 + wtx.cursor_write::() + .expect("c") + .upsert( + HashedStorageShardedKey { + hashed_address: addr, + sharded_key: ShardedKey::new(slot, u64::MAX), + }, + &BlockNumberList::new_pre_sorted([8]), + ) + .expect("upsert"); + + // Changeset at block 8: old value was 500 + let cs_key = BlockNumberHashedAddress((8u64, addr)); + wtx.cursor_dup_write::() + .expect("c") + .append_dup(cs_key, StorageEntry { key: slot, value: U256::from(500u64) }) + .expect("append"); + + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + + // Query at block 7 (before modification at 8) + let mut cur = V2StorageCursor::new( + tx.cursor_dup_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + addr, + 7, + false, + ); + + let result = cur.seek(slot).expect("ok").expect("should find"); + assert_eq!(result.0, slot); + assert_eq!(result.1, U256::from(500u64), "should get changeset value"); + } + + #[test] + fn storage_cursor_is_storage_empty() { + let db = setup_db(); + let addr_with = B256::from([0xBB; 32]); + let addr_without = B256::from([0xCC; 32]); + + { + let wtx = db.tx_mut().expect("rw tx"); + wtx.cursor_dup_write::() + .expect("c") + .upsert( + addr_with, + &StorageEntry { key: B256::from([0x01; 32]), value: U256::from(1u64) }, + ) + .expect("upsert"); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + + let mut cur_with = V2StorageCursor::new( + tx.cursor_dup_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + addr_with, + u64::MAX, + true, + ); + assert!(!cur_with.is_storage_empty().expect("ok")); + + let mut cur_without = V2StorageCursor::new( + tx.cursor_dup_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + addr_without, + u64::MAX, + true, + ); + assert!(cur_without.is_storage_empty().expect("ok")); + } + + /// Storage slot was zeroed (deleted from HashedStorages) after the target + /// block. The history walk must discover it. + #[test] + fn storage_cursor_discovers_slot_deleted_after_target_block() { + let db = setup_db(); + let addr = B256::from([0xAA; 32]); + let s1 = B256::from([0x01; 32]); + let s2 = B256::from([0x02; 32]); // deleted after target + let s3 = B256::from([0x03; 32]); + + { + let wtx = db.tx_mut().expect("rw tx"); + let mut c = wtx.cursor_dup_write::().expect("c"); + // s1 and s3 exist; s2 was zeroed at block 10 + c.upsert(addr, &StorageEntry { key: s1, value: U256::from(100u64) }) + .expect("upsert"); + c.upsert(addr, &StorageEntry { key: s3, value: U256::from(300u64) }) + .expect("upsert"); + + // s2 history: modified at [5, 10] + wtx.cursor_write::() + .expect("c") + .upsert( + HashedStorageShardedKey { + hashed_address: addr, + sharded_key: ShardedKey::new(s2, u64::MAX), + }, + &BlockNumberList::new_pre_sorted([5, 10]), + ) + .expect("upsert"); + + // Changeset at block 10: s2 = 200 before block 10 + let cs_key = BlockNumberHashedAddress((10u64, addr)); + wtx.cursor_dup_write::() + .expect("c") + .append_dup(cs_key, StorageEntry { key: s2, value: U256::from(200u64) }) + .expect("append"); + + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut cur = V2StorageCursor::new( + tx.cursor_dup_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + addr, + 9, + false, + ); + + let r1 = cur.seek(B256::ZERO).expect("ok").expect("s1"); + assert_eq!(r1.0, s1); + assert_eq!(r1.1, U256::from(100u64)); + + let r2 = cur.next().expect("ok").expect("s2 from history"); + assert_eq!(r2.0, s2); + assert_eq!(r2.1, U256::from(200u64)); + + let r3 = cur.next().expect("ok").expect("s3"); + assert_eq!(r3.0, s3); + assert_eq!(r3.1, U256::from(300u64)); + + assert!(cur.next().expect("ok").is_none()); + } + + /// is_storage_empty must return false when storage existed at the target + /// block but has since been wiped from current state. + #[test] + fn storage_cursor_is_storage_empty_false_for_historical_only_slots() { + let db = setup_db(); + let addr = B256::from([0xDD; 32]); + let slot = B256::from([0x01; 32]); + + { + let wtx = db.tx_mut().expect("rw tx"); + // No current state for addr — all storage was wiped at block 10. + + // History: slot modified at [5, 10] + wtx.cursor_write::() + .expect("c") + .upsert( + HashedStorageShardedKey { + hashed_address: addr, + sharded_key: ShardedKey::new(slot, u64::MAX), + }, + &BlockNumberList::new_pre_sorted([5, 10]), + ) + .expect("upsert"); + + // Changeset at 10: value=42 before block 10 + let cs_key = BlockNumberHashedAddress((10u64, addr)); + wtx.cursor_dup_write::() + .expect("c") + .append_dup(cs_key, StorageEntry { key: slot, value: U256::from(42u64) }) + .expect("append"); + + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut cur = V2StorageCursor::new( + tx.cursor_dup_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + addr, + 9, + false, + ); + + assert!(!cur.is_storage_empty().expect("ok"), "should find historical slot"); + } + + /// History-only storage key must NOT cross into a different address. + #[test] + fn storage_cursor_deleted_slot_does_not_cross_address() { + let db = setup_db(); + let addr1 = B256::from([0x01; 32]); + let addr2 = B256::from([0x02; 32]); + let slot = B256::from([0x0A; 32]); + + { + let wtx = db.tx_mut().expect("rw tx"); + // No current storage for addr1. + // addr2 has a history entry for the same slot. + wtx.cursor_write::() + .expect("c") + .upsert( + HashedStorageShardedKey { + hashed_address: addr2, + sharded_key: ShardedKey::new(slot, u64::MAX), + }, + &BlockNumberList::new_pre_sorted([5]), + ) + .expect("upsert"); + + let cs_key = BlockNumberHashedAddress((5u64, addr2)); + wtx.cursor_dup_write::() + .expect("c") + .append_dup(cs_key, StorageEntry { key: slot, value: U256::from(99u64) }) + .expect("append"); + + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut cur = V2StorageCursor::new( + tx.cursor_dup_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + addr1, + 4, + true, + ); + + // addr1 has no storage — history walk finds addr2's entry but must filter it out. + assert!(cur.seek(B256::ZERO).expect("ok").is_none()); + } + + /// set_hashed_address resets merge state so the cursor works correctly + /// for the new address. + #[test] + fn storage_cursor_set_hashed_address_resets_merge_state() { + let db = setup_db(); + let addr1 = B256::from([0x01; 32]); + let addr2 = B256::from([0x02; 32]); + let slot = B256::from([0x0A; 32]); + + { + let wtx = db.tx_mut().expect("rw tx"); + let mut c = wtx.cursor_dup_write::().expect("c"); + c.upsert(addr1, &StorageEntry { key: slot, value: U256::from(11u64) }) + .expect("upsert"); + c.upsert(addr2, &StorageEntry { key: slot, value: U256::from(22u64) }) + .expect("upsert"); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut cur = V2StorageCursor::new( + tx.cursor_dup_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + addr1, + u64::MAX, + true, + ); + + let r1 = cur.seek(B256::ZERO).expect("ok").expect("addr1 slot"); + assert_eq!(r1.1, U256::from(11u64)); + + cur.set_hashed_address(addr2); + let r2 = cur.seek(B256::ZERO).expect("ok").expect("addr2 slot"); + assert_eq!(r2.1, U256::from(22u64)); + } + + // Regression: root trie path [] with child path [0] history. + // Before the length-prefixed encoding fix, the V2AccountTrieCursor would return + // the current-state root node instead of the historical one. + #[test] + fn account_trie_cursor_root_path_resolves_historical_with_child_paths() { + let db = setup_db(); + let root_path = Nibbles::default(); + let child_path = Nibbles::from_nibbles([0]); + + let root_node_at_block5 = BranchNodeCompact::new( + 0b11, + 0, + 0, + vec![], + Some(B256::repeat_byte(0x55)), + ); + let root_node_at_block10 = BranchNodeCompact::new( + 0b111, + 0, + 0, + vec![], + Some(B256::repeat_byte(0xAA)), + ); + let child_node_at_block10 = BranchNodeCompact::new( + 0b101, + 0, + 0, + vec![], + Some(B256::repeat_byte(0xBB)), + ); + + { + let wtx = db.tx_mut().expect("rw tx"); + + // Current state: root has block 10's node, child has block 10's node + wtx.cursor_write::() + .expect("c") + .upsert(StoredNibbles(root_path), &root_node_at_block10) + .expect("upsert root"); + wtx.cursor_write::() + .expect("c") + .upsert(StoredNibbles(child_path), &child_node_at_block10) + .expect("upsert child"); + + // Changeset at block 10: root had block5's node before block 10 + wtx.cursor_dup_write::() + .expect("c") + .append_dup( + 10, + TrieChangeSetsEntry { + nibbles: StoredNibblesSubKey(root_path), + node: Some(root_node_at_block5.clone()), + }, + ) + .expect("append root cs"); + + // History: root modified at block 10 + wtx.cursor_write::() + .expect("c") + .upsert( + AccountTrieShardedKey::new(StoredNibbles(root_path), u64::MAX), + &BlockNumberList::new_pre_sorted([10]), + ) + .expect("upsert root history"); + + // History: child [0] modified at block 10 + wtx.cursor_write::() + .expect("c") + .upsert( + AccountTrieShardedKey::new(StoredNibbles(child_path), u64::MAX), + &BlockNumberList::new_pre_sorted([10]), + ) + .expect("upsert child history"); + + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut cur = V2AccountTrieCursor::new( + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + 8, // max_block_number: query at block 8 (before block 10's change) + false, // is_latest = false (historical query) + ); + + // seek_exact on root path should return the historical value (block 5's node) + let out = TrieCursor::seek_exact(&mut cur, root_path) + .expect("ok") + .expect("root should exist at block 8"); + assert_eq!( + out.1, root_node_at_block5, + "Root path should return the historical node from changeset, not current state" + ); + } + + // ====================== Storage Trie Cursor tests ====================== + + #[test] + fn storage_trie_cursor_reads_current_state_when_no_history() { + let db = setup_db(); + let addr = B256::from([0x55; 32]); + let path = Nibbles::from_nibbles([0x0D]); + + { + let wtx = db.tx_mut().expect("rw tx"); + wtx.cursor_dup_write::() + .expect("c") + .upsert( + addr, + &StorageTrieEntry { nibbles: StoredNibblesSubKey(path), node: node() }, + ) + .expect("upsert"); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut cur = V2StorageTrieCursor::new( + tx.cursor_dup_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + addr, + u64::MAX, + true, + ); + + let out = TrieCursor::seek_exact(&mut cur, path).expect("ok").expect("some"); + assert_eq!(out.0, path); + } + + #[test] + fn storage_trie_cursor_resolves_from_changeset() { + let db = setup_db(); + let addr = B256::from([0x55; 32]); + let path = Nibbles::from_nibbles([0x0D]); + let old_node = node(); + let new_node = node2(); + + { + let wtx = db.tx_mut().expect("rw tx"); + + // Current state + wtx.cursor_dup_write::() + .expect("c") + .upsert( + addr, + &StorageTrieEntry { nibbles: StoredNibblesSubKey(path), node: new_node }, + ) + .expect("upsert"); + + // History: modified at block 6 + wtx.cursor_write::() + .expect("c") + .upsert( + StorageTrieShardedKey::new(addr, StoredNibbles(path), u64::MAX), + &BlockNumberList::new_pre_sorted([6]), + ) + .expect("upsert"); + + // Changeset at block 6: old node + let cs_key = BlockNumberHashedAddress((6u64, addr)); + let cs_entry = TrieChangeSetsEntry { + nibbles: StoredNibblesSubKey(path), + node: Some(old_node.clone()), + }; + wtx.cursor_dup_write::() + .expect("c") + .append_dup(cs_key, cs_entry) + .expect("append"); + + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + + // Query at block 5 (before modification at 6) + let mut cur = V2StorageTrieCursor::new( + tx.cursor_dup_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + addr, + 5, + false, + ); + + let out = TrieCursor::seek_exact(&mut cur, path).expect("ok").expect("some"); + assert_eq!(out.0, path); + assert_eq!(out.1, old_node, "should get old node from changeset"); + } + + #[test] + fn storage_trie_cursor_respects_address_boundary() { + let db = setup_db(); + let addr_a = B256::from([0x33; 32]); + let addr_b = B256::from([0x44; 32]); + let p1 = Nibbles::from_nibbles([0x05]); + let p2 = Nibbles::from_nibbles([0x06]); + + { + let wtx = db.tx_mut().expect("rw tx"); + let mut c = wtx.cursor_dup_write::().expect("c"); + c.upsert( + addr_a, + &StorageTrieEntry { nibbles: StoredNibblesSubKey(p1), node: node() }, + ) + .expect("upsert"); + c.upsert( + addr_b, + &StorageTrieEntry { nibbles: StoredNibblesSubKey(p2), node: node() }, + ) + .expect("upsert"); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut cur = V2StorageTrieCursor::new( + tx.cursor_dup_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + addr_a, + u64::MAX, + true, + ); + + let out = TrieCursor::seek(&mut cur, p1).expect("ok").expect("some"); + assert_eq!(out.0, p1); + + // next() should return None — crossed address boundary + let out = TrieCursor::next(&mut cur).expect("ok"); + assert!(out.is_none(), "must not cross address boundary (DupSort)"); + } + + #[test] + fn storage_trie_cursor_set_hashed_address() { + let db = setup_db(); + let addr_a = B256::from([0x55; 32]); + let addr_b = B256::from([0x66; 32]); + let path = Nibbles::from_nibbles([0x01]); + + { + let wtx = db.tx_mut().expect("rw tx"); + let mut c = wtx.cursor_dup_write::().expect("c"); + c.upsert( + addr_a, + &StorageTrieEntry { nibbles: StoredNibblesSubKey(path), node: node() }, + ) + .expect("upsert"); + c.upsert( + addr_b, + &StorageTrieEntry { nibbles: StoredNibblesSubKey(path), node: node() }, + ) + .expect("upsert"); + wtx.commit().expect("commit"); + } + + let tx = db.tx().expect("ro tx"); + let mut cur = V2StorageTrieCursor::new( + tx.cursor_dup_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_read::().expect("c"), + tx.cursor_dup_read::().expect("c"), + addr_a, + u64::MAX, + true, + ); + + assert!(TrieCursor::seek_exact(&mut cur, path).expect("ok").is_some()); + cur.set_hashed_address(addr_b); + assert!(TrieCursor::seek_exact(&mut cur, path).expect("ok").is_some()); + } +} diff --git a/rust/op-reth/crates/trie/src/db/mod.rs b/rust/op-reth/crates/trie/src/db/mod.rs index b32f557341d..477c783ce89 100644 --- a/rust/op-reth/crates/trie/src/db/mod.rs +++ b/rust/op-reth/crates/trie/src/db/mod.rs @@ -15,3 +15,12 @@ mod cursor; pub use cursor::{ BlockNumberVersionedCursor, MdbxAccountCursor, MdbxStorageCursor, MdbxTrieCursor, }; + +mod cursor_v2; +pub use cursor_v2::{ + V2AccountCursor, V2AccountTrieCursor, + V2StorageCursor, V2StorageTrieCursor, +}; + +mod store_v2; +pub use store_v2::{MdbxProofsProviderV2, MdbxProofsStorageV2}; diff --git a/rust/op-reth/crates/trie/src/db/models/key.rs b/rust/op-reth/crates/trie/src/db/models/key.rs new file mode 100644 index 00000000000..8584b31ad57 --- /dev/null +++ b/rust/op-reth/crates/trie/src/db/models/key.rs @@ -0,0 +1,265 @@ +use alloy_primitives::B256; +use bytes::BufMut; +use reth_codecs::Compact; +use reth_db::{ + models::sharded_key::ShardedKey, + table::{Compress, Decode, Decompress, Encode}, + DatabaseError, +}; +use reth_primitives_traits::{Account, ValueWithSubKey}; +use reth_trie_common::{BranchNodeCompact, StoredNibbles, StoredNibblesSubKey}; +use serde::{Deserialize, Serialize}; + +/// Sharded key for hashed accounts history. +/// +/// Wraps `ShardedKey` to provide `Encode`/`Decode` impls needed by MDBX. +#[derive(Debug, Default, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, Hash)] +pub struct HashedAccountShardedKey(pub ShardedKey); + +impl HashedAccountShardedKey { + /// Create a new sharded key for a hashed account. + pub const fn new(key: B256, highest_block_number: u64) -> Self { + Self(ShardedKey::new(key, highest_block_number)) + } +} + +impl Encode for HashedAccountShardedKey { + type Encoded = [u8; 40]; // 32 (B256) + 8 (BlockNumber) + + fn encode(self) -> Self::Encoded { + let mut buf = [0u8; 40]; + buf[..32].copy_from_slice(self.0.key.as_slice()); + buf[32..].copy_from_slice(&self.0.highest_block_number.to_be_bytes()); + buf + } +} + +impl Decode for HashedAccountShardedKey { + fn decode(value: &[u8]) -> Result { + if value.len() != 40 { + return Err(DatabaseError::Decode); + } + let key = B256::from_slice(&value[..32]); + let highest_block_number = + u64::from_be_bytes(value[32..].try_into().map_err(|_| DatabaseError::Decode)?); + Ok(Self(ShardedKey::new(key, highest_block_number))) + } +} + +/// Sharded key for account trie history. +/// +/// Uses **length-prefixed encoding** to avoid sort ambiguity in MDBX: +/// +/// ```text +/// [nibble_count: 1 byte] ++ [raw nibble bytes] ++ [block_number: 8 BE bytes] +/// ``` +/// +/// See [`crate::db::models::value::StorageTrieShardedKey`] for the same rationale +/// applied to per-account storage tries. +#[derive(Debug, Default, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, Hash)] +pub struct AccountTrieShardedKey { + /// Trie path as nibbles. + pub key: StoredNibbles, + /// Highest block number in this shard (or `u64::MAX` for the sentinel). + pub highest_block_number: u64, +} + +impl AccountTrieShardedKey { + /// Create a new sharded key for an account trie path. + pub fn new(key: StoredNibbles, highest_block_number: u64) -> Self { + Self { key, highest_block_number } + } +} + +impl Encode for AccountTrieShardedKey { + type Encoded = Vec; + + fn encode(self) -> Self::Encoded { + let nibble_bytes: Vec = self.key.0.iter().collect(); + let nibble_count = nibble_bytes.len() as u8; + let mut buf = Vec::with_capacity(1 + nibble_bytes.len() + 8); + buf.push(nibble_count); + buf.extend_from_slice(&nibble_bytes); + buf.extend_from_slice(&self.highest_block_number.to_be_bytes()); + buf + } +} + +impl Decode for AccountTrieShardedKey { + fn decode(value: &[u8]) -> Result { + // Minimum: 1 (count) + 0 (nibbles) + 8 (block) = 9 + if value.len() < 9 { + return Err(DatabaseError::Decode); + } + let nibble_count = value[0] as usize; + let expected_len = 1 + nibble_count + 8; + if value.len() != expected_len { + return Err(DatabaseError::Decode); + } + let nibble_bytes = &value[1..1 + nibble_count]; + let key = StoredNibbles::from( + reth_trie_common::Nibbles::from_nibbles_unchecked(nibble_bytes), + ); + let block_bytes = &value[1 + nibble_count..]; + let highest_block_number = + u64::from_be_bytes(block_bytes.try_into().map_err(|_| DatabaseError::Decode)?); + Ok(Self { key, highest_block_number }) + } +} + +/// Account state before a block, keyed by hashed address. +/// +/// This is the hashed-address equivalent of reth's [`AccountBeforeTx`](reth_db_models::AccountBeforeTx), +/// designed for our v2 `AccountChangeSets` table where keys are `keccak256(address)`. +/// +/// Layout: `[hashed_address: 32 bytes][account: Compact-encoded or empty]` +/// +/// - The 32-byte hashed address acts as the [`DupSort::SubKey`]. +/// - An empty remainder means the account did not exist before this block (creation). +/// - A non-empty remainder is the [`Account`] state before the block was applied. +/// +/// [`DupSort::SubKey`]: reth_db::table::DupSort::SubKey +#[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct HashedAccountBeforeTx { + /// Hashed address (`keccak256(address)`). Acts as `DupSort::SubKey`. + pub hashed_address: B256, + /// Account state before the block. `None` means the account didn't exist. + pub info: Option, +} + +impl HashedAccountBeforeTx { + /// Creates a new instance. + pub const fn new(hashed_address: B256, info: Option) -> Self { + Self { hashed_address, info } + } +} + +impl ValueWithSubKey for HashedAccountBeforeTx { + type SubKey = B256; + + fn get_subkey(&self) -> Self::SubKey { + self.hashed_address + } +} + +impl Compress for HashedAccountBeforeTx { + type Compressed = Vec; + + fn compress_to_buf>(&self, buf: &mut B) { + // SubKey: raw 32 bytes (uncompressed so MDBX can seek by it) + buf.put_slice(self.hashed_address.as_slice()); + // Value: compress the account if present, otherwise write nothing + if let Some(account) = &self.info { + account.compress_to_buf(buf); + } + } +} + +impl Decompress for HashedAccountBeforeTx { + fn decompress(value: &[u8]) -> Result { + if value.len() < 32 { + return Err(DatabaseError::Decode); + } + + let hashed_address = B256::from_slice(&value[..32]); + let info = if value.len() > 32 { + Some(Account::decompress(&value[32..])?) + } else { + None + }; + + Ok(Self { hashed_address, info }) + } +} + +/// Trie changeset entry representing the state of a trie node before a block. +/// +/// `nibbles` is the subkey when used as a value in the changeset tables. +/// This is a local definition since the upstream `reth-trie-common` crate does +/// not provide this type. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct TrieChangeSetsEntry { + /// The nibbles of the intermediate node + pub nibbles: StoredNibblesSubKey, + /// Node value prior to the block being processed, None indicating it didn't exist. + pub node: Option, +} + +impl ValueWithSubKey for TrieChangeSetsEntry { + type SubKey = StoredNibblesSubKey; + + fn get_subkey(&self) -> Self::SubKey { + self.nibbles.clone() + } +} + +impl Compress for TrieChangeSetsEntry { + type Compressed = Vec; + + fn compress_to_buf>(&self, buf: &mut B) { + let _ = self.nibbles.to_compact(buf); + if let Some(ref node) = self.node { + let _ = node.to_compact(buf); + } + } +} + +impl Decompress for TrieChangeSetsEntry { + fn decompress(value: &[u8]) -> Result { + if value.is_empty() { + return Ok(Self { + nibbles: StoredNibblesSubKey::from(reth_trie_common::Nibbles::default()), + node: None, + }); + } + + let (nibbles, rest) = StoredNibblesSubKey::from_compact(value, 65); + let node = if rest.is_empty() { None } else { Some(BranchNodeCompact::from_compact(rest, rest.len()).0) }; + Ok(Self { nibbles, node }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use reth_db::table::{Compress, Decompress}; + + #[test] + fn test_hashed_account_before_tx_roundtrip_some() { + let original = HashedAccountBeforeTx { + hashed_address: B256::repeat_byte(0xaa), + info: Some(Account { + nonce: 42, + balance: alloy_primitives::U256::from(1000u64), + bytecode_hash: None, + }), + }; + + let compressed = original.clone().compress(); + assert!(compressed.len() > 32, "Should contain address + account data"); + + let decompressed = HashedAccountBeforeTx::decompress(&compressed).unwrap(); + assert_eq!(original, decompressed); + } + + #[test] + fn test_hashed_account_before_tx_roundtrip_none() { + let original = HashedAccountBeforeTx { + hashed_address: B256::repeat_byte(0xbb), + info: None, + }; + + let compressed = original.clone().compress(); + assert_eq!(compressed.len(), 32, "None account should be just the 32-byte address"); + + let decompressed = HashedAccountBeforeTx::decompress(&compressed).unwrap(); + assert_eq!(original, decompressed); + } + + #[test] + fn test_hashed_account_before_tx_subkey() { + let addr = B256::repeat_byte(0xcc); + let entry = HashedAccountBeforeTx::new(addr, None); + assert_eq!(entry.get_subkey(), addr); + } +} \ No newline at end of file diff --git a/rust/op-reth/crates/trie/src/db/models/kv.rs b/rust/op-reth/crates/trie/src/db/models/kv.rs index 5585336f4bb..e42bea5007b 100644 --- a/rust/op-reth/crates/trie/src/db/models/kv.rs +++ b/rust/op-reth/crates/trie/src/db/models/kv.rs @@ -1,3 +1,5 @@ +//! KV conversion helpers for trie history tables. + use crate::db::{ AccountTrieHistory, HashedAccountHistory, HashedStorageHistory, HashedStorageKey, MaybeDeleted, StorageTrieHistory, StorageTrieKey, StorageValue, VersionedValue, diff --git a/rust/op-reth/crates/trie/src/db/models/mod.rs b/rust/op-reth/crates/trie/src/db/models/mod.rs index b6d524528c6..2fcbc3e614d 100644 --- a/rust/op-reth/crates/trie/src/db/models/mod.rs +++ b/rust/op-reth/crates/trie/src/db/models/mod.rs @@ -12,18 +12,22 @@ pub use version::*; mod storage; pub use storage::*; mod change_set; -pub(crate) mod kv; pub use change_set::*; +pub mod kv; pub use kv::*; +mod key; +pub use key::*; +mod value; +pub use value::*; -use alloy_primitives::B256; +use alloy_primitives::{B256, BlockNumber}; use reth_db::{ TableSet, TableType, TableViewer, table::{DupSort, TableInfo}, - tables, + tables, BlockNumberList }; -use reth_primitives_traits::Account; -use reth_trie_common::{BranchNodeCompact, StoredNibbles}; +use reth_primitives_traits::{Account, StorageEntry}; +use reth_trie_common::{BranchNodeCompact, StoredNibbles, StoredNibblesSubKey, StorageTrieEntry}; use std::fmt; tables! { @@ -82,4 +86,149 @@ tables! { type Key = u64; // Block number type Value = ChangeSet; } + + // ==================== V2 Tables ==================== + // + // The v2 schema uses the same 3-table-per-data-type pattern: + // + // - **Current state** tables hold the latest values for fast reads. + // - **ChangeSet** tables group changes by block number for efficient pruning/unwinding. + // - **History** tables store sharded bitmaps for historical lookups. + // + + // -------------------- Hashed Accounts -------------------- + + /// Sharded history index for hashed accounts. + /// + /// Maps `ShardedKey` (hashed address + highest block number in shard) + /// to a bitmap of block numbers that modified this account. Used for historical + /// lookups: find the relevant block in the bitmap, then read the changeset. + table HashedAccountsHistory { + type Key = HashedAccountShardedKey; + type Value = BlockNumberList; + } + + /// Account changesets grouped by block number. + /// + /// Each entry stores the hashed address and the account state **before** the + /// block was applied (`None` if the account didn't exist). Grouped by block + /// number for efficient pruning (delete all entries for a block in one + /// operation) and unwinding (restore old values on reorg). + table HashedAccountChangeSets { + type Key = BlockNumber; + type Value = HashedAccountBeforeTx; + type SubKey = B256; + } + + /// Current state of all accounts, keyed by `keccak256(address)`. + /// + /// Holds the latest account data (nonce, balance, code hash, storage root). + /// Primary read target for state root computation and proof generation — + /// no version lookup needed. + table HashedAccounts { + type Key = B256; + type Value = Account; + } + + // -------------------- Hashed Storages -------------------- + + /// Sharded history index for storage slots. + /// + /// Composite key of `(hashed_address, hashed_storage_key, highest_block_number)`. + /// Maps to a bitmap of block numbers that modified this storage slot. + table HashedStoragesHistory { + type Key = HashedStorageShardedKey; + type Value = BlockNumberList; + } + + /// Storage changesets grouped by block number and account. + /// + /// Composite key of `(block_number, hashed_address)`. Each entry stores the + /// hashed storage key and value **before** the block was applied. + /// A value of [`U256::ZERO`](alloy_primitives::U256::ZERO) means the slot + /// did not exist (needs to be removed on unwind). + table HashedStorageChangeSets { + type Key = BlockNumberHashedAddress; + type Value = StorageEntry; + type SubKey = B256; + } + + /// Current storage values, keyed by hashed address with hashed storage key + /// as the `DupSort` subkey. + /// + /// Holds the latest storage slot values for each account. Primary read target + /// for storage proof generation. + table HashedStorages { + type Key = B256; + type Value = StorageEntry; + type SubKey = B256; + } + + // -------------------- Account Trie -------------------- + + /// Sharded history index for the account state trie. + /// + /// Maps `ShardedKey` (trie path + highest block number in shard) + /// to a bitmap of block numbers that modified this path. + table AccountsTrieHistory { + type Key = AccountTrieShardedKey; + type Value = BlockNumberList; + } + + /// Account trie changesets grouped by block number. + /// + /// Each entry stores the trie path and the branch node value **before** the + /// block was applied (`None` if the node didn't exist). Enables efficient + /// pruning and unwinding of trie state. + /// + /// NOTE: Named `AccountTrieChangeSets` (singular) to avoid collision with + /// upstream reth's `ORPHAN_TABLES` list which drops `AccountsTrieChangeSets`. + table AccountTrieChangeSets { + type Key = BlockNumber; + type Value = TrieChangeSetsEntry; + type SubKey = StoredNibblesSubKey; + } + + /// Current state of the account Merkle Patricia Trie. + /// + /// Maps trie paths to the latest branch node. Primary read target during + /// proof generation — no version lookup needed. + table AccountsTrie { + type Key = StoredNibbles; + type Value = BranchNodeCompact; + } + + // -------------------- Storage Trie -------------------- + + /// Sharded history index for per-account storage tries. + /// + /// Composite key of `(hashed_address, trie_path, highest_block_number)`. + /// Maps to a bitmap of block numbers that modified this storage trie node. + table StoragesTrieHistory { + type Key = StorageTrieShardedKey; + type Value = BlockNumberList; + } + + /// Storage trie changesets grouped by block number and account. + /// + /// Composite key of `(block_number, hashed_address)`. Each entry stores the + /// trie path and the branch node value **before** the block was applied. + /// + /// NOTE: Named `StorageTrieChangeSets` (singular) to avoid collision with + /// upstream reth's `ORPHAN_TABLES` list which drops `StoragesTrieChangeSets`. + table StorageTrieChangeSets { + type Key = BlockNumberHashedAddress; + type Value = TrieChangeSetsEntry; + type SubKey = StoredNibblesSubKey; + } + + /// Current state of each account's storage Merkle Patricia Trie. + /// + /// Keyed by hashed account address, with the trie path as the `DupSort` subkey. + /// Holds the latest branch node for each path in each account's storage trie. + table StoragesTrie { + type Key = B256; + type Value = StorageTrieEntry; + type SubKey = StoredNibblesSubKey; + } } diff --git a/rust/op-reth/crates/trie/src/db/models/value.rs b/rust/op-reth/crates/trie/src/db/models/value.rs new file mode 100644 index 00000000000..0c3b4d5864c --- /dev/null +++ b/rust/op-reth/crates/trie/src/db/models/value.rs @@ -0,0 +1,135 @@ +use alloy_primitives::{BlockNumber, B256}; +use reth_db::{ + models::sharded_key::ShardedKey, + table::{Decode, Encode}, + DatabaseError, +}; +use reth_trie_common::StoredNibbles; +use serde::{Deserialize, Serialize}; + +/// Keys Hashed Storage History by: Hashed Address + Sharded Key (Storage Key + Sharded Block). +#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)] +pub struct HashedStorageShardedKey { + /// The hashed address of the account owning the storage. + pub hashed_address: B256, + /// The sharded key combining the storage key and sharded block number. + pub sharded_key: ShardedKey, +} + +impl Encode for HashedStorageShardedKey { + type Encoded = Vec; + fn encode(self) -> Self::Encoded { + let mut buf = Vec::with_capacity(32 + 32 + 8); + buf.extend_from_slice(self.hashed_address.as_slice()); + // ShardedKey: Key (32 bytes) + BlockNumber (8 bytes BE) + buf.extend_from_slice(self.sharded_key.key.as_slice()); + buf.extend_from_slice(&self.sharded_key.highest_block_number.to_be_bytes()); + buf + } +} + +impl Decode for HashedStorageShardedKey { + fn decode(value: &[u8]) -> Result { + // 32 (Addr) + 32 (Key) + 8 (Block) = 72 bytes + if value.len() < 72 { + return Err(DatabaseError::Decode); + } + let (addr, rest) = value.split_at(32); + let hashed_address = B256::from_slice(addr); + let key = B256::from_slice(&rest[..32]); + let highest_block_number = + u64::from_be_bytes(rest[32..40].try_into().map_err(|_| DatabaseError::Decode)?); + Ok(Self { + hashed_address, + sharded_key: ShardedKey::new(key, highest_block_number), + }) + } +} + +/// Keys Storage `ChangeSets` by: Block Number + Hashed Address. +/// Replaces `BlockNumberAddress` which uses unhashed Address. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)] +pub struct BlockNumberHashedAddress(pub (BlockNumber, B256)); + +impl Encode for BlockNumberHashedAddress { + type Encoded = [u8; 40]; // 8 + 32 + fn encode(self) -> Self::Encoded { + let mut buf = [0u8; 40]; + buf[..8].copy_from_slice(&self.0 .0.to_be_bytes()); + buf[8..].copy_from_slice(self.0 .1.as_slice()); + buf + } +} + +impl Decode for BlockNumberHashedAddress { + fn decode(value: &[u8]) -> Result { + if value.len() < 40 { + return Err(DatabaseError::Decode); + } + let block_num = u64::from_be_bytes(value[..8].try_into().unwrap()); + let hash = B256::from_slice(&value[8..40]); + Ok(Self((block_num, hash))) + } +} + +/// Keys Storage Trie History by: Hashed Address + Nibbles + Sharded Block. +/// +/// Uses **length-prefixed encoding** for the nibble portion to avoid sort +/// ambiguity in MDBX (same rationale as [`AccountTrieShardedKey`](super::key::AccountTrieShardedKey)): +/// +/// ```text +/// [hashed_address: 32 bytes] ++ [nibble_count: 1 byte] ++ [nibble_bytes] ++ [block_number: 8 BE bytes] +/// ``` +#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize)] +pub struct StorageTrieShardedKey { + /// The hashed address of the account owning the storage trie. + pub hashed_address: B256, + /// The trie path (nibbles). + pub key: StoredNibbles, + /// Highest block number in this shard (or `u64::MAX` for the sentinel). + pub highest_block_number: u64, +} + +impl StorageTrieShardedKey { + /// Create a new storage trie sharded key. + pub fn new(hashed_address: B256, key: StoredNibbles, highest_block_number: u64) -> Self { + Self { hashed_address, key, highest_block_number } + } +} + +impl Encode for StorageTrieShardedKey { + type Encoded = Vec; + fn encode(self) -> Self::Encoded { + let nibble_bytes: Vec = self.key.0.iter().collect(); + let nibble_count = nibble_bytes.len() as u8; + let mut buf = Vec::with_capacity(32 + 1 + nibble_bytes.len() + 8); + buf.extend_from_slice(self.hashed_address.as_slice()); + buf.push(nibble_count); + buf.extend_from_slice(&nibble_bytes); + buf.extend_from_slice(&self.highest_block_number.to_be_bytes()); + buf + } +} + +impl Decode for StorageTrieShardedKey { + fn decode(value: &[u8]) -> Result { + // Minimum: 32 (addr) + 1 (count) + 0 (nibbles) + 8 (block) = 41 + if value.len() < 41 { + return Err(DatabaseError::Decode); + } + let hashed_address = B256::from_slice(&value[..32]); + let nibble_count = value[32] as usize; + let expected_len = 32 + 1 + nibble_count + 8; + if value.len() != expected_len { + return Err(DatabaseError::Decode); + } + let nibble_bytes = &value[33..33 + nibble_count]; + let key = StoredNibbles::from( + reth_trie_common::Nibbles::from_nibbles_unchecked(nibble_bytes), + ); + let block_bytes = &value[33 + nibble_count..]; + let highest_block_number = + u64::from_be_bytes(block_bytes.try_into().map_err(|_| DatabaseError::Decode)?); + Ok(Self { hashed_address, key, highest_block_number }) + } +} \ No newline at end of file diff --git a/rust/op-reth/crates/trie/src/db/store_v2.rs b/rust/op-reth/crates/trie/src/db/store_v2.rs new file mode 100644 index 00000000000..3138a567556 --- /dev/null +++ b/rust/op-reth/crates/trie/src/db/store_v2.rs @@ -0,0 +1,3810 @@ +//! V2 MDBX implementation of [`OpProofsStore`](crate::OpProofsStore). +//! +//! This module implements the v2 table schema using **3-table-per-data-type** pattern: +//! +//! | Domain | Current State | ChangeSet | History Bitmap | +//! |--------|--------------|-----------|----------------| +//! | Hashed Accounts | [`HashedAccounts`] | [`HashedAccountChangeSets`] | [`HashedAccountsHistory`] | +//! | Hashed Storages | [`HashedStorages`] | [`HashedStorageChangeSets`] | [`HashedStoragesHistory`] | +//! | Account Trie | [`AccountsTrie`] | [`AccountTrieChangeSets`] | [`AccountsTrieHistory`] | +//! | Storage Trie | [`StoragesTrie`] | [`StorageTrieChangeSets`] | [`StoragesTrieHistory`] | + +use super::{BlockNumberHash, ProofWindow, ProofWindowKey, Tables}; +use crate::{ + api::{ + InitialStateAnchor, InitialStateStatus, OpProofsInitProvider, OpProofsProviderRO, + OpProofsProviderRw, OpProofsStore, WriteCounts, + }, + db::{ + cursor_v2::{V2AccountCursor, V2AccountTrieCursor, V2StorageCursor, V2StorageTrieCursor}, + models::{ + AccountTrieShardedKey, AccountsTrie, AccountTrieChangeSets, + AccountsTrieHistory, BlockNumberHashedAddress, HashedAccountBeforeTx, + HashedAccountChangeSets, HashedAccountShardedKey, HashedAccounts, + HashedAccountsHistory, HashedStorageChangeSets, HashedStorageShardedKey, + HashedStorages, HashedStoragesHistory, StorageTrieShardedKey, StoragesTrie, + StorageTrieChangeSets, StoragesTrieHistory, TrieChangeSetsEntry, + }, + HashedStorageKey, StorageTrieKey, + }, + BlockStateDiff, OpProofsStorageError, OpProofsStorageResult, +}; +use alloy_eips::{eip1898::BlockWithParent, BlockNumHash, NumHash}; +use alloy_primitives::{BlockNumber, B256, U256}; +#[cfg(feature = "metrics")] +use metrics::{gauge, Label}; +use reth_db::{ + cursor::{DbCursorRO, DbCursorRW, DbDupCursorRO, DbDupCursorRW}, + models::sharded_key::ShardedKey, + mdbx::{init_db_for, DatabaseArguments}, + table::Table, + transaction::{DbTx, DbTxMut}, + BlockNumberList, Database, DatabaseEnv, DatabaseError, +}; +use reth_primitives_traits::{Account, StorageEntry}; +use reth_trie::{ + updates::{StorageTrieUpdates, TrieUpdates}, + BranchNodeCompact, HashedPostState, Nibbles, StorageTrieEntry, StoredNibbles, + StoredNibblesSubKey, +}; +use std::{collections::{BTreeMap, BTreeSet}, fmt::Debug, path::Path, sync::Arc}; + +/// Maximum number of block indices per shard in history bitmap tables. +const NUM_OF_INDICES_IN_SHARD: usize = 2_000; + +/// V2 MDBX implementation of [`OpProofsStore`]. +/// +/// Uses the v2 3-table-per-data-type schema. Each data domain (accounts, storages, +/// account trie, storage trie) has a current-state table, a changeset table, +/// and a sharded history bitmap table. +#[derive(Debug)] +pub struct MdbxProofsStorageV2 { + env: DatabaseEnv, +} + +impl MdbxProofsStorageV2 { + /// Creates a new [`MdbxProofsStorageV2`] instance with the given path. + pub fn new(path: &Path) -> Result { + let env = init_db_for::<_, Tables>(path, DatabaseArguments::default()) + .map_err(|e| DatabaseError::Other(format!("Failed to open database: {e}")))?; + Ok(Self { env }) + } +} + +impl OpProofsStore for MdbxProofsStorageV2 { + type ProviderRO<'a> = Arc::TX>>; + type ProviderRw<'a> = MdbxProofsProviderV2<::TXMut>; + type Initializer<'a> = MdbxProofsProviderV2<::TXMut>; + + fn provider_ro<'a>(&'a self) -> OpProofsStorageResult> { + Ok(Arc::new(MdbxProofsProviderV2::new(self.env.tx()?))) + } + + fn provider_rw<'a>(&'a self) -> OpProofsStorageResult> { + Ok(MdbxProofsProviderV2::new(self.env.tx_mut()?)) + } + + fn initialization_provider<'a>(&'a self) -> OpProofsStorageResult> { + Ok(MdbxProofsProviderV2::new(self.env.tx_mut()?)) + } +} + +/// [`DatabaseMetrics`](reth_db::database_metrics::DatabaseMetrics) implementation for +/// [`MdbxProofsStorageV2`]. Reports per-table size, page counts, and entry counts. +#[cfg(feature = "metrics")] +impl reth_db::database_metrics::DatabaseMetrics for MdbxProofsStorageV2 { + fn report_metrics(&self) { + for (name, value, labels) in self.gauge_metrics() { + gauge!(name, labels).set(value); + } + } + + fn gauge_metrics(&self) -> Vec<(&'static str, f64, Vec