forge_verify/
bytecode.rs

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