1use crate::{
3 etherscan::EtherscanVerificationProvider,
4 utils::{
5 BytecodeType, JsonResult, check_and_encode_args, check_explorer_args, configure_env_block,
6 load_fork_config_and_evm_opts, 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::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 has_explorer_config = self.verifier.verifier.is_some()
210 || self.verifier.verifier_url.is_some()
211 || self.verifier.verifier_api_key.is_some()
212 || self.etherscan.key.is_some();
213
214 let etherscan = match EtherscanVerificationProvider.client(
217 &self.etherscan,
218 &self.verifier,
219 &config,
220 ) {
221 Ok(client) => Some(client),
222 Err(err) => {
223 if has_explorer_config {
224 return Err(err);
225 }
226 if !shell::is_json() {
227 sh_warn!(
228 "Failed to create a block explorer client: {err}. Continuing with the local project configuration."
229 )?;
230 }
231 None
232 }
233 };
234
235 let code = provider.get_code_at(self.address).await?;
237 if code.is_empty() {
238 eyre::bail!("No bytecode found at address {}", self.address);
239 }
240
241 if !shell::is_json() {
242 sh_status!(
243 "Verifying bytecode for contract {} at address {}",
244 self.contract.name,
245 self.address
246 )?;
247 }
248
249 let mut json_results: Vec<JsonResult> = vec![];
250
251 let (creation_data, maybe_predeploy) = match ðerscan {
256 Some(etherscan) => {
257 let creation_data = etherscan.contract_creation_data(self.address).await;
258
259 match maybe_predeploy_contract(creation_data) {
261 Ok(res) => res,
262 Err(err) => {
263 if has_explorer_config {
264 return Err(err);
265 }
266 if !shell::is_json() {
267 sh_warn!(
268 "Failed to fetch creation data from the block explorer: {err}"
269 )?;
270 }
271 (None, false)
272 }
273 }
274 }
275 None => (None, false),
276 };
277
278 trace!(maybe_predeploy = ?maybe_predeploy);
279
280 let source_code = match ðerscan {
282 Some(etherscan) => match etherscan.contract_source_code(self.address).await {
283 Ok(source_code) => {
284 if let Some(metadata) = source_code.items.first() {
285 if metadata.contract_name != self.contract.name {
287 eyre::bail!("Contract name mismatch");
288 }
289 Some(source_code)
290 } else {
291 if !shell::is_json() {
292 sh_warn!(
293 "Block explorer returned no source metadata. Continuing with the local project configuration; compiler settings mismatches will not be reported."
294 )?;
295 }
296 None
297 }
298 }
299 Err(err) => {
300 if has_explorer_config {
301 return Err(err.into());
302 }
303 if !shell::is_json() {
304 sh_warn!(
305 "Failed to fetch contract source code from the block explorer: {err}. Continuing with the local project configuration; compiler settings mismatches will not be reported."
306 )?;
307 }
308 None
309 }
310 },
311 None => None,
312 };
313
314 let etherscan_metadata = source_code.as_ref().and_then(|source| source.items.first());
316
317 let evm_version = match etherscan_metadata {
320 Some(metadata) => metadata.evm_version()?.unwrap_or_default(),
321 None => config.evm_version,
322 };
323
324 let artifact = crate::utils::build_project(&self, &config)?;
326
327 let local_bytecode = artifact
329 .bytecode
330 .as_ref()
331 .and_then(|b| b.to_owned().into_bytes())
332 .ok_or_eyre("Unlinked bytecode is not supported for verification")?;
333
334 let provided_constructor_args = if let Some(path) = self.constructor_args_path.clone() {
336 Some(read_constructor_args_file(path)?)
338 } else {
339 self.constructor_args.clone()
340 }
341 .map(|args| check_and_encode_args(&artifact, args))
342 .transpose()?
343 .or(self.encoded_constructor_args.clone().map(hex::decode).transpose()?);
344
345 let mut constructor_args = if let Some(provided) = provided_constructor_args {
346 provided.into()
347 } else if let Some(source_code) = &source_code {
348 check_explorer_args(source_code)?
350 } else {
351 Bytes::new()
352 };
353
354 crate::utils::check_args_len(&artifact, &constructor_args)?;
357
358 if creation_data.is_none() {
362 if !shell::is_json() {
363 if maybe_predeploy {
364 sh_warn!(
365 "Attempting to verify predeployed contract at {:?}. Ignoring creation code verification.",
366 self.address
367 )?;
368 } else {
369 sh_warn!("Creation data is unavailable. Ignoring creation code verification.")?;
370 }
371 }
372
373 if self.ignore.is_some_and(|b| b.is_runtime()) {
376 if shell::is_json() {
377 sh_println!("{}", serde_json::to_string(&json_results)?)?;
378 }
379 return Ok(());
380 }
381
382 let deploy_block = if maybe_predeploy {
383 0_u64
385 } else {
386 match self.block {
387 Some(BlockId::Number(BlockNumberOrTag::Number(block))) => block,
388 Some(_) => {
389 eyre::bail!("Invalid block number");
390 }
391 None => provider.get_block_number().await?,
392 }
393 };
394
395 trace!(%constructor_args);
397 let mut local_bytecode_vec = local_bytecode.to_vec();
398 local_bytecode_vec.extend_from_slice(&constructor_args);
399
400 let (mut fork_config, evm_opts) = load_fork_config_and_evm_opts(&config)?;
401 let (mut evm_env, _, mut executor) = crate::utils::get_tracing_executor::<FEN>(
402 &mut fork_config,
403 deploy_block,
404 evm_version,
405 evm_opts,
406 )
407 .await?;
408
409 evm_env.block_env.set_number(U256::from(deploy_block));
410 let deploy_block_info = provider.get_block(deploy_block.into()).full().await?;
411
412 let deployer = Address::with_last_byte(0x1);
414 let mut tx_env = TxEnvFor::<FEN>::default();
415 tx_env.set_caller(deployer);
416 tx_env.set_kind(TxKind::Create);
417 tx_env.set_data(Bytes::from(local_bytecode_vec));
418 tx_env.set_chain_id(Some(evm_env.cfg_env.chain_id));
419 tx_env.set_gas_limit(evm_env.block_env.gas_limit());
420 tx_env.set_gas_price(evm_env.block_env.basefee() as u128);
421
422 if let Some(ref block) = deploy_block_info {
423 configure_env_block::<FEN>(&mut evm_env, block, config.networks);
424 tx_env.set_gas_limit(block.header().gas_limit());
425 tx_env.set_gas_price(block.header().base_fee_per_gas().unwrap_or_default() as u128);
426 }
427
428 let kind = TxKind::Create;
429
430 let account_info = AccountInfo {
432 balance: U256::from(100 * 10_u128.pow(18)),
433 nonce: 0,
434 ..Default::default()
435 };
436 executor.backend_mut().insert_account_info(deployer, account_info);
437
438 let fork_address = crate::utils::deploy_contract::<FEN>(
439 &mut executor,
440 &evm_env,
441 &tx_env,
442 config.evm_spec_id::<SpecFor<FEN>>(),
443 kind,
444 )?;
445
446 let (deployed_bytecode, onchain_runtime_code) = crate::utils::get_runtime_codes::<FEN>(
450 &mut executor,
451 &provider,
452 self.address,
453 fork_address,
454 (!maybe_predeploy).then_some(deploy_block),
455 )
456 .await?;
457
458 let match_type = crate::utils::match_bytecodes(
459 deployed_bytecode.original_byte_slice(),
460 &onchain_runtime_code,
461 &constructor_args,
462 true,
463 config.bytecode_hash,
464 );
465
466 crate::utils::print_result(
467 match_type,
468 BytecodeType::Runtime,
469 &mut json_results,
470 etherscan_metadata,
471 &config,
472 );
473
474 if shell::is_json() {
475 sh_println!("{}", serde_json::to_string(&json_results)?)?;
476 }
477
478 return Ok(());
479 }
480
481 let creation_data = creation_data.unwrap();
483 trace!(creation_tx_hash = ?creation_data.transaction_hash);
485 let transaction = provider
486 .get_transaction_by_hash(creation_data.transaction_hash)
487 .await
488 .or_else(|e| {
489 eyre::bail!("Couldn't fetch transaction from RPC: {:?}", e);
490 })?
491 .ok_or_else(|| {
492 eyre::eyre!("Transaction not found for hash {}", creation_data.transaction_hash)
493 })?;
494 let tx_hash = transaction.tx_hash();
495 let receipt = provider
496 .get_transaction_receipt(creation_data.transaction_hash)
497 .await
498 .or_else(|e| {
499 eyre::bail!("Couldn't fetch transaction receipt from RPC: {:?}", e);
500 })?;
501 let receipt = if let Some(receipt) = receipt {
502 receipt
503 } else {
504 eyre::bail!(
505 "Receipt not found for transaction hash {}",
506 creation_data.transaction_hash
507 );
508 };
509
510 let creation_block = transaction.block_number();
511
512 let maybe_creation_code = if receipt.to().is_none()
514 && receipt.contract_address() == Some(self.address)
515 {
516 transaction.input().clone()
517 } else if receipt.to() == Some(DEFAULT_CREATE2_DEPLOYER) {
518 Bytes::copy_from_slice(&transaction.input()[32..])
519 } else {
520 let traces = provider
522 .trace_transaction(creation_data.transaction_hash)
523 .await
524 .unwrap_or_default();
525
526 let creation_bytecode =
527 traces.iter().find_map(|trace| match (&trace.trace.result, &trace.trace.action) {
528 (
529 Some(TraceOutput::Create(CreateOutput { address, .. })),
530 Action::Create(CreateAction { init, .. }),
531 ) if *address == self.address => Some(init.clone()),
532 _ => None,
533 });
534
535 creation_bytecode.ok_or_else(|| {
536 eyre::eyre!(
537 "Could not extract the creation code for contract at address {}",
538 self.address
539 )
540 })?
541 };
542
543 if !maybe_creation_code.ends_with(&constructor_args) {
546 trace!("mismatch of constructor args with etherscan");
547 if maybe_creation_code.len() >= local_bytecode.len() {
549 constructor_args =
550 Bytes::copy_from_slice(&maybe_creation_code[local_bytecode.len()..]);
551 trace!(
552 target: "forge::verify",
553 "setting constructor args to latest {} bytes of bytecode",
554 constructor_args.len()
555 );
556 }
557 }
558
559 trace!(%constructor_args);
561 let mut local_bytecode_vec = local_bytecode.to_vec();
562 local_bytecode_vec.extend_from_slice(&constructor_args);
563
564 trace!(ignore = ?self.ignore);
565 if self.ignore.is_none_or(|b| !b.is_creation()) {
567 let match_type = crate::utils::match_bytecodes(
569 local_bytecode_vec.as_slice(),
570 &maybe_creation_code,
571 &constructor_args,
572 false,
573 config.bytecode_hash,
574 );
575
576 crate::utils::print_result(
577 match_type,
578 BytecodeType::Creation,
579 &mut json_results,
580 etherscan_metadata,
581 &config,
582 );
583
584 if match_type.is_none() {
586 crate::utils::print_result(
587 None,
588 BytecodeType::Runtime,
589 &mut json_results,
590 etherscan_metadata,
591 &config,
592 );
593 if shell::is_json() {
594 sh_println!("{}", serde_json::to_string(&json_results)?)?;
595 }
596 return Ok(());
597 }
598 }
599
600 if self.ignore.is_none_or(|b| !b.is_runtime()) {
601 if let TxKind::Call(to) = ConsensusTransaction::kind(&transaction)
604 && to != DEFAULT_CREATE2_DEPLOYER
605 {
606 let message = format!(
607 "Runtime bytecode verification is not supported for this contract: its \
608 creation transaction calls custom factory {to}. forge can only verify \
609 runtime bytecode for direct CREATE transactions and calls to the default \
610 CREATE2 deployer; skipping runtime bytecode verification."
611 );
612 if shell::is_json() {
613 json_results.push(JsonResult {
614 bytecode_type: BytecodeType::Runtime,
615 match_type: None,
616 message: Some(message),
617 });
618 sh_println!("{}", serde_json::to_string(&json_results)?)?;
619 } else {
620 sh_warn!("{message}")?;
621 }
622 return Ok(());
623 }
624
625 let simulation_block = match self.block {
627 Some(BlockId::Number(BlockNumberOrTag::Number(block))) => block,
628 Some(_) => { eyre::bail!("Invalid block number"); },
629 None => {
630 creation_block.ok_or_else(|| {
631 eyre::eyre!("Failed to get block number of the contract creation tx, specify using the --block flag")
632 })?
633 }
634 };
635
636 let (mut fork_config, evm_opts) = load_fork_config_and_evm_opts(&config)?;
638 let (mut evm_env, _tx_env, mut executor) = crate::utils::get_tracing_executor::<FEN>(
639 &mut fork_config,
640 simulation_block - 1, evm_version,
642 evm_opts,
643 )
644 .await?;
645 evm_env.block_env.set_number(U256::from(simulation_block));
646 let block = provider.get_block(simulation_block.into()).full().await?;
647
648 let prev_block_id = BlockId::number(simulation_block - 1);
651
652 let prev_block_nonce =
655 provider.get_transaction_count(transaction.from()).block_id(prev_block_id).await?;
656
657 if let Some(ref block) = block {
658 configure_env_block::<FEN>(&mut evm_env, block, config.networks);
659
660 let BlockTransactions::Full(txs) = block.transactions() else {
661 return Err(eyre::eyre!("Could not get block txs"));
662 };
663
664 for tx in txs {
666 trace!("replay tx::: {}", tx.tx_hash());
667 if is_known_system_sender(tx.from())
668 || tx.transaction_type() == Some(SYSTEM_TRANSACTION_TYPE)
669 {
670 continue;
671 }
672 if tx.tx_hash() == tx_hash {
673 break;
674 }
675
676 let tx_env = TxEnvFor::<FEN>::from_recovered_tx(tx.as_ref(), tx.from());
677
678 if ConsensusTransaction::to(tx).is_some() {
679 executor.transact_with_env(evm_env.clone(), tx_env.clone()).wrap_err_with(
680 || {
681 format!(
682 "Failed to execute transaction: {:?} in block {}",
683 tx.tx_hash(),
684 evm_env.block_env.number()
685 )
686 },
687 )?;
688 } else if let Err(error) =
689 executor.deploy_with_env(evm_env.clone(), tx_env.clone(), None)
690 {
691 match error {
692 EvmError::Execution(_) => (),
694 error => {
695 return Err(error).wrap_err_with(|| {
696 format!(
697 "Failed to deploy transaction: {:?} in block {}",
698 tx.tx_hash(),
699 evm_env.block_env.number()
700 )
701 });
702 }
703 }
704 }
705 }
706 }
707
708 let kind = ConsensusTransaction::kind(&transaction);
709 let mut tx_env =
710 TxEnvFor::<FEN>::from_recovered_tx(transaction.as_ref(), transaction.from());
711 tx_env.set_nonce(prev_block_nonce);
712
713 if let TxKind::Call(to) = kind {
715 if to == DEFAULT_CREATE2_DEPLOYER {
716 let mut input = transaction.input()[..32].to_vec(); input.extend_from_slice(&local_bytecode_vec);
718 tx_env.set_data(Bytes::from(input));
719
720 executor.deploy_create2_deployer()?;
722 }
723 } else {
724 tx_env.set_data(Bytes::from(local_bytecode_vec));
725 }
726
727 let fork_address = crate::utils::deploy_contract::<FEN>(
728 &mut executor,
729 &evm_env,
730 &tx_env,
731 config.evm_spec_id::<SpecFor<FEN>>(),
732 kind,
733 )?;
734
735 let (fork_runtime_code, onchain_runtime_code) = crate::utils::get_runtime_codes::<FEN>(
737 &mut executor,
738 &provider,
739 self.address,
740 fork_address,
741 Some(simulation_block),
742 )
743 .await?;
744
745 let match_type = crate::utils::match_bytecodes(
747 fork_runtime_code.original_byte_slice(),
748 &onchain_runtime_code,
749 &constructor_args,
750 true,
751 config.bytecode_hash,
752 );
753
754 crate::utils::print_result(
755 match_type,
756 BytecodeType::Runtime,
757 &mut json_results,
758 etherscan_metadata,
759 &config,
760 );
761 }
762
763 if shell::is_json() {
764 sh_println!("{}", serde_json::to_string(&json_results)?)?;
765 }
766 Ok(())
767 }
768}
769
770#[cfg(test)]
771mod tests {
772 use super::*;
773
774 #[test]
775 fn can_parse_network() {
776 let args = VerifyBytecodeArgs::parse_from([
777 "foundry-cli",
778 "0x0000000000000000000000000000000000000000",
779 "src/Counter.sol:Counter",
780 "--network",
781 "tempo",
782 ]);
783
784 assert_eq!(args.network, Some(NetworkVariant::Tempo));
785 }
786
787 #[test]
788 fn configured_network_uses_config_network() {
789 let config = Config { networks: NetworkVariant::Tempo.into(), ..Default::default() };
790
791 assert_eq!(
792 VerifyBytecodeArgs::configured_network(None, &config),
793 Some(NetworkVariant::Tempo)
794 );
795 }
796
797 #[test]
798 fn configured_network_prefers_cli_network() {
799 let config = Config { networks: NetworkVariant::Tempo.into(), ..Default::default() };
800
801 assert_eq!(
802 VerifyBytecodeArgs::configured_network(Some(NetworkVariant::Ethereum), &config),
803 Some(NetworkVariant::Ethereum)
804 );
805 }
806}