1use 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#[derive(Clone, Debug, Parser)]
41pub struct VerifyBytecodeArgs {
42 pub address: Address,
44
45 pub contract: ContractInfo,
47
48 #[arg(long, value_name = "BLOCK")]
50 pub block: Option<BlockId>,
51
52 #[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 #[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 #[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 #[arg(short = 'r', long, value_name = "RPC_URL", env = "ETH_RPC_URL")]
80 pub rpc_url: Option<String>,
81
82 #[command(flatten)]
84 pub etherscan: EtherscanOpts,
85
86 #[command(flatten)]
88 pub verifier: VerifierArgs,
89
90 #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")]
95 pub root: Option<PathBuf>,
96
97 #[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 pub async fn run(mut self) -> Result<()> {
131 let config = self.load_config()?;
133 let provider = utils::get_provider(&config)?;
134
135 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 self.etherscan.chain = Some(chain);
144 self.etherscan.key = config.get_etherscan_config_with_chain(Some(chain))?.map(|c| c.key);
145
146 let etherscan =
148 EtherscanVerificationProvider.client(&self.etherscan, &self.verifier, &config)?;
149
150 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 let creation_data = etherscan.contract_creation_data(self.address).await;
168
169 let (creation_data, maybe_predeploy) = maybe_predeploy_contract(creation_data)?;
171
172 trace!(maybe_predeploy = ?maybe_predeploy);
173
174 let source_code = etherscan.contract_source_code(self.address).await?;
176
177 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 let etherscan_metadata = source_code.items.first().unwrap();
185
186 let artifact = crate::utils::build_project(&self, &config)?;
188
189 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 let provided_constructor_args = if let Some(path) = self.constructor_args_path.to_owned() {
198 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 check_explorer_args(source_code.clone())?
212 };
213
214 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 trace!(%constructor_args);
228 let mut local_bytecode_vec = local_bytecode.to_vec();
229 local_bytecode_vec.extend_from_slice(&constructor_args);
230
231 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 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 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 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 let creation_data = creation_data.unwrap();
313 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 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 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 if !maybe_creation_code.ends_with(&constructor_args) {
382 trace!("mismatch of constructor args with etherscan");
383 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 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 if !self.ignore.is_some_and(|b| b.is_creation()) {
403 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 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 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 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, 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 let prev_block_id = BlockId::number(simulation_block - 1);
463
464 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 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 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 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(); input.extend_from_slice(&local_bytecode_vec);
530 transaction.input = TransactionInput::both(Bytes::from(input));
531
532 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 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 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}