diff --git a/crates/revm/revm-inspectors/src/tracing/arena.rs b/crates/revm/revm-inspectors/src/tracing/arena.rs index d157b705f2ff..cb7c6b5187d7 100644 --- a/crates/revm/revm-inspectors/src/tracing/arena.rs +++ b/crates/revm/revm-inspectors/src/tracing/arena.rs @@ -56,6 +56,16 @@ impl CallTraceArena { } } } + + /// Returns the nodes in the arena + pub fn nodes(&self) -> &[CallTraceNode] { + &self.arena + } + + /// Consumes the arena and returns the nodes + pub fn into_nodes(self) -> Vec { + self.arena + } } /// How to push a trace into the arena diff --git a/crates/revm/revm-inspectors/src/tracing/mod.rs b/crates/revm/revm-inspectors/src/tracing/mod.rs index 343f27e0147c..0523f09d01c8 100644 --- a/crates/revm/revm-inspectors/src/tracing/mod.rs +++ b/crates/revm/revm-inspectors/src/tracing/mod.rs @@ -20,7 +20,7 @@ mod builder; mod config; mod fourbyte; mod opcount; -mod types; +pub mod types; mod utils; use crate::tracing::{ arena::PushTraceKind, diff --git a/crates/revm/revm-inspectors/src/tracing/types.rs b/crates/revm/revm-inspectors/src/tracing/types.rs index 525e60d03f33..ad5e776b0a89 100644 --- a/crates/revm/revm-inspectors/src/tracing/types.rs +++ b/crates/revm/revm-inspectors/src/tracing/types.rs @@ -14,162 +14,58 @@ use revm::interpreter::{opcode, CallContext, CallScheme, CreateScheme, Instructi use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, VecDeque}; -/// A unified representation of a call -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "UPPERCASE")] -#[allow(missing_docs)] -pub enum CallKind { - #[default] - Call, - StaticCall, - CallCode, - DelegateCall, - Create, - Create2, -} - -impl CallKind { - /// Returns true if the call is a create - #[inline] - pub fn is_any_create(&self) -> bool { - matches!(self, CallKind::Create | CallKind::Create2) - } - - /// Returns true if the call is a delegate of some sorts - #[inline] - pub fn is_delegate(&self) -> bool { - matches!(self, CallKind::DelegateCall | CallKind::CallCode) - } - - /// Returns true if the call is [CallKind::StaticCall]. - #[inline] - pub fn is_static_call(&self) -> bool { - matches!(self, CallKind::StaticCall) - } -} - -impl std::fmt::Display for CallKind { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - CallKind::Call => { - write!(f, "CALL") - } - CallKind::StaticCall => { - write!(f, "STATICCALL") - } - CallKind::CallCode => { - write!(f, "CALLCODE") - } - CallKind::DelegateCall => { - write!(f, "DELEGATECALL") - } - CallKind::Create => { - write!(f, "CREATE") - } - CallKind::Create2 => { - write!(f, "CREATE2") - } - } - } -} - -impl From for CallKind { - fn from(scheme: CallScheme) -> Self { - match scheme { - CallScheme::Call => CallKind::Call, - CallScheme::StaticCall => CallKind::StaticCall, - CallScheme::CallCode => CallKind::CallCode, - CallScheme::DelegateCall => CallKind::DelegateCall, - } - } -} - -impl From for CallKind { - fn from(create: CreateScheme) -> Self { - match create { - CreateScheme::Create => CallKind::Create, - CreateScheme::Create2 { .. } => CallKind::Create2, - } - } -} - -impl From for ActionType { - fn from(kind: CallKind) -> Self { - match kind { - CallKind::Call | CallKind::StaticCall | CallKind::DelegateCall | CallKind::CallCode => { - ActionType::Call - } - CallKind::Create => ActionType::Create, - CallKind::Create2 => ActionType::Create, - } - } -} - -impl From for CallType { - fn from(ty: CallKind) -> Self { - match ty { - CallKind::Call => CallType::Call, - CallKind::StaticCall => CallType::StaticCall, - CallKind::CallCode => CallType::CallCode, - CallKind::DelegateCall => CallType::DelegateCall, - CallKind::Create => CallType::None, - CallKind::Create2 => CallType::None, - } - } -} - /// A trace of a call. #[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) struct CallTrace { +pub struct CallTrace { /// The depth of the call - pub(crate) depth: usize, + pub depth: usize, /// Whether the call was successful - pub(crate) success: bool, + pub success: bool, /// caller of this call - pub(crate) caller: Address, + pub caller: Address, /// The destination address of the call or the address from the created contract. /// /// In other words, this is the callee if the [CallKind::Call] or the address of the created /// contract if [CallKind::Create]. - pub(crate) address: Address, + pub address: Address, /// Whether this is a call to a precompile /// /// Note: This is an Option because not all tracers make use of this - pub(crate) maybe_precompile: Option, + pub maybe_precompile: Option, /// Holds the target for the selfdestruct refund target if `status` is /// [InstructionResult::SelfDestruct] - pub(crate) selfdestruct_refund_target: Option
, + pub selfdestruct_refund_target: Option
, /// The kind of call this is - pub(crate) kind: CallKind, + pub kind: CallKind, /// The value transferred in the call - pub(crate) value: U256, + pub value: U256, /// The calldata for the call, or the init code for contract creations - pub(crate) data: Bytes, + pub data: Bytes, /// The return data of the call if this was not a contract creation, otherwise it is the /// runtime bytecode of the created contract - pub(crate) output: Bytes, + pub output: Bytes, /// The gas cost of the call - pub(crate) gas_used: u64, + pub gas_used: u64, /// The gas limit of the call - pub(crate) gas_limit: u64, + pub gas_limit: u64, /// The status of the trace's call - pub(crate) status: InstructionResult, + pub status: InstructionResult, /// call context of the runtime - pub(crate) call_context: Option>, + pub call_context: Option>, /// Opcode-level execution steps - pub(crate) steps: Vec, + pub steps: Vec, } impl CallTrace { - // Returns true if the status code is an error or revert, See [InstructionResult::Revert] + /// Returns true if the status code is an error or revert, See [InstructionResult::Revert] #[inline] - pub(crate) fn is_error(&self) -> bool { + pub fn is_error(&self) -> bool { self.status.is_error() } - // Returns true if the status code is a revert + /// Returns true if the status code is a revert #[inline] - pub(crate) fn is_revert(&self) -> bool { + pub fn is_revert(&self) -> bool { self.status == InstructionResult::Revert } @@ -223,26 +119,26 @@ impl Default for CallTrace { /// A node in the arena #[derive(Default, Debug, Clone, PartialEq, Eq)] -pub(crate) struct CallTraceNode { +pub struct CallTraceNode { /// Parent node index in the arena - pub(crate) parent: Option, + pub parent: Option, /// Children node indexes in the arena - pub(crate) children: Vec, + pub children: Vec, /// This node's index in the arena - pub(crate) idx: usize, + pub idx: usize, /// The call trace - pub(crate) trace: CallTrace, + pub trace: CallTrace, /// Logs - pub(crate) logs: Vec, + pub logs: Vec, /// Ordering of child calls and logs - pub(crate) ordering: Vec, + pub ordering: Vec, } impl CallTraceNode { /// Returns the call context's execution address /// /// See `Inspector::call` impl of [TracingInspector](crate::tracing::TracingInspector) - pub(crate) fn execution_address(&self) -> Address { + pub fn execution_address(&self) -> Address { if self.trace.kind.is_delegate() { self.trace.caller } else { @@ -256,7 +152,7 @@ impl CallTraceNode { /// /// If the slot is accessed more than once, the result only includes the first time it was /// accessed, in other words in only returns the original value of the slot. - pub(crate) fn touched_slots(&self) -> BTreeMap { + pub fn touched_slots(&self) -> BTreeMap { let mut touched_slots = BTreeMap::new(); for change in self.trace.steps.iter().filter_map(|s| s.storage_change.as_ref()) { match touched_slots.entry(change.key) { @@ -471,6 +367,110 @@ impl CallTraceNode { } } +/// A unified representation of a call +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "UPPERCASE")] +#[allow(missing_docs)] +pub enum CallKind { + #[default] + Call, + StaticCall, + CallCode, + DelegateCall, + Create, + Create2, +} + +impl CallKind { + /// Returns true if the call is a create + #[inline] + pub fn is_any_create(&self) -> bool { + matches!(self, CallKind::Create | CallKind::Create2) + } + + /// Returns true if the call is a delegate of some sorts + #[inline] + pub fn is_delegate(&self) -> bool { + matches!(self, CallKind::DelegateCall | CallKind::CallCode) + } + + /// Returns true if the call is [CallKind::StaticCall]. + #[inline] + pub fn is_static_call(&self) -> bool { + matches!(self, CallKind::StaticCall) + } +} + +impl std::fmt::Display for CallKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + CallKind::Call => { + write!(f, "CALL") + } + CallKind::StaticCall => { + write!(f, "STATICCALL") + } + CallKind::CallCode => { + write!(f, "CALLCODE") + } + CallKind::DelegateCall => { + write!(f, "DELEGATECALL") + } + CallKind::Create => { + write!(f, "CREATE") + } + CallKind::Create2 => { + write!(f, "CREATE2") + } + } + } +} + +impl From for CallKind { + fn from(scheme: CallScheme) -> Self { + match scheme { + CallScheme::Call => CallKind::Call, + CallScheme::StaticCall => CallKind::StaticCall, + CallScheme::CallCode => CallKind::CallCode, + CallScheme::DelegateCall => CallKind::DelegateCall, + } + } +} + +impl From for CallKind { + fn from(create: CreateScheme) -> Self { + match create { + CreateScheme::Create => CallKind::Create, + CreateScheme::Create2 { .. } => CallKind::Create2, + } + } +} + +impl From for ActionType { + fn from(kind: CallKind) -> Self { + match kind { + CallKind::Call | CallKind::StaticCall | CallKind::DelegateCall | CallKind::CallCode => { + ActionType::Call + } + CallKind::Create => ActionType::Create, + CallKind::Create2 => ActionType::Create, + } + } +} + +impl From for CallType { + fn from(ty: CallKind) -> Self { + match ty { + CallKind::Call => CallType::Call, + CallKind::StaticCall => CallType::StaticCall, + CallKind::CallCode => CallType::CallCode, + CallKind::DelegateCall => CallType::DelegateCall, + CallKind::Create => CallType::None, + CallKind::Create2 => CallType::None, + } + } +} + pub(crate) struct CallTraceStepStackItem<'a> { /// The trace node that contains this step pub(crate) trace_node: &'a CallTraceNode, @@ -482,55 +482,57 @@ pub(crate) struct CallTraceStepStackItem<'a> { /// Ordering enum for calls and logs #[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) enum LogCallOrder { +pub enum LogCallOrder { + /// Contains the index of the corresponding log Log(usize), + /// Contains the index of the corresponding trace node Call(usize), } /// Ethereum log. #[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) struct RawLog { +pub struct RawLog { /// Indexed event params are represented as log topics. - pub(crate) topics: Vec, + pub topics: Vec, /// Others are just plain data. - pub(crate) data: Bytes, + pub data: Bytes, } /// Represents a tracked call step during execution #[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) struct CallTraceStep { +pub struct CallTraceStep { // Fields filled in `step` /// Call depth - pub(crate) depth: u64, + pub depth: u64, /// Program counter before step execution - pub(crate) pc: usize, + pub pc: usize, /// Opcode to be executed - pub(crate) op: OpCode, + pub op: OpCode, /// Current contract address - pub(crate) contract: Address, + pub contract: Address, /// Stack before step execution - pub(crate) stack: Option>, + pub stack: Option>, /// The new stack items placed by this step if any - pub(crate) push_stack: Option>, + pub push_stack: Option>, /// All allocated memory in a step /// /// This will be empty if memory capture is disabled - pub(crate) memory: RecordedMemory, + pub memory: RecordedMemory, /// Size of memory at the beginning of the step - pub(crate) memory_size: usize, + pub memory_size: usize, /// Remaining gas before step execution - pub(crate) gas_remaining: u64, + pub gas_remaining: u64, /// Gas refund counter before step execution - pub(crate) gas_refund_counter: u64, + pub gas_refund_counter: u64, // Fields filled in `step_end` /// Gas cost of step execution - pub(crate) gas_cost: u64, + pub gas_cost: u64, /// Change of the contract state after step execution (effect of the SLOAD/SSTORE instructions) - pub(crate) storage_change: Option, + pub storage_change: Option, /// Final status of the step /// /// This is set after the step was executed. - pub(crate) status: InstructionResult, + pub status: InstructionResult, } // === impl CallTraceStep === @@ -609,25 +611,37 @@ impl CallTraceStep { /// from an SSTORE or SLOAD instruction. #[allow(clippy::upper_case_acronyms)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(crate) enum StorageChangeReason { +pub enum StorageChangeReason { + /// SLOAD opcode SLOAD, + /// SSTORE opcode SSTORE, } -/// Represents a storage change during execution +/// Represents a storage change during execution. +/// +/// This maps to evm internals: +/// [JournalEntry::StorageChange](revm::journaled_state::JournalEntry::StorageChange) +/// +/// It is used to track both storage change and warm load of a storage slot. For warm load in regard +/// to EIP-2929 AccessList had_value will be None. #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(crate) struct StorageChange { - pub(crate) key: U256, - pub(crate) value: U256, - pub(crate) had_value: Option, - pub(crate) reason: StorageChangeReason, +pub struct StorageChange { + /// key of the storage slot + pub key: U256, + /// Current value of the storage slot + pub value: U256, + /// The previous value of the storage slot, if any + pub had_value: Option, + /// How this storage was accessed + pub reason: StorageChangeReason, } /// Represents the memory captured during execution /// /// This is a wrapper around the [SharedMemory](revm::interpreter::SharedMemory) context memory. #[derive(Debug, Clone, PartialEq, Eq, Default)] -pub(crate) struct RecordedMemory(pub(crate) Vec); +pub struct RecordedMemory(pub(crate) Vec); impl RecordedMemory { #[inline] @@ -635,8 +649,9 @@ impl RecordedMemory { Self(mem) } + /// Returns the memory as a byte slice #[inline] - pub(crate) fn as_bytes(&self) -> &[u8] { + pub fn as_bytes(&self) -> &[u8] { &self.0 } @@ -645,19 +660,27 @@ impl RecordedMemory { self.0.resize(size, 0); } + /// Returns the size of the memory #[inline] - pub(crate) fn len(&self) -> usize { + pub fn len(&self) -> usize { self.0.len() } + /// Returns whether the memory is empty #[inline] - pub(crate) fn is_empty(&self) -> bool { + pub fn is_empty(&self) -> bool { self.0.is_empty() } /// Converts the memory into 32byte hex chunks #[inline] - pub(crate) fn memory_chunks(&self) -> Vec { + pub fn memory_chunks(&self) -> Vec { convert_memory(self.as_bytes()) } } + +impl AsRef<[u8]> for RecordedMemory { + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +}