foundry_debugger/
builder.rs1use crate::soldb::{SoldbDebugger, write_bundle};
4use alloy_primitives::{
5 Address, Bytes,
6 map::{AddressHashMap, HashMap},
7};
8use eyre::Result;
9use foundry_common::{ContractsByArtifact, get_contract_name};
10use foundry_evm_core::Breakpoints;
11use foundry_evm_traces::{
12 CallTraceArena, CallTraceDecoder, CallTraceNode, Traces,
13 debug::{ContractSources, DebugRecording},
14};
15use std::path::Path;
16
17#[derive(Debug, Default)]
19#[must_use = "builders do nothing unless you call `run` on them"]
20pub struct DebuggerBuilder {
21 trace_arenas: Vec<CallTraceArena>,
23 debug_steps: Vec<Option<DebugRecording>>,
25 identified_contracts: AddressHashMap<String>,
27 known_contracts: ContractsByArtifact,
29 sources: ContractSources,
31 breakpoints: Breakpoints,
33}
34
35impl DebuggerBuilder {
36 #[inline]
38 pub fn new() -> Self {
39 Self::default()
40 }
41
42 #[inline]
44 pub fn traces(mut self, traces: Traces) -> Self {
45 for (_, arena) in traces {
46 self = self.trace_arena(arena.arena);
47 *self.debug_steps.last_mut().unwrap() = arena.debug_steps;
48 }
49 self
50 }
51
52 #[inline]
54 pub fn trace_arena(mut self, arena: CallTraceArena) -> Self {
55 self.trace_arenas.push(arena);
56 self.debug_steps.push(None);
57 self
58 }
59
60 #[inline]
62 pub fn decoders(mut self, decoders: &[CallTraceDecoder]) -> Self {
63 for decoder in decoders {
64 self = self.decoder(decoder);
65 }
66 self
67 }
68
69 #[inline]
71 pub fn decoder(mut self, decoder: &CallTraceDecoder) -> Self {
72 for (address, identifier) in &decoder.contracts {
73 self.identified_contracts.insert(*address, get_contract_name(identifier).to_string());
74 }
75 self
76 }
77
78 #[inline]
80 pub fn known_contracts(mut self, known_contracts: &ContractsByArtifact) -> Self {
81 self.known_contracts = known_contracts.clone();
82 self
83 }
84
85 #[inline]
87 pub fn identified_contracts(
88 mut self,
89 identified_contracts: impl IntoIterator<Item = (Address, String)>,
90 ) -> Self {
91 self.identified_contracts.extend(identified_contracts);
92 self
93 }
94
95 #[inline]
97 pub fn sources(mut self, sources: ContractSources) -> Self {
98 self.sources = sources;
99 self
100 }
101
102 #[inline]
104 pub fn breakpoints(mut self, breakpoints: Breakpoints) -> Self {
105 self.breakpoints = breakpoints;
106 self
107 }
108
109 pub fn run(self, dump: Option<&Path>) -> Result<()> {
113 let Self {
114 trace_arenas,
115 debug_steps,
116 identified_contracts,
117 known_contracts,
118 sources,
119 breakpoints,
120 } = self;
121 let recordings = if debug_steps.iter().all(Option::is_some) {
123 debug_steps.into_iter().flatten().collect()
124 } else {
125 eyre::ensure!(
126 debug_steps.iter().all(Option::is_none),
127 "only some debug traces have recorded steps"
128 );
129 Vec::new()
130 };
131 let mut identified_code = HashMap::default();
132 let contract_names = trace_arenas
133 .iter()
134 .map(|arena| {
135 arena
136 .nodes()
137 .iter()
138 .map(|node| {
139 node_contract_name(
140 node,
141 &known_contracts,
142 &identified_contracts,
143 &mut identified_code,
144 )
145 })
146 .collect::<Vec<_>>()
147 })
148 .collect::<Vec<_>>();
149 match dump {
150 Some(dir) => write_bundle(
151 dir,
152 trace_arenas,
153 recordings,
154 &contract_names,
155 &sources,
156 &known_contracts,
157 ),
158 None => SoldbDebugger::new(
159 trace_arenas,
160 recordings,
161 &contract_names,
162 &sources,
163 &known_contracts,
164 &breakpoints,
165 )?
166 .try_run(),
167 }
168 }
169}
170
171fn node_contract_name(
174 node: &CallTraceNode,
175 known_contracts: &ContractsByArtifact,
176 identified_contracts: &AddressHashMap<String>,
177 identified_code: &mut HashMap<(Address, Bytes), Option<String>>,
178) -> Option<String> {
179 let address = node.trace.address;
180 let address_name = identified_contracts.get(&address);
181 match &node.trace.bytecode {
182 Some(code) if !code.is_empty() && !node.trace.kind.is_any_create() => identified_code
183 .entry((address, code.clone()))
184 .or_insert_with(|| identify_code(known_contracts, address_name, code))
185 .clone(),
186 _ => address_name.cloned(),
187 }
188}
189
190fn identify_code(
193 known_contracts: &ContractsByArtifact,
194 address_name: Option<&String>,
195 code: &[u8],
196) -> Option<String> {
197 if let Some((id, _)) = known_contracts
199 .find_by_deployed_code_exact_preferred(code, |id| address_name == Some(&id.name))
200 {
201 return Some(id.name.clone());
202 }
203 address_name.filter(|name| !known_contracts.iter().any(|(id, _)| id.name == **name)).cloned()
205}