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

bump compilers #8126

Merged
merged 9 commits into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 4 additions & 4 deletions Cargo.lock

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

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ foundry-wallets = { path = "crates/wallets" }
foundry-linking = { path = "crates/linking" }

# solc & compilation utilities
foundry-block-explorers = { version = "0.3.0", default-features = false }
foundry-compilers = { version = "0.6.2", default-features = false }
foundry-block-explorers = { version = "0.4.0", default-features = false }
foundry-compilers = { version = "0.7.0", default-features = false }

## revm
# no default features to avoid c-kzg
Expand Down
184 changes: 99 additions & 85 deletions crates/common/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ use eyre::{Context, Result};
use foundry_block_explorers::contract::Metadata;
use foundry_compilers::{
artifacts::{BytecodeObject, ContractBytecodeSome, Libraries, Source},
compilers::{solc::SolcCompiler, CompilationError, Compiler},
compilers::{multi::MultiCompilerLanguage, solc::SolcCompiler, Compiler},
remappings::Remapping,
report::{BasicStdoutReporter, NoReporter, Report},
Artifact, ArtifactId, Project, ProjectBuilder, ProjectCompileOutput, ProjectPathsConfig, Solc,
SolcConfig,
Artifact, Project, ProjectBuilder, ProjectCompileOutput, ProjectPathsConfig, Solc, SolcConfig,
};
use foundry_linking::Linker;
use num_format::{Locale, ToFormattedString};
Expand All @@ -20,6 +19,7 @@ use std::{
fmt::Display,
io::IsTerminal,
path::{Path, PathBuf},
sync::Arc,
time::Instant,
};

Expand Down Expand Up @@ -122,10 +122,7 @@ impl ProjectCompiler {
}

