1use crate::{BlockExecutionOutput, BlockExecutionResult};
2use alloc::{vec, vec::Vec};
3use alloy_eips::eip7685::Requests;
4use alloy_primitives::{logs_bloom, map::HashMap, Address, BlockNumber, Bloom, Log, B256, U256};
5use reth_primitives_traits::{Account, Bytecode, Receipt, StorageEntry};
6use reth_trie_common::{HashedPostState, KeyHasher};
7use revm::{
8 database::{states::BundleState, BundleAccount},
9 state::{AccountInfo, FlaggedStorage},
10};
11
12pub type BundleStateInit = HashMap<
14 Address,
15 (Option<Account>, Option<Account>, HashMap<B256, ((U256, bool), (U256, bool))>),
16>;
17
18pub type AccountRevertInit = (Option<Option<Account>>, Vec<StorageEntry>);
20
21pub type RevertsInit = HashMap<BlockNumber, HashMap<Address, AccountRevertInit>>;
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub struct ChangedAccount {
27 pub address: Address,
29 pub nonce: u64,
31 pub balance: U256,
33}
34
35impl ChangedAccount {
36 pub const fn empty(address: Address) -> Self {
38 Self { address, nonce: 0, balance: U256::ZERO }
39 }
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
47#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
48pub struct ExecutionOutcome<T = reth_ethereum_primitives::Receipt> {
49 pub bundle: BundleState,
51 pub receipts: Vec<Vec<T>>,
55 pub first_block: BlockNumber,
57 pub requests: Vec<Requests>,
64}
65
66impl<T> Default for ExecutionOutcome<T> {
67 fn default() -> Self {
68 Self {
69 bundle: Default::default(),
70 receipts: Default::default(),
71 first_block: Default::default(),
72 requests: Default::default(),
73 }
74 }
75}
76
77impl<T> ExecutionOutcome<T> {
78 pub const fn new(
83 bundle: BundleState,
84 receipts: Vec<Vec<T>>,
85 first_block: BlockNumber,
86 requests: Vec<Requests>,
87 ) -> Self {
88 Self { bundle, receipts, first_block, requests }
89 }
90
91 pub fn new_init(
96 state_init: BundleStateInit,
97 revert_init: RevertsInit,
98 contracts_init: impl IntoIterator<Item = (B256, Bytecode)>,
99 receipts: Vec<Vec<T>>,
100 first_block: BlockNumber,
101 requests: Vec<Requests>,
102 ) -> Self {
103 let mut reverts = revert_init.into_iter().collect::<Vec<_>>();
105 reverts.sort_unstable_by_key(|a| a.0);
106
107 let bundle = BundleState::new(
109 state_init.into_iter().map(|(address, (original, present, storage))| {
110 (
111 address,
112 original.map(Into::into),
113 present.map(Into::into),
114 storage
115 .into_iter()
116 .map(|(k, (orig_value, new_value))| {
117 (
118 k.into(),
119 (
120 FlaggedStorage::new_from_tuple(orig_value),
121 FlaggedStorage::new_from_tuple(new_value),
122 ),
123 )
124 })
125 .collect(),
126 )
127 }),
128 reverts.into_iter().map(|(_, reverts)| {
129 reverts.into_iter().map(|(address, (original, storage))| {
131 (
132 address,
133 original.map(|i| i.map(Into::into)),
134 storage.into_iter().map(|entry| {
135 (
136 entry.key.into(),
137 FlaggedStorage { value: entry.value, is_private: entry.is_private },
138 )
139 }),
140 )
141 })
142 }),
143 contracts_init.into_iter().map(|(code_hash, bytecode)| (code_hash, bytecode.0)),
144 );
145
146 Self { bundle, receipts, first_block, requests }
147 }
148
149 pub fn single(block_number: u64, output: BlockExecutionOutput<T>) -> Self {
151 Self {
152 bundle: output.state,
153 receipts: vec![output.result.receipts],
154 first_block: block_number,
155 requests: vec![output.result.requests],
156 }
157 }
158
159 pub fn from_blocks(
161 first_block: u64,
162 bundle: BundleState,
163 results: Vec<BlockExecutionResult<T>>,
164 ) -> Self {
165 let mut value = Self { bundle, first_block, receipts: Vec::new(), requests: Vec::new() };
166 for result in results {
167 value.receipts.push(result.receipts);
168 value.requests.push(result.requests);
169 }
170 value
171 }
172
173 pub const fn state(&self) -> &BundleState {
175 &self.bundle
176 }
177
178 pub const fn state_mut(&mut self) -> &mut BundleState {
180 &mut self.bundle
181 }
182
183 pub const fn set_first_block(&mut self, first_block: BlockNumber) {
185 self.first_block = first_block;
186 }
187
188 pub fn accounts_iter(&self) -> impl Iterator<Item = (Address, Option<&AccountInfo>)> {
190 self.bundle.state().iter().map(|(a, acc)| (*a, acc.info.as_ref()))
191 }
192
193 pub fn bundle_accounts_iter(&self) -> impl Iterator<Item = (Address, &BundleAccount)> {
195 self.bundle.state().iter().map(|(a, acc)| (*a, acc))
196 }
197
198 pub fn account(&self, address: &Address) -> Option<Option<Account>> {
200 self.bundle.account(address).map(|a| a.info.as_ref().map(Into::into))
201 }
202
203 pub fn storage(&self, address: &Address, storage_key: U256) -> Option<FlaggedStorage> {
207 self.bundle.account(address).and_then(|a| a.storage_slot(storage_key))
208 }
209
210 pub fn bytecode(&self, code_hash: &B256) -> Option<Bytecode> {
212 self.bundle.bytecode(code_hash).map(Bytecode)
213 }
214
215 pub fn hash_state_slow<KH: KeyHasher>(&self) -> HashedPostState {
218 HashedPostState::from_bundle_state::<KH>(&self.bundle.state)
219 }
220
221 pub fn block_number_to_index(&self, block_number: BlockNumber) -> Option<usize> {
223 if self.first_block > block_number {
224 return None
225 }
226 let index = block_number - self.first_block;
227 if index >= self.receipts.len() as u64 {
228 return None
229 }
230 Some(index as usize)
231 }
232
233 pub fn generic_receipts_root_slow(
237 &self,
238 block_number: BlockNumber,
239 f: impl FnOnce(&[T]) -> B256,
240 ) -> Option<B256> {
241 Some(f(self.receipts.get(self.block_number_to_index(block_number)?)?))
242 }
243
244 pub const fn receipts(&self) -> &Vec<Vec<T>> {
246 &self.receipts
247 }
248
249 pub const fn receipts_mut(&mut self) -> &mut Vec<Vec<T>> {
251 &mut self.receipts
252 }
253
254 pub fn receipts_by_block(&self, block_number: BlockNumber) -> &[T] {
256 let Some(index) = self.block_number_to_index(block_number) else { return &[] };
257 &self.receipts[index]
258 }
259
260 pub fn is_empty(&self) -> bool {
262 self.len() == 0
263 }
264
265 pub fn len(&self) -> usize {
267 self.receipts.len()
268 }
269
270 pub const fn first_block(&self) -> BlockNumber {
272 self.first_block
273 }
274
275 pub fn last_block(&self) -> BlockNumber {
277 (self.first_block + self.len() as u64).saturating_sub(1)
278 }
279
280 pub fn revert_to(&mut self, block_number: BlockNumber) -> bool {
288 let Some(index) = self.block_number_to_index(block_number) else { return false };
289
290 let new_len = index + 1;
292 let rm_trx: usize = self.len() - new_len;
293
294 self.receipts.truncate(new_len);
296 self.requests.truncate(new_len);
298 self.bundle.revert(rm_trx);
300
301 true
302 }
303
304 pub fn split_at(self, at: BlockNumber) -> (Option<Self>, Self)
313 where
314 T: Clone,
315 {
316 if at == self.first_block {
317 return (None, self)
318 }
319
320 let (mut lower_state, mut higher_state) = (self.clone(), self);
321
322 lower_state.revert_to(at.checked_sub(1).unwrap());
324
325 let at_idx = higher_state.block_number_to_index(at).unwrap();
327 higher_state.receipts = higher_state.receipts.split_off(at_idx);
328 if at_idx < higher_state.requests.len() {
331 higher_state.requests = higher_state.requests.split_off(at_idx);
332 }
333 higher_state.bundle.take_n_reverts(at_idx);
334 higher_state.first_block = at;
335
336 (Some(lower_state), higher_state)
337 }
338
339 pub fn extend(&mut self, other: Self) {
345 self.bundle.extend(other.bundle);
346 self.receipts.extend(other.receipts);
347 self.requests.extend(other.requests);
348 }
349
350 pub fn prepend_state(&mut self, mut other: BundleState) {
355 let other_len = other.reverts.len();
356 let this_bundle = core::mem::take(&mut self.bundle);
358 other.extend(this_bundle);
360 other.take_n_reverts(other_len);
362 core::mem::swap(&mut self.bundle, &mut other)
364 }
365
366 pub fn with_receipts(mut self, receipts: Vec<Vec<T>>) -> Self {
368 self.receipts = receipts;
369 self
370 }
371
372 pub fn with_requests(mut self, requests: Vec<Requests>) -> Self {
374 self.requests = requests;
375 self
376 }
377
378 pub fn changed_accounts(&self) -> impl Iterator<Item = ChangedAccount> + '_ {
384 self.accounts_iter().filter_map(|(addr, acc)| acc.map(|acc| (addr, acc))).map(
385 |(address, acc)| ChangedAccount { address, nonce: acc.nonce, balance: acc.balance },
386 )
387 }
388}
389
390impl<T: Receipt<Log = Log>> ExecutionOutcome<T> {
391 pub fn logs(&self, block_number: BlockNumber) -> Option<impl Iterator<Item = &Log>> {
393 let index = self.block_number_to_index(block_number)?;
394 Some(self.receipts[index].iter().flat_map(|r| r.logs()))
395 }
396
397 pub fn block_logs_bloom(&self, block_number: BlockNumber) -> Option<Bloom> {
399 Some(logs_bloom(self.logs(block_number)?))
400 }
401}
402
403impl ExecutionOutcome {
404 pub fn ethereum_receipts_root(&self, block_number: BlockNumber) -> Option<B256> {
409 self.generic_receipts_root_slow(
410 block_number,
411 reth_ethereum_primitives::Receipt::calculate_receipt_root_no_memo,
412 )
413 }
414}
415
416impl<T> From<(BlockExecutionOutput<T>, BlockNumber)> for ExecutionOutcome<T> {
417 fn from((output, block_number): (BlockExecutionOutput<T>, BlockNumber)) -> Self {
418 Self::single(block_number, output)
419 }
420}
421
422#[cfg(feature = "serde-bincode-compat")]
423pub(super) mod serde_bincode_compat {
424 use alloc::{borrow::Cow, vec::Vec};
425 use alloy_eips::eip7685::Requests;
426 use alloy_primitives::BlockNumber;
427 use reth_primitives_traits::serde_bincode_compat::SerdeBincodeCompat;
428 use revm::database::BundleState;
429 use serde::{Deserialize, Deserializer, Serialize, Serializer};
430 use serde_with::{DeserializeAs, SerializeAs};
431
432 #[derive(Debug, Serialize, Deserialize)]
450 pub struct ExecutionOutcome<'a, T>
451 where
452 T: SerdeBincodeCompat + core::fmt::Debug,
453 {
454 bundle: Cow<'a, BundleState>,
455 receipts: Vec<Vec<T::BincodeRepr<'a>>>,
456 first_block: BlockNumber,
457 #[expect(clippy::owned_cow)]
458 requests: Cow<'a, Vec<Requests>>,
459 }
460
461 impl<'a, T> From<&'a super::ExecutionOutcome<T>> for ExecutionOutcome<'a, T>
462 where
463 T: SerdeBincodeCompat + core::fmt::Debug,
464 {
465 fn from(value: &'a super::ExecutionOutcome<T>) -> Self {
466 ExecutionOutcome {
467 bundle: Cow::Borrowed(&value.bundle),
468 receipts: value
469 .receipts
470 .iter()
471 .map(|vec| vec.iter().map(|receipt| T::as_repr(receipt)).collect())
472 .collect(),
473 first_block: value.first_block,
474 requests: Cow::Borrowed(&value.requests),
475 }
476 }
477 }
478
479 impl<'a, T> From<ExecutionOutcome<'a, T>> for super::ExecutionOutcome<T>
480 where
481 T: SerdeBincodeCompat + core::fmt::Debug,
482 {
483 fn from(value: ExecutionOutcome<'a, T>) -> Self {
484 Self {
485 bundle: value.bundle.into_owned(),
486 receipts: value
487 .receipts
488 .into_iter()
489 .map(|vec| vec.into_iter().map(|receipt| T::from_repr(receipt)).collect())
490 .collect(),
491 first_block: value.first_block,
492 requests: value.requests.into_owned(),
493 }
494 }
495 }
496
497 impl<T> SerializeAs<super::ExecutionOutcome<T>> for ExecutionOutcome<'_, T>
498 where
499 T: SerdeBincodeCompat + core::fmt::Debug,
500 {
501 fn serialize_as<S>(
502 source: &super::ExecutionOutcome<T>,
503 serializer: S,
504 ) -> Result<S::Ok, S::Error>
505 where
506 S: Serializer,
507 {
508 ExecutionOutcome::from(source).serialize(serializer)
509 }
510 }
511
512 impl<'de, T> DeserializeAs<'de, super::ExecutionOutcome<T>> for ExecutionOutcome<'de, T>
513 where
514 T: SerdeBincodeCompat + core::fmt::Debug,
515 {
516 fn deserialize_as<D>(deserializer: D) -> Result<super::ExecutionOutcome<T>, D::Error>
517 where
518 D: Deserializer<'de>,
519 {
520 ExecutionOutcome::deserialize(deserializer).map(Into::into)
521 }
522 }
523
524 impl<T: SerdeBincodeCompat + core::fmt::Debug> SerdeBincodeCompat for super::ExecutionOutcome<T> {
525 type BincodeRepr<'a> = ExecutionOutcome<'a, T>;
526
527 fn as_repr(&self) -> Self::BincodeRepr<'_> {
528 self.into()
529 }
530
531 fn from_repr(repr: Self::BincodeRepr<'_>) -> Self {
532 repr.into()
533 }
534 }
535
536 #[cfg(test)]
537 mod tests {
538 use super::super::{serde_bincode_compat, ExecutionOutcome};
539 use rand::Rng;
540 use reth_ethereum_primitives::Receipt;
541 use reth_primitives_traits::serde_bincode_compat::SerdeBincodeCompat;
542 use serde::{Deserialize, Serialize};
543 use serde_with::serde_as;
544
545 #[test]
546 fn test_chain_bincode_roundtrip() {
547 #[serde_as]
548 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
549 struct Data<T: SerdeBincodeCompat + core::fmt::Debug> {
550 #[serde_as(as = "serde_bincode_compat::ExecutionOutcome<'_, T>")]
551 data: ExecutionOutcome<T>,
552 }
553
554 let mut bytes = [0u8; 1024];
555 rand::rng().fill(bytes.as_mut_slice());
556 let data = Data {
557 data: ExecutionOutcome {
558 bundle: Default::default(),
559 receipts: vec![],
560 first_block: 0,
561 requests: vec![],
562 },
563 };
564
565 let encoded = bincode::serialize(&data).unwrap();
566 let decoded = bincode::deserialize::<Data<Receipt>>(&encoded).unwrap();
567 assert_eq!(decoded, data);
568 }
569 }
570}
571
572#[cfg(test)]
573mod tests {
574 use super::*;
575 use alloy_consensus::TxType;
576 use alloy_primitives::{bytes, Address, LogData, B256};
577
578 #[test]
579 fn test_initialisation() {
580 let bundle = BundleState::new(
582 vec![(Address::new([2; 20]), None, Some(AccountInfo::default()), HashMap::default())],
583 vec![vec![(Address::new([2; 20]), None, vec![])]],
584 vec![],
585 );
586
587 let receipts = vec![vec![Some(reth_ethereum_primitives::Receipt {
589 tx_type: TxType::Legacy,
590 cumulative_gas_used: 46913,
591 logs: vec![],
592 success: true,
593 })]];
594
595 let requests = vec![Requests::new(vec![bytes!("dead"), bytes!("beef"), bytes!("beebee")])];
597
598 let first_block = 123;
600
601 let exec_res = ExecutionOutcome {
604 bundle: bundle.clone(),
605 receipts: receipts.clone(),
606 requests: requests.clone(),
607 first_block,
608 };
609
610 assert_eq!(
612 ExecutionOutcome::new(bundle, receipts.clone(), first_block, requests.clone()),
613 exec_res
614 );
615
616 let mut state_init: BundleStateInit = HashMap::default();
618 state_init
619 .insert(Address::new([2; 20]), (None, Some(Account::default()), HashMap::default()));
620
621 let mut revert_inner: HashMap<Address, AccountRevertInit> = HashMap::default();
623 revert_inner.insert(Address::new([2; 20]), (None, vec![]));
624
625 let mut revert_init: RevertsInit = HashMap::default();
627 revert_init.insert(123, revert_inner);
628
629 assert_eq!(
632 ExecutionOutcome::new_init(
633 state_init,
634 revert_init,
635 vec![],
636 receipts,
637 first_block,
638 requests,
639 ),
640 exec_res
641 );
642 }
643
644 #[test]
645 fn test_block_number_to_index() {
646 let receipts = vec![vec![Some(reth_ethereum_primitives::Receipt {
648 tx_type: TxType::Legacy,
649 cumulative_gas_used: 46913,
650 logs: vec![],
651 success: true,
652 })]];
653
654 let first_block = 123;
656
657 let exec_res = ExecutionOutcome {
660 bundle: Default::default(),
661 receipts,
662 requests: vec![],
663 first_block,
664 };
665
666 assert_eq!(exec_res.block_number_to_index(12), None);
668
669 assert_eq!(exec_res.block_number_to_index(133), None);
671
672 assert_eq!(exec_res.block_number_to_index(123), Some(0));
674 }
675
676 #[test]
677 fn test_get_logs() {
678 let receipts = vec![vec![reth_ethereum_primitives::Receipt {
680 tx_type: TxType::Legacy,
681 cumulative_gas_used: 46913,
682 logs: vec![Log::<LogData>::default()],
683 success: true,
684 }]];
685
686 let first_block = 123;
688
689 let exec_res = ExecutionOutcome {
692 bundle: Default::default(),
693 receipts,
694 requests: vec![],
695 first_block,
696 };
697
698 let logs: Vec<&Log> = exec_res.logs(123).unwrap().collect();
700
701 assert_eq!(logs, vec![&Log::<LogData>::default()]);
703 }
704
705 #[test]
706 fn test_receipts_by_block() {
707 let receipts = vec![vec![Some(reth_ethereum_primitives::Receipt {
709 tx_type: TxType::Legacy,
710 cumulative_gas_used: 46913,
711 logs: vec![Log::<LogData>::default()],
712 success: true,
713 })]];
714
715 let first_block = 123;
717
718 let exec_res = ExecutionOutcome {
721 bundle: Default::default(), receipts, requests: vec![], first_block, };
726
727 let receipts_by_block: Vec<_> = exec_res.receipts_by_block(123).iter().collect();
729
730 assert_eq!(
732 receipts_by_block,
733 vec![&Some(reth_ethereum_primitives::Receipt {
734 tx_type: TxType::Legacy,
735 cumulative_gas_used: 46913,
736 logs: vec![Log::<LogData>::default()],
737 success: true,
738 })]
739 );
740 }
741
742 #[test]
743 fn test_receipts_len() {
744 let receipts = vec![vec![Some(reth_ethereum_primitives::Receipt {
746 tx_type: TxType::Legacy,
747 cumulative_gas_used: 46913,
748 logs: vec![Log::<LogData>::default()],
749 success: true,
750 })]];
751
752 let receipts_empty = vec![];
754
755 let first_block = 123;
757
758 let exec_res = ExecutionOutcome {
761 bundle: Default::default(), receipts, requests: vec![], first_block, };
766
767 assert_eq!(exec_res.len(), 1);
769
770 assert!(!exec_res.is_empty());
772
773 let exec_res_empty_receipts: ExecutionOutcome = ExecutionOutcome {
775 bundle: Default::default(), receipts: receipts_empty, requests: vec![], first_block, };
780
781 assert_eq!(exec_res_empty_receipts.len(), 0);
783
784 assert!(exec_res_empty_receipts.is_empty());
786 }
787
788 #[test]
789 fn test_revert_to() {
790 let receipt = reth_ethereum_primitives::Receipt {
792 tx_type: TxType::Legacy,
793 cumulative_gas_used: 46913,
794 logs: vec![],
795 success: true,
796 };
797
798 let receipts = vec![vec![Some(receipt.clone())], vec![Some(receipt.clone())]];
800
801 let first_block = 123;
803
804 let request = bytes!("deadbeef");
806
807 let requests =
809 vec![Requests::new(vec![request.clone()]), Requests::new(vec![request.clone()])];
810
811 let mut exec_res =
814 ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block };
815
816 assert!(exec_res.revert_to(123));
818
819 assert_eq!(exec_res.receipts, vec![vec![Some(receipt)]]);
821
822 assert_eq!(exec_res.requests, vec![Requests::new(vec![request])]);
824
825 assert!(!exec_res.revert_to(133));
828
829 assert!(!exec_res.revert_to(10));
832 }
833
834 #[test]
835 fn test_extend_execution_outcome() {
836 let receipt = reth_ethereum_primitives::Receipt {
838 tx_type: TxType::Legacy,
839 cumulative_gas_used: 46913,
840 logs: vec![],
841 success: true,
842 };
843
844 let receipts = vec![vec![Some(receipt.clone())]];
846
847 let request = bytes!("deadbeef");
849
850 let requests = vec![Requests::new(vec![request.clone()])];
852
853 let first_block = 123;
855
856 let mut exec_res =
858 ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block };
859
860 exec_res.extend(exec_res.clone());
862
863 assert_eq!(
865 exec_res,
866 ExecutionOutcome {
867 bundle: Default::default(),
868 receipts: vec![vec![Some(receipt.clone())], vec![Some(receipt)]],
869 requests: vec![Requests::new(vec![request.clone()]), Requests::new(vec![request])],
870 first_block: 123,
871 }
872 );
873 }
874
875 #[test]
876 fn test_split_at_execution_outcome() {
877 let receipt = reth_ethereum_primitives::Receipt {
879 tx_type: TxType::Legacy,
880 cumulative_gas_used: 46913,
881 logs: vec![],
882 success: true,
883 };
884
885 let receipts = vec![
887 vec![Some(receipt.clone())],
888 vec![Some(receipt.clone())],
889 vec![Some(receipt.clone())],
890 ];
891
892 let first_block = 123;
894
895 let request = bytes!("deadbeef");
897
898 let requests = vec![
900 Requests::new(vec![request.clone()]),
901 Requests::new(vec![request.clone()]),
902 Requests::new(vec![request.clone()]),
903 ];
904
905 let exec_res =
908 ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block };
909
910 let result = exec_res.clone().split_at(124);
912
913 let lower_execution_outcome = ExecutionOutcome {
915 bundle: Default::default(),
916 receipts: vec![vec![Some(receipt.clone())]],
917 requests: vec![Requests::new(vec![request.clone()])],
918 first_block,
919 };
920
921 let higher_execution_outcome = ExecutionOutcome {
923 bundle: Default::default(),
924 receipts: vec![vec![Some(receipt.clone())], vec![Some(receipt)]],
925 requests: vec![Requests::new(vec![request.clone()]), Requests::new(vec![request])],
926 first_block: 124,
927 };
928
929 assert_eq!(result.0, Some(lower_execution_outcome));
931 assert_eq!(result.1, higher_execution_outcome);
932
933 assert_eq!(exec_res.clone().split_at(123), (None, exec_res));
935 }
936
937 #[test]
938 fn test_changed_accounts() {
939 let address1 = Address::random();
941 let address2 = Address::random();
942 let address3 = Address::random();
943
944 let account_info1 =
946 AccountInfo { nonce: 1, balance: U256::from(100), code_hash: B256::ZERO, code: None };
947 let account_info2 =
948 AccountInfo { nonce: 2, balance: U256::from(200), code_hash: B256::ZERO, code: None };
949
950 let mut bundle_state = BundleState::default();
952 bundle_state.state.insert(
953 address1,
954 BundleAccount {
955 info: Some(account_info1),
956 storage: Default::default(),
957 original_info: Default::default(),
958 status: Default::default(),
959 },
960 );
961 bundle_state.state.insert(
962 address2,
963 BundleAccount {
964 info: Some(account_info2),
965 storage: Default::default(),
966 original_info: Default::default(),
967 status: Default::default(),
968 },
969 );
970
971 bundle_state.state.insert(
973 address3,
974 BundleAccount {
975 info: None,
976 storage: Default::default(),
977 original_info: Default::default(),
978 status: Default::default(),
979 },
980 );
981
982 let execution_outcome: ExecutionOutcome = ExecutionOutcome {
983 bundle: bundle_state,
984 receipts: Default::default(),
985 first_block: 0,
986 requests: vec![],
987 };
988
989 let changed_accounts: Vec<ChangedAccount> = execution_outcome.changed_accounts().collect();
991
992 assert_eq!(changed_accounts.len(), 2);
994
995 assert!(changed_accounts.contains(&ChangedAccount {
996 address: address1,
997 nonce: 1,
998 balance: U256::from(100)
999 }));
1000
1001 assert!(changed_accounts.contains(&ChangedAccount {
1002 address: address2,
1003 nonce: 2,
1004 balance: U256::from(200)
1005 }));
1006 }
1007}