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