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_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#[derive(Clone, Debug, Parser)]
39pub struct VerifyBytecodeArgs {
40 pub address: Address,
42
43 pub contract: ContractInfo,
45
46 #[arg(long, value_name = "BLOCK")]
48 pub block: Option<BlockId>,
49
50 #[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 #[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 #[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 #[arg(short = 'r', long, value_name = "RPC_URL", env = "ETH_RPC_URL")]
78 pub rpc_url: Option<String>,
79
80 #[command(flatten)]
82 pub etherscan: EtherscanOpts,
83
84 #[command(flatten)]
86 pub verifier: VerifierArgs,
87
88 #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")]
93 pub root: Option<PathBuf>,
94
95 #[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 pub async fn run(mut self) -> Result<()> {
129 let config = self.load_config()?;
131 let provider = utils::get_provider(&config)?;
132
133 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 self.etherscan.chain = Some(chain);
142 self.etherscan.key = config.get_etherscan_config_with_chain(Some(chain))?.map(|c| c.key);
143
144 let etherscan =
146 EtherscanVerificationProvider.client(&self.etherscan, &self.verifier, &config)?;
147
148 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 let creation_data = etherscan.contract_creation_data(self.address).await;
166
167 let (creation_data, maybe_predeploy) = maybe_predeploy_contract(creation_data)?;
169
170 trace!(maybe_predeploy = ?maybe_predeploy);
171
172 let source_code = etherscan.contract_source_code(self.address).await?;
174
175 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 let etherscan_metadata = source_code.items.first().unwrap();
183
184 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 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 let provided_constructor_args = if let Some(path) = self.constructor_args_path.to_owned() {
203 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 check_explorer_args(source_code.clone())?
217 };
218
219 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 trace!(%constructor_args);
233 let mut local_bytecode_vec = local_bytecode.to_vec();
234 local_bytecode_vec.extend_from_slice(&constructor_args);
235
236 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 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 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 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 let creation_data = creation_data.unwrap();
317 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 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 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 if !maybe_creation_code.ends_with(&constructor_args) {
384 trace!("mismatch of constructor args with etherscan");
385 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 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 if !self.ignore.is_some_and(|b| b.is_creation()) {
405 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 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 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 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, 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 let prev_block_id = BlockId::number(simulation_block - 1);
471
472 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 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(); input.extend_from_slice(&local_bytecode_vec);
489 transaction.input = TransactionInput::both(Bytes::from(input));
490
491 executor.deploy_create2_deployer()?;
493 }
494 } else {
495 transaction.input = TransactionInput::both(Bytes::from(local_bytecode_vec));
496 }
497
498 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 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 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}