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