1use crate::{
3 etherscan::EtherscanVerificationProvider,
4 utils::{
5 check_and_encode_args, check_explorer_args, configure_env_block, maybe_predeploy_contract,
6 BytecodeType, JsonResult,
7 },
8 verify::VerifierArgs,
9};
10use alloy_primitives::{hex, Address, Bytes, TxKind, U256};
11use alloy_provider::{
12 ext::TraceApi,
13 network::{AnyTxEnvelope, TransactionBuilder},
14 Provider,
15};
16use alloy_rpc_types::{
17 trace::parity::{Action, CreateAction, CreateOutput, TraceOutput},
18 BlockId, BlockNumberOrTag, TransactionInput, TransactionRequest,
19};
20use clap::{Parser, ValueHint};
21use eyre::{Context, OptionExt, Result};
22use foundry_cli::{
23 opts::EtherscanOpts,
24 utils::{self, read_constructor_args_file, LoadConfig},
25};
26use foundry_common::shell;
27use foundry_compilers::{artifacts::EvmVersion, info::ContractInfo};
28use foundry_config::{figment, impl_figment_convert, Config};
29use foundry_evm::{constants::DEFAULT_CREATE2_DEPLOYER, utils::configure_tx_req_env};
30use foundry_evm_core::AsEnvMut;
31use revm::state::AccountInfo;
32use std::path::PathBuf;
33
34impl_figment_convert!(VerifyBytecodeArgs);
35
36#[derive(Clone, Debug, Parser)]
38pub struct VerifyBytecodeArgs {
39 pub address: Address,
41
42 pub contract: ContractInfo,
44
45 #[arg(long, value_name = "BLOCK")]
47 pub block: Option<BlockId>,
48
49 #[arg(
51 long,
52 num_args(1..),
53 conflicts_with_all = &["constructor_args_path", "encoded_constructor_args"],
54 value_name = "ARGS",
55 )]
56 pub constructor_args: Option<Vec<String>>,
57
58 #[arg(
60 long,
61 conflicts_with_all = &["constructor_args_path", "constructor_args"],
62 value_name = "HEX",
63 )]
64 pub encoded_constructor_args: Option<String>,
65
66 #[arg(
68 long,
69 value_hint = ValueHint::FilePath,
70 value_name = "PATH",
71 conflicts_with_all = &["constructor_args", "encoded_constructor_args"]
72 )]
73 pub constructor_args_path: Option<PathBuf>,
74
75 #[arg(short = 'r', long, value_name = "RPC_URL", env = "ETH_RPC_URL")]
77 pub rpc_url: Option<String>,
78
79 #[command(flatten)]
81 pub etherscan: EtherscanOpts,
82
83 #[command(flatten)]
85 pub verifier: VerifierArgs,
86
87 #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")]
92 pub root: Option<PathBuf>,
93
94 #[arg(long, value_name = "BYTECODE_TYPE")]
96 pub ignore: Option<BytecodeType>,
97}
98
99impl figment::Provider for VerifyBytecodeArgs {
100 fn metadata(&self) -> figment::Metadata {
101 figment::Metadata::named("Verify Bytecode Provider")
102 }
103
104 fn data(
105 &self,
106 ) -> Result<figment::value::Map<figment::Profile, figment::value::Dict>, figment::Error> {
107 let mut dict = self.etherscan.dict();
108
109 if let Some(api_key) = &self.verifier.verifier_api_key {
110 dict.insert("etherscan_api_key".into(), api_key.as_str().into());
111 }
112
113 if let Some(api_version) = &self.verifier.verifier_api_version {
114 dict.insert("etherscan_api_version".into(), api_version.to_string().into());
115 }
116
117 if let Some(block) = &self.block {
118 dict.insert("block".into(), figment::value::Value::serialize(block)?);
119 }
120 if let Some(rpc_url) = &self.rpc_url {
121 dict.insert("eth_rpc_url".into(), rpc_url.to_string().into());
122 }
123
124 Ok(figment::value::Map::from([(Config::selected_profile(), dict)]))
125 }
126}
127
128impl VerifyBytecodeArgs {
129 pub async fn run(mut self) -> Result<()> {
132 let config = self.load_config()?;
134 let provider = utils::get_provider(&config)?;
135
136 let chain = match config.get_rpc_url() {
139 Some(_) => utils::get_chain(config.chain, &provider).await?,
140 None => config.chain.unwrap_or_default(),
141 };
142
143 self.etherscan.chain = Some(chain);
145 self.etherscan.key = config.get_etherscan_config_with_chain(Some(chain))?.map(|c| c.key);
146
147 let etherscan =
149 EtherscanVerificationProvider.client(&self.etherscan, &self.verifier, &config)?;
150
151 let code = provider.get_code_at(self.address).await?;
153 if code.is_empty() {
154 eyre::bail!("No bytecode found at address {}", self.address);
155 }
156
157 if !shell::is_json() {
158 sh_println!(
159 "Verifying bytecode for contract {} at address {}",
160 self.contract.name,
161 self.address
162 )?;
163 }
164
165 let mut json_results: Vec<JsonResult> = vec![];
166
167 let creation_data = etherscan.contract_creation_data(self.address).await;
169
170 let (creation_data, maybe_predeploy) = maybe_predeploy_contract(creation_data)?;
172
173 trace!(maybe_predeploy = ?maybe_predeploy);
174
175 let source_code = etherscan.contract_source_code(self.address).await?;
177
178 let name = source_code.items.first().map(|item| item.contract_name.to_owned());
180 if name.as_ref() != Some(&self.contract.name) {
181 eyre::bail!("Contract name mismatch");
182 }
183
184 let etherscan_metadata = source_code.items.first().unwrap();
186
187 let artifact = if let Ok(local_bytecode) =
189 crate::utils::build_using_cache(&self, etherscan_metadata, &config)
190 {
191 trace!("using cache");
192 local_bytecode
193 } else {
194 crate::utils::build_project(&self, &config)?
195 };
196
197 let local_bytecode = artifact
199 .bytecode
200 .as_ref()
201 .and_then(|b| b.to_owned().into_bytes())
202 .ok_or_eyre("Unlinked bytecode is not supported for verification")?;
203
204 let provided_constructor_args = if let Some(path) = self.constructor_args_path.to_owned() {
206 Some(read_constructor_args_file(path)?)
208 } else {
209 self.constructor_args.to_owned()
210 }
211 .map(|args| check_and_encode_args(&artifact, args))
212 .transpose()?
213 .or(self.encoded_constructor_args.to_owned().map(hex::decode).transpose()?);
214
215 let mut constructor_args = if let Some(provided) = provided_constructor_args {
216 provided.into()
217 } else {
218 check_explorer_args(source_code.clone())?
220 };
221
222 crate::utils::check_args_len(&artifact, &constructor_args)?;
225
226 if maybe_predeploy {
227 if !shell::is_json() {
228 sh_warn!(
229 "Attempting to verify predeployed contract at {:?}. Ignoring creation code verification.",
230 self.address
231 )?;
232 }
233
234 trace!(%constructor_args);
236 let mut local_bytecode_vec = local_bytecode.to_vec();
237 local_bytecode_vec.extend_from_slice(&constructor_args);
238
239 let gen_blk_num = 0_u64;
241 let (mut fork_config, evm_opts) = config.clone().load_config_and_evm_opts()?;
242 let (mut env, mut executor) = crate::utils::get_tracing_executor(
243 &mut fork_config,
244 gen_blk_num,
245 etherscan_metadata.evm_version()?.unwrap_or(EvmVersion::default()),
246 evm_opts,
247 )
248 .await?;
249
250 env.evm_env.block_env.number = 0;
251 let genesis_block = provider.get_block(gen_blk_num.into()).full().await?;
252
253 let deployer = Address::with_last_byte(0x1);
255 let mut gen_tx_req = TransactionRequest::default()
256 .with_from(deployer)
257 .with_input(Bytes::from(local_bytecode_vec))
258 .into_create();
259
260 if let Some(ref block) = genesis_block {
261 configure_env_block(&mut env.as_env_mut(), block);
262 gen_tx_req.max_fee_per_gas = block.header.base_fee_per_gas.map(|g| g as u128);
263 gen_tx_req.gas = Some(block.header.gas_limit);
264 gen_tx_req.gas_price = block.header.base_fee_per_gas.map(|g| g as u128);
265 }
266
267 configure_tx_req_env(&mut env.as_env_mut(), &gen_tx_req, None)
268 .wrap_err("Failed to configure tx request env")?;
269
270 let account_info = AccountInfo {
272 balance: U256::from(100 * 10_u128.pow(18)),
273 nonce: 0,
274 ..Default::default()
275 };
276 executor.backend_mut().insert_account_info(deployer, account_info);
277
278 let fork_address = crate::utils::deploy_contract(
279 &mut executor,
280 &env,
281 config.evm_spec_id(),
282 gen_tx_req.to,
283 )?;
284
285 let (deployed_bytecode, onchain_runtime_code) = crate::utils::get_runtime_codes(
287 &mut executor,
288 &provider,
289 self.address,
290 fork_address,
291 None,
292 )
293 .await?;
294
295 let match_type = crate::utils::match_bytecodes(
296 deployed_bytecode.original_byte_slice(),
297 &onchain_runtime_code,
298 &constructor_args,
299 true,
300 config.bytecode_hash,
301 );
302
303 crate::utils::print_result(
304 match_type,
305 BytecodeType::Runtime,
306 &mut json_results,
307 etherscan_metadata,
308 &config,
309 );
310
311 if shell::is_json() {
312 sh_println!("{}", serde_json::to_string(&json_results)?)?;
313 }
314
315 return Ok(());
316 }
317
318 let creation_data = creation_data.unwrap();
320 trace!(creation_tx_hash = ?creation_data.transaction_hash);
322 let transaction = provider
323 .get_transaction_by_hash(creation_data.transaction_hash)
324 .await
325 .or_else(|e| eyre::bail!("Couldn't fetch transaction from RPC: {:?}", e))?
326 .ok_or_else(|| {
327 eyre::eyre!("Transaction not found for hash {}", creation_data.transaction_hash)
328 })?;
329 let receipt = provider
330 .get_transaction_receipt(creation_data.transaction_hash)
331 .await
332 .or_else(|e| eyre::bail!("Couldn't fetch transaction receipt from RPC: {:?}", e))?;
333 let receipt = if let Some(receipt) = receipt {
334 receipt
335 } else {
336 eyre::bail!(
337 "Receipt not found for transaction hash {}",
338 creation_data.transaction_hash
339 );
340 };
341
342 let mut transaction: TransactionRequest = match transaction.inner.inner.inner() {
343 AnyTxEnvelope::Ethereum(tx) => tx.clone().into(),
344 AnyTxEnvelope::Unknown(_) => unreachable!("Unknown transaction type"),
345 };
346
347 let maybe_creation_code = if receipt.to.is_none() &&
349 receipt.contract_address == Some(self.address)
350 {
351 match &transaction.input.input {
352 Some(input) => &input[..],
353 None => unreachable!("creation tx input is None"),
354 }
355 } else if receipt.to == Some(DEFAULT_CREATE2_DEPLOYER) {
356 match &transaction.input.input {
357 Some(input) => &input[32..],
358 None => unreachable!("creation tx input is None"),
359 }
360 } else {
361 let traces = provider
363 .trace_transaction(creation_data.transaction_hash)
364 .await
365 .unwrap_or_default();
366
367 let creation_bytecode =
368 traces.iter().find_map(|trace| match (&trace.trace.result, &trace.trace.action) {
369 (
370 Some(TraceOutput::Create(CreateOutput { address, .. })),
371 Action::Create(CreateAction { init, .. }),
372 ) if *address == self.address => Some(init.clone()),
373 _ => None,
374 });
375
376 &creation_bytecode.ok_or_else(|| {
377 eyre::eyre!(
378 "Could not extract the creation code for contract at address {}",
379 self.address
380 )
381 })?
382 };
383
384 if !maybe_creation_code.ends_with(&constructor_args) {
387 trace!("mismatch of constructor args with etherscan");
388 if maybe_creation_code.len() >= local_bytecode.len() {
390 constructor_args =
391 Bytes::copy_from_slice(&maybe_creation_code[local_bytecode.len()..]);
392 trace!(
393 target: "forge::verify",
394 "setting constructor args to latest {} bytes of bytecode",
395 constructor_args.len()
396 );
397 }
398 }
399
400 trace!(%constructor_args);
402 let mut local_bytecode_vec = local_bytecode.to_vec();
403 local_bytecode_vec.extend_from_slice(&constructor_args);
404
405 trace!(ignore = ?self.ignore);
406 if !self.ignore.is_some_and(|b| b.is_creation()) {
408 let match_type = crate::utils::match_bytecodes(
410 local_bytecode_vec.as_slice(),
411 maybe_creation_code,
412 &constructor_args,
413 false,
414 config.bytecode_hash,
415 );
416
417 crate::utils::print_result(
418 match_type,
419 BytecodeType::Creation,
420 &mut json_results,
421 etherscan_metadata,
422 &config,
423 );
424
425 if match_type.is_none() {
427 crate::utils::print_result(
428 None,
429 BytecodeType::Runtime,
430 &mut json_results,
431 etherscan_metadata,
432 &config,
433 );
434 if shell::is_json() {
435 sh_println!("{}", serde_json::to_string(&json_results)?)?;
436 }
437 return Ok(());
438 }
439 }
440
441 if !self.ignore.is_some_and(|b| b.is_runtime()) {
442 let simulation_block = match self.block {
444 Some(BlockId::Number(BlockNumberOrTag::Number(block))) => block,
445 Some(_) => eyre::bail!("Invalid block number"),
446 None => {
447 let provider = utils::get_provider(&config)?;
448 provider
449 .get_transaction_by_hash(creation_data.transaction_hash)
450 .await.or_else(|e| eyre::bail!("Couldn't fetch transaction from RPC: {:?}", e))?.ok_or_else(|| {
451 eyre::eyre!("Transaction not found for hash {}", creation_data.transaction_hash)
452 })?
453 .block_number.ok_or_else(|| {
454 eyre::eyre!("Failed to get block number of the contract creation tx, specify using the --block flag")
455 })?
456 }
457 };
458
459 let (mut fork_config, evm_opts) = config.clone().load_config_and_evm_opts()?;
461 let (mut env, mut executor) = crate::utils::get_tracing_executor(
462 &mut fork_config,
463 simulation_block - 1, etherscan_metadata.evm_version()?.unwrap_or(EvmVersion::default()),
465 evm_opts,
466 )
467 .await?;
468 env.evm_env.block_env.number = simulation_block;
469 let block = provider.get_block(simulation_block.into()).full().await?;
470
471 let prev_block_id = BlockId::number(simulation_block - 1);
474
475 let prev_block_nonce = provider
478 .get_transaction_count(transaction.from.unwrap())
479 .block_id(prev_block_id)
480 .await?;
481 transaction.set_nonce(prev_block_nonce);
482
483 if let Some(ref block) = block {
484 configure_env_block(&mut env.as_env_mut(), block)
485 }
486
487 if let Some(TxKind::Call(to)) = transaction.kind() {
489 if to == DEFAULT_CREATE2_DEPLOYER {
490 let mut input = transaction.input.input.unwrap()[..32].to_vec(); input.extend_from_slice(&local_bytecode_vec);
492 transaction.input = TransactionInput::both(Bytes::from(input));
493
494 executor.deploy_create2_deployer()?;
496 }
497 } else {
498 transaction.input = TransactionInput::both(Bytes::from(local_bytecode_vec));
499 }
500
501 configure_tx_req_env(&mut env.as_env_mut(), &transaction, None)
503 .wrap_err("Failed to configure tx request env")?;
504
505 let fork_address = crate::utils::deploy_contract(
506 &mut executor,
507 &env,
508 config.evm_spec_id(),
509 transaction.to,
510 )?;
511
512 let (fork_runtime_code, onchain_runtime_code) = crate::utils::get_runtime_codes(
514 &mut executor,
515 &provider,
516 self.address,
517 fork_address,
518 Some(simulation_block),
519 )
520 .await?;
521
522 let match_type = crate::utils::match_bytecodes(
524 fork_runtime_code.original_byte_slice(),
525 &onchain_runtime_code,
526 &constructor_args,
527 true,
528 config.bytecode_hash,
529 );
530
531 crate::utils::print_result(
532 match_type,
533 BytecodeType::Runtime,
534 &mut json_results,
535 etherscan_metadata,
536 &config,
537 );
538 }
539
540 if shell::is_json() {
541 sh_println!("{}", serde_json::to_string(&json_results)?)?;
542 }
543 Ok(())
544 }
545}