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