Skip to content

Commit

Permalink
first pass
Browse files Browse the repository at this point in the history
  • Loading branch information
zemse committed Aug 9, 2024
1 parent 21fae5e commit 504d210
Show file tree
Hide file tree
Showing 7 changed files with 387 additions and 14 deletions.
68 changes: 64 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,9 @@ solang-parser = "=0.3.3"
# no default features to avoid c-kzg
revm = { version = "13.0.0", default-features = false }
revm-primitives = { version = "8.0.0", default-features = false }
revm-inspectors = { version = "0.5", features = ["serde"] }
# revm-inspectors = { version = "0.5", features = ["serde"] }
# TODO
revm-inspectors = { path = "/Users/sohamzemse/Workspace/opensource/external/paradigm/revm-inspectors", features = ["serde"] }

## ethers
ethers-contract-abigen = { version = "2.0.14", default-features = false }
Expand Down
157 changes: 157 additions & 0 deletions crates/evm/traces/src/folded_stack_trace/builder.rs
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",
]
);
}
}
91 changes: 91 additions & 0 deletions crates/evm/traces/src/folded_stack_trace/mod.rs
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();
}
}
}
Loading

0 comments on commit 504d210

Please sign in to comment.