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