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