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 { bundle, first_block, receipts: Vec::new(), requests: Vec::new() };
148 for result in results {
149 value.receipts.push(result.receipts);
150 value.requests.push(result.requests);
151 }
152 value
153 }
154
155 pub const fn state(&self) -> &BundleState {
157 &self.bundle
158 }
159
160 pub const fn state_mut(&mut self) -> &mut BundleState {
162 &mut self.bundle
163 }
164
165 pub const fn set_first_block(&mut self, first_block: BlockNumber) {
167 self.first_block = first_block;
168 }
169
170 pub fn accounts_iter(&self) -> impl Iterator<Item = (Address, Option<&AccountInfo>)> {
172 self.bundle.state().iter().map(|(a, acc)| (*a, acc.info.as_ref()))
173 }
174
175 pub fn bundle_accounts_iter(&self) -> impl Iterator<Item = (Address, &BundleAccount)> {
177 self.bundle.state().iter().map(|(a, acc)| (*a, acc))
178 }
179
180 pub fn account(&self, address: &Address) -> Option<Option<Account>> {
182 self.bundle.account(address).map(|a| a.info.as_ref().map(Into::into))
183 }
184
185 pub fn storage(&self, address: &Address, storage_key: U256) -> Option<U256> {
189 self.bundle.account(address).and_then(|a| a.storage_slot(storage_key))
190 }
191
192 pub fn bytecode(&self, code_hash: &B256) -> Option<Bytecode> {
194 self.bundle.bytecode(code_hash).map(Bytecode)
195 }
196
197 pub fn hash_state_slow<KH: KeyHasher>(&self) -> HashedPostState {
200 HashedPostState::from_bundle_state::<KH>(&self.bundle.state)
201 }
202
203 pub fn block_number_to_index(&self, block_number: BlockNumber) -> Option<usize> {
205 if self.first_block > block_number {
206 return None
207 }
208 let index = block_number - self.first_block;
209 if index >= self.receipts.len() as u64 {
210 return None
211 }
212 Some(index as usize)
213 }
214
215 pub fn generic_receipts_root_slow(
219 &self,
220 block_number: BlockNumber,
221 f: impl FnOnce(&[T]) -> B256,
222 ) -> Option<B256> {
223 Some(f(self.receipts.get(self.block_number_to_index(block_number)?)?))
224 }
225
226 pub const fn receipts(&self) -> &Vec<Vec<T>> {
228 &self.receipts
229 }
230
231 pub const fn receipts_mut(&mut self) -> &mut Vec<Vec<T>> {
233 &mut self.receipts
234 }
235
236 pub fn receipts_by_block(&self, block_number: BlockNumber) -> &[T] {
238 let Some(index) = self.block_number_to_index(block_number) else { return &[] };
239 &self.receipts[index]
240 }
241
242 pub fn is_empty(&self) -> bool {
244 self.len() == 0
245 }
246
247 pub fn len(&self) -> usize {
249 self.receipts.len()
250 }
251
252 pub const fn first_block(&self) -> BlockNumber {
254 self.first_block
255 }
256
257 pub fn last_block(&self) -> BlockNumber {
259 (self.first_block + self.len() as u64).saturating_sub(1)
260 }
261
262 pub fn revert_to(&mut self, block_number: BlockNumber) -> bool {
270 let Some(index) = self.block_number_to_index(block_number) else { return false };
271
272 let new_len = index + 1;
274 let rm_trx: usize = self.len() - new_len;
275
276 self.receipts.truncate(new_len);
278 self.requests.truncate(new_len);
280 self.bundle.revert(rm_trx);
282
283 true
284 }
285
286 pub fn split_at(self, at: BlockNumber) -> (Option<Self>, Self)
295 where
296 T: Clone,
297 {
298 if at == self.first_block {
299 return (None, self)
300 }
301
302 let (mut lower_state, mut higher_state) = (self.clone(), self);
303
304 lower_state.revert_to(at.checked_sub(1).unwrap());
306
307 let at_idx = higher_state.block_number_to_index(at).unwrap();
309 higher_state.receipts = higher_state.receipts.split_off(at_idx);
310 if at_idx < higher_state.requests.len() {
313 higher_state.requests = higher_state.requests.split_off(at_idx);
314 }
315 higher_state.bundle.take_n_reverts(at_idx);
316 higher_state.first_block = at;
317
318 (Some(lower_state), higher_state)
319 }
320
321 pub fn extend(&mut self, other: Self) {
327 self.bundle.extend(other.bundle);
328 self.receipts.extend(other.receipts);
329 self.requests.extend(other.requests);
330 }
331
332 pub fn prepend_state(&mut self, mut other: BundleState) {
337 let other_len = other.reverts.len();
338 let this_bundle = core::mem::take(&mut self.bundle);
340 other.extend(this_bundle);
342 other.take_n_reverts(other_len);
344 core::mem::swap(&mut self.bundle, &mut other)
346 }
347
348 pub fn with_receipts(mut self, receipts: Vec<Vec<T>>) -> Self {
350 self.receipts = receipts;
351 self
352 }
353
354 pub fn with_requests(mut self, requests: Vec<Requests>) -> Self {
356 self.requests = requests;
357 self
358 }
359
360 pub fn changed_accounts(&self) -> impl Iterator<Item = ChangedAccount> + '_ {
366 self.accounts_iter().filter_map(|(addr, acc)| acc.map(|acc| (addr, acc))).map(
367 |(address, acc)| ChangedAccount { address, nonce: acc.nonce, balance: acc.balance },
368 )
369 }
370}
371
372impl<T: Receipt<Log = Log>> ExecutionOutcome<T> {
373 pub fn logs(&self, block_number: BlockNumber) -> Option<impl Iterator<Item = &Log>> {
375 let index = self.block_number_to_index(block_number)?;
376 Some(self.receipts[index].iter().flat_map(|r| r.logs()))
377 }
378
379 pub fn block_logs_bloom(&self, block_number: BlockNumber) -> Option<Bloom> {
381 Some(logs_bloom(self.logs(block_number)?))
382 }
383}
384
385impl ExecutionOutcome {
386 pub fn ethereum_receipts_root(&self, block_number: BlockNumber) -> Option<B256> {
391 self.generic_receipts_root_slow(
392 block_number,
393 reth_ethereum_primitives::Receipt::calculate_receipt_root_no_memo,
394 )
395 }
396}
397
398impl<T> From<(BlockExecutionOutput<T>, BlockNumber)> for ExecutionOutcome<T> {
399 fn from((output, block_number): (BlockExecutionOutput<T>, BlockNumber)) -> Self {
400 Self::single(block_number, output)
401 }
402}
403
404#[cfg(feature = "serde-bincode-compat")]
405pub(super) mod serde_bincode_compat {
406 use alloc::{borrow::Cow, vec::Vec};
407 use alloy_eips::eip7685::Requests;
408 use alloy_primitives::BlockNumber;
409 use reth_primitives_traits::serde_bincode_compat::SerdeBincodeCompat;
410 use revm::database::BundleState;
411 use serde::{Deserialize, Deserializer, Serialize, Serializer};
412 use serde_with::{DeserializeAs, SerializeAs};
413
414 #[derive(Debug, Serialize, Deserialize)]
432 pub struct ExecutionOutcome<'a, T>
433 where
434 T: SerdeBincodeCompat + core::fmt::Debug,
435 {
436 bundle: Cow<'a, BundleState>,
437 receipts: Vec<Vec<T::BincodeRepr<'a>>>,
438 first_block: BlockNumber,
439 #[expect(clippy::owned_cow)]
440 requests: Cow<'a, Vec<Requests>>,
441 }
442
443 impl<'a, T> From<&'a super::ExecutionOutcome<T>> for ExecutionOutcome<'a, T>
444 where
445 T: SerdeBincodeCompat + core::fmt::Debug,
446 {
447 fn from(value: &'a super::ExecutionOutcome<T>) -> Self {
448 ExecutionOutcome {
449 bundle: Cow::Borrowed(&value.bundle),
450 receipts: value
451 .receipts
452 .iter()
453 .map(|vec| vec.iter().map(|receipt| T::as_repr(receipt)).collect())
454 .collect(),
455 first_block: value.first_block,
456 requests: Cow::Borrowed(&value.requests),
457 }
458 }
459 }
460
461 impl<'a, T> From<ExecutionOutcome<'a, T>> for super::ExecutionOutcome<T>
462 where
463 T: SerdeBincodeCompat + core::fmt::Debug,
464 {
465 fn from(value: ExecutionOutcome<'a, T>) -> Self {
466 Self {
467 bundle: value.bundle.into_owned(),
468 receipts: value
469 .receipts
470 .into_iter()
471 .map(|vec| vec.into_iter().map(|receipt| T::from_repr(receipt)).collect())
472 .collect(),
473 first_block: value.first_block,
474 requests: value.requests.into_owned(),
475 }
476 }
477 }
478
479 impl<T> SerializeAs<super::ExecutionOutcome<T>> for ExecutionOutcome<'_, T>
480 where
481 T: SerdeBincodeCompat + core::fmt::Debug,
482 {
483 fn serialize_as<S>(
484 source: &super::ExecutionOutcome<T>,
485 serializer: S,
486 ) -> Result<S::Ok, S::Error>
487 where
488 S: Serializer,
489 {
490 ExecutionOutcome::from(source).serialize(serializer)
491 }
492 }
493
494 impl<'de, T> DeserializeAs<'de, super::ExecutionOutcome<T>> for ExecutionOutcome<'de, T>
495 where
496 T: SerdeBincodeCompat + core::fmt::Debug,
497 {
498 fn deserialize_as<D>(deserializer: D) -> Result<super::ExecutionOutcome<T>, D::Error>
499 where
500 D: Deserializer<'de>,
501 {
502 ExecutionOutcome::deserialize(deserializer).map(Into::into)
503 }
504 }
505
506 impl<T: SerdeBincodeCompat + core::fmt::Debug> SerdeBincodeCompat for super::ExecutionOutcome<T> {
507 type BincodeRepr<'a> = ExecutionOutcome<'a, T>;
508
509 fn as_repr(&self) -> Self::BincodeRepr<'_> {
510 self.into()
511 }
512
513 fn from_repr(repr: Self::BincodeRepr<'_>) -> Self {
514 repr.into()
515 }
516 }
517
518 #[cfg(test)]
519 mod tests {
520 use super::super::{serde_bincode_compat, ExecutionOutcome};
521 use rand::Rng;
522 use reth_ethereum_primitives::Receipt;
523 use reth_primitives_traits::serde_bincode_compat::SerdeBincodeCompat;
524 use serde::{Deserialize, Serialize};
525 use serde_with::serde_as;
526
527 #[test]
528 fn test_chain_bincode_roundtrip() {
529 #[serde_as]
530 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
531 struct Data<T: SerdeBincodeCompat + core::fmt::Debug> {
532 #[serde_as(as = "serde_bincode_compat::ExecutionOutcome<'_, T>")]
533 data: ExecutionOutcome<T>,
534 }
535
536 let mut bytes = [0u8; 1024];
537 rand::rng().fill(bytes.as_mut_slice());
538 let data = Data {
539 data: ExecutionOutcome {
540 bundle: Default::default(),
541 receipts: vec![],
542 first_block: 0,
543 requests: vec![],
544 },
545 };
546
547 let encoded = bincode::serialize(&data).unwrap();
548 let decoded = bincode::deserialize::<Data<Receipt>>(&encoded).unwrap();
549 assert_eq!(decoded, data);
550 }
551 }
552}
553
554#[cfg(test)]
555mod tests {
556 use super::*;
557 use alloy_consensus::TxType;
558 use alloy_primitives::{bytes, Address, LogData, B256};
559
560 #[test]
561 fn test_initialisation() {
562 let bundle = BundleState::new(
564 vec![(Address::new([2; 20]), None, Some(AccountInfo::default()), HashMap::default())],
565 vec![vec![(Address::new([2; 20]), None, vec![])]],
566 vec![],
567 );
568
569 let receipts = vec![vec![Some(reth_ethereum_primitives::Receipt {
571 tx_type: TxType::Legacy,
572 cumulative_gas_used: 46913,
573 logs: vec![],
574 success: true,
575 })]];
576
577 let requests = vec![Requests::new(vec![bytes!("dead"), bytes!("beef"), bytes!("beebee")])];
579
580 let first_block = 123;
582
583 let exec_res = ExecutionOutcome {
586 bundle: bundle.clone(),
587 receipts: receipts.clone(),
588 requests: requests.clone(),
589 first_block,
590 };
591
592 assert_eq!(
594 ExecutionOutcome::new(bundle, receipts.clone(), first_block, requests.clone()),
595 exec_res
596 );
597
598 let mut state_init: BundleStateInit = HashMap::default();
600 state_init
601 .insert(Address::new([2; 20]), (None, Some(Account::default()), HashMap::default()));
602
603 let mut revert_inner: HashMap<Address, AccountRevertInit> = HashMap::default();
605 revert_inner.insert(Address::new([2; 20]), (None, vec![]));
606
607 let mut revert_init: RevertsInit = HashMap::default();
609 revert_init.insert(123, revert_inner);
610
611 assert_eq!(
614 ExecutionOutcome::new_init(
615 state_init,
616 revert_init,
617 vec![],
618 receipts,
619 first_block,
620 requests,
621 ),
622 exec_res
623 );
624 }
625
626 #[test]
627 fn test_block_number_to_index() {
628 let receipts = vec![vec![Some(reth_ethereum_primitives::Receipt {
630 tx_type: TxType::Legacy,
631 cumulative_gas_used: 46913,
632 logs: vec![],
633 success: true,
634 })]];
635
636 let first_block = 123;
638
639 let exec_res = ExecutionOutcome {
642 bundle: Default::default(),
643 receipts,
644 requests: vec![],
645 first_block,
646 };
647
648 assert_eq!(exec_res.block_number_to_index(12), None);
650
651 assert_eq!(exec_res.block_number_to_index(133), None);
653
654 assert_eq!(exec_res.block_number_to_index(123), Some(0));
656 }
657
658 #[test]
659 fn test_get_logs() {
660 let receipts = vec![vec![reth_ethereum_primitives::Receipt {
662 tx_type: TxType::Legacy,
663 cumulative_gas_used: 46913,
664 logs: vec![Log::<LogData>::default()],
665 success: true,
666 }]];
667
668 let first_block = 123;
670
671 let exec_res = ExecutionOutcome {
674 bundle: Default::default(),
675 receipts,
676 requests: vec![],
677 first_block,
678 };
679
680 let logs: Vec<&Log> = exec_res.logs(123).unwrap().collect();
682
683 assert_eq!(logs, vec![&Log::<LogData>::default()]);
685 }
686
687 #[test]
688 fn test_receipts_by_block() {
689 let receipts = vec![vec![Some(reth_ethereum_primitives::Receipt {
691 tx_type: TxType::Legacy,
692 cumulative_gas_used: 46913,
693 logs: vec![Log::<LogData>::default()],
694 success: true,
695 })]];
696
697 let first_block = 123;
699
700 let exec_res = ExecutionOutcome {
703 bundle: Default::default(), receipts, requests: vec![], first_block, };
708
709 let receipts_by_block: Vec<_> = exec_res.receipts_by_block(123).iter().collect();
711
712 assert_eq!(
714 receipts_by_block,
715 vec![&Some(reth_ethereum_primitives::Receipt {
716 tx_type: TxType::Legacy,
717 cumulative_gas_used: 46913,
718 logs: vec![Log::<LogData>::default()],
719 success: true,
720 })]
721 );
722 }
723
724 #[test]
725 fn test_receipts_len() {
726 let receipts = vec![vec![Some(reth_ethereum_primitives::Receipt {
728 tx_type: TxType::Legacy,
729 cumulative_gas_used: 46913,
730 logs: vec![Log::<LogData>::default()],
731 success: true,
732 })]];
733
734 let receipts_empty = vec![];
736
737 let first_block = 123;
739
740 let exec_res = ExecutionOutcome {
743 bundle: Default::default(), receipts, requests: vec![], first_block, };
748
749 assert_eq!(exec_res.len(), 1);
751
752 assert!(!exec_res.is_empty());
754
755 let exec_res_empty_receipts: ExecutionOutcome = ExecutionOutcome {
757 bundle: Default::default(), receipts: receipts_empty, requests: vec![], first_block, };
762
763 assert_eq!(exec_res_empty_receipts.len(), 0);
765
766 assert!(exec_res_empty_receipts.is_empty());
768 }
769
770 #[test]
771 fn test_revert_to() {
772 let receipt = reth_ethereum_primitives::Receipt {
774 tx_type: TxType::Legacy,
775 cumulative_gas_used: 46913,
776 logs: vec![],
777 success: true,
778 };
779
780 let receipts = vec![vec![Some(receipt.clone())], vec![Some(receipt.clone())]];
782
783 let first_block = 123;
785
786 let request = bytes!("deadbeef");
788
789 let requests =
791 vec![Requests::new(vec![request.clone()]), Requests::new(vec![request.clone()])];
792
793 let mut exec_res =
796 ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block };
797
798 assert!(exec_res.revert_to(123));
800
801 assert_eq!(exec_res.receipts, vec![vec![Some(receipt)]]);
803
804 assert_eq!(exec_res.requests, vec![Requests::new(vec![request])]);
806
807 assert!(!exec_res.revert_to(133));
810
811 assert!(!exec_res.revert_to(10));
814 }
815
816 #[test]
817 fn test_extend_execution_outcome() {
818 let receipt = reth_ethereum_primitives::Receipt {
820 tx_type: TxType::Legacy,
821 cumulative_gas_used: 46913,
822 logs: vec![],
823 success: true,
824 };
825
826 let receipts = vec![vec![Some(receipt.clone())]];
828
829 let request = bytes!("deadbeef");
831
832 let requests = vec![Requests::new(vec![request.clone()])];
834
835 let first_block = 123;
837
838 let mut exec_res =
840 ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block };
841
842 exec_res.extend(exec_res.clone());
844
845 assert_eq!(
847 exec_res,
848 ExecutionOutcome {
849 bundle: Default::default(),
850 receipts: vec![vec![Some(receipt.clone())], vec![Some(receipt)]],
851 requests: vec![Requests::new(vec![request.clone()]), Requests::new(vec![request])],
852 first_block: 123,
853 }
854 );
855 }
856
857 #[test]
858 fn test_split_at_execution_outcome() {
859 let receipt = reth_ethereum_primitives::Receipt {
861 tx_type: TxType::Legacy,
862 cumulative_gas_used: 46913,
863 logs: vec![],
864 success: true,
865 };
866
867 let receipts = vec![
869 vec![Some(receipt.clone())],
870 vec![Some(receipt.clone())],
871 vec![Some(receipt.clone())],
872 ];
873
874 let first_block = 123;
876
877 let request = bytes!("deadbeef");
879
880 let requests = vec![
882 Requests::new(vec![request.clone()]),
883 Requests::new(vec![request.clone()]),
884 Requests::new(vec![request.clone()]),
885 ];
886
887 let exec_res =
890 ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block };
891
892 let result = exec_res.clone().split_at(124);
894
895 let lower_execution_outcome = ExecutionOutcome {
897 bundle: Default::default(),
898 receipts: vec![vec![Some(receipt.clone())]],
899 requests: vec![Requests::new(vec![request.clone()])],
900 first_block,
901 };
902
903 let higher_execution_outcome = ExecutionOutcome {
905 bundle: Default::default(),
906 receipts: vec![vec![Some(receipt.clone())], vec![Some(receipt)]],
907 requests: vec![Requests::new(vec![request.clone()]), Requests::new(vec![request])],
908 first_block: 124,
909 };
910
911 assert_eq!(result.0, Some(lower_execution_outcome));
913 assert_eq!(result.1, higher_execution_outcome);
914
915 assert_eq!(exec_res.clone().split_at(123), (None, exec_res));
917 }
918
919 #[test]
920 fn test_changed_accounts() {
921 let address1 = Address::random();
923 let address2 = Address::random();
924 let address3 = Address::random();
925
926 let account_info1 =
928 AccountInfo { nonce: 1, balance: U256::from(100), code_hash: B256::ZERO, code: None };
929 let account_info2 =
930 AccountInfo { nonce: 2, balance: U256::from(200), code_hash: B256::ZERO, code: None };
931
932 let mut bundle_state = BundleState::default();
934 bundle_state.state.insert(
935 address1,
936 BundleAccount {
937 info: Some(account_info1),
938 storage: Default::default(),
939 original_info: Default::default(),
940 status: Default::default(),
941 },
942 );
943 bundle_state.state.insert(
944 address2,
945 BundleAccount {
946 info: Some(account_info2),
947 storage: Default::default(),
948 original_info: Default::default(),
949 status: Default::default(),
950 },
951 );
952
953 bundle_state.state.insert(
955 address3,
956 BundleAccount {
957 info: None,
958 storage: Default::default(),
959 original_info: Default::default(),
960 status: Default::default(),
961 },
962 );
963
964 let execution_outcome: ExecutionOutcome = ExecutionOutcome {
965 bundle: bundle_state,
966 receipts: Default::default(),
967 first_block: 0,
968 requests: vec![],
969 };
970
971 let changed_accounts: Vec<ChangedAccount> = execution_outcome.changed_accounts().collect();
973
974 assert_eq!(changed_accounts.len(), 2);
976
977 assert!(changed_accounts.contains(&ChangedAccount {
978 address: address1,
979 nonce: 1,
980 balance: U256::from(100)
981 }));
982
983 assert!(changed_accounts.contains(&ChangedAccount {
984 address: address2,
985 nonce: 2,
986 balance: U256::from(200)
987 }));
988 }
989}