Skip to main content

forge_verify/
bytecode.rs

1//! The `forge verify-bytecode` command.
2use 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/// CLI arguments for `forge verify-bytecode`.
75#[derive(Clone, Debug, Parser)]
76pub struct VerifyBytecodeArgs {
77    /// The address of the contract to verify.
78    pub address: Address,
79
80    /// The contract identifier in the form `<path>:<contractname>`.
81    pub contract: ContractInfo,
82
83    /// The block at which the bytecode should be verified.
84    #[arg(long, value_name = "BLOCK")]
85    pub block: Option<BlockId>,
86
87    /// The constructor args to generate the creation code.
88    #[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    /// The ABI-encoded constructor arguments.
97    #[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    /// The path to a file containing the constructor arguments.
105    #[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    /// The rpc url to use for verification.
114    #[arg(short = 'r', long, value_name = "RPC_URL", env = "ETH_RPC_URL")]
115    pub rpc_url: Option<String>,
116
117    /// Specify the network for correct encoding.
118    #[arg(long, short, num_args = 1, value_name = "NETWORK")]
119    pub network: Option<NetworkVariant>,
120
121    /// Etherscan options.
122    #[command(flatten)]
123    pub etherscan: EtherscanOpts,
124
125    /// Verifier options.
126    #[command(flatten)]
127    pub verifier: VerifierArgs,
128
129    /// Set pre-linked libraries.
130    #[arg(long, help_heading = "Linker options")]
131    pub libraries: Vec<String>,
132
133    /// The project's root path.
134    ///
135    /// By default root of the Git repository, if in one,
136    /// or the current working directory.
137    #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")]
138    pub root: Option<PathBuf>,
139
140    /// Ignore verification for creation or runtime bytecode.
141    #[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, &current)
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    /// Run the `verify-bytecode` command to verify the bytecode onchain against the locally built
223    /// bytecode.
224    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    /// Runs verification for networks whose replay does not require block ancestry.
303    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        // Setup
341        // `AnyNetwork` rather than `FEN::Network`: chains such as Arbitrum and Celo put
342        // transaction types the strict Ethereum envelope cannot decode into every block, which
343        // would fail the full block fetches below for the whole chain. Execution still uses `FEN`.
344        let provider = ProviderBuilder::<AnyNetwork>::from_config(&config)?.build()?;
345
346        // If chain is not set, we try to get it from the RPC.
347        // If RPC is not set, the default chain is used.
348        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        // Set Etherscan options.
358        self.etherscan.chain = Some(chain);
359        self.etherscan.key = config.get_etherscan_config_with_chain(Some(chain))?.map(|c| c.key);
360
361        // Whether a block explorer is configured for this chain. Client setup errors are only
362        // treated as "no explorer available" when no usable verifier, verifier URL, or resolved
363        // API key is configured.
364        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        // Etherscan client. May be unavailable (e.g. unknown chain, missing configuration), in
370        // which case verification proceeds with local data only.
371        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        // Get the bytecode at the address, bailing if it doesn't exist.
391        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        // Get creation tx hash. An unavailable explorer (missing API key, unsupported chain,
408        // unverified contract, etc.) must not prevent verification against a local build: fall
409        // back to verifying the runtime bytecode only.
410        // See <https://github.com/foundry-rs/foundry/issues/13479>.
411        let (creation_data, maybe_predeploy) = match &etherscan {
412            Some(etherscan) => {
413                let creation_data = etherscan.contract_creation_data(self.address).await;
414
415                // Check if contract is a predeploy
416                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        // Get the constructor args using `source_code` endpoint.
437        let source_code = match &etherscan {
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                        // Check if the contract name matches.
442                        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        // Obtain Etherscan compilation metadata.
471        let etherscan_metadata = source_code.as_ref().and_then(|source| source.items.first());
472
473        // Obtain local artifact
474        let artifact = crate::utils::build_project(&self, &config)?;
475
476        // Get local bytecode (creation code)
477        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        // Get and encode user provided constructor args
484        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                // Read from file.
489                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            // If no constructor args were provided, try to retrieve them from the explorer.
502            check_explorer_args(source_code)?
503        } else {
504            Bytes::new()
505        };
506
507        // This fails only when the contract expects constructor args but NONE were provided OR
508        // retrieved from explorer (in case of predeploys).
509        crate::utils::check_args_len(&artifact, &constructor_args)?;
510
511        // Without creation data (predeploys, or the explorer being unavailable), the creation
512        // code cannot be verified. Verify the runtime bytecode instead by deploying the local
513        // creation code and comparing the resulting runtime code with the onchain one.
514        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            // Without creation data there is nothing else to verify when the runtime bytecode is
527            // ignored.
528            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                // Deploy at genesis
537                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            // Append constructor args to the local_bytecode.
549            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            // Setup genesis tx_env and evm_evm.
572            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            // Seed deployer account with funds
582            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            // Compare runtime bytecode. The onchain code is read at `deploy_block` to stay
602            // anchored to the same height as the local fork. Predeploys keep reading at the
603            // latest block: their code is stable and genesis state often isn't served by RPCs.
604            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        // We can unwrap directly as maybe_predeploy is false
638        let creation_data = creation_data.unwrap();
639        // Get transaction and receipt.
640        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        // Extract creation code from creation tx input.
668        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            // Try to get creation bytecode from tx trace.
676            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        // In some cases, Etherscan will return incorrect constructor arguments. If this
700        // happens, try extracting arguments ourselves. Never replace user-provided arguments.
701        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                // If local bytecode is longer than on-chain one, this is probably not a match.
705                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        // Append constructor args to the local_bytecode.
716        trace!(%constructor_args);
717        let mut local_bytecode_vec = local_bytecode.to_vec();
718        local_bytecode_vec.extend_from_slice(&constructor_args);
719
720        // A suffix check alone is insufficient for dynamic ABI values: one valid encoding can
721        // be a suffix of a different valid encoding. Always compare the complete creation code
722        // when arguments came from the user, even if creation output is ignored.
723        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        // Check if `--ignore` is set to `creation`.
774        if self.ignore.is_none_or(|b| !b.is_creation()) {
775            // Compare creation code with locally built bytecode and `maybe_creation_code`.
776            crate::utils::print_result(
777                creation_match_type,
778                BytecodeType::Creation,
779                &mut json_results,
780                etherscan_metadata,
781                &config,
782            );
783
784            // If the creation code does not match, the runtime also won't match. Hence return.
785            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            // Runtime verification can only re-deploy local bytecode for direct `CREATE` and the
802            // default `CREATE2` deployer, so skip custom factory deployments.
803            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            // Get contract creation block.
826            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            // Fork the chain immediately before `simulation_block`, then execute with the target
840            // block's environment and effective runtime hardfork.
841            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, // env.fork_block_number
851                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
883/// Prepared runtime verification, before replaying the creation block's prefix.
884struct 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        // Read the call from the decoded env: batched transactions have no top-level `to`/`input`.
919        let kind = tx_env.kind();
920        let target_context =
921            target_context.unwrap_or_else(|| ChainFor::<FEN>::for_transaction(&tx_env));
922
923        // Replace the `input` with local creation code in the creation tx.
924        if let TxKind::Call(to) = kind {
925            if to == DEFAULT_CREATE2_DEPLOYER {
926                let mut input = tx_env.input()[..32].to_vec(); // Salt
927                input.extend_from_slice(&local_bytecode_vec);
928                tx_env.set_data(Bytes::from(input));
929
930                // Deploy default CREATE2 deployer
931                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        // State committed using deploy_with_env, now get the runtime bytecode from the db.
946        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        // Compare the onchain runtime bytecode with the runtime code from the fork.
958        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
980/// Replays ordinary transactions preceding `target_hash` and returns its execution context.
981fn 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/// Replays Monad transactions preceding `target_hash` with their ancestry context.
1017#[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    // Transact creations too: batched transactions can mix a creation with calls, so their result
1079    // need not be a deployment. Reverted transactions are committed and replay continues.
1080    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/// Fetches the block context Monad needs to reconstruct replay ordering.
1093#[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            // Unsupported envelopes from known system senders must be skipped before conversion.
1165            replay_transaction(foundry_common::MONAD_SYSTEM_ADDRESS, 0, B256::ZERO, u8::MAX),
1166            // System transaction types must also be skipped for otherwise ordinary senders.
1167            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}