reth_execution_types/
execution_outcome.rs

1use crate::BlockExecutionOutput;
2use alloy_eips::eip7685::Requests;
3use alloy_primitives::{logs_bloom, Address, BlockNumber, Bloom, Log, B256, U256};
4use reth_primitives::Receipts;
5use reth_primitives_traits::{Account, Bytecode, Receipt, StorageEntry};
6use reth_trie::{HashedPostState, KeyHasher};
7use revm::{
8    db::{states::BundleState, BundleAccount},
9    primitives::{AccountInfo, FlaggedStorage},
10};
11use std::collections::HashMap;
12
13/// Represents a changed account
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub struct ChangedAccount {
16    /// The address of the account.
17    pub address: Address,
18    /// Account nonce.
19    pub nonce: u64,
20    /// Account balance.
21    pub balance: U256,
22}
23
24impl ChangedAccount {
25    /// Creates a new [`ChangedAccount`] with the given address and 0 balance and nonce.
26    pub const fn empty(address: Address) -> Self {
27        Self { address, nonce: 0, balance: U256::ZERO }
28    }
29}
30
31/// Represents the outcome of block execution, including post-execution changes and reverts.
32///
33/// The `ExecutionOutcome` structure aggregates the state changes over an arbitrary number of
34/// blocks, capturing the resulting state, receipts, and requests following the execution.
35#[derive(Debug, Clone, PartialEq, Eq)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37pub struct ExecutionOutcome<T = reth_primitives::Receipt> {
38    /// Bundle state with reverts.
39    pub bundle: BundleState,
40    /// The collection of receipts.
41    /// Outer vector stores receipts for each block sequentially.
42    /// The inner vector stores receipts ordered by transaction number.
43    ///
44    /// If receipt is None it means it is pruned.
45    pub receipts: Receipts<T>,
46    /// First block of bundle state.
47    pub first_block: BlockNumber,
48    /// The collection of EIP-7685 requests.
49    /// Outer vector stores requests for each block sequentially.
50    /// The inner vector stores requests ordered by transaction number.
51    ///
52    /// A transaction may have zero or more requests, so the length of the inner vector is not
53    /// guaranteed to be the same as the number of transactions.
54    pub requests: Vec<Requests>,
55}
56
57impl<T> Default for ExecutionOutcome<T> {
58    fn default() -> Self {
59        Self {
60            bundle: Default::default(),
61            receipts: Default::default(),
62            first_block: Default::default(),
63            requests: Default::default(),
64        }
65    }
66}
67
68/// Type used to initialize revms bundle state.
69pub type BundleStateInit = HashMap<
70    Address,
71    (Option<Account>, Option<Account>, HashMap<B256, ((U256, bool), (U256, bool))>),
72>;
73
74/// Types used inside `RevertsInit` to initialize revms reverts.
75pub type AccountRevertInit = (Option<Option<Account>>, Vec<StorageEntry>);
76
77/// Type used to initialize revms reverts.
78pub type RevertsInit = HashMap<BlockNumber, HashMap<Address, AccountRevertInit>>;
79
80impl<T> ExecutionOutcome<T> {
81    /// Creates a new `ExecutionOutcome`.
82    ///
83    /// This constructor initializes a new `ExecutionOutcome` instance with the provided
84    /// bundle state, receipts, first block number, and EIP-7685 requests.
85    pub const fn new(
86        bundle: BundleState,
87        receipts: Receipts<T>,
88        first_block: BlockNumber,
89        requests: Vec<Requests>,
90    ) -> Self {
91        Self { bundle, receipts, first_block, requests }
92    }
93
94    /// Creates a new `ExecutionOutcome` from initialization parameters.
95    ///
96    /// This constructor initializes a new `ExecutionOutcome` instance using detailed
97    /// initialization parameters.
98    pub fn new_init(
99        state_init: BundleStateInit,
100        revert_init: RevertsInit,
101        contracts_init: impl IntoIterator<Item = (B256, Bytecode)>,
102        receipts: Receipts<T>,
103        first_block: BlockNumber,
104        requests: Vec<Requests>,
105    ) -> Self {
106        // sort reverts by block number
107        let mut reverts = revert_init.into_iter().collect::<Vec<_>>();
108        reverts.sort_unstable_by_key(|a| a.0);
109
110        // initialize revm bundle
111        let bundle = BundleState::new(
112            state_init.into_iter().map(|(address, (original, present, storage))| {
113                (
114                    address,
115                    original.map(Into::into),
116                    present.map(Into::into),
117                    storage
118                        .into_iter()
119                        .map(|(k, (orig_value, new_value))| {
120                            (
121                                k.into(),
122                                (
123                                    FlaggedStorage::new_from_tuple(orig_value),
124                                    FlaggedStorage::new_from_tuple(new_value),
125                                ),
126                            )
127                        })
128                        .collect(),
129                )
130            }),
131            reverts.into_iter().map(|(_, reverts)| {
132                // does not needs to be sorted, it is done when taking reverts.
133                reverts.into_iter().map(|(address, (original, storage))| {
134                    (
135                        address,
136                        original.map(|i| i.map(Into::into)),
137                        storage.into_iter().map(|entry| {
138                            (
139                                entry.key.into(),
140                                FlaggedStorage { value: entry.value, is_private: entry.is_private },
141                            )
142                        }),
143                    )
144                })
145            }),
146            contracts_init.into_iter().map(|(code_hash, bytecode)| (code_hash, bytecode.0)),
147        );
148
149        Self { bundle, receipts, first_block, requests }
150    }
151
152    /// Return revm bundle state.
153    pub const fn state(&self) -> &BundleState {
154        &self.bundle
155    }
156
157    /// Returns mutable revm bundle state.
158    pub fn state_mut(&mut self) -> &mut BundleState {
159        &mut self.bundle
160    }
161
162    /// Set first block.
163    pub fn set_first_block(&mut self, first_block: BlockNumber) {
164        self.first_block = first_block;
165    }
166
167    /// Return iterator over all accounts
168    pub fn accounts_iter(&self) -> impl Iterator<Item = (Address, Option<&AccountInfo>)> {
169        self.bundle.state().iter().map(|(a, acc)| (*a, acc.info.as_ref()))
170    }
171
172    /// Return iterator over all [`BundleAccount`]s in the bundle
173    pub fn bundle_accounts_iter(&self) -> impl Iterator<Item = (Address, &BundleAccount)> {
174        self.bundle.state().iter().map(|(a, acc)| (*a, acc))
175    }
176
177    /// Get account if account is known.
178    pub fn account(&self, address: &Address) -> Option<Option<Account>> {
179        self.bundle.account(address).map(|a| a.info.as_ref().map(Into::into))
180    }
181
182    /// Get storage if value is known.
183    ///
184    /// This means that depending on status we can potentially return `U256::ZERO`.
185    pub fn storage(&self, address: &Address, storage_key: U256) -> Option<FlaggedStorage> {
186        self.bundle.account(address).and_then(|a| a.storage_slot(storage_key))
187    }
188
189    /// Return bytecode if known.
190    pub fn bytecode(&self, code_hash: &B256) -> Option<Bytecode> {
191        self.bundle.bytecode(code_hash).map(Bytecode)
192    }
193
194    /// Returns [`HashedPostState`] for this execution outcome.
195    /// See [`HashedPostState::from_bundle_state`] for more info.
196    pub fn hash_state_slow<KH: KeyHasher>(&self) -> HashedPostState {
197        HashedPostState::from_bundle_state::<KH>(&self.bundle.state)
198    }
199
200    /// Transform block number to the index of block.
201    pub fn block_number_to_index(&self, block_number: BlockNumber) -> Option<usize> {
202        if self.first_block > block_number {
203            return None
204        }
205        let index = block_number - self.first_block;
206        if index >= self.receipts.len() as u64 {
207            return None
208        }
209        Some(index as usize)
210    }
211
212    /// Returns the receipt root for all recorded receipts.
213    /// Note: this function calculated Bloom filters for every receipt and created merkle trees
214    /// of receipt. This is a expensive operation.
215    pub fn generic_receipts_root_slow(
216        &self,
217        block_number: BlockNumber,
218        f: impl FnOnce(&[&T]) -> B256,
219    ) -> Option<B256> {
220        self.receipts.root_slow(self.block_number_to_index(block_number)?, f)
221    }
222
223    /// Returns reference to receipts.
224    pub const fn receipts(&self) -> &Receipts<T> {
225        &self.receipts
226    }
227
228    /// Returns mutable reference to receipts.
229    pub fn receipts_mut(&mut self) -> &mut Receipts<T> {
230        &mut self.receipts
231    }
232
233    /// Return all block receipts
234    pub fn receipts_by_block(&self, block_number: BlockNumber) -> &[Option<T>] {
235        let Some(index) = self.block_number_to_index(block_number) else { return &[] };
236        &self.receipts[index]
237    }
238
239    /// Is execution outcome empty.
240    pub fn is_empty(&self) -> bool {
241        self.len() == 0
242    }
243
244    /// Number of blocks in the execution outcome.
245    pub fn len(&self) -> usize {
246        self.receipts.len()
247    }
248
249    /// Return first block of the execution outcome
250    pub const fn first_block(&self) -> BlockNumber {
251        self.first_block
252    }
253
254    /// Revert the state to the given block number.
255    ///
256    /// Returns false if the block number is not in the bundle state.
257    ///
258    /// # Note
259    ///
260    /// The provided block number will stay inside the bundle state.
261    pub fn revert_to(&mut self, block_number: BlockNumber) -> bool {
262        let Some(index) = self.block_number_to_index(block_number) else { return false };
263
264        // +1 is for number of blocks that we have as index is included.
265        let new_len = index + 1;
266        let rm_trx: usize = self.len() - new_len;
267
268        // remove receipts
269        self.receipts.truncate(new_len);
270        // remove requests
271        self.requests.truncate(new_len);
272        // Revert last n reverts.
273        self.bundle.revert(rm_trx);
274
275        true
276    }
277
278    /// Splits the block range state at a given block number.
279    /// Returns two split states ([..at], [at..]).
280    /// The plain state of the 2nd bundle state will contain extra changes
281    /// that were made in state transitions belonging to the lower state.
282    ///
283    /// # Panics
284    ///
285    /// If the target block number is not included in the state block range.
286    pub fn split_at(self, at: BlockNumber) -> (Option<Self>, Self)
287    where
288        T: Clone,
289    {
290        if at == self.first_block {
291            return (None, self)
292        }
293
294        let (mut lower_state, mut higher_state) = (self.clone(), self);
295
296        // Revert lower state to [..at].
297        lower_state.revert_to(at.checked_sub(1).unwrap());
298
299        // Truncate higher state to [at..].
300        let at_idx = higher_state.block_number_to_index(at).unwrap();
301        higher_state.receipts = higher_state.receipts.split_off(at_idx).into();
302        // Ensure that there are enough requests to truncate.
303        // Sometimes we just have receipts and no requests.
304        if at_idx < higher_state.requests.len() {
305            higher_state.requests = higher_state.requests.split_off(at_idx);
306        }
307        higher_state.bundle.take_n_reverts(at_idx);
308        higher_state.first_block = at;
309
310        (Some(lower_state), higher_state)
311    }
312
313    /// Extend one state from another
314    ///
315    /// For state this is very sensitive operation and should be used only when
316    /// we know that other state was build on top of this one.
317    /// In most cases this would be true.
318    pub fn extend(&mut self, other: Self) {
319        self.bundle.extend(other.bundle);
320        self.receipts.extend(other.receipts.receipt_vec);
321        self.requests.extend(other.requests);
322    }
323
324    /// Prepends present the state with the given `BundleState`.
325    /// It adds changes from the given state but does not override any existing changes.
326    ///
327    /// Reverts  and receipts are not updated.
328    pub fn prepend_state(&mut self, mut other: BundleState) {
329        let other_len = other.reverts.len();
330        // take this bundle
331        let this_bundle = std::mem::take(&mut self.bundle);
332        // extend other bundle with this
333        other.extend(this_bundle);
334        // discard other reverts
335        other.take_n_reverts(other_len);
336        // swap bundles
337        std::mem::swap(&mut self.bundle, &mut other)
338    }
339
340    /// Create a new instance with updated receipts.
341    pub fn with_receipts(mut self, receipts: Receipts<T>) -> Self {
342        self.receipts = receipts;
343        self
344    }
345
346    /// Create a new instance with updated requests.
347    pub fn with_requests(mut self, requests: Vec<Requests>) -> Self {
348        self.requests = requests;
349        self
350    }
351
352    /// Returns an iterator over all changed accounts from the `ExecutionOutcome`.
353    ///
354    /// This method filters the accounts to return only those that have undergone changes
355    /// and maps them into `ChangedAccount` instances, which include the address, nonce, and
356    /// balance.
357    pub fn changed_accounts(&self) -> impl Iterator<Item = ChangedAccount> + '_ {
358        self.accounts_iter().filter_map(|(addr, acc)| acc.map(|acc| (addr, acc))).map(
359            |(address, acc)| ChangedAccount { address, nonce: acc.nonce, balance: acc.balance },
360        )
361    }
362}
363
364impl<T: Receipt<Log = Log>> ExecutionOutcome<T> {
365    /// Returns an iterator over all block logs.
366    pub fn logs(&self, block_number: BlockNumber) -> Option<impl Iterator<Item = &Log>> {
367        let index = self.block_number_to_index(block_number)?;
368        Some(self.receipts[index].iter().filter_map(|r| Some(r.as_ref()?.logs().iter())).flatten())
369    }
370
371    /// Return blocks logs bloom
372    pub fn block_logs_bloom(&self, block_number: BlockNumber) -> Option<Bloom> {
373        Some(logs_bloom(self.logs(block_number)?))
374    }
375}
376
377impl ExecutionOutcome {
378    /// Returns the ethereum receipt root for all recorded receipts.
379    ///
380    /// Note: this function calculated Bloom filters for every receipt and created merkle trees
381    /// of receipt. This is a expensive operation.
382    pub fn ethereum_receipts_root(&self, _block_number: BlockNumber) -> Option<B256> {
383        self.receipts.root_slow(self.block_number_to_index(_block_number)?, |receipts| {
384            reth_primitives::proofs::calculate_receipt_root_no_memo(receipts)
385        })
386    }
387}
388
389impl<T> From<(BlockExecutionOutput<T>, BlockNumber)> for ExecutionOutcome<T> {
390    fn from(value: (BlockExecutionOutput<T>, BlockNumber)) -> Self {
391        Self {
392            bundle: value.0.state,
393            receipts: Receipts::from(value.0.receipts),
394            first_block: value.1,
395            requests: vec![value.0.requests],
396        }
397    }
398}
399
400#[cfg(test)]
401mod tests {
402    use super::*;
403    #[cfg(not(feature = "optimism"))]
404    use alloy_primitives::bytes;
405    #[cfg(not(feature = "optimism"))]
406    use alloy_primitives::LogData;
407    use alloy_primitives::{Address, B256};
408    use reth_primitives::Receipts;
409    #[cfg(not(feature = "optimism"))]
410    use reth_primitives::TxType;
411
412    #[test]
413    #[cfg(not(feature = "optimism"))]
414    fn test_initialisation() {
415        // Create a new BundleState object with initial data
416        let bundle = BundleState::new(
417            vec![(Address::new([2; 20]), None, Some(AccountInfo::default()), HashMap::default())],
418            vec![vec![(Address::new([2; 20]), None, vec![])]],
419            vec![],
420        );
421
422        // Create a Receipts object with a vector of receipt vectors
423        let receipts = Receipts {
424            receipt_vec: vec![vec![Some(reth_primitives::Receipt {
425                tx_type: TxType::Legacy,
426                cumulative_gas_used: 46913,
427                logs: vec![],
428                success: true,
429            })]],
430        };
431
432        // Create a Requests object with a vector of requests
433        let requests = vec![Requests::new(vec![bytes!("dead"), bytes!("beef"), bytes!("beebee")])];
434
435        // Define the first block number
436        let first_block = 123;
437
438        // Create a ExecutionOutcome object with the created bundle, receipts, requests, and
439        // first_block
440        let exec_res = ExecutionOutcome {
441            bundle: bundle.clone(),
442            receipts: receipts.clone(),
443            requests: requests.clone(),
444            first_block,
445        };
446
447        // Assert that creating a new ExecutionOutcome using the constructor matches exec_res
448        assert_eq!(
449            ExecutionOutcome::new(bundle, receipts.clone(), first_block, requests.clone()),
450            exec_res
451        );
452
453        // Create a BundleStateInit object and insert initial data
454        let mut state_init: BundleStateInit = HashMap::default();
455        state_init
456            .insert(Address::new([2; 20]), (None, Some(Account::default()), HashMap::default()));
457
458        // Create a HashMap for account reverts and insert initial data
459        let mut revert_inner: HashMap<Address, AccountRevertInit> = HashMap::default();
460        revert_inner.insert(Address::new([2; 20]), (None, vec![]));
461
462        // Create a RevertsInit object and insert the revert_inner data
463        let mut revert_init: RevertsInit = HashMap::default();
464        revert_init.insert(123, revert_inner);
465
466        // Assert that creating a new ExecutionOutcome using the new_init method matches
467        // exec_res
468        assert_eq!(
469            ExecutionOutcome::new_init(
470                state_init,
471                revert_init,
472                vec![],
473                receipts,
474                first_block,
475                requests,
476            ),
477            exec_res
478        );
479    }
480
481    #[test]
482    #[cfg(not(feature = "optimism"))]
483    fn test_block_number_to_index() {
484        // Create a Receipts object with a vector of receipt vectors
485        let receipts = Receipts {
486            receipt_vec: vec![vec![Some(reth_primitives::Receipt {
487                tx_type: TxType::Legacy,
488                cumulative_gas_used: 46913,
489                logs: vec![],
490                success: true,
491            })]],
492        };
493
494        // Define the first block number
495        let first_block = 123;
496
497        // Create a ExecutionOutcome object with the created bundle, receipts, requests, and
498        // first_block
499        let exec_res = ExecutionOutcome {
500            bundle: Default::default(),
501            receipts,
502            requests: vec![],
503            first_block,
504        };
505
506        // Test before the first block
507        assert_eq!(exec_res.block_number_to_index(12), None);
508
509        // Test after after the first block but index larger than receipts length
510        assert_eq!(exec_res.block_number_to_index(133), None);
511
512        // Test after the first block
513        assert_eq!(exec_res.block_number_to_index(123), Some(0));
514    }
515
516    #[test]
517    #[cfg(not(feature = "optimism"))]
518    fn test_get_logs() {
519        // Create a Receipts object with a vector of receipt vectors
520        let receipts = Receipts {
521            receipt_vec: vec![vec![Some(reth_primitives::Receipt {
522                tx_type: TxType::Legacy,
523                cumulative_gas_used: 46913,
524                logs: vec![Log::<LogData>::default()],
525                success: true,
526            })]],
527        };
528
529        // Define the first block number
530        let first_block = 123;
531
532        // Create a ExecutionOutcome object with the created bundle, receipts, requests, and
533        // first_block
534        let exec_res = ExecutionOutcome {
535            bundle: Default::default(),
536            receipts,
537            requests: vec![],
538            first_block,
539        };
540
541        // Get logs for block number 123
542        let logs: Vec<&Log> = exec_res.logs(123).unwrap().collect();
543
544        // Assert that the logs match the expected logs
545        assert_eq!(logs, vec![&Log::<LogData>::default()]);
546    }
547
548    #[test]
549    #[cfg(not(feature = "optimism"))]
550    fn test_receipts_by_block() {
551        // Create a Receipts object with a vector of receipt vectors
552        let receipts = Receipts {
553            receipt_vec: vec![vec![Some(reth_primitives::Receipt {
554                tx_type: TxType::Legacy,
555                cumulative_gas_used: 46913,
556                logs: vec![Log::<LogData>::default()],
557                success: true,
558            })]],
559        };
560
561        // Define the first block number
562        let first_block = 123;
563
564        // Create a ExecutionOutcome object with the created bundle, receipts, requests, and
565        // first_block
566        let exec_res = ExecutionOutcome {
567            bundle: Default::default(), // Default value for bundle
568            receipts,                   // Include the created receipts
569            requests: vec![],           // Empty vector for requests
570            first_block,                // Set the first block number
571        };
572
573        // Get receipts for block number 123 and convert the result into a vector
574        let receipts_by_block: Vec<_> = exec_res.receipts_by_block(123).iter().collect();
575
576        // Assert that the receipts for block number 123 match the expected receipts
577        assert_eq!(
578            receipts_by_block,
579            vec![&Some(reth_primitives::Receipt {
580                tx_type: TxType::Legacy,
581                cumulative_gas_used: 46913,
582                logs: vec![Log::<LogData>::default()],
583                success: true,
584            })]
585        );
586    }
587
588    #[test]
589    #[cfg(not(feature = "optimism"))]
590    fn test_receipts_len() {
591        // Create a Receipts object with a vector of receipt vectors
592        let receipts = Receipts {
593            receipt_vec: vec![vec![Some(reth_primitives::Receipt {
594                tx_type: TxType::Legacy,
595                cumulative_gas_used: 46913,
596                logs: vec![Log::<LogData>::default()],
597                success: true,
598            })]],
599        };
600
601        // Create an empty Receipts object
602        let receipts_empty: Receipts = Receipts { receipt_vec: vec![] };
603
604        // Define the first block number
605        let first_block = 123;
606
607        // Create a ExecutionOutcome object with the created bundle, receipts, requests, and
608        // first_block
609        let exec_res = ExecutionOutcome {
610            bundle: Default::default(), // Default value for bundle
611            receipts,                   // Include the created receipts
612            requests: vec![],           // Empty vector for requests
613            first_block,                // Set the first block number
614        };
615
616        // Assert that the length of receipts in exec_res is 1
617        assert_eq!(exec_res.len(), 1);
618
619        // Assert that exec_res is not empty
620        assert!(!exec_res.is_empty());
621
622        // Create a ExecutionOutcome object with an empty Receipts object
623        let exec_res_empty_receipts = ExecutionOutcome {
624            bundle: Default::default(), // Default value for bundle
625            receipts: receipts_empty,   // Include the empty receipts
626            requests: vec![],           // Empty vector for requests
627            first_block,                // Set the first block number
628        };
629
630        // Assert that the length of receipts in exec_res_empty_receipts is 0
631        assert_eq!(exec_res_empty_receipts.len(), 0);
632
633        // Assert that exec_res_empty_receipts is empty
634        assert!(exec_res_empty_receipts.is_empty());
635    }
636
637    #[test]
638    #[cfg(not(feature = "optimism"))]
639    fn test_revert_to() {
640        // Create a random receipt object
641        let receipt = reth_primitives::Receipt {
642            tx_type: TxType::Legacy,
643            cumulative_gas_used: 46913,
644            logs: vec![],
645            success: true,
646        };
647
648        // Create a Receipts object with a vector of receipt vectors
649        let receipts = Receipts {
650            receipt_vec: vec![vec![Some(receipt.clone())], vec![Some(receipt.clone())]],
651        };
652
653        // Define the first block number
654        let first_block = 123;
655
656        // Create a request.
657        let request = bytes!("deadbeef");
658
659        // Create a vector of Requests containing the request.
660        let requests =
661            vec![Requests::new(vec![request.clone()]), Requests::new(vec![request.clone()])];
662
663        // Create a ExecutionOutcome object with the created bundle, receipts, requests, and
664        // first_block
665        let mut exec_res =
666            ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block };
667
668        // Assert that the revert_to method returns true when reverting to the initial block number.
669        assert!(exec_res.revert_to(123));
670
671        // Assert that the receipts are properly cut after reverting to the initial block number.
672        assert_eq!(exec_res.receipts, Receipts { receipt_vec: vec![vec![Some(receipt)]] });
673
674        // Assert that the requests are properly cut after reverting to the initial block number.
675        assert_eq!(exec_res.requests, vec![Requests::new(vec![request])]);
676
677        // Assert that the revert_to method returns false when attempting to revert to a block
678        // number greater than the initial block number.
679        assert!(!exec_res.revert_to(133));
680
681        // Assert that the revert_to method returns false when attempting to revert to a block
682        // number less than the initial block number.
683        assert!(!exec_res.revert_to(10));
684    }
685
686    #[test]
687    #[cfg(not(feature = "optimism"))]
688    fn test_extend_execution_outcome() {
689        // Create a Receipt object with specific attributes.
690        let receipt = reth_primitives::Receipt {
691            tx_type: TxType::Legacy,
692            cumulative_gas_used: 46913,
693            logs: vec![],
694            success: true,
695        };
696
697        // Create a Receipts object containing the receipt.
698        let receipts = Receipts { receipt_vec: vec![vec![Some(receipt.clone())]] };
699
700        // Create a request.
701        let request = bytes!("deadbeef");
702
703        // Create a vector of Requests containing the request.
704        let requests = vec![Requests::new(vec![request.clone()])];
705
706        // Define the initial block number.
707        let first_block = 123;
708
709        // Create an ExecutionOutcome object.
710        let mut exec_res =
711            ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block };
712
713        // Extend the ExecutionOutcome object by itself.
714        exec_res.extend(exec_res.clone());
715
716        // Assert the extended ExecutionOutcome matches the expected outcome.
717        assert_eq!(
718            exec_res,
719            ExecutionOutcome {
720                bundle: Default::default(),
721                receipts: Receipts {
722                    receipt_vec: vec![vec![Some(receipt.clone())], vec![Some(receipt)]]
723                },
724                requests: vec![Requests::new(vec![request.clone()]), Requests::new(vec![request])],
725                first_block: 123,
726            }
727        );
728    }
729
730    #[test]
731    #[cfg(not(feature = "optimism"))]
732    fn test_split_at_execution_outcome() {
733        // Create a random receipt object
734        let receipt = reth_primitives::Receipt {
735            tx_type: TxType::Legacy,
736            cumulative_gas_used: 46913,
737            logs: vec![],
738            success: true,
739        };
740
741        // Create a Receipts object with a vector of receipt vectors
742        let receipts = Receipts {
743            receipt_vec: vec![
744                vec![Some(receipt.clone())],
745                vec![Some(receipt.clone())],
746                vec![Some(receipt.clone())],
747            ],
748        };
749
750        // Define the first block number
751        let first_block = 123;
752
753        // Create a request.
754        let request = bytes!("deadbeef");
755
756        // Create a vector of Requests containing the request.
757        let requests = vec![
758            Requests::new(vec![request.clone()]),
759            Requests::new(vec![request.clone()]),
760            Requests::new(vec![request.clone()]),
761        ];
762
763        // Create a ExecutionOutcome object with the created bundle, receipts, requests, and
764        // first_block
765        let exec_res =
766            ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block };
767
768        // Split the ExecutionOutcome at block number 124
769        let result = exec_res.clone().split_at(124);
770
771        // Define the expected lower ExecutionOutcome after splitting
772        let lower_execution_outcome = ExecutionOutcome {
773            bundle: Default::default(),
774            receipts: Receipts { receipt_vec: vec![vec![Some(receipt.clone())]] },
775            requests: vec![Requests::new(vec![request.clone()])],
776            first_block,
777        };
778
779        // Define the expected higher ExecutionOutcome after splitting
780        let higher_execution_outcome = ExecutionOutcome {
781            bundle: Default::default(),
782            receipts: Receipts {
783                receipt_vec: vec![vec![Some(receipt.clone())], vec![Some(receipt)]],
784            },
785            requests: vec![Requests::new(vec![request.clone()]), Requests::new(vec![request])],
786            first_block: 124,
787        };
788
789        // Assert that the split result matches the expected lower and higher outcomes
790        assert_eq!(result.0, Some(lower_execution_outcome));
791        assert_eq!(result.1, higher_execution_outcome);
792
793        // Assert that splitting at the first block number returns None for the lower outcome
794        assert_eq!(exec_res.clone().split_at(123), (None, exec_res));
795    }
796
797    #[test]
798    fn test_changed_accounts() {
799        // Set up some sample accounts
800        let address1 = Address::random();
801        let address2 = Address::random();
802        let address3 = Address::random();
803
804        // Set up account info with some changes
805        let account_info1 =
806            AccountInfo { nonce: 1, balance: U256::from(100), code_hash: B256::ZERO, code: None };
807        let account_info2 =
808            AccountInfo { nonce: 2, balance: U256::from(200), code_hash: B256::ZERO, code: None };
809
810        // Set up the bundle state with these accounts
811        let mut bundle_state = BundleState::default();
812        bundle_state.state.insert(
813            address1,
814            BundleAccount {
815                info: Some(account_info1),
816                storage: Default::default(),
817                original_info: Default::default(),
818                status: Default::default(),
819            },
820        );
821        bundle_state.state.insert(
822            address2,
823            BundleAccount {
824                info: Some(account_info2),
825                storage: Default::default(),
826                original_info: Default::default(),
827                status: Default::default(),
828            },
829        );
830
831        // Unchanged account
832        bundle_state.state.insert(
833            address3,
834            BundleAccount {
835                info: None,
836                storage: Default::default(),
837                original_info: Default::default(),
838                status: Default::default(),
839            },
840        );
841
842        let execution_outcome: ExecutionOutcome = ExecutionOutcome {
843            bundle: bundle_state,
844            receipts: Receipts::default(),
845            first_block: 0,
846            requests: vec![],
847        };
848
849        // Get the changed accounts
850        let changed_accounts: Vec<ChangedAccount> = execution_outcome.changed_accounts().collect();
851
852        // Assert that the changed accounts match the expected ones
853        assert_eq!(changed_accounts.len(), 2);
854
855        assert!(changed_accounts.contains(&ChangedAccount {
856            address: address1,
857            nonce: 1,
858            balance: U256::from(100)
859        }));
860
861        assert!(changed_accounts.contains(&ChangedAccount {
862            address: address2,
863            nonce: 2,
864            balance: U256::from(200)
865        }));
866    }
867}