Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: identify internal function invocations in traces #8222

Merged
merged 38 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
0460633
fix: small debugger updates
klkvr Jun 20, 2024
9fd779a
[wip] feat: identify internal function invocations in traces
klkvr Jun 21, 2024
83c7a23
fmt
klkvr Jun 21, 2024
b1a365f
doc
klkvr Jun 21, 2024
2d17d37
correctly enable tracing
klkvr Jun 21, 2024
4728d2e
correctly enable tracing
klkvr Jun 21, 2024
5ed1abf
collect contract definition locs
klkvr Jun 22, 2024
6518cb9
feat: print traces in format of Contract::function
klkvr Jun 22, 2024
a038e05
Merge branch 'master' into klkvr/internal-fns-in-traces
klkvr Jun 22, 2024
06dc30a
wip
klkvr Jun 23, 2024
216e9da
refactor
klkvr Jun 23, 2024
d92f436
clippy
klkvr Jun 23, 2024
7fa698b
fix doc
klkvr Jun 23, 2024
b3ef110
track input/output values
klkvr Jun 24, 2024
3d59b3f
Merge branch 'master' into klkvr/internal-fns-in-traces
klkvr Jun 24, 2024
5972083
clippy
klkvr Jun 24, 2024
e9e97a0
clean up
klkvr Jun 26, 2024
44e976d
Merge branch 'master' into klkvr/internal-fns-in-traces
klkvr Jun 27, 2024
08fd6c5
TraceMode
klkvr Jun 27, 2024
34644d3
small fixes
klkvr Jun 27, 2024
a725b28
add doc
klkvr Jun 27, 2024
c47abbb
clippy
klkvr Jun 27, 2024
7d362f7
safer decofing from stack and memory
klkvr Jun 28, 2024
4d44529
use Into<Option<TraceMode>>
klkvr Jun 28, 2024
ecb5f13
TraceMode::None
klkvr Jun 28, 2024
6503571
fmt
klkvr Jun 28, 2024
7976e27
Merge branch 'master' into klkvr/internal-fns-in-traces
klkvr Jun 29, 2024
3a01a97
Merge branch 'master' into klkvr/internal-fns-in-traces
klkvr Jun 30, 2024
0031d77
Merge branch 'master' into klkvr/internal-fns-in-traces
klkvr Jul 8, 2024
e9ec97e
Merge branch 'master' into klkvr/internal-fns-in-traces
klkvr Jul 9, 2024
ec783d6
review fixes
klkvr Jul 9, 2024
ff2a11b
--decode-internal for single fn
klkvr Jul 9, 2024
e46c017
use Vec
klkvr Jul 9, 2024
062d550
TraceMode builder
klkvr Jul 9, 2024
5568fc7
Merge branch 'master' into klkvr/internal-fns-in-traces
klkvr Jul 11, 2024
9db774f
optional --decode-internal and tests
klkvr Jul 11, 2024
97c2b4b
update doc
klkvr Jul 11, 2024
92ece42
InternalTraceMode
klkvr Jul 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Cargo.lock

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

8 changes: 6 additions & 2 deletions crates/cast/bin/cmd/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ pub struct CallArgs {
#[arg(long, requires = "trace")]
debug: bool,

#[arg(long, requires = "trace")]
decode_internal: bool,

/// Labels to apply to the traces; format: `address:label`.
/// Can only be used with `--trace`.
#[arg(long, requires = "trace")]
Expand Down Expand Up @@ -106,6 +109,7 @@ impl CallArgs {
trace,
evm_version,
debug,
decode_internal,
labels,
data,
} = self;
Expand Down Expand Up @@ -159,7 +163,7 @@ impl CallArgs {
}

let (env, fork, chain) = TracingExecutor::get_fork_material(&config, evm_opts).await?;
let mut executor = TracingExecutor::new(env, fork, evm_version, debug);
let mut executor = TracingExecutor::new(env, fork, evm_version, debug, decode_internal);

let value = tx.value.unwrap_or_default();
let input = tx.inner.input.into_input().unwrap_or_default();
Expand All @@ -175,7 +179,7 @@ impl CallArgs {
),
};

handle_traces(trace, &config, chain, labels, debug).await?;
handle_traces(trace, &config, chain, labels, debug, decode_internal).await?;

return Ok(());
}
Expand Down
9 changes: 7 additions & 2 deletions crates/cast/bin/cmd/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ pub struct RunArgs {
#[arg(long, short)]
debug: bool,

/// Whether to identify internal functions in traces.
#[arg(long)]
decode_internal: bool,

/// Print out opcode traces.
#[arg(long, short)]
trace_printer: bool,
Expand Down Expand Up @@ -142,7 +146,8 @@ impl RunArgs {
}
}

