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