diff --git a/Cargo.lock b/Cargo.lock index 8d16f87e9a7f..04d6d4a54913 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4493,7 +4493,7 @@ dependencies = [ [[package]] name = "revm" version = "2.0.0" -source = "git+https://github.com/bluealloy/revm#1e25c99b544863d15610e9af8e623dd173397b48" +source = "git+https://github.com/bluealloy/revm#ca14d61375cc94b216742bb016d2199c0b006b17" dependencies = [ "arrayref", "auto_impl 1.0.1", @@ -4511,7 +4511,7 @@ dependencies = [ [[package]] name = "revm_precompiles" version = "1.1.1" -source = "git+https://github.com/bluealloy/revm#1e25c99b544863d15610e9af8e623dd173397b48" +source = "git+https://github.com/bluealloy/revm#ca14d61375cc94b216742bb016d2199c0b006b17" dependencies = [ "bytes", "hashbrown 0.12.0", diff --git a/anvil/src/eth/backend/mem/inspector.rs b/anvil/src/eth/backend/mem/inspector.rs index 2238f93312d3..cb45b30888a9 100644 --- a/anvil/src/eth/backend/mem/inspector.rs +++ b/anvil/src/eth/backend/mem/inspector.rs @@ -7,15 +7,18 @@ use crate::{ use bytes::Bytes; use ethers::types::{Address, Log, H256}; use foundry_evm::{ + call_inspectors, decode::decode_console_logs, executor::inspector::{LogCollector, Tracer}, revm, - revm::{CallInputs, EVMData, Gas, Return}, + revm::{CallInputs, EVMData, Gas, GasInspector, Return}, }; +use std::{cell::RefCell, rc::Rc}; /// The [`revm::Inspector`] used when transacting in the evm #[derive(Debug, Clone, Default)] pub struct Inspector { + pub gas: Option>>, pub tracer: Option, /// collects all `console.sol` logs pub logs: LogCollector, @@ -37,28 +40,48 @@ impl Inspector { self } - /// Enables steps recording for `Tracer` + /// Enables steps recording for `Tracer` and attaches `GasInspector` to it /// If `Tracer` wasn't configured before, configures it automatically pub fn with_steps_tracing(mut self) -> Self { if self.tracer.is_none() { self = self.with_tracing() } - self.tracer = self.tracer.map(|tracer| tracer.with_steps_recording()); + let gas_inspector = Rc::new(RefCell::new(GasInspector::default())); + self.gas = Some(gas_inspector.clone()); + self.tracer = self.tracer.map(|tracer| tracer.with_steps_recording(gas_inspector)); self } } impl revm::Inspector for Inspector { + fn initialize_interp( + &mut self, + interp: &mut Interpreter, + data: &mut EVMData<'_, DB>, + is_static: bool, + ) -> Return { + call_inspectors!( + inspector, + [&mut self.gas.as_deref().map(|gas| gas.borrow_mut()), &mut self.tracer], + { inspector.initialize_interp(interp, data, is_static) } + ); + Return::Continue + } + fn step( &mut self, interp: &mut Interpreter, data: &mut EVMData<'_, DB>, is_static: bool, ) -> Return { - if let Some(tracer) = self.tracer.as_mut() { - tracer.step(interp, data, is_static); - } + call_inspectors!( + inspector, + [&mut self.gas.as_deref().map(|gas| gas.borrow_mut()), &mut self.tracer], + { + inspector.step(interp, data, is_static); + } + ); Return::Continue } @@ -69,10 +92,17 @@ impl revm::Inspector for Inspector { topics: &[H256], data: &Bytes, ) { - if let Some(tracer) = self.tracer.as_mut() { - tracer.log(evm_data, address, topics, data); - } - self.logs.log(evm_data, address, topics, data); + call_inspectors!( + inspector, + [ + &mut self.gas.as_deref().map(|gas| gas.borrow_mut()), + &mut self.tracer, + Some(&mut self.logs) + ], + { + inspector.log(evm_data, address, topics, data); + } + ); } fn step_end( @@ -82,9 +112,13 @@ impl revm::Inspector for Inspector { is_static: bool, eval: Return, ) -> Return { - if let Some(tracer) = self.tracer.as_mut() { - tracer.step_end(interp, data, is_static, eval); - } + call_inspectors!( + inspector, + [&mut self.gas.as_deref().map(|gas| gas.borrow_mut()), &mut self.tracer], + { + inspector.step_end(interp, data, is_static, eval); + } + ); eval } @@ -94,10 +128,17 @@ impl revm::Inspector for Inspector { call: &mut CallInputs, is_static: bool, ) -> (Return, Gas, Bytes) { - if let Some(tracer) = self.tracer.as_mut() { - tracer.call(data, call, is_static); - } - self.logs.call(data, call, is_static); + call_inspectors!( + inspector, + [ + &mut self.gas.as_deref().map(|gas| gas.borrow_mut()), + &mut self.tracer, + Some(&mut self.logs) + ], + { + inspector.call(data, call, is_static); + } + ); (Return::Continue, Gas::new(call.gas_limit), Bytes::new()) } @@ -111,9 +152,13 @@ impl revm::Inspector for Inspector { out: Bytes, is_static: bool, ) -> (Return, Gas, Bytes) { - if let Some(tracer) = self.tracer.as_mut() { - tracer.call_end(data, inputs, remaining_gas, ret, out.clone(), is_static); - } + call_inspectors!( + inspector, + [&mut self.gas.as_deref().map(|gas| gas.borrow_mut()), &mut self.tracer], + { + inspector.call_end(data, inputs, remaining_gas, ret, out.clone(), is_static); + } + ); (ret, remaining_gas, out) } @@ -122,9 +167,13 @@ impl revm::Inspector for Inspector { data: &mut EVMData<'_, DB>, call: &mut CreateInputs, ) -> (Return, Option
, Gas, Bytes) { - if let Some(tracer) = self.tracer.as_mut() { - tracer.create(data, call); - } + call_inspectors!( + inspector, + [&mut self.gas.as_deref().map(|gas| gas.borrow_mut()), &mut self.tracer], + { + inspector.create(data, call); + } + ); (Return::Continue, None, Gas::new(call.gas_limit), Bytes::new()) } @@ -138,9 +187,13 @@ impl revm::Inspector for Inspector { gas: Gas, retdata: Bytes, ) -> (Return, Option
, Gas, Bytes) { - if let Some(tracer) = self.tracer.as_mut() { - tracer.create_end(data, inputs, status, address, gas, retdata.clone()); - } + call_inspectors!( + inspector, + [&mut self.gas.as_deref().map(|gas| gas.borrow_mut()), &mut self.tracer], + { + inspector.create_end(data, inputs, status, address, gas, retdata.clone()); + } + ); (status, address, gas, retdata) } } diff --git a/anvil/src/eth/backend/mem/storage.rs b/anvil/src/eth/backend/mem/storage.rs index 4672ed06849c..9cce1da68d26 100644 --- a/anvil/src/eth/backend/mem/storage.rs +++ b/anvil/src/eth/backend/mem/storage.rs @@ -295,7 +295,7 @@ impl MinedTransaction { } pub fn geth_trace(&self, opts: GethDebugTracingOptions) -> GethTrace { - self.info.traces.geth_trace(opts) + self.info.traces.geth_trace(self.receipt.gas_used(), opts) } } diff --git a/evm/src/executor/inspector/stack.rs b/evm/src/executor/inspector/stack.rs index 136cf5703bac..8668eba84e7f 100644 --- a/evm/src/executor/inspector/stack.rs +++ b/evm/src/executor/inspector/stack.rs @@ -15,6 +15,7 @@ use std::collections::BTreeMap; /// Helper macro to call the same method on multiple inspectors without resorting to dynamic /// dispatch +#[macro_export] macro_rules! call_inspectors { ($id:ident, [ $($inspector:expr),+ ], $call:block) => { $({ diff --git a/evm/src/executor/inspector/tracer.rs b/evm/src/executor/inspector/tracer.rs index 2371a05d4c37..265d5cd291e9 100644 --- a/evm/src/executor/inspector/tracer.rs +++ b/evm/src/executor/inspector/tracer.rs @@ -13,27 +13,35 @@ use ethers::{ types::{Address, H256, U256}, }; use revm::{ - opcode, return_ok, CallInputs, CallScheme, CreateInputs, Database, EVMData, Gas, Inspector, - Interpreter, JournalEntry, Return, + opcode, return_ok, CallInputs, CallScheme, CreateInputs, Database, EVMData, Gas, GasInspector, + Inspector, Interpreter, JournalEntry, Return, }; +use std::{cell::RefCell, rc::Rc}; /// An inspector that collects call traces. #[derive(Default, Debug, Clone)] pub struct Tracer { - pub record_steps: bool, + record_steps: bool, - pub trace_stack: Vec, pub traces: CallTraceArena, - pub step_stack: Vec<(usize, usize)>, // (trace_idx, step_idx) + trace_stack: Vec, + step_stack: Vec<(usize, usize)>, // (trace_idx, step_idx) + + gas_inspector: Rc>, } impl Tracer { - pub fn with_steps_recording(mut self) -> Self { + /// Enables step recording and uses [revm::GasInspector] to report gas costs for each step. + /// + /// Gas Inspector should be called externally **before** [Tracer], this is why we need it as + /// `Rc>` here. + pub fn with_steps_recording(mut self, gas_inspector: Rc>) -> Self { self.record_steps = true; + self.gas_inspector = gas_inspector; self } - pub fn start_trace( + fn start_trace( &mut self, depth: usize, address: Address, @@ -57,13 +65,7 @@ impl Tracer { )); } - pub fn fill_trace( - &mut self, - status: Return, - cost: u64, - output: Vec, - address: Option
, - ) { + fn fill_trace(&mut self, status: Return, cost: u64, output: Vec, address: Option
) { let success = matches!(status, return_ok!()); let trace = &mut self.traces.arena [self.trace_stack.pop().expect("more traces were filled than started")] @@ -78,11 +80,7 @@ impl Tracer { } } - pub fn start_step( - &mut self, - interp: &mut Interpreter, - data: &mut EVMData<'_, DB>, - ) { + fn start_step(&mut self, interp: &mut Interpreter, data: &mut EVMData<'_, DB>) { let trace_idx = *self.trace_stack.last().expect("can't start step without starting a trace first"); let trace = &mut self.traces.arena[trace_idx]; @@ -90,6 +88,7 @@ impl Tracer { self.step_stack.push((trace_idx, trace.trace.steps.len())); let pc = interp.program_counter(); + trace.trace.steps.push(CallTraceStep { depth: data.journaled_state.depth(), pc, @@ -97,7 +96,7 @@ impl Tracer { contract: interp.contract.address, stack: interp.stack.clone(), memory: interp.memory.clone(), - gas: interp.gas.remaining(), + gas: self.gas_inspector.borrow().gas_remaining(), gas_refund_counter: interp.gas.refunded() as u64, gas_cost: 0, state_diff: None, @@ -105,7 +104,7 @@ impl Tracer { }); } - pub fn fill_step( + fn fill_step( &mut self, interp: &mut Interpreter, data: &mut EVMData<'_, DB>, @@ -115,30 +114,30 @@ impl Tracer { self.step_stack.pop().expect("can't fill step without starting a step first"); let step = &mut self.traces.arena[trace_idx].trace.steps[step_idx]; - let pc = interp.program_counter() - 1; - let op = interp.contract.bytecode.bytecode()[pc]; - - let journal_entry = data - .journaled_state - .journal - .last() - // This should always work because revm initializes it as `vec![vec![]]` - .unwrap() - .last(); - - step.state_diff = match (op, journal_entry) { - ( - opcode::SLOAD | opcode::SSTORE, - Some(JournalEntry::StorageChage { address, key, .. }), - ) => { - let value = data.journaled_state.state[address].storage[key].present_value(); - Some((*key, value)) - } - _ => None, - }; + if let Some(pc) = interp.program_counter().checked_sub(1) { + let op = interp.contract.bytecode.bytecode()[pc]; + + let journal_entry = data + .journaled_state + .journal + .last() + // This should always work because revm initializes it as `vec![vec![]]` + .unwrap() + .last(); - // TODO: calculate spent gas as in `Debugger::step` - step.gas_cost = step.gas - interp.gas.remaining(); + step.state_diff = match (op, journal_entry) { + ( + opcode::SLOAD | opcode::SSTORE, + Some(JournalEntry::StorageChage { address, key, .. }), + ) => { + let value = data.journaled_state.state[address].storage[key].present_value(); + Some((*key, value)) + } + _ => None, + }; + + step.gas_cost = step.gas - self.gas_inspector.borrow().gas_remaining(); + } // Error codes only if status as u8 > Return::OutOfGas as u8 { @@ -192,36 +191,36 @@ where fn call( &mut self, data: &mut EVMData<'_, DB>, - call: &mut CallInputs, - _: bool, + inputs: &mut CallInputs, + _is_static: bool, ) -> (Return, Gas, Bytes) { - let (from, to) = match call.context.scheme { + let (from, to) = match inputs.context.scheme { CallScheme::DelegateCall | CallScheme::CallCode => { - (call.context.address, call.context.code_address) + (inputs.context.address, inputs.context.code_address) } - _ => (call.context.caller, call.context.address), + _ => (inputs.context.caller, inputs.context.address), }; self.start_trace( data.journaled_state.depth() as usize, to, - call.input.to_vec(), - call.transfer.value, - call.context.scheme.into(), + inputs.input.to_vec(), + inputs.transfer.value, + inputs.context.scheme.into(), from, ); - (Return::Continue, Gas::new(call.gas_limit), Bytes::new()) + (Return::Continue, Gas::new(inputs.gas_limit), Bytes::new()) } fn call_end( &mut self, data: &mut EVMData<'_, DB>, - _call: &CallInputs, + _inputs: &CallInputs, gas: Gas, status: Return, retdata: Bytes, - _: bool, + _is_static: bool, ) -> (Return, Gas, Bytes) { self.fill_trace( status, @@ -236,27 +235,27 @@ where fn create( &mut self, data: &mut EVMData<'_, DB>, - call: &mut CreateInputs, + inputs: &mut CreateInputs, ) -> (Return, Option
, Gas, Bytes) { // TODO: Does this increase gas cost? - let _ = data.journaled_state.load_account(call.caller, data.db); - let nonce = data.journaled_state.account(call.caller).info.nonce; + let _ = data.journaled_state.load_account(inputs.caller, data.db); + let nonce = data.journaled_state.account(inputs.caller).info.nonce; self.start_trace( data.journaled_state.depth() as usize, - get_create_address(call, nonce), - call.init_code.to_vec(), - call.value, - call.scheme.into(), - call.caller, + get_create_address(inputs, nonce), + inputs.init_code.to_vec(), + inputs.value, + inputs.scheme.into(), + inputs.caller, ); - (Return::Continue, None, Gas::new(call.gas_limit), Bytes::new()) + (Return::Continue, None, Gas::new(inputs.gas_limit), Bytes::new()) } fn create_end( &mut self, data: &mut EVMData<'_, DB>, - _: &CreateInputs, + _inputs: &CreateInputs, status: Return, address: Option
, gas: Gas, diff --git a/evm/src/trace/mod.rs b/evm/src/trace/mod.rs index 47879a367da8..3d1ceb5dc56e 100644 --- a/evm/src/trace/mod.rs +++ b/evm/src/trace/mod.rs @@ -89,11 +89,10 @@ impl CallTraceArena { .collect() } - pub fn geth_trace(&self, opts: GethDebugTracingOptions) -> GethTrace { + pub fn geth_trace(&self, receipt_gas_used: U256, opts: GethDebugTracingOptions) -> GethTrace { let mut storage = HashMap::>::new(); let mut trace = self.arena.iter().fold(GethTrace::default(), |mut acc, trace| { acc.failed |= !trace.trace.success; - acc.gas += trace.trace.gas_cost; acc.struct_logs.extend(trace.trace.steps.iter().map(|step| { let mut log: StructLog = step.into(); @@ -118,6 +117,7 @@ impl CallTraceArena { acc }); + trace.gas = receipt_gas_used.as_u64(); if let Some(last_trace) = self.arena.first() { trace.return_value = last_trace.trace.output.to_raw().into(); }