Skip to main content

forge_verify/
bytecode.rs

1//! The `forge verify-bytecode` command.
2use 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/// CLI arguments for `forge verify-bytecode`.
54#[derive(Clone, Debug, Parser)]
55pub struct VerifyBytecodeArgs {
56    /// The address of the contract to verify.
57    pub address: Address,
58
59    /// The contract identifier in the form `<path>:<contractname>`.
60    pub contract: ContractInfo,
61
62    /// The block at which the bytecode should be verified.
63    #[arg(long, value_name = "BLOCK")]
64    pub block: Option<BlockId>,
65
66    /// The constructor args to generate the creation code.
67    #[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    /// The ABI-encoded constructor arguments.
76    #[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    /// The path to a file containing the constructor arguments.
84    #[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    /// The rpc url to use for verification.
93    #[arg(short = 'r', long, value_name = "RPC_URL", env = "ETH_RPC_URL")]
94    pub rpc_url: Option<String>,
95
96    /// Specify the network for correct encoding.
97    #[arg(long, short, num_args = 1, value_name = "NETWORK")]
98    pub network: Option<NetworkVariant>,
99
100    /// Etherscan options.
101    #[command(flatten)]
102    pub etherscan: EtherscanOpts,
103
104    /// Verifier options.
105    #[command(flatten)]
106    pub verifier: VerifierArgs,
107
108    /// Set pre-linked libraries.
109    #[arg(long, help_heading = "Linker options")]
110    pub libraries: Vec<String>,
111
112    /// The project's root path.
113    ///
114    /// By default root of the Git repository, if in one,
115    /// or the current working directory.
116    #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")]
117    pub root: Option<PathBuf>,
118
119    /// Ignore verification for creation or runtime bytecode.
120    #[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    /// Run the `verify-bytecode` command to verify the bytecode onchain against the locally built
158    /// bytecode.
159    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        // Setup
200        let provider = ProviderBuilder::<FEN::Network>::from_config(&config)?.build()?;
201
202        // If chain is not set, we try to get it from the RPC.
203        // If RPC is not set, the default chain is used.
204        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        // Set Etherscan options.
210        self.etherscan.chain = Some(chain);
211        self.etherscan.key = config.get_etherscan_config_with_chain(Some(chain))?.map(|c| c.key);
212
213        // Whether a block explorer is configured for this chain. Client setup errors are only
214        // treated as "no explorer available" when no usable verifier, verifier URL, or resolved
215        // API key is configured.
216        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        // Etherscan client. May be unavailable (e.g. unknown chain, missing configuration), in
222        // which case verification proceeds with local data only.
223        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        // Get the bytecode at the address, bailing if it doesn't exist.
243        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        // Get creation tx hash. An unavailable explorer (missing API key, unsupported chain,
259        // unverified contract, etc.) must not prevent verification against a local build: fall
260        // back to verifying the runtime bytecode only.
261        // See <https://github.com/foundry-rs/foundry/issues/13479>.
262        let (creation_data, maybe_predeploy) = match &etherscan {
263            Some(etherscan) => {
264                let creation_data = etherscan.contract_creation_data(self.address).await;
265
266                // Check if contract is a predeploy
267                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        // Get the constructor args using `source_code` endpoint.
288        let source_code = match &etherscan {
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                        // Check if the contract name matches.
293                        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        // Obtain Etherscan compilation metadata.
322        let etherscan_metadata = source_code.as_ref().and_then(|source| source.items.first());
323
324        // The EVM version to verify against: the explorer-reported version when available,
325        // otherwise the local project configuration.
326        let evm_version = match etherscan_metadata {
327            Some(metadata) => metadata.evm_version()?.unwrap_or_default(),
328            None => config.evm_version,
329        };
330
331        // Obtain local artifact
332        let artifact = crate::utils::build_project(&self, &config)?;
333
334        // Get local bytecode (creation code)
335        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        // Get and encode user provided constructor args
342        let provided_constructor_args = if let Some(path) = self.constructor_args_path.clone() {
343            // Read from file
344            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            // If no constructor args were provided, try to retrieve them from the explorer.
356            check_explorer_args(source_code)?
357        } else {
358            Bytes::new()
359        };
360
361        // This fails only when the contract expects constructor args but NONE were provided OR
362        // retrieved from explorer (in case of predeploys).
363        crate::utils::check_args_len(&artifact, &constructor_args)?;
364
365        // Without creation data (predeploys, or the explorer being unavailable), the creation
366        // code cannot be verified. Verify the runtime bytecode instead by deploying the local
367        // creation code and comparing the resulting runtime code with the onchain one.
368        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            // Without creation data there is nothing else to verify when the runtime bytecode is
381            // ignored.
382            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                // Deploy at genesis
391                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            // Append constructor args to the local_bytecode.
403            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            // Setup genesis tx_env and evm_evm.
420            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            // Seed deployer account with funds
438            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            // Compare runtime bytecode. The onchain code is read at `deploy_block` to stay
454            // anchored to the same height as the local fork. Predeploys keep reading at the
455            // latest block: their code is stable and genesis state often isn't served by RPCs.
456            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        // We can unwrap directly as maybe_predeploy is false
489        let creation_data = creation_data.unwrap();
490        // Get transaction and receipt.
491        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        // Extract creation code from creation tx input.
520        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            // Try to get creation bytecode from tx trace.
528            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        // In some cases, Etherscan will return incorrect constructor arguments. If this
551        // happens, try extracting arguments ourselves.
552        if !maybe_creation_code.ends_with(&constructor_args) {
553            trace!("mismatch of constructor args with etherscan");
554            // If local bytecode is longer than on-chain one, this is probably not a match.
555            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        // Append constructor args to the local_bytecode.
567        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        // Check if `--ignore` is set to `creation`.
573        if self.ignore.is_none_or(|b| !b.is_creation()) {
574            // Compare creation code with locally built bytecode and `maybe_creation_code`.
575            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 the creation code does not match, the runtime also won't match. Hence return.
592            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            // Runtime verification can only re-deploy local bytecode for direct `CREATE` and the
609            // default `CREATE2` deployer, so skip custom factory deployments.
610            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            // Get contract creation block.
633            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            // Fork the chain at `simulation_block`.
644            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, // env.fork_block_number
648                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            // Workaround for the NonceTooHigh issue as we're not simulating prior txs of the same
656            // block.
657            let prev_block_id = BlockId::number(simulation_block - 1);
658
659            // Use `transaction.from` instead of `creation_data.contract_creator` to resolve
660            // blockscout creation data discrepancy in case of CREATE2.
661            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                // Replay txes in block until the contract creation one.
673                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                            // Reverted transactions should be skipped
701                            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            // Replace the `input` with local creation code in the creation tx.
722            if let TxKind::Call(to) = kind {
723                if to == DEFAULT_CREATE2_DEPLOYER {
724                    let mut input = transaction.input()[..32].to_vec(); // Salt
725                    input.extend_from_slice(&local_bytecode_vec);
726                    tx_env.set_data(Bytes::from(input));
727
728                    // Deploy default CREATE2 deployer
729                    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            // State committed using deploy_with_env, now get the runtime bytecode from the db.
744            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            // Compare the onchain runtime bytecode with the runtime code from the fork.
754            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}