1use crate::{DebugNode, DebuggerBuilder, ExitReason, tui::TUI};
4use alloy_primitives::map::AddressHashMap;
5use clap::ValueEnum;
6use eyre::Result;
7use foundry_common::slot_identifier::SlotIdentifier;
8use foundry_evm_core::Breakpoints;
9use foundry_evm_traces::debug::ContractSources;
10use std::path::Path;
11
12#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, ValueEnum)]
14pub enum DebuggerLayout {
15 #[default]
17 Auto,
18 Horizontal,
20 Vertical,
22}
23
24impl DebuggerLayout {
25 pub(crate) const fn next(self) -> Self {
26 match self {
27 Self::Auto | Self::Vertical => Self::Horizontal,
28 Self::Horizontal => Self::Vertical,
29 }
30 }
31
32 pub(crate) const fn as_str(self) -> &'static str {
33 match self {
34 Self::Auto => "auto",
35 Self::Horizontal => "horizontal",
36 Self::Vertical => "vertical",
37 }
38 }
39}
40
41#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
42pub struct DebuggerStats {
43 pub session_trace_gas_used: u64,
45 pub session_subcalls: usize,
47}
48
49pub struct DebuggerContext {
50 pub debug_arena: Vec<DebugNode>,
51 pub stats: Option<DebuggerStats>,
52 pub identified_contracts: AddressHashMap<String>,
53 pub(crate) slot_identifiers: Option<AddressHashMap<SlotIdentifier>>,
54 pub contracts_sources: ContractSources,
56 pub breakpoints: Breakpoints,
57 pub layout: DebuggerLayout,
58}
59
60pub struct Debugger {
61 context: DebuggerContext,
62}
63
64impl Debugger {
65 #[inline]
67 pub fn builder() -> DebuggerBuilder {
68 DebuggerBuilder::new()
69 }
70
71 pub const fn new(
73 debug_arena: Vec<DebugNode>,
74 identified_contracts: AddressHashMap<String>,
75 contracts_sources: ContractSources,
76 breakpoints: Breakpoints,
77 ) -> Self {
78 Self {
79 context: DebuggerContext {
80 debug_arena,
81 stats: None,
82 identified_contracts,
83 slot_identifiers: None,
84 contracts_sources,
85 breakpoints,
86 layout: DebuggerLayout::Auto,
87 },
88 }
89 }
90
91 pub(crate) const fn new_with_stats(
92 debug_arena: Vec<DebugNode>,
93 stats: DebuggerStats,
94 identified_contracts: AddressHashMap<String>,
95 slot_identifiers: AddressHashMap<SlotIdentifier>,
96 contracts_sources: ContractSources,
97 breakpoints: Breakpoints,
98 layout: DebuggerLayout,
99 ) -> Self {
100 Self {
101 context: DebuggerContext {
102 debug_arena,
103 stats: Some(stats),
104 identified_contracts,
105 slot_identifiers: Some(slot_identifiers),
106 contracts_sources,
107 breakpoints,
108 layout,
109 },
110 }
111 }
112
113 pub fn run_tui_exit(mut self) -> ! {
115 let code = match self.try_run_tui() {
116 Ok(ExitReason::CharExit) => 0,
117 Err(e) => {
118 let _ = sh_eprintln!("{e}");
119 1
120 }
121 };
122 std::process::exit(code)
123 }
124
125 pub fn try_run_tui(&mut self) -> Result<ExitReason> {
127 eyre::ensure!(!self.context.debug_arena.is_empty(), "debug arena is empty");
128
129 let mut tui = TUI::new(&mut self.context);
130 tui.try_run()
131 }
132
133 pub fn dump_to_file(&mut self, path: &Path) -> Result<()> {
135 eyre::ensure!(!self.context.debug_arena.is_empty(), "debug arena is empty");
136 crate::dump::dump(path, &self.context)
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143 use alloy_primitives::Address;
144 use foundry_common::ContractsByArtifactBuilder;
145 use foundry_compilers::{
146 ArtifactId,
147 artifacts::{CompactContractBytecodeCow, StorageLayout},
148 };
149 use foundry_evm_traces::CallTraceDecoder;
150 use std::borrow::Cow;
151
152 fn artifact_id(name: &str, profile: &str) -> ArtifactId {
153 ArtifactId {
154 path: format!("out/{profile}/{name}.json").into(),
155 name: name.to_string(),
156 source: format!("src/{name}.sol").into(),
157 version: "0.8.30".parse().unwrap(),
158 build_id: profile.to_string(),
159 profile: profile.to_string(),
160 }
161 }
162
163 #[test]
164 fn builder_skips_ambiguous_storage_layout_matches() {
165 let ids = [
166 artifact_id("Ambiguous", "default"),
167 artifact_id("Ambiguous", "optimized"),
168 artifact_id("Unique", "default"),
169 ];
170 let known_contracts = ContractsByArtifactBuilder::new(ids.iter().cloned().map(|id| {
171 (
172 id,
173 CompactContractBytecodeCow {
174 abi: Some(Cow::Owned(Default::default())),
175 ..Default::default()
176 },
177 )
178 }))
179 .with_storage_layouts(ids.iter().cloned().map(|id| (id, StorageLayout::default())))
180 .build();
181 let ambiguous_address = Address::repeat_byte(0x11);
182 let unique_address = Address::repeat_byte(0x22);
183 let mut decoder = CallTraceDecoder::default();
184 decoder.contracts.insert(ambiguous_address, ids[0].identifier());
185 decoder.contracts.insert(unique_address, ids[2].identifier());
186
187 let debugger =
188 Debugger::builder().decoder(&decoder).known_contracts(&known_contracts).build();
189 let slot_identifiers = debugger.context.slot_identifiers.as_ref().unwrap();
190
191 assert!(!slot_identifiers.contains_key(&ambiguous_address));
192 assert!(slot_identifiers.contains_key(&unique_address));
193 }
194}