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};
46use foundry_evm_networks::NetworkVariant;
47use revm::{context::Block as _, state::AccountInfo};
48use std::path::PathBuf;
49
50impl_figment_convert!(VerifyBytecodeArgs);
51
52/// CLI arguments for `forge verify-bytecode`.
53#[derive(Clone, Debug, Parser)]
54pub struct VerifyBytecodeArgs {
55    /// The address of the contract to verify.
56    pub address: Address,
57
58    /// The contract identifier in the form `<path>:<contractname>`.
59    pub contract: ContractInfo,
60
61    /// The block at which the bytecode should be verified.
62    #[arg(long, value_name = "BLOCK")]
63    pub block: Option<BlockId>,
64
65    /// The constructor args to generate the creation code.
66    #[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    /// The ABI-encoded constructor arguments.
75    #[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    /// The path to a file containing the constructor arguments.
83    #[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    /// The rpc url to use for verification.
92    #[arg(short = 'r', long, value_name = "RPC_URL", env = "ETH_RPC_URL")]
93    pub rpc_url: Option<String>,
94
95    /// Specify the network for correct encoding.
96    #[arg(long, short, num_args = 1, value_name = "NETWORK")]
97    pub network: Option<NetworkVariant>,
98
99    /// Etherscan options.
100    #[command(flatten)]
101    pub etherscan: EtherscanOpts,
102
103    /// Verifier options.
104    #[command(flatten)]
105    pub verifier: VerifierArgs,
106
107    /// The project's root path.
108    ///
109    /// By default root of the Git repository, if in one,
110    /// or the current working directory.
111    #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")]
112    pub root: Option<PathBuf>,
113
114    /// Ignore verification for creation or runtime bytecode.
115    #[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    /// Run the `verify-bytecode` command to verify the bytecode onchain against the locally built
153    /// bytecode.
154    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        // Setup
193        let provider = ProviderBuilder::<FEN::Network>::from_config(&config)?.build()?;
194
195        // If chain is not set, we try to get it from the RPC.
196        // If RPC is not set, the default chain is used.
197        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        // Set Etherscan options.
203        self.etherscan.chain = Some(chain);
204        self.etherscan.key = config.get_etherscan_config_with_chain(Some(chain))?.map(|c| c.key);
205
206        // Whether a block explorer is configured for this chain. Client setup errors are only
207        // treated as "no explorer available" when no usable verifier, verifier URL, or resolved
208        // API key is configured.
209        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        // Etherscan client. May be unavailable (e.g. unknown chain, missing configuration), in
215        // which case verification proceeds with local data only.
216        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        // Get the bytecode at the address, bailing if it doesn't exist.
236        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        // Get creation tx hash. An unavailable explorer (missing API key, unsupported chain,
252        // unverified contract, etc.) must not prevent verification against a local build: fall
253        // back to verifying the runtime bytecode only.
254        // See <https://github.com/foundry-rs/foundry/issues/13479>.
255        let (creation_data, maybe_predeploy) = match &etherscan {
256            Some(etherscan) => {
257                let creation_data = etherscan.contract_creation_data(self.address).await;
258
259                // Check if contract is a predeploy
260                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        // Get the constructor args using `source_code` endpoint.
281        let source_code = match &etherscan {
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                        // Check if the contract name matches.
286                        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        // Obtain Etherscan compilation metadata.
315        let etherscan_metadata = source_code.as_ref().and_then(|source| source.items.first());
316
317        // The EVM version to verify against: the explorer-reported version when available,
318        // otherwise the local project configuration.
319        let evm_version = match etherscan_metadata {
320            Some(metadata) => metadata.evm_version()?.unwrap_or_default(),
321            None => config.evm_version,
322        };
323
324        // Obtain local artifact
325        let artifact = crate::utils::build_project(&self, &config)?;
326
327        // Get local bytecode (creation code)
328        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        // Get and encode user provided constructor args
335        let provided_constructor_args = if let Some(path) = self.constructor_args_path.clone() {
336            // Read from file
337            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            // If no constructor args were provided, try to retrieve them from the explorer.
349            check_explorer_args(source_code)?
350        } else {
351            Bytes::new()
352        };
353
354        // This fails only when the contract expects constructor args but NONE were provided OR
355        // retrieved from explorer (in case of predeploys).
356        crate::utils::check_args_len(&artifact, &constructor_args)?;
357
358        // Without creation data (predeploys, or the explorer being unavailable), the creation
359        // code cannot be verified. Verify the runtime bytecode instead by deploying the local
360        // creation code and comparing the resulting runtime code with the onchain one.
361        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            // Without creation data there is nothing else to verify when the runtime bytecode is
374            // ignored.
375            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                // Deploy at genesis
384                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            // Append constructor args to the local_bytecode.
396            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            // Setup genesis tx_env and evm_evm.
413            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            // Seed deployer account with funds
431            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            // Compare runtime bytecode. The onchain code is read at `deploy_block` to stay
447            // anchored to the same height as the local fork. Predeploys keep reading at the
448            // latest block: their code is stable and genesis state often isn't served by RPCs.
449            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        // We can unwrap directly as maybe_predeploy is false
482        let creation_data = creation_data.unwrap();
483        // Get transaction and receipt.
484        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        // Extract creation code from creation tx input.
513        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            // Try to get creation bytecode from tx trace.
521            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        // In some cases, Etherscan will return incorrect constructor arguments. If this
544        // happens, try extracting arguments ourselves.
545        if !maybe_creation_code.ends_with(&constructor_args) {
546            trace!("mismatch of constructor args with etherscan");
547            // If local bytecode is longer than on-chain one, this is probably not a match.
548            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        // Append constructor args to the local_bytecode.
560        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        // Check if `--ignore` is set to `creation`.
566        if self.ignore.is_none_or(|b| !b.is_creation()) {
567            // Compare creation code with locally built bytecode and `maybe_creation_code`.
568            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 the creation code does not match, the runtime also won't match. Hence return.
585            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            // Runtime verification can only re-deploy local bytecode for direct `CREATE` and the
602            // default `CREATE2` deployer, so skip custom factory deployments.
603            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            // Get contract creation block.
626            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            // Fork the chain at `simulation_block`.
637            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, // env.fork_block_number
641                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            // Workaround for the NonceTooHigh issue as we're not simulating prior txs of the same
649            // block.
650            let prev_block_id = BlockId::number(simulation_block - 1);
651
652            // Use `transaction.from` instead of `creation_data.contract_creator` to resolve
653            // blockscout creation data discrepancy in case of CREATE2.
654            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                // Replay txes in block until the contract creation one.
665                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                            // Reverted transactions should be skipped
693                            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            // Replace the `input` with local creation code in the creation tx.
714            if let TxKind::Call(to) = kind {
715                if to == DEFAULT_CREATE2_DEPLOYER {
716                    let mut input = transaction.input()[..32].to_vec(); // Salt
717                    input.extend_from_slice(&local_bytecode_vec);
718                    tx_env.set_data(Bytes::from(input));
719
720                    // Deploy default CREATE2 deployer
721                    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            // State committed using deploy_with_env, now get the runtime bytecode from the db.
736            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            // Compare the onchain runtime bytecode with the runtime code from the fork.
746            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}