/// Compiles the project.
pub fn compile<C: Compiler>(
mut self,
project: &Project<C>,
) -> Result<ProjectCompileOutput<C::CompilationError>> {
pub fn compile<C: Compiler>(mut self, project: &Project<C>) -> Result<ProjectCompileOutput<C>> {
// TODO: Avoid process::exit
if !project.paths.has_input_files() && self.files.is_empty() {
println!("Nothing to compile");
Expand Down Expand Up @@ -159,9 +156,9 @@ impl ProjectCompiler {
/// ProjectCompiler::new().compile_with(|| Ok(prj.compile()?)).unwrap();
/// ```
#[instrument(target = "forge::compile", skip_all)]
fn compile_with<E: CompilationError, F>(self, f: F) -> Result<ProjectCompileOutput<E>>
fn compile_with<C: Compiler, F>(self, f: F) -> Result<ProjectCompileOutput<C>>
where
F: FnOnce() -> Result<ProjectCompileOutput<E>>,
F: FnOnce() -> Result<ProjectCompileOutput<C>>,
{
let quiet = self.quiet.unwrap_or(false);
let bail = self.bail.unwrap_or(true);
Expand Down Expand Up @@ -209,7 +206,7 @@ impl ProjectCompiler {
}

/// If configured, this will print sizes or names
fn handle_output<E>(&self, output: &ProjectCompileOutput<E>) {
fn handle_output<C: Compiler>(&self, output: &ProjectCompileOutput<C>) {
let print_names = self.print_names.unwrap_or(false);
let print_sizes = self.print_sizes.unwrap_or(false);

Expand Down Expand Up @@ -274,90 +271,121 @@ impl ProjectCompiler {
}
}

/// Contract source code and bytecode.
#[derive(Clone, Debug)]
pub struct SourceData {
pub source: Arc<String>,
pub language: MultiCompilerLanguage,
}

#[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 artifacts' contract names -> vector of file IDs
pub ids_by_name: HashMap<String, Vec<u32>>,
/// Map over file_id -> source code
pub sources_by_id: FxHashMap<u32, String>,
/// Map over file_id -> contract name -> bytecode
pub artifacts_by_id: FxHashMap<u32, HashMap<String, ContractBytecodeSome>>,
/// 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: &Path,
libraries: &Libraries,
link_data: Option<(&Path, &Libraries)>,
) -> Result<Self> {
let linker = Linker::new(root, output.artifact_ids().collect());

let mut sources = Self::default();

sources.insert(output, link_data)?;

Ok(sources)
}

pub fn insert<C: Compiler>(
&mut self,
output: &ProjectCompileOutput<C>,
link_data: Option<(&Path, &Libraries)>,
) -> Result<()>
where
C::Language: Into<MultiCompilerLanguage>,
{
let link_data = link_data.map(|(root, 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 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 linked = linker.link(&id, libraries)?;
let contract = compact_to_contract(linked.into_contract_bytecode())?;
sources.insert(&id, file_id, source_code, contract);
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");
}
}
Ok(sources)
}

/// Inserts a contract into the sources.
pub fn insert(
&mut self,
artifact_id: &ArtifactId,
file_id: u32,
source: String,
bytecode: ContractBytecodeSome,
) {
self.ids_by_name.entry(artifact_id.name.clone()).or_default().push(file_id);
self.sources_by_id.insert(file_id, source);
self.artifacts_by_id.entry(file_id).or_default().insert(artifact_id.name.clone(), bytecode);
}
// 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() },
);
}
}

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

/// Returns all sources for a contract by name.
pub fn get_sources<'a>(
&'a self,
name: &'a str,
) -> Option<impl Iterator<Item = (u32, &'_ str, &'_ ContractBytecodeSome)>> {
self.ids_by_name.get(name).map(|ids| {
ids.iter().filter_map(|id| {
Some((
*id,
self.sources_by_id.get(id)?.as_ref(),
self.artifacts_by_id.get(id)?.get(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, source, bytecode) sets.
pub fn entries(&self) -> impl Iterator<Item = (&str, &str, &ContractBytecodeSome)> {
self.artifacts_by_id
.iter()
.filter_map(|(id, artifacts)| {
let source = self.sources_by_id.get(id)?;
Some(
artifacts
.iter()
.map(move |(name, bytecode)| (name.as_ref(), source.as_ref(), bytecode)),
)
/// 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))
})
.flatten()
})
}
}

Expand Down Expand Up @@ -464,15 +492,15 @@ pub fn compile_target<C: Compiler>(
target_path: &Path,
project: &Project<C>,
quiet: bool,
) -> Result<ProjectCompileOutput<C::CompilationError>> {
) -> Result<ProjectCompileOutput<C>> {
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<(ArtifactId, u32, ContractBytecodeSome)> {
) -> Result<ProjectCompileOutput<SolcCompiler>> {
let root = tempfile::tempdir()?;
let root_path = root.path();
let project = etherscan_project(metadata, root_path)?;
Expand All @@ -483,23 +511,9 @@ pub async fn compile_from_source(
eyre::bail!("{project_output}")
}

let (artifact_id, file_id, contract) = project_output
.into_artifacts()
.find(|(artifact_id, _)| artifact_id.name == metadata.contract_name)
.map(|(aid, art)| {
(aid, art.source_file().expect("no source file").id, art.into_contract_bytecode())
})
.ok_or_else(|| {
eyre::eyre!(
"Unable to find bytecode in compiled output for contract: {}",
metadata.contract_name
)
})?;
let bytecode = compact_to_contract(contract)?;

root.close()?;

Ok((artifact_id, file_id, bytecode))
Ok(project_output)
}

/// Creates a [Project] from an Etherscan source.
Expand Down
31 changes: 21 additions & 10 deletions crates/debugger/src/tui/draw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use super::context::{BufferKind, DebuggerContext};
use crate::op::OpcodeParam;
use alloy_primitives::U256;
use foundry_compilers::sourcemap::SourceElement;
use foundry_compilers::{compilers::multi::MultiCompilerLanguage, sourcemap::SourceElement};
use ratatui::{
layout::{Alignment, Constraint, Direction, Layout, Rect},
style::{Color, Modifier, Style},
Expand Down Expand Up @@ -344,32 +344,43 @@ 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)) =
files_source_code.find_map(|(file_id, source_code, contract_source)| {
files_source_code.find_map(|(artifact, source)| {
let bytecode = if is_create {
&contract_source.bytecode
&artifact.bytecode.bytecode
} else {
contract_source.deployed_bytecode.bytecode.as_ref()?
artifact.bytecode.deployed_bytecode.bytecode.as_ref()?
};
let source_map = bytecode.source_map()?.ok()?;
let source_map = bytecode.source_map()?.expect("failed to parse");

let pc_ic_map = if is_create { create_map } else { rt_map };
let ic = pc_ic_map.get(pc)?;
let source_element = source_map.get(ic)?;

// Solc indexes source maps by instruction counter, but Vyper indexes by program
// counter.
let source_element = if matches!(source.language, MultiCompilerLanguage::Solc(_)) {
source_map.get(ic)?
} else {
source_map.get(pc)?
};
// if the source element has an index, find the sourcemap for that index
source_element
let res = source_element
.index()
// if index matches current file_id, return current source code
.and_then(|index| {
(index == file_id).then(|| (source_element.clone(), source_code))
(index == artifact.file_id)
.then(|| (source_element.clone(), source.source.as_str()))
})
.or_else(|| {
// otherwise find the source code for the element's index
self.debugger
.contracts_sources
.sources_by_id
.get(&artifact.build_id)?
.get(&source_element.index()?)
.map(|source_code| (source_element.clone(), source_code.as_ref()))
})
.map(|source| (source_element.clone(), source.source.as_str()))
});

res
})
else {
return Err(format!("No source map for contract {contract_name}"));
Expand Down
8 changes: 4 additions & 4 deletions crates/debugger/src/tui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,12 @@ impl Debugger {
) -> Self {
let pc_ic_maps = contracts_sources
.entries()
.filter_map(|(contract_name, _, contract)| {
.filter_map(|(name, artifact, _)| {
Some((
contract_name.to_owned(),
name.to_owned(),
(
PcIcMap::new(contract.bytecode.bytes()?),
PcIcMap::new(contract.deployed_bytecode.bytes()?),
PcIcMap::new(artifact.bytecode.bytecode.bytes()?),
PcIcMap::new(artifact.bytecode.deployed_bytecode.bytes()?),
),
))
})
Expand Down
Loading
Loading