Skip to content

Commit 504d210

Browse files
committed
first pass
1 parent 21fae5e commit 504d210

File tree

7 files changed

+387
-14
lines changed

7 files changed

+387
-14
lines changed

Cargo.lock

Lines changed: 64 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,9 @@ solang-parser = "=0.3.3"
166166
# no default features to avoid c-kzg
167167
revm = { version = "13.0.0", default-features = false }
168168
revm-primitives = { version = "8.0.0", default-features = false }
169-
revm-inspectors = { version = "0.5", features = ["serde"] }
169+
# revm-inspectors = { version = "0.5", features = ["serde"] }
170+
# TODO
171+
revm-inspectors = { path = "/Users/sohamzemse/Workspace/opensource/external/paradigm/revm-inspectors", features = ["serde"] }
170172

171173
## ethers
172174
ethers-contract-abigen = { version = "2.0.14", default-features = false }
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
/// Helps to build a folded stack trace.
2+
#[derive(Debug, Clone, Default)]
3+
pub(crate) struct FoldedStackTraceBuilder {
4+
traces: Vec<(Vec<String>, i64)>,
5+
exits: Option<u64>,
6+
}
7+
8+
impl FoldedStackTraceBuilder {
9+
/// Enter execution of a function call that consumes `gas`.
10+
pub fn enter(&mut self, label: String, gas: i64) {
11+
let mut trace_entry = self.traces.last().map(|entry| entry.0.clone()).unwrap_or_default();
12+
13+
let mut exits = self.exits.unwrap_or_default();
14+
while exits > 0 {
15+
trace_entry.pop();
16+
exits -= 1;
17+
}
18+
self.exits = None;
19+
20+
trace_entry.push(label);
21+
self.traces.push((trace_entry, gas));
22+
}
23+
24+
/// Exit execution of a function call.
25+
pub fn exit(&mut self) {
26+
self.exits = self.exits.map(|exits| exits + 1).or(Some(1));
27+
}
28+
29+
/// Returns folded stack trace.
30+
pub fn build(mut self) -> Vec<String> {
31+
self.subtract_children();
32+
self.build_without_subtraction()
33+
}
34+
35+
/// Internal method to build the folded stack trace without subtracting gas consumed by
36+
/// the children function calls.
37+
pub fn build_without_subtraction(&mut self) -> Vec<String> {
38+
let mut lines = Vec::new();
39+
for (trace, gas) in self.traces.iter() {
40+
lines.push(format!("{} {}", trace.join(";"), gas));
41+
}
42+
lines
43+
}
44+
45+
/// Subtracts gas consumed by the children function calls from the parent function calls.
46+
fn subtract_children(&mut self) {
47+
// Iterate over each trace to find the children and subtract their values from the parents
48+
for i in 0..self.traces.len() {
49+
let (left, right) = self.traces.split_at_mut(i);
50+
let (trace, gas) = &right[0];
51+
if trace.len() > 1 {
52+
let parent_trace_to_match = &trace[..trace.len() - 1];
53+
for parent in left {
54+
if parent.0 == parent_trace_to_match {
55+
parent.1 -= gas;
56+
break;
57+
}
58+
}
59+
}
60+
}
61+
}
62+
}
63+
64+
mod tests {
65+
#[test]
66+
fn test_insert_1() {
67+
let mut trace = super::FoldedStackTraceBuilder::default();
68+
trace.enter("top".to_string(), 500);
69+
trace.enter("child_a".to_string(), 100);
70+
trace.exit();
71+
trace.enter("child_b".to_string(), 200);
72+
73+
assert_eq!(
74+
trace.build_without_subtraction(),
75+
vec![
76+
"top 500", //
77+
"top;child_a 100",
78+
"top;child_b 200",
79+
]
80+
);
81+
assert_eq!(
82+
trace.build(),
83+
vec![
84+
"top 200", // 500 - 100 - 200
85+
"top;child_a 100",
86+
"top;child_b 200",
87+
]
88+
);
89+
}
90+
91+
#[test]
92+
fn test_insert_2() {
93+
let mut trace = super::FoldedStackTraceBuilder::default();
94+
trace.enter("top".to_string(), 500);
95+
trace.enter("child_a".to_string(), 300);
96+
trace.enter("child_b".to_string(), 100);
97+
trace.exit();
98+
trace.exit();
99+
trace.enter("child_c".to_string(), 100);
100+
101+
assert_eq!(
102+
trace.build_without_subtraction(),
103+
vec![
104+
"top 500", //
105+
"top;child_a 300",
106+
"top;child_a;child_b 100",
107+
"top;child_c 100",
108+
]
109+
);
110+
111+
assert_eq!(
112+
trace.build(),
113+
vec![
114+
"top 100", // 500 - 300 - 100
115+
"top;child_a 200", // 300 - 100
116+
"top;child_a;child_b 100",
117+
"top;child_c 100",
118+
]
119+
);
120+
}
121+
122+
#[test]
123+
fn test_insert_3() {
124+
let mut trace = super::FoldedStackTraceBuilder::default();
125+
trace.enter("top".to_string(), 1700);
126+
trace.enter("child_a".to_string(), 500);
127+
trace.exit();
128+
trace.enter("child_b".to_string(), 500);
129+
trace.enter("child_c".to_string(), 500);
130+
trace.exit();
131+
trace.exit();
132+
trace.exit();
133+
trace.enter("top2".to_string(), 1700);
134+
135+
assert_eq!(
136+
trace.build_without_subtraction(),
137+
vec![
138+
"top 1700", //
139+
"top;child_a 500",
140+
"top;child_b 500",
141+
"top;child_b;child_c 500",
142+
"top2 1700",
143+
]
144+
);
145+
146+
assert_eq!(
147+
trace.build(),
148+
vec![
149+
"top 700", //
150+
"top;child_a 500",
151+
"top;child_b 0",
152+
"top;child_b;child_c 500",
153+
"top2 1700",
154+
]
155+
);
156+
}
157+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
use alloy_primitives::hex::ToHexExt;
2+
use builder::FoldedStackTraceBuilder;
3+
use revm_inspectors::tracing::{
4+
types::{CallTraceNode, CallTraceStep, DecodedTraceStep, TraceMemberOrder},
5+
CallTraceArena,
6+
};
7+
8+
mod builder;
9+
10+
/// Builds a folded stack trace from the given `arena`.
11+
pub fn build(arena: &CallTraceArena) -> Vec<String> {
12+
let mut fst = FoldedStackTraceBuilder::default();
13+
14+
fst.process_call_node(arena.nodes(), 0);
15+
16+
fst.build()
17+
}
18+
19+
impl FoldedStackTraceBuilder {
20+
/// Creates an entry for a EVM CALL in the folded stack trace.
21+
fn process_call_node(&mut self, nodes: &[CallTraceNode], idx: usize) {
22+
let node = &nodes[idx];
23+
24+
let selector = node
25+
.selector()
26+
.map(|selector| selector.encode_hex_with_prefix())
27+
.unwrap_or("fallback".to_string());
28+
let signature =
29+
node.trace.decoded.call_data.as_ref().map(|dc| &dc.signature).unwrap_or(&selector);
30+
31+
let label = if let Some(label) = &node.trace.decoded.label {
32+
format!("{label}.{signature}")
33+
} else {
34+
signature.clone()
35+
};
36+
37+
self.enter(label, node.trace.gas_used as i64);
38+
39+
// Track step exits to do in this call context
40+
let mut step_exits = vec![];
41+
42+
// Process children nodes.
43+
for order in &node.ordering {
44+
match order {
45+
TraceMemberOrder::Call(child_idx) => {
46+
let child_node_idx = node.children[*child_idx];
47+
self.process_call_node(nodes, child_node_idx);
48+
self.exit();
49+
}
50+
TraceMemberOrder::Step(step_idx) => {
51+
self.exit_previous_steps(&mut step_exits, *step_idx);
52+
self.process_step(&node.trace.steps, *step_idx, &mut step_exits)
53+
}
54+
TraceMemberOrder::Log(_) => {}
55+
}
56+
}
57+
58+
self.exit();
59+
}
60+
61+
/// Creates an entry for an internal function call in the folded stack trace.
62+
fn process_step(
63+
&mut self,
64+
steps: &[CallTraceStep],
65+
step_idx: usize,
66+
step_exits: &mut Vec<usize>,
67+
) {
68+
let step = &steps[step_idx];
69+
if let Some(decoded_step) = &step.decoded {
70+
match decoded_step {
71+
DecodedTraceStep::InternalCall(decoded_internal_call, step_end_idx) => {
72+
let gas_used = steps[*step_end_idx].gas_used.saturating_sub(step.gas_used);
73+
self.enter(decoded_internal_call.func_name.clone(), gas_used as i64);
74+
step_exits.push(*step_end_idx);
75+
}
76+
DecodedTraceStep::Line(_) => {}
77+
}
78+
}
79+
}
80+
81+
/// Exits all the previous internal calls that should end before starting step_idx.
82+
fn exit_previous_steps(&mut self, step_exits: &mut Vec<usize>, step_idx: usize) {
83+
let initial_length = step_exits.len();
84+
step_exits.retain(|&number| number >= step_idx);
85+
86+
let num_exits = initial_length - step_exits.len();
87+
for _ in 0..num_exits {
88+
self.exit();
89+
}
90+
}
91+
}

0 commit comments

Comments
 (0)