diff --git a/crates/revm/revm-inspectors/src/tracing/builder/geth.rs b/crates/revm/revm-inspectors/src/tracing/builder/geth.rs index db402a53b9..248f301b1a 100644 --- a/crates/revm/revm-inspectors/src/tracing/builder/geth.rs +++ b/crates/revm/revm-inspectors/src/tracing/builder/geth.rs @@ -39,8 +39,8 @@ impl GethTraceBuilder { // Fill in memory and storage depending on the options if !opts.disable_storage.unwrap_or_default() { let contract_storage = storage.entry(step.contract).or_default(); - if let Some((key, value)) = step.state_diff { - contract_storage.insert(key.into(), value.into()); + if let Some(change) = step.storage_change { + contract_storage.insert(change.key.into(), change.value.into()); log.storage = Some(contract_storage.clone()); } } diff --git a/crates/revm/revm-inspectors/src/tracing/builder/parity.rs b/crates/revm/revm-inspectors/src/tracing/builder/parity.rs index 13a50de5b7..2ec9ec3133 100644 --- a/crates/revm/revm-inspectors/src/tracing/builder/parity.rs +++ b/crates/revm/revm-inspectors/src/tracing/builder/parity.rs @@ -104,26 +104,48 @@ impl ParityTraceBuilder { /// Returns the tracing types that are configured in the set pub fn into_trace_type_traces( self, - _trace_types: &HashSet, + trace_types: &HashSet, ) -> (Option>, Option, Option) { - // TODO(mattsse): impl conversion - (None, None, None) + if trace_types.is_empty() || self.nodes.is_empty() { + return (None, None, None) + } + + let with_traces = trace_types.contains(&TraceType::Trace); + let with_diff = trace_types.contains(&TraceType::StateDiff); + + let vm_trace = if trace_types.contains(&TraceType::VmTrace) { + Some(vm_trace(&self.nodes)) + } else { + None + }; + + let trace_addresses = self.trace_addresses(); + let mut traces = Vec::with_capacity(if with_traces { self.nodes.len() } else { 0 }); + let mut diff = StateDiff::default(); + + for (node, trace_address) in self.nodes.iter().zip(trace_addresses) { + if with_traces { + let trace = node.parity_transaction_trace(trace_address); + traces.push(trace); + } + if with_diff { + node.parity_update_state_diff(&mut diff); + } + } + + let traces = with_traces.then_some(traces); + let diff = with_diff.then_some(diff); + + (traces, vm_trace, diff) } /// Returns an iterator over all recorded traces for `trace_transaction` pub fn into_transaction_traces_iter(self) -> impl Iterator { let trace_addresses = self.trace_addresses(); - - self.nodes.into_iter().zip(trace_addresses).map(|(node, trace_address)| { - let action = node.parity_action(); - let output = TraceResult::parity_success(node.parity_trace_output()); - TransactionTrace { - action, - result: Some(output), - trace_address, - subtraces: node.children.len(), - } - }) + self.nodes + .into_iter() + .zip(trace_addresses) + .map(|(node, trace_address)| node.parity_transaction_trace(trace_address)) } /// Returns the raw traces of the transaction @@ -131,3 +153,10 @@ impl ParityTraceBuilder { self.into_transaction_traces_iter().collect() } } + +/// Construct the vmtrace for the entire callgraph +fn vm_trace(nodes: &[CallTraceNode]) -> VmTrace { + // TODO: populate vm trace + + VmTrace { code: nodes[0].trace.data.clone().into(), ops: vec![] } +} diff --git a/crates/revm/revm-inspectors/src/tracing/mod.rs b/crates/revm/revm-inspectors/src/tracing/mod.rs index b5a3415dd4..22a09a2f62 100644 --- a/crates/revm/revm-inspectors/src/tracing/mod.rs +++ b/crates/revm/revm-inspectors/src/tracing/mod.rs @@ -21,6 +21,7 @@ mod fourbyte; mod opcount; mod types; mod utils; +use crate::tracing::types::StorageChange; pub use builder::{geth::GethTraceBuilder, parity::ParityTraceBuilder}; pub use config::TracingInspectorConfig; pub use fourbyte::FourByteInspector; @@ -190,7 +191,7 @@ impl TracingInspector { // fields will be populated end of call gas_cost: 0, - state_diff: None, + storage_change: None, status: InstructionResult::Continue, }); } @@ -221,15 +222,16 @@ impl TracingInspector { .expect("exists; initialized with vec") .last(); - step.state_diff = match (op, journal_entry) { + step.storage_change = match (op, journal_entry) { ( opcode::SLOAD | opcode::SSTORE, - Some(JournalEntry::StorageChange { address, key, .. }), + Some(JournalEntry::StorageChange { address, key, had_value }), ) => { // SAFETY: (Address,key) exists if part if StorageChange let value = data.journaled_state.state[address].storage[key].present_value(); - Some((*key, value)) + let change = StorageChange { key: *key, value, had_value: *had_value }; + Some(change) } _ => None, }; diff --git a/crates/revm/revm-inspectors/src/tracing/types.rs b/crates/revm/revm-inspectors/src/tracing/types.rs index 723e065f63..372deb2b30 100644 --- a/crates/revm/revm-inspectors/src/tracing/types.rs +++ b/crates/revm/revm-inspectors/src/tracing/types.rs @@ -5,14 +5,16 @@ use reth_primitives::{bytes::Bytes, Address, H256, U256}; use reth_rpc_types::trace::{ geth::StructLog, parity::{ - Action, ActionType, CallAction, CallOutput, CallType, CreateAction, CreateOutput, - SelfdestructAction, TraceOutput, + Action, ActionType, CallAction, CallOutput, CallType, ChangedType, CreateAction, + CreateOutput, Delta, SelfdestructAction, StateDiff, TraceOutput, TraceResult, + TransactionTrace, }, }; use revm::interpreter::{ CallContext, CallScheme, CreateScheme, InstructionResult, Memory, OpCode, Stack, }; use serde::{Deserialize, Serialize}; +use std::collections::btree_map::Entry; /// A unified representation of a call #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] @@ -28,6 +30,13 @@ pub enum CallKind { Create2, } +impl CallKind { + /// Returns true if the call is a create + pub fn is_any_create(&self) -> bool { + matches!(self, CallKind::Create | CallKind::Create2) + } +} + impl From for CallKind { fn from(scheme: CallScheme) -> Self { match scheme { @@ -160,6 +169,68 @@ impl CallTraceNode { self.trace.status } + /// Updates the values of the state diff + pub(crate) fn parity_update_state_diff(&self, diff: &mut StateDiff) { + let addr = self.trace.address; + let acc = diff.entry(addr).or_default(); + + if self.kind().is_any_create() { + let code = self.trace.output.clone(); + if acc.code == Delta::Unchanged { + acc.code = Delta::Added(code.into()) + } + } + + // TODO: track nonce and balance changes + + // iterate over all storage diffs + for change in self.trace.steps.iter().filter_map(|s| s.storage_change) { + let StorageChange { key, value, had_value } = change; + let value = H256::from(value); + match acc.storage.entry(key.into()) { + Entry::Vacant(entry) => { + if let Some(had_value) = had_value { + entry.insert(Delta::Changed(ChangedType { + from: had_value.into(), + to: value, + })); + } else { + entry.insert(Delta::Added(value)); + } + } + Entry::Occupied(mut entry) => { + let value = match entry.get() { + Delta::Unchanged => Delta::Added(value), + Delta::Added(added) => { + if added == &value { + Delta::Added(*added) + } else { + Delta::Changed(ChangedType { from: *added, to: value }) + } + } + Delta::Removed(_) => Delta::Added(value), + Delta::Changed(c) => { + Delta::Changed(ChangedType { from: c.from, to: value }) + } + }; + entry.insert(value); + } + } + } + } + + /// Converts this node into a parity `TransactionTrace` + pub(crate) fn parity_transaction_trace(&self, trace_address: Vec) -> TransactionTrace { + let action = self.parity_action(); + let output = TraceResult::parity_success(self.parity_trace_output()); + TransactionTrace { + action, + result: Some(output), + trace_address, + subtraces: self.children.len(), + } + } + /// Returns the `Output` for a parity trace pub(crate) fn parity_trace_output(&self) -> TraceOutput { match self.kind() { @@ -252,7 +323,7 @@ pub struct CallTraceStep { /// Gas cost of step execution pub gas_cost: u64, /// Change of the contract state after step execution (effect of the SLOAD/SSTORE instructions) - pub state_diff: Option<(U256, U256)>, + pub storage_change: Option, /// Final status of the call pub status: InstructionResult, } @@ -290,3 +361,11 @@ impl From<&CallTraceStep> for StructLog { } } } + +/// Represents a storage change during execution +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct StorageChange { + pub key: U256, + pub value: U256, + pub had_value: Option, +} diff --git a/crates/rpc/rpc-types/src/eth/trace/parity.rs b/crates/rpc/rpc-types/src/eth/trace/parity.rs index 32465227fb..a60dd8e957 100644 --- a/crates/rpc/rpc-types/src/eth/trace/parity.rs +++ b/crates/rpc/rpc-types/src/eth/trace/parity.rs @@ -5,7 +5,10 @@ use reth_primitives::{Address, Bytes, H256, U256, U64}; use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; +use std::{ + collections::BTreeMap, + ops::{Deref, DerefMut}, +}; /// Result type for parity style transaction trace pub type TraceResult = crate::trace::common::TraceResult; @@ -90,10 +93,24 @@ pub struct AccountDiff { } /// New-type for list of account diffs -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize)] #[serde(transparent)] pub struct StateDiff(pub BTreeMap); +impl Deref for StateDiff { + type Target = BTreeMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for StateDiff { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase", tag = "type", content = "action")] pub enum Action { @@ -229,35 +246,50 @@ pub struct LocalizedTransactionTrace { pub block_hash: Option, } +/// A record of a full VM trace for a CALL/CREATE. #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct VmTrace { + /// The code to be executed. pub code: Bytes, + /// All executed instructions. pub ops: Vec, } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct VmInstruction { + /// The program counter. pub pc: usize, + /// The gas cost for this instruction. pub cost: u64, + /// Information concerning the execution of the operation. pub ex: Option, + /// Subordinate trace of the CALL/CREATE if applicable. pub sub: Option, } +/// A record of an executed VM operation. #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct VmExecutedOperation { + /// The total gas used. pub used: u64, + /// The stack item placed, if any. pub push: Option, + /// If altered, the memory delta. pub mem: Option, + /// The altered storage value, if any. pub store: Option, } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] +/// A diff of some chunk of memory. pub struct MemoryDelta { + /// Offset into memory the change begins. pub off: usize, + /// The changed data. pub data: Bytes, }