From 2afafd0e0a65b2f701acbb2320f21b2b73a32dfd Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Fri, 17 May 2024 03:00:43 +0300 Subject: [PATCH 1/8] fix: smarter verification --- Cargo.lock | 3 +- crates/cheatcodes/src/fs.rs | 4 +- crates/common/src/contracts.rs | 140 +++++++++-- crates/evm/traces/src/identifier/local.rs | 4 +- crates/forge/bin/cmd/build.rs | 1 + crates/forge/bin/cmd/create.rs | 8 +- crates/script/src/execute.rs | 17 +- crates/script/src/lib.rs | 4 +- crates/script/src/transaction.rs | 2 +- crates/script/src/verify.rs | 4 +- crates/verify/Cargo.toml | 3 +- crates/verify/src/etherscan/flatten.rs | 19 +- crates/verify/src/etherscan/mod.rs | 230 +++++-------------- crates/verify/src/etherscan/standard_json.rs | 33 +-- crates/verify/src/lib.rs | 97 +++++++- crates/verify/src/provider.rs | 95 +++++++- crates/verify/src/sourcify.rs | 73 ++---- 17 files changed, 435 insertions(+), 302 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 52a36a589dc4..aa965f0346da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3246,8 +3246,7 @@ dependencies = [ "foundry-evm", "foundry-test-utils", "futures", - "once_cell", - "regex", + "itertools 0.12.1", "reqwest", "revm-primitives", "semver 1.0.23", diff --git a/crates/cheatcodes/src/fs.rs b/crates/cheatcodes/src/fs.rs index 423ed2767fa0..33e29475c370 100644 --- a/crates/cheatcodes/src/fs.rs +++ b/crates/cheatcodes/src/fs.rs @@ -356,9 +356,9 @@ fn get_artifact_code(state: &Cheatcodes, path: &str, deployed: bool) -> Result, + pub bytecode: Option, /// Contract runtime code. - pub deployed_bytecode: Option, + pub deployed_bytecode: Option, +} + +impl ContractData { + /// Returns reference to bytes of contract creation code, if present. + pub fn bytecode(&self) -> Option<&Bytes> { + self.bytecode.as_ref()?.bytes().filter(|b| !b.is_empty()) + } + + /// Returns reference to bytes of contract deployed code, if present. + pub fn deployed_bytecode(&self) -> Option<&Bytes> { + self.deployed_bytecode.as_ref()?.bytes().filter(|b| !b.is_empty()) + } } type ArtifactWithContractRef<'a> = (&'a ArtifactId, &'a ContractData); @@ -42,18 +61,10 @@ impl ContractsByArtifact { .into_iter() .filter_map(|(id, artifact)| { let name = id.name.clone(); - let bytecode = artifact.bytecode.and_then(|b| b.into_bytes())?; - let deployed_bytecode = - artifact.deployed_bytecode.and_then(|b| b.into_bytes())?; - - // Exclude artifacts with present but empty bytecode. Such artifacts are usually - // interfaces and abstract contracts. - let bytecode = (bytecode.len() > 0).then_some(bytecode); - let deployed_bytecode = - (deployed_bytecode.len() > 0).then_some(deployed_bytecode); - let abi = artifact.abi?; - - Some((id, ContractData { name, abi, bytecode, deployed_bytecode })) + + let CompactContractBytecode { abi, bytecode, deployed_bytecode } = artifact; + + Some((id, ContractData { name, abi: abi?, bytecode, deployed_bytecode })) }) .collect(), ) @@ -62,7 +73,7 @@ impl ContractsByArtifact { /// Finds a contract which has a similar bytecode as `code`. pub fn find_by_creation_code(&self, code: &[u8]) -> Option { self.iter().find(|(_, contract)| { - if let Some(bytecode) = &contract.bytecode { + if let Some(bytecode) = contract.bytecode() { bytecode_diff_score(bytecode.as_ref(), code) <= 0.1 } else { false @@ -73,7 +84,7 @@ impl ContractsByArtifact { /// Finds a contract which has a similar deployed bytecode as `code`. pub fn find_by_deployed_code(&self, code: &[u8]) -> Option { self.iter().find(|(_, contract)| { - if let Some(deployed_bytecode) = &contract.deployed_bytecode { + if let Some(deployed_bytecode) = contract.deployed_bytecode() { bytecode_diff_score(deployed_bytecode.as_ref(), code) <= 0.1 } else { false @@ -81,6 +92,99 @@ impl ContractsByArtifact { }) } + /// Finds a contract which deployed bytecode exactly matches the given code. Accounts for link + /// references and immutables. + pub fn find_by_deployed_code_exact(&self, code: &[u8]) -> Option { + self.iter().find(|(_, contract)| { + let Some(ref deployed_bytecode) = contract.deployed_bytecode else { + return false; + }; + let Some(ref deployed_code) = deployed_bytecode.bytecode else { + return false; + }; + + let len = match deployed_code.object { + BytecodeObject::Bytecode(ref bytes) => bytes.len(), + BytecodeObject::Unlinked(ref bytes) => bytes.len() / 2, + }; + + if len != code.len() { + return false; + } + + // Collect ignored offsets by chaining link and immutable references. + let mut ignored = deployed_bytecode + .immutable_references + .values() + .chain(deployed_code.link_references.values().flat_map(|v| v.values())) + .flatten() + .cloned() + .collect::>(); + + // For libraries solidity adds a call protection prefix to the bytecode. We need to + // ignore it as it includes library address determined at runtime. + // See https://docs.soliditylang.org/en/latest/contracts.html#call-protection-for-libraries and + // https://github.com/NomicFoundation/hardhat/blob/af7807cf38842a4f56e7f4b966b806e39631568a/packages/hardhat-verify/src/internal/solc/bytecode.ts#L172 + let has_call_protection = match deployed_code.object { + BytecodeObject::Bytecode(ref bytes) => { + bytes.starts_with(&CALL_PROTECTION_BYTECODE_PREFIX) + } + BytecodeObject::Unlinked(ref bytes) => { + if let Ok(bytes) = + Bytes::from_str(&bytes[..CALL_PROTECTION_BYTECODE_PREFIX.len() * 2]) + { + bytes.starts_with(&CALL_PROTECTION_BYTECODE_PREFIX) + } else { + false + } + } + }; + + if has_call_protection { + ignored.push(Offsets { start: 1, length: 20 }); + } + + ignored.sort_by_key(|o| o.start); + + let mut left = 0; + for offset in ignored { + let right = offset.start as usize; + + let matched = match deployed_code.object { + BytecodeObject::Bytecode(ref bytes) => bytes[left..right] == code[left..right], + BytecodeObject::Unlinked(ref bytes) => { + if let Ok(bytes) = Bytes::from_str(&bytes[left * 2..right * 2]) { + bytes == code[left..right] + } else { + false + } + } + }; + + if !matched { + return false; + } + + left = right + offset.length as usize; + } + + if left < code.len() { + match deployed_code.object { + BytecodeObject::Bytecode(ref bytes) => bytes[left..] == code[left..], + BytecodeObject::Unlinked(ref bytes) => { + if let Ok(bytes) = Bytes::from_str(&bytes[left * 2..]) { + bytes == code[left..] + } else { + false + } + } + } + } else { + true + } + }) + } + /// Finds a contract which has the same contract name or identifier as `id`. If more than one is /// found, return error. pub fn find_by_name_or_identifier(&self, id: &str) -> Result> { diff --git a/crates/evm/traces/src/identifier/local.rs b/crates/evm/traces/src/identifier/local.rs index 129656b95921..e82d733789f8 100644 --- a/crates/evm/traces/src/identifier/local.rs +++ b/crates/evm/traces/src/identifier/local.rs @@ -19,7 +19,7 @@ impl<'a> LocalTraceIdentifier<'a> { pub fn new(known_contracts: &'a ContractsByArtifact) -> Self { let mut ordered_ids = known_contracts .iter() - .filter_map(|(id, contract)| Some((id, contract.deployed_bytecode.as_ref()?))) + .filter_map(|(id, contract)| Some((id, contract.deployed_bytecode()?))) .map(|(id, bytecode)| (id, bytecode.len())) .collect::>(); ordered_ids.sort_by_key(|(_, len)| *len); @@ -41,7 +41,7 @@ impl<'a> LocalTraceIdentifier<'a> { let mut check = |id| { let contract = self.known_contracts.get(id)?; - if let Some(deployed_bytecode) = &contract.deployed_bytecode { + if let Some(deployed_bytecode) = contract.deployed_bytecode() { let score = bytecode_diff_score(deployed_bytecode, code); if score == 0.0 { trace!(target: "evm::traces", "found exact match"); diff --git a/crates/forge/bin/cmd/build.rs b/crates/forge/bin/cmd/build.rs index 673198fdc80f..c42934a99997 100644 --- a/crates/forge/bin/cmd/build.rs +++ b/crates/forge/bin/cmd/build.rs @@ -77,6 +77,7 @@ pub struct BuildArgs { impl BuildArgs { pub fn run(self) -> Result { let mut config = self.try_load_config_emit_warnings()?; + println!("{:?}", config.libraries); let mut project = config.project()?; if install::install_missing_dependencies(&mut config, self.args.silent) && diff --git a/crates/forge/bin/cmd/create.rs b/crates/forge/bin/cmd/create.rs index 460dbdea70ed..71a1cffd7157 100644 --- a/crates/forge/bin/cmd/create.rs +++ b/crates/forge/bin/cmd/create.rs @@ -169,7 +169,7 @@ impl CreateArgs { // since we don't know the address yet. let mut verify = forge_verify::VerifyArgs { address: Default::default(), - contract: self.contract.clone(), + contract: Some(self.contract.clone()), compiler_version: None, constructor_args, constructor_args_path: None, @@ -199,7 +199,9 @@ impl CreateArgs { verify.etherscan.key = config.get_etherscan_config_with_chain(Some(chain.into()))?.map(|c| c.key); - verify.verification_provider()?.preflight_check(verify).await?; + let context = verify.resolve_context().await?; + + verify.verification_provider()?.preflight_check(verify, context).await?; Ok(()) } @@ -318,7 +320,7 @@ impl CreateArgs { if self.opts.compiler.optimize { self.opts.compiler.optimizer_runs } else { None }; let verify = forge_verify::VerifyArgs { address, - contract: self.contract, + contract: Some(self.contract), compiler_version: None, constructor_args, constructor_args_path: None, diff --git a/crates/script/src/execute.rs b/crates/script/src/execute.rs index a0c8397ca268..afba19f22f06 100644 --- a/crates/script/src/execute.rs +++ b/crates/script/src/execute.rs @@ -16,7 +16,7 @@ use foundry_cli::utils::{ensure_clean_constructor, needs_setup}; use foundry_common::{ fmt::{format_token, format_token_raw}, provider::get_http_provider, - shell, ContractData, ContractsByArtifact, + shell, ContractsByArtifact, }; use foundry_config::{Config, NamedChain}; use foundry_debugger::Debugger; @@ -61,20 +61,25 @@ impl LinkedState { pub async fn prepare_execution(self) -> Result { let Self { args, script_config, script_wallets, build_data } = self; - let ContractData { abi, bytecode, .. } = build_data.get_target_contract()?; + let target_contract = build_data.get_target_contract()?; - let bytecode = bytecode.ok_or_eyre("target contract has no bytecode")?; + let bytecode = target_contract.bytecode().ok_or_eyre("target contract has no bytecode")?; - let (func, calldata) = args.get_method_and_calldata(&abi)?; + let (func, calldata) = args.get_method_and_calldata(&target_contract.abi)?; - ensure_clean_constructor(&abi)?; + ensure_clean_constructor(&target_contract.abi)?; Ok(PreExecutionState { args, script_config, script_wallets, build_data, - execution_data: ExecutionData { func, calldata, bytecode, abi }, + execution_data: ExecutionData { + func, + calldata, + bytecode: bytecode.clone(), + abi: target_contract.abi, + }, }) } } diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index 7b84802b1183..88ab0782a851 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -361,8 +361,8 @@ impl ScriptArgs { // From artifacts for (artifact, contract) in known_contracts.iter() { - let Some(bytecode) = &contract.bytecode else { continue }; - let Some(deployed_bytecode) = &contract.deployed_bytecode else { continue }; + let Some(bytecode) = contract.bytecode() else { continue }; + let Some(deployed_bytecode) = contract.deployed_bytecode() else { continue }; bytecodes.push((artifact.name.clone(), bytecode, deployed_bytecode)); } diff --git a/crates/script/src/transaction.rs b/crates/script/src/transaction.rs index c46301f9f398..f5f9399d46a9 100644 --- a/crates/script/src/transaction.rs +++ b/crates/script/src/transaction.rs @@ -131,7 +131,7 @@ impl TransactionWithMetadata { let Some(data) = self.transaction.input.input() else { return Ok(()) }; let Some(info) = info else { return Ok(()) }; - let Some(bytecode) = info.bytecode.as_ref() else { return Ok(()) }; + let Some(bytecode) = info.bytecode() else { return Ok(()) }; // `create2` transactions are prefixed by a 32 byte salt. let creation_code = if is_create2 { diff --git a/crates/script/src/verify.rs b/crates/script/src/verify.rs index f6545c30194c..6437e9a5d929 100644 --- a/crates/script/src/verify.rs +++ b/crates/script/src/verify.rs @@ -106,7 +106,7 @@ impl VerifyBundle { libraries: &[String], ) -> Option { for (artifact, contract) in self.known_contracts.iter() { - let Some(bytecode) = contract.bytecode.as_ref() else { continue }; + let Some(bytecode) = contract.bytecode() else { continue }; // If it's a CREATE2, the tx.data comes with a 32-byte salt in the beginning // of the transaction if data.split_at(create2_offset).1.starts_with(bytecode) { @@ -130,7 +130,7 @@ impl VerifyBundle { let verify = VerifyArgs { address: contract_address, - contract, + contract: Some(contract), compiler_version: Some(version.to_string()), constructor_args: Some(hex::encode(constructor_args)), constructor_args_path: None, diff --git a/crates/verify/Cargo.toml b/crates/verify/Cargo.toml index 62979edbc8a7..5901f91540a6 100644 --- a/crates/verify/Cargo.toml +++ b/crates/verify/Cargo.toml @@ -33,9 +33,8 @@ reqwest = { workspace = true, features = ["json"] } async-trait = "0.1" futures = "0.3" semver = "1" -regex = { version = "1", default-features = false } -once_cell = "1" yansi.workspace = true +itertools.workspace = true [dev-dependencies] tokio = { workspace = true, features = ["macros"] } diff --git a/crates/verify/src/etherscan/flatten.rs b/crates/verify/src/etherscan/flatten.rs index ee0f95d83c09..56671ab5bbec 100644 --- a/crates/verify/src/etherscan/flatten.rs +++ b/crates/verify/src/etherscan/flatten.rs @@ -1,10 +1,12 @@ +use crate::provider::VerificationContext; + use super::{EtherscanSourceProvider, VerifyArgs}; use eyre::{Context, Result}; use foundry_block_explorers::verify::CodeFormat; use foundry_compilers::{ artifacts::{BytecodeHash, Source}, compilers::{solc::SolcVersionManager, Compiler, CompilerVersionManager}, - AggregatedCompilerOutput, Project, SolcInput, + AggregatedCompilerOutput, SolcInput, }; use semver::{BuildMetadata, Version}; use std::{collections::BTreeMap, path::Path}; @@ -15,11 +17,9 @@ impl EtherscanSourceProvider for EtherscanFlattenedSource { fn source( &self, args: &VerifyArgs, - project: &Project, - target: &Path, - version: &Version, + context: &VerificationContext, ) -> Result<(String, String, CodeFormat)> { - let metadata = project.settings.metadata.as_ref(); + let metadata = context.project.settings.metadata.as_ref(); let bch = metadata.and_then(|m| m.bytecode_hash).unwrap_or_default(); eyre::ensure!( @@ -28,11 +28,13 @@ impl EtherscanSourceProvider for EtherscanFlattenedSource { bch, ); - let source = project.flatten(target).wrap_err("Failed to flatten contract")?; + let source = + context.project.flatten(&context.target_path).wrap_err("Failed to flatten contract")?; if !args.force { // solc dry run of flattened code - self.check_flattened(source.clone(), version, target).map_err(|err| { + self.check_flattened(source.clone(), &context.compiler_version, &context.target_path) + .map_err(|err| { eyre::eyre!( "Failed to compile the flattened code locally: `{}`\ To skip this solc dry, have a look at the `--force` flag of this command.", @@ -41,8 +43,7 @@ impl EtherscanSourceProvider for EtherscanFlattenedSource { })?; } - let name = args.contract.name.clone(); - Ok((source, name, CodeFormat::SingleFile)) + Ok((source, context.target_name.clone(), CodeFormat::SingleFile)) } } diff --git a/crates/verify/src/etherscan/mod.rs b/crates/verify/src/etherscan/mod.rs index 8ebe0dc46a24..2929099c06e8 100644 --- a/crates/verify/src/etherscan/mod.rs +++ b/crates/verify/src/etherscan/mod.rs @@ -1,5 +1,5 @@ use super::{provider::VerificationProvider, VerifyArgs, VerifyCheckArgs}; -use crate::retry::RETRY_CHECK_ON_VERIFY; +use crate::{provider::VerificationContext, retry::RETRY_CHECK_ON_VERIFY}; use alloy_json_abi::Function; use alloy_provider::Provider; use eyre::{eyre, Context, OptionExt, Result}; @@ -9,38 +9,21 @@ use foundry_block_explorers::{ verify::{CodeFormat, VerifyContract}, Client, }; -use foundry_cli::utils::{self, get_cached_entry_by_name, read_constructor_args_file, LoadConfig}; -use foundry_common::{abi::encode_function_args, retry::Retry}; -use foundry_compilers::{ - artifacts::{BytecodeObject, CompactContract}, - cache::CacheEntry, - info::ContractInfo, - Artifact, Project, Solc, -}; -use foundry_config::{Chain, Config, SolcReq}; +use foundry_cli::utils::{self, read_constructor_args_file, LoadConfig}; +use foundry_common::{abi::encode_function_args, retry::Retry, shell}; +use foundry_compilers::{artifacts::BytecodeObject, Artifact}; +use foundry_config::{Chain, Config}; use foundry_evm::constants::DEFAULT_CREATE2_DEPLOYER; use futures::FutureExt; -use once_cell::sync::Lazy; -use regex::Regex; use semver::{BuildMetadata, Version}; -use std::{ - collections::HashSet, - fmt::Debug, - path::{Path, PathBuf}, -}; +use std::fmt::Debug; mod flatten; mod standard_json; -pub static RE_BUILD_COMMIT: Lazy = - Lazy::new(|| Regex::new(r"(?Pcommit\.[0-9,a-f]{8})").unwrap()); - #[derive(Clone, Debug, Default)] #[non_exhaustive] -pub struct EtherscanVerificationProvider { - /// Memoized cached entry of the target contract - cached_entry: Option<(PathBuf, CacheEntry, CompactContract)>, -} +pub struct EtherscanVerificationProvider; /// The contract source provider for [EtherscanVerificationProvider] /// @@ -49,21 +32,23 @@ trait EtherscanSourceProvider: Send + Sync + Debug { fn source( &self, args: &VerifyArgs, - project: &Project, - target: &Path, - version: &Version, + context: &VerificationContext, ) -> Result<(String, String, CodeFormat)>; } #[async_trait::async_trait] impl VerificationProvider for EtherscanVerificationProvider { - async fn preflight_check(&mut self, args: VerifyArgs) -> Result<()> { - let _ = self.prepare_request(&args).await?; + async fn preflight_check( + &mut self, + args: VerifyArgs, + context: VerificationContext, + ) -> Result<()> { + let _ = self.prepare_request(&args, &context).await?; Ok(()) } - async fn verify(&mut self, args: VerifyArgs) -> Result<()> { - let (etherscan, verify_args) = self.prepare_request(&args).await?; + async fn verify(&mut self, args: VerifyArgs, context: VerificationContext) -> Result<()> { + let (etherscan, verify_args) = self.prepare_request(&args, &context).await?; if !args.skip_is_verified_check && self.is_contract_verified(ðerscan, &verify_args).await? @@ -214,38 +199,12 @@ impl EtherscanVerificationProvider { } } - /// Return the memoized cache entry for the target contract. - /// Read the artifact from cache on first access. - fn cache_entry( - &mut self, - project: &Project, - contract: &ContractInfo, - ) -> Result<&(PathBuf, CacheEntry, CompactContract)> { - if let Some(ref entry) = self.cached_entry { - return Ok(entry) - } - - let cache = project.read_cache_file()?; - let (path, entry) = if let Some(path) = contract.path.as_ref() { - let path = project.root().join(path); - ( - path.clone(), - cache - .entry(&path) - .ok_or_else(|| { - eyre::eyre!(format!("Cache entry not found for {}", path.display())) - })? - .to_owned(), - ) - } else { - get_cached_entry_by_name(&cache, &contract.name)? - }; - let contract: CompactContract = cache.read_artifact(path.clone(), &contract.name)?; - Ok(self.cached_entry.insert((path, entry, contract))) - } - /// Configures the API request to the etherscan API using the given [`VerifyArgs`]. - async fn prepare_request(&mut self, args: &VerifyArgs) -> Result<(Client, VerifyContract)> { + async fn prepare_request( + &mut self, + args: &VerifyArgs, + context: &VerificationContext, + ) -> Result<(Client, VerifyContract)> { let config = args.try_load_config_emit_warnings()?; let etherscan = self.client( args.etherscan.chain.unwrap_or_default(), @@ -253,7 +212,7 @@ impl EtherscanVerificationProvider { args.etherscan.key().as_deref(), &config, )?; - let verify_args = self.create_verify_request(args, Some(config)).await?; + let verify_args = self.create_verify_request(args, context).await?; Ok((etherscan, verify_args)) } @@ -325,22 +284,14 @@ impl EtherscanVerificationProvider { pub async fn create_verify_request( &mut self, args: &VerifyArgs, - config: Option, + context: &VerificationContext, ) -> Result { - let mut config = - if let Some(config) = config { config } else { args.try_load_config_emit_warnings()? }; - - config.libraries.extend(args.libraries.clone()); - - let project = config.project()?; - - let contract_path = self.contract_path(args, &project)?; - let compiler_version = self.compiler_version(args, &config, &project)?; let (source, contract_name, code_format) = - self.source_provider(args).source(args, &project, &contract_path, &compiler_version)?; + self.source_provider(args).source(args, context)?; - let compiler_version = format!("v{}", ensure_solc_build_metadata(compiler_version).await?); - let constructor_args = self.constructor_args(args, &project, &config).await?; + let compiler_version = + format!("v{}", ensure_solc_build_metadata(context.compiler_version.clone()).await?); + let constructor_args = self.constructor_args(args, context).await?; let mut verify_args = VerifyContract::new(args.address, contract_name, source, compiler_version) .constructor_arguments(constructor_args) @@ -357,8 +308,8 @@ impl EtherscanVerificationProvider { if code_format == CodeFormat::SingleFile { verify_args = if let Some(optimizations) = args.num_of_optimizations { verify_args.optimized().runs(optimizations as u32) - } else if config.optimizer { - verify_args.optimized().runs(config.optimizer_runs.try_into()?) + } else if context.config.optimizer { + verify_args.optimized().runs(context.config.optimizer_runs.try_into()?) } else { verify_args.not_optimized() }; @@ -367,94 +318,16 @@ impl EtherscanVerificationProvider { Ok(verify_args) } - /// Get the target contract path. If it wasn't provided, attempt a lookup - /// in cache. Validate the path indeed exists on disk. - fn contract_path(&mut self, args: &VerifyArgs, project: &Project) -> Result { - let path = if let Some(path) = args.contract.path.as_ref() { - project.root().join(path) - } else { - let (path, _, _) = self.cache_entry(project, &args.contract).wrap_err( - "If cache is disabled, contract info must be provided in the format :", - )?; - path.to_owned() - }; - - // check that the provided contract is part of the source dir - if !path.exists() { - eyre::bail!("Contract {:?} does not exist.", path); - } - - Ok(path) - } - - /// Parse the compiler version. - /// The priority desc: - /// 1. Through CLI arg `--compiler-version` - /// 2. `solc` defined in foundry.toml - /// 3. The version contract was last compiled with. - fn compiler_version( - &mut self, - args: &VerifyArgs, - config: &Config, - project: &Project, - ) -> Result { - if let Some(ref version) = args.compiler_version { - return Ok(version.trim_start_matches('v').parse()?) - } - - if let Some(ref solc) = config.solc { - match solc { - SolcReq::Version(version) => return Ok(version.to_owned()), - SolcReq::Local(solc) => { - if solc.is_file() { - return Ok(Solc::new(solc).map(|solc| solc.version)?) - } - } - } - } - - let (_, entry, _) = self.cache_entry(project, &args.contract).wrap_err( - "If cache is disabled, compiler version must be either provided with `--compiler-version` option or set in foundry.toml" - )?; - let artifacts = entry.artifacts_versions().collect::>(); - - if artifacts.is_empty() { - eyre::bail!("No matching artifact found for {}", args.contract.name); - } - - // ensure we have a single version - let unique_versions = artifacts.iter().map(|a| a.0.to_string()).collect::>(); - if unique_versions.len() > 1 { - let versions = unique_versions.into_iter().collect::>(); - warn!("Ambiguous compiler versions found in cache: {}", versions.join(", ")); - eyre::bail!("Compiler version has to be set in `foundry.toml`. If the project was not deployed with foundry, specify the version through `--compiler-version` flag.") - } - - // we have a unique version - let mut version = artifacts[0].0.clone(); - version.build = match RE_BUILD_COMMIT.captures(version.build.as_str()) { - Some(cap) => BuildMetadata::new(cap.name("commit").unwrap().as_str())?, - _ => BuildMetadata::EMPTY, - }; - - Ok(version) - } - /// Return the optional encoded constructor arguments. If the path to /// constructor arguments was provided, read them and encode. Otherwise, /// return whatever was set in the [VerifyArgs] args. async fn constructor_args( &mut self, args: &VerifyArgs, - project: &Project, - config: &Config, + context: &VerificationContext, ) -> Result> { if let Some(ref constructor_args_path) = args.constructor_args_path { - let (_, _, contract) = self.cache_entry(project, &args.contract).wrap_err( - "Cache must be enabled in order to use the `--constructor-args-path` option", - )?; - let abi = - contract.abi.as_ref().ok_or_else(|| eyre!("Can't find ABI in cached artifact."))?; + let abi = context.get_target_abi()?; let constructor = abi .constructor() .ok_or_else(|| eyre!("Can't retrieve constructor info from artifact ABI."))?; @@ -473,7 +346,7 @@ impl EtherscanVerificationProvider { return Ok(Some(encoded_args[8..].into())) } if args.guess_constructor_args { - return Ok(Some(self.guess_constructor_args(args, project, config).await?)) + return Ok(Some(self.guess_constructor_args(args, context).await?)) } Ok(args.constructor_args.clone()) @@ -486,15 +359,14 @@ impl EtherscanVerificationProvider { async fn guess_constructor_args( &mut self, args: &VerifyArgs, - project: &Project, - config: &Config, + context: &VerificationContext, ) -> Result { - let provider = utils::get_provider(config)?; + let provider = utils::get_provider(&context.config)?; let client = self.client( args.etherscan.chain.unwrap_or_default(), args.verifier.verifier_url.as_deref(), args.etherscan.key.as_deref(), - config, + &context.config, )?; let creation_data = client.contract_creation_data(args.address).await?; @@ -507,20 +379,17 @@ impl EtherscanVerificationProvider { .await? .ok_or_eyre("Couldn't fetch transaction receipt from RPC")?; - let maybe_creation_code: &[u8]; - - if receipt.contract_address == Some(args.address) { - maybe_creation_code = &transaction.input; + let maybe_creation_code = if receipt.contract_address == Some(args.address) { + &transaction.input } else if transaction.to == Some(DEFAULT_CREATE2_DEPLOYER) { - maybe_creation_code = &transaction.input[32..]; + &transaction.input[32..] } else { eyre::bail!("Fetching of constructor arguments is not supported for contracts created by contracts") - } + }; - let contract_path = self.contract_path(args, project)?.to_string_lossy().into_owned(); - let output = project.compile()?; + let output = context.project.compile_file(&context.target_path)?; let artifact = output - .find(contract_path, &args.contract.name) + .find(context.target_path.to_string_lossy(), &context.target_name) .ok_or_eyre("Contract artifact wasn't found locally")?; let bytecode = artifact .get_bytecode_object() @@ -535,7 +404,9 @@ impl EtherscanVerificationProvider { if maybe_creation_code.starts_with(bytecode) { let constructor_args = &maybe_creation_code[bytecode.len()..]; - Ok(hex::encode(constructor_args)) + let constructor_args = hex::encode(constructor_args); + shell::println(format!("Identified constructor arguments: {constructor_args}"))?; + Ok(constructor_args) } else { eyre::bail!("Local bytecode doesn't match on-chain bytecode") } @@ -666,8 +537,9 @@ mod tests { "--root", root_path, ]); + let context = args.resolve_context().await.unwrap(); - let result = etherscan.preflight_check(args).await; + let result = etherscan.preflight_check(args, context).await; assert!(result.is_err()); assert_eq!( result.unwrap_err().to_string(), @@ -678,7 +550,9 @@ mod tests { let args = VerifyArgs::parse_from(["foundry-cli", address, contract_name, "--root", root_path]); - let result = etherscan.preflight_check(args).await; + let context = args.resolve_context().await.unwrap(); + + let result = etherscan.preflight_check(args, context).await; assert!(result.is_err()); assert_eq!( result.unwrap_err().to_string(), @@ -697,8 +571,9 @@ mod tests { "--root", root_path, ]); + let context = args.resolve_context().await.unwrap(); - let result = etherscan.preflight_check(args).await; + let result = etherscan.preflight_check(args, context).await; assert!(result.is_err()); assert_eq!( result.unwrap_err().to_string(), @@ -719,8 +594,9 @@ mod tests { "--root", &prj.root().to_string_lossy(), ]); + let context = args.resolve_context().await.unwrap(); let mut etherscan = EtherscanVerificationProvider::default(); - etherscan.preflight_check(args).await.unwrap(); + etherscan.preflight_check(args, context).await.unwrap(); }); } diff --git a/crates/verify/src/etherscan/standard_json.rs b/crates/verify/src/etherscan/standard_json.rs index db4854355e9a..bd5eb6d0f56e 100644 --- a/crates/verify/src/etherscan/standard_json.rs +++ b/crates/verify/src/etherscan/standard_json.rs @@ -1,35 +1,36 @@ +use crate::provider::VerificationContext; + use super::{EtherscanSourceProvider, VerifyArgs}; use eyre::{Context, Result}; use foundry_block_explorers::verify::CodeFormat; -use foundry_compilers::{artifacts::StandardJsonCompilerInput, Project}; -use semver::Version; -use std::path::Path; +use foundry_compilers::artifacts::StandardJsonCompilerInput; #[derive(Debug)] pub struct EtherscanStandardJsonSource; impl EtherscanSourceProvider for EtherscanStandardJsonSource { fn source( &self, - args: &VerifyArgs, - project: &Project, - target: &Path, - version: &Version, + _args: &VerifyArgs, + context: &VerificationContext, ) -> Result<(String, String, CodeFormat)> { - let mut input: StandardJsonCompilerInput = project - .standard_json_input(target) + let mut input: StandardJsonCompilerInput = context + .project + .standard_json_input(&context.target_path) .wrap_err("Failed to get standard json input")? - .normalize_evm_version(version); + .normalize_evm_version(&context.compiler_version); input.settings.libraries.libs = input .settings .libraries .libs .into_iter() - .map(|(f, libs)| (f.strip_prefix(project.root()).unwrap_or(&f).to_path_buf(), libs)) + .map(|(f, libs)| { + (f.strip_prefix(context.project.root()).unwrap_or(&f).to_path_buf(), libs) + }) .collect(); // remove all incompatible settings - input.settings.sanitize(version); + input.settings.sanitize(&context.compiler_version); let source = serde_json::to_string(&input).wrap_err("Failed to parse standard json input")?; @@ -38,8 +39,12 @@ impl EtherscanSourceProvider for EtherscanStandardJsonSource { let name = format!( "{}:{}", - target.strip_prefix(project.root()).unwrap_or(target).display(), - args.contract.name.clone() + context + .target_path + .strip_prefix(context.project.root()) + .unwrap_or(context.target_path.as_path()) + .display(), + context.target_name.clone() ); Ok((source, name, CodeFormat::StandardJsonInput)) } diff --git a/crates/verify/src/lib.rs b/crates/verify/src/lib.rs index ee6dd13f63fc..f8e20d49199e 100644 --- a/crates/verify/src/lib.rs +++ b/crates/verify/src/lib.rs @@ -4,17 +4,20 @@ extern crate tracing; use alloy_primitives::Address; +use alloy_provider::Provider; use clap::{Parser, ValueHint}; use eyre::Result; use foundry_cli::{ opts::{EtherscanOpts, RpcOpts}, - utils, - utils::LoadConfig, + utils::{self, LoadConfig}, }; -use foundry_compilers::{info::ContractInfo, EvmVersion}; -use foundry_config::{figment, impl_figment_convert, impl_figment_convert_cast, Config}; +use foundry_common::{compile::ProjectCompiler, ContractsByArtifact}; +use foundry_compilers::{info::ContractInfo, EvmVersion, Solc}; +use foundry_config::{figment, impl_figment_convert, impl_figment_convert_cast, Config, SolcReq}; +use itertools::Itertools; use provider::VerificationProviderType; use reqwest::Url; +use revm_primitives::HashSet; use std::path::PathBuf; mod etherscan; @@ -29,6 +32,8 @@ mod sourcify; pub use retry::RetryArgs; +use crate::provider::VerificationContext; + /// Verification provider arguments #[derive(Clone, Debug, Parser)] pub struct VerifierArgs { @@ -54,7 +59,7 @@ pub struct VerifyArgs { pub address: Address, /// The contract identifier in the form `:`. - pub contract: ContractInfo, + pub contract: Option, /// The ABI-encoded constructor arguments. #[arg( @@ -192,19 +197,22 @@ impl VerifyArgs { None => config.chain.unwrap_or_default(), }; + let context = self.resolve_context().await?; + self.etherscan.chain = Some(chain); self.etherscan.key = config.get_etherscan_config_with_chain(Some(chain))?.map(|c| c.key); if self.show_standard_json_input { - let args = - EtherscanVerificationProvider::default().create_verify_request(&self, None).await?; + let args = EtherscanVerificationProvider::default() + .create_verify_request(&self, &context) + .await?; println!("{}", args.source); return Ok(()) } let verifier_url = self.verifier.verifier_url.clone(); println!("Start verifying contract `{}` deployed on {chain}", self.address); - self.verifier.verifier.client(&self.etherscan.key())?.verify(self).await.map_err(|err| { + self.verifier.verifier.client(&self.etherscan.key())?.verify(self, context).await.map_err(|err| { if let Some(verifier_url) = verifier_url { match Url::parse(&verifier_url) { Ok(url) => { @@ -230,6 +238,79 @@ impl VerifyArgs { pub fn verification_provider(&self) -> Result> { self.verifier.verifier.client(&self.etherscan.key()) } + + /// Resolves [VerificationContext] object either from entered contract name or by trying to + /// match bytecode located at given address. + pub async fn resolve_context(&self) -> Result { + let mut config = self.load_config_emit_warnings(); + config.libraries.extend(self.libraries.clone()); + + let project = config.project()?; + + if let Some(ref contract) = self.contract { + let contract_path = if let Some(ref path) = contract.path { + PathBuf::from(path) + } else { + project.find_contract_path(&contract.name)? + }; + + let version = if let Some(ref version) = self.compiler_version { + version.trim_start_matches('v').parse()? + } else if let Some(ref solc) = config.solc { + match solc { + SolcReq::Version(version) => version.to_owned(), + SolcReq::Local(solc) => Solc::new(solc)?.version, + } + } else if let Some(entry) = project.read_cache_file()?.entry(contract_path.clone()) { + let unique_versions = entry + .artifacts + .get(&contract.name) + .map(|artifacts| artifacts.keys().collect::>()) + .unwrap_or_default(); + + if unique_versions.is_empty() { + eyre::bail!("No matching artifact found for {}", contract.name); + } else if unique_versions.len() > 1 { + warn!( + "Ambiguous compiler versions found in cache: {}", + unique_versions.iter().join(", ") + ); + eyre::bail!("Compiler version has to be set in `foundry.toml`. If the project was not deployed with foundry, specify the version through `--compiler-version` flag.") + } + + unique_versions.into_iter().next().unwrap().to_owned() + } else { + 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) + } else { + if config.get_rpc_url().is_none() { + eyre::bail!("You have to provide a contract name or a valid RPC URL") + } + let provider = utils::get_provider(&config)?; + let code = provider.get_code_at(self.address, Default::default()).await?; + + let output = ProjectCompiler::new().compile(&project)?; + let contracts = ContractsByArtifact::new( + output.artifact_ids().map(|(id, artifact)| (id, artifact.clone().into())), + ); + + let Some((artifact_id, _)) = contracts.find_by_deployed_code_exact(&code) else { + eyre::bail!(format!( + "Bytecode at {} does not match any local contracts", + self.address + )) + }; + + VerificationContext::new( + artifact_id.source.clone(), + artifact_id.name.split('.').next().unwrap().to_owned(), + artifact_id.version.clone(), + config, + ) + } + } } /// Check verification status arguments diff --git a/crates/verify/src/provider.rs b/crates/verify/src/provider.rs index deac48229b02..210283c3ac84 100644 --- a/crates/verify/src/provider.rs +++ b/crates/verify/src/provider.rs @@ -2,9 +2,92 @@ use super::{ etherscan::EtherscanVerificationProvider, sourcify::SourcifyVerificationProvider, VerifyArgs, VerifyCheckArgs, }; +use alloy_json_abi::JsonAbi; use async_trait::async_trait; -use eyre::Result; -use std::{fmt, str::FromStr}; +use eyre::{OptionExt, Result}; +use foundry_common::compile::ProjectCompiler; +use foundry_compilers::{ + artifacts::{output_selection::OutputSelection, Metadata, Source}, + compilers::{solc::SolcVersionManager, CompilerVersionManager}, + CompilerConfig, Graph, Project, +}; +use foundry_config::Config; +use semver::Version; +use std::{fmt, path::PathBuf, str::FromStr}; + +/// Container with data required for contract verification. +#[derive(Debug, Clone)] +pub struct VerificationContext { + pub config: Config, + pub project: Project, + pub target_path: PathBuf, + pub target_name: String, + pub compiler_version: Version, +} + +impl VerificationContext { + pub fn new( + target_path: PathBuf, + target_name: String, + compiler_version: Version, + config: Config, + ) -> Result { + let mut project = config.project()?; + project.no_artifacts = true; + + // Set project's compiler to always use resolved version. + let vm = SolcVersionManager::default(); + let solc = vm.get_or_install(&compiler_version)?; + project.compiler_config = CompilerConfig::Specific(solc); + + Ok(Self { config, project, target_name, target_path, compiler_version }) + } + + /// Compiles target contract requesting only ABI and returns it. + pub fn get_target_abi(&self) -> Result { + let mut project = self.project.clone(); + project.settings.output_selection = + OutputSelection::common_output_selection(["abi".to_string()]); + + let output = ProjectCompiler::new() + .quiet(true) + .files([self.target_path.clone()]) + .compile(&project)?; + + let artifact = output + .find(self.target_path.to_string_lossy(), &self.target_name) + .ok_or_eyre("failed to find target artifact when compiling for abi")?; + + artifact.abi.clone().ok_or_eyre("target artifact does not have an ABI") + } + + /// Compiles target file requesting only metadata and returns it. + pub fn get_target_metadata(&self) -> Result { + let mut project = self.project.clone(); + project.settings.output_selection = + OutputSelection::common_output_selection(["metadata".to_string()]); + + let output = ProjectCompiler::new() + .quiet(true) + .files([self.target_path.clone()]) + .compile(&project)?; + + let artifact = output + .find(self.target_path.to_string_lossy(), &self.target_name) + .ok_or_eyre("failed to find target artifact when compiling for metadata")?; + + artifact.metadata.clone().ok_or_eyre("target artifact does not have an ABI") + } + + /// Returns [Vec] containing imports of the target file. + pub fn get_target_imports(&self) -> Result> { + let mut sources = self.project.paths.read_input_files()?; + sources.insert(self.target_path.clone(), Source::read(&self.target_path)?); + let graph = Graph::resolve_sources(&self.project.paths, sources)?; + + Ok(graph.imports(&self.target_path).into_iter().cloned().collect()) + } +} /// An abstraction for various verification providers such as etherscan, sourcify, blockscout #[async_trait] @@ -16,10 +99,14 @@ pub trait VerificationProvider { /// [`VerifyArgs`] are valid to begin with. This should prevent situations where there's a /// contract deployment that's executed before the verify request and the subsequent verify task /// fails due to misconfiguration. - async fn preflight_check(&mut self, args: VerifyArgs) -> Result<()>; + async fn preflight_check( + &mut self, + args: VerifyArgs, + context: VerificationContext, + ) -> Result<()>; /// Sends the actual verify request for the targeted contract. - async fn verify(&mut self, args: VerifyArgs) -> Result<()>; + async fn verify(&mut self, args: VerifyArgs, context: VerificationContext) -> Result<()>; /// Checks whether the contract is verified. async fn check(&self, args: VerifyCheckArgs) -> Result<()>; diff --git a/crates/verify/src/sourcify.rs b/crates/verify/src/sourcify.rs index 71fde6336133..58cb2b4b93d7 100644 --- a/crates/verify/src/sourcify.rs +++ b/crates/verify/src/sourcify.rs @@ -1,13 +1,12 @@ use super::{provider::VerificationProvider, VerifyArgs, VerifyCheckArgs}; +use crate::provider::VerificationContext; use async_trait::async_trait; use eyre::Result; -use foundry_cli::utils::{get_cached_entry_by_name, LoadConfig}; use foundry_common::{fs, retry::Retry}; -use foundry_compilers::ConfigurableContractArtifact; use futures::FutureExt; use reqwest::Url; use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, path::PathBuf, str::FromStr}; +use std::{collections::HashMap, str::FromStr}; pub static SOURCIFY_URL: &str = "https://sourcify.dev/server/"; @@ -18,13 +17,17 @@ pub struct SourcifyVerificationProvider; #[async_trait] impl VerificationProvider for SourcifyVerificationProvider { - async fn preflight_check(&mut self, args: VerifyArgs) -> Result<()> { - let _ = self.prepare_request(&args)?; + async fn preflight_check( + &mut self, + args: VerifyArgs, + context: VerificationContext, + ) -> Result<()> { + let _ = self.prepare_request(&args, &context)?; Ok(()) } - async fn verify(&mut self, args: VerifyArgs) -> Result<()> { - let body = self.prepare_request(&args)?; + async fn verify(&mut self, args: VerifyArgs, context: VerificationContext) -> Result<()> { + let body = self.prepare_request(&args, &context)?; trace!("submitting verification request {:?}", body); @@ -36,7 +39,7 @@ impl VerificationProvider for SourcifyVerificationProvider { async { println!( "\nSubmitting verification for [{}] {:?}.", - args.contract.name, + context.target_name, args.address.to_string() ); let response = client @@ -99,54 +102,24 @@ impl VerificationProvider for SourcifyVerificationProvider { impl SourcifyVerificationProvider { /// Configures the API request to the sourcify API using the given [`VerifyArgs`]. - fn prepare_request(&self, args: &VerifyArgs) -> Result { - let mut config = args.try_load_config_emit_warnings()?; - config.libraries.extend(args.libraries.clone()); - - let project = config.project()?; - - if !config.cache { - eyre::bail!("Cache is required for sourcify verification.") - } - - let cache = project.read_cache_file()?; - let (path, entry) = get_cached_entry_by_name(&cache, &args.contract.name)?; + fn prepare_request( + &self, + args: &VerifyArgs, + context: &VerificationContext, + ) -> Result { + let metadata = context.get_target_metadata()?; + let imports = context.get_target_imports()?; - if entry.compiler_settings.metadata.is_none() { - eyre::bail!( - r#"Contract {} was compiled without the solc `metadata` setting. -Sourcify requires contract metadata for verification. -metadata output can be enabled via `extra_output = ["metadata"]` in `foundry.toml`"#, - args.contract.name - ) - } + let mut files = HashMap::with_capacity(2 + imports.len()); - let mut files = HashMap::with_capacity(2 + entry.imports.len()); - - // the metadata is included in the contract's artifact file - let artifact_path = entry - .find_artifact_path(&args.contract.name) - .ok_or_else(|| eyre::eyre!("No artifact found for contract {}", args.contract.name))?; - - let artifact: ConfigurableContractArtifact = fs::read_json_file(artifact_path)?; - if let Some(metadata) = artifact.metadata { - let metadata = serde_json::to_string_pretty(&metadata)?; - files.insert("metadata.json".to_string(), metadata); - } else { - eyre::bail!( - r#"No metadata found in artifact `{}` for contract {}. -Sourcify requires contract metadata for verification. -metadata output can be enabled via `extra_output = ["metadata"]` in `foundry.toml`"#, - artifact_path.display(), - args.contract.name - ) - } + let metadata = serde_json::to_string_pretty(&metadata)?; + files.insert("metadata.json".to_string(), metadata); - let contract_path = args.contract.path.clone().map_or(path, PathBuf::from); + let contract_path = context.target_path.clone(); let filename = contract_path.file_name().unwrap().to_string_lossy().to_string(); files.insert(filename, fs::read_to_string(&contract_path)?); - for import in entry.imports { + for import in imports { let import_entry = format!("{}", import.display()); files.insert(import_entry, fs::read_to_string(&import)?); } From 3336b1734abea84d683d6e89ab1ce0d49b1f8040 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Fri, 17 May 2024 03:15:25 +0300 Subject: [PATCH 2/8] version build regex --- Cargo.lock | 2 ++ crates/verify/Cargo.toml | 2 ++ crates/verify/src/etherscan/mod.rs | 11 +++++++++++ 3 files changed, 15 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index aa965f0346da..a8fd519afbaf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3247,6 +3247,8 @@ dependencies = [ "foundry-test-utils", "futures", "itertools 0.12.1", + "once_cell", + "regex", "reqwest", "revm-primitives", "semver 1.0.23", diff --git a/crates/verify/Cargo.toml b/crates/verify/Cargo.toml index 5901f91540a6..54d886f7cea6 100644 --- a/crates/verify/Cargo.toml +++ b/crates/verify/Cargo.toml @@ -33,6 +33,8 @@ reqwest = { workspace = true, features = ["json"] } async-trait = "0.1" futures = "0.3" semver = "1" +regex = { version = "1", default-features = false } +once_cell = "1" yansi.workspace = true itertools.workspace = true diff --git a/crates/verify/src/etherscan/mod.rs b/crates/verify/src/etherscan/mod.rs index 2929099c06e8..df52d77581a1 100644 --- a/crates/verify/src/etherscan/mod.rs +++ b/crates/verify/src/etherscan/mod.rs @@ -15,12 +15,17 @@ use foundry_compilers::{artifacts::BytecodeObject, Artifact}; use foundry_config::{Chain, Config}; use foundry_evm::constants::DEFAULT_CREATE2_DEPLOYER; use futures::FutureExt; +use once_cell::sync::Lazy; +use regex::Regex; use semver::{BuildMetadata, Version}; use std::fmt::Debug; mod flatten; mod standard_json; +pub static RE_BUILD_COMMIT: Lazy = + Lazy::new(|| Regex::new(r"(?Pcommit\.[0-9,a-f]{8})").unwrap()); + #[derive(Clone, Debug, Default)] #[non_exhaustive] pub struct EtherscanVerificationProvider; @@ -289,6 +294,12 @@ impl EtherscanVerificationProvider { let (source, contract_name, code_format) = self.source_provider(args).source(args, context)?; + let mut compiler_version = context.compiler_version.clone(); + compiler_version.build = match RE_BUILD_COMMIT.captures(compiler_version.build.as_str()) { + Some(cap) => BuildMetadata::new(cap.name("commit").unwrap().as_str())?, + _ => BuildMetadata::EMPTY, + }; + let compiler_version = format!("v{}", ensure_solc_build_metadata(context.compiler_version.clone()).await?); let constructor_args = self.constructor_args(args, context).await?; From e89bd502302490e6126e2ef8e48cc0b91fd37d07 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Fri, 17 May 2024 03:36:10 +0300 Subject: [PATCH 3/8] rm println --- crates/forge/bin/cmd/build.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/forge/bin/cmd/build.rs b/crates/forge/bin/cmd/build.rs index c42934a99997..673198fdc80f 100644 --- a/crates/forge/bin/cmd/build.rs +++ b/crates/forge/bin/cmd/build.rs @@ -77,7 +77,6 @@ pub struct BuildArgs { impl BuildArgs { pub fn run(self) -> Result { let mut config = self.try_load_config_emit_warnings()?; - println!("{:?}", config.libraries); let mut project = config.project()?; if install::install_missing_dependencies(&mut config, self.args.silent) && From 73c3055f6731412f2b2bdfdd1f1e89928190ffa5 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Fri, 17 May 2024 05:19:42 +0300 Subject: [PATCH 4/8] fix tests --- crates/verify/src/etherscan/mod.rs | 38 +----------------------------- crates/verify/src/lib.rs | 8 +++++-- 2 files changed, 7 insertions(+), 39 deletions(-) diff --git a/crates/verify/src/etherscan/mod.rs b/crates/verify/src/etherscan/mod.rs index df52d77581a1..8bb4084b1c20 100644 --- a/crates/verify/src/etherscan/mod.rs +++ b/crates/verify/src/etherscan/mod.rs @@ -548,48 +548,12 @@ mod tests { "--root", root_path, ]); - let context = args.resolve_context().await.unwrap(); - - let result = etherscan.preflight_check(args, context).await; + let result = args.resolve_context().await; assert!(result.is_err()); assert_eq!( result.unwrap_err().to_string(), "If cache is disabled, compiler version must be either provided with `--compiler-version` option or set in foundry.toml" ); - - // No contract path - let args = - VerifyArgs::parse_from(["foundry-cli", address, contract_name, "--root", root_path]); - - let context = args.resolve_context().await.unwrap(); - - let result = etherscan.preflight_check(args, context).await; - assert!(result.is_err()); - assert_eq!( - result.unwrap_err().to_string(), - "If cache is disabled, contract info must be provided in the format :" - ); - - // Constructor args path - let args = VerifyArgs::parse_from([ - "foundry-cli", - address, - &format!("{contract_path}:{contract_name}"), - "--constructor-args-path", - ".", - "--compiler-version", - "0.8.15", - "--root", - root_path, - ]); - let context = args.resolve_context().await.unwrap(); - - let result = etherscan.preflight_check(args, context).await; - assert!(result.is_err()); - assert_eq!( - result.unwrap_err().to_string(), - "Cache must be enabled in order to use the `--constructor-args-path` option", - ); } forgetest_async!(respects_path_for_duplicate, |prj, cmd| { diff --git a/crates/verify/src/lib.rs b/crates/verify/src/lib.rs index f8e20d49199e..5655791182a4 100644 --- a/crates/verify/src/lib.rs +++ b/crates/verify/src/lib.rs @@ -249,7 +249,7 @@ impl VerifyArgs { if let Some(ref contract) = self.contract { let contract_path = if let Some(ref path) = contract.path { - PathBuf::from(path) + project.root().join(PathBuf::from(path)) } else { project.find_contract_path(&contract.name)? }; @@ -261,7 +261,11 @@ impl VerifyArgs { SolcReq::Version(version) => version.to_owned(), SolcReq::Local(solc) => Solc::new(solc)?.version, } - } else if let Some(entry) = project.read_cache_file()?.entry(contract_path.clone()) { + } else if let Some(entry) = project + .read_cache_file() + .ok() + .and_then(|mut cache| cache.files.remove(&contract_path)) + { let unique_versions = entry .artifacts .get(&contract.name) From 8e661c42a1a24666921677e17a25708d723d4223 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Fri, 17 May 2024 05:25:14 +0300 Subject: [PATCH 5/8] clippy --- crates/verify/src/etherscan/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/verify/src/etherscan/mod.rs b/crates/verify/src/etherscan/mod.rs index 8bb4084b1c20..b0698d58b4fb 100644 --- a/crates/verify/src/etherscan/mod.rs +++ b/crates/verify/src/etherscan/mod.rs @@ -538,8 +538,6 @@ mod tests { let contract_path = format!("{src_dir}/Counter.sol"); fs::write(root.join(&contract_path), "").unwrap(); - let mut etherscan = EtherscanVerificationProvider::default(); - // No compiler argument let args = VerifyArgs::parse_from([ "foundry-cli", From c5c31c6d737998e2cc560485697ad15cd4de1ca2 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Fri, 17 May 2024 16:38:31 +0300 Subject: [PATCH 6/8] review fixes --- crates/common/src/contracts.rs | 9 ++++++--- crates/verify/src/etherscan/flatten.rs | 1 - crates/verify/src/etherscan/standard_json.rs | 1 - 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index e82ce148f54e..879a78ba2a31 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -1,7 +1,7 @@ //! Commonly used contract types and functions. use alloy_json_abi::{Event, Function, JsonAbi}; -use alloy_primitives::{bytes, Address, Bytes, Selector, B256}; +use alloy_primitives::{hex, Address, Bytes, Selector, B256}; use eyre::Result; use foundry_compilers::{ artifacts::{ @@ -16,8 +16,11 @@ use std::{ str::FromStr, }; -/// https://docs.soliditylang.org/en/latest/contracts.html#call-protection-for-libraries -const CALL_PROTECTION_BYTECODE_PREFIX: Bytes = bytes!("730000000000000000000000000000000000000000"); +/// Libraries' runtime code always starts with the following instruction: +/// `PUSH20 0x0000000000000000000000000000000000000000` +/// +/// See: +const CALL_PROTECTION_BYTECODE_PREFIX: [u8; 21] = hex!("730000000000000000000000000000000000000000"); /// Container for commonly used contract data. #[derive(Debug, Clone)] diff --git a/crates/verify/src/etherscan/flatten.rs b/crates/verify/src/etherscan/flatten.rs index 56671ab5bbec..b0ffb603bb0e 100644 --- a/crates/verify/src/etherscan/flatten.rs +++ b/crates/verify/src/etherscan/flatten.rs @@ -1,5 +1,4 @@ use crate::provider::VerificationContext; - use super::{EtherscanSourceProvider, VerifyArgs}; use eyre::{Context, Result}; use foundry_block_explorers::verify::CodeFormat; diff --git a/crates/verify/src/etherscan/standard_json.rs b/crates/verify/src/etherscan/standard_json.rs index bd5eb6d0f56e..0ca3f7b060aa 100644 --- a/crates/verify/src/etherscan/standard_json.rs +++ b/crates/verify/src/etherscan/standard_json.rs @@ -1,5 +1,4 @@ use crate::provider::VerificationContext; - use super::{EtherscanSourceProvider, VerifyArgs}; use eyre::{Context, Result}; use foundry_block_explorers::verify::CodeFormat; From 96d86591c02097fa7a3eb4b98f30f785c9d1d069 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Fri, 17 May 2024 16:46:57 +0300 Subject: [PATCH 7/8] fmt --- crates/common/src/contracts.rs | 3 ++- crates/verify/src/etherscan/flatten.rs | 2 +- crates/verify/src/etherscan/standard_json.rs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index 879a78ba2a31..508ddf10c1e7 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -20,7 +20,8 @@ use std::{ /// `PUSH20 0x0000000000000000000000000000000000000000` /// /// See: -const CALL_PROTECTION_BYTECODE_PREFIX: [u8; 21] = hex!("730000000000000000000000000000000000000000"); +const CALL_PROTECTION_BYTECODE_PREFIX: [u8; 21] = + hex!("730000000000000000000000000000000000000000"); /// Container for commonly used contract data. #[derive(Debug, Clone)] diff --git a/crates/verify/src/etherscan/flatten.rs b/crates/verify/src/etherscan/flatten.rs index b0ffb603bb0e..cebdfbd22e7a 100644 --- a/crates/verify/src/etherscan/flatten.rs +++ b/crates/verify/src/etherscan/flatten.rs @@ -1,5 +1,5 @@ -use crate::provider::VerificationContext; use super::{EtherscanSourceProvider, VerifyArgs}; +use crate::provider::VerificationContext; use eyre::{Context, Result}; use foundry_block_explorers::verify::CodeFormat; use foundry_compilers::{ diff --git a/crates/verify/src/etherscan/standard_json.rs b/crates/verify/src/etherscan/standard_json.rs index 0ca3f7b060aa..944ee6b101cf 100644 --- a/crates/verify/src/etherscan/standard_json.rs +++ b/crates/verify/src/etherscan/standard_json.rs @@ -1,5 +1,5 @@ -use crate::provider::VerificationContext; use super::{EtherscanSourceProvider, VerifyArgs}; +use crate::provider::VerificationContext; use eyre::{Context, Result}; use foundry_block_explorers::verify::CodeFormat; use foundry_compilers::artifacts::StandardJsonCompilerInput; From bf52b0b6d47bdb58acec19dab2327e0ad75b1636 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Wed, 22 May 2024 22:08:58 +0300 Subject: [PATCH 8/8] ref -> & --- crates/common/src/contracts.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index 508ddf10c1e7..e523c3813c58 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -100,10 +100,10 @@ impl ContractsByArtifact { /// references and immutables. pub fn find_by_deployed_code_exact(&self, code: &[u8]) -> Option { self.iter().find(|(_, contract)| { - let Some(ref deployed_bytecode) = contract.deployed_bytecode else { + let Some(deployed_bytecode) = &contract.deployed_bytecode else { return false; }; - let Some(ref deployed_code) = deployed_bytecode.bytecode else { + let Some(deployed_code) = &deployed_bytecode.bytecode else { return false; };