diff --git a/Cargo.lock b/Cargo.lock index 7dc745cbf0de..81adf0f4b1e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -799,6 +799,16 @@ dependencies = [ "libc", ] +[[package]] +name = "annotate-snippets" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24e35ed54e5ea7997c14ed4c70ba043478db1112e98263b3b035907aa197d991" +dependencies = [ + "anstyle", + "unicode-width 0.1.14", +] + [[package]] name = "anstream" version = "0.6.18" @@ -3572,9 +3582,9 @@ dependencies = [ [[package]] name = "foundry-block-explorers" -version = "0.7.3" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff37530e7c5deead0f9d7dc2a27b070e683bef79735ab453849ebdee74fa848f" +checksum = "0faa449506113b4969029da2ac1df3a1b3201bf10c99a4a8e6d684977b80c938" dependencies = [ "alloy-chains", "alloy-json-abi", @@ -3755,9 +3765,9 @@ dependencies = [ [[package]] name = "foundry-compilers" -version = "0.11.6" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4754b3f3bb924202b29bd7f0584ea1446018926342884c86029a7d56ef1a22c1" +checksum = "9edf09554357ebfcd2ea28503badbaca311aac3c947d269a6bae5256543aa172" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -3780,7 +3790,7 @@ dependencies = [ "serde", "serde_json", "sha2", - "solang-parser", + "solar-parse", "svm-rs", "svm-rs-builds", "tempfile", @@ -3793,9 +3803,9 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts" -version = "0.11.6" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6289da0f17fdb5a0454020dce595502b0abd2a56c15a36d4f6c05bd6c4ff864" +checksum = "134b2499a20136716422f1ae5afd3a5d6c2ef833bf97b7c956285ca96c1d9743" dependencies = [ "foundry-compilers-artifacts-solc", "foundry-compilers-artifacts-vyper", @@ -3803,9 +3813,9 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-solc" -version = "0.11.6" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf322ab7b726f2bafe9a7e6fb67db02801b35584a2b1d122b4feb52d8e9e7f" +checksum = "78a9be34b3a43e77871e5bbd75f4a81d23eebf8f098519ae1ae9e163a0f3d0da" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -3827,9 +3837,9 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-vyper" -version = "0.11.6" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec47f94c7833adfe8049c819d9e31a60c3f440a68cf5baf34c318413d3eb0700" +checksum = "1738950051ebcee2135adac07b3ef1708c6377ffed0c5e9a2bb485f31498befb" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -3842,9 +3852,9 @@ dependencies = [ [[package]] name = "foundry-compilers-core" -version = "0.11.6" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61971b34545e8ea01502df9d076e811ad3926f27d31adf2641e0c931ca646933" +checksum = "2ac2d9982eedb0eb3819f82c7efa1e28c1f78e031cdfb6adfa34690364fe5e0d" dependencies = [ "alloy-primitives", "cfg-if", @@ -4700,6 +4710,10 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] [[package]] name = "hashbrown" @@ -5255,6 +5269,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +[[package]] +name = "index_vec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44faf5bb8861a9c72e20d3fb0fdbd59233e43056e2b80475ab0aacdc2e781355" + [[package]] name = "indexmap" version = "1.9.3" @@ -5576,6 +5596,16 @@ dependencies = [ "regex-automata 0.4.9", ] +[[package]] +name = "lasso" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e14eda50a3494b3bf7b9ce51c52434a761e383d7238ce1dd5dcec2fbc13e9fb" +dependencies = [ + "dashmap", + "hashbrown 0.14.5", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -5712,6 +5742,12 @@ dependencies = [ "tendril", ] +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + [[package]] name = "matchers" version = "0.1.0" @@ -5890,9 +5926,9 @@ dependencies = [ [[package]] name = "mockall" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c28b3fb6d753d28c20e826cd46ee611fda1cf3cde03a443a974043247c065a" +checksum = "39a6bfcc6c8c7eed5ee98b9c3e33adc726054389233e201c95dab2d41a3839d2" dependencies = [ "cfg-if", "downcast", @@ -5904,9 +5940,9 @@ dependencies = [ [[package]] name = "mockall_derive" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "341014e7f530314e9a1fdbc7400b244efea7122662c96bfa248c31da5bfb2020" +checksum = "25ca3004c2efe9011bd4e461bd8256445052b9615405b4f7ea43fc8ca5c20898" dependencies = [ "cfg-if", "proc-macro2", @@ -6383,28 +6419,29 @@ dependencies = [ [[package]] name = "parity-scale-codec" -version = "3.6.12" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" +checksum = "8be4817d39f3272f69c59fe05d0535ae6456c2dc2fa1ba02910296c7e0a5c590" dependencies = [ "arrayvec", "bitvec", "byte-slice-cast", "impl-trait-for-tuples", "parity-scale-codec-derive", + "rustversion", "serde", ] [[package]] name = "parity-scale-codec-derive" -version = "3.6.12" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" +checksum = "8781a75c6205af67215f382092b6e0a4ff3734798523e69073d4bcd294ec767b" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.87", ] [[package]] @@ -8046,9 +8083,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.132" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "indexmap 2.6.0", "itoa", @@ -8357,6 +8394,109 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "solar-ast" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5aeaf7a4bd326242c909bd287291226a540b62b36fa5824880248f4b1d4d6af" +dependencies = [ + "alloy-primitives", + "bumpalo", + "either", + "num-bigint", + "num-rational", + "semver 1.0.23", + "solar-data-structures", + "solar-interface", + "solar-macros", + "strum", + "typed-arena", +] + +[[package]] +name = "solar-config" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31d00d672a40a1a3620d7696f01a2d3301abf883d8168e1a9da3bf83f0c8e343" +dependencies = [ + "strum", +] + +[[package]] +name = "solar-data-structures" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6e4eb0b72ed7adbb808897c85de08ea99609774a58c72e3dce55c758043ca2" +dependencies = [ + "bumpalo", + "index_vec", + "indexmap 2.6.0", + "parking_lot", + "rayon", + "rustc-hash", + "smallvec", +] + +[[package]] +name = "solar-interface" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21fb8925638f3da1bba7a9a6ebeac3511e5c6354f921f2bb2e1ddce4ac70c107" +dependencies = [ + "annotate-snippets", + "anstream", + "anstyle", + "const-hex", + "derive_builder", + "dunce", + "itertools 0.13.0", + "itoa", + "lasso", + "match_cfg", + "normalize-path", + "rayon", + "scc", + "scoped-tls", + "solar-config", + "solar-data-structures", + "solar-macros", + "thiserror 1.0.69", + "tracing", + "unicode-width 0.2.0", +] + +[[package]] +name = "solar-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0cc54b74e214647c1bbfc098d080cc5deac77f8dcb99aca91747276b01a15ad" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "solar-parse" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b82c3659c15975cd80e5e1c44591278c230c59ad89082d797837499a4784e1b" +dependencies = [ + "alloy-primitives", + "bitflags 2.6.0", + "bumpalo", + "itertools 0.13.0", + "memchr", + "num-bigint", + "num-rational", + "num-traits", + "smallvec", + "solar-ast", + "solar-data-structures", + "solar-interface", + "tracing", +] + [[package]] name = "soldeer-commands" version = "0.5.1" @@ -9267,6 +9407,12 @@ dependencies = [ "utf-8", ] +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + [[package]] name = "typenum" version = "1.17.0" diff --git a/Cargo.toml b/Cargo.toml index 0c0962a2d95e..624264cf218e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -168,8 +168,8 @@ foundry-wallets = { path = "crates/wallets" } foundry-linking = { path = "crates/linking" } # solc & compilation utilities -foundry-block-explorers = { version = "0.7.3", default-features = false } -foundry-compilers = { version = "0.11.6", default-features = false } +foundry-block-explorers = { version = "0.9.0", default-features = false } +foundry-compilers = { version = "0.12.1", default-features = false } foundry-fork-db = "0.6.0" solang-parser = "=0.3.3" diff --git a/crates/cast/bin/cmd/storage.rs b/crates/cast/bin/cmd/storage.rs index 5e2459127001..13fa908bc94a 100644 --- a/crates/cast/bin/cmd/storage.rs +++ b/crates/cast/bin/cmd/storage.rs @@ -23,7 +23,7 @@ use foundry_compilers::{ artifacts::{ConfigurableContractArtifact, StorageLayout}, compilers::{ solc::{Solc, SolcCompiler}, - Compiler, CompilerSettings, + Compiler, }, Artifact, Project, }; @@ -316,7 +316,7 @@ fn print_storage(layout: StorageLayout, values: Vec, pretty: bool) fn add_storage_layout_output(project: &mut Project) { project.artifacts.additional_values.storage_layout = true; - project.settings.update_output_selection(|selection| { + project.update_output_selection(|selection| { selection.0.values_mut().for_each(|contract_selection| { contract_selection .values_mut() diff --git a/crates/cli/src/utils/cmd.rs b/crates/cli/src/utils/cmd.rs index 2ebdc2539dff..19e4425b54c7 100644 --- a/crates/cli/src/utils/cmd.rs +++ b/crates/cli/src/utils/cmd.rs @@ -3,10 +3,10 @@ use alloy_primitives::Address; use eyre::{Result, WrapErr}; use foundry_common::{fs, TestFunctionExt}; use foundry_compilers::{ - artifacts::{CompactBytecode, CompactDeployedBytecode, Settings}, + artifacts::{CompactBytecode, Settings}, cache::{CacheEntry, CompilerCache}, utils::read_json_file, - Artifact, ProjectCompileOutput, + Artifact, ArtifactId, ProjectCompileOutput, }; use foundry_config::{error::ExtractConfigError, figment::Figment, Chain, Config, NamedChain}; use foundry_debugger::Debugger; @@ -32,17 +32,21 @@ use yansi::Paint; /// Runtime Bytecode of the given contract. #[track_caller] pub fn remove_contract( - output: &mut ProjectCompileOutput, + output: ProjectCompileOutput, path: &Path, name: &str, -) -> Result<(JsonAbi, CompactBytecode, CompactDeployedBytecode)> { - let contract = if let Some(contract) = output.remove(path, name) { - contract - } else { +) -> Result<(JsonAbi, CompactBytecode, ArtifactId)> { + let mut other = Vec::new(); + let Some((id, contract)) = output.into_artifacts().find_map(|(id, artifact)| { + if id.name == name && id.source == path { + Some((id, artifact)) + } else { + other.push(id.name); + None + } + }) else { let mut err = format!("could not find artifact: `{name}`"); - if let Some(suggestion) = - super::did_you_mean(name, output.artifacts().map(|(name, _)| name)).pop() - { + if let Some(suggestion) = super::did_you_mean(name, other).pop() { if suggestion != name { err = format!( r#"{err} @@ -64,12 +68,7 @@ pub fn remove_contract( .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", name))? - .into_owned(); - - Ok((abi, bin, runtime)) + Ok((abi, bin, id)) } /// Helper function for finding a contract by ContractName diff --git a/crates/config/src/compilation.rs b/crates/config/src/compilation.rs new file mode 100644 index 000000000000..b4f00b91b0d9 --- /dev/null +++ b/crates/config/src/compilation.rs @@ -0,0 +1,115 @@ +use crate::{filter::GlobMatcher, serde_helpers}; +use foundry_compilers::{ + artifacts::{BytecodeHash, EvmVersion}, + multi::{MultiCompilerRestrictions, MultiCompilerSettings}, + settings::VyperRestrictions, + solc::{Restriction, SolcRestrictions}, + RestrictionsWithVersion, +}; +use semver::VersionReq; +use serde::{Deserialize, Serialize}; + +/// Keeps possible overrides for default settings which users may configure to construct additional +/// settings profile. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct SettingsOverrides { + pub name: String, + pub via_ir: Option, + #[serde(default, with = "serde_helpers::display_from_str_opt")] + pub evm_version: Option, + pub optimizer: Option, + pub optimizer_runs: Option, + pub bytecode_hash: Option, +} + +impl SettingsOverrides { + /// Applies the overrides to the given settings. + pub fn apply(&self, settings: &mut MultiCompilerSettings) { + if let Some(via_ir) = self.via_ir { + settings.solc.via_ir = Some(via_ir); + } + + if let Some(evm_version) = self.evm_version { + settings.solc.evm_version = Some(evm_version); + settings.vyper.evm_version = Some(evm_version); + } + + if let Some(enabled) = self.optimizer { + settings.solc.optimizer.enabled = Some(enabled); + } + + if let Some(optimizer_runs) = self.optimizer_runs { + settings.solc.optimizer.runs = Some(optimizer_runs); + } + + if let Some(bytecode_hash) = self.bytecode_hash { + if let Some(metadata) = settings.solc.metadata.as_mut() { + metadata.bytecode_hash = Some(bytecode_hash); + } else { + settings.solc.metadata = Some(bytecode_hash.into()); + } + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum RestrictionsError { + #[error("specified both exact and relative restrictions for {0}")] + BothExactAndRelative(&'static str), +} + +/// Restrictions for compilation of given paths. +/// +/// Only purpose of this type is to accept user input to later construct +/// `RestrictionsWithVersion`. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct CompilationRestrictions { + pub paths: GlobMatcher, + pub version: Option, + pub via_ir: Option, + pub bytecode_hash: Option, + + pub min_optimizer_runs: Option, + pub optimizer_runs: Option, + pub max_optimizer_runs: Option, + + #[serde(default, with = "serde_helpers::display_from_str_opt")] + pub min_evm_version: Option, + #[serde(default, with = "serde_helpers::display_from_str_opt")] + pub evm_version: Option, + #[serde(default, with = "serde_helpers::display_from_str_opt")] + pub max_evm_version: Option, +} + +impl TryFrom for RestrictionsWithVersion { + type Error = RestrictionsError; + + fn try_from(value: CompilationRestrictions) -> Result { + let (min_evm, max_evm) = + match (value.min_evm_version, value.max_evm_version, value.evm_version) { + (None, None, Some(exact)) => (Some(exact), Some(exact)), + (min, max, None) => (min, max), + _ => return Err(RestrictionsError::BothExactAndRelative("evm_version")), + }; + let (min_opt, max_opt) = + match (value.min_optimizer_runs, value.max_optimizer_runs, value.optimizer_runs) { + (None, None, Some(exact)) => (Some(exact), Some(exact)), + (min, max, None) => (min, max), + _ => return Err(RestrictionsError::BothExactAndRelative("optimizer_runs")), + }; + Ok(Self { + restrictions: MultiCompilerRestrictions { + solc: SolcRestrictions { + evm_version: Restriction { min: min_evm, max: max_evm }, + via_ir: value.via_ir, + optimizer_runs: Restriction { min: min_opt, max: max_opt }, + bytecode_hash: value.bytecode_hash, + }, + vyper: VyperRestrictions { + evm_version: Restriction { min: min_evm, max: max_evm }, + }, + }, + version: value.version, + }) + } +} diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index b87eadcefa0b..6444802e3bc9 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -33,8 +33,10 @@ use foundry_compilers::{ Compiler, }, error::SolcError, + multi::{MultiCompilerParsedSource, MultiCompilerRestrictions}, solc::{CliSettings, SolcSettings}, - ConfigurableArtifacts, Project, ProjectPathsConfig, VyperLanguage, + ConfigurableArtifacts, Graph, Project, ProjectPathsConfig, RestrictionsWithVersion, + VyperLanguage, }; use inflector::Inflector; use regex::Regex; @@ -43,6 +45,7 @@ use semver::Version; use serde::{Deserialize, Serialize, Serializer}; use std::{ borrow::Cow, + collections::BTreeMap, fs, path::{Path, PathBuf}, str::FromStr, @@ -116,6 +119,9 @@ use vyper::VyperConfig; mod bind_json; use bind_json::BindJsonConfig; +mod compilation; +use compilation::{CompilationRestrictions, SettingsOverrides}; + /// Foundry configuration /// /// # Defaults @@ -479,6 +485,14 @@ pub struct Config { #[serde(rename = "__warnings", default, skip_serializing)] pub warnings: Vec, + /// Additional settings profiles to use when compiling. + #[serde(default)] + pub additional_compiler_profiles: Vec, + + /// Restrictions on compilation of certain files. + #[serde(default)] + pub compilation_restrictions: Vec, + /// PRIVATE: This structure may grow, As such, constructing this structure should /// _always_ be done using a public constructor or update syntax: /// @@ -867,12 +881,66 @@ impl Config { self.create_project(false, true) } + /// Builds mapping with additional settings profiles. + fn additional_settings( + &self, + base: &MultiCompilerSettings, + ) -> BTreeMap { + let mut map = BTreeMap::new(); + + for profile in &self.additional_compiler_profiles { + let mut settings = base.clone(); + profile.apply(&mut settings); + map.insert(profile.name.clone(), settings); + } + + map + } + + /// Resolves globs and builds a mapping from individual source files to their restrictions + fn restrictions( + &self, + paths: &ProjectPathsConfig, + ) -> Result>, SolcError> + { + let mut map = BTreeMap::new(); + + let graph = Graph::::resolve(paths)?; + let (sources, _) = graph.into_sources(); + + for res in &self.compilation_restrictions { + for source in sources.keys().filter(|path| { + if res.paths.is_match(path) { + true + } else if let Ok(path) = path.strip_prefix(&paths.root) { + res.paths.is_match(path) + } else { + false + } + }) { + let res: RestrictionsWithVersion<_> = + res.clone().try_into().map_err(SolcError::msg)?; + if !map.contains_key(source) { + map.insert(source.clone(), res); + } else { + map.get_mut(source.as_path()).unwrap().merge(res); + } + } + } + + Ok(map) + } + /// Creates a [Project] with the given `cached` and `no_artifacts` flags pub fn create_project(&self, cached: bool, no_artifacts: bool) -> Result { + let settings = self.compiler_settings()?; + let paths = self.project_paths(); let mut builder = Project::builder() .artifacts(self.configured_artifacts_handler()) - .paths(self.project_paths()) - .settings(self.compiler_settings()?) + .additional_settings(self.additional_settings(&settings)) + .restrictions(self.restrictions(&paths)?) + .settings(settings) + .paths(paths) .ignore_error_codes(self.ignored_error_codes.iter().copied().map(Into::into)) .ignore_paths(self.ignored_file_paths.clone()) .set_compiler_severity_filter(if self.deny_warnings { @@ -2243,6 +2311,8 @@ impl Default for Config { eof_version: None, alphanet: false, transaction_timeout: 120, + additional_compiler_profiles: Default::default(), + compilation_restrictions: Default::default(), eof: false, _non_exhaustive: (), } diff --git a/crates/forge/bin/cmd/bind_json.rs b/crates/forge/bin/cmd/bind_json.rs index c6a052836baf..de7a5a7a71aa 100644 --- a/crates/forge/bin/cmd/bind_json.rs +++ b/crates/forge/bin/cmd/bind_json.rs @@ -11,7 +11,7 @@ use foundry_compilers::{ multi::{MultiCompilerLanguage, MultiCompilerParsedSource}, project::ProjectCompiler, solc::SolcLanguage, - CompilerSettings, Graph, Project, + Graph, Project, }; use foundry_config::Config; use itertools::Itertools; @@ -72,7 +72,7 @@ impl BindJsonArgs { // We only generate bindings for a single Solidity version to avoid conflicts. let mut sources = graph // resolve graph into mapping language -> version -> sources - .into_sources_by_version(project.offline, &project.locked_versions, &project.compiler)? + .into_sources_by_version(&project)? .0 .into_iter() // we are only interested in Solidity sources @@ -81,7 +81,7 @@ impl BindJsonArgs { .1 .into_iter() // For now, we are always picking the latest version. - .max_by(|(v1, _), (v2, _)| v1.cmp(v2)) + .max_by(|(v1, _, _), (v2, _, _)| v1.cmp(v2)) .unwrap() .1; @@ -229,7 +229,7 @@ impl PreprocessedState { fn compile(self) -> Result { let Self { sources, target_path, mut project, config } = self; - project.settings.update_output_selection(|selection| { + project.update_output_selection(|selection| { *selection = OutputSelection::ast_output_selection(); }); diff --git a/crates/forge/bin/cmd/compiler.rs b/crates/forge/bin/cmd/compiler.rs index a7115c4876b8..f5d62b671419 100644 --- a/crates/forge/bin/cmd/compiler.rs +++ b/crates/forge/bin/cmd/compiler.rs @@ -63,18 +63,14 @@ impl ResolveArgs { let project = config.project()?; let graph = Graph::resolve(&project.paths)?; - let (sources, _) = graph.into_sources_by_version( - project.offline, - &project.locked_versions, - &project.compiler, - )?; + let (sources, _) = graph.into_sources_by_version(&project)?; let mut output: BTreeMap> = BTreeMap::new(); for (language, sources) in sources { let mut versions_with_paths: Vec = sources .iter() - .map(|(version, sources)| { + .map(|(version, sources, _)| { let paths: Vec = sources .iter() .filter_map(|(path_file, _)| { diff --git a/crates/forge/bin/cmd/create.rs b/crates/forge/bin/cmd/create.rs index 83494acb99de..e19feafcc628 100644 --- a/crates/forge/bin/cmd/create.rs +++ b/crates/forge/bin/cmd/create.rs @@ -20,7 +20,9 @@ use foundry_common::{ fmt::parse_tokens, shell, }; -use foundry_compilers::{artifacts::BytecodeObject, info::ContractInfo, utils::canonicalize}; +use foundry_compilers::{ + artifacts::BytecodeObject, info::ContractInfo, utils::canonicalize, ArtifactId, +}; use foundry_config::{ figment::{ self, @@ -106,9 +108,9 @@ impl CreateArgs { project.find_contract_path(&self.contract.name)? }; - let mut output = compile::compile_target(&target_path, &project, shell::is_json())?; + let output = compile::compile_target(&target_path, &project, shell::is_json())?; - let (abi, bin, _) = remove_contract(&mut output, &target_path, &self.contract.name)?; + let (abi, bin, id) = remove_contract(output, &target_path, &self.contract.name)?; let bin = match bin.object { BytecodeObject::Bytecode(_) => bin.object, @@ -148,8 +150,17 @@ impl CreateArgs { if self.unlocked { // Deploy with unlocked account let sender = self.eth.wallet.from.expect("required"); - self.deploy(abi, bin, params, provider, chain_id, sender, config.transaction_timeout) - .await + self.deploy( + abi, + bin, + params, + provider, + chain_id, + sender, + config.transaction_timeout, + id, + ) + .await } else { // Deploy with signer let signer = self.eth.wallet.signer().await?; @@ -157,8 +168,17 @@ impl CreateArgs { let provider = ProviderBuilder::<_, _, AnyNetwork>::default() .wallet(EthereumWallet::new(signer)) .on_provider(provider); - self.deploy(abi, bin, params, provider, chain_id, deployer, config.transaction_timeout) - .await + self.deploy( + abi, + bin, + params, + provider, + chain_id, + deployer, + config.transaction_timeout, + id, + ) + .await } } @@ -177,13 +197,14 @@ impl CreateArgs { &self, constructor_args: Option, chain: u64, + id: &ArtifactId, ) -> Result<()> { // NOTE: this does not represent the same `VerifyArgs` that would be sent after deployment, // since we don't know the address yet. let mut verify = forge_verify::VerifyArgs { address: Default::default(), contract: Some(self.contract.clone()), - compiler_version: None, + compiler_version: Some(id.version.to_string()), constructor_args, constructor_args_path: None, num_of_optimizations: None, @@ -204,6 +225,7 @@ impl CreateArgs { evm_version: self.opts.compiler.evm_version, show_standard_json_input: self.show_standard_json_input, guess_constructor_args: false, + compilation_profile: Some(id.profile.to_string()), }; // Check config for Etherscan API Keys to avoid preflight check failing if no @@ -229,6 +251,7 @@ impl CreateArgs { chain: u64, deployer_address: Address, timeout: u64, + id: ArtifactId, ) -> Result<()> { let bin = bin.into_bytes().unwrap_or_else(|| { panic!("no bytecode found in bin object for {}", self.contract.name) @@ -305,7 +328,7 @@ impl CreateArgs { constructor_args = Some(hex::encode(encoded_args)); } - self.verify_preflight_check(constructor_args.clone(), chain).await?; + self.verify_preflight_check(constructor_args.clone(), chain, &id).await?; } // Deploy the actual contract @@ -339,7 +362,7 @@ impl CreateArgs { let verify = forge_verify::VerifyArgs { address, contract: Some(self.contract), - compiler_version: None, + compiler_version: Some(id.version.to_string()), constructor_args, constructor_args_path: None, num_of_optimizations, @@ -357,6 +380,7 @@ impl CreateArgs { evm_version: self.opts.compiler.evm_version, show_standard_json_input: self.show_standard_json_input, guess_constructor_args: false, + compilation_profile: Some(id.profile.to_string()), }; sh_println!("Waiting for {} to detect contract deployment...", verify.verifier.verifier)?; verify.run().await diff --git a/crates/forge/bin/cmd/eip712.rs b/crates/forge/bin/cmd/eip712.rs index 5014d38d703b..eb1d8dc1d315 100644 --- a/crates/forge/bin/cmd/eip712.rs +++ b/crates/forge/bin/cmd/eip712.rs @@ -2,14 +2,10 @@ use clap::{Parser, ValueHint}; use eyre::{Ok, OptionExt, Result}; use foundry_cli::{opts::CoreBuildArgs, utils::LoadConfig}; use foundry_common::compile::ProjectCompiler; -use foundry_compilers::{ - artifacts::{ - output_selection::OutputSelection, - visitor::{Visitor, Walk}, - ContractDefinition, EnumDefinition, SourceUnit, StructDefinition, TypeDescriptions, - TypeName, - }, - CompilerSettings, +use foundry_compilers::artifacts::{ + output_selection::OutputSelection, + visitor::{Visitor, Walk}, + ContractDefinition, EnumDefinition, SourceUnit, StructDefinition, TypeDescriptions, TypeName, }; use std::{collections::BTreeMap, fmt::Write, path::PathBuf}; @@ -31,7 +27,7 @@ impl Eip712Args { let config = self.try_load_config_emit_warnings()?; let mut project = config.create_project(false, true)?; let target_path = dunce::canonicalize(self.target_path)?; - project.settings.update_output_selection(|selection| { + project.update_output_selection(|selection| { *selection = OutputSelection::ast_output_selection(); }); diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 613d570785b9..4e811bcbfbf9 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -23,7 +23,7 @@ use foundry_cli::{ use foundry_common::{compile::ProjectCompiler, evm::EvmArgs, fs, shell, TestFunctionExt}; use foundry_compilers::{ artifacts::output_selection::OutputSelection, - compilers::{multi::MultiCompilerLanguage, CompilerSettings, Language}, + compilers::{multi::MultiCompilerLanguage, Language}, utils::source_files_iter, ProjectCompileOutput, }; @@ -203,7 +203,7 @@ impl TestArgs { filter: &ProjectPathsAwareFilter, ) -> Result> { let mut project = config.create_project(true, true)?; - project.settings.update_output_selection(|selection| { + project.update_output_selection(|selection| { *selection = OutputSelection::common_output_selection(["abi".to_string()]); }); diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index 11bcd49e2c40..db87a85ba0d1 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -154,6 +154,8 @@ forgetest!(can_extract_config_values, |prj, cmd| { eof_version: None, alphanet: false, transaction_timeout: 120, + additional_compiler_profiles: Default::default(), + compilation_restrictions: Default::default(), eof: false, _non_exhaustive: (), }; diff --git a/crates/script/src/verify.rs b/crates/script/src/verify.rs index 55435c559242..220991703df7 100644 --- a/crates/script/src/verify.rs +++ b/crates/script/src/verify.rs @@ -155,6 +155,7 @@ impl VerifyBundle { evm_version: None, show_standard_json_input: false, guess_constructor_args: false, + compilation_profile: Some(artifact.profile.to_string()), }; return Some(verify) diff --git a/crates/verify/src/etherscan/flatten.rs b/crates/verify/src/etherscan/flatten.rs index ffe1496cad5d..a0b3defd7f90 100644 --- a/crates/verify/src/etherscan/flatten.rs +++ b/crates/verify/src/etherscan/flatten.rs @@ -90,7 +90,7 @@ impl EtherscanFlattenedSource { let out = SolcCompiler::Specific(solc).compile(&input)?; if out.errors.iter().any(|e| e.is_error()) { let mut o = AggregatedCompilerOutput::::default(); - o.extend(version, RawBuildInfo::new(&input, &out, false)?, out); + o.extend(version, RawBuildInfo::new(&input, &out, false)?, "default", out); let diags = o.diagnostics(&[], &[], Default::default()); eyre::bail!( diff --git a/crates/verify/src/etherscan/standard_json.rs b/crates/verify/src/etherscan/standard_json.rs index cab3010fa638..e2fb5d2a47c0 100644 --- a/crates/verify/src/etherscan/standard_json.rs +++ b/crates/verify/src/etherscan/standard_json.rs @@ -18,7 +18,8 @@ impl EtherscanSourceProvider for EtherscanStandardJsonSource { .wrap_err("Failed to get standard json input")? .normalize_evm_version(&context.compiler_version); - input.settings.libraries.libs = input + let mut settings = context.compiler_settings.solc.settings.clone(); + settings.libraries.libs = input .settings .libraries .libs @@ -28,8 +29,12 @@ impl EtherscanSourceProvider for EtherscanStandardJsonSource { }) .collect(); + settings.remappings = input.settings.remappings; + // remove all incompatible settings - input.settings.sanitize(&context.compiler_version, SolcLanguage::Solidity); + settings.sanitize(&context.compiler_version, SolcLanguage::Solidity); + + input.settings = settings; let source = serde_json::to_string(&input).wrap_err("Failed to parse standard json input")?; diff --git a/crates/verify/src/provider.rs b/crates/verify/src/provider.rs index bc01bd9e304d..ab6c5e9f642f 100644 --- a/crates/verify/src/provider.rs +++ b/crates/verify/src/provider.rs @@ -9,7 +9,8 @@ use eyre::{OptionExt, Result}; use foundry_common::compile::ProjectCompiler; use foundry_compilers::{ artifacts::{output_selection::OutputSelection, Metadata, Source}, - compilers::{multi::MultiCompilerParsedSource, solc::SolcCompiler, CompilerSettings}, + compilers::{multi::MultiCompilerParsedSource, solc::SolcCompiler}, + multi::MultiCompilerSettings, solc::Solc, Graph, Project, }; @@ -25,6 +26,7 @@ pub struct VerificationContext { pub target_path: PathBuf, pub target_name: String, pub compiler_version: Version, + pub compiler_settings: MultiCompilerSettings, } impl VerificationContext { @@ -33,6 +35,7 @@ impl VerificationContext { target_name: String, compiler_version: Version, config: Config, + compiler_settings: MultiCompilerSettings, ) -> Result { let mut project = config.project()?; project.no_artifacts = true; @@ -40,13 +43,13 @@ impl VerificationContext { let solc = Solc::find_or_install(&compiler_version)?; project.compiler.solc = Some(SolcCompiler::Specific(solc)); - Ok(Self { config, project, target_name, target_path, compiler_version }) + Ok(Self { config, project, target_name, target_path, compiler_version, compiler_settings }) } /// Compiles target contract requesting only ABI and returns it. pub fn get_target_abi(&self) -> Result { let mut project = self.project.clone(); - project.settings.update_output_selection(|selection| { + project.update_output_selection(|selection| { *selection = OutputSelection::common_output_selection(["abi".to_string()]) }); @@ -65,7 +68,7 @@ impl VerificationContext { /// Compiles target file requesting only metadata and returns it. pub fn get_target_metadata(&self) -> Result { let mut project = self.project.clone(); - project.settings.update_output_selection(|selection| { + project.update_output_selection(|selection| { *selection = OutputSelection::common_output_selection(["metadata".to_string()]); }); diff --git a/crates/verify/src/verify.rs b/crates/verify/src/verify.rs index 89cfd99aa676..9c32ee95f5bf 100644 --- a/crates/verify/src/verify.rs +++ b/crates/verify/src/verify.rs @@ -80,6 +80,10 @@ pub struct VerifyArgs { #[arg(long, value_name = "VERSION")] pub compiler_version: Option, + /// The compilation profile to use to build the smart contract. + #[arg(long, value_name = "PROFILE_NAME")] + pub compilation_profile: Option, + /// The number of optimization runs used to build the smart contract. #[arg(long, visible_alias = "optimizer-runs", value_name = "NUM")] pub num_of_optimizations: Option, @@ -258,6 +262,8 @@ impl VerifyArgs { project.find_contract_path(&contract.name)? }; + let cache = project.read_cache_file().ok(); + let version = if let Some(ref version) = self.compiler_version { version.trim_start_matches('v').parse()? } else if let Some(ref solc) = config.solc { @@ -265,10 +271,8 @@ impl VerifyArgs { SolcReq::Version(version) => version.to_owned(), SolcReq::Local(solc) => Solc::new(solc)?.version, } - } else if let Some(entry) = project - .read_cache_file() - .ok() - .and_then(|mut cache| cache.files.remove(&contract_path)) + } else if let Some(entry) = + cache.as_ref().and_then(|cache| cache.files.get(&contract_path).cloned()) { let unique_versions = entry .artifacts @@ -291,7 +295,48 @@ impl VerifyArgs { eyre::bail!("If cache is disabled, compiler version must be either provided with `--compiler-version` option or set in foundry.toml") }; - VerificationContext::new(contract_path, contract.name.clone(), version, config) + let settings = if let Some(profile) = &self.compilation_profile { + if profile == "default" { + &project.settings + } else if let Some(settings) = project.additional_settings.get(profile.as_str()) { + settings + } else { + eyre::bail!("Unknown compilation profile: {}", profile) + } + } else if let Some((cache, entry)) = cache + .as_ref() + .and_then(|cache| Some((cache, cache.files.get(&contract_path)?.clone()))) + { + let profiles = entry + .artifacts + .get(&contract.name) + .and_then(|artifacts| artifacts.get(&version)) + .map(|artifacts| artifacts.keys().collect::>()) + .unwrap_or_default(); + + if profiles.is_empty() { + eyre::bail!("No matching artifact found for {}", contract.name); + } else if profiles.len() > 1 { + eyre::bail!("Ambiguous compilation profiles found in cache: {}, please specify the profile through `--compilation-profile` flag", profiles.iter().join(", ")) + } + + let profile = profiles.into_iter().next().unwrap().to_owned(); + let settings = cache.profiles.get(&profile).expect("must be present"); + + settings + } else if project.additional_settings.is_empty() { + &project.settings + } else { + eyre::bail!("If cache is disabled, compilation profile must be provided with `--compiler-version` option or set in foundry.toml") + }; + + VerificationContext::new( + contract_path, + contract.name.clone(), + version, + config, + settings.clone(), + ) } else { if config.get_rpc_url().is_none() { eyre::bail!("You have to provide a contract name or a valid RPC URL") @@ -311,11 +356,19 @@ impl VerifyArgs { )) }; + let settings = project + .settings_profiles() + .find_map(|(name, settings)| { + (name == artifact_id.profile.as_str()).then_some(settings) + }) + .expect("must be present"); + VerificationContext::new( artifact_id.source.clone(), artifact_id.name.split('.').next().unwrap().to_owned(), artifact_id.version.clone(), config, + settings.clone(), ) } }