diff --git a/etherlink/kernel_latest/kernel/src/apply.rs b/etherlink/kernel_latest/kernel/src/apply.rs index 64357837922bf66a087932a32abdb55c98c2b762..6086c7537c4bf39aec873a01a390cb7da5813fb4 100644 --- a/etherlink/kernel_latest/kernel/src/apply.rs +++ b/etherlink/kernel_latest/kernel/src/apply.rs @@ -328,6 +328,7 @@ pub fn revm_run_transaction( effective_gas_price: U256, access_list: AccessList, config: &Config, + _tracer_input: Option, ) -> Result, anyhow::Error> { // Disclaimer: // The following code is over-complicated because we maintain @@ -397,6 +398,8 @@ pub fn revm_run_transaction( ) .collect::>(), ), + // TODO: Provide [tracer_input: Option] when relevant. + None, ) { Ok(outcome) => { let gas_used = outcome.result.gas_used(); @@ -557,6 +560,7 @@ fn apply_ethereum_transaction_common( effective_gas_price, transaction.access_list.clone(), evm_configuration, + tracer_input, ) { Ok(outcome) => outcome, Err(err) => { diff --git a/etherlink/kernel_latest/kernel/src/simulation.rs b/etherlink/kernel_latest/kernel/src/simulation.rs index 30c8dad3a27bd48745f5e0f9a283558afaf2b4dc..2c184d2a991f57b45c266643b3b4ff8274f9c416 100644 --- a/etherlink/kernel_latest/kernel/src/simulation.rs +++ b/etherlink/kernel_latest/kernel/src/simulation.rs @@ -537,6 +537,8 @@ impl Evaluation { // TODO: Replace this by the decoded access lists if any. empty_access_list(), evm_configuration, + // TODO: Provide [tracer_input: Option] when relevant. + None, ) { Ok(Some(outcome)) if !self.with_da_fees => { let result: SimulationResult = diff --git a/etherlink/kernel_latest/revm/src/inspectors/mod.rs b/etherlink/kernel_latest/revm/src/inspectors/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..2d78d6a4bb45e2f15b76b32754a803e1a0a3bbe7 --- /dev/null +++ b/etherlink/kernel_latest/revm/src/inspectors/mod.rs @@ -0,0 +1,200 @@ +// SPDX-FileCopyrightText: 2025 Functori +// +// SPDX-License-Identifier: MIT + +use crate::Error; +use crate::{precompile_provider::EtherlinkPrecompiles, EVMInnerContext}; +use noop::NoInspector; +use revm::{ + context::{ + result::{EVMError, ExecutionResult, HaltReason}, + ContextSetters, ContextTr, Evm, JournalTr, + }, + handler::{ + instructions::EthInstructions, EthFrame, EvmTr, EvmTrError, FrameResult, FrameTr, + Handler, + }, + inspector::InspectorHandler, + interpreter::{ + interpreter::{EthInterpreter, ReturnDataImpl}, + interpreter_action::FrameInit, + interpreter_types::StackTr, + CallInputs, CallOutcome, CreateInputs, CreateOutcome, Interpreter, + InterpreterTypes, + }, + state::EvmState, + ExecuteEvm, InspectEvm, Inspector, +}; +use tezos_evm_runtime::runtime::Runtime; + +pub mod noop; + +pub type EvmInspection<'a, Host> = Evm< + EVMInnerContext<'a, Host>, + EtherlinkInspector, + EthInstructions>, + EtherlinkPrecompiles, + EthFrame, +>; + +pub struct EtherlinkEvmInspector<'a, Host: Runtime> { + pub inner: EvmInspection<'a, Host>, +} + +impl<'a, Host: Runtime> ExecuteEvm for EtherlinkEvmInspector<'a, Host> { + type ExecutionResult = ExecutionResult; + type State = EvmState; + type Error = EVMError; + type Tx = as ContextTr>::Tx; + type Block = as ContextTr>::Block; + + fn set_block(&mut self, block: Self::Block) { + self.inner.set_block(block); + } + + fn transact_one( + &mut self, + tx: Self::Tx, + ) -> Result { + self.inner.transact_one(tx) + } + + fn finalize(&mut self) -> Self::State { + self.inner.finalize() + } + + fn replay( + &mut self, + ) -> Result< + revm::context::result::ExecResultAndState, + Self::Error, + > { + self.inner.replay() + } +} + +#[derive(Debug, Clone)] +pub struct EtherlinkHandler { + pub _phantom: core::marker::PhantomData<(CTX, ERROR, FRAME)>, +} + +impl Handler for EtherlinkHandler +where + EVM: EvmTr>, Frame = FRAME>, + ERROR: EvmTrError, + FRAME: FrameTr, +{ + type Evm = EVM; + type Error = ERROR; + type HaltReason = HaltReason; +} + +impl Default for EtherlinkHandler { + fn default() -> Self { + Self { + _phantom: core::marker::PhantomData, + } + } +} + +impl InspectorHandler + for EtherlinkHandler> +where + EVM: revm::inspector::InspectorEvmTr< + Context: ContextTr>, + Frame = EthFrame, + Inspector: Inspector<<::Evm as EvmTr>::Context, EthInterpreter>, + >, + ERROR: EvmTrError, +{ + type IT = EthInterpreter; +} + +impl InspectEvm for EtherlinkEvmInspector<'_, Host> { + type Inspector = EtherlinkInspector; + + fn set_inspector(&mut self, inspector: Self::Inspector) { + self.inner.inspector = inspector; + } + + fn inspect_one_tx( + &mut self, + tx: Self::Tx, + ) -> Result { + self.inner.set_tx(tx); + EtherlinkHandler::default().inspect_run(&mut self.inner) + } +} + +#[derive(Debug, Clone, Copy)] +pub enum TracerConfig { + NoOp, +} + +pub enum EtherlinkInspector { + NoOp(NoInspector), +} + +impl Default for EtherlinkInspector { + fn default() -> Self { + Self::NoOp(NoInspector) + } +} + +impl Inspector for EtherlinkInspector +where + CTX: ContextTr, + INTR: InterpreterTypes, +{ + fn initialize_interp(&mut self, interp: &mut Interpreter, context: &mut CTX) { + match self { + Self::NoOp(no_inspector) => no_inspector.initialize_interp(interp, context), + } + } + + fn step(&mut self, interp: &mut Interpreter, context: &mut CTX) { + match self { + Self::NoOp(no_inspector) => no_inspector.step(interp, context), + } + } + + fn step_end(&mut self, interp: &mut Interpreter, context: &mut CTX) { + match self { + Self::NoOp(no_inspector) => no_inspector.step_end(interp, context), + } + } + + fn call_end( + &mut self, + context: &mut CTX, + inputs: &CallInputs, + outcome: &mut CallOutcome, + ) { + match self { + Self::NoOp(no_inspector) => >::call_end( + no_inspector, + context, + inputs, + outcome, + ), + } + } + + fn create_end( + &mut self, + context: &mut CTX, + inputs: &CreateInputs, + outcome: &mut CreateOutcome, + ) { + match self { + Self::NoOp(no_inspector) => { + >::create_end( + no_inspector, + context, + inputs, + outcome, + ) + } + } + } +} diff --git a/etherlink/kernel_latest/revm/src/inspectors/noop.rs b/etherlink/kernel_latest/revm/src/inspectors/noop.rs new file mode 100644 index 0000000000000000000000000000000000000000..89f31d3dbfcc05af799fce730f6de694ec7cb057 --- /dev/null +++ b/etherlink/kernel_latest/revm/src/inspectors/noop.rs @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2025 Functori +// +// SPDX-License-Identifier: MIT + +use revm::{ + context::ContextTr, + interpreter::{interpreter_types::StackTr, InterpreterTypes}, + Inspector, +}; + +pub struct NoInspector; + +impl Inspector for NoInspector +where + CTX: ContextTr, + INTR: InterpreterTypes, +{ +} diff --git a/etherlink/kernel_latest/revm/src/lib.rs b/etherlink/kernel_latest/revm/src/lib.rs index 01b76be9b709fdbafbc80c50f23630f5cfc6791d..1a24fc6bce35ff892e16f2f11429cfc05eeb0008 100644 --- a/etherlink/kernel_latest/revm/src/lib.rs +++ b/etherlink/kernel_latest/revm/src/lib.rs @@ -3,23 +3,24 @@ // // SPDX-License-Identifier: MIT +use crate::inspectors::noop::NoInspector; use crate::{database::PrecompileDatabase, send_outbox_message::Withdrawal}; use database::EtherlinkVMDB; use helpers::u256_to_le_bytes; +use inspectors::{EtherlinkInspector, EvmInspection, TracerConfig}; use precompile_provider::EtherlinkPrecompiles; -use revm::context::result::EVMError; -use revm::context::tx::TxEnvBuilder; -use revm::handler::EthFrame; use revm::{ context::{ - result::ExecutionResult, transaction::AccessList, BlockEnv, CfgEnv, ContextTr, - DBErrorMarker, Evm, LocalContext, TxEnv, + result::{EVMError, ExecResultAndState, ExecutionResult}, + transaction::AccessList, + tx::TxEnvBuilder, + BlockEnv, CfgEnv, ContextTr, DBErrorMarker, Evm, TxEnv, }, context_interface::block::BlobExcessGasAndPrice, - handler::instructions::EthInstructions, + handler::{instructions::EthInstructions, EthFrame}, interpreter::interpreter::EthInterpreter, primitives::{hardfork::SpecId, Address, Bytes, FixedBytes, TxKind, U256}, - Context, ExecuteCommitEvm, Journal, MainBuilder, + Context, ExecuteCommitEvm, InspectEvm, Journal, MainBuilder, }; use tezos_ethereum::block::BlockConstants; use tezos_evm_runtime::runtime::Runtime; @@ -37,6 +38,7 @@ mod code_storage; mod constants; mod database; mod helpers; +mod inspectors; mod table; #[derive(Error, Debug)] @@ -139,6 +141,12 @@ fn tx_env<'a, Host: Runtime>( Ok(tx_env) } +fn get_inspector_from(tracer_config: TracerConfig) -> EtherlinkInspector { + match tracer_config { + TracerConfig::NoOp => EtherlinkInspector::NoOp(NoInspector), + } +} + type EVMInnerContext<'a, Host> = Context<&'a BlockEnv, &'a TxEnv, CfgEnv, EtherlinkVMDB<'a, Host>>; @@ -150,33 +158,53 @@ type EvmContext<'a, Host> = Evm< EthFrame, >; -fn evm<'a, Host: Runtime>( +fn evm_inspect<'a, Host: Runtime>( db: EtherlinkVMDB<'a, Host>, block: &'a BlockEnv, tx: &'a TxEnv, precompiles: EtherlinkPrecompiles, chain_id: u64, spec_id: SpecId, -) -> EvmContext<'a, Host> { + inspector: EtherlinkInspector, +) -> EvmInspection<'a, Host> { let cfg = CfgEnv::new().with_chain_id(chain_id).with_spec(spec_id); - let context: Context< + Context::< BlockEnv, TxEnv, CfgEnv, EtherlinkVMDB<'a, Host>, Journal>, - (), - LocalContext, - > = Context::new(db, spec_id); + >::new(db, spec_id) + .with_block(block) + .with_tx(tx) + .with_cfg(cfg) + .build_mainnet_with_inspector(inspector) + .with_precompiles(precompiles) +} - let evm = context - .with_block(block) - .with_tx(tx) - .with_cfg(cfg) - .build_mainnet(); +fn evm<'a, Host: Runtime>( + db: EtherlinkVMDB<'a, Host>, + block: &'a BlockEnv, + tx: &'a TxEnv, + precompiles: EtherlinkPrecompiles, + chain_id: u64, + spec_id: SpecId, +) -> EvmContext<'a, Host> { + let cfg = CfgEnv::new().with_chain_id(chain_id).with_spec(spec_id); - evm.with_precompiles(precompiles) + Context::< + BlockEnv, + TxEnv, + CfgEnv, + EtherlinkVMDB<'a, Host>, + Journal>, + >::new(db, spec_id) + .with_block(block) + .with_tx(tx) + .with_cfg(cfg) + .build_mainnet() + .with_precompiles(precompiles) } #[allow(clippy::too_many_arguments)] @@ -193,6 +221,7 @@ pub fn run_transaction<'a, Host: Runtime>( effective_gas_price: u128, value: U256, access_list: AccessList, + tracer_config: Option, ) -> Result> { let mut commit_status = true; let block_env = block_env(block_constants)?; @@ -217,37 +246,60 @@ pub fn run_transaction<'a, Host: Runtime>( caller, ); - let mut evm = evm( - db, - &block_env, - &tx, - precompiles, - block_constants.chain_id.as_u64(), - spec_id, - ); - - evm.db_mut().initialize_storage()?; + if let Some(tracer_config) = tracer_config { + let inspector = get_inspector_from(tracer_config); - let execution_result = evm.transact_commit(&tx)?; + let mut evm = evm_inspect( + db, + &block_env, + &tx, + precompiles, + block_constants.chain_id.as_u64(), + spec_id, + inspector, + ); - let withdrawals = evm.db_mut().take_withdrawals(); + let ExecResultAndState { result, .. } = evm.inspect_tx(&tx)?; - if !evm.db_mut().commit_status() { - // If something went wrong while commiting we drop the state changes - // made to the durable storage to avoid ending up in inconsistent state. - evm.db_mut().drop_storage()?; + let withdrawals = evm.db_mut().take_withdrawals(); - return Err(EVMError::Custom( - "Comitting ended up in an incorrect state change: reverting.".to_owned(), - )); + Ok(ExecutionOutcome { + result, + withdrawals, + }) } else { - evm.db_mut().commit_storage()?; - } + let mut evm = evm( + db, + &block_env, + &tx, + precompiles, + block_constants.chain_id.as_u64(), + spec_id, + ); - Ok(ExecutionOutcome { - result: execution_result, // contains logs and gas_used. - withdrawals, - }) + evm.db_mut().initialize_storage()?; + + let execution_result = evm.transact_commit(&tx)?; + + let withdrawals = evm.db_mut().take_withdrawals(); + + if !evm.db_mut().commit_status() { + // If something went wrong while commiting we drop the state changes + // made to the durable storage to avoid ending up in inconsistent state. + evm.db_mut().drop_storage()?; + + return Err(EVMError::Custom( + "Comitting ended up in an incorrect state change: reverting.".to_owned(), + )); + } else { + evm.db_mut().commit_storage()?; + } + + Ok(ExecutionOutcome { + result: execution_result, + withdrawals, + }) + } } #[cfg(test)] @@ -359,6 +411,7 @@ mod test { 0, value_sent, AccessList(vec![]), + None, ) .unwrap(); @@ -439,6 +492,7 @@ mod test { 1, value_sent, AccessList(vec![]), + None, ) .unwrap(); @@ -505,6 +559,7 @@ mod test { 1, U256::ZERO, AccessList(vec![]), + None, ); match result { @@ -583,6 +638,7 @@ mod test { 0, withdrawn_amount, AccessList(vec![]), + None, ) .unwrap(); @@ -666,6 +722,7 @@ mod test { 0, U256::ZERO, AccessList(vec![]), + None, ) .unwrap(); @@ -714,6 +771,7 @@ mod test { 1, U256::ZERO, AccessList(vec![]), + None, ); let contract_address = match result_create { @@ -742,6 +800,7 @@ mod test { 1, U256::ZERO, AccessList(vec![]), + None, ); match result_call {