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_primitives::{Address, Bytes, TxKind, U256, hex};
11use alloy_provider::{
12    Provider,
13    ext::TraceApi,
14    network::{
15        AnyTxEnvelope, TransactionBuilder, TransactionResponse, primitives::BlockTransactions,
16    },
17};
18use alloy_rpc_types::{
19    BlockId, BlockNumberOrTag, TransactionInput, TransactionRequest, TransactionTrait,
20    trace::parity::{Action, CreateAction, CreateOutput, TraceOutput},
21};
22use clap::{Parser, ValueHint};
23use eyre::{Context, OptionExt, Result};
24use foundry_cli::{
25    opts::EtherscanOpts,
26    utils::{self, LoadConfig, read_constructor_args_file},
27};
28use foundry_common::{SYSTEM_TRANSACTION_TYPE, is_known_system_sender, shell};
29use foundry_compilers::{artifacts::EvmVersion, info::ContractInfo};
30use foundry_config::{Config, figment, impl_figment_convert};
31use foundry_evm::{
32    constants::DEFAULT_CREATE2_DEPLOYER,
33    core::AsEnvMut,
34    executors::EvmError,
35    utils::{configure_tx_env, configure_tx_req_env},
36};
37use revm::state::AccountInfo;
38use std::path::PathBuf;
39
40impl_figment_convert!(VerifyBytecodeArgs);
41
42/// CLI arguments for `forge verify-bytecode`.
43#[derive(Clone, Debug, Parser)]
44pub struct VerifyBytecodeArgs {
45    /// The address of the contract to verify.
46    pub address: Address,
47
48    /// The contract identifier in the form `<path>:<contractname>`.
49    pub contract: ContractInfo,
50
51    /// The block at which the bytecode should be verified.
52    #[arg(long, value_name = "BLOCK")]
53    pub block: Option<BlockId>,
54
55    /// The constructor args to generate the creation code.
56    #[arg(
57        long,
58        num_args(1..),
59        conflicts_with_all = &["constructor_args_path", "encoded_constructor_args"],
60        value_name = "ARGS",
61    )]
62    pub constructor_args: Option<Vec<String>>,
63
64    /// The ABI-encoded constructor arguments.
65    #[arg(
66        long,
67        conflicts_with_all = &["constructor_args_path", "constructor_args"],
68        value_name = "HEX",
69    )]
70    pub encoded_constructor_args: Option<String>,
71
72    /// The path to a file containing the constructor arguments.
73    #[arg(
74        long,
75        value_hint = ValueHint::FilePath,
76        value_name = "PATH",
77        conflicts_with_all = &["constructor_args", "encoded_constructor_args"]
78    )]
79    pub constructor_args_path: Option<PathBuf>,
80
81    /// The rpc url to use for verification.
82    #[arg(short = 'r', long, value_name = "RPC_URL", env = "ETH_RPC_URL")]
83    pub rpc_url: Option<String>,
84
85    /// Etherscan options.
86    #[command(flatten)]
87    pub etherscan: EtherscanOpts,
88
89    /// Verifier options.
90    #[command(flatten)]
91    pub verifier: VerifierArgs,
92
93    /// The project's root path.
94    ///
95    /// By default root of the Git repository, if in one,
96    /// or the current working directory.
97    #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")]
98    pub root: Option<PathBuf>,
99
100    /// Ignore verification for creation or runtime bytecode.
101    #[arg(long, value_name = "BYTECODE_TYPE")]
102    pub ignore: Option<BytecodeType>,
103}
104
105impl figment::Provider for VerifyBytecodeArgs {
106    fn metadata(&self) -> figment::Metadata {
107        figment::Metadata::named("Verify Bytecode Provider")
108    }
109
110    fn data(
111        &self,
112    ) -> Result<figment::value::Map<figment::Profile, figment::value::Dict>, figment::Error> {
113        let mut dict = self.etherscan.dict();
114
115        if let Some(api_key) = &self.verifier.verifier_api_key {
116            dict.insert("etherscan_api_key".into(), api_key.as_str().into());
117        }
118
119        if let Some(block) = &self.block {
120            dict.insert("block".into(), figment::value::Value::serialize(block)?);
121        }
122        if let Some(rpc_url) = &self.rpc_url {
123            dict.insert("eth_rpc_url".into(), rpc_url.to_string().into());
124        }
125
126        Ok(figment::value::Map::from([(Config::selected_profile(), dict)]))
127    }
128}
129
130impl VerifyBytecodeArgs {
131    /// Run the `verify-bytecode` command to verify the bytecode onchain against the locally built
132    /// bytecode.
133    pub async fn run(mut self) -> Result<()> {
134        // Setup
135        let config = self.load_config()?;
136        let provider = utils::get_provider(&config)?;
137
138        // If chain is not set, we try to get it from the RPC.
139        // If RPC is not set, the default chain is used.
140        let chain = match config.get_rpc_url() {
141            Some(_) => utils::get_chain(config.chain, &provider).await?,
142            None => config.chain.unwrap_or_default(),
143        };
144
145        // Set Etherscan options.
146        self.etherscan.chain = Some(chain);
147        self.etherscan.key = config.get_etherscan_config_with_chain(Some(chain))?.map(|c| c.key);
148
149        // Etherscan client
150        let etherscan =
151            EtherscanVerificationProvider.client(&self.etherscan, &self.verifier, &config)?;
152
153        // Get the bytecode at the address, bailing if it doesn't exist.
154        let code = provider.get_code_at(self.address).await?;
155        if code.is_empty() {
156            eyre::bail!("No bytecode found at address {}", self.address);
157        }
158
159        if !shell::is_json() {
160            sh_println!(
161                "Verifying bytecode for contract {} at address {}",
162                self.contract.name,
163                self.address
164            )?;
165        }
166
167        let mut json_results: Vec<JsonResult> = vec![];
168
169        // Get creation tx hash.
170        let creation_data = etherscan.contract_creation_data(self.address).await;
171
172        // Check if contract is a predeploy
173        let (creation_data, maybe_predeploy) = maybe_predeploy_contract(creation_data)?;
174
175        trace!(maybe_predeploy = ?maybe_predeploy);
176
177        // Get the constructor args using `source_code` endpoint.
178        let source_code = etherscan.contract_source_code(self.address).await?;
179
180        // Check if the contract name matches.
181        let name = source_code.items.first().map(|item| item.contract_name.to_owned());
182        if name.as_ref() != Some(&self.contract.name) {
183            eyre::bail!("Contract name mismatch");
184        }
185
186        // Obtain Etherscan compilation metadata.
187        let etherscan_metadata = source_code.items.first().unwrap();
188
189        // Obtain local artifact
190        let artifact = if let Ok(local_bytecode) =
191            crate::utils::build_using_cache(&self, etherscan_metadata, &config)
192        {
193            trace!("using cache");
194            local_bytecode
195        } else {
196            crate::utils::build_project(&self, &config)?
197        };
198
199        // Get local bytecode (creation code)
200        let local_bytecode = artifact
201            .bytecode
202            .as_ref()
203            .and_then(|b| b.to_owned().into_bytes())
204            .ok_or_eyre("Unlinked bytecode is not supported for verification")?;
205
206        // Get and encode user provided constructor args
207        let provided_constructor_args = if let Some(path) = self.constructor_args_path.to_owned() {
208            // Read from file
209            Some(read_constructor_args_file(path)?)
210        } else {
211            self.constructor_args.to_owned()
212        }
213        .map(|args| check_and_encode_args(&artifact, args))
214        .transpose()?
215        .or(self.encoded_constructor_args.to_owned().map(hex::decode).transpose()?);
216
217        let mut constructor_args = if let Some(provided) = provided_constructor_args {
218            provided.into()
219        } else {
220            // If no constructor args were provided, try to retrieve them from the explorer.
221            check_explorer_args(source_code.clone())?
222        };
223
224        // This fails only when the contract expects constructor args but NONE were provided OR
225        // retrieved from explorer (in case of predeploys).
226        crate::utils::check_args_len(&artifact, &constructor_args)?;
227
228        if maybe_predeploy {
229            if !shell::is_json() {
230                sh_warn!(
231                    "Attempting to verify predeployed contract at {:?}. Ignoring creation code verification.",
232                    self.address
233                )?;
234            }
235
236            // Append constructor args to the local_bytecode.
237            trace!(%constructor_args);
238            let mut local_bytecode_vec = local_bytecode.to_vec();
239            local_bytecode_vec.extend_from_slice(&constructor_args);
240
241            // Deploy at genesis
242            let gen_blk_num = 0_u64;
243            let (mut fork_config, evm_opts) = config.clone().load_config_and_evm_opts()?;
244            let (mut env, mut executor) = crate::utils::get_tracing_executor(
245                &mut fork_config,
246                gen_blk_num,
247                etherscan_metadata.evm_version()?.unwrap_or(EvmVersion::default()),
248                evm_opts,
249            )
250            .await?;
251
252            env.evm_env.block_env.number = U256::ZERO;
253            let genesis_block = provider.get_block(gen_blk_num.into()).full().await?;
254
255            // Setup genesis tx and env.
256            let deployer = Address::with_last_byte(0x1);
257            let mut gen_tx_req = TransactionRequest::default()
258                .with_from(deployer)
259                .with_input(Bytes::from(local_bytecode_vec))
260                .into_create();
261
262            if let Some(ref block) = genesis_block {
263                configure_env_block(&mut env.as_env_mut(), block, config.networks);
264                gen_tx_req.max_fee_per_gas = block.header.base_fee_per_gas.map(|g| g as u128);
265                gen_tx_req.gas = Some(block.header.gas_limit);
266                gen_tx_req.gas_price = block.header.base_fee_per_gas.map(|g| g as u128);
267            }
268
269            configure_tx_req_env(&mut env.as_env_mut(), &gen_tx_req, None)
270                .wrap_err("Failed to configure tx request env")?;
271
272            // Seed deployer account with funds
273            let account_info = AccountInfo {
274                balance: U256::from(100 * 10_u128.pow(18)),
275                nonce: 0,
276                ..Default::default()
277            };
278            executor.backend_mut().insert_account_info(deployer, account_info);
279
280            let fork_address = crate::utils::deploy_contract(
281                &mut executor,
282                &env,
283                config.evm_spec_id(),
284                gen_tx_req.to,
285            )?;
286
287            // Compare runtime bytecode
288            let (deployed_bytecode, onchain_runtime_code) = crate::utils::get_runtime_codes(
289                &mut executor,
290                &provider,
291                self.address,
292                fork_address,
293                None,
294            )
295            .await?;
296
297            let match_type = crate::utils::match_bytecodes(
298                deployed_bytecode.original_byte_slice(),
299                &onchain_runtime_code,
300                &constructor_args,
301                true,
302                config.bytecode_hash,
303            );
304
305            crate::utils::print_result(
306                match_type,
307                BytecodeType::Runtime,
308                &mut json_results,
309                etherscan_metadata,
310                &config,
311            );
312
313            if shell::is_json() {
314                sh_println!("{}", serde_json::to_string(&json_results)?)?;
315            }
316
317            return Ok(());
318        }
319
320        // We can unwrap directly as maybe_predeploy is false
321        let creation_data = creation_data.unwrap();
322        // Get transaction and receipt.
323        trace!(creation_tx_hash = ?creation_data.transaction_hash);
324        let transaction = provider
325            .get_transaction_by_hash(creation_data.transaction_hash)
326            .await
327            .or_else(|e| eyre::bail!("Couldn't fetch transaction from RPC: {:?}", e))?
328            .ok_or_else(|| {
329                eyre::eyre!("Transaction not found for hash {}", creation_data.transaction_hash)
330            })?;
331        let tx_hash = transaction.tx_hash();
332        let receipt = provider
333            .get_transaction_receipt(creation_data.transaction_hash)
334            .await
335            .or_else(|e| eyre::bail!("Couldn't fetch transaction receipt from RPC: {:?}", e))?;
336        let receipt = if let Some(receipt) = receipt {
337            receipt
338        } else {
339            eyre::bail!(
340                "Receipt not found for transaction hash {}",
341                creation_data.transaction_hash
342            );
343        };
344
345        let mut transaction: TransactionRequest = match transaction.inner.inner.inner() {
346            AnyTxEnvelope::Ethereum(tx) => tx.clone().into(),
347            AnyTxEnvelope::Unknown(_) => unreachable!("Unknown transaction type"),
348        };
349
350        // Extract creation code from creation tx input.
351        let maybe_creation_code = if receipt.to.is_none()
352            && receipt.contract_address == Some(self.address)
353        {
354            match &transaction.input.input {
355                Some(input) => &input[..],
356                None => unreachable!("creation tx input is None"),
357            }
358        } else if receipt.to == Some(DEFAULT_CREATE2_DEPLOYER) {
359            match &transaction.input.input {
360                Some(input) => &input[32..],
361                None => unreachable!("creation tx input is None"),
362            }
363        } else {
364            // Try to get creation bytecode from tx trace.
365            let traces = provider
366                .trace_transaction(creation_data.transaction_hash)
367                .await
368                .unwrap_or_default();
369
370            let creation_bytecode =
371                traces.iter().find_map(|trace| match (&trace.trace.result, &trace.trace.action) {
372                    (
373                        Some(TraceOutput::Create(CreateOutput { address, .. })),
374                        Action::Create(CreateAction { init, .. }),
375                    ) if *address == self.address => Some(init.clone()),
376                    _ => None,
377                });
378
379            &creation_bytecode.ok_or_else(|| {
380                eyre::eyre!(
381                    "Could not extract the creation code for contract at address {}",
382                    self.address
383                )
384            })?
385        };
386
387        // In some cases, Etherscan will return incorrect constructor arguments. If this
388        // happens, try extracting arguments ourselves.
389        if !maybe_creation_code.ends_with(&constructor_args) {
390            trace!("mismatch of constructor args with etherscan");
391            // If local bytecode is longer than on-chain one, this is probably not a match.
392            if maybe_creation_code.len() >= local_bytecode.len() {
393                constructor_args =
394                    Bytes::copy_from_slice(&maybe_creation_code[local_bytecode.len()..]);
395                trace!(
396                    target: "forge::verify",
397                    "setting constructor args to latest {} bytes of bytecode",
398                    constructor_args.len()
399                );
400            }
401        }
402
403        // Append constructor args to the local_bytecode.
404        trace!(%constructor_args);
405        let mut local_bytecode_vec = local_bytecode.to_vec();
406        local_bytecode_vec.extend_from_slice(&constructor_args);
407
408        trace!(ignore = ?self.ignore);
409        // Check if `--ignore` is set to `creation`.
410        if !self.ignore.is_some_and(|b| b.is_creation()) {
411            // Compare creation code with locally built bytecode and `maybe_creation_code`.
412            let match_type = crate::utils::match_bytecodes(
413                local_bytecode_vec.as_slice(),
414                maybe_creation_code,
415                &constructor_args,
416                false,
417                config.bytecode_hash,
418            );
419
420            crate::utils::print_result(
421                match_type,
422                BytecodeType::Creation,
423                &mut json_results,
424                etherscan_metadata,
425                &config,
426            );
427
428            // If the creation code does not match, the runtime also won't match. Hence return.
429            if match_type.is_none() {
430                crate::utils::print_result(
431                    None,
432                    BytecodeType::Runtime,
433                    &mut json_results,
434                    etherscan_metadata,
435                    &config,
436                );
437                if shell::is_json() {
438                    sh_println!("{}", serde_json::to_string(&json_results)?)?;
439                }
440                return Ok(());
441            }
442        }
443
444        if !self.ignore.is_some_and(|b| b.is_runtime()) {
445            // Get contract creation block.
446            let simulation_block = match self.block {
447                Some(BlockId::Number(BlockNumberOrTag::Number(block))) => block,
448                Some(_) => eyre::bail!("Invalid block number"),
449                None => {
450                    let provider = utils::get_provider(&config)?;
451                    provider
452                    .get_transaction_by_hash(creation_data.transaction_hash)
453                    .await.or_else(|e| eyre::bail!("Couldn't fetch transaction from RPC: {:?}", e))?.ok_or_else(|| {
454                        eyre::eyre!("Transaction not found for hash {}", creation_data.transaction_hash)
455                    })?
456                    .block_number.ok_or_else(|| {
457                        eyre::eyre!("Failed to get block number of the contract creation tx, specify using the --block flag")
458                    })?
459                }
460            };
461
462            // Fork the chain at `simulation_block`.
463            let (mut fork_config, evm_opts) = config.clone().load_config_and_evm_opts()?;
464            let (mut env, mut executor) = crate::utils::get_tracing_executor(
465                &mut fork_config,
466                simulation_block - 1, // env.fork_block_number
467                etherscan_metadata.evm_version()?.unwrap_or(EvmVersion::default()),
468                evm_opts,
469            )
470            .await?;
471            env.evm_env.block_env.number = U256::from(simulation_block);
472            let block = provider.get_block(simulation_block.into()).full().await?;
473
474            // Workaround for the NonceTooHigh issue as we're not simulating prior txs of the same
475            // block.
476            let prev_block_id = BlockId::number(simulation_block - 1);
477
478            // Use `transaction.from` instead of `creation_data.contract_creator` to resolve
479            // blockscout creation data discrepancy in case of CREATE2.
480            let prev_block_nonce = provider
481                .get_transaction_count(transaction.from.unwrap())
482                .block_id(prev_block_id)
483                .await?;
484            transaction.set_nonce(prev_block_nonce);
485
486            if let Some(ref block) = block {
487                configure_env_block(&mut env.as_env_mut(), block, config.networks);
488
489                let BlockTransactions::Full(ref txs) = block.transactions else {
490                    return Err(eyre::eyre!("Could not get block txs"));
491                };
492
493                // Replay txes in block until the contract creation one.
494                for tx in txs {
495                    trace!("replay tx::: {}", tx.tx_hash());
496                    if is_known_system_sender(tx.from())
497                        || tx.transaction_type() == Some(SYSTEM_TRANSACTION_TYPE)
498                    {
499                        continue;
500                    }
501                    if tx.tx_hash() == tx_hash {
502                        break;
503                    }
504
505                    configure_tx_env(&mut env.as_env_mut(), &tx.inner);
506
507                    if let TxKind::Call(_) = tx.inner.kind() {
508                        executor.transact_with_env(env.clone()).wrap_err_with(|| {
509                            format!(
510                                "Failed to execute transaction: {:?} in block {}",
511                                tx.tx_hash(),
512                                env.evm_env.block_env.number
513                            )
514                        })?;
515                    } else if let Err(error) = executor.deploy_with_env(env.clone(), None) {
516                        match error {
517                            // Reverted transactions should be skipped
518                            EvmError::Execution(_) => (),
519                            error => {
520                                return Err(error).wrap_err_with(|| {
521                                    format!(
522                                        "Failed to deploy transaction: {:?} in block {}",
523                                        tx.tx_hash(),
524                                        env.evm_env.block_env.number
525                                    )
526                                });
527                            }
528                        }
529                    }
530                }
531            }
532
533            // Replace the `input` with local creation code in the creation tx.
534            if let Some(TxKind::Call(to)) = transaction.kind() {
535                if to == DEFAULT_CREATE2_DEPLOYER {
536                    let mut input = transaction.input.input.unwrap()[..32].to_vec(); // Salt
537                    input.extend_from_slice(&local_bytecode_vec);
538                    transaction.input = TransactionInput::both(Bytes::from(input));
539
540                    // Deploy default CREATE2 deployer
541                    executor.deploy_create2_deployer()?;
542                }
543            } else {
544                transaction.input = TransactionInput::both(Bytes::from(local_bytecode_vec));
545            }
546
547            // configure_req__env(&mut env, &transaction.inner);
548            configure_tx_req_env(&mut env.as_env_mut(), &transaction, None)
549                .wrap_err("Failed to configure tx request env")?;
550
551            let fork_address = crate::utils::deploy_contract(
552                &mut executor,
553                &env,
554                config.evm_spec_id(),
555                transaction.to,
556            )?;
557
558            // State committed using deploy_with_env, now get the runtime bytecode from the db.
559            let (fork_runtime_code, onchain_runtime_code) = crate::utils::get_runtime_codes(
560                &mut executor,
561                &provider,
562                self.address,
563                fork_address,
564                Some(simulation_block),
565            )
566            .await?;
567
568            // Compare the onchain runtime bytecode with the runtime code from the fork.
569            let match_type = crate::utils::match_bytecodes(
570                fork_runtime_code.original_byte_slice(),
571                &onchain_runtime_code,
572                &constructor_args,
573                true,
574                config.bytecode_hash,
575            );
576
577            crate::utils::print_result(
578                match_type,
579                BytecodeType::Runtime,
580                &mut json_results,
581                etherscan_metadata,
582                &config,
583            );
584        }
585
586        if shell::is_json() {
587            sh_println!("{}", serde_json::to_string(&json_results)?)?;
588        }
589        Ok(())
590    }
591}