reth_seismic_evm/
lib.rs

1//! EVM config for vanilla seismic.
2
3#![doc(
4    html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
5    html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
6    issue_tracker_base_url = "https://github.com/SeismicSystems/seismic-reth/issues/"
7)]
8#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
9#![cfg_attr(not(feature = "std"), no_std)]
10
11extern crate alloc;
12
13use alloc::{borrow::Cow, sync::Arc};
14use alloy_consensus::{BlockHeader, Header};
15use alloy_eips::eip1559::INITIAL_BASE_FEE;
16use alloy_evm::eth::EthBlockExecutionCtx;
17use alloy_primitives::{Bytes, U256};
18use build::SeismicBlockAssembler;
19use core::fmt::Debug;
20use reth_chainspec::{ChainSpec, EthChainSpec};
21use reth_ethereum_forks::EthereumHardfork;
22use reth_evm::{ConfigureEvm, EvmEnv, NextBlockEnvAttributes};
23use reth_primitives_traits::{SealedBlock, SealedHeader};
24use reth_seismic_primitives::{SeismicBlock, SeismicPrimitives};
25use revm::{
26    context::{BlockEnv, CfgEnv},
27    context_interface::block::BlobExcessGasAndPrice,
28};
29use seismic_enclave::rpc::SyncEnclaveApiClientBuilder;
30use seismic_revm::SeismicSpecId;
31use std::convert::Infallible;
32
33mod receipts;
34pub use receipts::*;
35mod build;
36
37pub mod config;
38use config::revm_spec;
39
40pub use alloy_seismic_evm::{block::SeismicBlockExecutorFactory, SeismicEvm, SeismicEvmFactory};
41
42/// Seismic EVM configuration.
43#[derive(Debug, Clone)]
44pub struct SeismicEvmConfig<CB>
45where
46    CB: SyncEnclaveApiClientBuilder,
47{
48    /// Inner [`SeismicBlockExecutorFactory`].
49    pub executor_factory: SeismicBlockExecutorFactory<
50        CB,
51        SeismicRethReceiptBuilder,
52        Arc<ChainSpec>,
53        SeismicEvmFactory<CB>,
54    >,
55    /// Seismic block assembler.
56    pub block_assembler: SeismicBlockAssembler<ChainSpec>,
57}
58
59impl<CB> SeismicEvmConfig<CB>
60where
61    CB: SyncEnclaveApiClientBuilder,
62{
63    /// Creates a new Ethereum EVM configuration with the given chain spec and EVM factory.
64    pub fn seismic(chain_spec: Arc<ChainSpec>, enclave_client: CB) -> Self {
65        SeismicEvmConfig::new_with_evm_factory(
66            chain_spec,
67            SeismicEvmFactory::<CB>::new(enclave_client.clone()),
68            enclave_client,
69        )
70    }
71}
72
73impl<CB> SeismicEvmConfig<CB>
74where
75    CB: SyncEnclaveApiClientBuilder,
76{
77    /// Creates a new Seismic EVM configuration with the given chain spec.
78    pub fn new(chain_spec: Arc<ChainSpec>, enclave_client: CB) -> Self {
79        Self::seismic(chain_spec, enclave_client)
80    }
81
82    /// Creates a new Ethereum EVM configuration with the given chain spec and EVM factory.
83    pub fn new_with_evm_factory(
84        chain_spec: Arc<ChainSpec>,
85        evm_factory: SeismicEvmFactory<CB>,
86        client_builder: CB,
87    ) -> Self {
88        Self {
89            block_assembler: SeismicBlockAssembler::new(chain_spec.clone()),
90            executor_factory: SeismicBlockExecutorFactory::new(
91                SeismicRethReceiptBuilder::default(),
92                chain_spec,
93                evm_factory,
94                client_builder,
95            ),
96        }
97    }
98
99    /// Returns the chain spec associated with this configuration.
100    pub const fn chain_spec(&self) -> &Arc<ChainSpec> {
101        self.executor_factory.spec()
102    }
103
104    /// Sets the extra data for the block assembler.
105    pub fn with_extra_data(mut self, extra_data: Bytes) -> Self {
106        self.block_assembler.extra_data = extra_data;
107        self
108    }
109}
110
111impl<CB> ConfigureEvm for SeismicEvmConfig<CB>
112where
113    CB: SyncEnclaveApiClientBuilder + 'static,
114{
115    type Primitives = SeismicPrimitives;
116    type Error = Infallible;
117    type NextBlockEnvCtx = NextBlockEnvAttributes;
118    type BlockExecutorFactory = SeismicBlockExecutorFactory<
119        CB,
120        SeismicRethReceiptBuilder,
121        Arc<ChainSpec>,
122        SeismicEvmFactory<CB>,
123    >;
124    type BlockAssembler = SeismicBlockAssembler<ChainSpec>;
125
126    fn block_executor_factory(&self) -> &Self::BlockExecutorFactory {
127        &self.executor_factory
128    }
129
130    fn block_assembler(&self) -> &Self::BlockAssembler {
131        &self.block_assembler
132    }
133
134    fn evm_env(&self, header: &Header) -> EvmEnv<SeismicSpecId> {
135        // TODO: use the correct spec id
136        let spec = SeismicSpecId::MERCURY;
137
138        // configure evm env based on parent block
139        let cfg_env = CfgEnv::new().with_chain_id(self.chain_spec().chain().id()).with_spec(spec);
140
141        let block_env = BlockEnv {
142            number: header.number(),
143            beneficiary: header.beneficiary(),
144            timestamp: header.timestamp(),
145            difficulty: U256::ZERO,
146            prevrandao: header.mix_hash(), /* Seismic genesis spec (Mercury) starts after Paris,
147                                            * so we always use header.mix_hash() */
148            gas_limit: header.gas_limit(),
149            basefee: header.base_fee_per_gas().unwrap_or_default(),
150            // EIP-4844 excess blob gas of this block, introduced in Cancun
151            blob_excess_gas_and_price: header
152                .excess_blob_gas
153                .map(|excess_blob_gas| BlobExcessGasAndPrice::new(excess_blob_gas, true)),
154        };
155
156        EvmEnv { cfg_env, block_env }
157    }
158
159    fn next_evm_env(
160        &self,
161        parent: &Header,
162        attributes: &NextBlockEnvAttributes,
163    ) -> Result<EvmEnv<SeismicSpecId>, Self::Error> {
164        let spec_id = revm_spec(self.chain_spec(), parent);
165
166        // configure evm env based on parent block
167        let cfg = CfgEnv::new().with_chain_id(self.chain_spec().chain().id()).with_spec(spec_id);
168
169        // if the parent block did not have excess blob gas (i.e. it was pre-cancun), but it is
170        // cancun now, we need to set the excess blob gas to the default value(0)
171        let blob_excess_gas_and_price = parent
172            .maybe_next_block_excess_blob_gas(
173                self.chain_spec().blob_params_at_timestamp(attributes.timestamp),
174            )
175            .map(|gas| BlobExcessGasAndPrice::new(gas, spec_id >= SeismicSpecId::MERCURY));
176
177        let mut basefee = parent.next_block_base_fee(
178            self.chain_spec().base_fee_params_at_timestamp(attributes.timestamp),
179        );
180
181        let mut gas_limit = attributes.gas_limit;
182
183        // If we are on the London fork boundary, we need to multiply the parent's gas limit by the
184        // elasticity multiplier to get the new gas limit.
185        if self.chain_spec().fork(EthereumHardfork::London).transitions_at_block(parent.number + 1)
186        {
187            let elasticity_multiplier = self
188                .chain_spec()
189                .base_fee_params_at_timestamp(attributes.timestamp)
190                .elasticity_multiplier;
191
192            // multiply the gas limit by the elasticity multiplier
193            gas_limit *= elasticity_multiplier as u64;
194
195            // set the base fee to the initial base fee from the EIP-1559 spec
196            basefee = Some(INITIAL_BASE_FEE)
197        }
198
199        let block_env = BlockEnv {
200            number: parent.number + 1,
201            beneficiary: attributes.suggested_fee_recipient,
202            timestamp: attributes.timestamp,
203            difficulty: U256::ZERO,
204            prevrandao: Some(attributes.prev_randao),
205            gas_limit,
206            // calculate basefee based on parent block's gas usage
207            basefee: basefee.unwrap_or_default(),
208            // calculate excess gas based on parent block's blob gas usage
209            blob_excess_gas_and_price,
210        };
211
212        Ok((cfg, block_env).into())
213    }
214
215    fn context_for_block<'a>(
216        &self,
217        block: &'a SealedBlock<SeismicBlock>,
218    ) -> EthBlockExecutionCtx<'a> {
219        EthBlockExecutionCtx {
220            parent_hash: block.header().parent_hash,
221            parent_beacon_block_root: block.header().parent_beacon_block_root,
222            ommers: &block.body().ommers,
223            withdrawals: block.body().withdrawals.as_ref().map(Cow::Borrowed),
224        }
225    }
226
227    fn context_for_next_block(
228        &self,
229        parent: &SealedHeader,
230        attributes: Self::NextBlockEnvCtx,
231    ) -> EthBlockExecutionCtx<'_> {
232        EthBlockExecutionCtx {
233            parent_hash: parent.hash(),
234            parent_beacon_block_root: attributes.parent_beacon_block_root,
235            ommers: &[],
236            withdrawals: attributes.withdrawals.map(Cow::Owned),
237        }
238    }
239}
240
241#[cfg(test)]
242mod tests {
243    use super::*;
244    use alloy_consensus::{Header, Receipt};
245    use alloy_eips::eip7685::Requests;
246    use alloy_evm::Evm;
247    use alloy_genesis::Genesis;
248    use alloy_primitives::{bytes, map::HashMap, Address, LogData, B256};
249    use reth_chainspec::ChainSpec;
250    use reth_evm::execute::ProviderError;
251    use reth_execution_types::{
252        AccountRevertInit, BundleStateInit, Chain, ExecutionOutcome, RevertsInit,
253    };
254    use reth_primitives_traits::{Account, RecoveredBlock};
255    use reth_seismic_chainspec::SEISMIC_MAINNET;
256    use reth_seismic_primitives::{SeismicBlock, SeismicPrimitives, SeismicReceipt};
257    use revm::{
258        database::{BundleState, CacheDB},
259        database_interface::EmptyDBTyped,
260        handler::PrecompileProvider,
261        inspector::NoOpInspector,
262        precompile::u64_to_address,
263        primitives::Log,
264        state::AccountInfo,
265    };
266    use seismic_enclave::MockEnclaveClientBuilder;
267    use std::sync::Arc;
268
269    fn test_evm_config() -> SeismicEvmConfig<MockEnclaveClientBuilder> {
270        SeismicEvmConfig::seismic(SEISMIC_MAINNET.clone(), MockEnclaveClientBuilder::new())
271    }
272
273    #[test]
274    fn test_fill_cfg_and_block_env() {
275        // Create a default header
276        let header = Header::default();
277
278        // Build the ChainSpec for Ethereum mainnet, activating London, Paris, and Shanghai
279        // hardforks
280        let chain_spec = ChainSpec::builder()
281            .chain(0.into())
282            .genesis(Genesis::default())
283            .london_activated()
284            .paris_activated()
285            .shanghai_activated()
286            .build();
287
288        // Use the `SeismicEvmConfig` to create the `cfg_env` and `block_env` based on the
289        // ChainSpec, Header, and total difficulty
290        let EvmEnv { cfg_env, .. } = SeismicEvmConfig::seismic(
291            Arc::new(chain_spec.clone()),
292            MockEnclaveClientBuilder::new(),
293        )
294        .evm_env(&header);
295
296        // Assert that the chain ID in the `cfg_env` is correctly set to the chain ID of the
297        // ChainSpec
298        assert_eq!(cfg_env.chain_id, chain_spec.chain().id());
299    }
300
301    #[test]
302    fn test_seismic_evm_with_env_default_spec() {
303        // Setup the EVM with test config and environment
304        let evm_config = test_evm_config(); // Provides SeismicEvm config with Seismic mainnet spec
305        let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
306        let evm_env = EvmEnv::default();
307        let evm: SeismicEvm<_, NoOpInspector> = evm_config.evm_with_env(db, evm_env.clone());
308        let precompiles = evm.precompiles().clone();
309
310        // Check that the EVM environment is correctly set
311        assert_eq!(evm.cfg, evm_env.cfg_env);
312        assert_eq!(evm.cfg.spec, SeismicSpecId::MERCURY);
313
314        // Check that the expected number of precompiles is set
315        let precompile_addresses =
316            [u64_to_address(101), u64_to_address(102), u64_to_address(103), u64_to_address(104)];
317        for &addr in &precompile_addresses {
318            let is_contained = precompiles.contains(&addr);
319            assert!(
320                is_contained,
321                "Expected Precompile at address for RETH evm generation {addr:?}"
322            );
323        }
324    }
325
326    #[test]
327    fn test_evm_with_env_custom_cfg() {
328        let evm_config = test_evm_config();
329
330        let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
331
332        // Create a custom configuration environment with a chain ID of 111
333        let cfg = CfgEnv::new().with_chain_id(111).with_spec(SeismicSpecId::default());
334
335        let evm_env = EvmEnv { cfg_env: cfg.clone(), ..Default::default() };
336
337        let evm = evm_config.evm_with_env(db, evm_env);
338
339        // Check that the EVM environment is initialized with the custom environment
340        assert_eq!(evm.cfg, cfg);
341    }
342
343    #[test]
344    fn test_evm_with_env_custom_block_and_tx() {
345        let evm_config = test_evm_config();
346
347        let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
348
349        // Create customs block and tx env
350        let block =
351            BlockEnv { basefee: 1000, gas_limit: 10_000_000, number: 42, ..Default::default() };
352
353        let evm_env = EvmEnv { block_env: block, ..Default::default() };
354
355        let evm = evm_config.evm_with_env(db, evm_env.clone());
356
357        // Verify that the block and transaction environments are set correctly
358        assert_eq!(evm.block, evm_env.block_env);
359    }
360
361    #[test]
362    fn test_evm_with_spec_id() {
363        let evm_config = test_evm_config();
364
365        let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
366
367        let evm_env = EvmEnv {
368            cfg_env: CfgEnv::new().with_spec(SeismicSpecId::MERCURY),
369            ..Default::default()
370        };
371
372        let evm = evm_config.evm_with_env(db, evm_env.clone());
373
374        assert_eq!(evm.cfg, evm_env.cfg_env);
375    }
376
377    #[test]
378    fn test_evm_with_env_and_default_inspector() {
379        let evm_config = test_evm_config();
380        let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
381
382        let evm_env = EvmEnv { cfg_env: Default::default(), ..Default::default() };
383
384        let evm = evm_config.evm_with_env_and_inspector(db, evm_env.clone(), NoOpInspector {});
385
386        // Check that the EVM environment is set to default values
387        assert_eq!(*evm.block(), evm_env.block_env);
388        assert_eq!(evm.cfg, evm_env.cfg_env);
389    }
390
391    #[test]
392    fn test_evm_with_env_inspector_and_custom_cfg() {
393        let evm_config = test_evm_config();
394        let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
395
396        let cfg = CfgEnv::new().with_chain_id(111).with_spec(SeismicSpecId::MERCURY);
397        let block = BlockEnv::default();
398        let evm_env = EvmEnv { block_env: block, cfg_env: cfg.clone() };
399
400        let evm = evm_config.evm_with_env_and_inspector(db, evm_env.clone(), NoOpInspector {});
401
402        // Check that the EVM environment is set with custom configuration
403        assert_eq!(evm.cfg, cfg);
404        assert_eq!(evm.block, evm_env.block_env);
405    }
406
407    #[test]
408    fn test_evm_with_env_inspector_and_custom_block_tx() {
409        let evm_config = test_evm_config();
410        let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
411
412        // Create custom block and tx environment
413        let block =
414            BlockEnv { basefee: 1000, gas_limit: 10_000_000, number: 42, ..Default::default() };
415        let evm_env = EvmEnv { block_env: block, ..Default::default() };
416
417        let evm = evm_config.evm_with_env_and_inspector(db, evm_env.clone(), NoOpInspector {});
418
419        // Verify that the block and transaction environments are set correctly
420        assert_eq!(evm.block, evm_env.block_env);
421    }
422
423    #[test]
424    fn test_evm_with_env_inspector_and_spec_id() {
425        let evm_config = test_evm_config();
426        let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
427
428        let evm_env = EvmEnv {
429            cfg_env: CfgEnv::new().with_spec(SeismicSpecId::MERCURY),
430            ..Default::default()
431        };
432
433        let evm = evm_config.evm_with_env_and_inspector(db, evm_env.clone(), NoOpInspector {});
434
435        // Check that the spec ID is set properly
436        assert_eq!(evm.cfg, evm_env.cfg_env);
437        assert_eq!(evm.block, evm_env.block_env);
438    }
439
440    #[test]
441    fn receipts_by_block_hash() {
442        // Create a default recovered block
443        let block: RecoveredBlock<SeismicBlock> = Default::default();
444
445        // Define block hashes for block1 and block2
446        let block1_hash = B256::new([0x01; 32]);
447        let block2_hash = B256::new([0x02; 32]);
448
449        // Clone the default block into block1 and block2
450        let mut block1 = block.clone();
451        let mut block2 = block;
452
453        // Set the hashes of block1 and block2
454        block1.set_block_number(10);
455        block1.set_hash(block1_hash);
456
457        block2.set_block_number(11);
458        block2.set_hash(block2_hash);
459
460        // Create a random receipt object, receipt1
461        let receipt1 = SeismicReceipt::Legacy(Receipt {
462            cumulative_gas_used: 46913,
463            logs: vec![],
464            status: true.into(),
465        });
466
467        // Create another random receipt object, receipt2
468        let receipt2 = SeismicReceipt::Legacy(Receipt {
469            cumulative_gas_used: 1325345,
470            logs: vec![],
471            status: true.into(),
472        });
473
474        // Create a Receipts object with a vector of receipt vectors
475        let receipts = vec![vec![receipt1.clone()], vec![receipt2]];
476
477        // Create an ExecutionOutcome object with the created bundle, receipts, an empty requests
478        // vector, and first_block set to 10
479        let execution_outcome = ExecutionOutcome::<SeismicReceipt> {
480            bundle: Default::default(),
481            receipts,
482            requests: vec![],
483            first_block: 10,
484        };
485
486        // Create a Chain object with a BTreeMap of blocks mapped to their block numbers,
487        // including block1_hash and block2_hash, and the execution_outcome
488        let chain: Chain<SeismicPrimitives> =
489            Chain::new([block1, block2], execution_outcome.clone(), None);
490
491        // Assert that the proper receipt vector is returned for block1_hash
492        assert_eq!(chain.receipts_by_block_hash(block1_hash), Some(vec![&receipt1]));
493
494        // Create an ExecutionOutcome object with a single receipt vector containing receipt1
495        let execution_outcome1 = ExecutionOutcome {
496            bundle: Default::default(),
497            receipts: vec![vec![receipt1]],
498            requests: vec![],
499            first_block: 10,
500        };
501
502        // Assert that the execution outcome at the first block contains only the first receipt
503        assert_eq!(chain.execution_outcome_at_block(10), Some(execution_outcome1));
504
505        // Assert that the execution outcome at the tip block contains the whole execution outcome
506        assert_eq!(chain.execution_outcome_at_block(11), Some(execution_outcome));
507    }
508
509    #[test]
510    fn test_initialisation() {
511        // Create a new BundleState object with initial data
512        let bundle = BundleState::new(
513            vec![(Address::new([2; 20]), None, Some(AccountInfo::default()), HashMap::default())],
514            vec![vec![(Address::new([2; 20]), None, vec![])]],
515            vec![],
516        );
517
518        // Create a Receipts object with a vector of receipt vectors
519        let receipts = vec![vec![Some(SeismicReceipt::Legacy(Receipt {
520            cumulative_gas_used: 46913,
521            logs: vec![],
522            status: true.into(),
523        }))]];
524
525        // Create a Requests object with a vector of requests
526        let requests = vec![Requests::new(vec![bytes!("dead"), bytes!("beef"), bytes!("beebee")])];
527
528        // Define the first block number
529        let first_block = 123;
530
531        // Create a ExecutionOutcome object with the created bundle, receipts, requests, and
532        // first_block
533        let exec_res = ExecutionOutcome {
534            bundle: bundle.clone(),
535            receipts: receipts.clone(),
536            requests: requests.clone(),
537            first_block,
538        };
539
540        // Assert that creating a new ExecutionOutcome using the constructor matches exec_res
541        assert_eq!(
542            ExecutionOutcome::new(bundle, receipts.clone(), first_block, requests.clone()),
543            exec_res
544        );
545
546        // Create a BundleStateInit object and insert initial data
547        let mut state_init: BundleStateInit = HashMap::default();
548        state_init
549            .insert(Address::new([2; 20]), (None, Some(Account::default()), HashMap::default()));
550
551        // Create a HashMap for account reverts and insert initial data
552        let mut revert_inner: HashMap<Address, AccountRevertInit> = HashMap::default();
553        revert_inner.insert(Address::new([2; 20]), (None, vec![]));
554
555        // Create a RevertsInit object and insert the revert_inner data
556        let mut revert_init: RevertsInit = HashMap::default();
557        revert_init.insert(123, revert_inner);
558
559        // Assert that creating a new ExecutionOutcome using the new_init method matches
560        // exec_res
561        assert_eq!(
562            ExecutionOutcome::new_init(
563                state_init,
564                revert_init,
565                vec![],
566                receipts,
567                first_block,
568                requests,
569            ),
570            exec_res
571        );
572    }
573
574    #[test]
575    fn test_block_number_to_index() {
576        // Create a Receipts object with a vector of receipt vectors
577        let receipts = vec![vec![Some(SeismicReceipt::Legacy(Receipt {
578            cumulative_gas_used: 46913,
579            logs: vec![],
580            status: true.into(),
581        }))]];
582
583        // Define the first block number
584        let first_block = 123;
585
586        // Create a ExecutionOutcome object with the created bundle, receipts, requests, and
587        // first_block
588        let exec_res = ExecutionOutcome {
589            bundle: Default::default(),
590            receipts,
591            requests: vec![],
592            first_block,
593        };
594
595        // Test before the first block
596        assert_eq!(exec_res.block_number_to_index(12), None);
597
598        // Test after after the first block but index larger than receipts length
599        assert_eq!(exec_res.block_number_to_index(133), None);
600
601        // Test after the first block
602        assert_eq!(exec_res.block_number_to_index(123), Some(0));
603    }
604
605    #[test]
606    fn test_get_logs() {
607        // Create a Receipts object with a vector of receipt vectors
608        let receipts = vec![vec![SeismicReceipt::Legacy(Receipt {
609            cumulative_gas_used: 46913,
610            logs: vec![Log::<LogData>::default()],
611            status: true.into(),
612        })]];
613
614        // Define the first block number
615        let first_block = 123;
616
617        // Create a ExecutionOutcome object with the created bundle, receipts, requests, and
618        // first_block
619        let exec_res = ExecutionOutcome {
620            bundle: Default::default(),
621            receipts,
622            requests: vec![],
623            first_block,
624        };
625
626        // Get logs for block number 123
627        let logs: Vec<&Log> = exec_res.logs(123).unwrap().collect();
628
629        // Assert that the logs match the expected logs
630        assert_eq!(logs, vec![&Log::<LogData>::default()]);
631    }
632
633    #[test]
634    fn test_receipts_by_block() {
635        // Create a Receipts object with a vector of receipt vectors
636        let receipts = vec![vec![Some(SeismicReceipt::Legacy(Receipt {
637            cumulative_gas_used: 46913,
638            logs: vec![Log::<LogData>::default()],
639            status: true.into(),
640        }))]];
641
642        // Define the first block number
643        let first_block = 123;
644
645        // Create a ExecutionOutcome object with the created bundle, receipts, requests, and
646        // first_block
647        let exec_res = ExecutionOutcome {
648            bundle: Default::default(), // Default value for bundle
649            receipts,                   // Include the created receipts
650            requests: vec![],           // Empty vector for requests
651            first_block,                // Set the first block number
652        };
653
654        // Get receipts for block number 123 and convert the result into a vector
655        let receipts_by_block: Vec<_> = exec_res.receipts_by_block(123).iter().collect();
656
657        // Assert that the receipts for block number 123 match the expected receipts
658        assert_eq!(
659            receipts_by_block,
660            vec![&Some(SeismicReceipt::Legacy(Receipt {
661                cumulative_gas_used: 46913,
662                logs: vec![Log::<LogData>::default()],
663                status: true.into(),
664            }))]
665        );
666    }
667
668    #[test]
669    fn test_receipts_len() {
670        // Create a Receipts object with a vector of receipt vectors
671        let receipts = vec![vec![Some(SeismicReceipt::Legacy(Receipt {
672            cumulative_gas_used: 46913,
673            logs: vec![Log::<LogData>::default()],
674            status: true.into(),
675        }))]];
676
677        // Create an empty Receipts object
678        let receipts_empty = vec![];
679
680        // Define the first block number
681        let first_block = 123;
682
683        // Create a ExecutionOutcome object with the created bundle, receipts, requests, and
684        // first_block
685        let exec_res = ExecutionOutcome {
686            bundle: Default::default(), // Default value for bundle
687            receipts,                   // Include the created receipts
688            requests: vec![],           // Empty vector for requests
689            first_block,                // Set the first block number
690        };
691
692        // Assert that the length of receipts in exec_res is 1
693        assert_eq!(exec_res.len(), 1);
694
695        // Assert that exec_res is not empty
696        assert!(!exec_res.is_empty());
697
698        // Create a ExecutionOutcome object with an empty Receipts object
699        let exec_res_empty_receipts: ExecutionOutcome<SeismicReceipt> = ExecutionOutcome {
700            bundle: Default::default(), // Default value for bundle
701            receipts: receipts_empty,   // Include the empty receipts
702            requests: vec![],           // Empty vector for requests
703            first_block,                // Set the first block number
704        };
705
706        // Assert that the length of receipts in exec_res_empty_receipts is 0
707        assert_eq!(exec_res_empty_receipts.len(), 0);
708
709        // Assert that exec_res_empty_receipts is empty
710        assert!(exec_res_empty_receipts.is_empty());
711    }
712
713    #[test]
714    fn test_revert_to() {
715        // Create a random receipt object
716        let receipt = SeismicReceipt::Legacy(Receipt {
717            cumulative_gas_used: 46913,
718            logs: vec![],
719            status: true.into(),
720        });
721
722        // Create a Receipts object with a vector of receipt vectors
723        let receipts = vec![vec![Some(receipt.clone())], vec![Some(receipt.clone())]];
724
725        // Define the first block number
726        let first_block = 123;
727
728        // Create a request.
729        let request = bytes!("deadbeef");
730
731        // Create a vector of Requests containing the request.
732        let requests =
733            vec![Requests::new(vec![request.clone()]), Requests::new(vec![request.clone()])];
734
735        // Create a ExecutionOutcome object with the created bundle, receipts, requests, and
736        // first_block
737        let mut exec_res =
738            ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block };
739
740        // Assert that the revert_to method returns true when reverting to the initial block number.
741        assert!(exec_res.revert_to(123));
742
743        // Assert that the receipts are properly cut after reverting to the initial block number.
744        assert_eq!(exec_res.receipts, vec![vec![Some(receipt)]]);
745
746        // Assert that the requests are properly cut after reverting to the initial block number.
747        assert_eq!(exec_res.requests, vec![Requests::new(vec![request])]);
748
749        // Assert that the revert_to method returns false when attempting to revert to a block
750        // number greater than the initial block number.
751        assert!(!exec_res.revert_to(133));
752
753        // Assert that the revert_to method returns false when attempting to revert to a block
754        // number less than the initial block number.
755        assert!(!exec_res.revert_to(10));
756    }
757
758    #[test]
759    fn test_extend_execution_outcome() {
760        // Create a Receipt object with specific attributes.
761        let receipt = SeismicReceipt::Legacy(Receipt {
762            cumulative_gas_used: 46913,
763            logs: vec![],
764            status: true.into(),
765        });
766
767        // Create a Receipts object containing the receipt.
768        let receipts = vec![vec![Some(receipt.clone())]];
769
770        // Create a request.
771        let request = bytes!("deadbeef");
772
773        // Create a vector of Requests containing the request.
774        let requests = vec![Requests::new(vec![request.clone()])];
775
776        // Define the initial block number.
777        let first_block = 123;
778
779        // Create an ExecutionOutcome object.
780        let mut exec_res =
781            ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block };
782
783        // Extend the ExecutionOutcome object by itself.
784        exec_res.extend(exec_res.clone());
785
786        // Assert the extended ExecutionOutcome matches the expected outcome.
787        assert_eq!(
788            exec_res,
789            ExecutionOutcome {
790                bundle: Default::default(),
791                receipts: vec![vec![Some(receipt.clone())], vec![Some(receipt)]],
792                requests: vec![Requests::new(vec![request.clone()]), Requests::new(vec![request])],
793                first_block: 123,
794            }
795        );
796    }
797
798    #[test]
799    fn test_split_at_execution_outcome() {
800        // Create a random receipt object
801        let receipt = SeismicReceipt::Legacy(Receipt {
802            cumulative_gas_used: 46913,
803            logs: vec![],
804            status: true.into(),
805        });
806
807        // Create a Receipts object with a vector of receipt vectors
808        let receipts = vec![
809            vec![Some(receipt.clone())],
810            vec![Some(receipt.clone())],
811            vec![Some(receipt.clone())],
812        ];
813
814        // Define the first block number
815        let first_block = 123;
816
817        // Create a request.
818        let request = bytes!("deadbeef");
819
820        // Create a vector of Requests containing the request.
821        let requests = vec![
822            Requests::new(vec![request.clone()]),
823            Requests::new(vec![request.clone()]),
824            Requests::new(vec![request.clone()]),
825        ];
826
827        // Create a ExecutionOutcome object with the created bundle, receipts, requests, and
828        // first_block
829        let exec_res =
830            ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block };
831
832        // Split the ExecutionOutcome at block number 124
833        let result = exec_res.clone().split_at(124);
834
835        // Define the expected lower ExecutionOutcome after splitting
836        let lower_execution_outcome = ExecutionOutcome {
837            bundle: Default::default(),
838            receipts: vec![vec![Some(receipt.clone())]],
839            requests: vec![Requests::new(vec![request.clone()])],
840            first_block,
841        };
842
843        // Define the expected higher ExecutionOutcome after splitting
844        let higher_execution_outcome = ExecutionOutcome {
845            bundle: Default::default(),
846            receipts: vec![vec![Some(receipt.clone())], vec![Some(receipt)]],
847            requests: vec![Requests::new(vec![request.clone()]), Requests::new(vec![request])],
848            first_block: 124,
849        };
850
851        // Assert that the split result matches the expected lower and higher outcomes
852        assert_eq!(result.0, Some(lower_execution_outcome));
853        assert_eq!(result.1, higher_execution_outcome);
854
855        // Assert that splitting at the first block number returns None for the lower outcome
856        assert_eq!(exec_res.clone().split_at(123), (None, exec_res));
857    }
858}