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 utils::apply_chain_specific_tx_replay_env_changes,
46};
47use foundry_evm_networks::NetworkVariant;
48use revm::{context::Block as _, state::AccountInfo};
49use std::path::PathBuf;
50
51impl_figment_convert!(VerifyBytecodeArgs);
52
53#[derive(Clone, Debug, Parser)]
55pub struct VerifyBytecodeArgs {
56 pub address: Address,
58
59 pub contract: ContractInfo,
61
62 #[arg(long, value_name = "BLOCK")]
64 pub block: Option<BlockId>,
65
66 #[arg(
68 long,
69 num_args(1..),
70 conflicts_with_all = &["constructor_args_path", "encoded_constructor_args"],
71 value_name = "ARGS",
72 )]
73 pub constructor_args: Option<Vec<String>>,
74
75 #[arg(
77 long,
78 conflicts_with_all = &["constructor_args_path", "constructor_args"],
79 value_name = "HEX",
80 )]
81 pub encoded_constructor_args: Option<String>,
82
83 #[arg(
85 long,
86 value_hint = ValueHint::FilePath,
87 value_name = "PATH",
88 conflicts_with_all = &["constructor_args", "encoded_constructor_args"]
89 )]
90 pub constructor_args_path: Option<PathBuf>,
91
92 #[arg(short = 'r', long, value_name = "RPC_URL", env = "ETH_RPC_URL")]
94 pub rpc_url: Option<String>,
95
96 #[arg(long, short, num_args = 1, value_name = "NETWORK")]
98 pub network: Option<NetworkVariant>,
99
100 #[command(flatten)]
102 pub etherscan: EtherscanOpts,
103
104 #[command(flatten)]
106 pub verifier: VerifierArgs,
107
108 #[arg(long, help_heading = "Linker options")]
110 pub libraries: Vec<String>,
111
112 #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")]
117 pub root: Option<PathBuf>,
118
119 #[arg(long, value_name = "BYTECODE_TYPE")]
121 pub ignore: Option<BytecodeType>,
122}
123
124impl figment::Provider for VerifyBytecodeArgs {
125 fn metadata(&self) -> figment::Metadata {
126 figment::Metadata::named("Verify Bytecode Provider")
127 }
128
129 fn data(
130 &self,
131 ) -> Result<figment::value::Map<figment::Profile, figment::value::Dict>, figment::Error> {
132 let mut dict = self.etherscan.dict();
133
134 if let Some(api_key) = &self.verifier.verifier_api_key {
135 dict.insert("etherscan_api_key".into(), api_key.as_str().into());
136 }
137
138 if let Some(block) = &self.block {
139 dict.insert("block".into(), figment::value::Value::serialize(block)?);
140 }
141 if let Some(rpc_url) = &self.rpc_url {
142 dict.insert("eth_rpc_url".into(), rpc_url.clone().into());
143 }
144
145 Ok(figment::value::Map::from([(Config::selected_profile(), dict)]))
146 }
147}
148
149impl VerifyBytecodeArgs {
150 fn configured_network(
151 cli_network: Option<NetworkVariant>,
152 config: &Config,
153 ) -> Option<NetworkVariant> {
154 cli_network.or_else(|| config.networks.resolved_network())
155 }
156
157 pub async fn run(mut self) -> Result<()> {
160 let mut config = self.load_config()?;
161 config.libraries.append(&mut self.libraries);
162
163 let network = if let Some(network) = Self::configured_network(self.network, &config) {
164 if self.network.is_some() {
165 config.networks = network.into();
166 }
167 network
168 } else {
169 let network = {
170 let provider = ProviderBuilder::<AnyNetwork>::from_config(&config)?.build()?;
171 NetworkVariant::from(provider.get_chain_id().await?)
172 };
173
174 if !network.is_ethereum() {
175 config.networks = network.into();
176 }
177
178 network
179 };
180
181 match network {
182 NetworkVariant::Ethereum => {
183 self.run_with_network_and_config::<EthEvmNetwork>(config).await
184 }
185 #[cfg(feature = "optimism")]
186 NetworkVariant::Optimism => {
187 self.run_with_network_and_config::<OpEvmNetwork>(config).await
188 }
189 NetworkVariant::Tempo => {
190 self.run_with_network_and_config::<TempoEvmNetwork>(config).await
191 }
192 }
193 }
194
195 async fn run_with_network_and_config<FEN>(mut self, config: Config) -> Result<()>
196 where
197 FEN: FoundryEvmNetwork,
198 {
199 let provider = ProviderBuilder::<FEN::Network>::from_config(&config)?.build()?;
201
202 let chain = match config.get_rpc_url() {
205 Some(_) => utils::get_chain::<FEN::Network, _>(config.chain, &provider).await?,
206 None => config.chain.unwrap_or_default(),
207 };
208
209 self.etherscan.chain = Some(chain);
211 self.etherscan.key = config.get_etherscan_config_with_chain(Some(chain))?.map(|c| c.key);
212
213 let has_explorer_config = self.verifier.verifier.is_some()
217 || self.verifier.verifier_url.is_some()
218 || self.verifier.verifier_api_key.is_some()
219 || self.etherscan.key.is_some();
220
221 let etherscan = match EtherscanVerificationProvider.client(
224 &self.etherscan,
225 &self.verifier,
226 &config,
227 ) {
228 Ok(client) => Some(client),
229 Err(err) => {
230 if has_explorer_config {
231 return Err(err);
232 }
233 if !shell::is_json() {
234 sh_warn!(
235 "Failed to create a block explorer client: {err}. Continuing with the local project configuration."
236 )?;
237 }
238 None
239 }
240 };
241
242 let code = provider.get_code_at(self.address).await?;
244 if code.is_empty() {
245 eyre::bail!("No bytecode found at address {}", self.address);
246 }
247
248 if !shell::is_json() {
249 sh_status!(
250 "Verifying bytecode for contract {} at address {}",
251 self.contract.name,
252 self.address
253 )?;
254 }
255
256 let mut json_results: Vec<JsonResult> = vec![];
257
258 let (creation_data, maybe_predeploy) = match ðerscan {
263 Some(etherscan) => {
264 let creation_data = etherscan.contract_creation_data(self.address).await;
265
266 match maybe_predeploy_contract(creation_data) {
268 Ok(res) => res,
269 Err(err) => {
270 if has_explorer_config {
271 return Err(err);
272 }
273 if !shell::is_json() {
274 sh_warn!(
275 "Failed to fetch creation data from the block explorer: {err}"
276 )?;
277 }
278 (None, false)
279 }
280 }
281 }
282 None => (None, false),
283 };
284
285 trace!(maybe_predeploy = ?maybe_predeploy);
286
287 let source_code = match ðerscan {
289 Some(etherscan) => match etherscan.contract_source_code(self.address).await {
290 Ok(source_code) => {
291 if let Some(metadata) = source_code.items.first() {
292 if metadata.contract_name != self.contract.name {
294 eyre::bail!("Contract name mismatch");
295 }
296 Some(source_code)
297 } else {
298 if !shell::is_json() {
299 sh_warn!(
300 "Block explorer returned no source metadata. Continuing with the local project configuration; compiler settings mismatches will not be reported."
301 )?;
302 }
303 None
304 }
305 }
306 Err(err) => {
307 if has_explorer_config {
308 return Err(err.into());
309 }
310 if !shell::is_json() {
311 sh_warn!(
312 "Failed to fetch contract source code from the block explorer: {err}. Continuing with the local project configuration; compiler settings mismatches will not be reported."
313 )?;
314 }
315 None
316 }
317 },
318 None => None,
319 };
320
321 let etherscan_metadata = source_code.as_ref().and_then(|source| source.items.first());
323
324 let evm_version = match etherscan_metadata {
327 Some(metadata) => metadata.evm_version()?.unwrap_or_default(),
328 None => config.evm_version,
329 };
330
331 let artifact = crate::utils::build_project(&self, &config)?;
333
334 let local_bytecode = artifact
336 .bytecode
337 .as_ref()
338 .and_then(|b| b.to_owned().into_bytes())
339 .ok_or_eyre("Unlinked bytecode is not supported for verification")?;
340
341 let provided_constructor_args = if let Some(path) = self.constructor_args_path.clone() {
343 Some(read_constructor_args_file(path)?)
345 } else {
346 self.constructor_args.clone()
347 }
348 .map(|args| check_and_encode_args(&artifact, args))
349 .transpose()?
350 .or(self.encoded_constructor_args.clone().map(hex::decode).transpose()?);
351
352 let mut constructor_args = if let Some(provided) = provided_constructor_args {
353 provided.into()
354 } else if let Some(source_code) = &source_code {
355 check_explorer_args(source_code)?
357 } else {
358 Bytes::new()
359 };
360
361 crate::utils::check_args_len(&artifact, &constructor_args)?;
364
365 if creation_data.is_none() {
369 if !shell::is_json() {
370 if maybe_predeploy {
371 sh_warn!(
372 "Attempting to verify predeployed contract at {:?}. Ignoring creation code verification.",
373 self.address
374 )?;
375 } else {
376 sh_warn!("Creation data is unavailable. Ignoring creation code verification.")?;
377 }
378 }
379
380 if self.ignore.is_some_and(|b| b.is_runtime()) {
383 if shell::is_json() {
384 sh_println!("{}", serde_json::to_string(&json_results)?)?;
385 }
386 return Ok(());
387 }
388
389 let deploy_block = if maybe_predeploy {
390 0_u64
392 } else {
393 match self.block {
394 Some(BlockId::Number(BlockNumberOrTag::Number(block))) => block,
395 Some(_) => {
396 eyre::bail!("Invalid block number");
397 }
398 None => provider.get_block_number().await?,
399 }
400 };
401
402 trace!(%constructor_args);
404 let mut local_bytecode_vec = local_bytecode.to_vec();
405 local_bytecode_vec.extend_from_slice(&constructor_args);
406
407 let (mut fork_config, evm_opts) = load_fork_config_and_evm_opts(&config)?;
408 let (mut evm_env, _, mut executor) = crate::utils::get_tracing_executor::<FEN>(
409 &mut fork_config,
410 deploy_block,
411 evm_version,
412 evm_opts,
413 )
414 .await?;
415
416 evm_env.block_env.set_number(U256::from(deploy_block));
417 let deploy_block_info = provider.get_block(deploy_block.into()).full().await?;
418
419 let deployer = Address::with_last_byte(0x1);
421 let mut tx_env = TxEnvFor::<FEN>::default();
422 tx_env.set_caller(deployer);
423 tx_env.set_kind(TxKind::Create);
424 tx_env.set_data(Bytes::from(local_bytecode_vec));
425 tx_env.set_chain_id(Some(evm_env.cfg_env.chain_id));
426 tx_env.set_gas_limit(evm_env.block_env.gas_limit());
427 tx_env.set_gas_price(evm_env.block_env.basefee() as u128);
428
429 if let Some(ref block) = deploy_block_info {
430 configure_env_block::<FEN>(&mut evm_env, block, config.networks);
431 tx_env.set_gas_limit(block.header().gas_limit());
432 tx_env.set_gas_price(block.header().base_fee_per_gas().unwrap_or_default() as u128);
433 }
434
435 let kind = TxKind::Create;
436
437 let account_info = AccountInfo {
439 balance: U256::from(100 * 10_u128.pow(18)),
440 nonce: 0,
441 ..Default::default()
442 };
443 executor.backend_mut().insert_account_info(deployer, account_info);
444
445 let fork_address = crate::utils::deploy_contract::<FEN>(
446 &mut executor,
447 &evm_env,
448 &tx_env,
449 config.evm_spec_id::<SpecFor<FEN>>(),
450 kind,
451 )?;
452
453 let (deployed_bytecode, onchain_runtime_code) = crate::utils::get_runtime_codes::<FEN>(
457 &mut executor,
458 &provider,
459 self.address,
460 fork_address,
461 (!maybe_predeploy).then_some(deploy_block),
462 )
463 .await?;
464
465 let match_type = crate::utils::match_bytecodes(
466 deployed_bytecode.original_byte_slice(),
467 &onchain_runtime_code,
468 &constructor_args,
469 true,
470 config.bytecode_hash,
471 );
472
473 crate::utils::print_result(
474 match_type,
475 BytecodeType::Runtime,
476 &mut json_results,
477 etherscan_metadata,
478 &config,
479 );
480
481 if shell::is_json() {
482 sh_println!("{}", serde_json::to_string(&json_results)?)?;
483 }
484
485 return Ok(());
486 }
487
488 let creation_data = creation_data.unwrap();
490 trace!(creation_tx_hash = ?creation_data.transaction_hash);
492 let transaction = provider
493 .get_transaction_by_hash(creation_data.transaction_hash)
494 .await
495 .or_else(|e| {
496 eyre::bail!("Couldn't fetch transaction from RPC: {:?}", e);
497 })?
498 .ok_or_else(|| {
499 eyre::eyre!("Transaction not found for hash {}", creation_data.transaction_hash)
500 })?;
501 let tx_hash = transaction.tx_hash();
502 let receipt = provider
503 .get_transaction_receipt(creation_data.transaction_hash)
504 .await
505 .or_else(|e| {
506 eyre::bail!("Couldn't fetch transaction receipt from RPC: {:?}", e);
507 })?;
508 let receipt = if let Some(receipt) = receipt {
509 receipt
510 } else {
511 eyre::bail!(
512 "Receipt not found for transaction hash {}",
513 creation_data.transaction_hash
514 );
515 };
516
517 let creation_block = transaction.block_number();
518
519 let maybe_creation_code = if receipt.to().is_none()
521 && receipt.contract_address() == Some(self.address)
522 {
523 transaction.input().clone()
524 } else if receipt.to() == Some(DEFAULT_CREATE2_DEPLOYER) {
525 Bytes::copy_from_slice(&transaction.input()[32..])
526 } else {
527 let traces = provider
529 .trace_transaction(creation_data.transaction_hash)
530 .await
531 .unwrap_or_default();
532
533 let creation_bytecode =
534 traces.iter().find_map(|trace| match (&trace.trace.result, &trace.trace.action) {
535 (
536 Some(TraceOutput::Create(CreateOutput { address, .. })),
537 Action::Create(CreateAction { init, .. }),
538 ) if *address == self.address => Some(init.clone()),
539 _ => None,
540 });
541
542 creation_bytecode.ok_or_else(|| {
543 eyre::eyre!(
544 "Could not extract the creation code for contract at address {}",
545 self.address
546 )
547 })?
548 };
549
550 if !maybe_creation_code.ends_with(&constructor_args) {
553 trace!("mismatch of constructor args with etherscan");
554 if maybe_creation_code.len() >= local_bytecode.len() {
556 constructor_args =
557 Bytes::copy_from_slice(&maybe_creation_code[local_bytecode.len()..]);
558 trace!(
559 target: "forge::verify",
560 "setting constructor args to latest {} bytes of bytecode",
561 constructor_args.len()
562 );
563 }
564 }
565
566 trace!(%constructor_args);
568 let mut local_bytecode_vec = local_bytecode.to_vec();
569 local_bytecode_vec.extend_from_slice(&constructor_args);
570
571 trace!(ignore = ?self.ignore);
572 if self.ignore.is_none_or(|b| !b.is_creation()) {
574 let match_type = crate::utils::match_bytecodes(
576 local_bytecode_vec.as_slice(),
577 &maybe_creation_code,
578 &constructor_args,
579 false,
580 config.bytecode_hash,
581 );
582
583 crate::utils::print_result(
584 match_type,
585 BytecodeType::Creation,
586 &mut json_results,
587 etherscan_metadata,
588 &config,
589 );
590
591 if match_type.is_none() {
593 crate::utils::print_result(
594 None,
595 BytecodeType::Runtime,
596 &mut json_results,
597 etherscan_metadata,
598 &config,
599 );
600 if shell::is_json() {
601 sh_println!("{}", serde_json::to_string(&json_results)?)?;
602 }
603 return Ok(());
604 }
605 }
606
607 if self.ignore.is_none_or(|b| !b.is_runtime()) {
608 if let TxKind::Call(to) = ConsensusTransaction::kind(&transaction)
611 && to != DEFAULT_CREATE2_DEPLOYER
612 {
613 let message = format!(
614 "Runtime bytecode verification is not supported for this contract: its \
615 creation transaction calls custom factory {to}. forge can only verify \
616 runtime bytecode for direct CREATE transactions and calls to the default \
617 CREATE2 deployer; skipping runtime bytecode verification."
618 );
619 if shell::is_json() {
620 json_results.push(JsonResult {
621 bytecode_type: BytecodeType::Runtime,
622 match_type: None,
623 message: Some(message),
624 });
625 sh_println!("{}", serde_json::to_string(&json_results)?)?;
626 } else {
627 sh_warn!("{message}")?;
628 }
629 return Ok(());
630 }
631
632 let simulation_block = match self.block {
634 Some(BlockId::Number(BlockNumberOrTag::Number(block))) => block,
635 Some(_) => { eyre::bail!("Invalid block number"); },
636 None => {
637 creation_block.ok_or_else(|| {
638 eyre::eyre!("Failed to get block number of the contract creation tx, specify using the --block flag")
639 })?
640 }
641 };
642
643 let (mut fork_config, evm_opts) = load_fork_config_and_evm_opts(&config)?;
645 let (mut evm_env, _tx_env, mut executor) = crate::utils::get_tracing_executor::<FEN>(
646 &mut fork_config,
647 simulation_block - 1, evm_version,
649 evm_opts,
650 )
651 .await?;
652 evm_env.block_env.set_number(U256::from(simulation_block));
653 let block = provider.get_block(simulation_block.into()).full().await?;
654
655 let prev_block_id = BlockId::number(simulation_block - 1);
658
659 let prev_block_nonce =
662 provider.get_transaction_count(transaction.from()).block_id(prev_block_id).await?;
663
664 apply_chain_specific_tx_replay_env_changes(&mut evm_env);
665 if let Some(ref block) = block {
666 configure_env_block::<FEN>(&mut evm_env, block, config.networks);
667
668 let BlockTransactions::Full(txs) = block.transactions() else {
669 return Err(eyre::eyre!("Could not get block txs"));
670 };
671
672 for tx in txs {
674 trace!("replay tx::: {}", tx.tx_hash());
675 if is_known_system_sender(tx.from())
676 || tx.transaction_type() == Some(SYSTEM_TRANSACTION_TYPE)
677 {
678 continue;
679 }
680 if tx.tx_hash() == tx_hash {
681 break;
682 }
683
684 let tx_env = TxEnvFor::<FEN>::from_recovered_tx(tx.as_ref(), tx.from());
685
686 if ConsensusTransaction::to(tx).is_some() {
687 executor.transact_with_env(evm_env.clone(), tx_env.clone()).wrap_err_with(
688 || {
689 format!(
690 "Failed to execute transaction: {:?} in block {}",
691 tx.tx_hash(),
692 evm_env.block_env.number()
693 )
694 },
695 )?;
696 } else if let Err(error) =
697 executor.deploy_with_env(evm_env.clone(), tx_env.clone(), None)
698 {
699 match error {
700 EvmError::Execution(_) => (),
702 error => {
703 return Err(error).wrap_err_with(|| {
704 format!(
705 "Failed to deploy transaction: {:?} in block {}",
706 tx.tx_hash(),
707 evm_env.block_env.number()
708 )
709 });
710 }
711 }
712 }
713 }
714 }
715
716 let kind = ConsensusTransaction::kind(&transaction);
717 let mut tx_env =
718 TxEnvFor::<FEN>::from_recovered_tx(transaction.as_ref(), transaction.from());
719 tx_env.set_nonce(prev_block_nonce);
720
721 if let TxKind::Call(to) = kind {
723 if to == DEFAULT_CREATE2_DEPLOYER {
724 let mut input = transaction.input()[..32].to_vec(); input.extend_from_slice(&local_bytecode_vec);
726 tx_env.set_data(Bytes::from(input));
727
728 executor.deploy_create2_deployer()?;
730 }
731 } else {
732 tx_env.set_data(Bytes::from(local_bytecode_vec));
733 }
734
735 let fork_address = crate::utils::deploy_contract::<FEN>(
736 &mut executor,
737 &evm_env,
738 &tx_env,
739 config.evm_spec_id::<SpecFor<FEN>>(),
740 kind,
741 )?;
742
743 let (fork_runtime_code, onchain_runtime_code) = crate::utils::get_runtime_codes::<FEN>(
745 &mut executor,
746 &provider,
747 self.address,
748 fork_address,
749 Some(simulation_block),
750 )
751 .await?;
752
753 let match_type = crate::utils::match_bytecodes(
755 fork_runtime_code.original_byte_slice(),
756 &onchain_runtime_code,
757 &constructor_args,
758 true,
759 config.bytecode_hash,
760 );
761
762 crate::utils::print_result(
763 match_type,
764 BytecodeType::Runtime,
765 &mut json_results,
766 etherscan_metadata,
767 &config,
768 );
769 }
770
771 if shell::is_json() {
772 sh_println!("{}", serde_json::to_string(&json_results)?)?;
773 }
774 Ok(())
775 }
776}
777
778#[cfg(test)]
779mod tests {
780 use super::*;
781
782 #[test]
783 fn can_parse_network() {
784 let args = VerifyBytecodeArgs::parse_from([
785 "foundry-cli",
786 "0x0000000000000000000000000000000000000000",
787 "src/Counter.sol:Counter",
788 "--network",
789 "tempo",
790 ]);
791
792 assert_eq!(args.network, Some(NetworkVariant::Tempo));
793 }
794
795 #[test]
796 fn configured_network_uses_config_network() {
797 let config = Config { networks: NetworkVariant::Tempo.into(), ..Default::default() };
798
799 assert_eq!(
800 VerifyBytecodeArgs::configured_network(None, &config),
801 Some(NetworkVariant::Tempo)
802 );
803 }
804
805 #[test]
806 fn configured_network_prefers_cli_network() {
807 let config = Config { networks: NetworkVariant::Tempo.into(), ..Default::default() };
808
809 assert_eq!(
810 VerifyBytecodeArgs::configured_network(Some(NetworkVariant::Ethereum), &config),
811 Some(NetworkVariant::Ethereum)
812 );
813 }
814}