1use 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, 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 revm_primitives::{AccountInfo, TxKind};
27use std::path::PathBuf;
28
29impl_figment_convert!(VerifyBytecodeArgs);
30
31#[derive(Clone, Debug, Parser)]
33pub struct VerifyBytecodeArgs {
34 pub address: Address,
36
37 pub contract: ContractInfo,
39
40 #[arg(long, value_name = "BLOCK")]
42 pub block: Option<BlockId>,
43
44 #[arg(
46 long,
47 num_args(1..),
48 conflicts_with_all = &["constructor_args_path", "encoded_constructor_args"],
49 value_name = "ARGS",
50 )]
51 pub constructor_args: Option<Vec<String>>,
52
53 #[arg(
55 long,
56 conflicts_with_all = &["constructor_args_path", "constructor_args"],
57 value_name = "HEX",
58 )]
59 pub encoded_constructor_args: Option<String>,
60
61 #[arg(
63 long,
64 value_hint = ValueHint::FilePath,
65 value_name = "PATH",
66 conflicts_with_all = &["constructor_args", "encoded_constructor_args"]
67 )]
68 pub constructor_args_path: Option<PathBuf>,
69
70 #[arg(short = 'r', long, value_name = "RPC_URL", env = "ETH_RPC_URL")]
72 pub rpc_url: Option<String>,
73
74 #[command(flatten)]
76 pub etherscan: EtherscanOpts,
77
78 #[command(flatten)]
80 pub verifier: VerifierArgs,
81
82 #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")]
87 pub root: Option<PathBuf>,
88
89 #[arg(long, value_name = "BYTECODE_TYPE")]
91 pub ignore: Option<BytecodeType>,
92}
93
94impl figment::Provider for VerifyBytecodeArgs {
95 fn metadata(&self) -> figment::Metadata {
96 figment::Metadata::named("Verify Bytecode Provider")
97 }
98
99 fn data(
100 &self,
101 ) -> Result<figment::value::Map<figment::Profile, figment::value::Dict>, figment::Error> {
102 let mut dict = self.etherscan.dict();
103
104 if let Some(api_key) = &self.verifier.verifier_api_key {
105 dict.insert("etherscan_api_key".into(), api_key.as_str().into());
106 }
107
108 if let Some(block) = &self.block {
109 dict.insert("block".into(), figment::value::Value::serialize(block)?);
110 }
111 if let Some(rpc_url) = &self.rpc_url {
112 dict.insert("eth_rpc_url".into(), rpc_url.to_string().into());
113 }
114
115 Ok(figment::value::Map::from([(Config::selected_profile(), dict)]))
116 }
117}
118
119impl VerifyBytecodeArgs {
120 pub async fn run(mut self) -> Result<()> {
123 let config = self.load_config()?;
125 let provider = utils::get_provider(&config)?;
126
127 let chain = match config.get_rpc_url() {
130 Some(_) => utils::get_chain(config.chain, &provider).await?,
131 None => config.chain.unwrap_or_default(),
132 };
133
134 self.etherscan.chain = Some(chain);
136 self.etherscan.key = config.get_etherscan_config_with_chain(Some(chain))?.map(|c| c.key);
137
138 let etherscan = EtherscanVerificationProvider.client(
140 self.etherscan.chain.unwrap_or_default(),
141 &self.verifier.verifier,
142 self.verifier.verifier_url.as_deref(),
143 self.etherscan.key().as_deref(),
144 &config,
145 )?;
146
147 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 let creation_data = etherscan.contract_creation_data(self.address).await;
165
166 let (creation_data, maybe_predeploy) = maybe_predeploy_contract(creation_data)?;
168
169 trace!(maybe_predeploy = ?maybe_predeploy);
170
171 let source_code = etherscan.contract_source_code(self.address).await?;
173
174 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 let etherscan_metadata = source_code.items.first().unwrap();
182
183 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 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 let provided_constructor_args = if let Some(path) = self.constructor_args_path.to_owned() {
202 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 check_explorer_args(source_code.clone())?
216 };
217
218 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 trace!(%constructor_args);
232 let mut local_bytecode_vec = local_bytecode.to_vec();
233 local_bytecode_vec.extend_from_slice(&constructor_args);
234
235 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.block.number = U256::ZERO; let genesis_block = provider.get_block(gen_blk_num.into()).full().await?;
248
249 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, 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, &gen_tx_req, None)
266 .wrap_err("Failed to configure tx request env")?;
267
268 let account_info = AccountInfo {
270 balance: U256::from(100 * 10_u128.pow(18)),
271 nonce: 0,
272 ..Default::default()
273 };
274 executor.backend_mut().insert_account_info(deployer, account_info);
275
276 let fork_address = crate::utils::deploy_contract(
277 &mut executor,
278 &env,
279 config.evm_spec_id(),
280 gen_tx_req.to,
281 )?;
282
283 let (deployed_bytecode, onchain_runtime_code) = crate::utils::get_runtime_codes(
285 &mut executor,
286 &provider,
287 self.address,
288 fork_address,
289 None,
290 )
291 .await?;
292
293 let match_type = crate::utils::match_bytecodes(
294 &deployed_bytecode.original_bytes(),
295 &onchain_runtime_code,
296 &constructor_args,
297 true,
298 config.bytecode_hash,
299 );
300
301 crate::utils::print_result(
302 match_type,
303 BytecodeType::Runtime,
304 &mut json_results,
305 etherscan_metadata,
306 &config,
307 );
308
309 if shell::is_json() {
310 sh_println!("{}", serde_json::to_string(&json_results)?)?;
311 }
312
313 return Ok(());
314 }
315
316 let creation_data = creation_data.unwrap();
318 trace!(creation_tx_hash = ?creation_data.transaction_hash);
320 let transaction = provider
321 .get_transaction_by_hash(creation_data.transaction_hash)
322 .await
323 .or_else(|e| eyre::bail!("Couldn't fetch transaction from RPC: {:?}", e))?
324 .ok_or_else(|| {
325 eyre::eyre!("Transaction not found for hash {}", creation_data.transaction_hash)
326 })?;
327 let receipt = provider
328 .get_transaction_receipt(creation_data.transaction_hash)
329 .await
330 .or_else(|e| eyre::bail!("Couldn't fetch transaction receipt from RPC: {:?}", e))?;
331 let receipt = if let Some(receipt) = receipt {
332 receipt
333 } else {
334 eyre::bail!(
335 "Receipt not found for transaction hash {}",
336 creation_data.transaction_hash
337 );
338 };
339
340 let mut transaction: TransactionRequest = match transaction.inner.inner.inner() {
341 AnyTxEnvelope::Ethereum(tx) => tx.clone().into(),
342 AnyTxEnvelope::Unknown(_) => unreachable!("Unknown transaction type"),
343 };
344
345 let maybe_creation_code =
347 if receipt.to.is_none() && receipt.contract_address == Some(self.address) {
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 eyre::bail!(
359 "Could not extract the creation code for contract at address {}",
360 self.address
361 );
362 };
363
364 if !maybe_creation_code.ends_with(&constructor_args) {
367 trace!("mismatch of constructor args with etherscan");
368 if maybe_creation_code.len() >= local_bytecode.len() {
370 constructor_args =
371 Bytes::copy_from_slice(&maybe_creation_code[local_bytecode.len()..]);
372 trace!(
373 target: "forge::verify",
374 "setting constructor args to latest {} bytes of bytecode",
375 constructor_args.len()
376 );
377 }
378 }
379
380 trace!(%constructor_args);
382 let mut local_bytecode_vec = local_bytecode.to_vec();
383 local_bytecode_vec.extend_from_slice(&constructor_args);
384
385 trace!(ignore = ?self.ignore);
386 if !self.ignore.is_some_and(|b| b.is_creation()) {
388 let match_type = crate::utils::match_bytecodes(
390 local_bytecode_vec.as_slice(),
391 maybe_creation_code,
392 &constructor_args,
393 false,
394 config.bytecode_hash,
395 );
396
397 crate::utils::print_result(
398 match_type,
399 BytecodeType::Creation,
400 &mut json_results,
401 etherscan_metadata,
402 &config,
403 );
404
405 if match_type.is_none() {
407 crate::utils::print_result(
408 None,
409 BytecodeType::Runtime,
410 &mut json_results,
411 etherscan_metadata,
412 &config,
413 );
414 if shell::is_json() {
415 sh_println!("{}", serde_json::to_string(&json_results)?)?;
416 }
417 return Ok(());
418 }
419 }
420
421 if !self.ignore.is_some_and(|b| b.is_runtime()) {
422 let simulation_block = match self.block {
424 Some(BlockId::Number(BlockNumberOrTag::Number(block))) => block,
425 Some(_) => eyre::bail!("Invalid block number"),
426 None => {
427 let provider = utils::get_provider(&config)?;
428 provider
429 .get_transaction_by_hash(creation_data.transaction_hash)
430 .await.or_else(|e| eyre::bail!("Couldn't fetch transaction from RPC: {:?}", e))?.ok_or_else(|| {
431 eyre::eyre!("Transaction not found for hash {}", creation_data.transaction_hash)
432 })?
433 .block_number.ok_or_else(|| {
434 eyre::eyre!("Failed to get block number of the contract creation tx, specify using the --block flag")
435 })?
436 }
437 };
438
439 let (mut fork_config, evm_opts) = config.clone().load_config_and_evm_opts()?;
441 let (mut env, mut executor) = crate::utils::get_tracing_executor(
442 &mut fork_config,
443 simulation_block - 1, etherscan_metadata.evm_version()?.unwrap_or(EvmVersion::default()),
445 evm_opts,
446 )
447 .await?;
448 env.block.number = U256::from(simulation_block);
449 let block = provider.get_block(simulation_block.into()).full().await?;
450
451 let prev_block_id = BlockId::number(simulation_block - 1);
454
455 let prev_block_nonce = provider
458 .get_transaction_count(transaction.from.unwrap())
459 .block_id(prev_block_id)
460 .await?;
461 transaction.set_nonce(prev_block_nonce);
462
463 if let Some(ref block) = block {
464 configure_env_block(&mut env, block)
465 }
466
467 if let Some(TxKind::Call(to)) = transaction.kind() {
469 if to == DEFAULT_CREATE2_DEPLOYER {
470 let mut input = transaction.input.input.unwrap()[..32].to_vec(); input.extend_from_slice(&local_bytecode_vec);
472 transaction.input = TransactionInput::both(Bytes::from(input));
473
474 executor.deploy_create2_deployer()?;
476 }
477 } else {
478 transaction.input = TransactionInput::both(Bytes::from(local_bytecode_vec));
479 }
480
481 configure_tx_req_env(&mut env, &transaction, None)
483 .wrap_err("Failed to configure tx request env")?;
484
485 let fork_address = crate::utils::deploy_contract(
486 &mut executor,
487 &env,
488 config.evm_spec_id(),
489 transaction.to,
490 )?;
491
492 let (fork_runtime_code, onchain_runtime_code) = crate::utils::get_runtime_codes(
494 &mut executor,
495 &provider,
496 self.address,
497 fork_address,
498 Some(simulation_block),
499 )
500 .await?;
501
502 let match_type = crate::utils::match_bytecodes(
504 &fork_runtime_code.original_bytes(),
505 &onchain_runtime_code,
506 &constructor_args,
507 true,
508 config.bytecode_hash,
509 );
510
511 crate::utils::print_result(
512 match_type,
513 BytecodeType::Runtime,
514 &mut json_results,
515 etherscan_metadata,
516 &config,
517 );
518 }
519
520 if shell::is_json() {
521 sh_println!("{}", serde_json::to_string(&json_results)?)?;
522 }
523 Ok(())
524 }
525}