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,
10};
11
12pub type BundleStateInit =
14 HashMap<Address, (Option<Account>, Option<Account>, HashMap<B256, (U256, U256)>)>;
15
16pub type AccountRevertInit = (Option<Option<Account>>, Vec<StorageEntry>);
18
19pub type RevertsInit = HashMap<BlockNumber, HashMap<Address, AccountRevertInit>>;
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub struct ChangedAccount {
25 pub address: Address,
27 pub nonce: u64,
29 pub balance: U256,
31}
32
33impl ChangedAccount {
34 pub const fn empty(address: Address) -> Self {
36 Self { address, nonce: 0, balance: U256::ZERO }
37 }
38}
39
40#[derive(Debug, Clone, PartialEq, Eq)]
45#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
46pub struct ExecutionOutcome<T = reth_ethereum_primitives::Receipt> {
47 pub bundle: BundleState,
49 pub receipts: Vec<Vec<T>>,
53 pub first_block: BlockNumber,
55 pub requests: Vec<Requests>,
62}
63
64impl<T> Default for ExecutionOutcome<T> {
65 fn default() -> Self {
66 Self {
67 bundle: Default::default(),
68 receipts: Default::default(),
69 first_block: Default::default(),
70 requests: Default::default(),
71 }
72 }
73}
74
75impl<T> ExecutionOutcome<T> {
76 pub const fn new(
81 bundle: BundleState,
82 receipts: Vec<Vec<T>>,
83 first_block: BlockNumber,
84 requests: Vec<Requests>,
85 ) -> Self {
86 Self { bundle, receipts, first_block, requests }
87 }
88
89 pub fn new_init(
94 state_init: BundleStateInit,
95 revert_init: RevertsInit,
96 contracts_init: impl IntoIterator<Item = (B256, Bytecode)>,
97 receipts: Vec<Vec<T>>,
98 first_block: BlockNumber,
99 requests: Vec<Requests>,
100 ) -> Self {
101 let mut reverts = revert_init.into_iter().collect::<Vec<_>>();
103 reverts.sort_unstable_by_key(|a| a.0);
104
105 let bundle = BundleState::new(
107 state_init.into_iter().map(|(address, (original, present, storage))| {
108 (
109 address,
110 original.map(Into::into),
111 present.map(Into::into),
112 storage.into_iter().map(|(k, s)| (k.into(), s)).collect(),
113 )
114 }),
115 reverts.into_iter().map(|(_, reverts)| {
116 reverts.into_iter().map(|(address, (original, storage))| {
118 (
119 address,
120 original.map(|i| i.map(Into::into)),
121 storage.into_iter().map(|entry| (entry.key.into(), entry.value)),
122 )
123 })
124 }),
125 contracts_init.into_iter().map(|(code_hash, bytecode)| (code_hash, bytecode.0)),
126 );
127
128 Self { bundle, receipts, first_block, requests }
129 }
130
131 pub fn single(block_number: u64, output: BlockExecutionOutput<T>) -> Self {
133 Self {
134 bundle: output.state,
135 receipts: vec![output.result.receipts],
136 first_block: block_number,
137 requests: vec![output.result.requests],
138 }
139 }
140
141 pub fn from_blocks(
143 first_block: u64,
144 bundle: BundleState,
145 results: Vec<BlockExecutionResult<T>>,
146 ) -> Self {
147 let mut value = Self {
148 bundle,
149 first_block,
150 receipts: Vec::with_capacity(results.len()),
151 requests: Vec::with_capacity(results.len()),
152 };
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 account_state(&self, address: &Address) -> Option<&BundleAccount> {
192 self.bundle.account(address)
193 }
194
195 pub fn storage(&self, address: &Address, storage_key: U256) -> Option<U256> {
199 self.bundle.account(address).and_then(|a| a.storage_slot(storage_key))
200 }
201
202 pub fn bytecode(&self, code_hash: &B256) -> Option<Bytecode> {
204 self.bundle.bytecode(code_hash).map(Bytecode)
205 }
206
207 pub fn hash_state_slow<KH: KeyHasher>(&self) -> HashedPostState {
210 HashedPostState::from_bundle_state::<KH>(&self.bundle.state)
211 }
212
213 pub const fn block_number_to_index(&self, block_number: BlockNumber) -> Option<usize> {
215 if self.first_block > block_number {
216 return None
217 }
218 let index = block_number - self.first_block;
219 if index >= self.receipts.len() as u64 {
220 return None
221 }
222 Some(index as usize)
223 }
224
225 pub fn generic_receipts_root_slow(
229 &self,
230 block_number: BlockNumber,
231 f: impl FnOnce(&[T]) -> B256,
232 ) -> Option<B256> {
233 Some(f(self.receipts.get(self.block_number_to_index(block_number)?)?))
234 }
235
236 pub const fn receipts(&self) -> &Vec<Vec<T>> {
238 &self.receipts
239 }
240
241 pub const fn receipts_mut(&mut self) -> &mut Vec<Vec<T>> {
243 &mut self.receipts
244 }
245
246 pub fn receipts_by_block(&self, block_number: BlockNumber) -> &[T] {
248 let Some(index) = self.block_number_to_index(block_number) else { return &[] };
249 &self.receipts[index]
250 }
251
252 pub const fn is_empty(&self) -> bool {
254 self.len() == 0
255 }
256
257 pub const fn len(&self) -> usize {
259 self.receipts.len()
260 }
261
262 pub const fn first_block(&self) -> BlockNumber {
264 self.first_block
265 }
266
267 pub const fn last_block(&self) -> BlockNumber {
269 (self.first_block + self.len() as u64).saturating_sub(1)
270 }
271
272 pub fn revert_to(&mut self, block_number: BlockNumber) -> bool {
280 let Some(index) = self.block_number_to_index(block_number) else { return false };
281
282 let new_len = index + 1;
284 let rm_trx: usize = self.len() - new_len;
285
286 self.receipts.truncate(new_len);
288 self.requests.truncate(new_len);
290 self.bundle.revert(rm_trx);
292
293 true
294 }
295
296 pub fn split_at(self, at: BlockNumber) -> (Option<Self>, Self)
305 where
306 T: Clone,
307 {
308 if at == self.first_block {
309 return (None, self)
310 }
311
312 let (mut lower_state, mut higher_state) = (self.clone(), self);
313
314 lower_state.revert_to(at.checked_sub(1).unwrap());
316
317 let at_idx = higher_state.block_number_to_index(at).unwrap();
319 higher_state.receipts = higher_state.receipts.split_off(at_idx);
320 if at_idx < higher_state.requests.len() {
323 higher_state.requests = higher_state.requests.split_off(at_idx);
324 }
325 higher_state.bundle.take_n_reverts(at_idx);
326 higher_state.first_block = at;
327
328 (Some(lower_state), higher_state)
329 }
330
331 pub fn extend(&mut self, other: Self) {
337 self.bundle.extend(other.bundle);
338 self.receipts.extend(other.receipts);
339 self.requests.extend(other.requests);
340 }
341
342 pub fn prepend_state(&mut self, mut other: BundleState) {
347 let other_len = other.reverts.len();
348 let this_bundle = core::mem::take(&mut self.bundle);
350 other.extend(this_bundle);
352 other.take_n_reverts(other_len);
354 core::mem::swap(&mut self.bundle, &mut other)
356 }
357
358 pub fn with_receipts(mut self, receipts: Vec<Vec<T>>) -> Self {
360 self.receipts = receipts;
361 self
362 }
363
364 pub fn with_requests(mut self, requests: Vec<Requests>) -> Self {
366 self.requests = requests;
367 self
368 }
369
370 pub fn changed_accounts(&self) -> impl Iterator<Item = ChangedAccount> + '_ {
376 self.accounts_iter().filter_map(|(addr, acc)| acc.map(|acc| (addr, acc))).map(
377 |(address, acc)| ChangedAccount { address, nonce: acc.nonce, balance: acc.balance },
378 )
379 }
380}
381
382impl<T: Receipt<Log = Log>> ExecutionOutcome<T> {
383 pub fn logs(&self, block_number: BlockNumber) -> Option<impl Iterator<Item = &Log>> {
385 let index = self.block_number_to_index(block_number)?;
386 Some(self.receipts[index].iter().flat_map(|r| r.logs()))
387 }
388
389 pub fn block_logs_bloom(&self, block_number: BlockNumber) -> Option<Bloom> {
391 Some(logs_bloom(self.logs(block_number)?))
392 }
393}
394
395impl ExecutionOutcome {
396 pub fn ethereum_receipts_root(&self, block_number: BlockNumber) -> Option<B256> {
401 self.generic_receipts_root_slow(
402 block_number,
403 reth_ethereum_primitives::Receipt::calculate_receipt_root_no_memo,
404 )
405 }
406}
407
408impl<T> From<(BlockExecutionOutput<T>, BlockNumber)> for ExecutionOutcome<T> {
409 fn from((output, block_number): (BlockExecutionOutput<T>, BlockNumber)) -> Self {
410 Self::single(block_number, output)
411 }
412}
413
414#[cfg(feature = "serde-bincode-compat")]
415pub(super) mod serde_bincode_compat {
416 use alloc::{borrow::Cow, vec::Vec};
417 use alloy_eips::eip7685::Requests;
418 use alloy_primitives::BlockNumber;
419 use reth_primitives_traits::serde_bincode_compat::SerdeBincodeCompat;
420 use revm::database::BundleState;
421 use serde::{Deserialize, Deserializer, Serialize, Serializer};
422 use serde_with::{DeserializeAs, SerializeAs};
423
424 #[derive(Debug, Serialize, Deserialize)]
442 pub struct ExecutionOutcome<'a, T>
443 where
444 T: SerdeBincodeCompat + core::fmt::Debug,
445 {
446 bundle: Cow<'a, BundleState>,
447 receipts: Vec<Vec<T::BincodeRepr<'a>>>,
448 first_block: BlockNumber,
449 #[expect(clippy::owned_cow)]
450 requests: Cow<'a, Vec<Requests>>,
451 }
452
453 impl<'a, T> From<&'a super::ExecutionOutcome<T>> for ExecutionOutcome<'a, T>
454 where
455 T: SerdeBincodeCompat + core::fmt::Debug,
456 {
457 fn from(value: &'a super::ExecutionOutcome<T>) -> Self {
458 ExecutionOutcome {
459 bundle: Cow::Borrowed(&value.bundle),
460 receipts: value
461 .receipts
462 .iter()
463 .map(|vec| vec.iter().map(|receipt| T::as_repr(receipt)).collect())
464 .collect(),
465 first_block: value.first_block,
466 requests: Cow::Borrowed(&value.requests),
467 }
468 }
469 }
470
471 impl<'a, T> From<ExecutionOutcome<'a, T>> for super::ExecutionOutcome<T>
472 where
473 T: SerdeBincodeCompat + core::fmt::Debug,
474 {
475 fn from(value: ExecutionOutcome<'a, T>) -> Self {
476 Self {
477 bundle: value.bundle.into_owned(),
478 receipts: value
479 .receipts
480 .into_iter()
481 .map(|vec| vec.into_iter().map(|receipt| T::from_repr(receipt)).collect())
482 .collect(),
483 first_block: value.first_block,
484 requests: value.requests.into_owned(),
485 }
486 }
487 }
488
489 impl<T> SerializeAs<super::ExecutionOutcome<T>> for ExecutionOutcome<'_, T>
490 where
491 T: SerdeBincodeCompat + core::fmt::Debug,
492 {
493 fn serialize_as<S>(
494 source: &super::ExecutionOutcome<T>,
495 serializer: S,
496 ) -> Result<S::Ok, S::Error>
497 where
498 S: Serializer,
499 {
500 ExecutionOutcome::from(source).serialize(serializer)
501 }
502 }
503
504 impl<'de, T> DeserializeAs<'de, super::ExecutionOutcome<T>> for ExecutionOutcome<'de, T>
505 where
506 T: SerdeBincodeCompat + core::fmt::Debug,
507 {
508 fn deserialize_as<D>(deserializer: D) -> Result<super::ExecutionOutcome<T>, D::Error>
509 where
510 D: Deserializer<'de>,
511 {
512 ExecutionOutcome::deserialize(deserializer).map(Into::into)
513 }
514 }
515
516 impl<T: SerdeBincodeCompat + core::fmt::Debug> SerdeBincodeCompat for super::ExecutionOutcome<T> {
517 type BincodeRepr<'a> = ExecutionOutcome<'a, T>;
518
519 fn as_repr(&self) -> Self::BincodeRepr<'_> {
520 self.into()
521 }
522
523 fn from_repr(repr: Self::BincodeRepr<'_>) -> Self {
524 repr.into()
525 }
526 }
527
528 #[cfg(test)]
529 mod tests {
530 use super::super::{serde_bincode_compat, ExecutionOutcome};
531 use rand::Rng;
532 use reth_ethereum_primitives::Receipt;
533 use reth_primitives_traits::serde_bincode_compat::SerdeBincodeCompat;
534 use serde::{Deserialize, Serialize};
535 use serde_with::serde_as;
536
537 #[test]
538 fn test_chain_bincode_roundtrip() {
539 #[serde_as]
540 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
541 struct Data<T: SerdeBincodeCompat + core::fmt::Debug> {
542 #[serde_as(as = "serde_bincode_compat::ExecutionOutcome<'_, T>")]
543 data: ExecutionOutcome<T>,
544 }
545
546 let mut bytes = [0u8; 1024];
547 rand::rng().fill(bytes.as_mut_slice());
548 let data = Data {
549 data: ExecutionOutcome {
550 bundle: Default::default(),
551 receipts: vec![],
552 first_block: 0,
553 requests: vec![],
554 },
555 };
556
557 let encoded = bincode::serialize(&data).unwrap();
558 let decoded = bincode::deserialize::<Data<Receipt>>(&encoded).unwrap();
559 assert_eq!(decoded, data);
560 }
561 }
562}
563
564#[cfg(test)]
565mod tests {
566 use super::*;
567 use alloy_consensus::TxType;
568 use alloy_primitives::{bytes, Address, LogData, B256};
569
570 #[test]
571 fn test_initialization() {
572 let bundle = BundleState::new(
574 vec![(Address::new([2; 20]), None, Some(AccountInfo::default()), HashMap::default())],
575 vec![vec![(Address::new([2; 20]), None, vec![])]],
576 vec![],
577 );
578
579 let receipts = vec![vec![Some(reth_ethereum_primitives::Receipt {
581 tx_type: TxType::Legacy,
582 cumulative_gas_used: 46913,
583 logs: vec![],
584 success: true,
585 })]];
586
587 let requests = vec![Requests::new(vec![bytes!("dead"), bytes!("beef"), bytes!("beebee")])];
589
590 let first_block = 123;
592
593 let exec_res = ExecutionOutcome {
596 bundle: bundle.clone(),
597 receipts: receipts.clone(),
598 requests: requests.clone(),
599 first_block,
600 };
601
602 assert_eq!(
604 ExecutionOutcome::new(bundle, receipts.clone(), first_block, requests.clone()),
605 exec_res
606 );
607
608 let mut state_init: BundleStateInit = HashMap::default();
610 state_init
611 .insert(Address::new([2; 20]), (None, Some(Account::default()), HashMap::default()));
612
613 let mut revert_inner: HashMap<Address, AccountRevertInit> = HashMap::default();
615 revert_inner.insert(Address::new([2; 20]), (None, vec![]));
616
617 let mut revert_init: RevertsInit = HashMap::default();
619 revert_init.insert(123, revert_inner);
620
621 assert_eq!(
624 ExecutionOutcome::new_init(
625 state_init,
626 revert_init,
627 vec![],
628 receipts,
629 first_block,
630 requests,
631 ),
632 exec_res
633 );
634 }
635
636 #[test]
637 fn test_block_number_to_index() {
638 let receipts = vec![vec![Some(reth_ethereum_primitives::Receipt {
640 tx_type: TxType::Legacy,
641 cumulative_gas_used: 46913,
642 logs: vec![],
643 success: true,
644 })]];
645
646 let first_block = 123;
648
649 let exec_res = ExecutionOutcome {
652 bundle: Default::default(),
653 receipts,
654 requests: vec![],
655 first_block,
656 };
657
658 assert_eq!(exec_res.block_number_to_index(12), None);
660
661 assert_eq!(exec_res.block_number_to_index(133), None);
663
664 assert_eq!(exec_res.block_number_to_index(123), Some(0));
666 }
667
668 #[test]
669 fn test_get_logs() {
670 let receipts = vec![vec![reth_ethereum_primitives::Receipt {
672 tx_type: TxType::Legacy,
673 cumulative_gas_used: 46913,
674 logs: vec![Log::<LogData>::default()],
675 success: true,
676 }]];
677
678 let first_block = 123;
680
681 let exec_res = ExecutionOutcome {
684 bundle: Default::default(),
685 receipts,
686 requests: vec![],
687 first_block,
688 };
689
690 let logs: Vec<&Log> = exec_res.logs(123).unwrap().collect();
692
693 assert_eq!(logs, vec![&Log::<LogData>::default()]);
695 }
696
697 #[test]
698 fn test_receipts_by_block() {
699 let receipts = vec![vec![Some(reth_ethereum_primitives::Receipt {
701 tx_type: TxType::Legacy,
702 cumulative_gas_used: 46913,
703 logs: vec![Log::<LogData>::default()],
704 success: true,
705 })]];
706
707 let first_block = 123;
709
710 let exec_res = ExecutionOutcome {
713 bundle: Default::default(), receipts, requests: vec![], first_block, };
718
719 let receipts_by_block: Vec<_> = exec_res.receipts_by_block(123).iter().collect();
721
722 assert_eq!(
724 receipts_by_block,
725 vec![&Some(reth_ethereum_primitives::Receipt {
726 tx_type: TxType::Legacy,
727 cumulative_gas_used: 46913,
728 logs: vec![Log::<LogData>::default()],
729 success: true,
730 })]
731 );
732 }
733
734 #[test]
735 fn test_receipts_len() {
736 let receipts = vec![vec![Some(reth_ethereum_primitives::Receipt {
738 tx_type: TxType::Legacy,
739 cumulative_gas_used: 46913,
740 logs: vec![Log::<LogData>::default()],
741 success: true,
742 })]];
743
744 let receipts_empty = vec![];
746
747 let first_block = 123;
749
750 let exec_res = ExecutionOutcome {
753 bundle: Default::default(), receipts, requests: vec![], first_block, };
758
759 assert_eq!(exec_res.len(), 1);
761
762 assert!(!exec_res.is_empty());
764
765 let exec_res_empty_receipts: ExecutionOutcome = ExecutionOutcome {
767 bundle: Default::default(), receipts: receipts_empty, requests: vec![], first_block, };
772
773 assert_eq!(exec_res_empty_receipts.len(), 0);
775
776 assert!(exec_res_empty_receipts.is_empty());
778 }
779
780 #[test]
781 fn test_revert_to() {
782 let receipt = reth_ethereum_primitives::Receipt {
784 tx_type: TxType::Legacy,
785 cumulative_gas_used: 46913,
786 logs: vec![],
787 success: true,
788 };
789
790 let receipts = vec![vec![Some(receipt.clone())], vec![Some(receipt.clone())]];
792
793 let first_block = 123;
795
796 let request = bytes!("deadbeef");
798
799 let requests =
801 vec![Requests::new(vec![request.clone()]), Requests::new(vec![request.clone()])];
802
803 let mut exec_res =
806 ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block };
807
808 assert!(exec_res.revert_to(123));
810
811 assert_eq!(exec_res.receipts, vec![vec![Some(receipt)]]);
813
814 assert_eq!(exec_res.requests, vec![Requests::new(vec![request])]);
816
817 assert!(!exec_res.revert_to(133));
820
821 assert!(!exec_res.revert_to(10));
824 }
825
826 #[test]
827 fn test_extend_execution_outcome() {
828 let receipt = reth_ethereum_primitives::Receipt {
830 tx_type: TxType::Legacy,
831 cumulative_gas_used: 46913,
832 logs: vec![],
833 success: true,
834 };
835
836 let receipts = vec![vec![Some(receipt.clone())]];
838
839 let request = bytes!("deadbeef");
841
842 let requests = vec![Requests::new(vec![request.clone()])];
844
845 let first_block = 123;
847
848 let mut exec_res =
850 ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block };
851
852 exec_res.extend(exec_res.clone());
854
855 assert_eq!(
857 exec_res,
858 ExecutionOutcome {
859 bundle: Default::default(),
860 receipts: vec![vec![Some(receipt.clone())], vec![Some(receipt)]],
861 requests: vec![Requests::new(vec![request.clone()]), Requests::new(vec![request])],
862 first_block: 123,
863 }
864 );
865 }
866
867 #[test]
868 fn test_split_at_execution_outcome() {
869 let receipt = reth_ethereum_primitives::Receipt {
871 tx_type: TxType::Legacy,
872 cumulative_gas_used: 46913,
873 logs: vec![],
874 success: true,
875 };
876
877 let receipts = vec![
879 vec![Some(receipt.clone())],
880 vec![Some(receipt.clone())],
881 vec![Some(receipt.clone())],
882 ];
883
884 let first_block = 123;
886
887 let request = bytes!("deadbeef");
889
890 let requests = vec![
892 Requests::new(vec![request.clone()]),
893 Requests::new(vec![request.clone()]),
894 Requests::new(vec![request.clone()]),
895 ];
896
897 let exec_res =
900 ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block };
901
902 let result = exec_res.clone().split_at(124);
904
905 let lower_execution_outcome = ExecutionOutcome {
907 bundle: Default::default(),
908 receipts: vec![vec![Some(receipt.clone())]],
909 requests: vec![Requests::new(vec![request.clone()])],
910 first_block,
911 };
912
913 let higher_execution_outcome = ExecutionOutcome {
915 bundle: Default::default(),
916 receipts: vec![vec![Some(receipt.clone())], vec![Some(receipt)]],
917 requests: vec![Requests::new(vec![request.clone()]), Requests::new(vec![request])],
918 first_block: 124,
919 };
920
921 assert_eq!(result.0, Some(lower_execution_outcome));
923 assert_eq!(result.1, higher_execution_outcome);
924
925 assert_eq!(exec_res.clone().split_at(123), (None, exec_res));
927 }
928
929 #[test]
930 fn test_changed_accounts() {
931 let address1 = Address::random();
933 let address2 = Address::random();
934 let address3 = Address::random();
935
936 let account_info1 =
938 AccountInfo { nonce: 1, balance: U256::from(100), code_hash: B256::ZERO, code: None };
939 let account_info2 =
940 AccountInfo { nonce: 2, balance: U256::from(200), code_hash: B256::ZERO, code: None };
941
942 let mut bundle_state = BundleState::default();
944 bundle_state.state.insert(
945 address1,
946 BundleAccount {
947 info: Some(account_info1),
948 storage: Default::default(),
949 original_info: Default::default(),
950 status: Default::default(),
951 },
952 );
953 bundle_state.state.insert(
954 address2,
955 BundleAccount {
956 info: Some(account_info2),
957 storage: Default::default(),
958 original_info: Default::default(),
959 status: Default::default(),
960 },
961 );
962
963 bundle_state.state.insert(
965 address3,
966 BundleAccount {
967 info: None,
968 storage: Default::default(),
969 original_info: Default::default(),
970 status: Default::default(),
971 },
972 );
973
974 let execution_outcome: ExecutionOutcome = ExecutionOutcome {
975 bundle: bundle_state,
976 receipts: Default::default(),
977 first_block: 0,
978 requests: vec![],
979 };
980
981 let changed_accounts: Vec<ChangedAccount> = execution_outcome.changed_accounts().collect();
983
984 assert_eq!(changed_accounts.len(), 2);
986
987 assert!(changed_accounts.contains(&ChangedAccount {
988 address: address1,
989 nonce: 1,
990 balance: U256::from(100)
991 }));
992
993 assert!(changed_accounts.contains(&ChangedAccount {
994 address: address2,
995 nonce: 2,
996 balance: U256::from(200)
997 }));
998 }
999}