reth_optimism_evm/
lib.rs

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