Skip to content

Commit

Permalink
feat: optimize compilation by reading AST (#7599)
Browse files Browse the repository at this point in the history
* feat: optimize compiler runs by reading AST

* clippy

* fallback to solc

* fmt

* clippy

* update fixtures

* fix for windows

* wip

* bump compilers

* clippy + fmt

* fix
  • Loading branch information
klkvr authored Apr 17, 2024
1 parent 46abc42 commit 0a4d246
Show file tree
Hide file tree
Showing 12 changed files with 53 additions and 85 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ foundry-linking = { path = "crates/linking" }

# solc & compilation utilities
foundry-block-explorers = { version = "0.2.6", default-features = false }
foundry-compilers = { version = "0.3.14", default-features = false }
foundry-compilers = { version = "0.3.17", default-features = false }

## revm
# no default features to avoid c-kzg
Expand Down
24 changes: 14 additions & 10 deletions crates/cli/src/utils/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use foundry_common::{cli_warn, fs, TestFunctionExt};
use foundry_compilers::{
artifacts::{CompactBytecode, CompactDeployedBytecode},
cache::{CacheEntry, SolFilesCache},
info::ContractInfo,
utils::read_json_file,
Artifact, ProjectCompileOutput,
};
Expand All @@ -20,24 +19,29 @@ use foundry_evm::{
render_trace_arena, CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, Traces,
},
};
use std::{fmt::Write, path::PathBuf, str::FromStr};
use std::{
fmt::Write,
path::{Path, PathBuf},
str::FromStr,
};
use yansi::Paint;

