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