Skip to content

Commit

Permalink
Refactored debugger to extract TUI abstraction. Added option to dump …
Browse files Browse the repository at this point in the history
…debugger context to file as json.
  • Loading branch information
piohei committed Mar 19, 2024
1 parent 0026488 commit 27dbb83
Show file tree
Hide file tree
Showing 17 changed files with 417 additions and 128 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 13 additions & 13 deletions crates/cli/src/utils/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,14 +167,14 @@ pub fn has_different_gas_calc(chain_id: u64) -> bool {
if let Some(chain) = Chain::from(chain_id).named() {
return matches!(
chain,
NamedChain::Arbitrum |
NamedChain::ArbitrumTestnet |
NamedChain::ArbitrumGoerli |
NamedChain::ArbitrumSepolia |
NamedChain::Moonbeam |
NamedChain::Moonriver |
NamedChain::Moonbase |
NamedChain::MoonbeamDev
NamedChain::Arbitrum
| NamedChain::ArbitrumTestnet
| NamedChain::ArbitrumGoerli
| NamedChain::ArbitrumSepolia
| NamedChain::Moonbeam
| NamedChain::Moonriver
| NamedChain::Moonbase
| NamedChain::MoonbeamDev
);
}
false
Expand All @@ -185,10 +185,10 @@ pub fn has_batch_support(chain_id: u64) -> bool {
if let Some(chain) = Chain::from(chain_id).named() {
return !matches!(
chain,
NamedChain::Arbitrum |
NamedChain::ArbitrumTestnet |
NamedChain::ArbitrumGoerli |
NamedChain::ArbitrumSepolia
NamedChain::Arbitrum
| NamedChain::ArbitrumTestnet
| NamedChain::ArbitrumGoerli
| NamedChain::ArbitrumSepolia
);
}
true
Expand Down Expand Up @@ -413,7 +413,7 @@ pub async fn handle_traces(
.decoder(&decoder)
.sources(sources)
.build();
debugger.try_run()?;
debugger.try_run_tui()?;
} else {
print_traces(&mut result, &decoder).await?;
}
Expand Down
30 changes: 17 additions & 13 deletions crates/common/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,9 +261,9 @@ impl ProjectCompiler {
let dev_functions =
artifact.abi.as_ref().map(|abi| abi.functions()).into_iter().flatten().filter(
|func| {
func.name.is_test() ||
func.name.eq("IS_TEST") ||
func.name.eq("IS_SCRIPT")
func.name.is_test()
|| func.name.eq("IS_TEST")
|| func.name.eq("IS_SCRIPT")
},
);

Expand All @@ -287,8 +287,8 @@ impl ProjectCompiler {
pub struct ContractSources {
/// Map over artifacts' contract names -> vector of file IDs
pub ids_by_name: HashMap<String, Vec<u32>>,
/// Map over file_id -> (source code, contract)
pub sources_by_id: FxHashMap<u32, (String, ContractBytecodeSome)>,
/// Map over file_id -> (source code, contract, source path)
pub sources_by_id: FxHashMap<u32, (String, ContractBytecodeSome, Option<PathBuf>)>,
}

impl ContractSources {
Expand All @@ -301,16 +301,17 @@ impl ContractSources {
for (id, artifact) in output.artifact_ids() {
if let Some(file_id) = artifact.id {
let abs_path = root.join(&id.source);
let source_code = std::fs::read_to_string(abs_path).wrap_err_with(|| {
format!("failed to read artifact source file for `{}`", id.identifier())
})?;
let source_code =
std::fs::read_to_string(abs_path.clone()).wrap_err_with(|| {
format!("failed to read artifact source file for `{}`", id.identifier())
})?;
let compact = CompactContractBytecode {
abi: artifact.abi.clone(),
bytecode: artifact.bytecode.clone(),
deployed_bytecode: artifact.deployed_bytecode.clone(),
};
let contract = compact_to_contract(compact)?;
sources.insert(&id, file_id, source_code, contract);
sources.insert(&id, file_id, source_code, contract, Some(abs_path));
} else {
warn!(id = id.identifier(), "source not found");
}
Expand All @@ -325,28 +326,31 @@ impl ContractSources {
file_id: u32,
source: String,
bytecode: ContractBytecodeSome,
source_path: Option<PathBuf>,
) {
self.ids_by_name.entry(artifact_id.name.clone()).or_default().push(file_id);
self.sources_by_id.insert(file_id, (source, bytecode));
self.sources_by_id.insert(file_id, (source, bytecode, source_path));
}

/// Returns the source for a contract by file ID.
pub fn get(&self, id: u32) -> Option<&(String, ContractBytecodeSome)> {
pub fn get(&self, id: u32) -> Option<&(String, ContractBytecodeSome, Option<PathBuf>)> {
self.sources_by_id.get(&id)
}

/// Returns all sources for a contract by name.
pub fn get_sources(
&self,
name: &str,
) -> Option<impl Iterator<Item = (u32, &(String, ContractBytecodeSome))>> {
) -> Option<impl Iterator<Item = (u32, &(String, ContractBytecodeSome, Option<PathBuf>))>> {
self.ids_by_name
.get(name)
.map(|ids| ids.iter().filter_map(|id| Some((*id, self.sources_by_id.get(id)?))))
}

/// Returns all (name, source) pairs.
pub fn entries(&self) -> impl Iterator<Item = (String, &(String, ContractBytecodeSome))> {
pub fn entries(
&self,
) -> impl Iterator<Item = (String, &(String, ContractBytecodeSome, Option<PathBuf>))> {
self.ids_by_name.iter().flat_map(|(name, ids)| {
ids.iter().filter_map(|id| self.sources_by_id.get(id).map(|s| (name.clone(), s)))
})
Expand Down
1 change: 1 addition & 0 deletions crates/debugger/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ eyre.workspace = true
ratatui = { version = "0.24.0", default-features = false, features = ["crossterm"] }
revm.workspace = true
tracing.workspace = true
serde.workspace = true
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! TUI debugger builder.
//! Debugger builder.

use crate::Debugger;
use alloy_primitives::Address;
Expand Down
16 changes: 16 additions & 0 deletions crates/debugger/src/context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use alloy_primitives::Address;
use foundry_common::compile::ContractSources;
use foundry_common::evm::Breakpoints;
use foundry_evm_core::debug::DebugNodeFlat;
use foundry_evm_core::utils::PcIcMap;
use std::collections::{BTreeMap, HashMap};

pub struct DebuggerContext {
pub debug_arena: Vec<DebugNodeFlat>,
pub identified_contracts: HashMap<Address, String>,
/// Source map of contract sources
pub contracts_sources: ContractSources,
/// A mapping of source -> (PC -> IC map for deploy code, PC -> IC map for runtime code)
pub pc_ic_maps: BTreeMap<String, (PcIcMap, PcIcMap)>,
pub breakpoints: Breakpoints,
}
83 changes: 83 additions & 0 deletions crates/debugger/src/debugger.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//! Debugger implementation.

use alloy_primitives::Address;
use eyre::Result;
use foundry_common::{compile::ContractSources, evm::Breakpoints};
use foundry_evm_core::{debug::DebugNodeFlat, utils::PcIcMap};
use revm::primitives::SpecId;
use std::collections::HashMap;
use std::path::PathBuf;

use crate::context::DebuggerContext;
use crate::tui::TUI;
use crate::{DebuggerBuilder, ExitReason, FileDumper};

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<DebugNodeFlat>,
identified_contracts: HashMap<Address, String>,
contracts_sources: ContractSources,
breakpoints: Breakpoints,
) -> Self {
let pc_ic_maps = contracts_sources
.entries()
.filter_map(|(contract_name, (_, contract, _))| {
Some((
contract_name.clone(),
(
PcIcMap::new(SpecId::LATEST, contract.bytecode.bytes()?),
PcIcMap::new(SpecId::LATEST, contract.deployed_bytecode.bytes()?),
),
))
})
.collect();
Self {
context: DebuggerContext {
debug_arena,
identified_contracts,
contracts_sources,
pc_ic_maps,
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<ExitReason> {
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()
}
}

0 comments on commit 27dbb83

Please sign in to comment.