1use crate::{
3 etherscan::EtherscanVerificationProvider,
4 utils::{
5 BytecodeType, JsonResult, check_and_encode_args, check_explorer_args,
6 load_fork_config_and_evm_opts, maybe_predeploy_contract, validate_encoded_constructor_args,
7 },
8 verify::VerifierArgs,
9};
10use alloy_consensus::Transaction as ConsensusTransaction;
11use alloy_network::{AnyNetwork, AnyRpcBlock, AnyRpcTransaction};
12use alloy_primitives::{Address, B256, Bytes, TxKind, U256, hex};
13use alloy_provider::{
14 Provider,
15 ext::TraceApi,
16 network::{BlockResponse, ReceiptResponse, TransactionResponse, primitives::BlockTransactions},
17};
18use alloy_rpc_types::{
19 BlockId, BlockNumberOrTag,
20 trace::parity::{Action, CreateAction, CreateOutput, TraceOutput},
21};
22use clap::{Parser, ValueHint};
23use eyre::{Context, OptionExt, Result};
24use foundry_block_explorers::contract::Metadata;
25use foundry_cli::{
26 opts::EtherscanOpts,
27 utils::{self, LoadConfig, read_constructor_args_file},
28};
29use foundry_common::{
30 SYSTEM_TRANSACTION_TYPE, is_known_system_sender,
31 provider::{ProviderBuilder, RetryProvider},
32 shell,
33};
34use foundry_compilers::info::ContractInfo;
35use foundry_config::{Chain, Config, figment, impl_figment_convert};
36use foundry_evm::{
37 constants::DEFAULT_CREATE2_DEPLOYER,
38 core::{
39 FoundryChain, FoundryTransaction as _,
40 env::FromAnyRpcTransaction as _,
41 evm::{ChainFor, EthEvmNetwork, EvmEnvFor, FoundryEvmNetwork, TempoEvmNetwork, TxEnvFor},
42 },
43 executors::{EvmError, Executor, ExecutorBuilder, TracingExecutor},
44 opts::{EvmOpts, ForkEndpointIdentity},
45 utils::apply_chain_specific_tx_replay_env_changes_for_chain,
46};
47use foundry_evm_networks::NetworkVariant;
48use revm::{context::Block as _, state::AccountInfo};
49use std::path::PathBuf;
50
51#[cfg(feature = "base")]
52use foundry_evm::core::evm::BaseEvmNetwork;
53
54#[cfg(feature = "monad")]
55use foundry_evm::core::evm::{BlockContext, MonadEvmNetwork};
56
57#[cfg(feature = "optimism")]
58use foundry_evm::core::evm::OpEvmNetwork;
59
60impl_figment_convert!(VerifyBytecodeArgs);
61
62#[derive(Clone, Debug, Parser)]
64pub struct VerifyBytecodeArgs {
65 pub address: Address,
67
68 pub contract: ContractInfo,
70
71 #[arg(long, value_name = "BLOCK")]
73 pub block: Option<BlockId>,
74
75 #[arg(
77 long,
78 num_args(1..),
79 conflicts_with_all = &["constructor_args_path", "encoded_constructor_args"],
80 value_name = "ARGS",
81 )]
82 pub constructor_args: Option<Vec<String>>,
83
84 #[arg(
86 long,
87 conflicts_with_all = &["constructor_args_path", "constructor_args"],
88 value_name = "HEX",
89 )]
90 pub encoded_constructor_args: Option<String>,
91
92 #[arg(
94 long,
95 value_hint = ValueHint::FilePath,
96 value_name = "PATH",
97 conflicts_with_all = &["constructor_args", "encoded_constructor_args"]
98 )]
99 pub constructor_args_path: Option<PathBuf>,
100
101 #[arg(short = 'r', long, value_name = "RPC_URL", env = "ETH_RPC_URL")]
103 pub rpc_url: Option<String>,
104
105 #[arg(long, short, num_args = 1, value_name = "NETWORK")]
107 pub network: Option<NetworkVariant>,
108
109 #[command(flatten)]
111 pub etherscan: EtherscanOpts,
112
113 #[command(flatten)]
115 pub verifier: VerifierArgs,
116
117 #[arg(long, help_heading = "Linker options")]
119 pub libraries: Vec<String>,
120
121 #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")]
126 pub root: Option<PathBuf>,
127
128 #[arg(long, value_name = "BYTECODE_TYPE")]
130 pub ignore: Option<BytecodeType>,
131}
132
133impl figment::Provider for VerifyBytecodeArgs {
134 fn metadata(&self) -> figment::Metadata {
135 figment::Metadata::named("Verify Bytecode Provider")
136 }
137
138 fn data(
139 &self,
140 ) -> Result<figment::value::Map<figment::Profile, figment::value::Dict>, figment::Error> {
141 let mut dict = self.etherscan.dict();
142
143 if let Some(api_key) = &self.verifier.verifier_api_key {
144 dict.insert("etherscan_api_key".into(), api_key.as_str().into());
145 }
146
147 if let Some(block) = &self.block {
148 dict.insert("block".into(), figment::value::Value::serialize(block)?);
149 }
150 if let Some(rpc_url) = &self.rpc_url {
151 dict.insert("eth_rpc_url".into(), rpc_url.clone().into());
152 }
153
154 Ok(figment::value::Map::from([(Config::selected_profile(), dict)]))
155 }
156}
157
158impl VerifyBytecodeArgs {
159 fn configured_network(
160 cli_network: Option<NetworkVariant>,
161 config: &Config,
162 ) -> Option<NetworkVariant> {
163 cli_network.or_else(|| {
164 config.networks.has_network_selection().then(|| config.networks.execution_network())
165 })
166 }
167
168 async fn endpoint_identity(config: &Config) -> Result<Option<ForkEndpointIdentity>> {
169 let (_, mut evm_opts) = load_fork_config_and_evm_opts(config)?;
170 if evm_opts.fork_url.is_none() {
171 evm_opts.fork_url = Some(config.get_rpc_url_or_localhost_http()?.into_owned());
172 }
173 evm_opts.infer_network_from_fork().await?;
174 Ok(evm_opts.fork_endpoint)
175 }
176
177 async fn ensure_endpoint_identity_unchanged(
178 config: &Config,
179 expected: Option<&ForkEndpointIdentity>,
180 ) -> Result<()> {
181 let Some(expected) = expected else { return Ok(()) };
182 let current = Self::endpoint_identity(config).await?.ok_or_else(|| {
183 eyre::eyre!("RPC endpoint identity disappeared while verify-bytecode was running")
184 })?;
185 Self::validate_endpoint_identity(expected, ¤t)
186 }
187
188 fn validate_endpoint_identity(
189 expected: &ForkEndpointIdentity,
190 current: &ForkEndpointIdentity,
191 ) -> Result<()> {
192 if current != expected {
193 eyre::bail!(
194 "RPC endpoint identity changed while verify-bytecode was running; retry against \
195 a stable endpoint"
196 );
197 }
198 Ok(())
199 }
200
201 fn apply_endpoint_expectation(
202 evm_opts: &mut EvmOpts,
203 endpoint_identity: Option<&ForkEndpointIdentity>,
204 network_was_inferred: bool,
205 ) {
206 if let Some(identity) = endpoint_identity {
207 evm_opts.expect_fork_endpoint(identity.clone(), network_was_inferred);
208 }
209 }
210
211 fn effective_network(
212 configured: Option<NetworkVariant>,
213 endpoint_identity: Option<&ForkEndpointIdentity>,
214 ) -> NetworkVariant {
215 configured
216 .or_else(|| endpoint_identity.map(|identity| identity.network))
217 .unwrap_or(NetworkVariant::Ethereum)
218 }
219
220 fn materialize_execution_network(
221 config: &mut Config,
222 endpoint_identity: Option<&ForkEndpointIdentity>,
223 ) -> NetworkVariant {
224 let configured = Self::configured_network(None, config);
225 let network = Self::effective_network(configured, endpoint_identity);
226 if configured.is_none() {
227 config.networks = if let Some(identity) = endpoint_identity {
228 config.networks.with_rpc_profile(identity.network_profile)
229 } else {
230 network.into()
231 };
232 }
233 network
234 }
235
236 fn explorer_chain(
237 configured: Option<Chain>,
238 endpoint_identity: Option<&ForkEndpointIdentity>,
239 ) -> Option<Chain> {
240 configured.or_else(|| endpoint_identity.map(|identity| identity.source_chain_id.into()))
241 }
242
243 pub async fn run(mut self) -> Result<()> {
246 let mut config = self.load_config_with_dependencies()?;
247 config.libraries.append(&mut self.libraries);
248
249 if let Some(network) = self.network {
250 config.networks = network.into();
251 }
252 let network_was_inferred = Self::configured_network(None, &config).is_none();
253 let endpoint_identity = Self::endpoint_identity(&config).await?;
254 let network = Self::materialize_execution_network(&mut config, endpoint_identity.as_ref());
255
256 match network {
257 NetworkVariant::Ethereum => {
258 self.run_with_network::<EthEvmNetwork>(
259 config,
260 endpoint_identity,
261 network_was_inferred,
262 ExecutorBuilder::<EthEvmNetwork>::new(),
263 )
264 .await
265 }
266 #[cfg(feature = "base")]
267 NetworkVariant::Base => {
268 self.run_with_network::<BaseEvmNetwork>(
269 config,
270 endpoint_identity,
271 network_was_inferred,
272 ExecutorBuilder::<BaseEvmNetwork>::new(),
273 )
274 .await
275 }
276 #[cfg(feature = "optimism")]
277 NetworkVariant::Optimism => {
278 self.run_with_network::<OpEvmNetwork>(
279 config,
280 endpoint_identity,
281 network_was_inferred,
282 ExecutorBuilder::<OpEvmNetwork>::new(),
283 )
284 .await
285 }
286 NetworkVariant::Tempo => {
287 self.run_with_network::<TempoEvmNetwork>(
288 config,
289 endpoint_identity,
290 network_was_inferred,
291 ExecutorBuilder::<TempoEvmNetwork>::new(),
292 )
293 .await
294 }
295 #[cfg(feature = "monad")]
296 NetworkVariant::Monad => {
297 let Some(mut verification) = self
298 .prepare_runtime_verification::<MonadEvmNetwork>(
299 config,
300 endpoint_identity,
301 network_was_inferred,
302 ExecutorBuilder::<MonadEvmNetwork>::new(),
303 )
304 .await?
305 else {
306 return Ok(());
307 };
308 let context = replay_monad_block_transactions(
309 &verification.config,
310 verification.block.as_ref(),
311 verification.simulation_block,
312 verification.transaction.tx_hash(),
313 &mut verification.executor,
314 &verification.evm_env,
315 )
316 .await?;
317 verification.finish(context).await
318 }
319 }
320 }
321
322 async fn run_with_network<FEN: FoundryEvmNetwork>(
324 self,
325 config: Config,
326 endpoint_identity: Option<ForkEndpointIdentity>,
327 network_was_inferred: bool,
328 executor_builder: ExecutorBuilder<FEN>,
329 ) -> Result<()> {
330 let Some(mut verification) = self
331 .prepare_runtime_verification::<FEN>(
332 config,
333 endpoint_identity,
334 network_was_inferred,
335 executor_builder,
336 )
337 .await?
338 else {
339 return Ok(());
340 };
341 let context = replay_block_transactions(
342 verification.block.as_ref(),
343 verification.transaction.tx_hash(),
344 &mut verification.executor,
345 &verification.evm_env,
346 )?;
347 verification.finish(context).await
348 }
349
350 async fn prepare_runtime_verification<FEN>(
351 mut self,
352 config: Config,
353 endpoint_identity: Option<ForkEndpointIdentity>,
354 network_was_inferred: bool,
355 executor_builder: ExecutorBuilder<FEN>,
356 ) -> Result<Option<RuntimeVerification<FEN>>>
357 where
358 FEN: FoundryEvmNetwork,
359 {
360 let provider = ProviderBuilder::<AnyNetwork>::from_config(&config)?.build()?;
365
366 let chain = match (
369 Self::explorer_chain(config.chain, endpoint_identity.as_ref()),
370 config.get_rpc_url(),
371 ) {
372 (Some(chain), _) => chain,
373 (None, Some(_)) => utils::get_chain::<AnyNetwork, _>(None, &provider).await?,
374 (None, None) => Default::default(),
375 };
376
377 self.etherscan.chain = Some(chain);
379 self.etherscan.key = config.get_etherscan_config_with_chain(Some(chain))?.map(|c| c.key);
380
381 let has_explorer_config = self.verifier.verifier.is_some()
385 || self.verifier.verifier_url.is_some()
386 || self.verifier.verifier_api_key.is_some()
387 || self.etherscan.key.is_some();
388
389 let etherscan = match EtherscanVerificationProvider.client(
392 &self.etherscan,
393 &self.verifier,
394 &config,
395 ) {
396 Ok(client) => Some(client),
397 Err(err) => {
398 if has_explorer_config {
399 return Err(err);
400 }
401 if !shell::is_json() {
402 sh_warn!(
403 "Failed to create a block explorer client: {err}. Continuing with the local project configuration."
404 )?;
405 }
406 None
407 }
408 };
409
410 let code = provider.get_code_at(self.address).await?;
412 Self::ensure_endpoint_identity_unchanged(&config, endpoint_identity.as_ref()).await?;
413 if code.is_empty() {
414 eyre::bail!("No bytecode found at address {}", self.address);
415 }
416
417 if !shell::is_json() {
418 sh_status!(
419 "Verifying bytecode for contract {} at address {}",
420 self.contract.name,
421 self.address
422 )?;
423 }
424
425 let mut json_results: Vec<JsonResult> = vec![];
426
427 let (creation_data, maybe_predeploy) = match ðerscan {
432 Some(etherscan) => {
433 let creation_data = etherscan.contract_creation_data(self.address).await;
434
435 match maybe_predeploy_contract(creation_data) {
437 Ok(res) => res,
438 Err(err) => {
439 if has_explorer_config {
440 return Err(err);
441 }
442 if !shell::is_json() {
443 sh_warn!(
444 "Failed to fetch creation data from the block explorer: {err}"
445 )?;
446 }
447 (None, false)
448 }
449 }
450 }
451 None => (None, false),
452 };
453
454 trace!(maybe_predeploy = ?maybe_predeploy);
455
456 let source_code = match ðerscan {
458 Some(etherscan) => match etherscan.contract_source_code(self.address).await {
459 Ok(source_code) => {
460 if let Some(metadata) = source_code.items.first() {
461 if metadata.contract_name != self.contract.name {
463 eyre::bail!("Contract name mismatch");
464 }
465 Some(source_code)
466 } else {
467 if !shell::is_json() {
468 sh_warn!(
469 "Block explorer returned no source metadata. Continuing with the local project configuration; compiler settings mismatches will not be reported."
470 )?;
471 }
472 None
473 }
474 }
475 Err(err) => {
476 if has_explorer_config {
477 return Err(err.into());
478 }
479 if !shell::is_json() {
480 sh_warn!(
481 "Failed to fetch contract source code from the block explorer: {err}. Continuing with the local project configuration; compiler settings mismatches will not be reported."
482 )?;
483 }
484 None
485 }
486 },
487 None => None,
488 };
489
490 let etherscan_metadata = source_code.as_ref().and_then(|source| source.items.first());
492
493 let artifact = crate::utils::build_project(&self, &config)?;
495
496 let local_bytecode = artifact
498 .bytecode
499 .as_ref()
500 .and_then(|b| b.to_owned().into_bytes())
501 .ok_or_eyre("Unlinked bytecode is not supported for verification")?;
502
503 let provided_constructor_args = if let Some(encoded) = &self.encoded_constructor_args {
505 Some(validate_encoded_constructor_args(&artifact, hex::decode(encoded)?)?)
506 } else {
507 if let Some(path) = self.constructor_args_path.clone() {
508 Some(read_constructor_args_file(path)?)
510 } else {
511 self.constructor_args.clone()
512 }
513 .map(|args| check_and_encode_args(&artifact, args))
514 .transpose()?
515 };
516
517 let args_from_user = provided_constructor_args.is_some();
518 let mut constructor_args = if let Some(provided) = provided_constructor_args {
519 provided.into()
520 } else if let Some(source_code) = &source_code {
521 check_explorer_args(source_code)?
523 } else {
524 Bytes::new()
525 };
526
527 crate::utils::check_args_len(&artifact, &constructor_args)?;
530
531 if creation_data.is_none() {
535 if !shell::is_json() {
536 if maybe_predeploy {
537 sh_warn!(
538 "Attempting to verify predeployed contract at {:?}. Ignoring creation code verification.",
539 self.address
540 )?;
541 } else {
542 sh_warn!("Creation data is unavailable. Ignoring creation code verification.")?;
543 }
544 }
545
546 if self.ignore.is_some_and(|b| b.is_runtime()) {
549 if shell::is_json() {
550 sh_println!("{}", serde_json::to_string(&json_results)?)?;
551 }
552 return Ok(None);
553 }
554
555 let deploy_block = if maybe_predeploy {
556 0_u64
558 } else {
559 match self.block {
560 Some(BlockId::Number(BlockNumberOrTag::Number(block))) => block,
561 Some(_) => {
562 eyre::bail!("Invalid block number");
563 }
564 None => provider.get_block_number().await?,
565 }
566 };
567
568 trace!(%constructor_args);
570 let mut local_bytecode_vec = local_bytecode.to_vec();
571 local_bytecode_vec.extend_from_slice(&constructor_args);
572
573 let deploy_block_info = provider.get_block(deploy_block.into()).full().await?;
574 let (mut fork_config, mut evm_opts) = load_fork_config_and_evm_opts(&config)?;
575 Self::apply_endpoint_expectation(
576 &mut evm_opts,
577 endpoint_identity.as_ref(),
578 network_was_inferred,
579 );
580 let (evm_env, _, mut executor) = crate::utils::get_tracing_executor::<FEN>(
581 &mut fork_config,
582 deploy_block,
583 deploy_block,
584 deploy_block_info.as_ref(),
585 evm_opts,
586 executor_builder.clone(),
587 )
588 .await?;
589 Self::ensure_endpoint_identity_unchanged(&config, endpoint_identity.as_ref()).await?;
590
591 let deployer = Address::with_last_byte(0x1);
593 let mut tx_env = TxEnvFor::<FEN>::default();
594 tx_env.set_caller(deployer);
595 tx_env.set_kind(TxKind::Create);
596 tx_env.set_data(Bytes::from(local_bytecode_vec));
597 tx_env.set_chain_id(Some(evm_env.cfg_env.chain_id));
598 tx_env.set_gas_limit(evm_env.block_env.gas_limit());
599 tx_env.set_gas_price(evm_env.block_env.basefee() as u128);
600
601 let account_info = AccountInfo {
603 balance: U256::from(100 * 10_u128.pow(18)),
604 nonce: 0,
605 ..Default::default()
606 };
607 executor.backend_mut().insert_account_info(deployer, account_info);
608
609 let fork_address = if maybe_predeploy || deploy_block == 0 {
610 crate::utils::deploy_contract::<FEN>(
611 &mut executor,
612 &evm_env,
613 &tx_env,
614 TxKind::Create,
615 ChainFor::<FEN>::for_transaction(&tx_env),
616 )?
617 } else {
618 executor.deploy_with_env(evm_env.clone(), tx_env.clone(), None)?.address
619 };
620
621 let (deployed_bytecode, onchain_runtime_code) = crate::utils::get_runtime_codes::<FEN>(
625 &mut executor,
626 &provider,
627 self.address,
628 fork_address,
629 (!maybe_predeploy).then_some(deploy_block),
630 )
631 .await?;
632 Self::ensure_endpoint_identity_unchanged(&config, endpoint_identity.as_ref()).await?;
633
634 let match_type = crate::utils::match_bytecodes(
635 deployed_bytecode.original_byte_slice(),
636 &onchain_runtime_code,
637 &constructor_args,
638 true,
639 config.bytecode_hash,
640 );
641
642 crate::utils::print_result(
643 match_type,
644 BytecodeType::Runtime,
645 &mut json_results,
646 etherscan_metadata,
647 &config,
648 );
649
650 if shell::is_json() {
651 sh_println!("{}", serde_json::to_string(&json_results)?)?;
652 }
653
654 return Ok(None);
655 }
656
657 let creation_data = creation_data.unwrap();
659 trace!(creation_tx_hash = ?creation_data.transaction_hash);
661 let transaction = provider
662 .get_transaction_by_hash(creation_data.transaction_hash)
663 .await
664 .or_else(|e| {
665 eyre::bail!("Couldn't fetch transaction from RPC: {:?}", e);
666 })?
667 .ok_or_else(|| {
668 eyre::eyre!("Transaction not found for hash {}", creation_data.transaction_hash)
669 })?;
670 let receipt = provider
671 .get_transaction_receipt(creation_data.transaction_hash)
672 .await
673 .or_else(|e| {
674 eyre::bail!("Couldn't fetch transaction receipt from RPC: {:?}", e);
675 })?;
676 let receipt = if let Some(receipt) = receipt {
677 receipt
678 } else {
679 eyre::bail!(
680 "Receipt not found for transaction hash {}",
681 creation_data.transaction_hash
682 );
683 };
684
685 let creation_block = transaction.block_number();
686
687 let maybe_creation_code = if receipt.to().is_none()
689 && receipt.contract_address() == Some(self.address)
690 {
691 transaction.input().clone()
692 } else if receipt.to() == Some(DEFAULT_CREATE2_DEPLOYER) {
693 Bytes::copy_from_slice(&transaction.input()[32..])
694 } else {
695 let traces = provider
697 .trace_transaction(creation_data.transaction_hash)
698 .await
699 .unwrap_or_default();
700
701 let creation_bytecode =
702 traces.iter().find_map(|trace| match (&trace.trace.result, &trace.trace.action) {
703 (
704 Some(TraceOutput::Create(CreateOutput { address, .. })),
705 Action::Create(CreateAction { init, .. }),
706 ) if *address == self.address => Some(init.clone()),
707 _ => None,
708 });
709
710 creation_bytecode.ok_or_else(|| {
711 eyre::eyre!(
712 "Could not extract the creation code for contract at address {}",
713 self.address
714 )
715 })?
716 };
717 Self::ensure_endpoint_identity_unchanged(&config, endpoint_identity.as_ref()).await?;
718
719 if !args_from_user && !maybe_creation_code.ends_with(&constructor_args) {
722 trace!("mismatch of constructor args with etherscan");
723 if maybe_creation_code.len() >= local_bytecode.len() {
724 constructor_args =
726 Bytes::copy_from_slice(&maybe_creation_code[local_bytecode.len()..]);
727 trace!(
728 target: "forge::verify",
729 "setting constructor args to latest {} bytes of bytecode",
730 constructor_args.len()
731 );
732 }
733 }
734
735 trace!(%constructor_args);
737 let mut local_bytecode_vec = local_bytecode.to_vec();
738 local_bytecode_vec.extend_from_slice(&constructor_args);
739
740 let creation_match_type = crate::utils::match_bytecodes(
744 local_bytecode_vec.as_slice(),
745 &maybe_creation_code,
746 &constructor_args,
747 false,
748 config.bytecode_hash,
749 );
750 if args_from_user
751 && creation_match_type.is_none()
752 && self.ignore.is_none_or(|b| !b.is_creation())
753 {
754 let message =
755 "Provided constructor args could not be validated against deployment creation code";
756 if shell::is_json() {
757 json_results.push(JsonResult {
758 bytecode_type: BytecodeType::Creation,
759 match_type: None,
760 message: Some(message.to_string()),
761 });
762 if self.ignore.is_none_or(|b| !b.is_runtime()) {
763 json_results.push(JsonResult {
764 bytecode_type: BytecodeType::Runtime,
765 match_type: None,
766 message: Some(message.to_string()),
767 });
768 }
769 sh_println!("{}", serde_json::to_string(&json_results)?)?;
770 } else {
771 sh_warn!("{message}")?;
772 crate::utils::print_result(
773 None,
774 BytecodeType::Creation,
775 &mut json_results,
776 etherscan_metadata,
777 &config,
778 );
779 if self.ignore.is_none_or(|b| !b.is_runtime()) {
780 crate::utils::print_result(
781 None,
782 BytecodeType::Runtime,
783 &mut json_results,
784 etherscan_metadata,
785 &config,
786 );
787 }
788 }
789 return Ok(None);
790 }
791
792 trace!(ignore = ?self.ignore);
793 if self.ignore.is_none_or(|b| !b.is_creation()) {
795 crate::utils::print_result(
797 creation_match_type,
798 BytecodeType::Creation,
799 &mut json_results,
800 etherscan_metadata,
801 &config,
802 );
803
804 if creation_match_type.is_none() {
806 crate::utils::print_result(
807 None,
808 BytecodeType::Runtime,
809 &mut json_results,
810 etherscan_metadata,
811 &config,
812 );
813 if shell::is_json() {
814 sh_println!("{}", serde_json::to_string(&json_results)?)?;
815 }
816 return Ok(None);
817 }
818 }
819
820 if self.ignore.is_none_or(|b| !b.is_runtime()) {
821 if let TxKind::Call(to) = ConsensusTransaction::kind(&transaction)
824 && to != DEFAULT_CREATE2_DEPLOYER
825 {
826 let message = format!(
827 "Runtime bytecode verification is not supported for this contract: its \
828 creation transaction calls custom factory {to}. forge can only verify \
829 runtime bytecode for direct CREATE transactions and calls to the default \
830 CREATE2 deployer; skipping runtime bytecode verification."
831 );
832 if shell::is_json() {
833 json_results.push(JsonResult {
834 bytecode_type: BytecodeType::Runtime,
835 match_type: None,
836 message: Some(message),
837 });
838 sh_println!("{}", serde_json::to_string(&json_results)?)?;
839 } else {
840 sh_warn!("{message}")?;
841 }
842 return Ok(None);
843 }
844
845 let simulation_block = match self.block {
847 Some(BlockId::Number(BlockNumberOrTag::Number(block))) => block,
848 Some(_) => {
849 eyre::bail!("Invalid block number");
850 }
851 None => creation_block.ok_or_else(|| {
852 eyre::eyre!(
853 "Failed to get block number of the contract creation tx, specify using the \
854 --block flag"
855 )
856 })?,
857 };
858
859 let block = provider.get_block(simulation_block.into()).full().await?;
862 let (mut fork_config, mut evm_opts) = load_fork_config_and_evm_opts(&config)?;
863 Self::apply_endpoint_expectation(
864 &mut evm_opts,
865 endpoint_identity.as_ref(),
866 network_was_inferred,
867 );
868 let (mut evm_env, _tx_env, executor) = crate::utils::get_tracing_executor::<FEN>(
869 &mut fork_config,
870 simulation_block - 1, simulation_block,
872 block.as_ref(),
873 evm_opts,
874 executor_builder,
875 )
876 .await?;
877 Self::ensure_endpoint_identity_unchanged(&config, endpoint_identity.as_ref()).await?;
878
879 let prev_block_id = BlockId::number(simulation_block - 1);
882
883 let prev_block_nonce =
886 provider.get_transaction_count(transaction.from()).block_id(prev_block_id).await?;
887
888 apply_chain_specific_tx_replay_env_changes_for_chain(&mut evm_env, chain.id());
889 return Ok(Some(RuntimeVerification {
890 address: self.address,
891 config,
892 endpoint_identity,
893 provider,
894 executor,
895 evm_env,
896 block,
897 simulation_block,
898 transaction,
899 prev_block_nonce,
900 local_bytecode_vec,
901 constructor_args,
902 json_results,
903 etherscan_metadata: source_code.and_then(|source| source.items.into_iter().next()),
904 }));
905 }
906 if shell::is_json() {
907 sh_println!("{}", serde_json::to_string(&json_results)?)?;
908 }
909 Ok(None)
910 }
911}
912
913struct RuntimeVerification<FEN: FoundryEvmNetwork> {
915 address: Address,
916 config: Config,
917 endpoint_identity: Option<ForkEndpointIdentity>,
918 provider: RetryProvider,
919 executor: TracingExecutor<FEN>,
920 evm_env: EvmEnvFor<FEN>,
921 block: Option<AnyRpcBlock>,
922 simulation_block: u64,
923 transaction: AnyRpcTransaction,
924 prev_block_nonce: u64,
925 local_bytecode_vec: Vec<u8>,
926 constructor_args: Bytes,
927 json_results: Vec<JsonResult>,
928 etherscan_metadata: Option<Metadata>,
929}
930
931impl<FEN: FoundryEvmNetwork> RuntimeVerification<FEN> {
932 async fn finish(self, target_context: Option<ChainFor<FEN>>) -> Result<()> {
933 let Self {
934 address,
935 config,
936 endpoint_identity,
937 provider,
938 mut executor,
939 evm_env,
940 simulation_block,
941 transaction,
942 prev_block_nonce,
943 local_bytecode_vec,
944 constructor_args,
945 mut json_results,
946 etherscan_metadata,
947 ..
948 } = self;
949 let kind = ConsensusTransaction::kind(&transaction);
950 let mut tx_env = TxEnvFor::<FEN>::from_any_rpc_transaction(&transaction)?;
951 tx_env.set_nonce(prev_block_nonce);
952 let target_context =
953 target_context.unwrap_or_else(|| ChainFor::<FEN>::for_transaction(&tx_env));
954
955 if let TxKind::Call(to) = kind {
957 if to == DEFAULT_CREATE2_DEPLOYER {
958 let mut input = transaction.input()[..32].to_vec(); input.extend_from_slice(&local_bytecode_vec);
960 tx_env.set_data(Bytes::from(input));
961
962 executor.deploy_create2_deployer()?;
964 }
965 } else {
966 tx_env.set_data(Bytes::from(local_bytecode_vec));
967 }
968
969 let fork_address = crate::utils::deploy_contract::<FEN>(
970 &mut executor,
971 &evm_env,
972 &tx_env,
973 kind,
974 target_context,
975 )?;
976
977 let (fork_runtime_code, onchain_runtime_code) = crate::utils::get_runtime_codes::<FEN>(
979 &mut executor,
980 &provider,
981 address,
982 fork_address,
983 Some(simulation_block),
984 )
985 .await?;
986 VerifyBytecodeArgs::ensure_endpoint_identity_unchanged(&config, endpoint_identity.as_ref())
987 .await?;
988
989 let match_type = crate::utils::match_bytecodes(
991 fork_runtime_code.original_byte_slice(),
992 &onchain_runtime_code,
993 &constructor_args,
994 true,
995 config.bytecode_hash,
996 );
997
998 crate::utils::print_result(
999 match_type,
1000 BytecodeType::Runtime,
1001 &mut json_results,
1002 etherscan_metadata.as_ref(),
1003 &config,
1004 );
1005 if shell::is_json() {
1006 sh_println!("{}", serde_json::to_string(&json_results)?)?;
1007 }
1008 Ok(())
1009 }
1010}
1011
1012fn replay_block_transactions<FEN: FoundryEvmNetwork>(
1014 block: Option<&AnyRpcBlock>,
1015 target_hash: B256,
1016 executor: &mut Executor<FEN>,
1017 evm_env: &EvmEnvFor<FEN>,
1018) -> Result<Option<ChainFor<FEN>>> {
1019 let Some(block) = block else { return Ok(None) };
1020 let BlockTransactions::Full(txs) = block.transactions() else {
1021 return Err(eyre::eyre!("Could not get block txs"));
1022 };
1023 let target_tx = txs
1024 .iter()
1025 .find(|tx| tx.tx_hash() == target_hash)
1026 .ok_or_else(|| eyre::eyre!("transaction {target_hash:?} is missing from its block"))?;
1027 let target_tx_env = TxEnvFor::<FEN>::from_any_rpc_transaction(target_tx)?;
1028
1029 for tx in txs {
1030 trace!("replay tx::: {}", tx.tx_hash());
1031 if tx.tx_hash() == target_hash {
1032 break;
1033 }
1034 if is_known_system_sender(tx.from())
1035 || tx.transaction_type() == Some(SYSTEM_TRANSACTION_TYPE)
1036 {
1037 continue;
1038 }
1039
1040 let tx_env = TxEnvFor::<FEN>::from_any_rpc_transaction(tx)?;
1041 let chain_context = ChainFor::<FEN>::for_transaction(&tx_env);
1042 execute_replay_transaction(executor, evm_env, tx, tx_env, chain_context)?;
1043 }
1044
1045 Ok(Some(ChainFor::<FEN>::for_transaction(&target_tx_env)))
1046}
1047
1048#[cfg(feature = "monad")]
1050async fn replay_monad_block_transactions(
1051 config: &Config,
1052 block: Option<&AnyRpcBlock>,
1053 block_number: u64,
1054 target_hash: B256,
1055 executor: &mut Executor<MonadEvmNetwork>,
1056 evm_env: &EvmEnvFor<MonadEvmNetwork>,
1057) -> Result<Option<ChainFor<MonadEvmNetwork>>> {
1058 let block = block.ok_or_else(|| {
1059 eyre::eyre!("block {block_number} is required to reconstruct transaction context")
1060 })?;
1061 let BlockTransactions::Full(txs) = block.transactions() else {
1062 return Err(eyre::eyre!("Could not get block txs"));
1063 };
1064 let block_context = monad_block_context(config, block_number).await?;
1065 let target_index = txs
1066 .iter()
1067 .position(|tx| tx.tx_hash() == target_hash)
1068 .ok_or_else(|| eyre::eyre!("transaction {target_hash:?} is missing from its block"))?;
1069
1070 for (index, tx) in txs.iter().enumerate() {
1071 trace!("replay tx::: {}", tx.tx_hash());
1072 if tx.tx_hash() == target_hash {
1073 break;
1074 }
1075
1076 let tx_env = TxEnvFor::<MonadEvmNetwork>::from_any_rpc_transaction(tx)?;
1077 let chain_context = block_context.transaction(index);
1078 if is_known_system_sender(tx.from())
1079 || tx.transaction_type() == Some(SYSTEM_TRANSACTION_TYPE)
1080 {
1081 let _ = executor
1082 .try_transact_system_replay_with_env_and_context(
1083 evm_env.clone(),
1084 tx_env,
1085 chain_context,
1086 )
1087 .wrap_err_with(|| {
1088 format!(
1089 "Failed to replay system transaction: {:?} in block {}",
1090 tx.tx_hash(),
1091 evm_env.block_env.number()
1092 )
1093 })?;
1094 continue;
1095 }
1096
1097 execute_replay_transaction(executor, evm_env, tx, tx_env, chain_context)?;
1098 }
1099
1100 Ok(Some(block_context.transaction(target_index)))
1101}
1102
1103fn execute_replay_transaction<FEN: FoundryEvmNetwork>(
1104 executor: &mut Executor<FEN>,
1105 evm_env: &EvmEnvFor<FEN>,
1106 tx: &alloy_network::AnyRpcTransaction,
1107 tx_env: TxEnvFor<FEN>,
1108 chain_context: ChainFor<FEN>,
1109) -> Result<()> {
1110 if ConsensusTransaction::to(tx).is_some() {
1111 executor
1112 .transact_with_env_and_context(evm_env.clone(), tx_env, chain_context)
1113 .wrap_err_with(|| {
1114 format!(
1115 "Failed to execute transaction: {:?} in block {}",
1116 tx.tx_hash(),
1117 evm_env.block_env.number()
1118 )
1119 })?;
1120 } else if let Err(error) =
1121 executor.deploy_with_env_and_context(evm_env.clone(), tx_env, chain_context, None)
1122 {
1123 match error {
1124 EvmError::Execution(_) => (),
1126 error => {
1127 return Err(error).wrap_err_with(|| {
1128 format!(
1129 "Failed to deploy transaction: {:?} in block {}",
1130 tx.tx_hash(),
1131 evm_env.block_env.number()
1132 )
1133 });
1134 }
1135 }
1136 }
1137 Ok(())
1138}
1139
1140#[cfg(feature = "monad")]
1142async fn monad_block_context(
1143 config: &Config,
1144 block_number: u64,
1145) -> Result<BlockContext<MonadEvmNetwork>> {
1146 let provider =
1147 ProviderBuilder::<<MonadEvmNetwork as FoundryEvmNetwork>::Network>::from_config(config)?
1148 .build()?;
1149 let block = provider.get_block(block_number.into()).full().await?.ok_or_else(|| {
1150 eyre::eyre!("block {block_number} is required to reconstruct transaction context")
1151 })?;
1152 BlockContext::<MonadEvmNetwork>::fetch(&provider, &block).await
1153}
1154
1155#[cfg(test)]
1156mod tests {
1157 use super::*;
1158 use alloy_network::{AnyHeader, AnyRpcHeader};
1159 use foundry_evm::core::backend::Backend;
1160
1161 fn replay_block(transactions: Vec<AnyRpcTransaction>) -> AnyRpcBlock {
1162 AnyRpcBlock::new(
1163 alloy_rpc_types::Block::new(
1164 AnyRpcHeader::from_sealed(AnyHeader::default().seal(B256::ZERO)),
1165 BlockTransactions::Full(transactions),
1166 )
1167 .into(),
1168 )
1169 }
1170
1171 fn replay_transaction(
1172 caller: Address,
1173 nonce: u64,
1174 hash: B256,
1175 transaction_type: u8,
1176 ) -> AnyRpcTransaction {
1177 serde_json::from_value(serde_json::json!({
1178 "type": format!("0x{transaction_type:x}"),
1179 "hash": hash, "nonce": format!("0x{nonce:x}"),
1180 "from": caller, "to": Address::with_last_byte(0x43),
1181 "value": "0x7", "gas": "0x5208", "gasPrice": "0x0", "input": "0x",
1182 "v": "0x1b", "r": "0x1", "s": "0x1"
1183 }))
1184 .unwrap()
1185 }
1186
1187 #[test]
1188 fn replay_prefix_excludes_target_and_skips_system_envelopes() {
1189 let caller = Address::with_last_byte(0x42);
1190 let recipient = Address::with_last_byte(0x43);
1191 let target = B256::with_last_byte(2);
1192 let env = EvmEnvFor::<EthEvmNetwork>::default();
1193 let mut executor = ExecutorBuilder::<EthEvmNetwork>::new().build(
1194 env.clone(),
1195 TxEnvFor::<EthEvmNetwork>::default(),
1196 Backend::spawn(None).unwrap(),
1197 Default::default(),
1198 );
1199 executor.backend_mut().insert_account_info(
1200 caller,
1201 AccountInfo { balance: U256::from(100), ..Default::default() },
1202 );
1203 let block = replay_block(vec![
1204 replay_transaction(foundry_common::MONAD_SYSTEM_ADDRESS, 0, B256::ZERO, u8::MAX),
1206 replay_transaction(caller, 0, B256::with_last_byte(4), SYSTEM_TRANSACTION_TYPE),
1208 replay_transaction(caller, 0, B256::with_last_byte(1), 0),
1209 replay_transaction(caller, 1, target, 0),
1210 replay_transaction(caller, 2, B256::with_last_byte(3), 0),
1211 ]);
1212
1213 replay_block_transactions(Some(&block), target, &mut executor, &env).unwrap();
1214
1215 assert_eq!(executor.get_balance(recipient).unwrap(), U256::from(7));
1216 assert_eq!(executor.get_nonce(caller).unwrap(), 1);
1217 }
1218
1219 #[test]
1220 fn replay_missing_target_does_not_execute_prefix() {
1221 let caller = Address::with_last_byte(0x42);
1222 let env = EvmEnvFor::<EthEvmNetwork>::default();
1223 let mut executor = ExecutorBuilder::<EthEvmNetwork>::new().build(
1224 env.clone(),
1225 TxEnvFor::<EthEvmNetwork>::default(),
1226 Backend::spawn(None).unwrap(),
1227 Default::default(),
1228 );
1229 executor.backend_mut().insert_account_info(
1230 caller,
1231 AccountInfo { balance: U256::from(100), ..Default::default() },
1232 );
1233 let block = replay_block(vec![replay_transaction(caller, 0, B256::ZERO, 0)]);
1234
1235 let error =
1236 replay_block_transactions(Some(&block), B256::with_last_byte(1), &mut executor, &env)
1237 .unwrap_err();
1238
1239 assert!(error.to_string().contains("missing from its block"), "{error:?}");
1240 assert_eq!(executor.get_nonce(caller).unwrap(), 0);
1241 assert_eq!(executor.get_balance(caller).unwrap(), U256::from(100));
1242 }
1243
1244 #[test]
1245 fn can_parse_tempo_network() {
1246 let args = VerifyBytecodeArgs::parse_from([
1247 "foundry-cli",
1248 "0x0000000000000000000000000000000000000000",
1249 "src/Counter.sol:Counter",
1250 "--network",
1251 "tempo",
1252 ]);
1253
1254 assert_eq!(args.network, Some(NetworkVariant::Tempo));
1255 }
1256
1257 #[test]
1258 #[cfg(feature = "monad")]
1259 fn can_parse_monad_network() {
1260 let args = VerifyBytecodeArgs::parse_from([
1261 "foundry-cli",
1262 "0x0000000000000000000000000000000000000000",
1263 "src/Counter.sol:Counter",
1264 "--network",
1265 "monad",
1266 ]);
1267
1268 assert_eq!(args.network, Some(NetworkVariant::Monad));
1269 }
1270
1271 #[test]
1272 fn configured_network_uses_tempo_config_network() {
1273 let config = Config { networks: NetworkVariant::Tempo.into(), ..Default::default() };
1274
1275 assert_eq!(
1276 VerifyBytecodeArgs::configured_network(None, &config),
1277 Some(NetworkVariant::Tempo)
1278 );
1279 }
1280
1281 #[test]
1282 fn configured_network_preserves_celo_execution_profile() {
1283 let mut config = Config {
1284 networks: foundry_evm_networks::NetworkConfigs::with_celo(),
1285 ..Default::default()
1286 };
1287 let endpoint_identity = ForkEndpointIdentity {
1288 endpoint: "http://localhost:8545".to_string(),
1289 execution_chain_id: 1,
1290 source_chain_id: 1,
1291 network: NetworkVariant::Tempo,
1292 network_profile: NetworkVariant::Tempo.into(),
1293 reported_hardfork: None,
1294 hardfork: None,
1295 instance_id: None,
1296 source_fork_block_number: None,
1297 source_fork_block_hash: None,
1298 };
1299
1300 assert_eq!(
1301 VerifyBytecodeArgs::configured_network(None, &config),
1302 Some(NetworkVariant::Ethereum)
1303 );
1304 assert_eq!(
1305 VerifyBytecodeArgs::materialize_execution_network(
1306 &mut config,
1307 Some(&endpoint_identity)
1308 ),
1309 NetworkVariant::Ethereum
1310 );
1311 assert!(config.networks.is_celo());
1312 }
1313
1314 #[test]
1315 fn verify_bytecode_requires_stable_endpoint_identity() {
1316 let expected = ForkEndpointIdentity {
1317 endpoint: "http://localhost:8545".to_string(),
1318 execution_chain_id: 1,
1319 source_chain_id: 1,
1320 network: NetworkVariant::Ethereum,
1321 network_profile: Default::default(),
1322 reported_hardfork: Some("FutureA".to_string()),
1323 hardfork: None,
1324 instance_id: Some(B256::with_last_byte(1)),
1325 source_fork_block_number: None,
1326 source_fork_block_hash: None,
1327 };
1328
1329 assert!(VerifyBytecodeArgs::validate_endpoint_identity(&expected, &expected).is_ok());
1330
1331 let mut reset = expected.clone();
1332 reset.instance_id = Some(B256::with_last_byte(2));
1333 assert!(VerifyBytecodeArgs::validate_endpoint_identity(&expected, &reset).is_err());
1334
1335 let mut changed_hardfork = expected.clone();
1336 changed_hardfork.reported_hardfork = Some("FutureB".to_string());
1337 assert!(
1338 VerifyBytecodeArgs::validate_endpoint_identity(&expected, &changed_hardfork).is_err()
1339 );
1340
1341 let mut evm_opts = EvmOpts::default();
1342 VerifyBytecodeArgs::apply_endpoint_expectation(&mut evm_opts, Some(&expected), true);
1343 assert_eq!(evm_opts.expected_fork_endpoint, Some(expected));
1344 assert!(evm_opts.fork_network_is_inferred);
1345 }
1346
1347 #[test]
1348 #[cfg(feature = "monad")]
1349 fn configured_network_uses_monad_config_network() {
1350 let config = Config { networks: NetworkVariant::Monad.into(), ..Default::default() };
1351
1352 assert_eq!(
1353 VerifyBytecodeArgs::configured_network(None, &config),
1354 Some(NetworkVariant::Monad)
1355 );
1356 }
1357
1358 #[test]
1359 #[cfg(feature = "monad")]
1360 fn configured_network_prefers_cli_network() {
1361 let config = Config { networks: NetworkVariant::Monad.into(), ..Default::default() };
1362
1363 assert_eq!(
1364 VerifyBytecodeArgs::configured_network(Some(NetworkVariant::Ethereum), &config),
1365 Some(NetworkVariant::Ethereum)
1366 );
1367 }
1368
1369 #[test]
1370 #[cfg(feature = "monad")]
1371 fn nested_endpoint_separates_execution_family_from_explorer_chain() {
1372 let identity = ForkEndpointIdentity {
1373 endpoint: "http://localhost:8545".to_string(),
1374 execution_chain_id: 1,
1375 source_chain_id: 143,
1376 network: NetworkVariant::Monad,
1377 network_profile: NetworkVariant::Monad.into(),
1378 reported_hardfork: None,
1379 hardfork: None,
1380 instance_id: None,
1381 source_fork_block_number: Some(123),
1382 source_fork_block_hash: None,
1383 };
1384
1385 assert_eq!(
1386 VerifyBytecodeArgs::effective_network(None, Some(&identity)),
1387 NetworkVariant::Monad
1388 );
1389 assert_eq!(VerifyBytecodeArgs::explorer_chain(None, Some(&identity)).unwrap().id(), 143);
1390 assert_eq!(
1391 VerifyBytecodeArgs::explorer_chain(Some(Chain::from_id(1)), Some(&identity))
1392 .unwrap()
1393 .id(),
1394 1
1395 );
1396 }
1397
1398 #[cfg(feature = "base")]
1399 #[test]
1400 fn configured_network_preserves_base() {
1401 let config = Config { networks: NetworkVariant::Base.into(), ..Default::default() };
1402 assert_eq!(
1403 VerifyBytecodeArgs::configured_network(None, &config),
1404 Some(NetworkVariant::Base)
1405 );
1406 }
1407}