Skip to content

Commit

Permalink
feat: display source name in debugger (#8154)
Browse files Browse the repository at this point in the history
* feat: display source name in debugger

* fmt

* clippy

* refactor
  • Loading branch information
klkvr authored Jun 13, 2024
1 parent 65bdd31 commit 6a85dba
Show file tree
Hide file tree
Showing 8 changed files with 52 additions and 48 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

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

1 change: 0 additions & 1 deletion crates/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ reqwest.workspace = true
semver = "1"
serde_json.workspace = true
serde.workspace = true
tempfile.workspace = true
thiserror = "1"
tokio.workspace = true
tracing.workspace = true
Expand Down
38 changes: 13 additions & 25 deletions crates/common/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ impl ProjectCompiler {
pub struct SourceData {
pub source: Arc<String>,
pub language: MultiCompilerLanguage,
pub name: String,
}

#[derive(Clone, Debug)]
Expand All @@ -297,24 +298,27 @@ impl ContractSources {
/// Collects the contract sources and artifacts from the project compile output.
pub fn from_project_output(
output: &ProjectCompileOutput,
link_data: Option<(&Path, &Libraries)>,
root: impl AsRef<Path>,
libraries: Option<&Libraries>,
) -> Result<Self> {
let mut sources = Self::default();

sources.insert(output, link_data)?;
sources.insert(output, root, libraries)?;

Ok(sources)
}

pub fn insert<C: Compiler>(
&mut self,
output: &ProjectCompileOutput<C>,
link_data: Option<(&Path, &Libraries)>,
root: impl AsRef<Path>,
libraries: Option<&Libraries>,
) -> Result<()>
where
C::Language: Into<MultiCompilerLanguage>,
{
let link_data = link_data.map(|(root, libraries)| {
let root = root.as_ref();
let link_data = libraries.map(|libraries| {
let linker = Linker::new(root, output.artifact_ids().collect());
(linker, libraries)
});
Expand Down Expand Up @@ -355,7 +359,11 @@ impl ContractSources {

self.sources_by_id.entry(build_id.clone()).or_default().insert(
*source_id,
SourceData { source: source_code, language: build.language.into() },
SourceData {
source: source_code,
language: build.language.into(),
name: path.strip_prefix(root).unwrap_or(path).to_string_lossy().to_string(),
},
);
}
}
Expand Down Expand Up @@ -496,26 +504,6 @@ pub fn compile_target<C: Compiler>(
ProjectCompiler::new().quiet(quiet).files([target_path.into()]).compile(project)
}

/// Compiles an Etherscan source from metadata by creating a project.
/// Returns the artifact_id, the file_id, and the bytecode
pub async fn compile_from_source(
metadata: &Metadata,
) -> Result<ProjectCompileOutput<SolcCompiler>> {
let root = tempfile::tempdir()?;
let root_path = root.path();
let project = etherscan_project(metadata, root_path)?;

let project_output = project.compile()?;

if project_output.has_compiler_errors() {
eyre::bail!("{project_output}")
}

root.close()?;

Ok(project_output)
}

/// Creates a [Project] from an Etherscan source.
pub fn etherscan_project(
metadata: &Metadata,
Expand Down
30 changes: 19 additions & 11 deletions crates/debugger/src/tui/draw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,24 +191,29 @@ impl DebuggerContext<'_> {
}

fn draw_src(&self, f: &mut Frame<'_>, area: Rect) {
let text_output = self.src_text(area);
let title = match self.call_kind() {
let (text_output, source_name) = self.src_text(area);
let call_kind_text = match self.call_kind() {
CallKind::Create | CallKind::Create2 => "Contract creation",
CallKind::Call => "Contract call",
CallKind::StaticCall => "Contract staticcall",
CallKind::CallCode => "Contract callcode",
CallKind::DelegateCall => "Contract delegatecall",
CallKind::AuthCall => "Contract authcall",
};
let title = format!(
"{} {} ",
call_kind_text,
source_name.map(|s| format!("| {s}")).unwrap_or_default()
);
let block = Block::default().title(title).borders(Borders::ALL);
let paragraph = Paragraph::new(text_output).block(block).wrap(Wrap { trim: false });
f.render_widget(paragraph, area);
}

fn src_text(&self, area: Rect) -> Text<'_> {
let (source_element, source_code) = match self.src_map() {
fn src_text(&self, area: Rect) -> (Text<'_>, Option<&str>) {
let (source_element, source_code, source_file) = match self.src_map() {
Ok(r) => r,
Err(e) => return Text::from(e),
Err(e) => return (Text::from(e), None),
};

// We are handed a vector of SourceElements that give us a span of sourcecode that is
Expand Down Expand Up @@ -330,10 +335,11 @@ impl DebuggerContext<'_> {
}
}

Text::from(lines.lines)
(Text::from(lines.lines), Some(source_file))
}

fn src_map(&self) -> Result<(SourceElement, &str), String> {
/// Returns source map, source code and source name of the current line.
fn src_map(&self) -> Result<(SourceElement, &str, &str), String> {
let address = self.address();
let Some(contract_name) = self.debugger.identified_contracts.get(address) else {
return Err(format!("Unknown contract at address {address}"));
Expand All @@ -351,7 +357,7 @@ impl DebuggerContext<'_> {

let is_create = matches!(self.call_kind(), CallKind::Create | CallKind::Create2);
let pc = self.current_step().pc;
let Some((source_element, source_code)) =
let Some((source_element, source_code, source_file)) =
files_source_code.find_map(|(artifact, source)| {
let bytecode = if is_create {
&artifact.bytecode.bytecode
Expand All @@ -376,7 +382,7 @@ impl DebuggerContext<'_> {
// if index matches current file_id, return current source code
.and_then(|index| {
(index == artifact.file_id)
.then(|| (source_element.clone(), source.source.as_str()))
.then(|| (source_element.clone(), source.source.as_str(), &source.name))
})
.or_else(|| {
// otherwise find the source code for the element's index
Expand All @@ -385,7 +391,9 @@ impl DebuggerContext<'_> {
.sources_by_id
.get(&artifact.build_id)?
.get(&source_element.index()?)
.map(|source| (source_element.clone(), source.source.as_str()))
.map(|source| {
(source_element.clone(), source.source.as_str(), &source.name)
})
});

res
Expand All @@ -394,7 +402,7 @@ impl DebuggerContext<'_> {
return Err(format!("No source map for contract {contract_name}"));
};

Ok((source_element, source_code))
Ok((source_element, source_code, source_file))
}

fn draw_op_list(&self, f: &mut Frame<'_>, area: Rect) {
Expand Down
1 change: 1 addition & 0 deletions crates/evm/traces/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ tokio = { workspace = true, features = ["time", "macros"] }
tracing = "0.1"
yansi.workspace = true
rustc-hash.workspace = true
tempfile.workspace = true

[dev-dependencies]
tempfile.workspace = true
23 changes: 15 additions & 8 deletions crates/evm/traces/src/identifier/etherscan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ use foundry_block_explorers::{
contract::{ContractMetadata, Metadata},
errors::EtherscanError,
};
use foundry_common::compile::{self, ContractSources};
use foundry_common::compile::{etherscan_project, ContractSources};
use foundry_config::{Chain, Config};
use futures::{
future::{join_all, Future},
stream::{FuturesUnordered, Stream, StreamExt},
task::{Context, Poll},
TryFutureExt,
};
use std::{
borrow::Cow,
Expand Down Expand Up @@ -64,11 +63,18 @@ impl EtherscanIdentifier {
.iter()
// filter out vyper files
.filter(|(_, metadata)| !metadata.is_vyper())
.map(|(address, metadata)| {
.map(|(address, metadata)| async move {
println!("Compiling: {} {address}", metadata.contract_name);
let err_msg =
format!("Failed to compile contract {} from {address}", metadata.contract_name);
compile::compile_from_source(metadata).map_err(move |err| err.wrap_err(err_msg))
let root = tempfile::tempdir()?;
let root_path = root.path();
let project = etherscan_project(metadata, root_path)?;
let output = project.compile()?;

if output.has_compiler_errors() {
eyre::bail!("{output}")
}

Ok((project, output, root))
})
.collect::<Vec<_>>();

Expand All @@ -78,8 +84,9 @@ impl EtherscanIdentifier {
let mut sources: ContractSources = Default::default();

// construct the map
for output in outputs {
sources.insert(&output?, None)?;
for res in outputs {
let (project, output, _) = res?;
sources.insert(&output, project.root(), None)?;
}

Ok(sources)
Expand Down
3 changes: 2 additions & 1 deletion crates/forge/bin/cmd/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,8 @@ impl TestArgs {

let sources = ContractSources::from_project_output(
output_clone.as_ref().unwrap(),
Some((project.root(), &libraries)),
project.root(),
Some(&libraries),
)?;

// Run the debugger.
Expand Down
3 changes: 2 additions & 1 deletion crates/script/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ impl LinkedBuildData {
) -> Result<Self> {
let sources = ContractSources::from_project_output(
&build_data.output,
Some((&build_data.project_root, &libraries)),
&build_data.project_root,
Some(&libraries),
)?;

let known_contracts =
Expand Down

0 comments on commit 6a85dba

Please sign in to comment.