/// Given a `Project`'s output, removes the matching ABI, Bytecode and
/// Runtime Bytecode of the given contract.
#[track_caller]
pub fn remove_contract(
output: &mut ProjectCompileOutput,
info: &ContractInfo,
path: &Path,
name: &str,
) -> Result<(JsonAbi, CompactBytecode, CompactDeployedBytecode)> {
let contract = if let Some(contract) = output.remove_contract(info) {
let contract = if let Some(contract) = output.remove(path.to_string_lossy(), name) {
contract
} else {
let mut err = format!("could not find artifact: `{}`", info.name);
let mut err = format!("could not find artifact: `{}`", name);
if let Some(suggestion) =
super::did_you_mean(&info.name, output.artifacts().map(|(name, _)| name)).pop()
super::did_you_mean(name, output.artifacts().map(|(name, _)| name)).pop()
{
if suggestion != info.name {
if suggestion != name {
err = format!(
r#"{err}
Expand All @@ -50,17 +54,17 @@ pub fn remove_contract(

let abi = contract
.get_abi()
.ok_or_else(|| eyre::eyre!("contract {} does not contain abi", info))?
.ok_or_else(|| eyre::eyre!("contract {} does not contain abi", name))?
.into_owned();

let bin = contract
.get_bytecode()
.ok_or_else(|| eyre::eyre!("contract {} does not contain bytecode", info))?
.ok_or_else(|| eyre::eyre!("contract {} does not contain bytecode", name))?
.into_owned();

let runtime = contract
.get_deployed_bytecode()
.ok_or_else(|| eyre::eyre!("contract {} does not contain deployed bytecode", info))?
.ok_or_else(|| eyre::eyre!("contract {} does not contain deployed bytecode", name))?
.into_owned();

Ok((abi, bin, runtime))
Expand Down
23 changes: 4 additions & 19 deletions crates/common/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use foundry_compilers::{
artifacts::{BytecodeObject, ContractBytecodeSome, Libraries},
remappings::Remapping,
report::{BasicStdoutReporter, NoReporter, Report},
Artifact, ArtifactId, FileFilter, Graph, Project, ProjectCompileOutput, ProjectPathsConfig,
Solc, SolcConfig,
Artifact, ArtifactId, FileFilter, Project, ProjectCompileOutput, ProjectPathsConfig, Solc,
SolcConfig,
};
use foundry_linking::Linker;
use num_format::{Locale, ToFormattedString};
Expand Down Expand Up @@ -470,27 +470,12 @@ pub struct ContractInfo {
/// If `verify` and it's a standalone script, throw error. Only allowed for projects.
///
/// **Note:** this expects the `target_path` to be absolute
pub fn compile_target_with_filter(
pub fn compile_target(
target_path: &Path,
project: &Project,
quiet: bool,
verify: bool,
skip: Vec<SkipBuildFilter>,
) -> Result<ProjectCompileOutput> {
let graph = Graph::resolve(&project.paths)?;

// Checking if it's a standalone script, or part of a project.
let mut compiler = ProjectCompiler::new().quiet(quiet);
if !skip.is_empty() {
compiler = compiler.filter(Box::new(SkipBuildFilters::new(skip)?));
}
if !graph.files().contains_key(target_path) {
if verify {
eyre::bail!("You can only verify deployments from inside a project! Make sure it exists with `forge tree`.");
}
compiler = compiler.files([target_path.into()]);
}
compiler.compile(project)
ProjectCompiler::new().quiet(quiet).files([target_path.into()]).compile(project)
}

/// Compiles an Etherscan source from metadata by creating a project.
Expand Down
1 change: 1 addition & 0 deletions crates/config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4553,6 +4553,7 @@ mod tests {
stack_allocation: None,
optimizer_steps: Some("dhfoDgvulfnTUtnIf".to_string()),
}),
simple_counter_for_loop_unchecked_increment: None,
}),
..Default::default()
};
Expand Down
22 changes: 13 additions & 9 deletions crates/forge/bin/cmd/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ use foundry_cli::{
utils::{self, read_constructor_args_file, remove_contract, LoadConfig},
};
use foundry_common::{
compile::ProjectCompiler, fmt::parse_tokens, provider::alloy::estimate_eip1559_fees,
compile::{self},
fmt::parse_tokens,
provider::alloy::estimate_eip1559_fees,
};
use foundry_compilers::{artifacts::BytecodeObject, info::ContractInfo, utils::canonicalized};
use foundry_compilers::{artifacts::BytecodeObject, info::ContractInfo, utils::canonicalize};
use serde_json::json;
use std::{borrow::Borrow, marker::PhantomData, path::PathBuf, sync::Arc};

Expand Down Expand Up @@ -84,15 +86,17 @@ impl CreateArgs {
pub async fn run(mut self) -> Result<()> {
// Find Project & Compile
let project = self.opts.project()?;
let mut output =
ProjectCompiler::new().quiet_if(self.json || self.opts.silent).compile(&project)?;

if let Some(ref mut path) = self.contract.path {
// paths are absolute in the project's output
*path = canonicalized(project.root().join(&path)).to_string_lossy().to_string();
}
let target_path = if let Some(ref mut path) = self.contract.path {
canonicalize(project.root().join(path))?
} else {
project.find_contract_path(&self.contract.name)?
};

let mut output =
compile::compile_target(&target_path, &project, self.json || self.opts.silent)?;

let (abi, bin, _) = remove_contract(&mut output, &self.contract)?;
let (abi, bin, _) = remove_contract(&mut output, &target_path, &self.contract.name)?;

let bin = match bin.object {
BytecodeObject::Bytecode(_) => bin.object,
Expand Down
4 changes: 2 additions & 2 deletions crates/forge/bin/cmd/flatten.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use foundry_cli::{
opts::{CoreBuildArgs, ProjectPathsArgs},
utils::LoadConfig,
};
use foundry_common::{compile::ProjectCompiler, fs};
use foundry_common::{compile::compile_target, fs};
use foundry_compilers::{error::SolcError, flatten::Flattener};
use std::path::PathBuf;

Expand Down Expand Up @@ -42,7 +42,7 @@ impl FlattenArgs {
let project = config.ephemeral_no_artifacts_project()?;

let target_path = dunce::canonicalize(target_path)?;
let compiler_output = ProjectCompiler::new().files([target_path.clone()]).compile(&project);
let compiler_output = compile_target(&target_path, &project, false);

let flattened = match compiler_output {
Ok(compiler_output) => {
Expand Down
9 changes: 7 additions & 2 deletions crates/forge/bin/cmd/selectors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use foundry_cli::{
utils::FoundryPathExt,
};
use foundry_common::{
compile::ProjectCompiler,
compile::{compile_target, ProjectCompiler},
selectors::{import_selectors, SelectorImportData},
};
use foundry_compilers::{artifacts::output_selection::ContractOutputSelection, info::ContractInfo};
Expand Down Expand Up @@ -71,7 +71,12 @@ impl SelectorsSubcommands {
};

let project = build_args.project()?;
let output = ProjectCompiler::new().quiet(true).compile(&project)?;
let output = if let Some(name) = &contract {
let target_path = project.find_contract_path(name)?;
compile_target(&target_path, &project, false)?
} else {
ProjectCompiler::new().compile(&project)?
};
let artifacts = if all {
output
.into_artifacts_with_files()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Compiling 27 files with 0.8.23
Compiling 1 files with 0.8.23
Solc 0.8.23 finished in 2.27s
Compiler run successful!
Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Compiling 27 files with 0.8.23
Compiling 1 files with 0.8.23
Solc 0.8.23 finished in 1.95s
Compiler run successful!
Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Compiling 28 files with 0.8.23
Compiling 1 files with 0.8.23
Solc 0.8.23 finished in 2.82s
Compiler run successful!
Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Expand Down
43 changes: 6 additions & 37 deletions crates/script/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,15 @@ use crate::{

use alloy_primitives::{Address, Bytes};
use alloy_provider::Provider;
use eyre::{Context, OptionExt, Result};
use eyre::{OptionExt, Result};
use foundry_cheatcodes::ScriptWallets;
use foundry_cli::utils::get_cached_entry_by_name;
use foundry_common::{
compile::{self, ContractSources, ProjectCompiler},
compile::{self, ContractSources},
provider::alloy::try_get_http_provider,
ContractData, ContractsByArtifact,
};
use foundry_compilers::{
artifacts::{BytecodeObject, Libraries},
cache::SolFilesCache,
info::ContractInfo,
ArtifactId, ProjectCompileOutput,
};
Expand Down Expand Up @@ -149,54 +147,25 @@ impl PreprocessedState {
pub fn compile(self) -> Result<CompiledState> {
let Self { args, script_config, script_wallets } = self;
let project = script_config.config.project()?;
let filters = args.skip.clone().unwrap_or_default();

let mut target_name = args.target_contract.clone();

// If we've received correct path, use it as target_path
// Otherwise, parse input as <path>:<name> and use the path from the contract info, if
// present.
let target_path = if let Ok(path) = dunce::canonicalize(&args.path) {
Some(path)
path
} else {
let contract = ContractInfo::from_str(&args.path)?;
target_name = Some(contract.name.clone());
if let Some(path) = contract.path {
Some(dunce::canonicalize(path)?)
dunce::canonicalize(path)?
} else {
None
project.find_contract_path(contract.name.as_str())?
}
};

// If we've found target path above, only compile it.
// Otherwise, compile everything to match contract by name later.
let output = if let Some(target_path) = target_path.clone() {
compile::compile_target_with_filter(
&target_path,
&project,
args.opts.silent,
args.verify,
filters,
)
} else if !project.paths.has_input_files() {
Err(eyre::eyre!("The project doesn't have any input files. Make sure the `script` directory is configured properly in foundry.toml. Otherwise, provide the path to the file."))
} else {
ProjectCompiler::new().compile(&project)
}?;

// If we still don't have target path, find it by name in the compilation cache.
let target_path = if let Some(target_path) = target_path {
target_path
} else {
let target_name = target_name.clone().expect("was set above");
let cache = SolFilesCache::read_joined(&project.paths)
.wrap_err("Could not open compiler cache")?;
let (path, _) = get_cached_entry_by_name(&cache, &target_name)
.wrap_err("Could not find target contract in cache")?;
path
};

let target_path = project.root().join(target_path);
let output = compile::compile_target(&target_path, &project, args.opts.silent)?;

let mut target_id: Option<ArtifactId> = None;

Expand Down

0 comments on commit 0a4d246

Please sign in to comment.