From b275a449ea1e9e2706e4ba65801f6ec789aa940e Mon Sep 17 00:00:00 2001 From: Rodi-Can Bozman Date: Tue, 22 Jul 2025 10:21:58 +0200 Subject: [PATCH 1/2] Etherlink/Revm: enable precompile tracing with call tracer The commit also contain a small cleanup on helper file. No reason to put it here, but doing a seperate MR/commit seems overkill. --- .../kernel_latest/revm/src/helpers/rlp.rs | 6 +- .../revm/src/inspectors/call_tracer.rs | 55 ++++++++++++++++++- etherlink/kernel_latest/revm/src/lib.rs | 9 ++- .../revm/src/precompile_provider.rs | 5 +- 4 files changed, 65 insertions(+), 10 deletions(-) diff --git a/etherlink/kernel_latest/revm/src/helpers/rlp.rs b/etherlink/kernel_latest/revm/src/helpers/rlp.rs index d86f16f84d8b..b88ae9d50a3d 100644 --- a/etherlink/kernel_latest/revm/src/helpers/rlp.rs +++ b/etherlink/kernel_latest/revm/src/helpers/rlp.rs @@ -21,7 +21,7 @@ pub fn append_address<'a>( } pub fn append_option_canonical<'a, T, Enc>( - stream: &'a mut rlp::RlpStream, + stream: &'a mut RlpStream, v: &Option, append: Enc, ) where @@ -38,11 +38,11 @@ pub fn append_option_canonical<'a, T, Enc>( } } -pub fn append_option_u64_le(stream: &mut rlp::RlpStream, v: &Option) { +pub fn append_option_u64_le(stream: &mut RlpStream, v: &Option) { append_option_canonical(stream, v, append_u64_le) } -pub fn append_option_address(stream: &mut rlp::RlpStream, address: &Option
) { +pub fn append_option_address(stream: &mut RlpStream, address: &Option
) { append_option_canonical(stream, address, append_address) } diff --git a/etherlink/kernel_latest/revm/src/inspectors/call_tracer.rs b/etherlink/kernel_latest/revm/src/inspectors/call_tracer.rs index c56601ab04c0..052ed002fee7 100644 --- a/etherlink/kernel_latest/revm/src/inspectors/call_tracer.rs +++ b/etherlink/kernel_latest/revm/src/inspectors/call_tracer.rs @@ -9,20 +9,23 @@ use crate::{ append_address, append_option_address, append_option_canonical, append_option_u64_le, append_u16_le, append_u256_le, append_u64_le, }, + precompile_provider::EtherlinkPrecompiles, }; -use tezos_ethereum::Log as RlpLog; use revm::{ context::{ContextTr, CreateScheme, JournalTr, Transaction}, interpreter::{ gas::calculate_initial_tx_gas_for_tx, interpreter::ReturnDataImpl, interpreter_types::StackTr, CallInputs, CallOutcome, CallScheme, CreateInputs, - CreateOutcome, InitialAndFloorGas, InstructionResult, InterpreterTypes, + CreateOutcome, Gas, InitialAndFloorGas, InstructionResult, InterpreterResult, + InterpreterTypes, }, primitives::{hardfork::SpecId, hash_map::HashMap, Address, Bytes, Log, B256, U256}, Inspector, }; use rlp::{Encodable, RlpStream}; +use std::ops::Range; +use tezos_ethereum::Log as RlpLog; use tezos_evm_logging::{log, Level::Debug}; use tezos_evm_runtime::runtime::Runtime; @@ -170,6 +173,7 @@ impl CallTrace { pub struct CallTracer { config: CallTracerConfig, + precompiles: EtherlinkPrecompiles, call_trace: HashMap, transaction_hash: Option, initial_gas: u64, @@ -179,11 +183,13 @@ pub struct CallTracer { impl CallTracer { pub fn new( config: CallTracerConfig, + precompiles: EtherlinkPrecompiles, spec_id: SpecId, transaction_hash: Option, ) -> Self { Self { config, + precompiles, call_trace: HashMap::with_capacity(1), transaction_hash, initial_gas: 0, @@ -266,11 +272,13 @@ where CallScheme::CallCode => ("CALLCODE", inputs.target_address), }; + let call_data = inputs.input.bytes(context); + let mut call_trace = CallTrace::new_minimal_trace( type_.into(), from, inputs.value.get(), - inputs.input.bytes(context).to_vec(), + call_data.to_vec(), depth, ); @@ -279,6 +287,47 @@ where self.set_call_trace(depth, call_trace); + if let Some(precompile) = self + .precompiles + .builtins + .precompiles + .get(&inputs.bytecode_address) + { + // Hack-ish behavior. In case the invoked address is a precompile we need to + // pre-simulate its result because the `call_end` hook is never called when a + // precompile contract is called. + + let memory_offset = Range { start: 0, end: 0 }; // Ignored. + let mut outcome = match precompile(&call_data, inputs.gas_limit) { + Ok(result) => CallOutcome { + result: InterpreterResult { + result: InstructionResult::Return, + output: result.bytes, + gas: Gas::new_spent(result.gas_used), + }, + memory_offset, + }, + Err(_) => CallOutcome { + result: InterpreterResult { + result: InstructionResult::PrecompileError, + // No return data, indicates a precompile contract error. + output: Bytes::new(), + gas: Gas::new_spent(inputs.gas_limit), + }, + memory_offset, + }, + }; + + >::call_end( + self, + context, + inputs, + &mut outcome, + ); + + return None; + } + // NB: Always return [None] or else the result of the call will be overriden. None } diff --git a/etherlink/kernel_latest/revm/src/lib.rs b/etherlink/kernel_latest/revm/src/lib.rs index 4067f33b5cfc..5874dcde02d6 100644 --- a/etherlink/kernel_latest/revm/src/lib.rs +++ b/etherlink/kernel_latest/revm/src/lib.rs @@ -143,13 +143,18 @@ fn tx_env<'a, Host: Runtime>( Ok(tx_env) } -fn get_inspector_from(tracer_input: TracerInput, spec_id: SpecId) -> EtherlinkInspector { +fn get_inspector_from( + tracer_input: TracerInput, + precompiles: EtherlinkPrecompiles, + spec_id: SpecId, +) -> EtherlinkInspector { match tracer_input { TracerInput::CallTracer(CallTracerInput { config, transaction_hash, }) => EtherlinkInspector::CallTracer(Box::new(CallTracer::new( config, + precompiles, spec_id, transaction_hash, ))), @@ -257,7 +262,7 @@ pub fn run_transaction<'a, Host: Runtime>( ); if let Some(tracer_input) = tracer_input { - let inspector = get_inspector_from(tracer_input, spec_id); + let inspector = get_inspector_from(tracer_input, precompiles.clone(), spec_id); let mut evm = evm_inspect( db, diff --git a/etherlink/kernel_latest/revm/src/precompile_provider.rs b/etherlink/kernel_latest/revm/src/precompile_provider.rs index e9b1ac784601..afa97453f8c8 100644 --- a/etherlink/kernel_latest/revm/src/precompile_provider.rs +++ b/etherlink/kernel_latest/revm/src/precompile_provider.rs @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: 2025 Nomadic Labs +// SPDX-FileCopyrightText: 2025 Functori // // SPDX-License-Identifier: MIT @@ -18,9 +19,9 @@ use crate::{ table::table_precompile, }; -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct EtherlinkPrecompiles { - builtins: EthPrecompiles, + pub builtins: EthPrecompiles, } impl EtherlinkPrecompiles { -- GitLab From ea2bc996d7465e335a1793bcb470330231a034fd Mon Sep 17 00:00:00 2001 From: Rodi-Can Bozman Date: Tue, 22 Jul 2025 10:04:15 +0200 Subject: [PATCH 2/2] Etherlink/Tezt: enable [test_trace_transaction_calltracer_precompiles] with revm --- etherlink/tezt/tests/evm_sequencer.ml | 1 + 1 file changed, 1 insertion(+) diff --git a/etherlink/tezt/tests/evm_sequencer.ml b/etherlink/tezt/tests/evm_sequencer.ml index 810e2b63ea7a..9de850a9d15d 100644 --- a/etherlink/tezt/tests/evm_sequencer.ml +++ b/etherlink/tezt/tests/evm_sequencer.ml @@ -8610,6 +8610,7 @@ let test_trace_transaction_calltracer_precompiles = ~da_fee:Wei.zero ~maximum_allowed_ticks:100_000_000_000_000L ~time_between_blocks:Nothing + ~use_revm:activate_revm_registration @@ fun {sequencer; evm_version; _} _protocol -> let endpoint = Evm_node.endpoint sequencer in let sender = Eth_account.bootstrap_accounts.(0) in -- GitLab