From a8946b12fc2f986c77f964d079b7f648434b076b Mon Sep 17 00:00:00 2001 From: Piotr Heilman Date: Thu, 22 Aug 2024 10:17:43 +0200 Subject: [PATCH 1/7] Refactored debugger to extract TUI abstraction. Added option to dump debugger context to file as json. --- crates/cli/src/utils/cmd.rs | 2 +- crates/debugger/src/{tui => }/builder.rs | 2 +- crates/debugger/src/context.rs | 12 ++ crates/debugger/src/debugger.rs | 67 +++++++++ crates/debugger/src/file_dumper/mod.rs | 177 +++++++++++++++++++++++ crates/debugger/src/lib.rs | 12 +- crates/debugger/src/tui/context.rs | 23 +-- crates/debugger/src/tui/draw.rs | 8 +- crates/debugger/src/tui/mod.rs | 52 +------ crates/forge/bin/cmd/debug.rs | 10 ++ crates/forge/bin/cmd/test/mod.rs | 14 +- crates/script/src/execute.rs | 17 ++- crates/script/src/lib.rs | 16 +- 13 files changed, 342 insertions(+), 70 deletions(-) rename crates/debugger/src/{tui => }/builder.rs (99%) create mode 100644 crates/debugger/src/context.rs create mode 100644 crates/debugger/src/debugger.rs create mode 100644 crates/debugger/src/file_dumper/mod.rs diff --git a/crates/cli/src/utils/cmd.rs b/crates/cli/src/utils/cmd.rs index 8a4bff7294101..4e289ba90e59b 100644 --- a/crates/cli/src/utils/cmd.rs +++ b/crates/cli/src/utils/cmd.rs @@ -410,7 +410,7 @@ pub async fn handle_traces( .decoder(&decoder) .sources(sources) .build(); - debugger.try_run()?; + debugger.try_run_tui()?; } else { print_traces(&mut result, &decoder, verbose).await?; } diff --git a/crates/debugger/src/tui/builder.rs b/crates/debugger/src/builder.rs similarity index 99% rename from crates/debugger/src/tui/builder.rs rename to crates/debugger/src/builder.rs index 484611c70e65c..fd2dce3dfe099 100644 --- a/crates/debugger/src/tui/builder.rs +++ b/crates/debugger/src/builder.rs @@ -1,4 +1,4 @@ -//! TUI debugger builder. +//! Debugger builder. use crate::{node::flatten_call_trace, DebugNode, Debugger}; use alloy_primitives::{map::AddressHashMap, Address}; diff --git a/crates/debugger/src/context.rs b/crates/debugger/src/context.rs new file mode 100644 index 0000000000000..1d89b549e82e3 --- /dev/null +++ b/crates/debugger/src/context.rs @@ -0,0 +1,12 @@ +use crate::DebugNode; +use alloy_primitives::map::AddressHashMap; +use foundry_common::evm::Breakpoints; +use foundry_evm_traces::debug::ContractSources; + +pub struct DebuggerContext { + pub debug_arena: Vec, + pub identified_contracts: AddressHashMap, + /// Source map of contract sources + pub contracts_sources: ContractSources, + pub breakpoints: Breakpoints, +} diff --git a/crates/debugger/src/debugger.rs b/crates/debugger/src/debugger.rs new file mode 100644 index 0000000000000..723a4cb85f4f6 --- /dev/null +++ b/crates/debugger/src/debugger.rs @@ -0,0 +1,67 @@ +//! Debugger implementation. + +use crate::{ + context::DebuggerContext, tui::TUI, DebugNode, DebuggerBuilder, ExitReason, FileDumper, +}; +use alloy_primitives::map::AddressHashMap; +use eyre::Result; +use foundry_common::evm::Breakpoints; +use foundry_evm_traces::debug::ContractSources; +use std::path::PathBuf; + +pub struct Debugger { + context: DebuggerContext, +} + +impl Debugger { + /// Creates a new debugger builder. + #[inline] + pub fn builder() -> DebuggerBuilder { + DebuggerBuilder::new() + } + + /// Creates a new debugger. + pub fn new( + debug_arena: Vec, + identified_contracts: AddressHashMap, + contracts_sources: ContractSources, + breakpoints: Breakpoints, + ) -> Self { + Self { + context: DebuggerContext { + debug_arena, + identified_contracts, + contracts_sources, + breakpoints, + }, + } + } + + /// Starts the debugger TUI. Terminates the current process on failure or user exit. + pub fn run_tui_exit(mut self) -> ! { + let code = match self.try_run_tui() { + Ok(ExitReason::CharExit) => 0, + Err(e) => { + println!("{e}"); + 1 + } + }; + std::process::exit(code) + } + + /// Starts the debugger TUI. + pub fn try_run_tui(&mut self) -> Result { + eyre::ensure!(!self.context.debug_arena.is_empty(), "debug arena is empty"); + + let mut tui = TUI::new(&mut self.context); + tui.try_run() + } + + /// Dumps debugger data to file. + pub fn dump_to_file(&mut self, path: &PathBuf) -> Result<()> { + eyre::ensure!(!self.context.debug_arena.is_empty(), "debug arena is empty"); + + let mut file_dumper = FileDumper::new(path, &mut self.context); + file_dumper.run() + } +} diff --git a/crates/debugger/src/file_dumper/mod.rs b/crates/debugger/src/file_dumper/mod.rs new file mode 100644 index 0000000000000..68339bf8cc79f --- /dev/null +++ b/crates/debugger/src/file_dumper/mod.rs @@ -0,0 +1,177 @@ +//! The file dumper implementation + +use crate::{context::DebuggerContext, DebugNode}; +use alloy_primitives::Address; +use eyre::Result; +use foundry_common::fs::write_json_file; +use foundry_compilers::{artifacts::sourcemap::Jump, multi::MultiCompilerLanguage}; +use foundry_evm_traces::debug::ContractSources; +use serde::Serialize; +use std::{collections::HashMap, ops::Deref, path::PathBuf}; + +/// The file dumper +pub struct FileDumper<'a> { + path: &'a PathBuf, + debugger_context: &'a mut DebuggerContext, +} + +impl<'a> FileDumper<'a> { + pub fn new(path: &'a PathBuf, debugger_context: &'a mut DebuggerContext) -> Self { + Self { path, debugger_context } + } + + pub fn run(&mut self) -> Result<()> { + let data = DebuggerDump::from(self.debugger_context); + write_json_file(self.path, &data).unwrap(); + Ok(()) + } +} + +impl DebuggerDump { + fn from(debugger_context: &DebuggerContext) -> Self { + Self { + contracts: to_contracts_dump(debugger_context), + debug_arena: debugger_context.debug_arena.clone(), + } + } +} + +#[derive(Serialize)] +struct DebuggerDump { + contracts: ContractsDump, + debug_arena: Vec, +} + +#[derive(Serialize)] +pub struct SourceElementDump { + offset: u32, + length: u32, + index: i32, + jump: u32, + modifier_depth: u32, +} + +#[derive(Serialize)] +struct ContractsDump { + // Map of call address to contract name + identified_contracts: HashMap, + sources: ContractsSourcesDump, +} + +#[derive(Serialize)] +struct ContractsSourcesDump { + sources_by_id: HashMap>, + artifacts_by_name: HashMap>, +} + +#[derive(Serialize)] +struct SourceDataDump { + source: String, + language: MultiCompilerLanguage, + path: PathBuf, +} + +#[derive(Serialize)] +struct ArtifactDataDump { + pub source_map: Option>, + pub source_map_runtime: Option>, + pub pc_ic_map: Option>, + pub pc_ic_map_runtime: Option>, + pub build_id: String, + pub file_id: u32, +} + +fn to_contracts_dump(debugger_context: &DebuggerContext) -> ContractsDump { + ContractsDump { + identified_contracts: debugger_context + .identified_contracts + .iter() + .map(|(k, v)| (*k, v.clone())) + .collect(), + sources: to_contracts_sources_dump(&debugger_context.contracts_sources), + } +} + +fn to_contracts_sources_dump(contracts_sources: &ContractSources) -> ContractsSourcesDump { + ContractsSourcesDump { + sources_by_id: contracts_sources + .sources_by_id + .iter() + .map(|(name, inner_map)| { + ( + name.clone(), + inner_map + .iter() + .map(|(id, source_data)| { + ( + *id, + SourceDataDump { + source: source_data.source.deref().clone(), + language: source_data.language, + path: source_data.path.clone(), + }, + ) + }) + .collect(), + ) + }) + .collect(), + artifacts_by_name: contracts_sources + .artifacts_by_name + .iter() + .map(|(name, data)| { + ( + name.clone(), + data.iter() + .map(|artifact_data| ArtifactDataDump { + source_map: artifact_data.source_map.clone().map(|source_map| { + source_map + .iter() + .map(|v| SourceElementDump { + offset: v.offset(), + length: v.length(), + index: v.index_i32(), + jump: match v.jump() { + Jump::In => 0, + Jump::Out => 1, + Jump::Regular => 2, + }, + modifier_depth: v.modifier_depth(), + }) + .collect() + }), + source_map_runtime: artifact_data.source_map_runtime.clone().map( + |source_map| { + source_map + .iter() + .map(|v| SourceElementDump { + offset: v.offset(), + length: v.length(), + index: v.index_i32(), + jump: match v.jump() { + Jump::In => 0, + Jump::Out => 1, + Jump::Regular => 2, + }, + modifier_depth: v.modifier_depth(), + }) + .collect() + }, + ), + pc_ic_map: artifact_data + .pc_ic_map + .clone() + .map(|v| v.inner.iter().map(|(k, v)| (*k, *v)).collect()), + pc_ic_map_runtime: artifact_data + .pc_ic_map_runtime + .clone() + .map(|v| v.inner.iter().map(|(k, v)| (*k, *v)).collect()), + build_id: artifact_data.build_id.clone(), + file_id: artifact_data.file_id, + }) + .collect(), + ) + }) + .collect(), + } +} diff --git a/crates/debugger/src/lib.rs b/crates/debugger/src/lib.rs index 678ae8672d360..db8476e4056e7 100644 --- a/crates/debugger/src/lib.rs +++ b/crates/debugger/src/lib.rs @@ -1,6 +1,6 @@ //! # foundry-debugger //! -//! Interactive Solidity TUI debugger. +//! Interactive Solidity TUI debugger and debugger data file dumper #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] @@ -10,8 +10,16 @@ extern crate tracing; mod op; +mod builder; +mod context; +mod debugger; +mod file_dumper; mod tui; -pub use tui::{Debugger, DebuggerBuilder, ExitReason}; mod node; pub use node::DebugNode; + +pub use builder::DebuggerBuilder; +pub use debugger::Debugger; +pub use file_dumper::FileDumper; +pub use tui::{ExitReason, TUI}; diff --git a/crates/debugger/src/tui/context.rs b/crates/debugger/src/tui/context.rs index c3645e31b1533..80e16046c212e 100644 --- a/crates/debugger/src/tui/context.rs +++ b/crates/debugger/src/tui/context.rs @@ -1,6 +1,6 @@ //! Debugger context and event handler implementation. -use crate::{DebugNode, Debugger, ExitReason}; +use crate::{context::DebuggerContext, DebugNode, ExitReason}; use alloy_primitives::{hex, Address}; use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind}; use foundry_evm_core::buffer::BufferKind; @@ -16,8 +16,8 @@ pub(crate) struct DrawMemory { pub(crate) current_stack_startline: usize, } -pub(crate) struct DebuggerContext<'a> { - pub(crate) debugger: &'a mut Debugger, +pub(crate) struct TUIContext<'a> { + pub(crate) debugger_context: &'a mut DebuggerContext, /// Buffer for keys prior to execution, i.e. '10' + 'k' => move up 10 operations. pub(crate) key_buffer: String, @@ -35,10 +35,10 @@ pub(crate) struct DebuggerContext<'a> { pub(crate) active_buffer: BufferKind, } -impl<'a> DebuggerContext<'a> { - pub(crate) fn new(debugger: &'a mut Debugger) -> Self { - DebuggerContext { - debugger, +impl<'a> TUIContext<'a> { + pub(crate) fn new(debugger_context: &'a mut DebuggerContext) -> Self { + TUIContext { + debugger_context, key_buffer: String::with_capacity(64), current_step: 0, @@ -58,7 +58,7 @@ impl<'a> DebuggerContext<'a> { } pub(crate) fn debug_arena(&self) -> &[DebugNode] { - &self.debugger.debug_arena + &self.debugger_context.debug_arena } pub(crate) fn debug_call(&self) -> &DebugNode { @@ -87,7 +87,8 @@ impl<'a> DebuggerContext<'a> { fn gen_opcode_list(&mut self) { self.opcode_list.clear(); - let debug_steps = &self.debugger.debug_arena[self.draw_memory.inner_call_index].steps; + let debug_steps = + &self.debugger_context.debug_arena[self.draw_memory.inner_call_index].steps; for step in debug_steps { self.opcode_list.push(pretty_opcode(step)); } @@ -109,7 +110,7 @@ impl<'a> DebuggerContext<'a> { } } -impl DebuggerContext<'_> { +impl TUIContext<'_> { pub(crate) fn handle_event(&mut self, event: Event) -> ControlFlow { let ret = match event { Event::Key(event) => self.handle_key_event(event), @@ -259,7 +260,7 @@ impl DebuggerContext<'_> { fn handle_breakpoint(&mut self, c: char) { // Find the location of the called breakpoint in the whole debug arena (at this address with // this pc) - if let Some((caller, pc)) = self.debugger.breakpoints.get(&c) { + if let Some((caller, pc)) = self.debugger_context.breakpoints.get(&c) { for (i, node) in self.debug_arena().iter().enumerate() { if node.address == *caller { if let Some(step) = node.steps.iter().position(|step| step.pc == *pc) { diff --git a/crates/debugger/src/tui/draw.rs b/crates/debugger/src/tui/draw.rs index 55e4834f58d8b..18b58927966d0 100644 --- a/crates/debugger/src/tui/draw.rs +++ b/crates/debugger/src/tui/draw.rs @@ -1,6 +1,6 @@ //! TUI draw implementation. -use super::context::DebuggerContext; +use super::context::TUIContext; use crate::op::OpcodeParam; use foundry_compilers::artifacts::sourcemap::SourceElement; use foundry_evm_core::buffer::{get_buffer_accesses, BufferKind}; @@ -15,7 +15,7 @@ use ratatui::{ use revm_inspectors::tracing::types::CallKind; use std::{collections::VecDeque, fmt::Write, io}; -impl DebuggerContext<'_> { +impl TUIContext<'_> { /// Draws the TUI layout and subcomponents to the given terminal. pub(crate) fn draw(&self, terminal: &mut super::DebuggerTerminal) -> io::Result<()> { terminal.draw(|f| self.draw_layout(f)).map(drop) @@ -343,11 +343,11 @@ impl DebuggerContext<'_> { /// Returns source map, source code and source name of the current line. fn src_map(&self) -> Result<(SourceElement, &SourceData), String> { let address = self.address(); - let Some(contract_name) = self.debugger.identified_contracts.get(address) else { + let Some(contract_name) = self.debugger_context.identified_contracts.get(address) else { return Err(format!("Unknown contract at address {address}")); }; - self.debugger + self.debugger_context .contracts_sources .find_source_mapping( contract_name, diff --git a/crates/debugger/src/tui/mod.rs b/crates/debugger/src/tui/mod.rs index 75b747a7ed02e..69ec0c201a06a 100644 --- a/crates/debugger/src/tui/mod.rs +++ b/crates/debugger/src/tui/mod.rs @@ -1,14 +1,11 @@ //! The TUI implementation. -use alloy_primitives::map::AddressHashMap; use crossterm::{ event::{self, DisableMouseCapture, EnableMouseCapture, Event}, execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; use eyre::Result; -use foundry_common::evm::Breakpoints; -use foundry_evm_traces::debug::ContractSources; use ratatui::{ backend::{Backend, CrosstermBackend}, Terminal, @@ -21,13 +18,9 @@ use std::{ time::{Duration, Instant}, }; -mod builder; -pub use builder::DebuggerBuilder; - mod context; -use context::DebuggerContext; - -use crate::DebugNode; +use crate::context::DebuggerContext; +use context::TUIContext; mod draw; @@ -41,47 +34,18 @@ pub enum ExitReason { } /// The TUI debugger. -pub struct Debugger { - debug_arena: Vec, - identified_contracts: AddressHashMap, - /// Source map of contract sources - contracts_sources: ContractSources, - breakpoints: Breakpoints, +pub struct TUI<'a> { + debugger_context: &'a mut DebuggerContext, } -impl Debugger { - /// Creates a new debugger builder. - #[inline] - pub fn builder() -> DebuggerBuilder { - DebuggerBuilder::new() - } - +impl<'a> TUI<'a> { /// Creates a new debugger. - pub fn new( - debug_arena: Vec, - identified_contracts: AddressHashMap, - contracts_sources: ContractSources, - breakpoints: Breakpoints, - ) -> Self { - Self { debug_arena, identified_contracts, contracts_sources, breakpoints } - } - - /// Starts the debugger TUI. Terminates the current process on failure or user exit. - pub fn run_exit(mut self) -> ! { - let code = match self.try_run() { - Ok(ExitReason::CharExit) => 0, - Err(e) => { - println!("{e}"); - 1 - } - }; - std::process::exit(code) + pub fn new(debugger_context: &'a mut DebuggerContext) -> Self { + Self { debugger_context } } /// Starts the debugger TUI. pub fn try_run(&mut self) -> Result { - eyre::ensure!(!self.debug_arena.is_empty(), "debug arena is empty"); - let backend = CrosstermBackend::new(io::stdout()); let terminal = Terminal::new(backend)?; TerminalGuard::with(terminal, |terminal| self.try_run_real(terminal)) @@ -90,7 +54,7 @@ impl Debugger { #[instrument(target = "debugger", name = "run", skip_all, ret)] fn try_run_real(&mut self, terminal: &mut DebuggerTerminal) -> Result { // Create the context. - let mut cx = DebuggerContext::new(self); + let mut cx = TUIContext::new(self.debugger_context); cx.init(); diff --git a/crates/forge/bin/cmd/debug.rs b/crates/forge/bin/cmd/debug.rs index 8fe1d2e32a258..421478bd5762c 100644 --- a/crates/forge/bin/cmd/debug.rs +++ b/crates/forge/bin/cmd/debug.rs @@ -33,6 +33,15 @@ pub struct DebugArgs { #[arg(long)] pub debug: bool, + /// File path to dump execution details as JSON. + #[arg( + long, + requires = "debug", + value_hint = ValueHint::FilePath, + value_name = "PATH" + )] + pub dump: Option, + #[command(flatten)] pub opts: CoreBuildArgs, @@ -51,6 +60,7 @@ impl DebugArgs { opts: self.opts, evm_opts: self.evm_opts, debug: true, + dump: self.dump, retry: RETRY_VERIFY_ON_CREATE, ..Default::default() }; diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 7038fc0b5a8b0..079895328ec7b 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -99,6 +99,14 @@ pub struct TestArgs { #[arg(long, value_name = "DEPRECATED_TEST_FUNCTION_REGEX")] decode_internal: Option>, + /// Dumps all debugger steps to file. + #[arg( + long, + requires = "debug", + value_hint = ValueHint::FilePath, + )] + dump: Option, + /// Print a gas report. #[arg(long, env = "FORGE_GAS_REPORT")] gas_report: bool, @@ -453,7 +461,11 @@ impl TestArgs { } let mut debugger = builder.build(); - debugger.try_run()?; + if let Some(dump_path) = self.dump { + debugger.dump_to_file(&dump_path)?; + } else { + debugger.try_run_tui()?; + } } Ok(outcome) diff --git a/crates/script/src/execute.rs b/crates/script/src/execute.rs index 4c2e893686ce1..d1191505a328d 100644 --- a/crates/script/src/execute.rs +++ b/crates/script/src/execute.rs @@ -34,6 +34,7 @@ use foundry_evm::{ }; use futures::future::join_all; use itertools::Itertools; +use std::path::PathBuf; use yansi::Paint; /// State after linking, contains the linked build data along with library addresses and optional @@ -495,7 +496,17 @@ impl PreSimulationState { } pub fn run_debugger(self) -> Result<()> { - let mut debugger = Debugger::builder() + self.create_debugger().try_run_tui()?; + Ok(()) + } + + pub fn run_debug_file_dumper(self, path: &PathBuf) -> Result<()> { + self.create_debugger().dump_to_file(path)?; + Ok(()) + } + + fn create_debugger(self) -> Debugger { + Debugger::builder() .traces( self.execution_result .traces @@ -506,8 +517,6 @@ impl PreSimulationState { .decoder(&self.execution_artifacts.decoder) .sources(self.build_data.sources) .breakpoints(self.execution_result.breakpoints) - .build(); - debugger.try_run()?; - Ok(()) + .build() } } diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index af44f2062802d..3b152753c9c85 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -50,7 +50,8 @@ use foundry_evm::{ traces::{TraceMode, Traces}, }; use foundry_wallets::MultiWalletOpts; -use serde::Serialize; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; use yansi::Paint; mod broadcast; @@ -149,6 +150,14 @@ pub struct ScriptArgs { #[arg(long)] pub debug: bool, + /// Dumps all debugger steps to file. + #[arg( + long, + requires = "debug", + value_hint = ValueHint::FilePath, + )] + pub dump: Option, + /// Makes sure a transaction is sent, /// only after its previous one has been confirmed and succeeded. #[arg(long)] @@ -244,7 +253,10 @@ impl ScriptArgs { .await?; if pre_simulation.args.debug { - return pre_simulation.run_debugger() + return match pre_simulation.args.dump.clone() { + Some(ref path) => pre_simulation.run_debug_file_dumper(path), + None => pre_simulation.run_debugger(), + } } if pre_simulation.args.json { From edfbfa6069adf475517c1e4c1db970849410c797 Mon Sep 17 00:00:00 2001 From: Piotr Heilman <1212808+piohei@users.noreply.github.com> Date: Fri, 11 Oct 2024 13:19:29 +0200 Subject: [PATCH 2/7] Update crates/forge/bin/cmd/test/mod.rs Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> --- crates/forge/bin/cmd/test/mod.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 079895328ec7b..39f2b45557e3d 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -101,9 +101,10 @@ pub struct TestArgs { /// Dumps all debugger steps to file. #[arg( - long, - requires = "debug", - value_hint = ValueHint::FilePath, + long, + requires = "debug", + value_hint = ValueHint::FilePath, + value_name = "PATH" )] dump: Option, From 17e351eaa64f7a35a5d1de7eb1f1f0e811d1dbee Mon Sep 17 00:00:00 2001 From: Piotr Heilman <1212808+piohei@users.noreply.github.com> Date: Fri, 11 Oct 2024 13:19:36 +0200 Subject: [PATCH 3/7] Update crates/script/src/lib.rs Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> --- crates/script/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index 3b152753c9c85..49650584fd712 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -50,7 +50,7 @@ use foundry_evm::{ traces::{TraceMode, Traces}, }; use foundry_wallets::MultiWalletOpts; -use serde::{Deserialize, Serialize}; +use serde::Serialize; use std::path::PathBuf; use yansi::Paint; @@ -155,6 +155,7 @@ pub struct ScriptArgs { long, requires = "debug", value_hint = ValueHint::FilePath, + value_name = "PATH" )] pub dump: Option, From 601c61f04257add2f1c89fc57113e8abe04103dd Mon Sep 17 00:00:00 2001 From: Piotr Heilman Date: Tue, 22 Oct 2024 12:35:31 +0200 Subject: [PATCH 4/7] Cleanup code. --- crates/debugger/src/file_dumper/mod.rs | 196 +++++++++++++------------ 1 file changed, 105 insertions(+), 91 deletions(-) diff --git a/crates/debugger/src/file_dumper/mod.rs b/crates/debugger/src/file_dumper/mod.rs index 68339bf8cc79f..a5f583e273844 100644 --- a/crates/debugger/src/file_dumper/mod.rs +++ b/crates/debugger/src/file_dumper/mod.rs @@ -5,9 +5,10 @@ use alloy_primitives::Address; use eyre::Result; use foundry_common::fs::write_json_file; use foundry_compilers::{artifacts::sourcemap::Jump, multi::MultiCompilerLanguage}; -use foundry_evm_traces::debug::ContractSources; +use foundry_evm_traces::debug::{ArtifactData, ContractSources, SourceData}; use serde::Serialize; use std::{collections::HashMap, ops::Deref, path::PathBuf}; +use foundry_compilers::artifacts::sourcemap::SourceElement; /// The file dumper pub struct FileDumper<'a> { @@ -30,7 +31,7 @@ impl<'a> FileDumper<'a> { impl DebuggerDump { fn from(debugger_context: &DebuggerContext) -> Self { Self { - contracts: to_contracts_dump(debugger_context), + contracts: ContractsDump::new(debugger_context), debug_arena: debugger_context.debug_arena.clone(), } } @@ -81,97 +82,110 @@ struct ArtifactDataDump { pub file_id: u32, } -fn to_contracts_dump(debugger_context: &DebuggerContext) -> ContractsDump { - ContractsDump { - identified_contracts: debugger_context - .identified_contracts - .iter() - .map(|(k, v)| (*k, v.clone())) - .collect(), - sources: to_contracts_sources_dump(&debugger_context.contracts_sources), + +impl ContractsDump { + pub fn new(debugger_context: &DebuggerContext) -> Self { + Self { + identified_contracts: debugger_context + .identified_contracts + .iter() + .map(|(k, v)| (*k, v.clone())) + .collect(), + sources: ContractsSourcesDump::new(&debugger_context.contracts_sources), + } } } -fn to_contracts_sources_dump(contracts_sources: &ContractSources) -> ContractsSourcesDump { - ContractsSourcesDump { - sources_by_id: contracts_sources - .sources_by_id - .iter() - .map(|(name, inner_map)| { - ( - name.clone(), - inner_map - .iter() - .map(|(id, source_data)| { - ( - *id, - SourceDataDump { - source: source_data.source.deref().clone(), - language: source_data.language, - path: source_data.path.clone(), - }, - ) - }) - .collect(), - ) - }) - .collect(), - artifacts_by_name: contracts_sources - .artifacts_by_name - .iter() - .map(|(name, data)| { - ( - name.clone(), - data.iter() - .map(|artifact_data| ArtifactDataDump { - source_map: artifact_data.source_map.clone().map(|source_map| { - source_map - .iter() - .map(|v| SourceElementDump { - offset: v.offset(), - length: v.length(), - index: v.index_i32(), - jump: match v.jump() { - Jump::In => 0, - Jump::Out => 1, - Jump::Regular => 2, - }, - modifier_depth: v.modifier_depth(), - }) - .collect() - }), - source_map_runtime: artifact_data.source_map_runtime.clone().map( - |source_map| { - source_map - .iter() - .map(|v| SourceElementDump { - offset: v.offset(), - length: v.length(), - index: v.index_i32(), - jump: match v.jump() { - Jump::In => 0, - Jump::Out => 1, - Jump::Regular => 2, - }, - modifier_depth: v.modifier_depth(), - }) - .collect() - }, - ), - pc_ic_map: artifact_data - .pc_ic_map - .clone() - .map(|v| v.inner.iter().map(|(k, v)| (*k, *v)).collect()), - pc_ic_map_runtime: artifact_data - .pc_ic_map_runtime - .clone() - .map(|v| v.inner.iter().map(|(k, v)| (*k, *v)).collect()), - build_id: artifact_data.build_id.clone(), - file_id: artifact_data.file_id, - }) - .collect(), - ) - }) - .collect(), +impl ContractsSourcesDump { + pub fn new(contracts_sources: &ContractSources) -> Self { + Self { + sources_by_id: contracts_sources + .sources_by_id + .iter() + .map(|(name, inner_map)| { + ( + name.clone(), + inner_map + .iter() + .map(|(id, source_data)| { + ( + *id, + SourceDataDump::new(source_data), + ) + }) + .collect(), + ) + }) + .collect(), + artifacts_by_name: contracts_sources + .artifacts_by_name + .iter() + .map(|(name, data)| { + ( + name.clone(), + data.iter() + .map(ArtifactDataDump::new) + .collect(), + ) + }) + .collect(), + } + } +} + +impl SourceDataDump { + pub fn new(v: &SourceData) -> Self { + Self { + source: v.source.deref().clone(), + language: v.language, + path: v.path.clone(), + } } } + +impl SourceElementDump { + pub fn new(v: &SourceElement) -> Self { + Self { + offset: v.offset(), + length: v.length(), + index: v.index_i32(), + jump: match v.jump() { + Jump::In => 0, + Jump::Out => 1, + Jump::Regular => 2, + }, + modifier_depth: v.modifier_depth(), + } + } +} + +impl ArtifactDataDump { + pub fn new(v: &ArtifactData) -> Self { + Self { + source_map: v.source_map.clone().map(|source_map| { + source_map + .iter() + .map(SourceElementDump::new) + .collect() + }), + source_map_runtime: v.source_map_runtime.clone().map( + |source_map| { + source_map + .iter() + .map(SourceElementDump::new) + .collect() + }, + ), + pc_ic_map: v + .pc_ic_map + .clone() + .map(|v| v.inner.iter().map(|(k, v)| (*k, *v)).collect()), + pc_ic_map_runtime: v + .pc_ic_map_runtime + .clone() + .map(|v| v.inner.iter().map(|(k, v)| (*k, *v)).collect()), + build_id: v.build_id.clone(), + file_id: v.file_id, + } + } +} \ No newline at end of file From 7ff50e385f4cd4329191e1a4e75cc71710a7063d Mon Sep 17 00:00:00 2001 From: Piotr Heilman Date: Tue, 22 Oct 2024 13:11:45 +0200 Subject: [PATCH 5/7] Added test. --- crates/forge/tests/cli/test_cmd.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/crates/forge/tests/cli/test_cmd.rs b/crates/forge/tests/cli/test_cmd.rs index 4f812e79cf6ef..1c8ae36689db9 100644 --- a/crates/forge/tests/cli/test_cmd.rs +++ b/crates/forge/tests/cli/test_cmd.rs @@ -2373,3 +2373,23 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) "#]]); }); + +// Tests if dump of execution was created. +forgetest!(test_debug_with_dump, |prj, cmd| { + prj.add_source( + "dummy", + r" +contract Dummy { + function testDummy() public {} +} +", + ) + .unwrap(); + + let dump_path = prj.root().join("dump.json"); + + cmd.args(["test", "--debug", "testDummy", "--dump", dump_path.to_str().unwrap()]); + cmd.assert_success(); + + assert!(dump_path.exists()); +}); From 1bae5f8a02892576d68f8f9ccb005c62a20313e5 Mon Sep 17 00:00:00 2001 From: Piotr Heilman Date: Tue, 22 Oct 2024 13:13:27 +0200 Subject: [PATCH 6/7] Reformat code. --- crates/debugger/src/file_dumper/mod.rs | 50 +++++++------------------- 1 file changed, 13 insertions(+), 37 deletions(-) diff --git a/crates/debugger/src/file_dumper/mod.rs b/crates/debugger/src/file_dumper/mod.rs index a5f583e273844..7e41bbe7b67d5 100644 --- a/crates/debugger/src/file_dumper/mod.rs +++ b/crates/debugger/src/file_dumper/mod.rs @@ -4,11 +4,11 @@ use crate::{context::DebuggerContext, DebugNode}; use alloy_primitives::Address; use eyre::Result; use foundry_common::fs::write_json_file; +use foundry_compilers::artifacts::sourcemap::SourceElement; use foundry_compilers::{artifacts::sourcemap::Jump, multi::MultiCompilerLanguage}; use foundry_evm_traces::debug::{ArtifactData, ContractSources, SourceData}; use serde::Serialize; use std::{collections::HashMap, ops::Deref, path::PathBuf}; -use foundry_compilers::artifacts::sourcemap::SourceElement; /// The file dumper pub struct FileDumper<'a> { @@ -82,7 +82,6 @@ struct ArtifactDataDump { pub file_id: u32, } - impl ContractsDump { pub fn new(debugger_context: &DebuggerContext) -> Self { Self { @@ -107,12 +106,7 @@ impl ContractsSourcesDump { name.clone(), inner_map .iter() - .map(|(id, source_data)| { - ( - *id, - SourceDataDump::new(source_data), - ) - }) + .map(|(id, source_data)| (*id, SourceDataDump::new(source_data))) .collect(), ) }) @@ -121,12 +115,7 @@ impl ContractsSourcesDump { .artifacts_by_name .iter() .map(|(name, data)| { - ( - name.clone(), - data.iter() - .map(ArtifactDataDump::new) - .collect(), - ) + (name.clone(), data.iter().map(ArtifactDataDump::new).collect()) }) .collect(), } @@ -135,11 +124,7 @@ impl ContractsSourcesDump { impl SourceDataDump { pub fn new(v: &SourceData) -> Self { - Self { - source: v.source.deref().clone(), - language: v.language, - path: v.path.clone(), - } + Self { source: v.source.deref().clone(), language: v.language, path: v.path.clone() } } } @@ -162,24 +147,15 @@ impl SourceElementDump { impl ArtifactDataDump { pub fn new(v: &ArtifactData) -> Self { Self { - source_map: v.source_map.clone().map(|source_map| { - source_map - .iter() - .map(SourceElementDump::new) - .collect() - }), - source_map_runtime: v.source_map_runtime.clone().map( - |source_map| { - source_map - .iter() - .map(SourceElementDump::new) - .collect() - }, - ), - pc_ic_map: v - .pc_ic_map + source_map: v + .source_map .clone() - .map(|v| v.inner.iter().map(|(k, v)| (*k, *v)).collect()), + .map(|source_map| source_map.iter().map(SourceElementDump::new).collect()), + source_map_runtime: v + .source_map_runtime + .clone() + .map(|source_map| source_map.iter().map(SourceElementDump::new).collect()), + pc_ic_map: v.pc_ic_map.clone().map(|v| v.inner.iter().map(|(k, v)| (*k, *v)).collect()), pc_ic_map_runtime: v .pc_ic_map_runtime .clone() @@ -188,4 +164,4 @@ impl ArtifactDataDump { file_id: v.file_id, } } -} \ No newline at end of file +} From 62e1466b0d399116dbdb4343e40bd1de083d1844 Mon Sep 17 00:00:00 2001 From: Piotr Heilman Date: Tue, 22 Oct 2024 13:18:16 +0200 Subject: [PATCH 7/7] Reformat code. --- crates/debugger/src/file_dumper/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/debugger/src/file_dumper/mod.rs b/crates/debugger/src/file_dumper/mod.rs index 7e41bbe7b67d5..969ad882f5931 100644 --- a/crates/debugger/src/file_dumper/mod.rs +++ b/crates/debugger/src/file_dumper/mod.rs @@ -4,8 +4,10 @@ use crate::{context::DebuggerContext, DebugNode}; use alloy_primitives::Address; use eyre::Result; use foundry_common::fs::write_json_file; -use foundry_compilers::artifacts::sourcemap::SourceElement; -use foundry_compilers::{artifacts::sourcemap::Jump, multi::MultiCompilerLanguage}; +use foundry_compilers::{ + artifacts::sourcemap::{Jump, SourceElement}, + multi::MultiCompilerLanguage, +}; use foundry_evm_traces::debug::{ArtifactData, ContractSources, SourceData}; use serde::Serialize; use std::{collections::HashMap, ops::Deref, path::PathBuf};