-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
387 additions
and
14 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
/// Helps to build a folded stack trace. | ||
#[derive(Debug, Clone, Default)] | ||
pub(crate) struct FoldedStackTraceBuilder { | ||
traces: Vec<(Vec<String>, i64)>, | ||
exits: Option<u64>, | ||
} | ||
|
||
impl FoldedStackTraceBuilder { | ||
/// Enter execution of a function call that consumes `gas`. | ||
pub fn enter(&mut self, label: String, gas: i64) { | ||
let mut trace_entry = self.traces.last().map(|entry| entry.0.clone()).unwrap_or_default(); | ||
|
||
let mut exits = self.exits.unwrap_or_default(); | ||
while exits > 0 { | ||
trace_entry.pop(); | ||
exits -= 1; | ||
} | ||
self.exits = None; | ||
|
||
trace_entry.push(label); | ||
self.traces.push((trace_entry, gas)); | ||
} | ||
|
||
/// Exit execution of a function call. | ||
pub fn exit(&mut self) { | ||
self.exits = self.exits.map(|exits| exits + 1).or(Some(1)); | ||
} | ||
|
||
/// Returns folded stack trace. | ||
pub fn build(mut self) -> Vec<String> { | ||
self.subtract_children(); | ||
self.build_without_subtraction() | ||
} | ||
|
||
/// Internal method to build the folded stack trace without subtracting gas consumed by | ||
/// the children function calls. | ||
pub fn build_without_subtraction(&mut self) -> Vec<String> { | ||
let mut lines = Vec::new(); | ||
for (trace, gas) in self.traces.iter() { | ||
lines.push(format!("{} {}", trace.join(";"), gas)); | ||
} | ||
lines | ||
} | ||
|
||
/// Subtracts gas consumed by the children function calls from the parent function calls. | ||
fn subtract_children(&mut self) { | ||
// Iterate over each trace to find the children and subtract their values from the parents | ||
for i in 0..self.traces.len() { | ||
let (left, right) = self.traces.split_at_mut(i); | ||
let (trace, gas) = &right[0]; | ||
if trace.len() > 1 { | ||
let parent_trace_to_match = &trace[..trace.len() - 1]; | ||
for parent in left { | ||
if parent.0 == parent_trace_to_match { | ||
parent.1 -= gas; | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
mod tests { | ||
#[test] | ||
fn test_insert_1() { | ||
let mut trace = super::FoldedStackTraceBuilder::default(); | ||
trace.enter("top".to_string(), 500); | ||
trace.enter("child_a".to_string(), 100); | ||
trace.exit(); | ||
trace.enter("child_b".to_string(), 200); | ||
|
||
assert_eq!( | ||
trace.build_without_subtraction(), | ||
vec![ | ||
"top 500", // | ||
"top;child_a 100", | ||
"top;child_b 200", | ||
] | ||
); | ||
assert_eq!( | ||
trace.build(), | ||
vec![ | ||
"top 200", // 500 - 100 - 200 | ||
"top;child_a 100", | ||
"top;child_b 200", | ||
] | ||
); | ||
} | ||
|
||
#[test] | ||
fn test_insert_2() { | ||
let mut trace = super::FoldedStackTraceBuilder::default(); | ||
trace.enter("top".to_string(), 500); | ||
trace.enter("child_a".to_string(), 300); | ||
trace.enter("child_b".to_string(), 100); | ||
trace.exit(); | ||
trace.exit(); | ||
trace.enter("child_c".to_string(), 100); | ||
|
||
assert_eq!( | ||
trace.build_without_subtraction(), | ||
vec![ | ||
"top 500", // | ||
"top;child_a 300", | ||
"top;child_a;child_b 100", | ||
"top;child_c 100", | ||
] | ||
); | ||
|
||
assert_eq!( | ||
trace.build(), | ||
vec![ | ||
"top 100", // 500 - 300 - 100 | ||
"top;child_a 200", // 300 - 100 | ||
"top;child_a;child_b 100", | ||
"top;child_c 100", | ||
] | ||
); | ||
} | ||
|
||
#[test] | ||
fn test_insert_3() { | ||
let mut trace = super::FoldedStackTraceBuilder::default(); | ||
trace.enter("top".to_string(), 1700); | ||
trace.enter("child_a".to_string(), 500); | ||
trace.exit(); | ||
trace.enter("child_b".to_string(), 500); | ||
trace.enter("child_c".to_string(), 500); | ||
trace.exit(); | ||
trace.exit(); | ||
trace.exit(); | ||
trace.enter("top2".to_string(), 1700); | ||
|
||
assert_eq!( | ||
trace.build_without_subtraction(), | ||
vec![ | ||
"top 1700", // | ||
"top;child_a 500", | ||
"top;child_b 500", | ||
"top;child_b;child_c 500", | ||
"top2 1700", | ||
] | ||
); | ||
|
||
assert_eq!( | ||
trace.build(), | ||
vec![ | ||
"top 700", // | ||
"top;child_a 500", | ||
"top;child_b 0", | ||
"top;child_b;child_c 500", | ||
"top2 1700", | ||
] | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
use alloy_primitives::hex::ToHexExt; | ||
use builder::FoldedStackTraceBuilder; | ||
use revm_inspectors::tracing::{ | ||
types::{CallTraceNode, CallTraceStep, DecodedTraceStep, TraceMemberOrder}, | ||
CallTraceArena, | ||
}; | ||
|
||
mod builder; | ||
|
||
/// Builds a folded stack trace from the given `arena`. | ||
pub fn build(arena: &CallTraceArena) -> Vec<String> { | ||
let mut fst = FoldedStackTraceBuilder::default(); | ||
|
||
fst.process_call_node(arena.nodes(), 0); | ||
|
||
fst.build() | ||
} | ||
|
||
impl FoldedStackTraceBuilder { | ||
/// Creates an entry for a EVM CALL in the folded stack trace. | ||
fn process_call_node(&mut self, nodes: &[CallTraceNode], idx: usize) { | ||
let node = &nodes[idx]; | ||
|
||
let selector = node | ||
.selector() | ||
.map(|selector| selector.encode_hex_with_prefix()) | ||
.unwrap_or("fallback".to_string()); | ||
let signature = | ||
node.trace.decoded.call_data.as_ref().map(|dc| &dc.signature).unwrap_or(&selector); | ||
|
||
let label = if let Some(label) = &node.trace.decoded.label { | ||
format!("{label}.{signature}") | ||
} else { | ||
signature.clone() | ||
}; | ||
|
||
self.enter(label, node.trace.gas_used as i64); | ||
|
||
// Track step exits to do in this call context | ||
let mut step_exits = vec![]; | ||
|
||
// Process children nodes. | ||
for order in &node.ordering { | ||
match order { | ||
TraceMemberOrder::Call(child_idx) => { | ||
let child_node_idx = node.children[*child_idx]; | ||
self.process_call_node(nodes, child_node_idx); | ||
self.exit(); | ||
} | ||
TraceMemberOrder::Step(step_idx) => { | ||
self.exit_previous_steps(&mut step_exits, *step_idx); | ||
self.process_step(&node.trace.steps, *step_idx, &mut step_exits) | ||
} | ||
TraceMemberOrder::Log(_) => {} | ||
} | ||
} | ||
|
||
self.exit(); | ||
} | ||
|
||
/// Creates an entry for an internal function call in the folded stack trace. | ||
fn process_step( | ||
&mut self, | ||
steps: &[CallTraceStep], | ||
step_idx: usize, | ||
step_exits: &mut Vec<usize>, | ||
) { | ||
let step = &steps[step_idx]; | ||
if let Some(decoded_step) = &step.decoded { | ||
match decoded_step { | ||
DecodedTraceStep::InternalCall(decoded_internal_call, step_end_idx) => { | ||
let gas_used = steps[*step_end_idx].gas_used.saturating_sub(step.gas_used); | ||
self.enter(decoded_internal_call.func_name.clone(), gas_used as i64); | ||
step_exits.push(*step_end_idx); | ||
} | ||
DecodedTraceStep::Line(_) => {} | ||
} | ||
} | ||
} | ||
|
||
/// Exits all the previous internal calls that should end before starting step_idx. | ||
fn exit_previous_steps(&mut self, step_exits: &mut Vec<usize>, step_idx: usize) { | ||
let initial_length = step_exits.len(); | ||
step_exits.retain(|&number| number >= step_idx); | ||
|
||
let num_exits = initial_length - step_exits.len(); | ||
for _ in 0..num_exits { | ||
self.exit(); | ||
} | ||
} | ||
} |
Oops, something went wrong.