Skip to main content

foundry_debugger/
builder.rs

1//! Debugger builder.
2
3use 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/// Debugger builder.
10#[derive(Debug, Default)]
11#[must_use = "builders do nothing unless you call `build` on them"]
12pub struct DebuggerBuilder {
13    /// Debug traces returned from the EVM execution.
14    debug_arena: Vec<DebugNode>,
15    /// Aggregate stats for the traces passed to the debugger.
16    stats: DebuggerStats,
17    /// Identified contracts.
18    identified_contracts: AddressHashMap<String>,
19    /// Map of source files.
20    sources: ContractSources,
21    /// Map of the debugger breakpoints.
22    breakpoints: Breakpoints,
23}
24
25impl DebuggerBuilder {
26    /// Creates a new debugger builder.
27    #[inline]
28    pub fn new() -> Self {
29        Self::default()
30    }
31
32    /// Extends the debug arena.
33    #[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    /// Extends the debug arena.
42    #[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    /// Extends the identified contracts from multiple decoders.
55    #[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    /// Extends the identified contracts from a decoder.
64    #[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    /// Extends the identified contracts.
71    #[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    /// Sets the sources for the debugger.
81    #[inline]
82    pub fn sources(mut self, sources: ContractSources) -> Self {
83        self.sources = sources;
84        self
85    }
86
87    /// Sets the breakpoints for the debugger.
88    #[inline]
89    pub fn breakpoints(mut self, breakpoints: Breakpoints) -> Self {
90        self.breakpoints = breakpoints;
91        self
92    }
93
94    /// Builds the debugger.
95    #[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}