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_consensus::{BlockHeader, Transaction as ConsensusTransaction};
11use alloy_evm::FromRecoveredTx;
12use alloy_primitives::{Address, Bytes, TxKind, U256, hex};
13use alloy_provider::{
14 Provider,
15 ext::TraceApi,
16 network::{
17 AnyNetwork, BlockResponse, ReceiptResponse, TransactionResponse,
18 primitives::BlockTransactions,
19 },
20};
21use alloy_rpc_types::{
22 BlockId, BlockNumberOrTag,
23 trace::parity::{Action, CreateAction, CreateOutput, TraceOutput},
24};
25use clap::{Parser, ValueHint};
26use eyre::{Context, OptionExt, Result};
27use foundry_cli::{
28 opts::EtherscanOpts,
29 utils::{self, LoadConfig, read_constructor_args_file},
30};
31use foundry_common::{
32 SYSTEM_TRANSACTION_TYPE, is_known_system_sender, provider::ProviderBuilder, shell,
33};
34use foundry_compilers::{artifacts::EvmVersion, info::ContractInfo};
35use foundry_config::{Config, figment, impl_figment_convert};
36#[cfg(feature = "optimism")]
37use foundry_evm::core::evm::OpEvmNetwork;
38use foundry_evm::{
39 constants::DEFAULT_CREATE2_DEPLOYER,
40 core::{
41 FoundryBlock as _, FoundryTransaction as _,
42 evm::{EthEvmNetwork, FoundryEvmNetwork, SpecFor, TempoEvmNetwork, TxEnvFor},
43 },
44 executors::EvmError,
45};
46use foundry_evm_networks::NetworkVariant;
47use revm::{context::Block as _, state::AccountInfo};
48use std::path::PathBuf;
49
50impl_figment_convert!(VerifyBytecodeArgs);
51
52#[derive(Clone, Debug, Parser)]
54pub struct VerifyBytecodeArgs {
55 pub address: Address,
57
58 pub contract: ContractInfo,
60
61 #[arg(long, value_name = "BLOCK")]
63 pub block: Option<BlockId>,
64
65 #[arg(
67 long,
68 num_args(1..),
69 conflicts_with_all = &["constructor_args_path", "encoded_constructor_args"],
70 value_name = "ARGS",
71 )]
72 pub constructor_args: Option<Vec<String>>,
73
74 #[arg(
76 long,
77 conflicts_with_all = &["constructor_args_path", "constructor_args"],
78 value_name = "HEX",
79 )]
80 pub encoded_constructor_args: Option<String>,
81
82 #[arg(
84 long,
85 value_hint = ValueHint::FilePath,
86 value_name = "PATH",
87 conflicts_with_all = &["constructor_args", "encoded_constructor_args"]
88 )]
89 pub constructor_args_path: Option<PathBuf>,
90
91 #[arg(short = 'r', long, value_name = "RPC_URL", env = "ETH_RPC_URL")]
93 pub rpc_url: Option<String>,
94
95 #[arg(long, short, num_args = 1, value_name = "NETWORK")]
97 pub network: Option<NetworkVariant>,
98
99 #[command(flatten)]
101 pub etherscan: EtherscanOpts,
102
103 #[command(flatten)]
105 pub verifier: VerifierArgs,
106
107 #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")]
112 pub root: Option<PathBuf>,
113
114 #[arg(long, value_name = "BYTECODE_TYPE")]
116 pub ignore: Option<BytecodeType>,
117}
118
119impl figment::Provider for VerifyBytecodeArgs {
120 fn metadata(&self) -> figment::Metadata {
121 figment::Metadata::named("Verify Bytecode Provider")
122 }
123
124 fn data(
125 &self,
126 ) -> Result<figment::value::Map<figment::Profile, figment::value::Dict>, figment::Error> {
127 let mut dict = self.etherscan.dict();
128
129 if let Some(api_key) = &self.verifier.verifier_api_key {
130 dict.insert("etherscan_api_key".into(), api_key.as_str().into());
131 }
132
133 if let Some(block) = &self.block {
134 dict.insert("block".into(), figment::value::Value::serialize(block)?);
135 }
136 if let Some(rpc_url) = &self.rpc_url {
137 dict.insert("eth_rpc_url".into(), rpc_url.clone().into());
138 }
139
140 Ok(figment::value::Map::from([(Config::selected_profile(), dict)]))
141 }
142}
143
144impl VerifyBytecodeArgs {
145 fn configured_network(
146 cli_network: Option<NetworkVariant>,
147 config: &Config,
148 ) -> Option<NetworkVariant> {
149 cli_network.or_else(|| config.networks.resolved_network())
150 }
151
152 pub async fn run(self) -> Result<()> {
155 let mut config = self.load_config()?;
156 let network = if let Some(network) = Self::configured_network(self.network, &config) {
157 if self.network.is_some() {
158 config.networks = network.into();
159 }
160 network
161 } else {
162 let network = {
163 let provider = ProviderBuilder::<AnyNetwork>::from_config(&config)?.build()?;
164 provider.get_chain_id().await?.into()
165 };
166
167 if !matches!(network, NetworkVariant::Ethereum) {
168 config.networks = network.into();
169 }
170
171 network
172 };
173
174 match network {
175 NetworkVariant::Ethereum => {
176 self.run_with_network_and_config::<EthEvmNetwork>(config).await
177 }
178 #[cfg(feature = "optimism")]
179 NetworkVariant::Optimism => {
180 self.run_with_network_and_config::<OpEvmNetwork>(config).await
181 }
182 NetworkVariant::Tempo => {
183 self.run_with_network_and_config::<TempoEvmNetwork>(config).await
184 }
185 }
186 }
187
188 async fn run_with_network_and_config<FEN>(mut self, config: Config) -> Result<()>
189 where
190 FEN: FoundryEvmNetwork,
191 {
192 let provider = ProviderBuilder::<FEN::Network>::from_config(&config)?.build()?;
194
195 let chain = match config.get_rpc_url() {
198 Some(_) => utils::get_chain::<FEN::Network, _>(config.chain, &provider).await?,
199 None => config.chain.unwrap_or_default(),
200 };
201
202 self.etherscan.chain = Some(chain);
204 self.etherscan.key = config.get_etherscan_config_with_chain(Some(chain))?.map(|c| c.key);
205
206 let etherscan =
208 EtherscanVerificationProvider.client(&self.etherscan, &self.verifier, &config)?;
209
210 let code = provider.get_code_at(self.address).await?;
212 if code.is_empty() {
213 eyre::bail!("No bytecode found at address {}", self.address);
214 }
215
216 if !shell::is_json() {
217 sh_status!(
218 "Verifying bytecode for contract {} at address {}",
219 self.contract.name,
220 self.address
221 )?;
222 }
223
224 let mut json_results: Vec<JsonResult> = vec![];
225
226 let creation_data = etherscan.contract_creation_data(self.address).await;
228
229 let (creation_data, maybe_predeploy) = maybe_predeploy_contract(creation_data)?;
231
232 trace!(maybe_predeploy = ?maybe_predeploy);
233
234 let source_code = etherscan.contract_source_code(self.address).await?;
236
237 let name = source_code.items.first().map(|item| item.contract_name.clone());
239 if name.as_ref() != Some(&self.contract.name) {
240 eyre::bail!("Contract name mismatch");
241 }
242
243 let etherscan_metadata = source_code.items.first().unwrap();
245
246 let artifact = crate::utils::build_project(&self, &config)?;
248
249 let local_bytecode = artifact
251 .bytecode
252 .as_ref()
253 .and_then(|b| b.to_owned().into_bytes())
254 .ok_or_eyre("Unlinked bytecode is not supported for verification")?;
255
256 let provided_constructor_args = if let Some(path) = self.constructor_args_path.clone() {
258 Some(read_constructor_args_file(path)?)
260 } else {
261 self.constructor_args.clone()
262 }
263 .map(|args| check_and_encode_args(&artifact, args))
264 .transpose()?
265 .or(self.encoded_constructor_args.clone().map(hex::decode).transpose()?);
266
267 let mut constructor_args = if let Some(provided) = provided_constructor_args {
268 provided.into()
269 } else {
270 check_explorer_args(source_code.clone())?
272 };
273
274 crate::utils::check_args_len(&artifact, &constructor_args)?;
277
278 if maybe_predeploy {
279 if !shell::is_json() {
280 sh_warn!(
281 "Attempting to verify predeployed contract at {:?}. Ignoring creation code verification.",
282 self.address
283 )?;
284 }
285
286 trace!(%constructor_args);
288 let mut local_bytecode_vec = local_bytecode.to_vec();
289 local_bytecode_vec.extend_from_slice(&constructor_args);
290
291 let gen_blk_num = 0_u64;
293 let (mut fork_config, evm_opts) = config.clone().load_config_and_evm_opts()?;
294 let (mut evm_env, _, mut executor) = crate::utils::get_tracing_executor::<FEN>(
295 &mut fork_config,
296 gen_blk_num,
297 etherscan_metadata.evm_version()?.unwrap_or(EvmVersion::default()),
298 evm_opts,
299 )
300 .await?;
301
302 evm_env.block_env.set_number(U256::ZERO);
303 let genesis_block = provider.get_block(gen_blk_num.into()).full().await?;
304
305 let deployer = Address::with_last_byte(0x1);
307 let mut tx_env = TxEnvFor::<FEN>::default();
308 tx_env.set_caller(deployer);
309 tx_env.set_kind(TxKind::Create);
310 tx_env.set_data(Bytes::from(local_bytecode_vec));
311 tx_env.set_chain_id(Some(evm_env.cfg_env.chain_id));
312 tx_env.set_gas_limit(evm_env.block_env.gas_limit());
313 tx_env.set_gas_price(evm_env.block_env.basefee() as u128);
314
315 if let Some(ref block) = genesis_block {
316 configure_env_block::<FEN>(&mut evm_env, block, config.networks);
317 tx_env.set_gas_limit(block.header().gas_limit());
318 tx_env.set_gas_price(block.header().base_fee_per_gas().unwrap_or_default() as u128);
319 }
320
321 let kind = TxKind::Create;
322
323 let account_info = AccountInfo {
325 balance: U256::from(100 * 10_u128.pow(18)),
326 nonce: 0,
327 ..Default::default()
328 };
329 executor.backend_mut().insert_account_info(deployer, account_info);
330
331 let fork_address = crate::utils::deploy_contract::<FEN>(
332 &mut executor,
333 &evm_env,
334 &tx_env,
335 config.evm_spec_id::<SpecFor<FEN>>(),
336 kind,
337 )?;
338
339 let (deployed_bytecode, onchain_runtime_code) = crate::utils::get_runtime_codes::<FEN>(
341 &mut executor,
342 &provider,
343 self.address,
344 fork_address,
345 None,
346 )
347 .await?;
348
349 let match_type = crate::utils::match_bytecodes(
350 deployed_bytecode.original_byte_slice(),
351 &onchain_runtime_code,
352 &constructor_args,
353 true,
354 config.bytecode_hash,
355 );
356
357 crate::utils::print_result(
358 match_type,
359 BytecodeType::Runtime,
360 &mut json_results,
361 etherscan_metadata,
362 &config,
363 );
364
365 if shell::is_json() {
366 sh_println!("{}", serde_json::to_string(&json_results)?)?;
367 }
368
369 return Ok(());
370 }
371
372 let creation_data = creation_data.unwrap();
374 trace!(creation_tx_hash = ?creation_data.transaction_hash);
376 let transaction = provider
377 .get_transaction_by_hash(creation_data.transaction_hash)
378 .await
379 .or_else(|e| eyre::bail!("Couldn't fetch transaction from RPC: {:?}", e))?
380 .ok_or_else(|| {
381 eyre::eyre!("Transaction not found for hash {}", creation_data.transaction_hash)
382 })?;
383 let tx_hash = transaction.tx_hash();
384 let receipt = provider
385 .get_transaction_receipt(creation_data.transaction_hash)
386 .await
387 .or_else(|e| eyre::bail!("Couldn't fetch transaction receipt from RPC: {:?}", e))?;
388 let receipt = if let Some(receipt) = receipt {
389 receipt
390 } else {
391 eyre::bail!(
392 "Receipt not found for transaction hash {}",
393 creation_data.transaction_hash
394 );
395 };
396
397 let creation_block = transaction.block_number();
398
399 let maybe_creation_code = if receipt.to().is_none()
401 && receipt.contract_address() == Some(self.address)
402 {
403 transaction.input().clone()
404 } else if receipt.to() == Some(DEFAULT_CREATE2_DEPLOYER) {
405 Bytes::copy_from_slice(&transaction.input()[32..])
406 } else {
407 let traces = provider
409 .trace_transaction(creation_data.transaction_hash)
410 .await
411 .unwrap_or_default();
412
413 let creation_bytecode =
414 traces.iter().find_map(|trace| match (&trace.trace.result, &trace.trace.action) {
415 (
416 Some(TraceOutput::Create(CreateOutput { address, .. })),
417 Action::Create(CreateAction { init, .. }),
418 ) if *address == self.address => Some(init.clone()),
419 _ => None,
420 });
421
422 creation_bytecode.ok_or_else(|| {
423 eyre::eyre!(
424 "Could not extract the creation code for contract at address {}",
425 self.address
426 )
427 })?
428 };
429
430 if !maybe_creation_code.ends_with(&constructor_args) {
433 trace!("mismatch of constructor args with etherscan");
434 if maybe_creation_code.len() >= local_bytecode.len() {
436 constructor_args =
437 Bytes::copy_from_slice(&maybe_creation_code[local_bytecode.len()..]);
438 trace!(
439 target: "forge::verify",
440 "setting constructor args to latest {} bytes of bytecode",
441 constructor_args.len()
442 );
443 }
444 }
445
446 trace!(%constructor_args);
448 let mut local_bytecode_vec = local_bytecode.to_vec();
449 local_bytecode_vec.extend_from_slice(&constructor_args);
450
451 trace!(ignore = ?self.ignore);
452 if self.ignore.is_none_or(|b| !b.is_creation()) {
454 let match_type = crate::utils::match_bytecodes(
456 local_bytecode_vec.as_slice(),
457 &maybe_creation_code,
458 &constructor_args,
459 false,
460 config.bytecode_hash,
461 );
462
463 crate::utils::print_result(
464 match_type,
465 BytecodeType::Creation,
466 &mut json_results,
467 etherscan_metadata,
468 &config,
469 );
470
471 if match_type.is_none() {
473 crate::utils::print_result(
474 None,
475 BytecodeType::Runtime,
476 &mut json_results,
477 etherscan_metadata,
478 &config,
479 );
480 if shell::is_json() {
481 sh_println!("{}", serde_json::to_string(&json_results)?)?;
482 }
483 return Ok(());
484 }
485 }
486
487 if self.ignore.is_none_or(|b| !b.is_runtime()) {
488 let simulation_block = match self.block {
490 Some(BlockId::Number(BlockNumberOrTag::Number(block))) => block,
491 Some(_) => eyre::bail!("Invalid block number"),
492 None => {
493 creation_block.ok_or_else(|| {
494 eyre::eyre!("Failed to get block number of the contract creation tx, specify using the --block flag")
495 })?
496 }
497 };
498
499 let (mut fork_config, evm_opts) = config.clone().load_config_and_evm_opts()?;
501 let (mut evm_env, _tx_env, mut executor) = crate::utils::get_tracing_executor::<FEN>(
502 &mut fork_config,
503 simulation_block - 1, etherscan_metadata.evm_version()?.unwrap_or(EvmVersion::default()),
505 evm_opts,
506 )
507 .await?;
508 evm_env.block_env.set_number(U256::from(simulation_block));
509 let block = provider.get_block(simulation_block.into()).full().await?;
510
511 let prev_block_id = BlockId::number(simulation_block - 1);
514
515 let prev_block_nonce =
518 provider.get_transaction_count(transaction.from()).block_id(prev_block_id).await?;
519
520 if let Some(ref block) = block {
521 configure_env_block::<FEN>(&mut evm_env, block, config.networks);
522
523 let BlockTransactions::Full(txs) = block.transactions() else {
524 return Err(eyre::eyre!("Could not get block txs"));
525 };
526
527 for tx in txs {
529 trace!("replay tx::: {}", tx.tx_hash());
530 if is_known_system_sender(tx.from())
531 || tx.transaction_type() == Some(SYSTEM_TRANSACTION_TYPE)
532 {
533 continue;
534 }
535 if tx.tx_hash() == tx_hash {
536 break;
537 }
538
539 let tx_env = TxEnvFor::<FEN>::from_recovered_tx(tx.as_ref(), tx.from());
540
541 if ConsensusTransaction::to(tx).is_some() {
542 executor.transact_with_env(evm_env.clone(), tx_env.clone()).wrap_err_with(
543 || {
544 format!(
545 "Failed to execute transaction: {:?} in block {}",
546 tx.tx_hash(),
547 evm_env.block_env.number()
548 )
549 },
550 )?;
551 } else if let Err(error) =
552 executor.deploy_with_env(evm_env.clone(), tx_env.clone(), None)
553 {
554 match error {
555 EvmError::Execution(_) => (),
557 error => {
558 return Err(error).wrap_err_with(|| {
559 format!(
560 "Failed to deploy transaction: {:?} in block {}",
561 tx.tx_hash(),
562 evm_env.block_env.number()
563 )
564 });
565 }
566 }
567 }
568 }
569 }
570
571 let kind = ConsensusTransaction::kind(&transaction);
572 let mut tx_env =
573 TxEnvFor::<FEN>::from_recovered_tx(transaction.as_ref(), transaction.from());
574 tx_env.set_nonce(prev_block_nonce);
575
576 if let TxKind::Call(to) = kind {
578 if to == DEFAULT_CREATE2_DEPLOYER {
579 let mut input = transaction.input()[..32].to_vec(); input.extend_from_slice(&local_bytecode_vec);
581 tx_env.set_data(Bytes::from(input));
582
583 executor.deploy_create2_deployer()?;
585 }
586 } else {
587 tx_env.set_data(Bytes::from(local_bytecode_vec));
588 }
589
590 let fork_address = crate::utils::deploy_contract::<FEN>(
591 &mut executor,
592 &evm_env,
593 &tx_env,
594 config.evm_spec_id::<SpecFor<FEN>>(),
595 kind,
596 )?;
597
598 let (fork_runtime_code, onchain_runtime_code) = crate::utils::get_runtime_codes::<FEN>(
600 &mut executor,
601 &provider,
602 self.address,
603 fork_address,
604 Some(simulation_block),
605 )
606 .await?;
607
608 let match_type = crate::utils::match_bytecodes(
610 fork_runtime_code.original_byte_slice(),
611 &onchain_runtime_code,
612 &constructor_args,
613 true,
614 config.bytecode_hash,
615 );
616
617 crate::utils::print_result(
618 match_type,
619 BytecodeType::Runtime,
620 &mut json_results,
621 etherscan_metadata,
622 &config,
623 );
624 }
625
626 if shell::is_json() {
627 sh_println!("{}", serde_json::to_string(&json_results)?)?;
628 }
629 Ok(())
630 }
631}
632
633#[cfg(test)]
634mod tests {
635 use super::*;
636
637 #[test]
638 fn can_parse_network() {
639 let args = VerifyBytecodeArgs::parse_from([
640 "foundry-cli",
641 "0x0000000000000000000000000000000000000000",
642 "src/Counter.sol:Counter",
643 "--network",
644 "tempo",
645 ]);
646
647 assert_eq!(args.network, Some(NetworkVariant::Tempo));
648 }
649
650 #[test]
651 fn configured_network_uses_config_network() {
652 let config = Config { networks: NetworkVariant::Tempo.into(), ..Default::default() };
653
654 assert_eq!(
655 VerifyBytecodeArgs::configured_network(None, &config),
656 Some(NetworkVariant::Tempo)
657 );
658 }
659
660 #[test]
661 fn configured_network_prefers_cli_network() {
662 let config = Config { networks: NetworkVariant::Tempo.into(), ..Default::default() };
663
664 assert_eq!(
665 VerifyBytecodeArgs::configured_network(Some(NetworkVariant::Ethereum), &config),
666 Some(NetworkVariant::Ethereum)
667 );
668 }
669}