let mut executor = TracingExecutor::new(env.clone(), fork, evm_version, self.debug);
let mut executor =
TracingExecutor::new(env.clone(), fork, evm_version, self.debug, self.decode_internal);
let mut env =
EnvWithHandlerCfg::new_with_spec_id(Box::new(env.clone()), executor.spec_id());

Expand Down Expand Up @@ -220,7 +225,7 @@ impl RunArgs {
}
};

handle_traces(result, &config, chain, self.label, self.debug).await?;
handle_traces(result, &config, chain, self.label, self.debug, self.decode_internal).await?;

Ok(())
}
Expand Down
14 changes: 14 additions & 0 deletions crates/cli/src/utils/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use foundry_evm::{
executors::{DeployResult, EvmError, RawCallResult},
opts::EvmOpts,
traces::{
debug::DebugTraceIdentifier,
identifier::{EtherscanIdentifier, SignaturesIdentifier},
render_trace_arena, CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, Traces,
},
Expand Down Expand Up @@ -357,6 +358,7 @@ pub async fn handle_traces(
chain: Option<Chain>,
labels: Vec<String>,
debug: bool,
decode_internal: bool,
) -> Result<()> {
let labels = labels.iter().filter_map(|label_str| {
let mut iter = label_str.split(':');
Expand Down Expand Up @@ -384,6 +386,18 @@ pub async fn handle_traces(
}
}

if decode_internal {
let sources = if let Some(etherscan_identifier) = &etherscan_identifier {
etherscan_identifier.get_compiled_contracts().await?
} else {
Default::default()
};
mattsse marked this conversation as resolved.
Show resolved Hide resolved

let identifier = DebugTraceIdentifier::new(sources);

decoder.debug_identifier = Some(identifier);
klkvr marked this conversation as resolved.
Show resolved Hide resolved
}

if debug {
let sources = if let Some(etherscan_identifier) = etherscan_identifier {
etherscan_identifier.get_compiled_contracts().await?
Expand Down
1 change: 0 additions & 1 deletion crates/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ workspace = true
foundry-block-explorers = { workspace = true, features = ["foundry-compilers"] }
foundry-compilers.workspace = true
foundry-config.workspace = true
foundry-linking.workspace = true

alloy-consensus.workspace = true
alloy-contract.workspace = true
Expand Down
140 changes: 5 additions & 135 deletions crates/common/src/compile.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,24 @@
//! Support for compiling [foundry_compilers::Project]

use crate::{compact_to_contract, term::SpinnerReporter, TestFunctionExt};
use crate::{term::SpinnerReporter, TestFunctionExt};
use comfy_table::{presets::ASCII_MARKDOWN, Attribute, Cell, CellAlignment, Color, Table};
use eyre::{Context, Result};
use eyre::Result;
use foundry_block_explorers::contract::Metadata;
use foundry_compilers::{
artifacts::{remappings::Remapping, BytecodeObject, ContractBytecodeSome, Libraries, Source},
artifacts::{remappings::Remapping, BytecodeObject, Source},
compilers::{
multi::MultiCompilerLanguage,
solc::{Solc, SolcCompiler},
Compiler,
},
report::{BasicStdoutReporter, NoReporter, Report},
Artifact, Project, ProjectBuilder, ProjectCompileOutput, ProjectPathsConfig, SolcConfig,
};
use foundry_linking::Linker;
use num_format::{Locale, ToFormattedString};
use rustc_hash::FxHashMap;
use std::{
collections::{BTreeMap, HashMap},
collections::BTreeMap,
fmt::Display,
io::IsTerminal,
path::{Path, PathBuf},
sync::Arc,
time::Instant,
};

Expand Down Expand Up @@ -261,132 +257,6 @@ impl ProjectCompiler {
}
}

#[derive(Clone, Debug)]
pub struct SourceData {
pub source: Arc<String>,
pub language: MultiCompilerLanguage,
pub name: String,
}

#[derive(Clone, Debug)]
pub struct ArtifactData {
pub bytecode: ContractBytecodeSome,
pub build_id: String,
pub file_id: u32,
}

/// Contract source code and bytecode data used for debugger.
#[derive(Clone, Debug, Default)]
pub struct ContractSources {
/// Map over build_id -> file_id -> (source code, language)
pub sources_by_id: HashMap<String, FxHashMap<u32, SourceData>>,
/// Map over contract name -> Vec<(bytecode, build_id, file_id)>
pub artifacts_by_name: HashMap<String, Vec<ArtifactData>>,
}

impl ContractSources {
/// Collects the contract sources and artifacts from the project compile output.
pub fn from_project_output(
output: &ProjectCompileOutput,
root: impl AsRef<Path>,
libraries: Option<&Libraries>,
) -> Result<Self> {
let mut sources = Self::default();

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

Ok(sources)
}

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

for (id, artifact) in output.artifact_ids() {
if let Some(file_id) = artifact.id {
let artifact = if let Some((linker, libraries)) = link_data.as_ref() {
linker.link(&id, libraries)?.into_contract_bytecode()
} else {
artifact.clone().into_contract_bytecode()
};
let bytecode = compact_to_contract(artifact.clone().into_contract_bytecode())?;

self.artifacts_by_name.entry(id.name.clone()).or_default().push(ArtifactData {
bytecode,
build_id: id.build_id.clone(),
file_id,
});
} else {
warn!(id = id.identifier(), "source not found");
}
}

// Not all source files produce artifacts, so we are populating sources by using build
// infos.
let mut files: BTreeMap<PathBuf, Arc<String>> = BTreeMap::new();
for (build_id, build) in output.builds() {
for (source_id, path) in &build.source_id_to_path {
let source_code = if let Some(source) = files.get(path) {
source.clone()
} else {
let source = Source::read(path).wrap_err_with(|| {
format!("failed to read artifact source file for `{}`", path.display())
})?;
files.insert(path.clone(), source.content.clone());
source.content
};

self.sources_by_id.entry(build_id.clone()).or_default().insert(
*source_id,
SourceData {
source: source_code,
language: build.language.into(),
name: path.strip_prefix(root).unwrap_or(path).to_string_lossy().to_string(),
},
);
}
}

Ok(())
}

/// Returns all sources for a contract by name.
pub fn get_sources(
&self,
name: &str,
) -> Option<impl Iterator<Item = (&ArtifactData, &SourceData)>> {
self.artifacts_by_name.get(name).map(|artifacts| {
artifacts.iter().filter_map(|artifact| {
let source =
self.sources_by_id.get(artifact.build_id.as_str())?.get(&artifact.file_id)?;
Some((artifact, source))
})
})
}

/// Returns all (name, bytecode, source) sets.
pub fn entries(&self) -> impl Iterator<Item = (&str, &ArtifactData, &SourceData)> {
self.artifacts_by_name.iter().flat_map(|(name, artifacts)| {
artifacts.iter().filter_map(|artifact| {
let source =
self.sources_by_id.get(artifact.build_id.as_str())?.get(&artifact.file_id)?;
Some((name.as_str(), artifact, source))
})
})
}
}

// https://eips.ethereum.org/EIPS/eip-170
const CONTRACT_SIZE_LIMIT: usize = 24576;

Expand Down Expand Up @@ -503,7 +373,7 @@ pub fn etherscan_project(
let sources_path = target_path.join(&metadata.contract_name);
metadata.source_tree().write_to(&target_path)?;

let mut settings = metadata.source_code.settings()?.unwrap_or_default();
let mut settings = metadata.settings()?;

// make remappings absolute with our root
for remapping in settings.remappings.iter_mut() {
Expand Down
3 changes: 0 additions & 3 deletions crates/common/src/contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,6 @@ pub struct ContractsByArtifact(Arc<BTreeMap<ArtifactId, ContractData>>);

impl ContractsByArtifact {
/// Creates a new instance by collecting all artifacts with present bytecode from an iterator.
///
/// It is recommended to use this method with an output of
/// [foundry_linking::Linker::get_linked_artifacts].
pub fn new(artifacts: impl IntoIterator<Item = (ArtifactId, CompactContractBytecode)>) -> Self {
let map = artifacts
.into_iter()
Expand Down
4 changes: 2 additions & 2 deletions crates/debugger/src/tui/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

use crate::Debugger;
use alloy_primitives::Address;
use foundry_common::{compile::ContractSources, evm::Breakpoints, get_contract_name};
use foundry_common::{evm::Breakpoints, get_contract_name};
use foundry_evm_core::debug::{DebugArena, DebugNodeFlat};
use foundry_evm_traces::CallTraceDecoder;
use foundry_evm_traces::{debug::ContractSources, CallTraceDecoder};
use std::collections::HashMap;

/// Debugger builder.
Expand Down
Loading
Loading