diff --git a/Cargo.lock b/Cargo.lock index 4aa52b0626bc1..41a0e84d183ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -331,6 +331,33 @@ dependencies = [ "serde", ] +[[package]] +name = "alloy-op-evm" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "324cf0b3b08b4c3354460dee8208384a59245da8b0eaefe9e962d3942d919d58" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-evm", + "alloy-op-hardforks", + "alloy-primitives", + "auto_impl", + "op-alloy-consensus", + "op-revm", + "revm", +] + +[[package]] +name = "alloy-op-hardforks" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "217a4efe17c43df77c1f261825350be0be1d907f56eb38a4b258936e33cfd1d8" +dependencies = [ + "alloy-hardforks", + "auto_impl", +] + [[package]] name = "alloy-primitives" version = "0.8.25" @@ -998,6 +1025,7 @@ dependencies = [ "alloy-evm", "alloy-genesis", "alloy-network", + "alloy-op-evm", "alloy-primitives", "alloy-provider", "alloy-pubsub", @@ -4380,6 +4408,7 @@ dependencies = [ "foundry-test-utils", "futures", "itertools 0.14.0", + "op-revm", "parking_lot", "revm", "revm-inspectors", diff --git a/Cargo.toml b/Cargo.toml index e7e812ce5f347..d409dd175c888 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -245,6 +245,7 @@ alloy-sol-types = "0.8.22" alloy-chains = "0.1" alloy-evm = "0.3.2" +alloy-op-evm = "0.3.2" alloy-rlp = "0.3" alloy-trie = "0.7.0" diff --git a/crates/anvil/Cargo.toml b/crates/anvil/Cargo.toml index 06c29d5b922d8..babfb3c8d2499 100644 --- a/crates/anvil/Cargo.toml +++ b/crates/anvil/Cargo.toml @@ -31,6 +31,7 @@ foundry-evm-core.workspace = true # alloy alloy-evm.workspace = true +alloy-op-evm.workspace = true alloy-primitives = { workspace = true, features = ["serde"] } alloy-consensus = { workspace = true, features = ["k256", "kzg"] } alloy-contract = { workspace = true, features = ["pubsub"] } diff --git a/crates/anvil/core/src/eth/transaction/mod.rs b/crates/anvil/core/src/eth/transaction/mod.rs index b1551a6f08110..50263984d0924 100644 --- a/crates/anvil/core/src/eth/transaction/mod.rs +++ b/crates/anvil/core/src/eth/transaction/mod.rs @@ -23,6 +23,7 @@ use alloy_serde::{OtherFields, WithOtherFields}; use bytes::BufMut; use foundry_evm::traces::CallTraceNode; use op_alloy_consensus::{TxDeposit, DEPOSIT_TX_TYPE_ID}; +use op_revm::{transaction::deposit::DepositTransactionParts, OpTransaction}; use revm::{context::TxEnv, interpreter::InstructionResult}; use serde::{Deserialize, Serialize}; use std::ops::{Deref, Mul}; @@ -381,7 +382,9 @@ impl PendingTransaction { /// Converts the [PendingTransaction] into the [TxEnv] context that [`revm`](foundry_evm) /// expects. - pub fn to_revm_tx_env(&self) -> TxEnv { + /// + /// Base [`TxEnv`] is encapsulated in the [`OpTransaction`] + pub fn to_revm_tx_env(&self) -> OpTransaction { fn transact_to(kind: &TxKind) -> TxKind { match kind { TxKind::Call(c) => TxKind::Call(*c), @@ -394,7 +397,7 @@ impl PendingTransaction { TypedTransaction::Legacy(tx) => { let chain_id = tx.tx().chain_id; let TxLegacy { nonce, gas_price, gas_limit, value, to, input, .. } = tx.tx(); - TxEnv { + OpTransaction::new(TxEnv { caller, kind: transact_to(to), data: input.clone(), @@ -405,8 +408,9 @@ impl PendingTransaction { gas_priority_fee: None, gas_limit: *gas_limit, access_list: vec![].into(), + tx_type: 0, ..Default::default() - } + }) } TypedTransaction::EIP2930(tx) => { let TxEip2930 { @@ -420,7 +424,7 @@ impl PendingTransaction { access_list, .. } = tx.tx(); - TxEnv { + OpTransaction::new(TxEnv { caller, kind: transact_to(to), data: input.clone(), @@ -431,8 +435,9 @@ impl PendingTransaction { gas_priority_fee: None, gas_limit: *gas_limit, access_list: access_list.clone().into(), + tx_type: 1, ..Default::default() - } + }) } TypedTransaction::EIP1559(tx) => { let TxEip1559 { @@ -447,7 +452,7 @@ impl PendingTransaction { access_list, .. } = tx.tx(); - TxEnv { + OpTransaction::new(TxEnv { caller, kind: transact_to(to), data: input.clone(), @@ -458,8 +463,9 @@ impl PendingTransaction { gas_priority_fee: Some(*max_priority_fee_per_gas), gas_limit: *gas_limit, access_list: access_list.clone().into(), + tx_type: 2, ..Default::default() - } + }) } TypedTransaction::EIP4844(tx) => { let TxEip4844 { @@ -476,7 +482,7 @@ impl PendingTransaction { blob_versioned_hashes, .. } = tx.tx().tx(); - TxEnv { + OpTransaction::new(TxEnv { caller, kind: TxKind::Call(*to), data: input.clone(), @@ -489,8 +495,9 @@ impl PendingTransaction { blob_hashes: blob_versioned_hashes.clone(), gas_limit: *gas_limit, access_list: access_list.clone().into(), + tx_type: 3, ..Default::default() - } + }) } TypedTransaction::EIP7702(tx) => { let TxEip7702 { @@ -505,7 +512,7 @@ impl PendingTransaction { authorization_list, input, } = tx.tx(); - TxEnv { + OpTransaction::new(TxEnv { caller, kind: TxKind::Call(*to), data: input.clone(), @@ -517,8 +524,9 @@ impl PendingTransaction { gas_limit: *gas_limit, access_list: access_list.clone().into(), authorization_list: authorization_list.clone(), + tx_type: 4, ..Default::default() - } + }) } TypedTransaction::Deposit(tx) => { let chain_id = tx.chain_id(); @@ -533,7 +541,7 @@ impl PendingTransaction { is_system_tx, .. } = tx; - TxEnv { + let base = TxEnv { caller, kind: transact_to(kind), data: input.clone(), @@ -544,15 +552,19 @@ impl PendingTransaction { gas_priority_fee: None, gas_limit: { *gas_limit }, access_list: vec![].into(), - // TODO: add Optimism support - // optimism: OptimismFields { - // source_hash: Some(*source_hash), - // mint: Some(mint.to::()), - // is_system_transaction: Some(*is_system_tx), - // enveloped_tx: None, - // }, + tx_type: DEPOSIT_TX_TYPE_ID, ..Default::default() - } + }; + + let deposit = DepositTransactionParts { + source_hash: *source_hash, + mint: Some(mint.to::()), + is_system_transaction: *is_system_tx, + }; + + let mut encoded = Vec::new(); + tx.encode_2718(&mut encoded); + OpTransaction { base, deposit, enveloped_tx: Some(encoded.into()) } } } } diff --git a/crates/anvil/src/cmd.rs b/crates/anvil/src/cmd.rs index 4f71060042780..6c67829d2fffe 100644 --- a/crates/anvil/src/cmd.rs +++ b/crates/anvil/src/cmd.rs @@ -1,7 +1,7 @@ use crate::{ config::{ForkChoice, DEFAULT_MNEMONIC}, eth::{backend::db::SerializableState, pool::transactions::TransactionOrder, EthApi}, - AccountGenerator, EthereumHardfork, NodeConfig, CHAIN_ID, + AccountGenerator, EthereumHardfork, NodeConfig, OptimismHardfork, CHAIN_ID, }; use alloy_genesis::Genesis; use alloy_primitives::{utils::Unit, B256, U256}; @@ -217,11 +217,11 @@ impl NodeArgs { let hardfork = match &self.hardfork { Some(hf) => { - // if self.evm.optimism { - // Some(OptimismHardfork::from_str(hf)?.into()) - // } else { - Some(EthereumHardfork::from_str(hf)?.into()) - // } + if self.evm.optimism { + Some(OptimismHardfork::from_str(hf)?.into()) + } else { + Some(EthereumHardfork::from_str(hf)?.into()) + } } None => None, }; @@ -791,9 +791,8 @@ fn duration_from_secs_f64(s: &str) -> Result { #[cfg(test)] mod tests { - use crate::EthereumHardfork; - use super::*; + use crate::{EthereumHardfork, OptimismHardfork}; use std::{env, net::Ipv4Addr}; #[test] @@ -833,13 +832,13 @@ mod tests { assert_eq!(config.hardfork, Some(EthereumHardfork::Berlin.into())); } - // #[test] - // fn can_parse_optimism_hardfork() { - // let args: NodeArgs = - // NodeArgs::parse_from(["anvil", "--optimism", "--hardfork", "Regolith"]); - // let config = args.into_node_config().unwrap(); - // assert_eq!(config.hardfork, Some(OptimismHardfork::Regolith.into())); - // } + #[test] + fn can_parse_optimism_hardfork() { + let args: NodeArgs = + NodeArgs::parse_from(["anvil", "--optimism", "--hardfork", "Regolith"]); + let config = args.into_node_config().unwrap(); + assert_eq!(config.hardfork, Some(OptimismHardfork::Regolith.into())); + } #[test] fn cant_parse_invalid_hardfork() { diff --git a/crates/anvil/src/config.rs b/crates/anvil/src/config.rs index 0879e04e33e12..ca20622910b07 100644 --- a/crates/anvil/src/config.rs +++ b/crates/anvil/src/config.rs @@ -12,7 +12,7 @@ use crate::{ }, hardfork::ChainHardfork, mem::{self, in_memory_db::MemDb}, - EthereumHardfork, FeeManager, PrecompileFactory, + EthereumHardfork, FeeManager, OptimismHardfork, PrecompileFactory, }; use alloy_consensus::BlockHeader; use alloy_genesis::Genesis; @@ -527,9 +527,9 @@ impl NodeConfig { if let Some(hardfork) = self.hardfork { return hardfork; } - // if self.enable_optimism { - // return OptimismHardfork::default().into(); - // } + if self.enable_optimism { + return OptimismHardfork::default().into(); + } EthereumHardfork::default().into() } @@ -1048,6 +1048,8 @@ impl NodeConfig { TxEnv { chain_id: Some(self.get_chain_id()), ..Default::default() }, ); + env.is_optimism = self.enable_optimism; + let fees = FeeManager::new( spec_id, self.get_base_fee(), diff --git a/crates/anvil/src/eth/backend/evm.rs b/crates/anvil/src/eth/backend/evm.rs new file mode 100644 index 0000000000000..4ef9f8ad2ef5f --- /dev/null +++ b/crates/anvil/src/eth/backend/evm.rs @@ -0,0 +1,281 @@ +use crate::eth::error::BlockchainError; +use alloy_evm::{eth::EthEvmContext, Database, EthEvm, Evm, EvmEnv}; +use alloy_op_evm::OpEvm; +use alloy_primitives::{Address, Bytes}; +use op_revm::{ + transaction::deposit::DepositTransactionParts, OpContext, OpHaltReason, OpTransaction, + OpTransactionError, +}; +use revm::{ + context::{ + result::{EVMError, ExecutionResult, HaltReason, ResultAndState}, + BlockEnv, TxEnv, + }, + handler::PrecompileProvider, + interpreter::InterpreterResult, + primitives::hardfork::SpecId, + DatabaseCommit, Inspector, +}; + +type AnvilEvmResult = + Result, EVMError>; + +type AnvilExecResult = + Result, EVMError>; +pub enum EitherEvm +where + DB: Database, +{ + Eth(EthEvm), + Op(OpEvm), +} + +impl EitherEvm +where + DB: Database, + I: Inspector> + Inspector>, + P: PrecompileProvider, Output = InterpreterResult> + + PrecompileProvider, Output = InterpreterResult>, +{ + pub fn transact_deposit( + &mut self, + tx: TxEnv, + deposit: DepositTransactionParts, + ) -> AnvilEvmResult { + match self { + EitherEvm::Eth(_) => { + return Err(EVMError::Custom( + BlockchainError::DepositTransactionUnsupported.to_string(), + )); + } + EitherEvm::Op(evm) => { + let op_tx = OpTransaction { base: tx, deposit, enveloped_tx: None }; + evm.transact_raw(op_tx) + } + } + } + + pub fn transact_deposit_commit( + &mut self, + tx: TxEnv, + deposit: DepositTransactionParts, + ) -> AnvilExecResult + where + DB: DatabaseCommit, + { + match self { + EitherEvm::Eth(_) => { + return Err(EVMError::Custom( + BlockchainError::DepositTransactionUnsupported.to_string(), + )); + } + EitherEvm::Op(evm) => { + let op_tx = OpTransaction { base: tx, deposit, enveloped_tx: None }; + evm.transact_commit(op_tx) + } + } + } + + fn map_eth_result( + &self, + result: Result, EVMError>, + ) -> AnvilEvmResult { + match result { + Ok(result) => { + // Map the halt reason + Ok(result.map_haltreason(|hr| OpHaltReason::Base(hr))) + } + Err(e) => { + // Map the TransactionError + match e { + EVMError::Transaction(invalid_tx) => { + Err(EVMError::Transaction(OpTransactionError::Base(invalid_tx))) + } + EVMError::Database(e) => Err(EVMError::Database(e)), + EVMError::Header(e) => Err(EVMError::Header(e)), + EVMError::Custom(e) => Err(EVMError::Custom(e)), + } + } + } + } + + fn map_exec_result( + &self, + result: Result>, + ) -> AnvilExecResult { + match result { + Ok(result) => { + // Map the halt reason + Ok(result.map_haltreason(|hr| OpHaltReason::Base(hr))) + } + Err(e) => { + // Map the TransactionError + match e { + EVMError::Transaction(invalid_tx) => { + Err(EVMError::Transaction(OpTransactionError::Base(invalid_tx))) + } + EVMError::Database(e) => Err(EVMError::Database(e)), + EVMError::Header(e) => Err(EVMError::Header(e)), + EVMError::Custom(e) => Err(EVMError::Custom(e)), + } + } + } + } +} + +impl Evm for EitherEvm +where + DB: Database, + I: Inspector> + Inspector>, + P: PrecompileProvider, Output = InterpreterResult> + + PrecompileProvider, Output = InterpreterResult>, +{ + type DB = DB; + type Error = EVMError; + type HaltReason = OpHaltReason; + type Tx = TxEnv; + type Spec = SpecId; + + fn block(&self) -> &BlockEnv { + match self { + EitherEvm::Eth(evm) => evm.block(), + EitherEvm::Op(evm) => evm.block(), + } + } + + fn db_mut(&mut self) -> &mut Self::DB { + match self { + EitherEvm::Eth(evm) => evm.db_mut(), + EitherEvm::Op(evm) => evm.db_mut(), + } + } + + fn into_db(self) -> Self::DB + where + Self: Sized, + { + match self { + EitherEvm::Eth(evm) => evm.into_db(), + EitherEvm::Op(evm) => evm.into_db(), + } + } + + fn finish(self) -> (Self::DB, EvmEnv) + where + Self: Sized, + { + match self { + EitherEvm::Eth(evm) => evm.finish(), + EitherEvm::Op(evm) => { + let (db, env) = evm.finish(); + // Convert the OpSpecId to EthSpecId + let eth_spec_id = env.spec_id().into_eth_spec(); + let cfg = env.cfg_env.with_spec(eth_spec_id); + + (db, EvmEnv { cfg_env: cfg, block_env: env.block_env }) + } + } + } + + fn enable_inspector(&mut self) { + match self { + EitherEvm::Eth(evm) => evm.enable_inspector(), + EitherEvm::Op(evm) => evm.enable_inspector(), + } + } + + fn disable_inspector(&mut self) { + match self { + EitherEvm::Eth(evm) => evm.disable_inspector(), + EitherEvm::Op(evm) => evm.disable_inspector(), + } + } + + fn set_inspector_enabled(&mut self, enabled: bool) { + match self { + EitherEvm::Eth(evm) => evm.set_inspector_enabled(enabled), + EitherEvm::Op(evm) => evm.set_inspector_enabled(enabled), + } + } + + fn into_env(self) -> EvmEnv + where + Self: Sized, + { + match self { + EitherEvm::Eth(evm) => evm.into_env(), + EitherEvm::Op(evm) => { + let env = evm.into_env(); + let eth_spec_id = env.spec_id().into_eth_spec(); + let cfg = env.cfg_env.with_spec(eth_spec_id); + EvmEnv { cfg_env: cfg, block_env: env.block_env } + } + } + } + + fn transact( + &mut self, + tx: impl alloy_evm::IntoTxEnv, + ) -> Result, Self::Error> { + match self { + EitherEvm::Eth(evm) => { + let eth = evm.transact(tx); + self.map_eth_result(eth) + } + EitherEvm::Op(evm) => { + let op_tx = OpTransaction::new(tx.into_tx_env()); + evm.transact(op_tx) + } + } + } + + fn transact_commit( + &mut self, + tx: impl alloy_evm::IntoTxEnv, + ) -> Result, Self::Error> + where + Self::DB: DatabaseCommit, + { + match self { + EitherEvm::Eth(evm) => { + let eth = evm.transact_commit(tx); + self.map_exec_result(eth) + } + EitherEvm::Op(evm) => { + let op_tx = OpTransaction::new(tx.into_tx_env()); + evm.transact_commit(op_tx) + } + } + } + + fn transact_raw( + &mut self, + tx: Self::Tx, + ) -> Result, Self::Error> { + match self { + Self::Eth(evm) => { + let res = evm.transact_raw(tx); + self.map_eth_result(res) + } + Self::Op(evm) => { + let op_tx = OpTransaction::new(tx); + evm.transact_raw(op_tx) + } + } + } + + fn transact_system_call( + &mut self, + caller: Address, + contract: Address, + data: Bytes, + ) -> Result, Self::Error> { + match self { + EitherEvm::Eth(evm) => { + let eth = evm.transact_system_call(caller, contract, data); + self.map_eth_result(eth) + } + EitherEvm::Op(evm) => evm.transact_system_call(caller, contract, data), + } + } +} diff --git a/crates/anvil/src/eth/backend/executor.rs b/crates/anvil/src/eth/backend/executor.rs index 9fc9d2fd63674..3035885a4438b 100644 --- a/crates/anvil/src/eth/backend/executor.rs +++ b/crates/anvil/src/eth/backend/executor.rs @@ -1,6 +1,9 @@ +use crate::eth::backend::evm::EitherEvm; use crate::{ eth::{ - backend::{db::Db, validate::TransactionValidator}, + backend::{ + db::Db, mem::op_haltreason_to_instruction_result, validate::TransactionValidator, + }, error::InvalidTransactionError, pool::transactions::PoolTransaction, }, @@ -10,7 +13,8 @@ use crate::{ }; use alloy_consensus::{constants::EMPTY_WITHDRAWALS, Receipt, ReceiptWithBloom}; use alloy_eips::{eip2718::Encodable2718, eip7685::EMPTY_REQUESTS_HASH}; -use alloy_evm::eth::EthEvmContext; +use alloy_evm::{eth::EthEvmContext, EthEvm, Evm}; +use alloy_op_evm::OpEvm; use alloy_primitives::{Bloom, BloomInput, Log, B256}; use anvil_core::eth::{ block::{Block, BlockInfo, PartialHeader}, @@ -21,15 +25,18 @@ use anvil_core::eth::{ }; use foundry_evm::{backend::DatabaseError, traces::CallTraceNode, Env}; use foundry_evm_core::evm::FoundryPrecompiles; +use op_revm::{ + transaction::deposit::DEPOSIT_TRANSACTION_TYPE, L1BlockInfo, OpContext, OpTransaction, + OpTransactionError, +}; use revm::{ - context::{Block as RevmBlock, BlockEnv, CfgEnv, Evm, JournalTr}, + context::{Block as RevmBlock, BlockEnv, CfgEnv, Evm as RevmEvm, JournalTr}, context_interface::result::{EVMError, ExecutionResult, Output}, database::WrapDatabaseRef, handler::instructions::EthInstructions, - interpreter::{interpreter::EthInterpreter, InstructionResult}, + interpreter::InstructionResult, primitives::hardfork::SpecId, - Database, DatabaseRef, ExecuteCommitEvm, ExecuteEvm, InspectCommitEvm, InspectEvm, Inspector, - Journal, MainBuilder, + Database, DatabaseRef, Inspector, Journal, }; use std::sync::Arc; @@ -242,15 +249,14 @@ impl TransactionExecutor<'_, DB, V> { } fn env_for(&self, tx: &PendingTransaction) -> Env { - let mut tx_env = tx.to_revm_tx_env(); + let op_tx_env = tx.to_revm_tx_env(); - // TODO: support optimism - // if self.cfg_env.handler_cfg.is_optimism { - // tx_env.optimism.enveloped_tx = - // Some(alloy_rlp::encode(&tx.transaction.transaction).into()); - // } + let mut env = Env::from(self.cfg_env.clone(), self.block_env.clone(), op_tx_env.base); + if env.tx.tx_type == DEPOSIT_TRANSACTION_TYPE { + env = env.with_deposit(op_tx_env.deposit); + } - Env::from(self.cfg_env.clone(), self.block_env.clone(), tx_env) + env } } @@ -319,11 +325,21 @@ impl Iterator for &mut TransactionExec inspector = inspector.with_trace_printer(); } - let mut evm = new_evm_with_inspector(&mut *self.db, &env, &mut inspector); - // Set the tx - evm.set_tx(env.tx); + let mut evm = evm_with_inspector( + &mut *self.db, + &env, + &mut inspector, + transaction.tx_type() == DEPOSIT_TRANSACTION_TYPE, + ); + + let tx_commit = if transaction.tx_type() == DEPOSIT_TRANSACTION_TYPE { + // Unwrap is safe. This should always be set if the transaction is a deposit + evm.transact_deposit_commit(env.tx, env.deposit.unwrap()) + } else { + evm.transact_commit(env.tx) + }; trace!(target: "backend", "[{:?}] executing", transaction.hash()); - let exec_result = match evm.inspect_replay_commit() { + let exec_result = match tx_commit { Ok(exec_result) => exec_result, Err(err) => { warn!(target: "backend", "[{:?}] failed to execute: {:?}", transaction.hash(), err); @@ -332,7 +348,14 @@ impl Iterator for &mut TransactionExec return Some(TransactionExecutionOutcome::DatabaseError(transaction, err)) } EVMError::Transaction(err) => { - return Some(TransactionExecutionOutcome::Invalid(transaction, err.into())) + let err = match err { + OpTransactionError::Base(err) => err.into(), + OpTransactionError::HaltedDepositPostRegolith | + OpTransactionError::DepositSystemTxPostRegolith => { + InvalidTransactionError::DepositTxErrorPostRegolith + } + }; + return Some(TransactionExecutionOutcome::Invalid(transaction, err)) } e => panic!("failed to execute transaction: {e}"), } @@ -351,7 +374,9 @@ impl Iterator for &mut TransactionExec ExecutionResult::Revert { gas_used, output } => { (InstructionResult::Revert, gas_used, Some(Output::Call(output)), None) } - ExecutionResult::Halt { reason, gas_used } => (reason.into(), gas_used, None, None), + ExecutionResult::Halt { reason, gas_used } => { + (op_haltreason_to_instruction_result(reason), gas_used, None, None) + } }; if exit_reason == InstructionResult::OutOfGas { @@ -395,71 +420,81 @@ fn build_logs_bloom(logs: Vec, bloom: &mut Bloom) { } } -pub type AnvilEvmContext<'db, DB> = EthEvmContext; - -pub type AnvilEvm<'db, DB, I, P = FoundryPrecompiles> = - Evm, I, EthInstructions>, P>; - /// Creates a database with given database and inspector, optionally enabling odyssey features. -pub fn new_evm_with_inspector<'db, CTX, DB>( +pub fn evm_with_inspector( db: DB, env: &Env, - inspector: &'db mut dyn Inspector, -) -> AnvilEvm<'db, DB, &'db mut dyn Inspector> + inspector: I, + is_optimism: bool, +) -> EitherEvm where DB: Database, + I: Inspector> + Inspector>, { - let evm_context = AnvilEvmContext { - journaled_state: { - let mut journal = Journal::new(db); - journal.set_spec_id(env.evm_env.cfg_env.spec); - journal - }, - block: env.evm_env.block_env.clone(), - cfg: env.evm_env.cfg_env.clone(), - tx: env.tx.clone(), - chain: (), - error: Ok(()), - }; - - let evm = Evm::new_with_inspector( - evm_context, - inspector, - EthInstructions::default(), - FoundryPrecompiles::new(), - ); - - evm -} + if is_optimism { + let op_context = OpContext { + journaled_state: { + let mut journal = Journal::new(db); + // Converting SpecId into OpSpecId + journal.set_spec_id(env.evm_env.cfg_env.spec.into()); + journal + }, + block: env.evm_env.block_env.clone(), + cfg: env.evm_env.cfg_env.clone().with_spec(op_revm::OpSpecId::BEDROCK), + tx: OpTransaction::new(env.tx.clone()), + chain: L1BlockInfo::default(), + error: Ok(()), + }; -/// Creates a new [`AnvilEvmContext`] with the given database and environment. -pub fn new_evm_context(db: DB, env: &Env) -> AnvilEvmContext<'_, DB> -where - DB: Database, -{ - AnvilEvmContext { - journaled_state: { - let mut journal = Journal::new(db); - journal.set_spec_id(env.evm_env.cfg_env.spec); - journal - }, - block: env.evm_env.block_env.clone(), - cfg: env.evm_env.cfg_env.clone(), - tx: env.tx.clone(), - chain: (), - error: Ok(()), + let evm = op_revm::OpEvm(RevmEvm::new_with_inspector( + op_context, + inspector, + EthInstructions::default(), + FoundryPrecompiles::default(), + )); + + let op = OpEvm::new(evm, true); + + EitherEvm::Op(op) + } else { + let evm_context = EthEvmContext { + journaled_state: { + let mut journal = Journal::new(db); + journal.set_spec_id(env.evm_env.cfg_env.spec); + journal + }, + block: env.evm_env.block_env.clone(), + cfg: env.evm_env.cfg_env.clone(), + tx: env.tx.clone(), + chain: (), + error: Ok(()), + }; + + let evm = RevmEvm::new_with_inspector( + evm_context, + inspector, + EthInstructions::default(), + FoundryPrecompiles::new(), + ); + + let eth = EthEvm::new(evm, true); + + EitherEvm::Eth(eth) } } /// Creates a new EVM with the given inspector and wraps the database in a `WrapDatabaseRef`. -pub fn new_evm_with_inspector_ref<'db, CTX, DB>( +pub fn evm_with_inspector_ref<'db, DB, I>( db: &'db DB, env: &Env, - inspector: &'db mut dyn Inspector, -) -> AnvilEvm<'db, WrapDatabaseRef<&'db DB>, &'db mut dyn Inspector> + inspector: &'db mut I, + is_optimism: bool, +) -> EitherEvm, &'db mut I, FoundryPrecompiles> where DB: DatabaseRef + 'db + ?Sized, + I: Inspector>> + + Inspector>>, WrapDatabaseRef<&'db DB>: Database, { - new_evm_with_inspector(WrapDatabaseRef(db), env, inspector) + evm_with_inspector(WrapDatabaseRef(db), env, inspector, is_optimism) } diff --git a/crates/anvil/src/eth/backend/mem/inspector.rs b/crates/anvil/src/eth/backend/mem/inspector.rs index f2f0d032eb5d8..26f1998f5f9ad 100644 --- a/crates/anvil/src/eth/backend/mem/inspector.rs +++ b/crates/anvil/src/eth/backend/mem/inspector.rs @@ -1,6 +1,7 @@ //! Anvil specific [`revm::Inspector`] implementation -use crate::foundry_common::ErrorExt; +use crate::{eth::macros::node_info, foundry_common::ErrorExt}; +use alloy_evm::eth::EthEvmContext; use alloy_primitives::{Address, Log, U256}; use alloy_sol_types::SolInterface; use foundry_evm::{ @@ -24,8 +25,6 @@ use revm::{ Database, Inspector, }; -use crate::eth::{backend::executor::AnvilEvmContext, macros::node_info}; - /// The [`revm::Inspector`] used when transacting in the evm #[derive(Clone, Debug, Default)] pub struct AnvilInspector { @@ -196,7 +195,7 @@ where #[inline] fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) { if let Some(tracer) = &mut self.tracer { - Inspector::>::selfdestruct(tracer, contract, target, value); + Inspector::>::selfdestruct(tracer, contract, target, value); } } } diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index 8a898f30809d7..71c94d4e909db 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -1,7 +1,7 @@ //! In-memory blockchain backend. use self::state::trie_storage; -use super::executor::{new_evm_with_inspector_ref, AnvilEvm}; +use super::{evm::EitherEvm, executor::evm_with_inspector_ref}; use crate::{ config::PruneStateHistoryConfig, eth::{ @@ -40,7 +40,7 @@ use alloy_consensus::{ transaction::Recovered, Account, BlockHeader, EnvKzgSettings, Header, Receipt, ReceiptWithBloom, Signed, Transaction as TransactionTrait, TxEnvelope }; use alloy_eips::{eip1559::BaseFeeParams, eip4844::MAX_BLOBS_PER_BLOCK}; -use alloy_evm::Database; +use alloy_evm::{eth::EthEvmContext, Database, Evm}; use alloy_network::{ AnyHeader, AnyRpcBlock, AnyRpcHeader, AnyRpcTransaction, AnyTxEnvelope, AnyTxType, EthereumWallet, UnknownTxEnvelope, UnknownTypedTransaction, @@ -92,11 +92,13 @@ use foundry_evm::{ traces::TracingInspectorConfig, Env, }; +use foundry_evm_core::evm::FoundryPrecompiles; use futures::channel::mpsc::{unbounded, UnboundedSender}; use op_alloy_consensus::{TxDeposit, DEPOSIT_TX_TYPE_ID}; +use op_revm::{OpContext, OpHaltReason}; use parking_lot::{Mutex, RwLock}; use revm::{ - context::{Block as RevmBlock, BlockEnv, ContextTr, TxEnv}, context_interface::{ + context::{result::HaltReason, Block as RevmBlock, BlockEnv, ContextTr, TxEnv}, context_interface::{ block::BlobExcessGasAndPrice, result::{ExecutionResult, Output, ResultAndState}, }, database::{CacheDB, DatabaseRef, WrapDatabaseRef}, interpreter::InstructionResult, primitives::{hardfork::SpecId, KECCAK_EMPTY}, state::AccountInfo, DatabaseCommit, ExecuteEvm, InspectEvm, Inspector @@ -747,11 +749,9 @@ impl Backend { (self.spec_id() as u8) >= (SpecId::PRAGUE as u8) } - // TODO: support Optimism /// Returns true if op-stack deposits are active pub fn is_optimism(&self) -> bool { - // self.env.read().handler_cfg.is_optimism - false + self.env.read().is_optimism } /// Returns an error if EIP1559 is not active (pre Berlin) @@ -1039,24 +1039,25 @@ impl Backend { /// Creates an EVM instance with optionally injected precompiles. #[expect(clippy::type_complexity)] - fn new_evm_with_inspector_ref<'db, CTX>( + fn new_evm_with_inspector_ref<'db, I>( &self, db: &'db dyn DatabaseRef, env: &Env, - inspector: &'db mut dyn Inspector, - ) -> AnvilEvm< - 'db, + inspector: &'db mut I, + ) -> EitherEvm< WrapDatabaseRef<&'db dyn DatabaseRef>, - &'db mut dyn Inspector, + &'db mut I, + FoundryPrecompiles, > where - CTX: ContextTr>>, + I: Inspector>>> + Inspector>>>, + WrapDatabaseRef<&'db dyn DatabaseRef>: Database, { - let mut evm = new_evm_with_inspector_ref(db, env, inspector); + evm_with_inspector_ref(db, env, inspector, self.is_optimism()) + // TODO(yash): inject precompiles // if let Some(factory) = &self.precompile_factory { // inject_precompiles(&mut evm, factory.precompiles()); // } - evm } /// executes the transactions without writing to the underlying database @@ -1068,18 +1069,17 @@ impl Backend { BlockchainError, > { let mut env = self.next_env(); - env.tx = tx.pending_transaction.to_revm_tx_env(); - - // TODO: support Optimism - // if env.handler_cfg.is_optimism { - // env.tx.optimism.enveloped_tx = - // Some(alloy_rlp::encode(&tx.pending_transaction.transaction.transaction).into()); - // } + let op_tx_env = tx.pending_transaction.to_revm_tx_env(); + env.tx = op_tx_env.base; let db = self.db.read().await; let mut inspector = self.build_inspector(); let mut evm = self.new_evm_with_inspector_ref(db.as_dyn(), &env, &mut inspector); - let ResultAndState { result, state } = evm.inspect_with_tx(env.tx)?; + let ResultAndState { result, state } = if env.tx.tx_type == DEPOSIT_TX_TYPE_ID { + evm.transact_deposit(env.tx, op_tx_env.deposit)? + } else { + evm.transact(env.tx)? + }; let (exit_reason, gas_used, out, logs) = match result { ExecutionResult::Success { reason, gas_used, logs, output, .. } => { (reason.into(), gas_used, Some(output), Some(logs)) @@ -1087,7 +1087,10 @@ impl Backend { ExecutionResult::Revert { gas_used, output } => { (InstructionResult::Revert, gas_used, Some(Output::Call(output)), None) } - ExecutionResult::Halt { reason, gas_used } => (reason.into(), gas_used, None, None), + ExecutionResult::Halt { reason, gas_used } => { + let eth_reason = op_haltreason_to_instruction_result(reason); + (eth_reason, gas_used, None, None) + }, }; drop(evm); @@ -1564,19 +1567,7 @@ impl Backend { // prepare inspector to capture transfer inside the evm so they are // recorded and included in logs let mut inspector = TransferInspector::new(false).with_logs(true); - let mut evm: revm::context::Evm< - _, - &mut dyn Inspector< - revm::Context< - BlockEnv, - TxEnv, - revm::context::CfgEnv, - WrapDatabaseRef<&(dyn DatabaseRef)>, // Cannot infer DB in the Inspector. - >, - >, - _, - _, - > = self.new_evm_with_inspector_ref( + let mut evm= self.new_evm_with_inspector_ref( cache_db.as_dyn(), &env, &mut inspector, @@ -1584,7 +1575,7 @@ impl Backend { trace!(target: "backend", env=?env.evm_env, spec=?env.evm_env.spec_id(),"simulate evm env"); - evm.inspect_with_tx(env.tx)? + evm.transact(env.tx)? } else { let mut inspector = self.build_inspector(); let mut evm = self.new_evm_with_inspector_ref( @@ -1594,7 +1585,7 @@ impl Backend { ); trace!(target: "backend", env=?env.evm_env, spec=?env.evm_env.spec_id(),"simulate evm env"); - evm.inspect_with_tx(env.tx)? + evm.transact(env.tx)? }; trace!(target: "backend", ?result, ?request, "simulate call"); @@ -1725,7 +1716,7 @@ impl Backend { let env = self.build_call_env(request, fee_details, block_env); let mut evm = self.new_evm_with_inspector_ref(state, &env, &mut inspector); - let ResultAndState { result, state } = evm.inspect_with_tx(env.tx)?; + let ResultAndState { result, state } = evm.transact(env.tx)?; let (exit_reason, gas_used, out) = match result { ExecutionResult::Success { reason, gas_used, output, .. } => { (reason.into(), gas_used, Some(output)) @@ -1733,7 +1724,7 @@ impl Backend { ExecutionResult::Revert { gas_used, output } => { (InstructionResult::Revert, gas_used, Some(Output::Call(output))) } - ExecutionResult::Halt { reason, gas_used } => (reason.into(), gas_used, None), + ExecutionResult::Halt { reason, gas_used } => (op_haltreason_to_instruction_result(reason), gas_used, None), }; drop(evm); inspector.print_logs(); @@ -1784,7 +1775,7 @@ impl Backend { &env, &mut inspector, ); - let ResultAndState { result, state: _ } = evm.inspect_with_tx(env.tx)?; + let ResultAndState { result, state: _ } = evm.transact(env.tx)?; drop(evm); let tracing_inspector = inspector.tracer.expect("tracer disappeared"); @@ -1816,7 +1807,7 @@ impl Backend { let env = self.build_call_env(request, fee_details, block); let mut evm = self.new_evm_with_inspector_ref(state.as_dyn(), &env, &mut inspector); - let ResultAndState { result, state: _ } = evm.inspect_with_tx(env.tx)?; + let ResultAndState { result, state: _ } = evm.transact(env.tx)?; let (exit_reason, gas_used, out) = match result { ExecutionResult::Success { reason, gas_used, output, .. } => { @@ -1825,7 +1816,7 @@ impl Backend { ExecutionResult::Revert { gas_used, output } => { (InstructionResult::Revert, gas_used, Some(Output::Call(output))) } - ExecutionResult::Halt { reason, gas_used } => (reason.into(), gas_used, None), + ExecutionResult::Halt { reason, gas_used } => (op_haltreason_to_instruction_result(reason), gas_used, None), }; drop(evm); @@ -1855,20 +1846,8 @@ impl Backend { AccessListInspector::new(request.access_list.clone().unwrap_or_default()); let env = self.build_call_env(request, fee_details, block_env); - let mut evm: revm::context::Evm< - _, - &mut dyn Inspector< - revm::Context< - BlockEnv, - TxEnv, - revm::context::CfgEnv, - WrapDatabaseRef<&(dyn DatabaseRef)>, - >, - >, - _, - _, - > = self.new_evm_with_inspector_ref(state, &env, &mut inspector); - let ResultAndState { result, state: _ } = evm.inspect_with_tx(env.tx)?; + let mut evm = self.new_evm_with_inspector_ref(state, &env, &mut inspector); + let ResultAndState { result, state: _ } = evm.transact(env.tx)?; let (exit_reason, gas_used, out) = match result { ExecutionResult::Success { reason, gas_used, output, .. } => { (reason.into(), gas_used, Some(output)) @@ -1876,7 +1855,7 @@ impl Backend { ExecutionResult::Revert { gas_used, output } => { (InstructionResult::Revert, gas_used, Some(Output::Call(output))) } - ExecutionResult::Halt { reason, gas_used } => (reason.into(), gas_used, None), + ExecutionResult::Halt { reason, gas_used } => (op_haltreason_to_instruction_result(reason), gas_used, None), }; drop(evm); let access_list = inspector.access_list(); @@ -3336,3 +3315,10 @@ pub fn is_arbitrum(chain_id: u64) -> bool { } false } + +pub fn op_haltreason_to_instruction_result(op_reason: OpHaltReason) -> InstructionResult { + match op_reason { + OpHaltReason::Base(eth_h) => eth_h.into(), + OpHaltReason::FailedDeposit => InstructionResult::Stop, + } +} \ No newline at end of file diff --git a/crates/anvil/src/eth/backend/mod.rs b/crates/anvil/src/eth/backend/mod.rs index 48b08e07cd1ec..8084d17a872c5 100644 --- a/crates/anvil/src/eth/backend/mod.rs +++ b/crates/anvil/src/eth/backend/mod.rs @@ -8,6 +8,7 @@ pub mod mem; pub mod cheats; pub mod time; +pub mod evm; pub mod executor; pub mod fork; pub mod genesis; diff --git a/crates/anvil/src/eth/error.rs b/crates/anvil/src/eth/error.rs index 25909ec41c7b3..d233a2a2c04a4 100644 --- a/crates/anvil/src/eth/error.rs +++ b/crates/anvil/src/eth/error.rs @@ -11,6 +11,7 @@ use anvil_rpc::{ response::ResponseResult, }; use foundry_evm::{backend::DatabaseError, decode::RevertDecoder}; +use op_revm::OpTransactionError; use revm::{ context_interface::result::{EVMError, InvalidHeader, InvalidTransaction}, interpreter::InstructionResult, @@ -124,6 +125,31 @@ where } } +impl From> for BlockchainError +where + T: Into, +{ + fn from(err: EVMError) -> Self { + match err { + EVMError::Transaction(err) => match err { + OpTransactionError::Base(err) => InvalidTransactionError::from(err).into(), + OpTransactionError::DepositSystemTxPostRegolith => { + Self::DepositTransactionUnsupported + } + OpTransactionError::HaltedDepositPostRegolith => { + Self::DepositTransactionUnsupported + } + }, + EVMError::Header(err) => match err { + InvalidHeader::ExcessBlobGasNotSet => Self::ExcessBlobGasNotSet, + InvalidHeader::PrevrandaoNotSet => Self::PrevrandaoNotSet, + }, + EVMError::Database(err) => err.into(), + EVMError::Custom(err) => Self::Message(err), + } + } +} + impl From for BlockchainError { fn from(value: WalletError) -> Self { match value { @@ -262,6 +288,9 @@ pub enum InvalidTransactionError { /// Forwards error from the revm #[error(transparent)] Revm(revm::context_interface::result::InvalidTransaction), + /// Deposit transaction error post regolith + #[error("op-deposit failure post regolith")] + DepositTxErrorPostRegolith, } impl From for InvalidTransactionError { @@ -309,7 +338,6 @@ impl From for InvalidTransactionError { } } } - /// Helper trait to easily convert results to rpc results pub(crate) trait ToRpcResponseResult { fn to_rpc_result(self) -> ResponseResult; diff --git a/crates/anvil/src/eth/pool/transactions.rs b/crates/anvil/src/eth/pool/transactions.rs index 36e421d7a50e1..6cafa4c4cd3ce 100644 --- a/crates/anvil/src/eth/pool/transactions.rs +++ b/crates/anvil/src/eth/pool/transactions.rs @@ -98,6 +98,11 @@ impl PoolTransaction { pub fn gas_price(&self) -> u128 { self.pending_transaction.transaction.gas_price() } + + /// Returns the type of the transaction + pub fn tx_type(&self) -> u8 { + self.pending_transaction.transaction.r#type().unwrap_or_default() + } } impl fmt::Debug for PoolTransaction { diff --git a/crates/anvil/src/lib.rs b/crates/anvil/src/lib.rs index d61e0f73a7a72..3c8e1dc1e0909 100644 --- a/crates/anvil/src/lib.rs +++ b/crates/anvil/src/lib.rs @@ -48,7 +48,7 @@ pub use config::{ }; mod hardfork; -pub use hardfork::EthereumHardfork; +pub use hardfork::{EthereumHardfork, OptimismHardfork}; /// ethereum related implementations pub mod eth; diff --git a/crates/anvil/tests/it/fork.rs b/crates/anvil/tests/it/fork.rs index 81cb6e87216e7..47c9f15409338 100644 --- a/crates/anvil/tests/it/fork.rs +++ b/crates/anvil/tests/it/fork.rs @@ -18,7 +18,9 @@ use alloy_signer_local::PrivateKeySigner; use anvil::{eth::EthApi, spawn, NodeConfig, NodeHandle}; use foundry_common::provider::get_http_provider; use foundry_config::Config; -use foundry_test_utils::rpc::{self, next_http_rpc_endpoint, next_rpc_endpoint}; +use foundry_test_utils::rpc::{ + self, next_http_archive_rpc_url, next_http_rpc_endpoint, next_rpc_endpoint, +}; use futures::StreamExt; use std::{sync::Arc, thread::sleep, time::Duration}; @@ -1328,6 +1330,7 @@ async fn test_immutable_fork_transaction_hash() { use std::str::FromStr; // Fork to a block with a specific transaction + // let fork_tx_hash = TxHash::from_str("39d64ebf9eb3f07ede37f8681bc3b61928817276c4c4680b6ef9eac9f88b6786") .unwrap(); diff --git a/crates/evm/core/Cargo.toml b/crates/evm/core/Cargo.toml index 8bec5d07f4d0f..24363643a6c29 100644 --- a/crates/evm/core/Cargo.toml +++ b/crates/evm/core/Cargo.toml @@ -49,6 +49,7 @@ revm = { workspace = true, features = [ "secp256r1", ] } revm-inspectors.workspace = true +op-revm.workspace = true auto_impl.workspace = true eyre.workspace = true diff --git a/crates/evm/core/src/env.rs b/crates/evm/core/src/env.rs index f01c51d86c106..7ed255231649d 100644 --- a/crates/evm/core/src/env.rs +++ b/crates/evm/core/src/env.rs @@ -1,4 +1,5 @@ pub use alloy_evm::EvmEnv; +use op_revm::transaction::deposit::DepositTransactionParts; use revm::{ context::{BlockEnv, CfgEnv, JournalInner, JournalTr, TxEnv}, primitives::hardfork::SpecId, @@ -10,6 +11,8 @@ use revm::{ pub struct Env { pub evm_env: EvmEnv, pub tx: TxEnv, + pub is_optimism: bool, + pub deposit: Option, } /// Helper container type for [`EvmEnv`] and [`TxEnv`]. @@ -22,7 +25,12 @@ impl Env { } pub fn from(cfg: CfgEnv, block: BlockEnv, tx: TxEnv) -> Self { - Self { evm_env: EvmEnv { cfg_env: cfg, block_env: block }, tx } + Self { + evm_env: EvmEnv { cfg_env: cfg, block_env: block }, + tx, + is_optimism: false, + deposit: None, + } } pub fn from_with_spec_id(cfg: CfgEnv, block: BlockEnv, tx: TxEnv, spec_id: SpecId) -> Self { @@ -31,6 +39,11 @@ impl Env { Self::from(cfg, block, tx) } + + pub fn with_deposit(mut self, deposit: DepositTransactionParts) -> Self { + self.deposit = Some(deposit); + self + } } /// Helper struct with mutable references to the block and cfg environments. @@ -46,6 +59,9 @@ impl EnvMut<'_> { Env { evm_env: EvmEnv { cfg_env: self.cfg.to_owned(), block_env: self.block.to_owned() }, tx: self.tx.to_owned(), + is_optimism: false, + // TODO: DepositTransactionParts get lost here + deposit: None, } } } diff --git a/crates/evm/core/src/fork/init.rs b/crates/evm/core/src/fork/init.rs index c1c72e7e1df45..83122ccab7aa0 100644 --- a/crates/evm/core/src/fork/init.rs +++ b/crates/evm/core/src/fork/init.rs @@ -77,6 +77,8 @@ pub async fn environment>( gas_limit: block.header().gas_limit() as u64, ..Default::default() }, + is_optimism: false, + deposit: None, }; apply_chain_and_block_specific_env_changes::(&mut env, &block); diff --git a/crates/evm/core/src/opts.rs b/crates/evm/core/src/opts.rs index 56b4700959ce6..6aaeceba5d6b7 100644 --- a/crates/evm/core/src/opts.rs +++ b/crates/evm/core/src/opts.rs @@ -174,6 +174,8 @@ impl EvmOpts { caller: self.sender, ..Default::default() }, + is_optimism: false, + deposit: None, } } diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index cdd8e08077d43..d01a9b90d5210 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -666,6 +666,8 @@ impl Executor { chain_id: Some(self.env().evm_env.cfg_env.chain_id), ..self.env().tx.clone() }, + is_optimism: false, + deposit: None, } }