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        maybe_predeploy_contract,
7    },
8    verify::VerifierArgs,
9};
10use alloy_consensus::{BlockHeader, Transaction as ConsensusTransaction};
11use alloy_evm::FromRecoveredTx;
12use alloy_primitives::{Address, Bytes, TxKind, U256, hex};
13use alloy_provider::{
14    Provider,
15    ext::TraceApi,
16    network::{
17        AnyNetwork, BlockResponse, ReceiptResponse, TransactionResponse,
18        primitives::BlockTransactions,
19    },
20};
21use alloy_rpc_types::{
22    BlockId, BlockNumberOrTag,
23    trace::parity::{Action, CreateAction, CreateOutput, TraceOutput},
24};
25use clap::{Parser, ValueHint};
26use eyre::{Context, OptionExt, Result};
27use foundry_cli::{
28    opts::EtherscanOpts,
29    utils::{self, LoadConfig, read_constructor_args_file},
30};
31use foundry_common::{
32    SYSTEM_TRANSACTION_TYPE, is_known_system_sender, provider::ProviderBuilder, shell,
33};
34use foundry_compilers::{artifacts::EvmVersion, info::ContractInfo};
35use foundry_config::{Config, figment, impl_figment_convert};
36#[cfg(feature = "optimism")]
37use foundry_evm::core::evm::OpEvmNetwork;
38use foundry_evm::{
39    constants::DEFAULT_CREATE2_DEPLOYER,
40    core::{
41        FoundryBlock as _, FoundryTransaction as _,
42        evm::{EthEvmNetwork, FoundryEvmNetwork, SpecFor, TempoEvmNetwork, TxEnvFor},
43    },
44    executors::EvmError,
45};
46use foundry_evm_networks::NetworkVariant;
47use revm::{context::Block as _, state::AccountInfo};
48use std::path::PathBuf;
49
50impl_figment_convert!(VerifyBytecodeArgs);
51
52/// 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        // Etherscan client
207        let etherscan =
208            EtherscanVerificationProvider.client(&self.etherscan, &self.verifier, &config)?;
209
210        // Get the bytecode at the address, bailing if it doesn't exist.
211        let code = provider.get_code_at(self.address).await?;
212        if code.is_empty() {
213            eyre::bail!("No bytecode found at address {}", self.address);
214        }
215
216        if !shell::is_json() {
217            sh_status!(
218                "Verifying bytecode for contract {} at address {}",
219                self.contract.name,
220                self.address
221            )?;
222        }
223
224        let mut json_results: Vec<JsonResult> = vec![];
225
226        // Get creation tx hash.
227        let creation_data = etherscan.contract_creation_data(self.address).await;
228
229        // Check if contract is a predeploy
230        let (creation_data, maybe_predeploy) = maybe_predeploy_contract(creation_data)?;
231
232        trace!(maybe_predeploy = ?maybe_predeploy);
233
234        // Get the constructor args using `source_code` endpoint.
235        let source_code = etherscan.contract_source_code(self.address).await?;
236
237        // Check if the contract name matches.
238        let name = source_code.items.first().map(|item| item.contract_name.clone());
239        if name.as_ref() != Some(&self.contract.name) {
240            eyre::bail!("Contract name mismatch");
241        }
242
243        // Obtain Etherscan compilation metadata.
244        let etherscan_metadata = source_code.items.first().unwrap();
245
246        // Obtain local artifact
247        let artifact = crate::utils::build_project(&self, &config)?;
248
249        // Get local bytecode (creation code)
250        let local_bytecode = artifact
251            .bytecode
252            .as_ref()
253            .and_then(|b| b.to_owned().into_bytes())
254            .ok_or_eyre("Unlinked bytecode is not supported for verification")?;
255
256        // Get and encode user provided constructor args
257        let provided_constructor_args = if let Some(path) = self.constructor_args_path.clone() {
258            // Read from file
259            Some(read_constructor_args_file(path)?)
260        } else {
261            self.constructor_args.clone()
262        }
263        .map(|args| check_and_encode_args(&artifact, args))
264        .transpose()?
265        .or(self.encoded_constructor_args.clone().map(hex::decode).transpose()?);
266
267        let mut constructor_args = if let Some(provided) = provided_constructor_args {
268            provided.into()
269        } else {
270            // If no constructor args were provided, try to retrieve them from the explorer.
271            check_explorer_args(source_code.clone())?
272        };
273
274        // This fails only when the contract expects constructor args but NONE were provided OR
275        // retrieved from explorer (in case of predeploys).
276        crate::utils::check_args_len(&artifact, &constructor_args)?;
277
278        if maybe_predeploy {
279            if !shell::is_json() {
280                sh_warn!(
281                    "Attempting to verify predeployed contract at {:?}. Ignoring creation code verification.",
282                    self.address
283                )?;
284            }
285
286            // Append constructor args to the local_bytecode.
287            trace!(%constructor_args);
288            let mut local_bytecode_vec = local_bytecode.to_vec();
289            local_bytecode_vec.extend_from_slice(&constructor_args);
290
291            // Deploy at genesis
292            let gen_blk_num = 0_u64;
293            let (mut fork_config, evm_opts) = config.clone().load_config_and_evm_opts()?;
294            let (mut evm_env, _, mut executor) = crate::utils::get_tracing_executor::<FEN>(
295                &mut fork_config,
296                gen_blk_num,
297                etherscan_metadata.evm_version()?.unwrap_or(EvmVersion::default()),
298                evm_opts,
299            )
300            .await?;
301
302            evm_env.block_env.set_number(U256::ZERO);
303            let genesis_block = provider.get_block(gen_blk_num.into()).full().await?;
304
305            // Setup genesis tx_env and evm_evm.
306            let deployer = Address::with_last_byte(0x1);
307            let mut tx_env = TxEnvFor::<FEN>::default();
308            tx_env.set_caller(deployer);
309            tx_env.set_kind(TxKind::Create);
310            tx_env.set_data(Bytes::from(local_bytecode_vec));
311            tx_env.set_chain_id(Some(evm_env.cfg_env.chain_id));
312            tx_env.set_gas_limit(evm_env.block_env.gas_limit());
313            tx_env.set_gas_price(evm_env.block_env.basefee() as u128);
314
315            if let Some(ref block) = genesis_block {
316                configure_env_block::<FEN>(&mut evm_env, block, config.networks);
317                tx_env.set_gas_limit(block.header().gas_limit());
318                tx_env.set_gas_price(block.header().base_fee_per_gas().unwrap_or_default() as u128);
319            }
320
321            let kind = TxKind::Create;
322
323            // Seed deployer account with funds
324            let account_info = AccountInfo {
325                balance: U256::from(100 * 10_u128.pow(18)),
326                nonce: 0,
327                ..Default::default()
328            };
329            executor.backend_mut().insert_account_info(deployer, account_info);
330
331            let fork_address = crate::utils::deploy_contract::<FEN>(
332                &mut executor,
333                &evm_env,
334                &tx_env,
335                config.evm_spec_id::<SpecFor<FEN>>(),
336                kind,
337            )?;
338
339            // Compare runtime bytecode
340            let (deployed_bytecode, onchain_runtime_code) = crate::utils::get_runtime_codes::<FEN>(
341                &mut executor,
342                &provider,
343                self.address,
344                fork_address,
345                None,
346            )
347            .await?;
348
349            let match_type = crate::utils::match_bytecodes(
350                deployed_bytecode.original_byte_slice(),
351                &onchain_runtime_code,
352                &constructor_args,
353                true,
354                config.bytecode_hash,
355            );
356
357            crate::utils::print_result(
358                match_type,
359                BytecodeType::Runtime,
360                &mut json_results,
361                etherscan_metadata,
362                &config,
363            );
364
365            if shell::is_json() {
366                sh_println!("{}", serde_json::to_string(&json_results)?)?;
367            }
368
369            return Ok(());
370        }
371
372        // We can unwrap directly as maybe_predeploy is false
373        let creation_data = creation_data.unwrap();
374        // Get transaction and receipt.
375        trace!(creation_tx_hash = ?creation_data.transaction_hash);
376        let transaction = provider
377            .get_transaction_by_hash(creation_data.transaction_hash)
378            .await
379            .or_else(|e| eyre::bail!("Couldn't fetch transaction from RPC: {:?}", e))?
380            .ok_or_else(|| {
381                eyre::eyre!("Transaction not found for hash {}", creation_data.transaction_hash)
382            })?;
383        let tx_hash = transaction.tx_hash();
384        let receipt = provider
385            .get_transaction_receipt(creation_data.transaction_hash)
386            .await
387            .or_else(|e| eyre::bail!("Couldn't fetch transaction receipt from RPC: {:?}", e))?;
388        let receipt = if let Some(receipt) = receipt {
389            receipt
390        } else {
391            eyre::bail!(
392                "Receipt not found for transaction hash {}",
393                creation_data.transaction_hash
394            );
395        };
396
397        let creation_block = transaction.block_number();
398
399        // Extract creation code from creation tx input.
400        let maybe_creation_code = if receipt.to().is_none()
401            && receipt.contract_address() == Some(self.address)
402        {
403            transaction.input().clone()
404        } else if receipt.to() == Some(DEFAULT_CREATE2_DEPLOYER) {
405            Bytes::copy_from_slice(&transaction.input()[32..])
406        } else {
407            // Try to get creation bytecode from tx trace.
408            let traces = provider
409                .trace_transaction(creation_data.transaction_hash)
410                .await
411                .unwrap_or_default();
412
413            let creation_bytecode =
414                traces.iter().find_map(|trace| match (&trace.trace.result, &trace.trace.action) {
415                    (
416                        Some(TraceOutput::Create(CreateOutput { address, .. })),
417                        Action::Create(CreateAction { init, .. }),
418                    ) if *address == self.address => Some(init.clone()),
419                    _ => None,
420                });
421
422            creation_bytecode.ok_or_else(|| {
423                eyre::eyre!(
424                    "Could not extract the creation code for contract at address {}",
425                    self.address
426                )
427            })?
428        };
429
430        // In some cases, Etherscan will return incorrect constructor arguments. If this
431        // happens, try extracting arguments ourselves.
432        if !maybe_creation_code.ends_with(&constructor_args) {
433            trace!("mismatch of constructor args with etherscan");
434            // If local bytecode is longer than on-chain one, this is probably not a match.
435            if maybe_creation_code.len() >= local_bytecode.len() {
436                constructor_args =
437                    Bytes::copy_from_slice(&maybe_creation_code[local_bytecode.len()..]);
438                trace!(
439                    target: "forge::verify",
440                    "setting constructor args to latest {} bytes of bytecode",
441                    constructor_args.len()
442                );
443            }
444        }
445
446        // Append constructor args to the local_bytecode.
447        trace!(%constructor_args);
448        let mut local_bytecode_vec = local_bytecode.to_vec();
449        local_bytecode_vec.extend_from_slice(&constructor_args);
450
451        trace!(ignore = ?self.ignore);
452        // Check if `--ignore` is set to `creation`.
453        if self.ignore.is_none_or(|b| !b.is_creation()) {
454            // Compare creation code with locally built bytecode and `maybe_creation_code`.
455            let match_type = crate::utils::match_bytecodes(
456                local_bytecode_vec.as_slice(),
457                &maybe_creation_code,
458                &constructor_args,
459                false,
460                config.bytecode_hash,
461            );
462
463            crate::utils::print_result(
464                match_type,
465                BytecodeType::Creation,
466                &mut json_results,
467                etherscan_metadata,
468                &config,
469            );
470
471            // If the creation code does not match, the runtime also won't match. Hence return.
472            if match_type.is_none() {
473                crate::utils::print_result(
474                    None,
475                    BytecodeType::Runtime,
476                    &mut json_results,
477                    etherscan_metadata,
478                    &config,
479                );
480                if shell::is_json() {
481                    sh_println!("{}", serde_json::to_string(&json_results)?)?;
482                }
483                return Ok(());
484            }
485        }
486
487        if self.ignore.is_none_or(|b| !b.is_runtime()) {
488            // Get contract creation block.
489            let simulation_block = match self.block {
490                Some(BlockId::Number(BlockNumberOrTag::Number(block))) => block,
491                Some(_) => eyre::bail!("Invalid block number"),
492                None => {
493                    creation_block.ok_or_else(|| {
494                        eyre::eyre!("Failed to get block number of the contract creation tx, specify using the --block flag")
495                    })?
496                }
497            };
498
499            // Fork the chain at `simulation_block`.
500            let (mut fork_config, evm_opts) = config.clone().load_config_and_evm_opts()?;
501            let (mut evm_env, _tx_env, mut executor) = crate::utils::get_tracing_executor::<FEN>(
502                &mut fork_config,
503                simulation_block - 1, // env.fork_block_number
504                etherscan_metadata.evm_version()?.unwrap_or(EvmVersion::default()),
505                evm_opts,
506            )
507            .await?;
508            evm_env.block_env.set_number(U256::from(simulation_block));
509            let block = provider.get_block(simulation_block.into()).full().await?;
510
511            // Workaround for the NonceTooHigh issue as we're not simulating prior txs of the same
512            // block.
513            let prev_block_id = BlockId::number(simulation_block - 1);
514
515            // Use `transaction.from` instead of `creation_data.contract_creator` to resolve
516            // blockscout creation data discrepancy in case of CREATE2.
517            let prev_block_nonce =
518                provider.get_transaction_count(transaction.from()).block_id(prev_block_id).await?;
519
520            if let Some(ref block) = block {
521                configure_env_block::<FEN>(&mut evm_env, block, config.networks);
522
523                let BlockTransactions::Full(txs) = block.transactions() else {
524                    return Err(eyre::eyre!("Could not get block txs"));
525                };
526
527                // Replay txes in block until the contract creation one.
528                for tx in txs {
529                    trace!("replay tx::: {}", tx.tx_hash());
530                    if is_known_system_sender(tx.from())
531                        || tx.transaction_type() == Some(SYSTEM_TRANSACTION_TYPE)
532                    {
533                        continue;
534                    }
535                    if tx.tx_hash() == tx_hash {
536                        break;
537                    }
538
539                    let tx_env = TxEnvFor::<FEN>::from_recovered_tx(tx.as_ref(), tx.from());
540
541                    if ConsensusTransaction::to(tx).is_some() {
542                        executor.transact_with_env(evm_env.clone(), tx_env.clone()).wrap_err_with(
543                            || {
544                                format!(
545                                    "Failed to execute transaction: {:?} in block {}",
546                                    tx.tx_hash(),
547                                    evm_env.block_env.number()
548                                )
549                            },
550                        )?;
551                    } else if let Err(error) =
552                        executor.deploy_with_env(evm_env.clone(), tx_env.clone(), None)
553                    {
554                        match error {
555                            // Reverted transactions should be skipped
556                            EvmError::Execution(_) => (),
557                            error => {
558                                return Err(error).wrap_err_with(|| {
559                                    format!(
560                                        "Failed to deploy transaction: {:?} in block {}",
561                                        tx.tx_hash(),
562                                        evm_env.block_env.number()
563                                    )
564                                });
565                            }
566                        }
567                    }
568                }
569            }
570
571            let kind = ConsensusTransaction::kind(&transaction);
572            let mut tx_env =
573                TxEnvFor::<FEN>::from_recovered_tx(transaction.as_ref(), transaction.from());
574            tx_env.set_nonce(prev_block_nonce);
575
576            // Replace the `input` with local creation code in the creation tx.
577            if let TxKind::Call(to) = kind {
578                if to == DEFAULT_CREATE2_DEPLOYER {
579                    let mut input = transaction.input()[..32].to_vec(); // Salt
580                    input.extend_from_slice(&local_bytecode_vec);
581                    tx_env.set_data(Bytes::from(input));
582
583                    // Deploy default CREATE2 deployer
584                    executor.deploy_create2_deployer()?;
585                }
586            } else {
587                tx_env.set_data(Bytes::from(local_bytecode_vec));
588            }
589
590            let fork_address = crate::utils::deploy_contract::<FEN>(
591                &mut executor,
592                &evm_env,
593                &tx_env,
594                config.evm_spec_id::<SpecFor<FEN>>(),
595                kind,
596            )?;
597
598            // State committed using deploy_with_env, now get the runtime bytecode from the db.
599            let (fork_runtime_code, onchain_runtime_code) = crate::utils::get_runtime_codes::<FEN>(
600                &mut executor,
601                &provider,
602                self.address,
603                fork_address,
604                Some(simulation_block),
605            )
606            .await?;
607
608            // Compare the onchain runtime bytecode with the runtime code from the fork.
609            let match_type = crate::utils::match_bytecodes(
610                fork_runtime_code.original_byte_slice(),
611                &onchain_runtime_code,
612                &constructor_args,
613                true,
614                config.bytecode_hash,
615            );
616
617            crate::utils::print_result(
618                match_type,
619                BytecodeType::Runtime,
620                &mut json_results,
621                etherscan_metadata,
622                &config,
623            );
624        }
625
626        if shell::is_json() {
627            sh_println!("{}", serde_json::to_string(&json_results)?)?;
628        }
629        Ok(())
630    }
631}
632
633#[cfg(test)]
634mod tests {
635    use super::*;
636
637    #[test]
638    fn can_parse_network() {
639        let args = VerifyBytecodeArgs::parse_from([
640            "foundry-cli",
641            "0x0000000000000000000000000000000000000000",
642            "src/Counter.sol:Counter",
643            "--network",
644            "tempo",
645        ]);
646
647        assert_eq!(args.network, Some(NetworkVariant::Tempo));
648    }
649
650    #[test]
651    fn configured_network_uses_config_network() {
652        let config = Config { networks: NetworkVariant::Tempo.into(), ..Default::default() };
653
654        assert_eq!(
655            VerifyBytecodeArgs::configured_network(None, &config),
656            Some(NetworkVariant::Tempo)
657        );
658    }
659
660    #[test]
661    fn configured_network_prefers_cli_network() {
662        let config = Config { networks: NetworkVariant::Tempo.into(), ..Default::default() };
663
664        assert_eq!(
665            VerifyBytecodeArgs::configured_network(Some(NetworkVariant::Ethereum), &config),
666            Some(NetworkVariant::Ethereum)
667        );
668    }
669}