From 07c36ed534fc15fa079c6b9ee0efb12eebcc6c94 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Sat, 6 Jan 2024 12:36:56 +0100 Subject: [PATCH 1/8] Add RpcOpts to VerifyArgs --- crates/forge/bin/cmd/create.rs | 7 ++++++- crates/forge/bin/cmd/script/verify.rs | 1 + crates/forge/bin/cmd/verify/mod.rs | 23 +++++++++++++++++++++-- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/crates/forge/bin/cmd/create.rs b/crates/forge/bin/cmd/create.rs index 8c4752603bdc..2d860fb8d65a 100644 --- a/crates/forge/bin/cmd/create.rs +++ b/crates/forge/bin/cmd/create.rs @@ -176,7 +176,11 @@ impl CreateArgs { constructor_args, constructor_args_path: None, num_of_optimizations: None, - etherscan: EtherscanOpts { key: self.eth.etherscan.key(), chain: Some(chain.into()) }, + etherscan: EtherscanOpts { + key: self.eth.etherscan.key.clone(), + chain: Some(chain.into()), + }, + rpc: Default::default(), flatten: false, force: false, skip_is_verified_check: true, @@ -326,6 +330,7 @@ impl CreateArgs { constructor_args_path: None, num_of_optimizations, etherscan: EtherscanOpts { key: self.eth.etherscan.key(), chain: Some(chain.into()) }, + rpc: Default::default(), flatten: false, force: false, skip_is_verified_check: false, diff --git a/crates/forge/bin/cmd/script/verify.rs b/crates/forge/bin/cmd/script/verify.rs index 6498b8f8a6dd..c370865620fe 100644 --- a/crates/forge/bin/cmd/script/verify.rs +++ b/crates/forge/bin/cmd/script/verify.rs @@ -105,6 +105,7 @@ impl VerifyBundle { constructor_args_path: None, num_of_optimizations: self.num_of_optimizations, etherscan: self.etherscan.clone(), + rpc: Default::default(), flatten: false, force: false, skip_is_verified_check: true, diff --git a/crates/forge/bin/cmd/verify/mod.rs b/crates/forge/bin/cmd/verify/mod.rs index c598006d9cd6..b942ca394c2a 100644 --- a/crates/forge/bin/cmd/verify/mod.rs +++ b/crates/forge/bin/cmd/verify/mod.rs @@ -2,7 +2,11 @@ use super::retry::RetryArgs; use alloy_primitives::Address; use clap::{Parser, ValueHint}; use eyre::Result; -use foundry_cli::{opts::EtherscanOpts, utils::LoadConfig}; +use foundry_cli::{ + opts::{EtherscanOpts, RpcOpts}, + utils, + utils::LoadConfig, +}; use foundry_compilers::{info::ContractInfo, EvmVersion}; use foundry_config::{figment, impl_figment_convert, impl_figment_convert_cast, Config}; use provider::VerificationProviderType; @@ -112,6 +116,9 @@ pub struct VerifyArgs { #[clap(flatten)] pub etherscan: EtherscanOpts, + #[clap(flatten)] + pub rpc: RpcOpts, + #[clap(flatten)] pub retry: RetryArgs, @@ -130,6 +137,8 @@ impl figment::Provider for VerifyArgs { &self, ) -> Result, figment::Error> { let mut dict = self.etherscan.dict(); + dict.extend(self.rpc.dict()); + if let Some(root) = self.root.as_ref() { dict.insert("root".to_string(), figment::value::Value::serialize(root)?); } @@ -154,7 +163,17 @@ impl VerifyArgs { /// Run the verify command to submit the contract's source code for verification on etherscan pub async fn run(mut self) -> Result<()> { let config = self.load_config_emit_warnings(); - let chain = config.chain.unwrap_or_default(); + + // If chain is not set, we try to get it from the RPC + // If RPC is not set, the default chain is used + let chain = match config.get_rpc_url() { + Some(_) => { + let provider = utils::get_provider(&config)?; + utils::get_chain(config.chain, provider).await? + } + None => config.chain.unwrap_or_default(), + }; + self.etherscan.chain = Some(chain); self.etherscan.key = config.get_etherscan_config_with_chain(Some(chain))?.map(|c| c.key); From fe8129c10ec44e113fd1e9c630e6b5745573691e Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Sun, 7 Jan 2024 01:07:35 +0100 Subject: [PATCH 2/8] --guess-constructor-args --- crates/forge/bin/cmd/create.rs | 2 + crates/forge/bin/cmd/script/verify.rs | 1 + crates/forge/bin/cmd/verify/etherscan/mod.rs | 65 ++++++++++++++++++-- crates/forge/bin/cmd/verify/mod.rs | 10 +++ 4 files changed, 72 insertions(+), 6 deletions(-) diff --git a/crates/forge/bin/cmd/create.rs b/crates/forge/bin/cmd/create.rs index 2d860fb8d65a..fff5b65057e2 100644 --- a/crates/forge/bin/cmd/create.rs +++ b/crates/forge/bin/cmd/create.rs @@ -192,6 +192,7 @@ impl CreateArgs { via_ir: self.opts.via_ir, evm_version: self.opts.compiler.evm_version, show_standard_json_input: self.show_standard_json_input, + guess_constructor_args: false, }; // Check config for Etherscan API Keys to avoid preflight check failing if no @@ -342,6 +343,7 @@ impl CreateArgs { via_ir: self.opts.via_ir, evm_version: self.opts.compiler.evm_version, show_standard_json_input: self.show_standard_json_input, + guess_constructor_args: false, }; println!("Waiting for {} to detect contract deployment...", verify.verifier.verifier); verify.run().await diff --git a/crates/forge/bin/cmd/script/verify.rs b/crates/forge/bin/cmd/script/verify.rs index c370865620fe..96b778edaf87 100644 --- a/crates/forge/bin/cmd/script/verify.rs +++ b/crates/forge/bin/cmd/script/verify.rs @@ -117,6 +117,7 @@ impl VerifyBundle { via_ir: self.via_ir, evm_version: None, show_standard_json_input: false, + guess_constructor_args: false, }; return Some(verify) diff --git a/crates/forge/bin/cmd/verify/etherscan/mod.rs b/crates/forge/bin/cmd/verify/etherscan/mod.rs index 919c83b8fb0a..00fa7936de13 100644 --- a/crates/forge/bin/cmd/verify/etherscan/mod.rs +++ b/crates/forge/bin/cmd/verify/etherscan/mod.rs @@ -1,7 +1,8 @@ use super::{provider::VerificationProvider, VerifyArgs, VerifyCheckArgs}; use crate::cmd::retry::RETRY_CHECK_ON_VERIFY; use alloy_json_abi::Function; -use eyre::{eyre, Context, Result}; +use ethers_providers::Middleware; +use eyre::{eyre, Context, OptionExt, Result}; use forge::hashbrown::HashSet; use foundry_block_explorers::{ errors::EtherscanError, @@ -9,10 +10,13 @@ use foundry_block_explorers::{ verify::{CodeFormat, VerifyContract}, Client, }; -use foundry_cli::utils::{get_cached_entry_by_name, read_constructor_args_file, LoadConfig}; -use foundry_common::{abi::encode_function_args, retry::Retry}; +use foundry_cli::utils::{self, get_cached_entry_by_name, read_constructor_args_file, LoadConfig}; +use foundry_common::{abi::encode_function_args, retry::Retry, types::ToEthers}; use foundry_compilers::{ - artifacts::CompactContract, cache::CacheEntry, info::ContractInfo, Project, Solc, + artifacts::{BytecodeObject, CompactContract}, + cache::CacheEntry, + info::ContractInfo, + Artifact, Project, Solc, }; use foundry_config::{Chain, Config, SolcReq}; use futures::FutureExt; @@ -332,7 +336,7 @@ impl EtherscanVerificationProvider { self.source_provider(args).source(args, &project, &contract_path, &compiler_version)?; let compiler_version = format!("v{}", ensure_solc_build_metadata(compiler_version).await?); - let constructor_args = self.constructor_args(args, &project)?; + let constructor_args = self.constructor_args(args, &project, &config).await?; let mut verify_args = VerifyContract::new(args.address, contract_name, source, compiler_version) .constructor_arguments(constructor_args) @@ -435,7 +439,12 @@ impl EtherscanVerificationProvider { /// 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. - fn constructor_args(&mut self, args: &VerifyArgs, project: &Project) -> Result> { + async fn constructor_args( + &mut self, + args: &VerifyArgs, + project: &Project, + config: &Config, + ) -> 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", @@ -458,6 +467,50 @@ impl EtherscanVerificationProvider { )?; let encoded_args = hex::encode(encoded_args); return Ok(Some(encoded_args[8..].into())) + } else if args.guess_constructor_args { + let provider = utils::get_provider(config)?; + let client = self.client( + args.etherscan.chain.unwrap_or_default(), + args.verifier.verifier_url.as_deref(), + args.etherscan.key.as_deref(), + &config, + )?; + + let creation_data = client.contract_creation_data(args.address).await?; + let transaction = provider + .get_transaction(creation_data.transaction_hash.to_ethers()) + .await? + .ok_or_eyre("Couldn't fetch transaction data from RPC")?; + let receipt = provider + .get_transaction_receipt(creation_data.transaction_hash.to_ethers()) + .await? + .ok_or_eyre("Couldn't fetch transaction receipt from RPC")?; + + if receipt.contract_address != Some(args.address.to_ethers()) { + eyre::bail!("Fetching of constructor arguments is not supported for contracts created by contracts") + } + + let output = project.compile_file(self.contract_path(args, project)?)?; + let artifact = output + .find_contract(&args.contract) + .ok_or_eyre("Contract artifact wasn't found locally")?; + let bytecode = artifact + .get_bytecode_object() + .ok_or_eyre("Contract artifact does not contain bytecode")?; + + let bytecode = match bytecode.as_ref() { + BytecodeObject::Bytecode(bytes) => Ok(bytes), + BytecodeObject::Unlinked(_) => Err(eyre!( + "You have to provide correct libraries to use --guess-constructor-args" + )), + }?; + + if transaction.input.starts_with(bytecode) { + let constructor_args = &transaction.input[bytecode.len()..]; + return Ok(Some(hex::encode(constructor_args))); + } else { + eyre::bail!("Local bytecode doesn't match on-chain bytecode") + } } Ok(args.constructor_args.clone()) diff --git a/crates/forge/bin/cmd/verify/mod.rs b/crates/forge/bin/cmd/verify/mod.rs index b942ca394c2a..16b83e98f911 100644 --- a/crates/forge/bin/cmd/verify/mod.rs +++ b/crates/forge/bin/cmd/verify/mod.rs @@ -61,6 +61,10 @@ pub struct VerifyArgs { #[clap(long, value_hint = ValueHint::FilePath, value_name = "PATH")] pub constructor_args_path: Option, + /// Try to extract constructor arguments from on-chain creation code. + #[clap(long)] + pub guess_constructor_args: bool, + /// The `solc` version to use to build the smart contract. #[clap(long, value_name = "VERSION")] pub compiler_version: Option, @@ -164,6 +168,12 @@ impl VerifyArgs { pub async fn run(mut self) -> Result<()> { let config = self.load_config_emit_warnings(); + if self.guess_constructor_args && config.get_rpc_url().is_none() { + eyre::bail!( + "You have to provide a valid RPC URL to use --guess-constructor-args feature" + ) + } + // If chain is not set, we try to get it from the RPC // If RPC is not set, the default chain is used let chain = match config.get_rpc_url() { From 53ab4e3c54443930b835f3193256626ddfede37d Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Sun, 7 Jan 2024 22:07:58 +0100 Subject: [PATCH 3/8] Add support for CREATE2 deployer --- crates/forge/bin/cmd/verify/etherscan/mod.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/forge/bin/cmd/verify/etherscan/mod.rs b/crates/forge/bin/cmd/verify/etherscan/mod.rs index 00fa7936de13..12caa1e43472 100644 --- a/crates/forge/bin/cmd/verify/etherscan/mod.rs +++ b/crates/forge/bin/cmd/verify/etherscan/mod.rs @@ -19,6 +19,7 @@ use foundry_compilers::{ Artifact, Project, Solc, }; use foundry_config::{Chain, Config, SolcReq}; +use foundry_evm::constants::DEFAULT_CREATE2_DEPLOYER; use futures::FutureExt; use once_cell::sync::Lazy; use regex::Regex; @@ -486,7 +487,13 @@ impl EtherscanVerificationProvider { .await? .ok_or_eyre("Couldn't fetch transaction receipt from RPC")?; - if receipt.contract_address != Some(args.address.to_ethers()) { + let maybe_creation_code: &[u8]; + + if receipt.contract_address == Some(args.address.to_ethers()) { + maybe_creation_code = &transaction.input; + } else if transaction.to == Some(DEFAULT_CREATE2_DEPLOYER.to_ethers()) { + maybe_creation_code = &transaction.input[32..]; + } else { eyre::bail!("Fetching of constructor arguments is not supported for contracts created by contracts") } @@ -505,8 +512,8 @@ impl EtherscanVerificationProvider { )), }?; - if transaction.input.starts_with(bytecode) { - let constructor_args = &transaction.input[bytecode.len()..]; + if maybe_creation_code.starts_with(bytecode) { + let constructor_args = &maybe_creation_code[bytecode.len()..]; return Ok(Some(hex::encode(constructor_args))); } else { eyre::bail!("Local bytecode doesn't match on-chain bytecode") From 468867b4225947fadb743bedf32a903adcaf65d7 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Mon, 8 Jan 2024 00:19:35 +0100 Subject: [PATCH 4/8] Fix artifact lookup --- crates/forge/bin/cmd/verify/etherscan/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge/bin/cmd/verify/etherscan/mod.rs b/crates/forge/bin/cmd/verify/etherscan/mod.rs index 12caa1e43472..60488c7a9cc5 100644 --- a/crates/forge/bin/cmd/verify/etherscan/mod.rs +++ b/crates/forge/bin/cmd/verify/etherscan/mod.rs @@ -499,7 +499,7 @@ impl EtherscanVerificationProvider { let output = project.compile_file(self.contract_path(args, project)?)?; let artifact = output - .find_contract(&args.contract) + .find_first(&args.contract.name) .ok_or_eyre("Contract artifact wasn't found locally")?; let bytecode = artifact .get_bytecode_object() From 64a28d56aeaa254b6814d4e66fcbbcf60c31b04e Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Mon, 8 Jan 2024 00:29:38 +0100 Subject: [PATCH 5/8] Update verification tests + Test for --guess-constructor-args --- crates/forge/tests/cli/verify.rs | 123 +++++++++++++++++++++++-------- 1 file changed, 92 insertions(+), 31 deletions(-) diff --git a/crates/forge/tests/cli/verify.rs b/crates/forge/tests/cli/verify.rs index 8874af9534e7..deedcc9abf83 100644 --- a/crates/forge/tests/cli/verify.rs +++ b/crates/forge/tests/cli/verify.rs @@ -55,6 +55,24 @@ function doStuff() external {{}} prj.add_source("Verify.sol", &contract).unwrap(); } +fn add_verify_target_with_constructor(prj: &TestProject) { + prj.add_source( + "Verify.sol", + r#" +import {Unique} from "./unique.sol"; +contract Verify is Unique { + struct SomeStruct { + uint256 a; + string str; + } + + constructor(SomeStruct memory st, address owner) {} +} +"#, + ) + .unwrap(); +} + fn parse_verification_result(cmd: &mut TestCommand, retries: u32) -> eyre::Result<()> { // give etherscan some time to verify the contract let retry = Retry::new(retries, Some(Duration::from_secs(30))); @@ -73,6 +91,39 @@ fn parse_verification_result(cmd: &mut TestCommand, retries: u32) -> eyre::Resul }) } +fn await_verification_response(info: EnvExternalities, mut cmd: TestCommand) { + let guid = { + // give etherscan some time to detect the transaction + let retry = Retry::new(5, Some(Duration::from_secs(60))); + retry + .run(|| -> eyre::Result { + let output = cmd.unchecked_output(); + let out = String::from_utf8_lossy(&output.stdout); + utils::parse_verification_guid(&out).ok_or_else(|| { + eyre::eyre!( + "Failed to get guid, stdout: {}, stderr: {}", + out, + String::from_utf8_lossy(&output.stderr) + ) + }) + }) + .expect("Failed to get verify guid") + }; + + // verify-check + cmd.forge_fuse() + .arg("verify-check") + .arg(guid) + .arg("--chain-id") + .arg(info.chain.to_string()) + .arg("--etherscan-api-key") + .arg(info.etherscan) + .arg("--verifier") + .arg(info.verifier); + + parse_verification_result(&mut cmd, 6).expect("Failed to verify check") +} + fn verify_on_chain(info: Option, prj: TestProject, mut cmd: TestCommand) { // only execute if keys present if let Some(info) = info { @@ -98,37 +149,41 @@ fn verify_on_chain(info: Option, prj: TestProject, mut cmd: Te info.verifier.to_string(), ]); - // `verify-contract` - let guid = { - // give etherscan some time to detect the transaction - let retry = Retry::new(5, Some(Duration::from_secs(60))); - retry - .run(|| -> eyre::Result { - let output = cmd.unchecked_output(); - let out = String::from_utf8_lossy(&output.stdout); - utils::parse_verification_guid(&out).ok_or_else(|| { - eyre::eyre!( - "Failed to get guid, stdout: {}, stderr: {}", - out, - String::from_utf8_lossy(&output.stderr) - ) - }) - }) - .expect("Failed to get verify guid") - }; - - // verify-check - cmd.forge_fuse() - .arg("verify-check") - .arg(guid) - .arg("--chain-id") - .arg(info.chain.to_string()) - .arg("--etherscan-api-key") - .arg(info.etherscan) - .arg("--verifier") - .arg(info.verifier); - - parse_verification_result(&mut cmd, 6).expect("Failed to verify check") + await_verification_response(info, cmd) + } +} + +fn guess_constructor_args(info: Option, prj: TestProject, mut cmd: TestCommand) { + // only execute if keys present + if let Some(info) = info { + println!("verifying on {}", info.chain); + add_unique(&prj); + add_verify_target_with_constructor(&prj); + + let contract_path = "src/Verify.sol:Verify"; + cmd.arg("create").args(info.create_args()).arg(contract_path).args(vec![ + "--constructor-args", + "(239,SomeString)", + "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", + ]); + + let out = cmd.stdout_lossy(); + let address = utils::parse_deployed_address(out.as_str()) + .unwrap_or_else(|| panic!("Failed to parse deployer {out}")); + + cmd.forge_fuse().arg("verify-contract").root_arg().args([ + "--rpc-url".to_string(), + info.rpc.to_string(), + address, + contract_path.to_string(), + "--etherscan-api-key".to_string(), + info.etherscan.to_string(), + "--verifier".to_string(), + info.verifier.to_string(), + "--guess-constructor-args".to_string(), + ]); + + await_verification_response(info, cmd) } } @@ -174,3 +229,9 @@ forgetest!(can_verify_random_contract_sepolia, |prj, cmd| { forgetest!(can_create_verify_random_contract_sepolia, |prj, cmd| { create_verify_on_chain(EnvExternalities::sepolia(), prj, cmd); }); + +// tests `create && contract-verify --guess-constructor-args && verify-check` on Goerli testnet if +// correct env vars are set +forgetest!(can_guess_constructor_args, |prj, cmd| { + guess_constructor_args(EnvExternalities::goerli(), prj, cmd); +}); From e5e5e6d827e02183c2b120df88dba78f9f4553a7 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Sun, 25 Feb 2024 15:35:56 +0400 Subject: [PATCH 6/8] chore: clippy --- crates/forge/bin/cmd/verify/etherscan/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge/bin/cmd/verify/etherscan/mod.rs b/crates/forge/bin/cmd/verify/etherscan/mod.rs index 60488c7a9cc5..fd16684445e4 100644 --- a/crates/forge/bin/cmd/verify/etherscan/mod.rs +++ b/crates/forge/bin/cmd/verify/etherscan/mod.rs @@ -474,7 +474,7 @@ impl EtherscanVerificationProvider { args.etherscan.chain.unwrap_or_default(), args.verifier.verifier_url.as_deref(), args.etherscan.key.as_deref(), - &config, + config, )?; let creation_data = client.contract_creation_data(args.address).await?; From 69b2e8539e037222ca69ef86c77d716205b00655 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Mon, 26 Feb 2024 16:42:09 +0400 Subject: [PATCH 7/8] update compilation + separate function --- crates/forge/bin/cmd/verify/etherscan/mod.rs | 109 ++++++++++--------- 1 file changed, 60 insertions(+), 49 deletions(-) diff --git a/crates/forge/bin/cmd/verify/etherscan/mod.rs b/crates/forge/bin/cmd/verify/etherscan/mod.rs index fd16684445e4..72b4dcdafa28 100644 --- a/crates/forge/bin/cmd/verify/etherscan/mod.rs +++ b/crates/forge/bin/cmd/verify/etherscan/mod.rs @@ -468,59 +468,70 @@ impl EtherscanVerificationProvider { )?; let encoded_args = hex::encode(encoded_args); return Ok(Some(encoded_args[8..].into())) - } else if args.guess_constructor_args { - let provider = utils::get_provider(config)?; - let client = self.client( - args.etherscan.chain.unwrap_or_default(), - args.verifier.verifier_url.as_deref(), - args.etherscan.key.as_deref(), - config, - )?; + } + if args.guess_constructor_args { + return Ok(Some(self.guess_constructor_args(args, project, config).await?)) + } - let creation_data = client.contract_creation_data(args.address).await?; - let transaction = provider - .get_transaction(creation_data.transaction_hash.to_ethers()) - .await? - .ok_or_eyre("Couldn't fetch transaction data from RPC")?; - let receipt = provider - .get_transaction_receipt(creation_data.transaction_hash.to_ethers()) - .await? - .ok_or_eyre("Couldn't fetch transaction receipt from RPC")?; - - let maybe_creation_code: &[u8]; - - if receipt.contract_address == Some(args.address.to_ethers()) { - maybe_creation_code = &transaction.input; - } else if transaction.to == Some(DEFAULT_CREATE2_DEPLOYER.to_ethers()) { - maybe_creation_code = &transaction.input[32..]; - } else { - eyre::bail!("Fetching of constructor arguments is not supported for contracts created by contracts") - } + Ok(args.constructor_args.clone()) + } - let output = project.compile_file(self.contract_path(args, project)?)?; - let artifact = output - .find_first(&args.contract.name) - .ok_or_eyre("Contract artifact wasn't found locally")?; - let bytecode = artifact - .get_bytecode_object() - .ok_or_eyre("Contract artifact does not contain bytecode")?; - - let bytecode = match bytecode.as_ref() { - BytecodeObject::Bytecode(bytes) => Ok(bytes), - BytecodeObject::Unlinked(_) => Err(eyre!( - "You have to provide correct libraries to use --guess-constructor-args" - )), - }?; - - if maybe_creation_code.starts_with(bytecode) { - let constructor_args = &maybe_creation_code[bytecode.len()..]; - return Ok(Some(hex::encode(constructor_args))); - } else { - eyre::bail!("Local bytecode doesn't match on-chain bytecode") - } + async fn guess_constructor_args( + &mut self, + args: &VerifyArgs, + project: &Project, + config: &Config, + ) -> Result { + let provider = utils::get_provider(config)?; + let client = self.client( + args.etherscan.chain.unwrap_or_default(), + args.verifier.verifier_url.as_deref(), + args.etherscan.key.as_deref(), + config, + )?; + + let creation_data = client.contract_creation_data(args.address).await?; + let transaction = provider + .get_transaction(creation_data.transaction_hash.to_ethers()) + .await? + .ok_or_eyre("Couldn't fetch transaction data from RPC")?; + let receipt = provider + .get_transaction_receipt(creation_data.transaction_hash.to_ethers()) + .await? + .ok_or_eyre("Couldn't fetch transaction receipt from RPC")?; + + let maybe_creation_code: &[u8]; + + if receipt.contract_address == Some(args.address.to_ethers()) { + maybe_creation_code = &transaction.input; + } else if transaction.to == Some(DEFAULT_CREATE2_DEPLOYER.to_ethers()) { + maybe_creation_code = &transaction.input[32..]; + } else { + eyre::bail!("Fetching of constructor arguments is not supported for contracts created by contracts") } - Ok(args.constructor_args.clone()) + let contract_path = self.contract_path(args, project)?.to_string_lossy().into_owned(); + let output = project.compile()?; + let artifact = output + .find(contract_path, &args.contract.name) + .ok_or_eyre("Contract artifact wasn't found locally")?; + let bytecode = artifact + .get_bytecode_object() + .ok_or_eyre("Contract artifact does not contain bytecode")?; + + let bytecode = match bytecode.as_ref() { + BytecodeObject::Bytecode(bytes) => Ok(bytes), + BytecodeObject::Unlinked(_) => { + Err(eyre!("You have to provide correct libraries to use --guess-constructor-args")) + } + }?; + + if maybe_creation_code.starts_with(bytecode) { + let constructor_args = &maybe_creation_code[bytecode.len()..]; + Ok(hex::encode(constructor_args)) + } else { + eyre::bail!("Local bytecode doesn't match on-chain bytecode") + } } } From f87b13ef486bf3dfaf4f54ae9206af21bf4b35a8 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Mon, 26 Feb 2024 19:24:17 +0400 Subject: [PATCH 8/8] doc --- crates/forge/bin/cmd/verify/etherscan/mod.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/forge/bin/cmd/verify/etherscan/mod.rs b/crates/forge/bin/cmd/verify/etherscan/mod.rs index 72b4dcdafa28..f63db1240eb1 100644 --- a/crates/forge/bin/cmd/verify/etherscan/mod.rs +++ b/crates/forge/bin/cmd/verify/etherscan/mod.rs @@ -476,6 +476,10 @@ impl EtherscanVerificationProvider { Ok(args.constructor_args.clone()) } + /// Uses Etherscan API to fetch contract creation transaction. + /// If transaction is a create transaction or a invocation of default CREATE2 deployer, tries to + /// match provided creation code with local bytecode of the target contract. + /// If bytecode match, returns latest bytes of on-chain creation code as constructor arguments. async fn guess_constructor_args( &mut self, args: &VerifyArgs,