foundry_evm_traces/
folded_stack_trace.rs1use alloy_primitives::hex::ToHexExt;
2use revm_inspectors::tracing::{
3 CallTraceArena,
4 types::{CallTraceNode, CallTraceStep, DecodedTraceStep, TraceMemberOrder},
5};
6
7pub fn build(arena: &CallTraceArena, isolate: bool) -> Vec<String> {
9 let mut fst = EvmFoldedStackTraceBuilder::new(isolate);
10 fst.process_call_node(arena.nodes(), 0);
11 fst.build()
12}
13
14pub struct EvmFoldedStackTraceBuilder {
16 isolate: bool,
19 fst: FoldedStackTraceBuilder,
21}
22
23impl EvmFoldedStackTraceBuilder {
24 pub fn new(isolate: bool) -> Self {
25 Self { isolate, fst: FoldedStackTraceBuilder::default() }
26 }
27
28 pub fn build(self) -> Vec<String> {
30 self.fst.build()
31 }
32
33 pub fn process_call_node(&mut self, nodes: &[CallTraceNode], idx: usize) {
36 let node = &nodes[idx];
37
38 let func_name = if node.trace.kind.is_any_create() {
39 let contract_name = node
40 .trace
41 .decoded
42 .as_ref()
43 .and_then(|dc| dc.label.as_deref())
44 .unwrap_or("Contract");
45 format!("new {contract_name}")
46 } else {
47 let selector = node
48 .selector()
49 .map(|selector| selector.encode_hex_with_prefix())
50 .unwrap_or_else(|| "fallback".to_string());
51 let signature = node
52 .trace
53 .decoded
54 .as_ref()
55 .and_then(|dc| dc.call_data.as_ref())
56 .map(|dc| &dc.signature)
57 .unwrap_or(&selector);
58
59 if let Some(label) = node.trace.decoded.as_ref().and_then(|dc| dc.label.as_ref()) {
60 format!("{label}.{signature}")
61 } else {
62 signature.clone()
63 }
64 };
65
66 let mut gas_used = node.trace.gas_used;
67 let max_refund_adjust_depth = if self.isolate { 1 } else { 0 };
68 if node.trace.depth <= max_refund_adjust_depth {
69 gas_used += node.trace.gas_refund_counter;
70 }
71
72 self.fst.enter(func_name, gas_used);
73
74 let mut step_exits = vec![];
76
77 for order in &node.ordering {
79 match order {
80 TraceMemberOrder::Call(child_idx) => {
81 let child_node_idx = node.children[*child_idx];
82 self.process_call_node(nodes, child_node_idx);
83 }
84 TraceMemberOrder::Step(step_idx) => {
85 self.exit_previous_steps(&mut step_exits, *step_idx);
86 self.process_step(&node.trace.steps, *step_idx, &mut step_exits)
87 }
88 TraceMemberOrder::Log(_) => {}
89 }
90 }
91
92 for _ in 0..step_exits.len() {
94 self.fst.exit();
95 }
96
97 self.fst.exit();
99 }
100
101 fn process_step(
105 &mut self,
106 steps: &[CallTraceStep],
107 step_idx: usize,
108 step_exits: &mut Vec<usize>,
109 ) {
110 let step = &steps[step_idx];
111 if let Some(decoded_step) = &step.decoded {
112 match decoded_step.as_ref() {
113 DecodedTraceStep::InternalCall(decoded_internal_call, step_end_idx) => {
114 let gas_used = step.gas_remaining - steps[*step_end_idx].gas_remaining;
115 self.fst.enter(decoded_internal_call.func_name.clone(), gas_used);
116 step_exits.push(*step_end_idx);
117 }
118 DecodedTraceStep::Line(_) => {}
119 }
120 }
121 }
122
123 fn exit_previous_steps(&mut self, step_exits: &mut Vec<usize>, step_idx: usize) {
125 let initial_length = step_exits.len();
126 step_exits.retain(|&number| number > step_idx);
127
128 let num_exits = initial_length - step_exits.len();
129 for _ in 0..num_exits {
130 self.fst.exit();
131 }
132 }
133}
134
135#[derive(Debug, Default)]
162pub struct FoldedStackTraceBuilder {
163 traces: Vec<TraceEntry>,
165 exits: usize,
167}
168
169#[derive(Debug, Default)]
170struct TraceEntry {
171 names: Vec<String>,
173 gas: u64,
175}
176
177impl FoldedStackTraceBuilder {
178 pub fn enter(&mut self, label: String, gas: u64) {
180 let mut names = self.traces.last().map(|entry| entry.names.clone()).unwrap_or_default();
181
182 while self.exits > 0 {
183 names.pop();
184 self.exits -= 1;
185 }
186
187 names.push(label);
188 self.traces.push(TraceEntry { names, gas });
189 }
190
191 pub fn exit(&mut self) {
193 self.exits += 1;
194 }
195
196 pub fn build(mut self) -> Vec<String> {
198 self.subtract_children();
199 self.build_without_subtraction()
200 }
201
202 pub fn build_without_subtraction(&mut self) -> Vec<String> {
205 let mut lines = Vec::new();
206 for TraceEntry { names, gas } in &self.traces {
207 lines.push(format!("{} {}", names.join(";"), gas));
208 }
209 lines
210 }
211
212 fn subtract_children(&mut self) {
214 for i in 0..self.traces.len() {
216 let (left, right) = self.traces.split_at_mut(i);
217 let TraceEntry { names, gas } = &right[0];
218 if names.len() > 1 {
219 let parent_trace_to_match = &names[..names.len() - 1];
220 for parent in left.iter_mut().rev() {
221 if parent.names == parent_trace_to_match {
222 parent.gas -= gas;
223 break;
224 }
225 }
226 }
227 }
228 }
229}
230
231mod tests {
232 #[test]
233 fn test_fst_1() {
234 let mut trace = super::FoldedStackTraceBuilder::default();
235 trace.enter("top".to_string(), 500);
236 trace.enter("child_a".to_string(), 100);
237 trace.exit();
238 trace.enter("child_b".to_string(), 200);
239
240 assert_eq!(
241 trace.build_without_subtraction(),
242 vec![
243 "top 500", "top;child_a 100",
245 "top;child_b 200",
246 ]
247 );
248 assert_eq!(
249 trace.build(),
250 vec![
251 "top 200", "top;child_a 100",
253 "top;child_b 200",
254 ]
255 );
256 }
257
258 #[test]
259 fn test_fst_2() {
260 let mut trace = super::FoldedStackTraceBuilder::default();
261 trace.enter("top".to_string(), 500);
262 trace.enter("child_a".to_string(), 300);
263 trace.enter("child_b".to_string(), 100);
264 trace.exit();
265 trace.exit();
266 trace.enter("child_c".to_string(), 100);
267
268 assert_eq!(
269 trace.build_without_subtraction(),
270 vec![
271 "top 500", "top;child_a 300",
273 "top;child_a;child_b 100",
274 "top;child_c 100",
275 ]
276 );
277
278 assert_eq!(
279 trace.build(),
280 vec![
281 "top 100", "top;child_a 200", "top;child_a;child_b 100",
284 "top;child_c 100",
285 ]
286 );
287 }
288
289 #[test]
290 fn test_fst_3() {
291 let mut trace = super::FoldedStackTraceBuilder::default();
292 trace.enter("top".to_string(), 1700);
293 trace.enter("child_a".to_string(), 500);
294 trace.exit();
295 trace.enter("child_b".to_string(), 500);
296 trace.enter("child_c".to_string(), 500);
297 trace.exit();
298 trace.exit();
299 trace.exit();
300 trace.enter("top2".to_string(), 1700);
301
302 assert_eq!(
303 trace.build_without_subtraction(),
304 vec![
305 "top 1700", "top;child_a 500",
307 "top;child_b 500",
308 "top;child_b;child_c 500",
309 "top2 1700",
310 ]
311 );
312
313 assert_eq!(
314 trace.build(),
315 vec![
316 "top 700", "top;child_a 500",
318 "top;child_b 0",
319 "top;child_b;child_c 500",
320 "top2 1700",
321 ]
322 );
323 }
324}