foundry_debugger/
builder.rs1use crate::{DebugNode, Debugger, debugger::DebuggerStats, node::flatten_call_trace};
4use alloy_primitives::{Address, map::AddressHashMap};
5use foundry_common::get_contract_name;
6use foundry_evm_core::Breakpoints;
7use foundry_evm_traces::{CallTraceArena, CallTraceDecoder, Traces, debug::ContractSources};
8
9#[derive(Debug, Default)]
11#[must_use = "builders do nothing unless you call `build` on them"]
12pub struct DebuggerBuilder {
13 debug_arena: Vec<DebugNode>,
15 stats: DebuggerStats,
17 identified_contracts: AddressHashMap<String>,
19 sources: ContractSources,
21 breakpoints: Breakpoints,
23}
24
25impl DebuggerBuilder {
26 #[inline]
28 pub fn new() -> Self {
29 Self::default()
30 }
31
32 #[inline]
34 pub fn traces(mut self, traces: Traces) -> Self {
35 for (_, arena) in traces {
36 self = self.trace_arena(arena.arena);
37 }
38 self
39 }
40
41 #[inline]
43 pub fn trace_arena(mut self, arena: CallTraceArena) -> Self {
44 if let Some(root) = arena.nodes().first() {
45 self.stats.session_trace_gas_used =
46 self.stats.session_trace_gas_used.saturating_add(root.trace.gas_used);
47 }
48 self.stats.session_subcalls =
49 self.stats.session_subcalls.saturating_add(arena.nodes().len().saturating_sub(1));
50 flatten_call_trace(arena, &mut self.debug_arena);
51 self
52 }
53
54 #[inline]
56 pub fn decoders(mut self, decoders: &[CallTraceDecoder]) -> Self {
57 for decoder in decoders {
58 self = self.decoder(decoder);
59 }
60 self
61 }
62
63 #[inline]
65 pub fn decoder(self, decoder: &CallTraceDecoder) -> Self {
66 let c = decoder.contracts.iter().map(|(k, v)| (*k, get_contract_name(v).to_string()));
67 self.identified_contracts(c)
68 }
69
70 #[inline]
72 pub fn identified_contracts(
73 mut self,
74 identified_contracts: impl IntoIterator<Item = (Address, String)>,
75 ) -> Self {
76 self.identified_contracts.extend(identified_contracts);
77 self
78 }
79
80 #[inline]
82 pub fn sources(mut self, sources: ContractSources) -> Self {
83 self.sources = sources;
84 self
85 }
86
87 #[inline]
89 pub fn breakpoints(mut self, breakpoints: Breakpoints) -> Self {
90 self.breakpoints = breakpoints;
91 self
92 }
93
94 #[inline]
96 pub fn build(self) -> Debugger {
97 let Self { debug_arena, stats, identified_contracts, sources, breakpoints } = self;
98 Debugger::new_with_stats(debug_arena, stats, identified_contracts, sources, breakpoints)
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 use alloy_primitives::Bytes;
106 use foundry_evm_traces::{CallKind, CallTrace, CallTraceNode};
107 use revm::{bytecode::opcode::OpCode, interpreter::InstructionResult};
108 use revm_inspectors::tracing::types::{CallTraceStep, TraceMemberOrder};
109
110 fn step() -> CallTraceStep {
111 CallTraceStep {
112 pc: 0,
113 op: OpCode::STOP,
114 stack: None,
115 push_stack: None,
116 memory: None,
117 returndata: Bytes::new(),
118 gas_remaining: 0,
119 gas_refund_counter: 0,
120 gas_used: 0,
121 gas_cost: 0,
122 storage_change: None,
123 status: Some(InstructionResult::Stop),
124 immediate_bytes: None,
125 decoded: None,
126 }
127 }
128
129 fn trace_arena(gas_used: u64, subcalls: usize) -> CallTraceArena {
130 let mut arena = CallTraceArena::default();
131
132 {
133 let root = &mut arena.nodes_mut()[0];
134 root.trace.steps.push(step());
135 root.trace.gas_limit = 1;
136 root.trace.gas_used = gas_used;
137 root.ordering.push(TraceMemberOrder::Step(0));
138
139 for idx in 1..=subcalls {
140 root.ordering.push(TraceMemberOrder::Call(idx - 1));
141 root.children.push(idx);
142 }
143 }
144
145 for idx in 1..=subcalls {
146 arena.nodes_mut().push(CallTraceNode {
147 parent: Some(0),
148 idx,
149 trace: CallTrace { depth: 1, kind: CallKind::Call, ..Default::default() },
150 ..Default::default()
151 });
152 }
153
154 arena
155 }
156
157 #[test]
158 fn trace_arena_accumulates_stats() {
159 let builder = DebuggerBuilder::new().trace_arena(trace_arena(100, 1));
160
161 assert_eq!(builder.stats.session_subcalls, 1);
162 assert_eq!(builder.stats.session_trace_gas_used, 100);
163 assert_eq!(builder.debug_arena.len(), 1);
164 }
165
166 #[test]
167 fn trace_arena_accumulates_session_stats_across_multiple_arenas() {
168 let builder = DebuggerBuilder::new()
169 .trace_arena(trace_arena(100, 1))
170 .trace_arena(trace_arena(200, 2));
171
172 assert_eq!(builder.stats.session_subcalls, 3);
173 assert_eq!(builder.stats.session_trace_gas_used, 300);
174 assert_eq!(builder.debug_arena.len(), 2);
175 }
176}