use crate::{
etherscan::EtherscanVerificationProvider,
utils::{
check_and_encode_args, check_explorer_args, configure_env_block, maybe_predeploy_contract,
BytecodeType, JsonResult,
},
verify::VerifierArgs,
};
use alloy_primitives::{hex, Address, Bytes, U256};
use alloy_provider::{
network::{AnyTxEnvelope, TransactionBuilder},
Provider,
};
use alloy_rpc_types::{BlockId, BlockNumberOrTag, TransactionInput, TransactionRequest};
use clap::{Parser, ValueHint};
use eyre::{Context, OptionExt, Result};
use foundry_cli::{
opts::EtherscanOpts,
utils::{self, read_constructor_args_file, LoadConfig},
};
use foundry_common::shell;
use foundry_compilers::{artifacts::EvmVersion, info::ContractInfo};
use foundry_config::{figment, impl_figment_convert, Config};
use foundry_evm::{constants::DEFAULT_CREATE2_DEPLOYER, utils::configure_tx_req_env};
use revm_primitives::{AccountInfo, TxKind};
use std::path::PathBuf;
impl_figment_convert!(VerifyBytecodeArgs);
#[derive(Clone, Debug, Parser)]
pub struct VerifyBytecodeArgs {
pub address: Address,
pub contract: ContractInfo,
#[arg(long, value_name = "BLOCK")]
pub block: Option<BlockId>,
#[arg(
long,
num_args(1..),
conflicts_with_all = &["constructor_args_path", "encoded_constructor_args"],
value_name = "ARGS",
)]
pub constructor_args: Option<Vec<String>>,
#[arg(
long,
conflicts_with_all = &["constructor_args_path", "constructor_args"],
value_name = "HEX",
)]
pub encoded_constructor_args: Option<String>,
#[arg(
long,
value_hint = ValueHint::FilePath,
value_name = "PATH",
conflicts_with_all = &["constructor_args", "encoded_constructor_args"]
)]
pub constructor_args_path: Option<PathBuf>,
#[arg(short = 'r', long, value_name = "RPC_URL", env = "ETH_RPC_URL")]
pub rpc_url: Option<String>,
#[command(flatten)]
pub etherscan: EtherscanOpts,
#[command(flatten)]
pub verifier: VerifierArgs,
#[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")]
pub root: Option<PathBuf>,
#[arg(long, value_name = "BYTECODE_TYPE")]
pub ignore: Option<BytecodeType>,
}
impl figment::Provider for VerifyBytecodeArgs {
fn metadata(&self) -> figment::Metadata {
figment::Metadata::named("Verify Bytecode Provider")
}
fn data(
&self,
) -> Result<figment::value::Map<figment::Profile, figment::value::Dict>, figment::Error> {
let mut dict = self.etherscan.dict();
if let Some(api_key) = &self.verifier.verifier_api_key {
dict.insert("etherscan_api_key".into(), api_key.as_str().into());
}
if let Some(block) = &self.block {
dict.insert("block".into(), figment::value::Value::serialize(block)?);
}
if let Some(rpc_url) = &self.rpc_url {
dict.insert("eth_rpc_url".into(), rpc_url.to_string().into());
}
Ok(figment::value::Map::from([(Config::selected_profile(), dict)]))
}
}
impl VerifyBytecodeArgs {
pub async fn run(mut self) -> Result<()> {
let config = self.load_config_emit_warnings();
let provider = utils::get_provider(&config)?;
let chain = match config.get_rpc_url() {
Some(_) => 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);
let etherscan = EtherscanVerificationProvider.client(
self.etherscan.chain.unwrap_or_default(),
self.verifier.verifier_url.as_deref(),
self.etherscan.key().as_deref(),
&config,
)?;
let code = provider.get_code_at(self.address).await?;
if code.is_empty() {
eyre::bail!("No bytecode found at address {}", self.address);
}
if !shell::is_json() {
sh_println!(
"Verifying bytecode for contract {} at address {}",
self.contract.name,
self.address
)?;
}
let mut json_results: Vec<JsonResult> = vec![];
let creation_data = etherscan.contract_creation_data(self.address).await;
let (creation_data, maybe_predeploy) = maybe_predeploy_contract(creation_data)?;
trace!(maybe_predeploy = ?maybe_predeploy);
let source_code = etherscan.contract_source_code(self.address).await?;
let name = source_code.items.first().map(|item| item.contract_name.to_owned());
if name.as_ref() != Some(&self.contract.name) {
eyre::bail!("Contract name mismatch");
}
let etherscan_metadata = source_code.items.first().unwrap();
let artifact = if let Ok(local_bytecode) =
crate::utils::build_using_cache(&self, etherscan_metadata, &config)
{
trace!("using cache");
local_bytecode
} else {
crate::utils::build_project(&self, &config)?
};
let local_bytecode = artifact
.bytecode
.as_ref()
.and_then(|b| b.to_owned().into_bytes())
.ok_or_eyre("Unlinked bytecode is not supported for verification")?;
let provided_constructor_args = if let Some(path) = self.constructor_args_path.to_owned() {
Some(read_constructor_args_file(path)?)
} else {
self.constructor_args.to_owned()
}
.map(|args| check_and_encode_args(&artifact, args))
.transpose()?
.or(self.encoded_constructor_args.to_owned().map(hex::decode).transpose()?);
let mut constructor_args = if let Some(provided) = provided_constructor_args {
provided.into()
} else {
check_explorer_args(source_code.clone())?
};
crate::utils::check_args_len(&artifact, &constructor_args)?;
if maybe_predeploy {
if !shell::is_json() {
sh_warn!(
"Attempting to verify predeployed contract at {:?}. Ignoring creation code verification.",
self.address
)?;
}
trace!(%constructor_args);
let mut local_bytecode_vec = local_bytecode.to_vec();
local_bytecode_vec.extend_from_slice(&constructor_args);
let gen_blk_num = 0_u64;
let (mut fork_config, evm_opts) = config.clone().load_config_and_evm_opts()?;
let (mut env, mut executor) = crate::utils::get_tracing_executor(
&mut fork_config,
gen_blk_num,
etherscan_metadata.evm_version()?.unwrap_or(EvmVersion::default()),
evm_opts,
)
.await?;
env.block.number = U256::ZERO; let genesis_block = provider.get_block(gen_blk_num.into(), true.into()).await?;
let deployer = Address::with_last_byte(0x1);
let mut gen_tx_req = TransactionRequest::default()
.with_from(deployer)
.with_input(Bytes::from(local_bytecode_vec))
.into_create();
if let Some(ref block) = genesis_block {
configure_env_block(&mut env, block);
gen_tx_req.max_fee_per_gas = block.header.base_fee_per_gas.map(|g| g as u128);
gen_tx_req.gas = Some(block.header.gas_limit);
gen_tx_req.gas_price = block.header.base_fee_per_gas.map(|g| g as u128);
}
configure_tx_req_env(&mut env, &gen_tx_req, None)
.wrap_err("Failed to configure tx request env")?;
let account_info = AccountInfo {
balance: U256::from(100 * 10_u128.pow(18)),
nonce: 0,
..Default::default()
};
executor.backend_mut().insert_account_info(deployer, account_info);
let fork_address = crate::utils::deploy_contract(
&mut executor,
&env,
config.evm_spec_id(),
gen_tx_req.to,
)?;
let (deployed_bytecode, onchain_runtime_code) = crate::utils::get_runtime_codes(
&mut executor,
&provider,
self.address,
fork_address,
None,
)
.await?;
let match_type = crate::utils::match_bytecodes(
&deployed_bytecode.original_bytes(),
&onchain_runtime_code,
&constructor_args,
true,
config.bytecode_hash,
);
crate::utils::print_result(
match_type,
BytecodeType::Runtime,
&mut json_results,
etherscan_metadata,
&config,
);
if shell::is_json() {
sh_println!("{}", serde_json::to_string(&json_results)?)?;
}
return Ok(());
}
let creation_data = creation_data.unwrap();
trace!(creation_tx_hash = ?creation_data.transaction_hash);
let transaction = provider
.get_transaction_by_hash(creation_data.transaction_hash)
.await
.or_else(|e| eyre::bail!("Couldn't fetch transaction from RPC: {:?}", e))?
.ok_or_else(|| {
eyre::eyre!("Transaction not found for hash {}", creation_data.transaction_hash)
})?;
let receipt = provider
.get_transaction_receipt(creation_data.transaction_hash)
.await
.or_else(|e| eyre::bail!("Couldn't fetch transaction receipt from RPC: {:?}", e))?;
let receipt = if let Some(receipt) = receipt {
receipt
} else {
eyre::bail!(
"Receipt not found for transaction hash {}",
creation_data.transaction_hash
);
};
let mut transaction: TransactionRequest = match transaction.inner.inner {
AnyTxEnvelope::Ethereum(tx) => tx.into(),
AnyTxEnvelope::Unknown(_) => unreachable!("Unknown transaction type"),
};
let maybe_creation_code =
if receipt.to.is_none() && receipt.contract_address == Some(self.address) {
match &transaction.input.input {
Some(input) => &input[..],
None => unreachable!("creation tx input is None"),
}
} else if receipt.to == Some(DEFAULT_CREATE2_DEPLOYER) {
match &transaction.input.input {
Some(input) => &input[32..],
None => unreachable!("creation tx input is None"),
}
} else {
eyre::bail!(
"Could not extract the creation code for contract at address {}",
self.address
);
};
if !maybe_creation_code.ends_with(&constructor_args) {
trace!("mismatch of constructor args with etherscan");
if maybe_creation_code.len() >= local_bytecode.len() {
constructor_args =
Bytes::copy_from_slice(&maybe_creation_code[local_bytecode.len()..]);
trace!(
target: "forge::verify",
"setting constructor args to latest {} bytes of bytecode",
constructor_args.len()
);
}
}
trace!(%constructor_args);
let mut local_bytecode_vec = local_bytecode.to_vec();
local_bytecode_vec.extend_from_slice(&constructor_args);
trace!(ignore = ?self.ignore);
if !self.ignore.is_some_and(|b| b.is_creation()) {
let match_type = crate::utils::match_bytecodes(
local_bytecode_vec.as_slice(),
maybe_creation_code,
&constructor_args,
false,
config.bytecode_hash,
);
crate::utils::print_result(
match_type,
BytecodeType::Creation,
&mut json_results,
etherscan_metadata,
&config,
);
if match_type.is_none() {
crate::utils::print_result(
None,
BytecodeType::Runtime,
&mut json_results,
etherscan_metadata,
&config,
);
if shell::is_json() {
sh_println!("{}", serde_json::to_string(&json_results)?)?;
}
return Ok(());
}
}
if !self.ignore.is_some_and(|b| b.is_runtime()) {
let simulation_block = match self.block {
Some(BlockId::Number(BlockNumberOrTag::Number(block))) => block,
Some(_) => eyre::bail!("Invalid block number"),
None => {
let provider = utils::get_provider(&config)?;
provider
.get_transaction_by_hash(creation_data.transaction_hash)
.await.or_else(|e| eyre::bail!("Couldn't fetch transaction from RPC: {:?}", e))?.ok_or_else(|| {
eyre::eyre!("Transaction not found for hash {}", creation_data.transaction_hash)
})?
.block_number.ok_or_else(|| {
eyre::eyre!("Failed to get block number of the contract creation tx, specify using the --block flag")
})?
}
};
let (mut fork_config, evm_opts) = config.clone().load_config_and_evm_opts()?;
let (mut env, mut executor) = crate::utils::get_tracing_executor(
&mut fork_config,
simulation_block - 1, etherscan_metadata.evm_version()?.unwrap_or(EvmVersion::default()),
evm_opts,
)
.await?;
env.block.number = U256::from(simulation_block);
let block = provider.get_block(simulation_block.into(), true.into()).await?;
let prev_block_id = BlockId::number(simulation_block - 1);
let prev_block_nonce = provider
.get_transaction_count(transaction.from.unwrap())
.block_id(prev_block_id)
.await?;
transaction.set_nonce(prev_block_nonce);
if let Some(ref block) = block {
configure_env_block(&mut env, block)
}
if let Some(TxKind::Call(to)) = transaction.kind() {
if to == DEFAULT_CREATE2_DEPLOYER {
let mut input = transaction.input.input.unwrap()[..32].to_vec(); input.extend_from_slice(&local_bytecode_vec);
transaction.input = TransactionInput::both(Bytes::from(input));
executor.deploy_create2_deployer()?;
}
} else {
transaction.input = TransactionInput::both(Bytes::from(local_bytecode_vec));
}
configure_tx_req_env(&mut env, &transaction, None)
.wrap_err("Failed to configure tx request env")?;
let fork_address = crate::utils::deploy_contract(
&mut executor,
&env,
config.evm_spec_id(),
transaction.to,
)?;
let (fork_runtime_code, onchain_runtime_code) = crate::utils::get_runtime_codes(
&mut executor,
&provider,
self.address,
fork_address,
Some(simulation_block),
)
.await?;
let match_type = crate::utils::match_bytecodes(
&fork_runtime_code.original_bytes(),
&onchain_runtime_code,
&constructor_args,
true,
config.bytecode_hash,
);
crate::utils::print_result(
match_type,
BytecodeType::Runtime,
&mut json_results,
etherscan_metadata,
&config,
);
}
if shell::is_json() {
sh_println!("{}", serde_json::to_string(&json_results)?)?;
}
Ok(())
}
}