1use crate::{BlockExecutionOutput, BlockExecutionResult};
2use alloc::{vec, vec::Vec};
3use alloy_eips::eip7685::Requests;
4use alloy_primitives::{
5 logs_bloom,
6 map::{AddressMap, B256Map, HashMap},
7 Address, BlockNumber, Bloom, Log, B256, U256,
8};
9use reth_primitives_traits::{Account, Bytecode, Receipt, StorageEntry};
10use reth_trie_common::{HashedPostState, KeyHasher};
11use revm::{
12 database::{states::BundleState, BundleAccount},
13 state::AccountInfo,
14};
15
16pub type BundleStateInit = AddressMap<(Option<Account>, Option<Account>, B256Map<(U256, U256)>)>;
18
19pub type AccountRevertInit = (Option<Option<Account>>, Vec<StorageEntry>);
21
22pub type RevertsInit = HashMap<BlockNumber, AddressMap<AccountRevertInit>>;
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
27pub struct ChangedAccount {
28 pub address: Address,
30 pub nonce: u64,
32 pub balance: U256,
34}
35
36impl ChangedAccount {
37 pub const fn empty(address: Address) -> Self {
39 Self { address, nonce: 0, balance: U256::ZERO }
40 }
41}
42
43#[derive(Debug, Clone, PartialEq, Eq)]
48#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
49pub struct ExecutionOutcome<T = reth_ethereum_primitives::Receipt> {
50 pub bundle: BundleState,
52 pub receipts: Vec<Vec<T>>,
56 pub first_block: BlockNumber,
58 pub requests: Vec<Requests>,
65}
66
67impl<T> Default for ExecutionOutcome<T> {
68 fn default() -> Self {
69 Self {
70 bundle: Default::default(),
71 receipts: Default::default(),
72 first_block: Default::default(),
73 requests: Default::default(),
74 }
75 }
76}
77
78impl<T> ExecutionOutcome<T> {
79 pub const fn new(
84 bundle: BundleState,
85 receipts: Vec<Vec<T>>,
86 first_block: BlockNumber,
87 requests: Vec<Requests>,
88 ) -> Self {
89 Self { bundle, receipts, first_block, requests }
90 }
91
92 pub fn new_init(
97 state_init: BundleStateInit,
98 revert_init: RevertsInit,
99 contracts_init: impl IntoIterator<Item = (B256, Bytecode)>,
100 receipts: Vec<Vec<T>>,
101 first_block: BlockNumber,
102 requests: Vec<Requests>,
103 ) -> Self {
104 let mut reverts = revert_init.into_iter().collect::<Vec<_>>();
106 reverts.sort_unstable_by_key(|a| a.0);
107
108 let bundle = BundleState::new(
110 state_init.into_iter().map(|(address, (original, present, storage))| {
111 (
112 address,
113 original.map(Into::into),
114 present.map(Into::into),
115 storage.into_iter().map(|(k, s)| (k.into(), s)).collect(),
116 )
117 }),
118 reverts.into_iter().map(|(_, reverts)| {
119 reverts.into_iter().map(|(address, (original, storage))| {
121 (
122 address,
123 original.map(|i| i.map(Into::into)),
124 storage.into_iter().map(|entry| (entry.key.into(), entry.value)),
125 )
126 })
127 }),
128 contracts_init.into_iter().map(|(code_hash, bytecode)| (code_hash, bytecode.0)),
129 );
130
131 Self { bundle, receipts, first_block, requests }
132 }
133
134 pub fn single(block_number: u64, output: BlockExecutionOutput<T>) -> Self {
136 Self {
137 bundle: output.state,
138 receipts: vec![output.result.receipts],
139 first_block: block_number,
140 requests: vec![output.result.requests],
141 }
142 }
143
144 pub fn from_blocks(
146 first_block: u64,
147 bundle: BundleState,
148 results: Vec<BlockExecutionResult<T>>,
149 ) -> Self {
150 let mut value = Self {
151 bundle,
152 first_block,
153 receipts: Vec::with_capacity(results.len()),
154 requests: Vec::with_capacity(results.len()),
155 };
156 for result in results {
157 value.receipts.push(result.receipts);
158 value.requests.push(result.requests);
159 }
160 value
161 }
162
163 pub const fn state(&self) -> &BundleState {
165 &self.bundle
166 }
167
168 pub const fn state_mut(&mut self) -> &mut BundleState {
170 &mut self.bundle
171 }
172
173 pub const fn set_first_block(&mut self, first_block: BlockNumber) {
175 self.first_block = first_block;
176 }
177
178 pub fn accounts_iter(&self) -> impl Iterator<Item = (Address, Option<&AccountInfo>)> {
180 self.bundle.state().iter().map(|(a, acc)| (*a, acc.info.as_ref()))
181 }
182
183 pub fn bundle_accounts_iter(&self) -> impl Iterator<Item = (Address, &BundleAccount)> {
185 self.bundle.state().iter().map(|(a, acc)| (*a, acc))
186 }
187
188 pub fn account(&self, address: &Address) -> Option<Option<Account>> {
190 self.bundle.account(address).map(|a| a.info.as_ref().map(Into::into))
191 }
192
193 pub fn account_state(&self, address: &Address) -> Option<&BundleAccount> {
195 self.bundle.account(address)
196 }
197
198 pub fn storage(&self, address: &Address, storage_key: U256) -> Option<U256> {
202 self.bundle.account(address).and_then(|a| a.storage_slot(storage_key))
203 }
204
205 pub fn bytecode(&self, code_hash: &B256) -> Option<Bytecode> {
207 self.bundle.bytecode(code_hash).map(Bytecode)
208 }
209
210 pub fn hash_state_slow<KH: KeyHasher>(&self) -> HashedPostState {
213 HashedPostState::from_bundle_state::<KH>(&self.bundle.state)
214 }
215
216 pub const fn block_number_to_index(&self, block_number: BlockNumber) -> Option<usize> {
218 if self.first_block > block_number {
219 return None
220 }
221 let index = block_number - self.first_block;
222 if index >= self.receipts.len() as u64 {
223 return None
224 }
225 Some(index as usize)
226 }
227
228 pub fn generic_receipts_root_slow(
232 &self,
233 block_number: BlockNumber,
234 f: impl FnOnce(&[T]) -> B256,
235 ) -> Option<B256> {
236 Some(f(self.receipts.get(self.block_number_to_index(block_number)?)?))
237 }
238
239 pub const fn receipts(&self) -> &Vec<Vec<T>> {
241 &self.receipts
242 }
243
244 pub const fn receipts_mut(&mut self) -> &mut Vec<Vec<T>> {
246 &mut self.receipts
247 }
248
249 pub fn receipts_by_block(&self, block_number: BlockNumber) -> &[T] {
251 let Some(index) = self.block_number_to_index(block_number) else { return &[] };
252 &self.receipts[index]
253 }
254
255 pub fn receipts_iter(&self) -> impl Iterator<Item = &[T]> + '_ {
260 self.receipts.iter().map(|v| v.as_slice())
261 }
262
263 pub const fn is_empty(&self) -> bool {
265 self.len() == 0
266 }
267
268 pub const fn len(&self) -> usize {
270 self.receipts.len()
271 }
272
273 pub const fn first_block(&self) -> BlockNumber {
275 self.first_block
276 }
277
278 pub const fn last_block(&self) -> BlockNumber {
280 (self.first_block + self.len() as u64).saturating_sub(1)
281 }
282
283 pub fn revert_to(&mut self, block_number: BlockNumber) -> bool {
291 let Some(index) = self.block_number_to_index(block_number) else { return false };
292
293 let new_len = index + 1;
295 let rm_trx: usize = self.len() - new_len;
296
297 self.receipts.truncate(new_len);
299 self.requests.truncate(new_len);
301 self.bundle.revert(rm_trx);
303
304 true
305 }
306
307 pub fn split_at(self, at: BlockNumber) -> (Option<Self>, Self)
316 where
317 T: Clone,
318 {
319 if at == self.first_block {
320 return (None, self)
321 }
322
323 let (mut lower_state, mut higher_state) = (self.clone(), self);
324
325 lower_state.revert_to(at.checked_sub(1).unwrap());
327
328 let at_idx = higher_state.block_number_to_index(at).unwrap();
330 higher_state.receipts = higher_state.receipts.split_off(at_idx);
331 if at_idx < higher_state.requests.len() {
334 higher_state.requests = higher_state.requests.split_off(at_idx);
335 }
336 higher_state.bundle.take_n_reverts(at_idx);
337 higher_state.first_block = at;
338
339 (Some(lower_state), higher_state)
340 }
341
342 pub fn extend(&mut self, other: Self) {
348 self.bundle.extend(other.bundle);
349 self.receipts.extend(other.receipts);
350 self.requests.extend(other.requests);
351 }
352
353 pub fn prepend_state(&mut self, mut other: BundleState) {
358 let other_len = other.reverts.len();
359 let this_bundle = core::mem::take(&mut self.bundle);
361 other.extend(this_bundle);
363 other.take_n_reverts(other_len);
365 core::mem::swap(&mut self.bundle, &mut other)
367 }
368
369 pub fn with_receipts(mut self, receipts: Vec<Vec<T>>) -> Self {
371 self.receipts = receipts;
372 self
373 }
374
375 pub fn with_requests(mut self, requests: Vec<Requests>) -> Self {
377 self.requests = requests;
378 self
379 }
380
381 pub fn changed_accounts(&self) -> impl Iterator<Item = ChangedAccount> + '_ {
387 self.accounts_iter().filter_map(|(addr, acc)| acc.map(|acc| (addr, acc))).map(
388 |(address, acc)| ChangedAccount { address, nonce: acc.nonce, balance: acc.balance },
389 )
390 }
391}
392
393impl<T: Receipt<Log = Log>> ExecutionOutcome<T> {
394 pub fn logs(&self, block_number: BlockNumber) -> Option<impl Iterator<Item = &Log>> {
396 let index = self.block_number_to_index(block_number)?;
397 Some(self.receipts[index].iter().flat_map(|r| r.logs()))
398 }
399
400 pub fn block_logs_bloom(&self, block_number: BlockNumber) -> Option<Bloom> {
402 Some(logs_bloom(self.logs(block_number)?))
403 }
404}
405
406impl ExecutionOutcome {
407 pub fn ethereum_receipts_root(&self, block_number: BlockNumber) -> Option<B256> {
412 self.generic_receipts_root_slow(
413 block_number,
414 reth_ethereum_primitives::Receipt::calculate_receipt_root_no_memo,
415 )
416 }
417}
418
419impl<T> From<(BlockExecutionOutput<T>, BlockNumber)> for ExecutionOutcome<T> {
420 fn from((output, block_number): (BlockExecutionOutput<T>, BlockNumber)) -> Self {
421 Self::single(block_number, output)
422 }
423}
424
425#[cfg(feature = "serde-bincode-compat")]
426pub(super) mod serde_bincode_compat {
427 use alloc::{borrow::Cow, vec::Vec};
428 use alloy_eips::eip7685::Requests;
429 use alloy_primitives::BlockNumber;
430 use reth_primitives_traits::serde_bincode_compat::SerdeBincodeCompat;
431 use revm::database::BundleState;
432 use serde::{Deserialize, Deserializer, Serialize, Serializer};
433 use serde_with::{DeserializeAs, SerializeAs};
434
435 #[derive(Debug, Serialize, Deserialize)]
453 pub struct ExecutionOutcome<'a, T>
454 where
455 T: SerdeBincodeCompat + core::fmt::Debug,
456 {
457 bundle: Cow<'a, BundleState>,
458 receipts: Vec<Vec<T::BincodeRepr<'a>>>,
459 first_block: BlockNumber,
460 #[expect(clippy::owned_cow)]
461 requests: Cow<'a, Vec<Requests>>,
462 }
463
464 impl<'a, T> From<&'a super::ExecutionOutcome<T>> for ExecutionOutcome<'a, T>
465 where
466 T: SerdeBincodeCompat + core::fmt::Debug,
467 {
468 fn from(value: &'a super::ExecutionOutcome<T>) -> Self {
469 ExecutionOutcome {
470 bundle: Cow::Borrowed(&value.bundle),
471 receipts: value
472 .receipts
473 .iter()
474 .map(|vec| vec.iter().map(|receipt| T::as_repr(receipt)).collect())
475 .collect(),
476 first_block: value.first_block,
477 requests: Cow::Borrowed(&value.requests),
478 }
479 }
480 }
481
482 impl<'a, T> From<ExecutionOutcome<'a, T>> for super::ExecutionOutcome<T>
483 where
484 T: SerdeBincodeCompat + core::fmt::Debug,
485 {
486 fn from(value: ExecutionOutcome<'a, T>) -> Self {
487 Self {
488 bundle: value.bundle.into_owned(),
489 receipts: value
490 .receipts
491 .into_iter()
492 .map(|vec| vec.into_iter().map(|receipt| T::from_repr(receipt)).collect())
493 .collect(),
494 first_block: value.first_block,
495 requests: value.requests.into_owned(),
496 }
497 }
498 }
499
500 impl<T> SerializeAs<super::ExecutionOutcome<T>> for ExecutionOutcome<'_, T>
501 where
502 T: SerdeBincodeCompat + core::fmt::Debug,
503 {
504 fn serialize_as<S>(
505 source: &super::ExecutionOutcome<T>,
506 serializer: S,
507 ) -> Result<S::Ok, S::Error>
508 where
509 S: Serializer,
510 {
511 ExecutionOutcome::from(source).serialize(serializer)
512 }
513 }
514
515 impl<'de, T> DeserializeAs<'de, super::ExecutionOutcome<T>> for ExecutionOutcome<'de, T>
516 where
517 T: SerdeBincodeCompat + core::fmt::Debug,
518 {
519 fn deserialize_as<D>(deserializer: D) -> Result<super::ExecutionOutcome<T>, D::Error>
520 where
521 D: Deserializer<'de>,
522 {
523 ExecutionOutcome::deserialize(deserializer).map(Into::into)
524 }
525 }
526
527 impl<T: SerdeBincodeCompat + core::fmt::Debug> SerdeBincodeCompat for super::ExecutionOutcome<T> {
528 type BincodeRepr<'a> = ExecutionOutcome<'a, T>;
529
530 fn as_repr(&self) -> Self::BincodeRepr<'_> {
531 self.into()
532 }
533
534 fn from_repr(repr: Self::BincodeRepr<'_>) -> Self {
535 repr.into()
536 }
537 }
538
539 #[cfg(test)]
540 mod tests {
541 use super::super::{serde_bincode_compat, ExecutionOutcome};
542 use rand::Rng;
543 use reth_ethereum_primitives::Receipt;
544 use reth_primitives_traits::serde_bincode_compat::SerdeBincodeCompat;
545 use serde::{Deserialize, Serialize};
546 use serde_with::serde_as;
547
548 #[test]
549 fn test_chain_bincode_roundtrip() {
550 #[serde_as]
551 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
552 struct Data<T: SerdeBincodeCompat + core::fmt::Debug> {
553 #[serde_as(as = "serde_bincode_compat::ExecutionOutcome<'_, T>")]
554 data: ExecutionOutcome<T>,
555 }
556
557 let mut bytes = [0u8; 1024];
558 rand::rng().fill(bytes.as_mut_slice());
559 let data = Data {
560 data: ExecutionOutcome {
561 bundle: Default::default(),
562 receipts: vec![],
563 first_block: 0,
564 requests: vec![],
565 },
566 };
567
568 let encoded = bincode::serialize(&data).unwrap();
569 let decoded = bincode::deserialize::<Data<Receipt>>(&encoded).unwrap();
570 assert_eq!(decoded, data);
571 }
572 }
573}
574
575#[cfg(test)]
576mod tests {
577 use super::*;
578 use alloy_consensus::TxType;
579 use alloy_primitives::{bytes, Address, LogData, B256};
580
581 #[test]
582 fn test_initialization() {
583 let bundle = BundleState::new(
585 vec![(Address::new([2; 20]), None, Some(AccountInfo::default()), HashMap::default())],
586 vec![vec![(Address::new([2; 20]), None, vec![])]],
587 vec![],
588 );
589
590 let receipts = vec![vec![Some(reth_ethereum_primitives::Receipt {
592 tx_type: TxType::Legacy,
593 cumulative_gas_used: 46913,
594 logs: vec![],
595 success: true,
596 })]];
597
598 let requests = vec![Requests::new(vec![bytes!("dead"), bytes!("beef"), bytes!("beebee")])];
600
601 let first_block = 123;
603
604 let exec_res = ExecutionOutcome {
607 bundle: bundle.clone(),
608 receipts: receipts.clone(),
609 requests: requests.clone(),
610 first_block,
611 };
612
613 assert_eq!(
615 ExecutionOutcome::new(bundle, receipts.clone(), first_block, requests.clone()),
616 exec_res
617 );
618
619 let mut state_init: BundleStateInit = AddressMap::default();
621 state_init
622 .insert(Address::new([2; 20]), (None, Some(Account::default()), B256Map::default()));
623
624 let mut revert_inner: AddressMap<AccountRevertInit> = AddressMap::default();
626 revert_inner.insert(Address::new([2; 20]), (None, vec![]));
627
628 let mut revert_init: RevertsInit = HashMap::default();
630 revert_init.insert(123, revert_inner);
631
632 assert_eq!(
635 ExecutionOutcome::new_init(
636 state_init,
637 revert_init,
638 vec![],
639 receipts,
640 first_block,
641 requests,
642 ),
643 exec_res
644 );
645 }
646
647 #[test]
648 fn test_block_number_to_index() {
649 let receipts = vec![vec![Some(reth_ethereum_primitives::Receipt {
651 tx_type: TxType::Legacy,
652 cumulative_gas_used: 46913,
653 logs: vec![],
654 success: true,
655 })]];
656
657 let first_block = 123;
659
660 let exec_res = ExecutionOutcome {
663 bundle: Default::default(),
664 receipts,
665 requests: vec![],
666 first_block,
667 };
668
669 assert_eq!(exec_res.block_number_to_index(12), None);
671
672 assert_eq!(exec_res.block_number_to_index(133), None);
674
675 assert_eq!(exec_res.block_number_to_index(123), Some(0));
677 }
678
679 #[test]
680 fn test_get_logs() {
681 let receipts = vec![vec![reth_ethereum_primitives::Receipt {
683 tx_type: TxType::Legacy,
684 cumulative_gas_used: 46913,
685 logs: vec![Log::<LogData>::default()],
686 success: true,
687 }]];
688
689 let first_block = 123;
691
692 let exec_res = ExecutionOutcome {
695 bundle: Default::default(),
696 receipts,
697 requests: vec![],
698 first_block,
699 };
700
701 let logs: Vec<&Log> = exec_res.logs(123).unwrap().collect();
703
704 assert_eq!(logs, vec![&Log::<LogData>::default()]);
706 }
707
708 #[test]
709 fn test_receipts_by_block() {
710 let receipts = vec![vec![Some(reth_ethereum_primitives::Receipt {
712 tx_type: TxType::Legacy,
713 cumulative_gas_used: 46913,
714 logs: vec![Log::<LogData>::default()],
715 success: true,
716 })]];
717
718 let first_block = 123;
720
721 let exec_res = ExecutionOutcome {
724 bundle: Default::default(), receipts, requests: vec![], first_block, };
729
730 let receipts_by_block: Vec<_> = exec_res.receipts_by_block(123).iter().collect();
732
733 assert_eq!(
735 receipts_by_block,
736 vec![&Some(reth_ethereum_primitives::Receipt {
737 tx_type: TxType::Legacy,
738 cumulative_gas_used: 46913,
739 logs: vec![Log::<LogData>::default()],
740 success: true,
741 })]
742 );
743 }
744
745 #[test]
746 fn test_receipts_len() {
747 let receipts = vec![vec![Some(reth_ethereum_primitives::Receipt {
749 tx_type: TxType::Legacy,
750 cumulative_gas_used: 46913,
751 logs: vec![Log::<LogData>::default()],
752 success: true,
753 })]];
754
755 let receipts_empty = vec![];
757
758 let first_block = 123;
760
761 let exec_res = ExecutionOutcome {
764 bundle: Default::default(), receipts, requests: vec![], first_block, };
769
770 assert_eq!(exec_res.len(), 1);
772
773 assert!(!exec_res.is_empty());
775
776 let exec_res_empty_receipts: ExecutionOutcome = ExecutionOutcome {
778 bundle: Default::default(), receipts: receipts_empty, requests: vec![], first_block, };
783
784 assert_eq!(exec_res_empty_receipts.len(), 0);
786
787 assert!(exec_res_empty_receipts.is_empty());
789 }
790
791 #[test]
792 fn test_revert_to() {
793 let receipt = reth_ethereum_primitives::Receipt {
795 tx_type: TxType::Legacy,
796 cumulative_gas_used: 46913,
797 logs: vec![],
798 success: true,
799 };
800
801 let receipts = vec![vec![Some(receipt.clone())], vec![Some(receipt.clone())]];
803
804 let first_block = 123;
806
807 let request = bytes!("deadbeef");
809
810 let requests =
812 vec![Requests::new(vec![request.clone()]), Requests::new(vec![request.clone()])];
813
814 let mut exec_res =
817 ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block };
818
819 assert!(exec_res.revert_to(123));
821
822 assert_eq!(exec_res.receipts, vec![vec![Some(receipt)]]);
824
825 assert_eq!(exec_res.requests, vec![Requests::new(vec![request])]);
827
828 assert!(!exec_res.revert_to(133));
831
832 assert!(!exec_res.revert_to(10));
835 }
836
837 #[test]
838 fn test_extend_execution_outcome() {
839 let receipt = reth_ethereum_primitives::Receipt {
841 tx_type: TxType::Legacy,
842 cumulative_gas_used: 46913,
843 logs: vec![],
844 success: true,
845 };
846
847 let receipts = vec![vec![Some(receipt.clone())]];
849
850 let request = bytes!("deadbeef");
852
853 let requests = vec![Requests::new(vec![request.clone()])];
855
856 let first_block = 123;
858
859 let mut exec_res =
861 ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block };
862
863 exec_res.extend(exec_res.clone());
865
866 assert_eq!(
868 exec_res,
869 ExecutionOutcome {
870 bundle: Default::default(),
871 receipts: vec![vec![Some(receipt.clone())], vec![Some(receipt)]],
872 requests: vec![Requests::new(vec![request.clone()]), Requests::new(vec![request])],
873 first_block: 123,
874 }
875 );
876 }
877
878 #[test]
879 fn test_split_at_execution_outcome() {
880 let receipt = reth_ethereum_primitives::Receipt {
882 tx_type: TxType::Legacy,
883 cumulative_gas_used: 46913,
884 logs: vec![],
885 success: true,
886 };
887
888 let receipts = vec![
890 vec![Some(receipt.clone())],
891 vec![Some(receipt.clone())],
892 vec![Some(receipt.clone())],
893 ];
894
895 let first_block = 123;
897
898 let request = bytes!("deadbeef");
900
901 let requests = vec![
903 Requests::new(vec![request.clone()]),
904 Requests::new(vec![request.clone()]),
905 Requests::new(vec![request.clone()]),
906 ];
907
908 let exec_res =
911 ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block };
912
913 let result = exec_res.clone().split_at(124);
915
916 let lower_execution_outcome = ExecutionOutcome {
918 bundle: Default::default(),
919 receipts: vec![vec![Some(receipt.clone())]],
920 requests: vec![Requests::new(vec![request.clone()])],
921 first_block,
922 };
923
924 let higher_execution_outcome = ExecutionOutcome {
926 bundle: Default::default(),
927 receipts: vec![vec![Some(receipt.clone())], vec![Some(receipt)]],
928 requests: vec![Requests::new(vec![request.clone()]), Requests::new(vec![request])],
929 first_block: 124,
930 };
931
932 assert_eq!(result.0, Some(lower_execution_outcome));
934 assert_eq!(result.1, higher_execution_outcome);
935
936 assert_eq!(exec_res.clone().split_at(123), (None, exec_res));
938 }
939
940 #[test]
941 fn test_changed_accounts() {
942 let address1 = Address::random();
944 let address2 = Address::random();
945 let address3 = Address::random();
946
947 let account_info1 = AccountInfo {
949 nonce: 1,
950 balance: U256::from(100),
951 code_hash: B256::ZERO,
952 code: None,
953 account_id: None,
954 };
955 let account_info2 = AccountInfo {
956 nonce: 2,
957 balance: U256::from(200),
958 code_hash: B256::ZERO,
959 code: None,
960 account_id: None,
961 };
962
963 let mut bundle_state = BundleState::default();
965 bundle_state.state.insert(
966 address1,
967 BundleAccount {
968 info: Some(account_info1),
969 storage: Default::default(),
970 original_info: Default::default(),
971 status: Default::default(),
972 },
973 );
974 bundle_state.state.insert(
975 address2,
976 BundleAccount {
977 info: Some(account_info2),
978 storage: Default::default(),
979 original_info: Default::default(),
980 status: Default::default(),
981 },
982 );
983
984 bundle_state.state.insert(
986 address3,
987 BundleAccount {
988 info: None,
989 storage: Default::default(),
990 original_info: Default::default(),
991 status: Default::default(),
992 },
993 );
994
995 let execution_outcome: ExecutionOutcome = ExecutionOutcome {
996 bundle: bundle_state,
997 receipts: Default::default(),
998 first_block: 0,
999 requests: vec![],
1000 };
1001
1002 let changed_accounts: Vec<ChangedAccount> = execution_outcome.changed_accounts().collect();
1004
1005 assert_eq!(changed_accounts.len(), 2);
1007
1008 assert!(changed_accounts.contains(&ChangedAccount {
1009 address: address1,
1010 nonce: 1,
1011 balance: U256::from(100)
1012 }));
1013
1014 assert!(changed_accounts.contains(&ChangedAccount {
1015 address: address2,
1016 nonce: 2,
1017 balance: U256::from(200)
1018 }));
1019 }
1020}