From 52c20864ba2af1e36ceaaae634db695dcba981b4 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Thu, 27 Jun 2024 09:47:13 +0400 Subject: [PATCH] refactor: use `revm-inspectors` traces for debugger (#8249) * move calldata to DebugNode * refactor: use tracer from inspectors for debugger * fix: rm hex * clippy * bump inspectors * newline * docs * fix * fmt --- Cargo.lock | 7 +- Cargo.toml | 3 +- crates/cli/src/utils/cmd.rs | 13 +- crates/debugger/Cargo.toml | 1 + crates/debugger/src/lib.rs | 3 + crates/debugger/src/node.rs | 85 ++++++ crates/debugger/src/tui/builder.rs | 17 +- crates/debugger/src/tui/context.rs | 48 +++- crates/debugger/src/tui/draw.rs | 119 +++++---- crates/debugger/src/tui/mod.rs | 8 +- crates/evm/core/Cargo.toml | 1 - crates/evm/core/src/debug.rs | 242 ------------------ crates/evm/core/src/lib.rs | 1 - crates/evm/evm/Cargo.toml | 1 - crates/evm/evm/src/executors/fuzz/mod.rs | 14 +- crates/evm/evm/src/executors/fuzz/types.rs | 5 - .../evm/evm/src/executors/invariant/replay.rs | 4 +- crates/evm/evm/src/executors/mod.rs | 17 +- crates/evm/evm/src/inspectors/debugger.rs | 150 ----------- crates/evm/evm/src/inspectors/mod.rs | 3 - crates/evm/evm/src/inspectors/stack.rs | 57 ++--- crates/evm/evm/src/lib.rs | 2 +- crates/evm/fuzz/src/lib.rs | 5 +- crates/forge/bin/cmd/test/mod.rs | 4 +- crates/forge/src/result.rs | 6 +- crates/forge/src/runner.rs | 51 +--- crates/script/src/execute.rs | 15 +- crates/script/src/lib.rs | 4 +- crates/script/src/runner.rs | 38 +-- 29 files changed, 280 insertions(+), 644 deletions(-) create mode 100644 crates/debugger/src/node.rs delete mode 100644 crates/evm/core/src/debug.rs delete mode 100644 crates/evm/evm/src/inspectors/debugger.rs diff --git a/Cargo.lock b/Cargo.lock index 06ece26b7e83..07ace0506a4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3842,6 +3842,7 @@ dependencies = [ "ratatui", "revm", "revm-inspectors", + "serde", "tracing", ] @@ -3853,7 +3854,6 @@ dependencies = [ "alloy-json-abi", "alloy-primitives", "alloy-sol-types", - "arrayvec", "eyre", "foundry-cheatcodes", "foundry-common", @@ -3900,7 +3900,6 @@ dependencies = [ "alloy-serde", "alloy-sol-types", "alloy-transport", - "arrayvec", "auto_impl", "eyre", "foundry-cheatcodes-spec", @@ -6991,9 +6990,9 @@ dependencies = [ [[package]] name = "revm-inspectors" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b0971cad2f8f1ecb10e270d80646e63bf19daef0dc0a17a45680d24bb346b7c" +checksum = "f3e260c899e462b4e189a3bfcf5a947bc3506f8bd89183859604bca009b57530" dependencies = [ "alloy-primitives", "alloy-rpc-types", diff --git a/Cargo.toml b/Cargo.toml index 80df56818f00..fa1f01148057 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -155,7 +155,7 @@ solang-parser = "=0.3.3" # no default features to avoid c-kzg revm = { version = "10.0.0", default-features = false } revm-primitives = { version = "5.0.0", default-features = false } -revm-inspectors = { version = "0.1.2", features = ["serde"] } +revm-inspectors = { version = "0.2", features = ["serde"] } ## ethers ethers-contract-abigen = { version = "2.0.14", default-features = false } @@ -205,7 +205,6 @@ quote = "1.0" syn = "2.0" prettyplease = "0.2.20" ahash = "0.8" -arrayvec = "0.7" base64 = "0.22" chrono = { version = "0.4", default-features = false, features = [ "clock", diff --git a/crates/cli/src/utils/cmd.rs b/crates/cli/src/utils/cmd.rs index 13da8d01df8d..8c9c124f6173 100644 --- a/crates/cli/src/utils/cmd.rs +++ b/crates/cli/src/utils/cmd.rs @@ -11,7 +11,6 @@ use foundry_compilers::{ use foundry_config::{error::ExtractConfigError, figment::Figment, Chain, Config, NamedChain}; use foundry_debugger::Debugger; use foundry_evm::{ - debug::DebugArena, executors::{DeployResult, EvmError, RawCallResult}, opts::EvmOpts, traces::{ @@ -315,20 +314,14 @@ pub fn read_constructor_args_file(constructor_args_path: PathBuf) -> Result, - pub debug: Option, pub gas_used: u64, } impl TraceResult { /// Create a new [`TraceResult`] from a [`RawCallResult`]. pub fn from_raw(raw: RawCallResult, trace_kind: TraceKind) -> Self { - let RawCallResult { gas_used, traces, reverted, debug, .. } = raw; - Self { - success: !reverted, - traces: traces.map(|arena| vec![(trace_kind, arena)]), - debug, - gas_used, - } + let RawCallResult { gas_used, traces, reverted, .. } = raw; + Self { success: !reverted, traces: traces.map(|arena| vec![(trace_kind, arena)]), gas_used } } } @@ -391,7 +384,7 @@ pub async fn handle_traces( Default::default() }; let mut debugger = Debugger::builder() - .debug_arena(result.debug.as_ref().expect("missing debug arena")) + .traces(result.traces.expect("missing traces")) .decoder(&decoder) .sources(sources) .build(); diff --git a/crates/debugger/Cargo.toml b/crates/debugger/Cargo.toml index 812c6fdada92..094f2aa1fb6b 100644 --- a/crates/debugger/Cargo.toml +++ b/crates/debugger/Cargo.toml @@ -26,3 +26,4 @@ eyre.workspace = true ratatui = { version = "0.26", default-features = false, features = ["crossterm"] } revm.workspace = true tracing.workspace = true +serde.workspace = true diff --git a/crates/debugger/src/lib.rs b/crates/debugger/src/lib.rs index ed5da934271a..678ae8672d36 100644 --- a/crates/debugger/src/lib.rs +++ b/crates/debugger/src/lib.rs @@ -12,3 +12,6 @@ mod op; mod tui; pub use tui::{Debugger, DebuggerBuilder, ExitReason}; + +mod node; +pub use node::DebugNode; diff --git a/crates/debugger/src/node.rs b/crates/debugger/src/node.rs new file mode 100644 index 000000000000..83477f006c8c --- /dev/null +++ b/crates/debugger/src/node.rs @@ -0,0 +1,85 @@ +use alloy_primitives::{Address, Bytes}; +use foundry_evm_traces::{CallKind, CallTraceArena}; +use revm_inspectors::tracing::types::{CallTraceStep, TraceMemberOrder}; +use serde::{Deserialize, Serialize}; + +/// Represents a part of the execution frame before the next call or end of the execution. +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct DebugNode { + /// Execution context. + /// + /// Note that this is the address of the *code*, not necessarily the address of the storage. + pub address: Address, + /// The kind of call this is. + pub kind: CallKind, + /// Calldata of the call. + pub calldata: Bytes, + /// The debug steps. + pub steps: Vec, +} + +impl DebugNode { + /// Creates a new debug node. + pub fn new( + address: Address, + kind: CallKind, + steps: Vec, + calldata: Bytes, + ) -> Self { + Self { address, kind, steps, calldata } + } +} + +/// Flattens given [CallTraceArena] into a list of [DebugNode]s. +/// +/// This is done by recursively traversing the call tree and collecting the steps in-between the +/// calls. +pub fn flatten_call_trace(arena: CallTraceArena, out: &mut Vec) { + #[derive(Debug, Clone, Copy)] + struct PendingNode { + node_idx: usize, + steps_count: usize, + } + + fn inner(arena: &CallTraceArena, node_idx: usize, out: &mut Vec) { + let mut pending = PendingNode { node_idx, steps_count: 0 }; + let node = &arena.nodes()[node_idx]; + for order in node.ordering.iter() { + match order { + TraceMemberOrder::Call(idx) => { + out.push(pending); + pending.steps_count = 0; + inner(arena, node.children[*idx], out); + } + TraceMemberOrder::Step(_) => { + pending.steps_count += 1; + } + _ => {} + } + } + out.push(pending); + } + let mut nodes = Vec::new(); + inner(&arena, 0, &mut nodes); + + let mut arena_nodes = arena.into_nodes(); + + for pending in nodes { + let steps = { + let other_steps = + arena_nodes[pending.node_idx].trace.steps.split_off(pending.steps_count); + std::mem::replace(&mut arena_nodes[pending.node_idx].trace.steps, other_steps) + }; + + // Skip nodes with empty steps as there's nothing to display for them. + if steps.is_empty() { + continue + } + + let call = &arena_nodes[pending.node_idx].trace; + let calldata = if call.kind.is_any_create() { Bytes::new() } else { call.data.clone() }; + let node = DebugNode::new(call.address, call.kind, steps, calldata); + + out.push(node); + } +} diff --git a/crates/debugger/src/tui/builder.rs b/crates/debugger/src/tui/builder.rs index 6289b0b8814f..70055243ea38 100644 --- a/crates/debugger/src/tui/builder.rs +++ b/crates/debugger/src/tui/builder.rs @@ -1,10 +1,9 @@ //! TUI debugger builder. -use crate::Debugger; +use crate::{node::flatten_call_trace, DebugNode, Debugger}; use alloy_primitives::Address; use foundry_common::{compile::ContractSources, evm::Breakpoints, get_contract_name}; -use foundry_evm_core::debug::{DebugArena, DebugNodeFlat}; -use foundry_evm_traces::CallTraceDecoder; +use foundry_evm_traces::{CallTraceArena, CallTraceDecoder, Traces}; use std::collections::HashMap; /// Debugger builder. @@ -12,7 +11,7 @@ use std::collections::HashMap; #[must_use = "builders do nothing unless you call `build` on them"] pub struct DebuggerBuilder { /// Debug traces returned from the EVM execution. - debug_arena: Vec, + debug_arena: Vec, /// Identified contracts. identified_contracts: HashMap, /// Map of source files. @@ -30,17 +29,17 @@ impl DebuggerBuilder { /// Extends the debug arena. #[inline] - pub fn debug_arenas(mut self, arena: &[DebugArena]) -> Self { - for arena in arena { - self = self.debug_arena(arena); + pub fn traces(mut self, traces: Traces) -> Self { + for (_, arena) in traces { + self = self.trace_arena(arena); } self } /// Extends the debug arena. #[inline] - pub fn debug_arena(mut self, arena: &DebugArena) -> Self { - arena.flatten_to(0, &mut self.debug_arena); + pub fn trace_arena(mut self, arena: CallTraceArena) -> Self { + flatten_call_trace(arena, &mut self.debug_arena); self } diff --git a/crates/debugger/src/tui/context.rs b/crates/debugger/src/tui/context.rs index 22ea7d5d3227..f4696c46e697 100644 --- a/crates/debugger/src/tui/context.rs +++ b/crates/debugger/src/tui/context.rs @@ -1,10 +1,10 @@ //! Debugger context and event handler implementation. -use crate::{Debugger, ExitReason}; -use alloy_primitives::Address; +use crate::{DebugNode, Debugger, ExitReason}; +use alloy_primitives::{hex, Address}; use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind}; -use foundry_evm_core::debug::{DebugNodeFlat, DebugStep}; -use revm_inspectors::tracing::types::CallKind; +use revm::interpreter::opcode; +use revm_inspectors::tracing::types::{CallKind, CallTraceStep}; use std::ops::ControlFlow; /// This is currently used to remember last scroll position so screen doesn't wiggle as much. @@ -84,11 +84,11 @@ impl<'a> DebuggerContext<'a> { self.gen_opcode_list(); } - pub(crate) fn debug_arena(&self) -> &[DebugNodeFlat] { + pub(crate) fn debug_arena(&self) -> &[DebugNode] { &self.debugger.debug_arena } - pub(crate) fn debug_call(&self) -> &DebugNodeFlat { + pub(crate) fn debug_call(&self) -> &DebugNode { &self.debug_arena()[self.draw_memory.inner_call_index] } @@ -103,19 +103,21 @@ impl<'a> DebuggerContext<'a> { } /// Returns the current debug steps. - pub(crate) fn debug_steps(&self) -> &[DebugStep] { + pub(crate) fn debug_steps(&self) -> &[CallTraceStep] { &self.debug_call().steps } /// Returns the current debug step. - pub(crate) fn current_step(&self) -> &DebugStep { + pub(crate) fn current_step(&self) -> &CallTraceStep { &self.debug_steps()[self.current_step] } fn gen_opcode_list(&mut self) { self.opcode_list.clear(); let debug_steps = &self.debugger.debug_arena[self.draw_memory.inner_call_index].steps; - self.opcode_list.extend(debug_steps.iter().map(DebugStep::pretty_opcode)); + for (i, step) in debug_steps.iter().enumerate() { + self.opcode_list.push(pretty_opcode(step, debug_steps.get(i + 1))); + } } fn gen_opcode_list_if_necessary(&mut self) { @@ -127,8 +129,8 @@ impl<'a> DebuggerContext<'a> { fn active_buffer(&self) -> &[u8] { match self.active_buffer { - BufferKind::Memory => &self.current_step().memory, - BufferKind::Calldata => &self.current_step().calldata, + BufferKind::Memory => self.current_step().memory.as_bytes(), + BufferKind::Calldata => &self.debug_call().calldata, BufferKind::Returndata => &self.current_step().returndata, } } @@ -186,7 +188,8 @@ impl DebuggerContext<'_> { }), // Scroll down the stack KeyCode::Char('J') => self.repeat(|this| { - let max_stack = this.current_step().stack.len().saturating_sub(1); + let max_stack = + this.current_step().stack.as_ref().map_or(0, |s| s.len()).saturating_sub(1); if this.draw_memory.current_stack_startline < max_stack { this.draw_memory.current_stack_startline += 1; } @@ -345,3 +348,24 @@ fn buffer_as_number(s: &str) -> usize { const MAX: usize = 100_000; s.parse().unwrap_or(MIN).clamp(MIN, MAX) } + +fn pretty_opcode(step: &CallTraceStep, next_step: Option<&CallTraceStep>) -> String { + let op = step.op; + let instruction = op.get(); + let push_size = if (opcode::PUSH1..=opcode::PUSH32).contains(&instruction) { + (instruction - opcode::PUSH0) as usize + } else { + 0 + }; + if push_size == 0 { + return step.op.to_string(); + } + + // Get push byte as the top-most stack item on the next step + if let Some(pushed) = next_step.and_then(|s| s.stack.as_ref()).and_then(|s| s.last()) { + let bytes = &pushed.to_be_bytes_vec()[32 - push_size..]; + format!("{op}(0x{})", hex::encode(bytes)) + } else { + op.to_string() + } +} diff --git a/crates/debugger/src/tui/draw.rs b/crates/debugger/src/tui/draw.rs index 7ac0c64e5c6e..e53910626835 100644 --- a/crates/debugger/src/tui/draw.rs +++ b/crates/debugger/src/tui/draw.rs @@ -429,7 +429,7 @@ impl DebuggerContext<'_> { "Address: {} | PC: {} | Gas used in call: {}", self.address(), self.current_step().pc, - self.current_step().total_gas_used, + self.current_step().gas_used, ); let block = Block::default().title(title).borders(Borders::ALL); let list = List::new(items) @@ -443,58 +443,67 @@ impl DebuggerContext<'_> { fn draw_stack(&self, f: &mut Frame<'_>, area: Rect) { let step = self.current_step(); - let stack = &step.stack; + let stack = step.stack.as_ref(); + let stack_len = stack.map_or(0, |s| s.len()); - let min_len = decimal_digits(stack.len()).max(2); + let min_len = decimal_digits(stack_len).max(2); - let params = OpcodeParam::of(step.instruction); + let params = OpcodeParam::of(step.op.get()); let text: Vec> = stack - .iter() - .rev() - .enumerate() - .skip(self.draw_memory.current_stack_startline) - .map(|(i, stack_item)| { - let param = params.iter().find(|param| param.index == i); - - let mut spans = Vec::with_capacity(1 + 32 * 2 + 3); - - // Stack index. - spans.push(Span::styled(format!("{i:0min_len$}| "), Style::new().fg(Color::White))); - - // Item hex bytes. - hex_bytes_spans(&stack_item.to_be_bytes::<32>(), &mut spans, |_, _| { - if param.is_some() { - Style::new().fg(Color::Cyan) - } else { - Style::new().fg(Color::White) - } - }); + .map(|stack| { + stack + .iter() + .rev() + .enumerate() + .skip(self.draw_memory.current_stack_startline) + .map(|(i, stack_item)| { + let param = params.iter().find(|param| param.index == i); + + let mut spans = Vec::with_capacity(1 + 32 * 2 + 3); + + // Stack index. + spans.push(Span::styled( + format!("{i:0min_len$}| "), + Style::new().fg(Color::White), + )); + + // Item hex bytes. + hex_bytes_spans(&stack_item.to_be_bytes::<32>(), &mut spans, |_, _| { + if param.is_some() { + Style::new().fg(Color::Cyan) + } else { + Style::new().fg(Color::White) + } + }); - if self.stack_labels { - if let Some(param) = param { - spans.push(Span::raw("| ")); - spans.push(Span::raw(param.name)); - } - } + if self.stack_labels { + if let Some(param) = param { + spans.push(Span::raw("| ")); + spans.push(Span::raw(param.name)); + } + } - spans.push(Span::raw("\n")); + spans.push(Span::raw("\n")); - Line::from(spans) + Line::from(spans) + }) + .collect() }) - .collect(); + .unwrap_or_default(); - let title = format!("Stack: {}", stack.len()); + let title = format!("Stack: {stack_len}"); let block = Block::default().title(title).borders(Borders::ALL); let paragraph = Paragraph::new(text).block(block).wrap(Wrap { trim: true }); f.render_widget(paragraph, area); } fn draw_buffer(&self, f: &mut Frame<'_>, area: Rect) { + let call = self.debug_call(); let step = self.current_step(); let buf = match self.active_buffer { BufferKind::Memory => step.memory.as_ref(), - BufferKind::Calldata => step.calldata.as_ref(), + BufferKind::Calldata => call.calldata.as_ref(), BufferKind::Returndata => step.returndata.as_ref(), }; @@ -506,18 +515,20 @@ impl DebuggerContext<'_> { let mut write_offset = None; let mut write_size = None; let mut color = None; - let stack_len = step.stack.len(); + let stack_len = step.stack.as_ref().map_or(0, |s| s.len()); if stack_len > 0 { - if let Some(accesses) = get_buffer_accesses(step.instruction, &step.stack) { - if let Some(read_access) = accesses.read { - offset = Some(read_access.1.offset); - size = Some(read_access.1.size); - color = Some(Color::Cyan); - } - if let Some(write_access) = accesses.write { - if self.active_buffer == BufferKind::Memory { - write_offset = Some(write_access.offset); - write_size = Some(write_access.size); + if let Some(stack) = step.stack.as_ref() { + if let Some(accesses) = get_buffer_accesses(step.op.get(), stack) { + if let Some(read_access) = accesses.read { + offset = Some(read_access.1.offset); + size = Some(read_access.1.size); + color = Some(Color::Cyan); + } + if let Some(write_access) = accesses.write { + if self.active_buffer == BufferKind::Memory { + write_offset = Some(write_access.offset); + write_size = Some(write_access.size); + } } } } @@ -530,13 +541,15 @@ impl DebuggerContext<'_> { if self.current_step > 0 { let prev_step = self.current_step - 1; let prev_step = &self.debug_steps()[prev_step]; - if let Some(write_access) = - get_buffer_accesses(prev_step.instruction, &prev_step.stack).and_then(|a| a.write) - { - if self.active_buffer == BufferKind::Memory { - offset = Some(write_access.offset); - size = Some(write_access.size); - color = Some(Color::Green); + if let Some(stack) = prev_step.stack.as_ref() { + if let Some(write_access) = + get_buffer_accesses(prev_step.op.get(), stack).and_then(|a| a.write) + { + if self.active_buffer == BufferKind::Memory { + offset = Some(write_access.offset); + size = Some(write_access.size); + color = Some(Color::Green); + } } } } diff --git a/crates/debugger/src/tui/mod.rs b/crates/debugger/src/tui/mod.rs index c810440e5b13..facbd9e69f13 100644 --- a/crates/debugger/src/tui/mod.rs +++ b/crates/debugger/src/tui/mod.rs @@ -8,7 +8,7 @@ use crossterm::{ }; use eyre::Result; use foundry_common::{compile::ContractSources, evm::Breakpoints}; -use foundry_evm_core::{debug::DebugNodeFlat, utils::PcIcMap}; +use foundry_evm_core::utils::PcIcMap; use ratatui::{ backend::{Backend, CrosstermBackend}, Terminal, @@ -28,6 +28,8 @@ pub use builder::DebuggerBuilder; mod context; use context::DebuggerContext; +use crate::DebugNode; + mod draw; type DebuggerTerminal = Terminal>; @@ -41,7 +43,7 @@ pub enum ExitReason { /// The TUI debugger. pub struct Debugger { - debug_arena: Vec, + debug_arena: Vec, identified_contracts: HashMap, /// Source map of contract sources contracts_sources: ContractSources, @@ -59,7 +61,7 @@ impl Debugger { /// Creates a new debugger. pub fn new( - debug_arena: Vec, + debug_arena: Vec, identified_contracts: HashMap, contracts_sources: ContractSources, breakpoints: Breakpoints, diff --git a/crates/evm/core/Cargo.toml b/crates/evm/core/Cargo.toml index 7369e55032fb..45d710e5580d 100644 --- a/crates/evm/core/Cargo.toml +++ b/crates/evm/core/Cargo.toml @@ -47,7 +47,6 @@ revm = { workspace = true, features = [ ] } revm-inspectors.workspace = true -arrayvec.workspace = true auto_impl.workspace = true eyre.workspace = true futures.workspace = true diff --git a/crates/evm/core/src/debug.rs b/crates/evm/core/src/debug.rs deleted file mode 100644 index bde6e338c754..000000000000 --- a/crates/evm/core/src/debug.rs +++ /dev/null @@ -1,242 +0,0 @@ -use crate::opcodes; -use alloy_primitives::{hex, Address, Bytes, U256}; -use arrayvec::ArrayVec; -use revm::interpreter::OpCode; -use revm_inspectors::tracing::types::CallKind; -use serde::{Deserialize, Serialize}; - -/// An arena of [DebugNode]s -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct DebugArena { - /// The arena of nodes - pub arena: Vec, -} - -impl Default for DebugArena { - fn default() -> Self { - Self::new() - } -} - -impl DebugArena { - /// Creates a new debug arena. - pub const fn new() -> Self { - Self { arena: Vec::new() } - } - - /// Pushes a new debug node into the arena - pub fn push_node(&mut self, mut new_node: DebugNode) -> usize { - fn recursively_push( - arena: &mut Vec, - entry: usize, - mut new_node: DebugNode, - ) -> usize { - match new_node.depth { - // We found the parent node, add the new node as a child - _ if arena[entry].depth == new_node.depth - 1 => { - let id = arena.len(); - new_node.location = arena[entry].children.len(); - new_node.parent = Some(entry); - arena[entry].children.push(id); - arena.push(new_node); - id - } - // We haven't found the parent node, go deeper - _ => { - let child = *arena[entry].children.last().expect("Disconnected debug node"); - recursively_push(arena, child, new_node) - } - } - } - - if self.arena.is_empty() { - // This is the initial node at depth 0, so we just insert it. - self.arena.push(new_node); - 0 - } else if new_node.depth == 0 { - // This is another node at depth 0, for example instructions between calls. We insert - // it as a child of the original root node. - let id = self.arena.len(); - new_node.location = self.arena[0].children.len(); - new_node.parent = Some(0); - self.arena[0].children.push(id); - self.arena.push(new_node); - id - } else { - // We try to find the parent of this node recursively - recursively_push(&mut self.arena, 0, new_node) - } - } - - /// Recursively traverses the tree of debug nodes and flattens it into a [Vec] where each - /// item contains: - /// - /// - The address of the contract being executed - /// - A [Vec] of debug steps along that contract's execution path - /// - An enum denoting the type of call this is - /// - /// This makes it easy to pretty print the execution steps. - pub fn flatten(&self, entry: usize) -> Vec { - let mut flattened = Vec::new(); - self.flatten_to(entry, &mut flattened); - flattened - } - - /// Recursively traverses the tree of debug nodes and flattens it into the given list. - /// - /// See [`flatten`](Self::flatten) for more information. - pub fn flatten_to(&self, entry: usize, out: &mut Vec) { - let node = &self.arena[entry]; - - if !node.steps.is_empty() { - out.push(node.flat()); - } - - for child in &node.children { - self.flatten_to(*child, out); - } - } -} - -/// A node in the arena. -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct DebugNode { - /// Parent node index in the arena. - pub parent: Option, - /// Children node indexes in the arena. - pub children: Vec, - /// Location in parent. - pub location: usize, - /// Execution context. - /// - /// Note that this is the address of the *code*, not necessarily the address of the storage. - pub address: Address, - /// The kind of call this is. - pub kind: CallKind, - /// Depth of the call. - pub depth: usize, - /// The debug steps. - pub steps: Vec, -} - -impl From for DebugNodeFlat { - #[inline] - fn from(node: DebugNode) -> Self { - node.into_flat() - } -} - -impl From<&DebugNode> for DebugNodeFlat { - #[inline] - fn from(node: &DebugNode) -> Self { - node.flat() - } -} - -impl DebugNode { - /// Creates a new debug node. - pub fn new(address: Address, depth: usize, steps: Vec) -> Self { - Self { address, depth, steps, ..Default::default() } - } - - /// Flattens this node into a [`DebugNodeFlat`]. - pub fn flat(&self) -> DebugNodeFlat { - DebugNodeFlat { address: self.address, kind: self.kind, steps: self.steps.clone() } - } - - /// Flattens this node into a [`DebugNodeFlat`]. - pub fn into_flat(self) -> DebugNodeFlat { - DebugNodeFlat { address: self.address, kind: self.kind, steps: self.steps } - } -} - -/// Flattened [`DebugNode`] from an arena. -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct DebugNodeFlat { - /// Execution context. - /// - /// Note that this is the address of the *code*, not necessarily the address of the storage. - pub address: Address, - /// The kind of call this is. - pub kind: CallKind, - /// The debug steps. - pub steps: Vec, -} - -impl DebugNodeFlat { - /// Creates a new debug node flat. - pub fn new(address: Address, kind: CallKind, steps: Vec) -> Self { - Self { address, kind, steps } - } -} - -/// A `DebugStep` is a snapshot of the EVM's runtime state. -/// -/// It holds the current program counter (where in the program you are), -/// the stack and memory (prior to the opcodes execution), any bytes to be -/// pushed onto the stack, and the instruction counter for use with sourcemap. -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct DebugStep { - /// Stack *prior* to running the associated opcode - pub stack: Vec, - /// Memory *prior* to running the associated opcode - pub memory: Bytes, - /// Calldata *prior* to running the associated opcode - pub calldata: Bytes, - /// Returndata *prior* to running the associated opcode - pub returndata: Bytes, - /// Opcode to be executed - pub instruction: u8, - /// Optional bytes that are being pushed onto the stack. - /// Empty if the opcode is not a push or PUSH0. - #[serde(serialize_with = "hex::serialize", deserialize_with = "deserialize_arrayvec_hex")] - pub push_bytes: ArrayVec, - /// The program counter at this step. - /// - /// Note: To map this step onto source code using a source map, you must convert the program - /// counter to an instruction counter. - pub pc: usize, - /// Cumulative gas usage - pub total_gas_used: u64, -} - -impl Default for DebugStep { - fn default() -> Self { - Self { - stack: vec![], - memory: Default::default(), - calldata: Default::default(), - returndata: Default::default(), - instruction: revm::interpreter::opcode::INVALID, - push_bytes: Default::default(), - pc: 0, - total_gas_used: 0, - } - } -} - -impl DebugStep { - /// Pretty print the step's opcode - pub fn pretty_opcode(&self) -> String { - let instruction = OpCode::new(self.instruction).map_or("INVALID", |op| op.as_str()); - if !self.push_bytes.is_empty() { - format!("{instruction}(0x{})", hex::encode(&self.push_bytes)) - } else { - instruction.to_string() - } - } - - /// Returns `true` if the opcode modifies memory. - pub fn opcode_modifies_memory(&self) -> bool { - OpCode::new(self.instruction).map_or(false, opcodes::modifies_memory) - } -} - -fn deserialize_arrayvec_hex<'de, D: serde::Deserializer<'de>>( - deserializer: D, -) -> Result, D::Error> { - let bytes: Vec = hex::deserialize(deserializer)?; - let mut array = ArrayVec::new(); - array.try_extend_from_slice(&bytes).map_err(serde::de::Error::custom)?; - Ok(array) -} diff --git a/crates/evm/core/src/lib.rs b/crates/evm/core/src/lib.rs index 43bf5f3d4b31..12297e806402 100644 --- a/crates/evm/core/src/lib.rs +++ b/crates/evm/core/src/lib.rs @@ -21,7 +21,6 @@ mod ic; pub mod backend; pub mod constants; -pub mod debug; pub mod decode; pub mod fork; pub mod opcodes; diff --git a/crates/evm/evm/Cargo.toml b/crates/evm/evm/Cargo.toml index bcd933a4bc73..39b48d1107ba 100644 --- a/crates/evm/evm/Cargo.toml +++ b/crates/evm/evm/Cargo.toml @@ -44,7 +44,6 @@ revm = { workspace = true, default-features = false, features = [ ] } revm-inspectors.workspace = true -arrayvec.workspace = true eyre.workspace = true parking_lot.workspace = true proptest = "1" diff --git a/crates/evm/evm/src/executors/fuzz/mod.rs b/crates/evm/evm/src/executors/fuzz/mod.rs index 9cd406f62195..5de2590148f4 100644 --- a/crates/evm/evm/src/executors/fuzz/mod.rs +++ b/crates/evm/evm/src/executors/fuzz/mod.rs @@ -3,6 +3,7 @@ use alloy_dyn_abi::JsonAbiExt; use alloy_json_abi::Function; use alloy_primitives::{Address, Bytes, U256}; use eyre::Result; +use foundry_common::evm::Breakpoints; use foundry_config::FuzzConfig; use foundry_evm_core::{ constants::MAGIC_ASSUME, @@ -80,6 +81,9 @@ impl FuzzedExecutor { // Stores coverage information for all fuzz cases let coverage: RefCell> = RefCell::default(); + // Stores breakpoints for the last fuzz case. + let breakpoints: RefCell> = RefCell::default(); + let state = self.build_fuzz_state(); let dictionary_weight = self.config.dictionary.dictionary_weight.min(100); @@ -110,6 +114,7 @@ impl FuzzedExecutor { traces.borrow_mut().pop(); } traces.borrow_mut().push(call_traces); + breakpoints.borrow_mut().replace(case.breakpoints); } match &mut *coverage.borrow_mut() { @@ -140,7 +145,11 @@ impl FuzzedExecutor { let (calldata, call) = counterexample.into_inner(); let mut traces = traces.into_inner(); - let last_run_traces = if run_result.is_ok() { traces.pop() } else { call.traces.clone() }; + let (last_run_traces, last_run_breakpoints) = if run_result.is_ok() { + (traces.pop(), breakpoints.into_inner()) + } else { + (call.traces.clone(), call.cheatcodes.map(|c| c.breakpoints)) + }; let mut result = FuzzTestResult { first_case: first_case.take().unwrap_or_default(), @@ -152,6 +161,7 @@ impl FuzzedExecutor { logs: call.logs, labeled_addresses: call.labels, traces: last_run_traces, + breakpoints: last_run_breakpoints, gas_report_traces: traces, coverage: coverage.into_inner(), }; @@ -219,12 +229,10 @@ impl FuzzedExecutor { case: FuzzCase { calldata, gas: call.gas_used, stipend: call.stipend }, traces: call.traces, coverage: call.coverage, - debug: call.debug, breakpoints, })) } else { Ok(FuzzOutcome::CounterExample(CounterExampleOutcome { - debug: call.debug.clone(), exit_reason: call.exit_reason, counterexample: (calldata, call), breakpoints, diff --git a/crates/evm/evm/src/executors/fuzz/types.rs b/crates/evm/evm/src/executors/fuzz/types.rs index b15cf3faae3c..93931f5b0a96 100644 --- a/crates/evm/evm/src/executors/fuzz/types.rs +++ b/crates/evm/evm/src/executors/fuzz/types.rs @@ -1,7 +1,6 @@ use crate::executors::RawCallResult; use alloy_primitives::Bytes; use foundry_common::evm::Breakpoints; -use foundry_evm_core::debug::DebugArena; use foundry_evm_coverage::HitMaps; use foundry_evm_fuzz::FuzzCase; use foundry_evm_traces::CallTraceArena; @@ -16,8 +15,6 @@ pub struct CaseOutcome { pub traces: Option, /// The coverage info collected during the call pub coverage: Option, - /// The debug nodes of the call - pub debug: Option, /// Breakpoints char pc map pub breakpoints: Breakpoints, } @@ -29,8 +26,6 @@ pub struct CounterExampleOutcome { pub counterexample: (Bytes, RawCallResult), /// The status of the call pub exit_reason: InstructionResult, - /// The debug nodes of the call - pub debug: Option, /// Breakpoints char pc map pub breakpoints: Breakpoints, } diff --git a/crates/evm/evm/src/executors/invariant/replay.rs b/crates/evm/evm/src/executors/invariant/replay.rs index e985cf7ba17e..391bf998804b 100644 --- a/crates/evm/evm/src/executors/invariant/replay.rs +++ b/crates/evm/evm/src/executors/invariant/replay.rs @@ -33,7 +33,9 @@ pub fn replay_run( inputs: &[BasicTxDetails], ) -> Result> { // We want traces for a failed case. - executor.set_tracing(true); + if executor.inspector().tracer.is_none() { + executor.set_tracing(true, false); + } let mut counterexample_sequence = vec![]; diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index ff621a5e20f9..09fd620f5f23 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -19,7 +19,6 @@ use foundry_evm_core::{ CALLER, CHEATCODE_ADDRESS, CHEATCODE_CONTRACT_HASH, DEFAULT_CREATE2_DEPLOYER, DEFAULT_CREATE2_DEPLOYER_CODE, }, - debug::DebugArena, decode::RevertDecoder, utils::StateChangeset, }; @@ -212,14 +211,8 @@ impl Executor { } #[inline] - pub fn set_tracing(&mut self, tracing: bool) -> &mut Self { - self.inspector_mut().tracing(tracing); - self - } - - #[inline] - pub fn set_debugger(&mut self, debugger: bool) -> &mut Self { - self.inspector_mut().enable_debugger(debugger); + pub fn set_tracing(&mut self, tracing: bool, debug: bool) -> &mut Self { + self.inspector_mut().tracing(tracing, debug); self } @@ -713,8 +706,6 @@ pub struct RawCallResult { pub traces: Option, /// The coverage info collected during the call pub coverage: Option, - /// The debug nodes of the call - pub debug: Option, /// Scripted transactions generated from this call pub transactions: Option, /// The changeset of the state. @@ -743,7 +734,6 @@ impl Default for RawCallResult { labels: HashMap::new(), traces: None, coverage: None, - debug: None, transactions: None, state_changeset: HashMap::default(), env: EnvWithHandlerCfg::new_with_spec_id(Box::default(), SpecId::LATEST), @@ -856,7 +846,7 @@ fn convert_executed_result( _ => Bytes::new(), }; - let InspectorData { mut logs, labels, traces, coverage, debug, cheatcodes, chisel_state } = + let InspectorData { mut logs, labels, traces, coverage, cheatcodes, chisel_state } = inspector.collect(); if logs.is_empty() { @@ -880,7 +870,6 @@ fn convert_executed_result( labels, traces, coverage, - debug, transactions, state_changeset, env, diff --git a/crates/evm/evm/src/inspectors/debugger.rs b/crates/evm/evm/src/inspectors/debugger.rs deleted file mode 100644 index c970cd67103e..000000000000 --- a/crates/evm/evm/src/inspectors/debugger.rs +++ /dev/null @@ -1,150 +0,0 @@ -use alloy_primitives::Address; -use arrayvec::ArrayVec; -use foundry_common::ErrorExt; -use foundry_evm_core::{ - backend::DatabaseExt, - debug::{DebugArena, DebugNode, DebugStep}, - utils::gas_used, -}; -use revm::{ - interpreter::{ - opcode, CallInputs, CallOutcome, CreateInputs, CreateOutcome, Gas, InstructionResult, - Interpreter, InterpreterResult, - }, - EvmContext, Inspector, -}; -use revm_inspectors::tracing::types::CallKind; - -/// An inspector that collects debug nodes on every step of the interpreter. -#[derive(Clone, Debug, Default)] -pub struct Debugger { - /// The arena of [DebugNode]s - pub arena: DebugArena, - /// The ID of the current [DebugNode]. - pub head: usize, - /// The current execution address. - pub context: Address, -} - -impl Debugger { - /// Enters a new execution context. - pub fn enter(&mut self, depth: usize, address: Address, kind: CallKind) { - self.context = address; - self.head = self.arena.push_node(DebugNode { depth, address, kind, ..Default::default() }); - } - - /// Exits the current execution context, replacing it with the previous one. - pub fn exit(&mut self) { - if let Some(parent_id) = self.arena.arena[self.head].parent { - let DebugNode { depth, address, kind, .. } = self.arena.arena[parent_id]; - self.enter(depth, address, kind); - } - } -} - -impl Inspector for Debugger { - fn step(&mut self, interp: &mut Interpreter, ecx: &mut EvmContext) { - let pc = interp.program_counter(); - let op = interp.current_opcode(); - - // Extract the push bytes - let push_size = if (opcode::PUSH1..=opcode::PUSH32).contains(&op) { - (op - opcode::PUSH0) as usize - } else { - 0 - }; - let push_bytes = (push_size > 0).then(|| { - let start = pc + 1; - let end = start + push_size; - let slice = &interp.contract.bytecode.bytecode()[start..end]; - debug_assert!(slice.len() <= 32); - let mut array = ArrayVec::new(); - array.try_extend_from_slice(slice).unwrap(); - array - }); - - let total_gas_used = gas_used( - ecx.spec_id(), - interp.gas.limit().saturating_sub(interp.gas.remaining()), - interp.gas.refunded() as u64, - ); - - // Reuse the memory from the previous step if the previous opcode did not modify it. - let memory = self.arena.arena[self.head] - .steps - .last() - .filter(|step| !step.opcode_modifies_memory()) - .map(|step| step.memory.clone()) - .unwrap_or_else(|| interp.shared_memory.context_memory().to_vec().into()); - - self.arena.arena[self.head].steps.push(DebugStep { - pc, - stack: interp.stack().data().clone(), - memory, - calldata: interp.contract().input.clone(), - returndata: interp.return_data_buffer.clone(), - instruction: op, - push_bytes: push_bytes.unwrap_or_default(), - total_gas_used, - }); - } - - fn call(&mut self, ecx: &mut EvmContext, inputs: &mut CallInputs) -> Option { - self.enter( - ecx.journaled_state.depth() as usize, - inputs.bytecode_address, - inputs.scheme.into(), - ); - - None - } - - fn call_end( - &mut self, - _context: &mut EvmContext, - _inputs: &CallInputs, - outcome: CallOutcome, - ) -> CallOutcome { - self.exit(); - - outcome - } - - fn create( - &mut self, - ecx: &mut EvmContext, - inputs: &mut CreateInputs, - ) -> Option { - if let Err(err) = ecx.load_account(inputs.caller) { - let gas = Gas::new(inputs.gas_limit); - return Some(CreateOutcome::new( - InterpreterResult { - result: InstructionResult::Revert, - output: err.abi_encode_revert(), - gas, - }, - None, - )); - } - - let nonce = ecx.journaled_state.account(inputs.caller).info.nonce; - self.enter( - ecx.journaled_state.depth() as usize, - inputs.created_address(nonce), - CallKind::Create, - ); - - None - } - - fn create_end( - &mut self, - _context: &mut EvmContext, - _inputs: &CreateInputs, - outcome: CreateOutcome, - ) -> CreateOutcome { - self.exit(); - - outcome - } -} diff --git a/crates/evm/evm/src/inspectors/mod.rs b/crates/evm/evm/src/inspectors/mod.rs index 786786b28e92..41008397a1cb 100644 --- a/crates/evm/evm/src/inspectors/mod.rs +++ b/crates/evm/evm/src/inspectors/mod.rs @@ -10,9 +10,6 @@ pub use revm_inspectors::access_list::AccessListInspector; mod chisel_state; pub use chisel_state::ChiselState; -mod debugger; -pub use debugger::Debugger; - mod logs; pub use logs::LogCollector; diff --git a/crates/evm/evm/src/inspectors/stack.rs b/crates/evm/evm/src/inspectors/stack.rs index e290a55fc7c1..b098d878a96d 100644 --- a/crates/evm/evm/src/inspectors/stack.rs +++ b/crates/evm/evm/src/inspectors/stack.rs @@ -1,12 +1,11 @@ use super::{ - Cheatcodes, CheatsConfig, ChiselState, CoverageCollector, Debugger, Fuzzer, LogCollector, + Cheatcodes, CheatsConfig, ChiselState, CoverageCollector, Fuzzer, LogCollector, StackSnapshotType, TracingInspector, TracingInspectorConfig, }; use alloy_primitives::{Address, Bytes, Log, TxKind, U256}; use foundry_cheatcodes::CheatcodesExecutor; use foundry_evm_core::{ backend::{update_state, DatabaseExt}, - debug::DebugArena, InspectorExt, }; use foundry_evm_coverage::HitMaps; @@ -47,7 +46,7 @@ pub struct InspectorStackBuilder { pub fuzzer: Option, /// Whether to enable tracing. pub trace: Option, - /// Whether to enable the debugger. + /// Whether to enable debug traces. pub debug: Option, /// Whether logs should be collected. pub logs: Option, @@ -177,9 +176,8 @@ impl InspectorStackBuilder { } stack.collect_coverage(coverage.unwrap_or(false)); stack.collect_logs(logs.unwrap_or(true)); - stack.enable_debugger(debug.unwrap_or(false)); stack.print(print.unwrap_or(false)); - stack.tracing(trace.unwrap_or(false)); + stack.tracing(trace.unwrap_or(false), debug.unwrap_or(false)); stack.enable_isolation(enable_isolation); @@ -243,7 +241,6 @@ pub struct InspectorData { pub logs: Vec, pub labels: HashMap, pub traces: Option, - pub debug: Option, pub coverage: Option, pub cheatcodes: Option, pub chisel_state: Option<(Vec, Vec, InstructionResult)>, @@ -290,7 +287,6 @@ pub struct InspectorStack { pub struct InspectorStackInner { pub chisel_state: Option, pub coverage: Option, - pub debugger: Option, pub fuzzer: Option, pub log_collector: Option, pub printer: Option, @@ -343,7 +339,7 @@ impl InspectorStack { )* }; } - push!(cheatcodes, chisel_state, coverage, debugger, fuzzer, log_collector, printer, tracer); + push!(cheatcodes, chisel_state, coverage, fuzzer, log_collector, printer, tracer); if self.enable_isolation { enabled.push("isolation"); } @@ -398,12 +394,6 @@ impl InspectorStack { self.coverage = yes.then(Default::default); } - /// Set whether to enable the debugger. - #[inline] - pub fn enable_debugger(&mut self, yes: bool) { - self.debugger = yes.then(Default::default); - } - /// Set whether to enable call isolation. #[inline] pub fn enable_isolation(&mut self, yes: bool) { @@ -424,15 +414,21 @@ impl InspectorStack { /// Set whether to enable the tracer. #[inline] - pub fn tracing(&mut self, yes: bool) { + pub fn tracing(&mut self, yes: bool, debug: bool) { self.tracer = yes.then(|| { TracingInspector::new(TracingInspectorConfig { - record_steps: false, - record_memory_snapshots: false, - record_stack_snapshots: StackSnapshotType::None, + record_steps: debug, + record_memory_snapshots: debug, + record_stack_snapshots: if debug { + StackSnapshotType::Full + } else { + StackSnapshotType::None + }, record_state_diff: false, exclude_precompile_calls: false, record_logs: true, + record_opcodes_filter: None, + record_returndata_snapshots: debug, }) }); } @@ -442,8 +438,7 @@ impl InspectorStack { pub fn collect(self) -> InspectorData { let Self { cheatcodes, - inner: - InspectorStackInner { chisel_state, coverage, debugger, log_collector, tracer, .. }, + inner: InspectorStackInner { chisel_state, coverage, log_collector, tracer, .. }, } = self; InspectorData { @@ -453,7 +448,6 @@ impl InspectorStack { .map(|cheatcodes| cheatcodes.labels.clone()) .unwrap_or_default(), traces: tracer.map(|tracer| tracer.get_traces().clone()), - debug: debugger.map(|debugger| debugger.arena), coverage: coverage.map(|coverage| coverage.maps), cheatcodes, chisel_state: chisel_state.and_then(|state| state.state), @@ -493,13 +487,7 @@ impl<'a> InspectorStackRefMut<'a> { let result = outcome.result.result; call_inspectors_adjust_depth!( #[ret] - [ - &mut self.fuzzer, - &mut self.debugger, - &mut self.tracer, - &mut self.cheatcodes, - &mut self.printer, - ], + [&mut self.fuzzer, &mut self.tracer, &mut self.cheatcodes, &mut self.printer,], |inspector| { let new_outcome = inspector.call_end(ecx, inputs, outcome.clone()); @@ -663,7 +651,6 @@ impl<'a, DB: DatabaseExt> Inspector for InspectorStackRefMut<'a> { call_inspectors_adjust_depth!( [ &mut self.fuzzer, - &mut self.debugger, &mut self.tracer, &mut self.coverage, &mut self.cheatcodes, @@ -701,13 +688,7 @@ impl<'a, DB: DatabaseExt> Inspector for InspectorStackRefMut<'a> { call_inspectors_adjust_depth!( #[ret] - [ - &mut self.fuzzer, - &mut self.debugger, - &mut self.tracer, - &mut self.log_collector, - &mut self.printer, - ], + [&mut self.fuzzer, &mut self.tracer, &mut self.log_collector, &mut self.printer,], |inspector| { let mut out = None; if let Some(output) = inspector.call(ecx, call) { @@ -785,7 +766,7 @@ impl<'a, DB: DatabaseExt> Inspector for InspectorStackRefMut<'a> { call_inspectors_adjust_depth!( #[ret] - [&mut self.debugger, &mut self.tracer, &mut self.coverage, &mut self.cheatcodes], + [&mut self.tracer, &mut self.coverage, &mut self.cheatcodes], |inspector| inspector.create(ecx, create).map(Some), self, ecx @@ -826,7 +807,7 @@ impl<'a, DB: DatabaseExt> Inspector for InspectorStackRefMut<'a> { call_inspectors_adjust_depth!( #[ret] - [&mut self.debugger, &mut self.tracer, &mut self.cheatcodes, &mut self.printer], + [&mut self.tracer, &mut self.cheatcodes, &mut self.printer], |inspector| { let new_outcome = inspector.create_end(ecx, call, outcome.clone()); diff --git a/crates/evm/evm/src/lib.rs b/crates/evm/evm/src/lib.rs index 598012770a45..8bbd7f1414d8 100644 --- a/crates/evm/evm/src/lib.rs +++ b/crates/evm/evm/src/lib.rs @@ -11,7 +11,7 @@ extern crate tracing; pub mod executors; pub mod inspectors; -pub use foundry_evm_core::{backend, constants, debug, decode, fork, opts, utils, InspectorExt}; +pub use foundry_evm_core::{backend, constants, decode, fork, opts, utils, InspectorExt}; pub use foundry_evm_coverage as coverage; pub use foundry_evm_fuzz as fuzz; pub use foundry_evm_traces as traces; diff --git a/crates/evm/fuzz/src/lib.rs b/crates/evm/fuzz/src/lib.rs index f675e775542e..e3ee6a05ed02 100644 --- a/crates/evm/fuzz/src/lib.rs +++ b/crates/evm/fuzz/src/lib.rs @@ -10,7 +10,7 @@ extern crate tracing; use alloy_dyn_abi::{DynSolValue, JsonAbiExt}; use alloy_primitives::{Address, Bytes, Log}; -use foundry_common::{calc, contracts::ContractsByAddress}; +use foundry_common::{calc, contracts::ContractsByAddress, evm::Breakpoints}; use foundry_evm_coverage::HitMaps; use foundry_evm_traces::CallTraceArena; use itertools::Itertools; @@ -181,6 +181,9 @@ pub struct FuzzTestResult { /// Raw coverage info pub coverage: Option, + + /// Breakpoints for debugger. Correspond to the same fuzz case as `traces`. + pub breakpoints: Option, } impl FuzzTestResult { diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 6f65dcfec5b4..6b2931de3481 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -343,7 +343,9 @@ impl TestArgs { // Run the debugger. let mut builder = Debugger::builder() - .debug_arenas(test_result.debug.as_slice()) + .traces( + test_result.traces.iter().filter(|(t, _)| t.is_execution()).cloned().collect(), + ) .sources(sources) .breakpoints(test_result.breakpoints.clone()); if let Some(decoder) = &outcome.last_run_decoder { diff --git a/crates/forge/src/result.rs b/crates/forge/src/result.rs index cdd2caf51807..e7c0b6101eb9 100644 --- a/crates/forge/src/result.rs +++ b/crates/forge/src/result.rs @@ -10,7 +10,6 @@ use eyre::Report; use foundry_common::{evm::Breakpoints, get_contract_name, get_file_name, shell}; use foundry_evm::{ coverage::HitMaps, - debug::DebugArena, executors::{EvmError, RawCallResult}, fuzz::{CounterExample, FuzzCase, FuzzFixtures, FuzzTestResult}, traces::{CallTraceArena, CallTraceDecoder, TraceKind, Traces}, @@ -378,9 +377,6 @@ pub struct TestResult { /// Labeled addresses pub labeled_addresses: HashMap, - /// The debug nodes of the call - pub debug: Option, - pub duration: Duration, /// pc breakpoint char map @@ -488,7 +484,6 @@ impl TestResult { }; self.reason = reason; self.decoded_logs = decode_console_logs(&self.logs); - self.debug = raw_call_result.debug; self.breakpoints = raw_call_result.cheatcodes.map(|c| c.breakpoints).unwrap_or_default(); self.duration = Duration::default(); self.gas_report_traces = Vec::new(); @@ -520,6 +515,7 @@ impl TestResult { self.decoded_logs = decode_console_logs(&self.logs); self.duration = Duration::default(); self.gas_report_traces = result.gas_report_traces.into_iter().map(|t| vec![t]).collect(); + self.breakpoints = result.breakpoints.unwrap_or_default(); self } diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index a1cffc6d2c33..59f903c9c653 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -20,7 +20,7 @@ use foundry_evm::{ constants::CALLER, decode::RevertDecoder, executors::{ - fuzz::{CaseOutcome, CounterExampleOutcome, FuzzOutcome, FuzzedExecutor}, + fuzz::FuzzedExecutor, invariant::{ check_sequence, replay_error, replay_run, InvariantExecutor, InvariantFuzzError, InvariantFuzzTestResult, @@ -312,13 +312,13 @@ impl<'a> ContractRunner<'a> { let tmp_tracing = self.executor.inspector().tracer.is_none() && has_invariants && call_setup; if tmp_tracing { - self.executor.set_tracing(true); + self.executor.set_tracing(true, false); } let setup_time = Instant::now(); let setup = self.setup(call_setup); debug!("finished setting up in {:?}", setup_time.elapsed()); if tmp_tracing { - self.executor.set_tracing(false); + self.executor.set_tracing(false, false); } if setup.reason.is_some() { @@ -626,16 +626,12 @@ impl<'a> ContractRunner<'a> { ) -> TestResult { let address = setup.address; let fuzz_fixtures = setup.fuzz_fixtures.clone(); - let mut test_result = TestResult::new(setup); + let test_result = TestResult::new(setup); // Run fuzz test let progress = start_fuzz_progress(self.progress, self.name, &func.name, fuzz_config.runs); - let fuzzed_executor = FuzzedExecutor::new( - self.executor.clone(), - runner.clone(), - self.sender, - fuzz_config.clone(), - ); + let fuzzed_executor = + FuzzedExecutor::new(self.executor.clone(), runner, self.sender, fuzz_config); let result = fuzzed_executor.fuzz( func, &fuzz_fixtures, @@ -651,41 +647,6 @@ impl<'a> ContractRunner<'a> { return test_result.single_skip() } - if self.debug { - let mut debug_executor = self.executor.clone(); - // turn the debug traces on - debug_executor.inspector_mut().enable_debugger(true); - debug_executor.inspector_mut().tracing(true); - let calldata = if let Some(counterexample) = result.counterexample.as_ref() { - match counterexample { - CounterExample::Single(ce) => ce.calldata.clone(), - _ => unimplemented!(), - } - } else { - result.first_case.calldata.clone() - }; - // rerun the last relevant test with traces - let debug_result = - FuzzedExecutor::new(debug_executor, runner, self.sender, fuzz_config).single_fuzz( - address, - should_fail, - calldata, - ); - - (test_result.debug, test_result.breakpoints) = match debug_result { - Ok(fuzz_outcome) => match fuzz_outcome { - FuzzOutcome::Case(CaseOutcome { debug, breakpoints, .. }) => { - (debug, breakpoints) - } - FuzzOutcome::CounterExample(CounterExampleOutcome { - debug, - breakpoints, - .. - }) => (debug, breakpoints), - }, - Err(_) => (Default::default(), Default::default()), - }; - } test_result.fuzz_result(result) } } diff --git a/crates/script/src/execute.rs b/crates/script/src/execute.rs index 2b57468e276b..3552e3977f97 100644 --- a/crates/script/src/execute.rs +++ b/crates/script/src/execute.rs @@ -154,7 +154,6 @@ impl PreExecutionState { setup_result.gas_used = script_result.gas_used; setup_result.logs.extend(script_result.logs); setup_result.traces.extend(script_result.traces); - setup_result.debug = script_result.debug; setup_result.labeled_addresses.extend(script_result.labeled_addresses); setup_result.returned = script_result.returned; setup_result.breakpoints = script_result.breakpoints; @@ -490,12 +489,18 @@ impl PreSimulationState { Ok(()) } - pub fn run_debugger(&self) -> Result<()> { + pub fn run_debugger(self) -> Result<()> { let mut debugger = Debugger::builder() - .debug_arenas(self.execution_result.debug.as_deref().unwrap_or_default()) + .traces( + self.execution_result + .traces + .into_iter() + .filter(|(t, _)| t.is_execution()) + .collect(), + ) .decoder(&self.execution_artifacts.decoder) - .sources(self.build_data.sources.clone()) - .breakpoints(self.execution_result.breakpoints.clone()) + .sources(self.build_data.sources) + .breakpoints(self.execution_result.breakpoints) .build(); debugger.try_run()?; Ok(()) diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index 9c75729ed453..c1ae8882e35d 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -37,7 +37,6 @@ use foundry_config::{ use foundry_evm::{ backend::Backend, constants::DEFAULT_CREATE2_DEPLOYER, - debug::DebugArena, executors::ExecutorBuilder, inspectors::{ cheatcodes::{BroadcastableTransactions, ScriptWallets}, @@ -235,7 +234,7 @@ impl ScriptArgs { .await?; if pre_simulation.args.debug { - pre_simulation.run_debugger()?; + return pre_simulation.run_debugger() } if pre_simulation.args.json { @@ -462,7 +461,6 @@ pub struct ScriptResult { pub success: bool, pub logs: Vec, pub traces: Traces, - pub debug: Option>, pub gas_used: u64, pub labeled_addresses: HashMap, pub transactions: Option, diff --git a/crates/script/src/runner.rs b/crates/script/src/runner.rs index 9ecd80859bb8..a4f437644dbc 100644 --- a/crates/script/src/runner.rs +++ b/crates/script/src/runner.rs @@ -134,8 +134,7 @@ impl ScriptRunner { // Deploy an instance of the contract let DeployResult { address, - raw: - RawCallResult { mut logs, traces: constructor_traces, debug: constructor_debug, .. }, + raw: RawCallResult { mut logs, traces: constructor_traces, .. }, } = self .executor .deploy(CALLER, code, U256::ZERO, None) @@ -144,15 +143,9 @@ impl ScriptRunner { traces.extend(constructor_traces.map(|traces| (TraceKind::Deployment, traces))); // Optionally call the `setUp` function - let (success, gas_used, labeled_addresses, transactions, debug) = if !setup { + let (success, gas_used, labeled_addresses, transactions) = if !setup { self.executor.backend_mut().set_test_contract(address); - ( - true, - 0, - Default::default(), - Some(library_transactions), - vec![constructor_debug].into_iter().collect(), - ) + (true, 0, Default::default(), Some(library_transactions)) } else { match self.executor.setup(Some(self.evm_opts.sender), address, None) { Ok(RawCallResult { @@ -160,7 +153,6 @@ impl ScriptRunner { traces: setup_traces, labels, logs: setup_logs, - debug, gas_used, transactions: setup_transactions, .. @@ -172,13 +164,7 @@ impl ScriptRunner { library_transactions.extend(txs); } - ( - !reverted, - gas_used, - labels, - Some(library_transactions), - vec![constructor_debug, debug].into_iter().collect(), - ) + (!reverted, gas_used, labels, Some(library_transactions)) } Err(EvmError::Execution(err)) => { let RawCallResult { @@ -186,7 +172,6 @@ impl ScriptRunner { traces: setup_traces, labels, logs: setup_logs, - debug, gas_used, transactions, .. @@ -198,13 +183,7 @@ impl ScriptRunner { library_transactions.extend(txs); } - ( - !reverted, - gas_used, - labels, - Some(library_transactions), - vec![constructor_debug, debug].into_iter().collect(), - ) + (!reverted, gas_used, labels, Some(library_transactions)) } Err(e) => return Err(e.into()), } @@ -220,7 +199,6 @@ impl ScriptRunner { transactions, logs, traces, - debug, address: None, ..Default::default() }, @@ -249,7 +227,7 @@ impl ScriptRunner { value.unwrap_or(U256::ZERO), None, ); - let (address, RawCallResult { gas_used, logs, traces, debug, .. }) = match res { + let (address, RawCallResult { gas_used, logs, traces, .. }) = match res { Ok(DeployResult { address, raw }) => (address, raw), Err(EvmError::Execution(err)) => { let ExecutionErr { raw, reason } = *err; @@ -268,7 +246,6 @@ impl ScriptRunner { traces: traces .map(|traces| vec![(TraceKind::Execution, traces)]) .unwrap_or_default(), - debug: debug.map(|debug| vec![debug]), address: Some(address), ..Default::default() }) @@ -304,7 +281,7 @@ impl ScriptRunner { res = self.executor.transact_raw(from, to, calldata, value)?; } - let RawCallResult { result, reverted, logs, traces, labels, debug, transactions, .. } = res; + let RawCallResult { result, reverted, logs, traces, labels, transactions, .. } = res; let breakpoints = res.cheatcodes.map(|cheats| cheats.breakpoints).unwrap_or_default(); Ok(ScriptResult { @@ -319,7 +296,6 @@ impl ScriptRunner { vec![(TraceKind::Execution, traces)] }) .unwrap_or_default(), - debug: debug.map(|d| vec![d]), labeled_addresses: labels, transactions, address: None,