foundry_debugger/tui/
context.rs1use crate::{DebugNode, ExitReason, debugger::DebuggerContext};
4use alloy_primitives::{Address, hex};
5use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind};
6use foundry_evm_core::buffer::BufferKind;
7use foundry_tui::TuiApp;
8use ratatui::Frame;
9use revm::bytecode::opcode::OpCode;
10use revm_inspectors::tracing::types::{CallKind, CallTraceStep};
11use std::ops::ControlFlow;
12
13#[derive(Default)]
15pub(crate) struct DrawMemory {
16 pub(crate) inner_call_index: usize,
17 pub(crate) current_buf_startline: usize,
18 pub(crate) current_stack_startline: usize,
19}
20
21pub(crate) struct TUIContext<'a> {
22 pub(crate) debugger_context: &'a mut DebuggerContext,
23
24 pub(crate) key_buffer: String,
26 pub(crate) current_step: usize,
28 pub(crate) draw_memory: DrawMemory,
29 pub(crate) opcode_list: Vec<String>,
30 pub(crate) last_index: usize,
31
32 pub(crate) stack_labels: bool,
33 pub(crate) buf_utf: bool,
35 pub(crate) show_shortcuts: bool,
36 pub(crate) active_buffer: BufferKind,
38}
39
40impl<'a> TUIContext<'a> {
41 pub(crate) fn new(debugger_context: &'a mut DebuggerContext) -> Self {
42 TUIContext {
43 debugger_context,
44
45 key_buffer: String::with_capacity(64),
46 current_step: 0,
47 draw_memory: DrawMemory::default(),
48 opcode_list: Vec::new(),
49 last_index: 0,
50
51 stack_labels: false,
52 buf_utf: false,
53 show_shortcuts: true,
54 active_buffer: BufferKind::Memory,
55 }
56 }
57
58 pub(crate) fn init(&mut self) {
59 self.gen_opcode_list();
60 }
61
62 pub(crate) fn debug_arena(&self) -> &[DebugNode] {
63 &self.debugger_context.debug_arena
64 }
65
66 pub(crate) fn debug_call(&self) -> &DebugNode {
67 &self.debug_arena()[self.draw_memory.inner_call_index]
68 }
69
70 pub(crate) fn address(&self) -> &Address {
72 &self.debug_call().address
73 }
74
75 pub(crate) fn call_kind(&self) -> CallKind {
77 self.debug_call().kind
78 }
79
80 pub(crate) fn debug_steps(&self) -> &[CallTraceStep] {
82 &self.debug_call().steps
83 }
84
85 pub(crate) fn current_step(&self) -> &CallTraceStep {
87 &self.debug_steps()[self.current_step]
88 }
89
90 fn gen_opcode_list(&mut self) {
91 self.opcode_list.clear();
92 let debug_steps =
93 &self.debugger_context.debug_arena[self.draw_memory.inner_call_index].steps;
94 for step in debug_steps {
95 self.opcode_list.push(pretty_opcode(step));
96 }
97 }
98
99 fn gen_opcode_list_if_necessary(&mut self) {
100 if self.last_index != self.draw_memory.inner_call_index {
101 self.gen_opcode_list();
102 self.last_index = self.draw_memory.inner_call_index;
103 }
104 }
105
106 fn active_buffer(&self) -> &[u8] {
107 match self.active_buffer {
108 BufferKind::Memory => self.current_step().memory.as_ref().unwrap().as_bytes(),
109 BufferKind::Calldata => &self.debug_call().calldata,
110 BufferKind::Returndata => &self.current_step().returndata,
111 }
112 }
113}
114
115impl TUIContext<'_> {
116 pub(crate) fn handle_event(&mut self, event: Event) -> ControlFlow<ExitReason> {
117 let ret = match event {
118 Event::Key(event) => self.handle_key_event(event),
119 Event::Mouse(event) => self.handle_mouse_event(event),
120 _ => ControlFlow::Continue(()),
121 };
122 self.gen_opcode_list_if_necessary();
124 ret
125 }
126
127 fn handle_key_event(&mut self, event: KeyEvent) -> ControlFlow<ExitReason> {
128 if let KeyCode::Char(c) = event.code
130 && c.is_alphabetic()
131 && self.key_buffer.starts_with('\'')
132 {
133 self.handle_breakpoint(c);
134 return ControlFlow::Continue(());
135 }
136
137 let control = event.modifiers.contains(KeyModifiers::CONTROL);
138
139 match event.code {
140 KeyCode::Char('q') => return ControlFlow::Break(ExitReason::CharExit),
142
143 KeyCode::Char('k') | KeyCode::Up if control => self.repeat(|this| {
145 this.draw_memory.current_buf_startline =
146 this.draw_memory.current_buf_startline.saturating_sub(1);
147 }),
148 KeyCode::Char('j') | KeyCode::Down if control => self.repeat(|this| {
150 let max_buf = (this.active_buffer().len() / 32).saturating_sub(1);
151 if this.draw_memory.current_buf_startline < max_buf {
152 this.draw_memory.current_buf_startline += 1;
153 }
154 }),
155
156 KeyCode::Char('k') | KeyCode::Up => self.repeat(Self::step_back),
158 KeyCode::Char('j') | KeyCode::Down => self.repeat(Self::step),
160
161 KeyCode::Char('K') => self.repeat(|this| {
163 this.draw_memory.current_stack_startline =
164 this.draw_memory.current_stack_startline.saturating_sub(1);
165 }),
166 KeyCode::Char('J') => self.repeat(|this| {
168 let max_stack =
169 this.current_step().stack.as_ref().map_or(0, |s| s.len()).saturating_sub(1);
170 if this.draw_memory.current_stack_startline < max_stack {
171 this.draw_memory.current_stack_startline += 1;
172 }
173 }),
174
175 KeyCode::Char('b') => {
177 self.active_buffer = self.active_buffer.next();
178 self.draw_memory.current_buf_startline = 0;
179 }
180
181 KeyCode::Char('g') => {
183 self.draw_memory.inner_call_index = 0;
184 self.current_step = 0;
185 }
186
187 KeyCode::Char('G') => {
189 self.draw_memory.inner_call_index = self.debug_arena().len() - 1;
190 self.current_step = self.n_steps() - 1;
191 }
192
193 KeyCode::Char('c') => {
195 self.draw_memory.inner_call_index =
196 self.draw_memory.inner_call_index.saturating_sub(1);
197 self.current_step = self.n_steps() - 1;
198 }
199
200 KeyCode::Char('C')
202 if self.debug_arena().len() > self.draw_memory.inner_call_index + 1 =>
203 {
204 self.draw_memory.inner_call_index += 1;
205 self.current_step = 0;
206 }
207
208 KeyCode::Char('s') => self.repeat(|this| {
210 let remaining_steps = &this.debug_steps()[this.current_step..];
211 if let Some((i, _)) =
212 remaining_steps.iter().enumerate().skip(1).find(|(i, step)| {
213 let prev = &remaining_steps[*i - 1];
214 is_jump(step, prev)
215 })
216 {
217 this.current_step += i
218 }
219 }),
220
221 KeyCode::Char('a') => self.repeat(|this| {
223 let ops = &this.debug_steps()[..this.current_step];
224 this.current_step = ops
225 .iter()
226 .enumerate()
227 .skip(1)
228 .rev()
229 .find(|&(i, op)| {
230 let prev = &ops[i - 1];
231 is_jump(op, prev)
232 })
233 .map(|(i, _)| i)
234 .unwrap_or_default();
235 }),
236
237 KeyCode::Char('t') => self.stack_labels = !self.stack_labels,
239
240 KeyCode::Char('m') => self.buf_utf = !self.buf_utf,
242
243 KeyCode::Char('h') => self.show_shortcuts = !self.show_shortcuts,
245
246 KeyCode::Char(
248 other @ ('0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '\''),
249 ) => {
250 self.key_buffer.push(other);
252 return ControlFlow::Continue(());
253 }
254
255 _ => {}
257 };
258
259 self.key_buffer.clear();
260 ControlFlow::Continue(())
261 }
262
263 fn handle_breakpoint(&mut self, c: char) {
264 if let Some((caller, pc)) = self.debugger_context.breakpoints.get(&c) {
267 for (i, node) in self.debug_arena().iter().enumerate() {
268 if node.address == *caller
269 && let Some(step) = node.steps.iter().position(|step| step.pc == *pc)
270 {
271 self.draw_memory.inner_call_index = i;
272 self.current_step = step;
273 break;
274 }
275 }
276 }
277 self.key_buffer.clear();
278 }
279
280 fn handle_mouse_event(&mut self, event: MouseEvent) -> ControlFlow<ExitReason> {
281 match event.kind {
282 MouseEventKind::ScrollUp => self.step_back(),
283 MouseEventKind::ScrollDown => self.step(),
284 _ => {}
285 }
286
287 ControlFlow::Continue(())
288 }
289
290 fn step_back(&mut self) {
291 if self.current_step > 0 {
292 self.current_step -= 1;
293 } else if self.draw_memory.inner_call_index > 0 {
294 self.draw_memory.inner_call_index -= 1;
295 self.current_step = self.n_steps() - 1;
296 }
297 }
298
299 fn step(&mut self) {
300 if self.current_step < self.n_steps() - 1 {
301 self.current_step += 1;
302 } else if self.draw_memory.inner_call_index < self.debug_arena().len() - 1 {
303 self.draw_memory.inner_call_index += 1;
304 self.current_step = 0;
305 }
306 }
307
308 fn repeat(&mut self, mut f: impl FnMut(&mut Self)) {
310 for _ in 0..buffer_as_number(&self.key_buffer) {
311 f(self);
312 }
313 }
314
315 fn n_steps(&self) -> usize {
316 self.debug_steps().len()
317 }
318}
319
320impl TuiApp for TUIContext<'_> {
321 type Exit = ExitReason;
322
323 fn draw(&mut self, frame: &mut Frame<'_>) {
324 self.draw_layout(frame);
325 }
326
327 fn handle_event(&mut self, event: Event) -> ControlFlow<Self::Exit> {
328 TUIContext::handle_event(self, event)
329 }
330}
331
332fn buffer_as_number(s: &str) -> usize {
334 const MIN: usize = 1;
335 const MAX: usize = 100_000;
336 s.parse().unwrap_or(MIN).clamp(MIN, MAX)
337}
338
339fn pretty_opcode(step: &CallTraceStep) -> String {
340 if let Some(immediate) = step.immediate_bytes.as_ref().filter(|b| !b.is_empty()) {
341 format!("{}(0x{})", step.op, hex::encode(immediate))
342 } else {
343 step.op.to_string()
344 }
345}
346
347fn is_jump(step: &CallTraceStep, prev: &CallTraceStep) -> bool {
348 if !matches!(prev.op, OpCode::JUMP | OpCode::JUMPI) {
349 return false;
350 }
351
352 let immediate_len = prev.immediate_bytes.as_ref().map_or(0, |b| b.len());
353
354 step.pc != prev.pc + 1 + immediate_len
355}