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, (FlaggedStorage, FlaggedStorage)>),
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))| (k.into(), (orig_value, new_value)))
117 .collect(),
118 )
119 }),
120 reverts.into_iter().map(|(_, reverts)| {
121 reverts.into_iter().map(|(address, (original, storage))| {
123 (
124 address,
125 original.map(|i| i.map(Into::into)),
126 storage.into_iter().map(|entry| (entry.key.into(), entry.value)),
127 )
128 })
129 }),
130 contracts_init.into_iter().map(|(code_hash, bytecode)| (code_hash, bytecode.0)),
131 );
132
133 Self { bundle, receipts, first_block, requests }
134 }
135
136 pub fn single(block_number: u64, output: BlockExecutionOutput<T>) -> Self {
138 Self {
139 bundle: output.state,
140 receipts: vec![output.result.receipts],
141 first_block: block_number,
142 requests: vec![output.result.requests],
143 }
144 }
145
146 pub fn from_blocks(
148 first_block: u64,
149 bundle: BundleState,
150 results: Vec<BlockExecutionResult<T>>,
151 ) -> Self {
152 let mut value = Self { bundle, first_block, receipts: Vec::new(), requests: Vec::new() };
153 for result in results {
154 value.receipts.push(result.receipts);
155 value.requests.push(result.requests);
156 }
157 value
158 }
159
160 pub const fn state(&self) -> &BundleState {
162 &self.bundle
163 }
164
165 pub const fn state_mut(&mut self) -> &mut BundleState {
167 &mut self.bundle
168 }
169
170 pub const fn set_first_block(&mut self, first_block: BlockNumber) {
172 self.first_block = first_block;
173 }
174
175 pub fn accounts_iter(&self) -> impl Iterator<Item = (Address, Option<&AccountInfo>)> {
177 self.bundle.state().iter().map(|(a, acc)| (*a, acc.info.as_ref()))
178 }
179
180 pub fn bundle_accounts_iter(&self) -> impl Iterator<Item = (Address, &BundleAccount)> {
182 self.bundle.state().iter().map(|(a, acc)| (*a, acc))
183 }
184
185 pub fn account(&self, address: &Address) -> Option<Option<Account>> {
187 self.bundle.account(address).map(|a| a.info.as_ref().map(Into::into))
188 }
189
190 pub fn storage(&self, address: &Address, storage_key: U256) -> Option<FlaggedStorage> {
194 self.bundle.account(address).and_then(|a| a.storage_slot(storage_key))
195 }
196
197 pub fn bytecode(&self, code_hash: &B256) -> Option<Bytecode> {
199 self.bundle.bytecode(code_hash).map(Bytecode)
200 }
201
202 pub fn hash_state_slow<KH: KeyHasher>(&self) -> HashedPostState {
205 HashedPostState::from_bundle_state::<KH>(&self.bundle.state)
206 }
207
208 pub fn block_number_to_index(&self, block_number: BlockNumber) -> Option<usize> {
210 if self.first_block > block_number {
211 return None
212 }
213 let index = block_number - self.first_block;
214 if index >= self.receipts.len() as u64 {
215 return None
216 }
217 Some(index as usize)
218 }
219
220 pub fn generic_receipts_root_slow(
224 &self,
225 block_number: BlockNumber,
226 f: impl FnOnce(&[T]) -> B256,
227 ) -> Option<B256> {
228 Some(f(self.receipts.get(self.block_number_to_index(block_number)?)?))
229 }
230
231 pub const fn receipts(&self) -> &Vec<Vec<T>> {
233 &self.receipts
234 }
235
236 pub const fn receipts_mut(&mut self) -> &mut Vec<Vec<T>> {
238 &mut self.receipts
239 }
240
241 pub fn receipts_by_block(&self, block_number: BlockNumber) -> &[T] {
243 let Some(index) = self.block_number_to_index(block_number) else { return &[] };
244 &self.receipts[index]
245 }
246
247 pub fn is_empty(&self) -> bool {
249 self.len() == 0
250 }
251
252 pub fn len(&self) -> usize {
254 self.receipts.len()
255 }
256
257 pub const fn first_block(&self) -> BlockNumber {
259 self.first_block
260 }
261
262 pub fn last_block(&self) -> BlockNumber {
264 (self.first_block + self.len() as u64).saturating_sub(1)
265 }
266
267 pub fn revert_to(&mut self, block_number: BlockNumber) -> bool {
275 let Some(index) = self.block_number_to_index(block_number) else { return false };
276
277 let new_len = index + 1;
279 let rm_trx: usize = self.len() - new_len;
280
281 self.receipts.truncate(new_len);
283 self.requests.truncate(new_len);
285 self.bundle.revert(rm_trx);
287
288 true
289 }
290
291 pub fn split_at(self, at: BlockNumber) -> (Option<Self>, Self)
300 where
301 T: Clone,
302 {
303 if at == self.first_block {
304 return (None, self)
305 }
306
307 let (mut lower_state, mut higher_state) = (self.clone(), self);
308
309 lower_state.revert_to(at.checked_sub(1).unwrap());
311
312 let at_idx = higher_state.block_number_to_index(at).unwrap();
314 higher_state.receipts = higher_state.receipts.split_off(at_idx);
315 if at_idx < higher_state.requests.len() {
318 higher_state.requests = higher_state.requests.split_off(at_idx);
319 }
320 higher_state.bundle.take_n_reverts(at_idx);
321 higher_state.first_block = at;
322
323 (Some(lower_state), higher_state)
324 }
325
326 pub fn extend(&mut self, other: Self) {
332 self.bundle.extend(other.bundle);
333 self.receipts.extend(other.receipts);
334 self.requests.extend(other.requests);
335 }
336
337 pub fn prepend_state(&mut self, mut other: BundleState) {
342 let other_len = other.reverts.len();
343 let this_bundle = core::mem::take(&mut self.bundle);
345 other.extend(this_bundle);
347 other.take_n_reverts(other_len);
349 core::mem::swap(&mut self.bundle, &mut other)
351 }
352
353 pub fn with_receipts(mut self, receipts: Vec<Vec<T>>) -> Self {
355 self.receipts = receipts;
356 self
357 }
358
359 pub fn with_requests(mut self, requests: Vec<Requests>) -> Self {
361 self.requests = requests;
362 self
363 }
364
365 pub fn changed_accounts(&self) -> impl Iterator<Item = ChangedAccount> + '_ {
371 self.accounts_iter().filter_map(|(addr, acc)| acc.map(|acc| (addr, acc))).map(
372 |(address, acc)| ChangedAccount { address, nonce: acc.nonce, balance: acc.balance },
373 )
374 }
375}
376
377impl<T: Receipt<Log = Log>> ExecutionOutcome<T> {
378 pub fn logs(&self, block_number: BlockNumber) -> Option<impl Iterator<Item = &Log>> {
380 let index = self.block_number_to_index(block_number)?;
381 Some(self.receipts[index].iter().flat_map(|r| r.logs()))
382 }
383
384 pub fn block_logs_bloom(&self, block_number: BlockNumber) -> Option<Bloom> {
386 Some(logs_bloom(self.logs(block_number)?))
387 }
388}
389
390impl ExecutionOutcome {
391 pub fn ethereum_receipts_root(&self, block_number: BlockNumber) -> Option<B256> {
396 self.generic_receipts_root_slow(
397 block_number,
398 reth_ethereum_primitives::Receipt::calculate_receipt_root_no_memo,
399 )
400 }
401}
402
403impl<T> From<(BlockExecutionOutput<T>, BlockNumber)> for ExecutionOutcome<T> {
404 fn from((output, block_number): (BlockExecutionOutput<T>, BlockNumber)) -> Self {
405 Self::single(block_number, output)
406 }
407}
408
409#[cfg(feature = "serde-bincode-compat")]
410pub(super) mod serde_bincode_compat {
411 use alloc::{borrow::Cow, vec::Vec};
412 use alloy_eips::eip7685::Requests;
413 use alloy_primitives::BlockNumber;
414 use reth_primitives_traits::serde_bincode_compat::SerdeBincodeCompat;
415 use revm::database::BundleState;
416 use serde::{Deserialize, Deserializer, Serialize, Serializer};
417 use serde_with::{DeserializeAs, SerializeAs};
418
419 #[derive(Debug, Serialize, Deserialize)]
437 pub struct ExecutionOutcome<'a, T>
438 where
439 T: SerdeBincodeCompat + core::fmt::Debug,
440 {
441 bundle: Cow<'a, BundleState>,
442 receipts: Vec<Vec<T::BincodeRepr<'a>>>,
443 first_block: BlockNumber,
444 #[expect(clippy::owned_cow)]
445 requests: Cow<'a, Vec<Requests>>,
446 }
447
448 impl<'a, T> From<&'a super::ExecutionOutcome<T>> for ExecutionOutcome<'a, T>
449 where
450 T: SerdeBincodeCompat + core::fmt::Debug,
451 {
452 fn from(value: &'a super::ExecutionOutcome<T>) -> Self {
453 ExecutionOutcome {
454 bundle: Cow::Borrowed(&value.bundle),
455 receipts: value
456 .receipts
457 .iter()
458 .map(|vec| vec.iter().map(|receipt| T::as_repr(receipt)).collect())
459 .collect(),
460 first_block: value.first_block,
461 requests: Cow::Borrowed(&value.requests),
462 }
463 }
464 }
465
466 impl<'a, T> From<ExecutionOutcome<'a, T>> for super::ExecutionOutcome<T>
467 where
468 T: SerdeBincodeCompat + core::fmt::Debug,
469 {
470 fn from(value: ExecutionOutcome<'a, T>) -> Self {
471 Self {
472 bundle: value.bundle.into_owned(),
473 receipts: value
474 .receipts
475 .into_iter()
476 .map(|vec| vec.into_iter().map(|receipt| T::from_repr(receipt)).collect())
477 .collect(),
478 first_block: value.first_block,
479 requests: value.requests.into_owned(),
480 }
481 }
482 }
483
484 impl<T> SerializeAs<super::ExecutionOutcome<T>> for ExecutionOutcome<'_, T>
485 where
486 T: SerdeBincodeCompat + core::fmt::Debug,
487 {
488 fn serialize_as<S>(
489 source: &super::ExecutionOutcome<T>,
490 serializer: S,
491 ) -> Result<S::Ok, S::Error>
492 where
493 S: Serializer,
494 {
495 ExecutionOutcome::from(source).serialize(serializer)
496 }
497 }
498
499 impl<'de, T> DeserializeAs<'de, super::ExecutionOutcome<T>> for ExecutionOutcome<'de, T>
500 where
501 T: SerdeBincodeCompat + core::fmt::Debug,
502 {
503 fn deserialize_as<D>(deserializer: D) -> Result<super::ExecutionOutcome<T>, D::Error>
504 where
505 D: Deserializer<'de>,
506 {
507 ExecutionOutcome::deserialize(deserializer).map(Into::into)
508 }
509 }
510
511 impl<T: SerdeBincodeCompat + core::fmt::Debug> SerdeBincodeCompat for super::ExecutionOutcome<T> {
512 type BincodeRepr<'a> = ExecutionOutcome<'a, T>;
513
514 fn as_repr(&self) -> Self::BincodeRepr<'_> {
515 self.into()
516 }
517
518 fn from_repr(repr: Self::BincodeRepr<'_>) -> Self {
519 repr.into()
520 }
521 }
522
523 #[cfg(test)]
524 mod tests {
525 use super::super::{serde_bincode_compat, ExecutionOutcome};
526 use rand::Rng;
527 use reth_ethereum_primitives::Receipt;
528 use reth_primitives_traits::serde_bincode_compat::SerdeBincodeCompat;
529 use serde::{Deserialize, Serialize};
530 use serde_with::serde_as;
531
532 #[test]
533 fn test_chain_bincode_roundtrip() {
534 #[serde_as]
535 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
536 struct Data<T: SerdeBincodeCompat + core::fmt::Debug> {
537 #[serde_as(as = "serde_bincode_compat::ExecutionOutcome<'_, T>")]
538 data: ExecutionOutcome<T>,
539 }
540
541 let mut bytes = [0u8; 1024];
542 rand::rng().fill(bytes.as_mut_slice());
543 let data = Data {
544 data: ExecutionOutcome {
545 bundle: Default::default(),
546 receipts: vec![],
547 first_block: 0,
548 requests: vec![],
549 },
550 };
551
552 let encoded = bincode::serialize(&data).unwrap();
553 let decoded = bincode::deserialize::<Data<Receipt>>(&encoded).unwrap();
554 assert_eq!(decoded, data);
555 }
556 }
557}
558
559#[cfg(test)]
560mod tests {
561 use super::*;
562 use alloy_consensus::TxType;
563 use alloy_primitives::{bytes, Address, LogData, B256};
564
565 #[test]
566 fn test_initialisation() {
567 let bundle = BundleState::new(
569 vec![(Address::new([2; 20]), None, Some(AccountInfo::default()), HashMap::default())],
570 vec![vec![(Address::new([2; 20]), None, vec![])]],
571 vec![],
572 );
573
574 let receipts = vec![vec![Some(reth_ethereum_primitives::Receipt {
576 tx_type: TxType::Legacy,
577 cumulative_gas_used: 46913,
578 logs: vec![],
579 success: true,
580 })]];
581
582 let requests = vec![Requests::new(vec![bytes!("dead"), bytes!("beef"), bytes!("beebee")])];
584
585 let first_block = 123;
587
588 let exec_res = ExecutionOutcome {
591 bundle: bundle.clone(),
592 receipts: receipts.clone(),
593 requests: requests.clone(),
594 first_block,
595 };
596
597 assert_eq!(
599 ExecutionOutcome::new(bundle, receipts.clone(), first_block, requests.clone()),
600 exec_res
601 );
602
603 let mut state_init: BundleStateInit = HashMap::default();
605 state_init
606 .insert(Address::new([2; 20]), (None, Some(Account::default()), HashMap::default()));
607
608 let mut revert_inner: HashMap<Address, AccountRevertInit> = HashMap::default();
610 revert_inner.insert(Address::new([2; 20]), (None, vec![]));
611
612 let mut revert_init: RevertsInit = HashMap::default();
614 revert_init.insert(123, revert_inner);
615
616 assert_eq!(
619 ExecutionOutcome::new_init(
620 state_init,
621 revert_init,
622 vec![],
623 receipts,
624 first_block,
625 requests,
626 ),
627 exec_res
628 );
629 }
630
631 #[test]
632 fn test_block_number_to_index() {
633 let receipts = vec![vec![Some(reth_ethereum_primitives::Receipt {
635 tx_type: TxType::Legacy,
636 cumulative_gas_used: 46913,
637 logs: vec![],
638 success: true,
639 })]];
640
641 let first_block = 123;
643
644 let exec_res = ExecutionOutcome {
647 bundle: Default::default(),
648 receipts,
649 requests: vec![],
650 first_block,
651 };
652
653 assert_eq!(exec_res.block_number_to_index(12), None);
655
656 assert_eq!(exec_res.block_number_to_index(133), None);
658
659 assert_eq!(exec_res.block_number_to_index(123), Some(0));
661 }
662
663 #[test]
664 fn test_get_logs() {
665 let receipts = vec![vec![reth_ethereum_primitives::Receipt {
667 tx_type: TxType::Legacy,
668 cumulative_gas_used: 46913,
669 logs: vec![Log::<LogData>::default()],
670 success: true,
671 }]];
672
673 let first_block = 123;
675
676 let exec_res = ExecutionOutcome {
679 bundle: Default::default(),
680 receipts,
681 requests: vec![],
682 first_block,
683 };
684
685 let logs: Vec<&Log> = exec_res.logs(123).unwrap().collect();
687
688 assert_eq!(logs, vec![&Log::<LogData>::default()]);
690 }
691
692 #[test]
693 fn test_receipts_by_block() {
694 let receipts = vec![vec![Some(reth_ethereum_primitives::Receipt {
696 tx_type: TxType::Legacy,
697 cumulative_gas_used: 46913,
698 logs: vec![Log::<LogData>::default()],
699 success: true,
700 })]];
701
702 let first_block = 123;
704
705 let exec_res = ExecutionOutcome {
708 bundle: Default::default(), receipts, requests: vec![], first_block, };
713
714 let receipts_by_block: Vec<_> = exec_res.receipts_by_block(123).iter().collect();
716
717 assert_eq!(
719 receipts_by_block,
720 vec![&Some(reth_ethereum_primitives::Receipt {
721 tx_type: TxType::Legacy,
722 cumulative_gas_used: 46913,
723 logs: vec![Log::<LogData>::default()],
724 success: true,
725 })]
726 );
727 }
728
729 #[test]
730 fn test_receipts_len() {
731 let receipts = vec![vec![Some(reth_ethereum_primitives::Receipt {
733 tx_type: TxType::Legacy,
734 cumulative_gas_used: 46913,
735 logs: vec![Log::<LogData>::default()],
736 success: true,
737 })]];
738
739 let receipts_empty = vec![];
741
742 let first_block = 123;
744
745 let exec_res = ExecutionOutcome {
748 bundle: Default::default(), receipts, requests: vec![], first_block, };
753
754 assert_eq!(exec_res.len(), 1);
756
757 assert!(!exec_res.is_empty());
759
760 let exec_res_empty_receipts: ExecutionOutcome = ExecutionOutcome {
762 bundle: Default::default(), receipts: receipts_empty, requests: vec![], first_block, };
767
768 assert_eq!(exec_res_empty_receipts.len(), 0);
770
771 assert!(exec_res_empty_receipts.is_empty());
773 }
774
775 #[test]
776 fn test_revert_to() {
777 let receipt = reth_ethereum_primitives::Receipt {
779 tx_type: TxType::Legacy,
780 cumulative_gas_used: 46913,
781 logs: vec![],
782 success: true,
783 };
784
785 let receipts = vec![vec![Some(receipt.clone())], vec![Some(receipt.clone())]];
787
788 let first_block = 123;
790
791 let request = bytes!("deadbeef");
793
794 let requests =
796 vec![Requests::new(vec![request.clone()]), Requests::new(vec![request.clone()])];
797
798 let mut exec_res =
801 ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block };
802
803 assert!(exec_res.revert_to(123));
805
806 assert_eq!(exec_res.receipts, vec![vec![Some(receipt)]]);
808
809 assert_eq!(exec_res.requests, vec![Requests::new(vec![request])]);
811
812 assert!(!exec_res.revert_to(133));
815
816 assert!(!exec_res.revert_to(10));
819 }
820
821 #[test]
822 fn test_extend_execution_outcome() {
823 let receipt = reth_ethereum_primitives::Receipt {
825 tx_type: TxType::Legacy,
826 cumulative_gas_used: 46913,
827 logs: vec![],
828 success: true,
829 };
830
831 let receipts = vec![vec![Some(receipt.clone())]];
833
834 let request = bytes!("deadbeef");
836
837 let requests = vec![Requests::new(vec![request.clone()])];
839
840 let first_block = 123;
842
843 let mut exec_res =
845 ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block };
846
847 exec_res.extend(exec_res.clone());
849
850 assert_eq!(
852 exec_res,
853 ExecutionOutcome {
854 bundle: Default::default(),
855 receipts: vec![vec![Some(receipt.clone())], vec![Some(receipt)]],
856 requests: vec![Requests::new(vec![request.clone()]), Requests::new(vec![request])],
857 first_block: 123,
858 }
859 );
860 }
861
862 #[test]
863 fn test_split_at_execution_outcome() {
864 let receipt = reth_ethereum_primitives::Receipt {
866 tx_type: TxType::Legacy,
867 cumulative_gas_used: 46913,
868 logs: vec![],
869 success: true,
870 };
871
872 let receipts = vec![
874 vec![Some(receipt.clone())],
875 vec![Some(receipt.clone())],
876 vec![Some(receipt.clone())],
877 ];
878
879 let first_block = 123;
881
882 let request = bytes!("deadbeef");
884
885 let requests = vec![
887 Requests::new(vec![request.clone()]),
888 Requests::new(vec![request.clone()]),
889 Requests::new(vec![request.clone()]),
890 ];
891
892 let exec_res =
895 ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block };
896
897 let result = exec_res.clone().split_at(124);
899
900 let lower_execution_outcome = ExecutionOutcome {
902 bundle: Default::default(),
903 receipts: vec![vec![Some(receipt.clone())]],
904 requests: vec![Requests::new(vec![request.clone()])],
905 first_block,
906 };
907
908 let higher_execution_outcome = ExecutionOutcome {
910 bundle: Default::default(),
911 receipts: vec![vec![Some(receipt.clone())], vec![Some(receipt)]],
912 requests: vec![Requests::new(vec![request.clone()]), Requests::new(vec![request])],
913 first_block: 124,
914 };
915
916 assert_eq!(result.0, Some(lower_execution_outcome));
918 assert_eq!(result.1, higher_execution_outcome);
919
920 assert_eq!(exec_res.clone().split_at(123), (None, exec_res));
922 }
923
924 #[test]
925 fn test_changed_accounts() {
926 let address1 = Address::random();
928 let address2 = Address::random();
929 let address3 = Address::random();
930
931 let account_info1 =
933 AccountInfo { nonce: 1, balance: U256::from(100), code_hash: B256::ZERO, code: None };
934 let account_info2 =
935 AccountInfo { nonce: 2, balance: U256::from(200), code_hash: B256::ZERO, code: None };
936
937 let mut bundle_state = BundleState::default();
939 bundle_state.state.insert(
940 address1,
941 BundleAccount {
942 info: Some(account_info1),
943 storage: Default::default(),
944 original_info: Default::default(),
945 status: Default::default(),
946 },
947 );
948 bundle_state.state.insert(
949 address2,
950 BundleAccount {
951 info: Some(account_info2),
952 storage: Default::default(),
953 original_info: Default::default(),
954 status: Default::default(),
955 },
956 );
957
958 bundle_state.state.insert(
960 address3,
961 BundleAccount {
962 info: None,
963 storage: Default::default(),
964 original_info: Default::default(),
965 status: Default::default(),
966 },
967 );
968
969 let execution_outcome: ExecutionOutcome = ExecutionOutcome {
970 bundle: bundle_state,
971 receipts: Default::default(),
972 first_block: 0,
973 requests: vec![],
974 };
975
976 let changed_accounts: Vec<ChangedAccount> = execution_outcome.changed_accounts().collect();
978
979 assert_eq!(changed_accounts.len(), 2);
981
982 assert!(changed_accounts.contains(&ChangedAccount {
983 address: address1,
984 nonce: 1,
985 balance: U256::from(100)
986 }));
987
988 assert!(changed_accounts.contains(&ChangedAccount {
989 address: address2,
990 nonce: 2,
991 balance: U256::from(200)
992 }));
993 }
994}