foundry_evm_traces/
folded_stack_trace.rs
1use alloy_primitives::hex::ToHexExt;
2use revm_inspectors::tracing::{
3 types::{CallTraceNode, CallTraceStep, DecodedTraceStep, TraceMemberOrder},
4 CallTraceArena,
5};
6
7pub fn build(arena: &CallTraceArena) -> Vec<String> {
9 let mut fst = EvmFoldedStackTraceBuilder::default();
10 fst.process_call_node(arena.nodes(), 0);
11 fst.build()
12}
13
14#[derive(Default)]
16pub struct EvmFoldedStackTraceBuilder {
17 fst: FoldedStackTraceBuilder,
19}
20
21impl EvmFoldedStackTraceBuilder {
22 pub fn build(self) -> Vec<String> {
24 self.fst.build()
25 }
26
27 pub fn process_call_node(&mut self, nodes: &[CallTraceNode], idx: usize) {
30 let node = &nodes[idx];
31
32 let func_name = if node.trace.kind.is_any_create() {
33 let contract_name = node.trace.decoded.label.as_deref().unwrap_or("Contract");
34 format!("new {contract_name}")
35 } else {
36 let selector = node
37 .selector()
38 .map(|selector| selector.encode_hex_with_prefix())
39 .unwrap_or_else(|| "fallback".to_string());
40 let signature =
41 node.trace.decoded.call_data.as_ref().map(|dc| &dc.signature).unwrap_or(&selector);
42
43 if let Some(label) = &node.trace.decoded.label {
44 format!("{label}.{signature}")
45 } else {
46 signature.clone()
47 }
48 };
49
50 self.fst.enter(func_name, node.trace.gas_used as i64);
51
52 let mut step_exits = vec![];
54
55 for order in &node.ordering {
57 match order {
58 TraceMemberOrder::Call(child_idx) => {
59 let child_node_idx = node.children[*child_idx];
60 self.process_call_node(nodes, child_node_idx);
61 }
62 TraceMemberOrder::Step(step_idx) => {
63 self.exit_previous_steps(&mut step_exits, *step_idx);
64 self.process_step(&node.trace.steps, *step_idx, &mut step_exits)
65 }
66 TraceMemberOrder::Log(_) => {}
67 }
68 }
69
70 for _ in 0..step_exits.len() {
72 self.fst.exit();
73 }
74
75 self.fst.exit();
77 }
78
79 fn process_step(
83 &mut self,
84 steps: &[CallTraceStep],
85 step_idx: usize,
86 step_exits: &mut Vec<usize>,
87 ) {
88 let step = &steps[step_idx];
89 if let Some(decoded_step) = &step.decoded {
90 match decoded_step {
91 DecodedTraceStep::InternalCall(decoded_internal_call, step_end_idx) => {
92 let gas_used = steps[*step_end_idx].gas_used.saturating_sub(step.gas_used);
93 self.fst.enter(decoded_internal_call.func_name.clone(), gas_used as i64);
94 step_exits.push(*step_end_idx);
95 }
96 DecodedTraceStep::Line(_) => {}
97 }
98 }
99 }
100
101 fn exit_previous_steps(&mut self, step_exits: &mut Vec<usize>, step_idx: usize) {
103 let initial_length = step_exits.len();
104 step_exits.retain(|&number| number > step_idx);
105
106 let num_exits = initial_length - step_exits.len();
107 for _ in 0..num_exits {
108 self.fst.exit();
109 }
110 }
111}
112
113#[derive(Debug, Default)]
140pub struct FoldedStackTraceBuilder {
141 traces: Vec<TraceEntry>,
143 exits: usize,
145}
146
147#[derive(Debug, Default)]
148struct TraceEntry {
149 names: Vec<String>,
151 gas: i64,
153}
154
155impl FoldedStackTraceBuilder {
156 pub fn enter(&mut self, label: String, gas: i64) {
158 let mut names = self.traces.last().map(|entry| entry.names.clone()).unwrap_or_default();
159
160 while self.exits > 0 {
161 names.pop();
162 self.exits -= 1;
163 }
164
165 names.push(label);
166 self.traces.push(TraceEntry { names, gas });
167 }
168
169 pub fn exit(&mut self) {
171 self.exits += 1;
172 }
173
174 pub fn build(mut self) -> Vec<String> {
176 self.subtract_children();
177 self.build_without_subtraction()
178 }
179
180 fn build_without_subtraction(&mut self) -> Vec<String> {
183 let mut lines = Vec::new();
184 for TraceEntry { names, gas } in &self.traces {
185 lines.push(format!("{} {}", names.join(";"), gas));
186 }
187 lines
188 }
189
190 fn subtract_children(&mut self) {
192 for i in 0..self.traces.len() {
194 let (left, right) = self.traces.split_at_mut(i);
195 let TraceEntry { names, gas } = &right[0];
196 if names.len() > 1 {
197 let parent_trace_to_match = &names[..names.len() - 1];
198 for parent in left.iter_mut().rev() {
199 if parent.names == parent_trace_to_match {
200 parent.gas -= gas;
201 break;
202 }
203 }
204 }
205 }
206 }
207}
208
209mod tests {
210 #[test]
211 fn test_fst_1() {
212 let mut trace = super::FoldedStackTraceBuilder::default();
213 trace.enter("top".to_string(), 500);
214 trace.enter("child_a".to_string(), 100);
215 trace.exit();
216 trace.enter("child_b".to_string(), 200);
217
218 assert_eq!(
219 trace.build_without_subtraction(),
220 vec![
221 "top 500", "top;child_a 100",
223 "top;child_b 200",
224 ]
225 );
226 assert_eq!(
227 trace.build(),
228 vec![
229 "top 200", "top;child_a 100",
231 "top;child_b 200",
232 ]
233 );
234 }
235
236 #[test]
237 fn test_fst_2() {
238 let mut trace = super::FoldedStackTraceBuilder::default();
239 trace.enter("top".to_string(), 500);
240 trace.enter("child_a".to_string(), 300);
241 trace.enter("child_b".to_string(), 100);
242 trace.exit();
243 trace.exit();
244 trace.enter("child_c".to_string(), 100);
245
246 assert_eq!(
247 trace.build_without_subtraction(),
248 vec![
249 "top 500", "top;child_a 300",
251 "top;child_a;child_b 100",
252 "top;child_c 100",
253 ]
254 );
255
256 assert_eq!(
257 trace.build(),
258 vec![
259 "top 100", "top;child_a 200", "top;child_a;child_b 100",
262 "top;child_c 100",
263 ]
264 );
265 }
266
267 #[test]
268 fn test_fst_3() {
269 let mut trace = super::FoldedStackTraceBuilder::default();
270 trace.enter("top".to_string(), 1700);
271 trace.enter("child_a".to_string(), 500);
272 trace.exit();
273 trace.enter("child_b".to_string(), 500);
274 trace.enter("child_c".to_string(), 500);
275 trace.exit();
276 trace.exit();
277 trace.exit();
278 trace.enter("top2".to_string(), 1700);
279
280 assert_eq!(
281 trace.build_without_subtraction(),
282 vec![
283 "top 1700", "top;child_a 500",
285 "top;child_b 500",
286 "top;child_b;child_c 500",
287 "top2 1700",
288 ]
289 );
290
291 assert_eq!(
292 trace.build(),
293 vec![
294 "top 700", "top;child_a 500",
296 "top;child_b 0",
297 "top;child_b;child_c 500",
298 "top2 1700",
299 ]
300 );
301 }
302}