reth_trie_common/
hashed_state.rs

1use core::ops::Not;
2
3use crate::{
4    added_removed_keys::MultiAddedRemovedKeys,
5    prefix_set::{PrefixSetMut, TriePrefixSetsMut},
6    utils::extend_sorted_vec,
7    KeyHasher, MultiProofTargets, Nibbles,
8};
9use alloc::{borrow::Cow, vec::Vec};
10use alloy_primitives::{
11    keccak256,
12    map::{hash_map, B256Map, HashMap, HashSet},
13    Address, B256, U256,
14};
15use itertools::Itertools;
16#[cfg(feature = "rayon")]
17pub use rayon::*;
18use reth_primitives_traits::Account;
19
20#[cfg(feature = "rayon")]
21use rayon::prelude::{IntoParallelIterator, ParallelIterator};
22
23use revm_database::{AccountStatus, BundleAccount};
24
25/// In-memory hashed state that stores account and storage changes with keccak256-hashed keys in
26/// hash maps.
27#[derive(PartialEq, Eq, Clone, Default, Debug)]
28#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
29pub struct HashedPostState {
30    /// Mapping of hashed address to account info, `None` if destroyed.
31    pub accounts: B256Map<Option<Account>>,
32    /// Mapping of hashed address to hashed storage.
33    pub storages: B256Map<HashedStorage>,
34}
35
36impl HashedPostState {
37    /// Create new instance of [`HashedPostState`].
38    pub fn with_capacity(capacity: usize) -> Self {
39        Self {
40            accounts: B256Map::with_capacity_and_hasher(capacity, Default::default()),
41            storages: B256Map::with_capacity_and_hasher(capacity, Default::default()),
42        }
43    }
44
45    /// Initialize [`HashedPostState`] from bundle state.
46    /// Hashes all changed accounts and storage entries that are currently stored in the bundle
47    /// state.
48    #[inline]
49    #[cfg(feature = "rayon")]
50    pub fn from_bundle_state<'a, KH: KeyHasher>(
51        state: impl IntoParallelIterator<Item = (&'a Address, &'a BundleAccount)>,
52    ) -> Self {
53        let hashed = state
54            .into_par_iter()
55            .map(|(address, account)| {
56                let hashed_address = KH::hash_key(address);
57                let hashed_account = account.info.as_ref().map(Into::into);
58                let hashed_storage = HashedStorage::from_plain_storage(
59                    account.status,
60                    account.storage.iter().map(|(slot, value)| (slot, &value.present_value)),
61                );
62                (hashed_address, (hashed_account, hashed_storage))
63            })
64            .collect::<Vec<(B256, (Option<Account>, HashedStorage))>>();
65
66        let mut accounts = HashMap::with_capacity_and_hasher(hashed.len(), Default::default());
67        let mut storages = HashMap::with_capacity_and_hasher(hashed.len(), Default::default());
68        for (address, (account, storage)) in hashed {
69            accounts.insert(address, account);
70            if !storage.is_empty() {
71                storages.insert(address, storage);
72            }
73        }
74        Self { accounts, storages }
75    }
76
77    /// Initialize [`HashedPostState`] from bundle state.
78    /// Hashes all changed accounts and storage entries that are currently stored in the bundle
79    /// state.
80    #[cfg(not(feature = "rayon"))]
81    pub fn from_bundle_state<'a, KH: KeyHasher>(
82        state: impl IntoIterator<Item = (&'a Address, &'a BundleAccount)>,
83    ) -> Self {
84        let hashed = state
85            .into_iter()
86            .map(|(address, account)| {
87                let hashed_address = KH::hash_key(address);
88                let hashed_account = account.info.as_ref().map(Into::into);
89                let hashed_storage = HashedStorage::from_plain_storage(
90                    account.status,
91                    account.storage.iter().map(|(slot, value)| (slot, &value.present_value)),
92                );
93                (hashed_address, (hashed_account, hashed_storage))
94            })
95            .collect::<Vec<(B256, (Option<Account>, HashedStorage))>>();
96
97        let mut accounts = HashMap::with_capacity_and_hasher(hashed.len(), Default::default());
98        let mut storages = HashMap::with_capacity_and_hasher(hashed.len(), Default::default());
99        for (address, (account, storage)) in hashed {
100            accounts.insert(address, account);
101            if !storage.is_empty() {
102                storages.insert(address, storage);
103            }
104        }
105        Self { accounts, storages }
106    }
107
108    /// Construct [`HashedPostState`] from a single [`HashedStorage`].
109    pub fn from_hashed_storage(hashed_address: B256, storage: HashedStorage) -> Self {
110        Self {
111            accounts: HashMap::default(),
112            storages: HashMap::from_iter([(hashed_address, storage)]),
113        }
114    }
115
116    /// Set account entries on hashed state.
117    pub fn with_accounts(
118        mut self,
119        accounts: impl IntoIterator<Item = (B256, Option<Account>)>,
120    ) -> Self {
121        self.accounts = HashMap::from_iter(accounts);
122        self
123    }
124
125    /// Set storage entries on hashed state.
126    pub fn with_storages(
127        mut self,
128        storages: impl IntoIterator<Item = (B256, HashedStorage)>,
129    ) -> Self {
130        self.storages = HashMap::from_iter(storages);
131        self
132    }
133
134    /// Returns `true` if the hashed state is empty.
135    pub fn is_empty(&self) -> bool {
136        self.accounts.is_empty() && self.storages.is_empty()
137    }
138
139    /// Construct [`TriePrefixSetsMut`] from hashed post state.
140    /// The prefix sets contain the hashed account and storage keys that have been changed in the
141    /// post state.
142    pub fn construct_prefix_sets(&self) -> TriePrefixSetsMut {
143        // Populate account prefix set.
144        let mut account_prefix_set = PrefixSetMut::with_capacity(self.accounts.len());
145        let mut destroyed_accounts = HashSet::default();
146        for (hashed_address, account) in &self.accounts {
147            account_prefix_set.insert(Nibbles::unpack(hashed_address));
148
149            if account.is_none() {
150                destroyed_accounts.insert(*hashed_address);
151            }
152        }
153
154        // Populate storage prefix sets.
155        let mut storage_prefix_sets =
156            HashMap::with_capacity_and_hasher(self.storages.len(), Default::default());
157        for (hashed_address, hashed_storage) in &self.storages {
158            account_prefix_set.insert(Nibbles::unpack(hashed_address));
159            storage_prefix_sets.insert(*hashed_address, hashed_storage.construct_prefix_set());
160        }
161
162        TriePrefixSetsMut { account_prefix_set, storage_prefix_sets, destroyed_accounts }
163    }
164
165    /// Create multiproof targets for this state.
166    pub fn multi_proof_targets(&self) -> MultiProofTargets {
167        // Pre-allocate minimum capacity for the targets.
168        let mut targets = MultiProofTargets::with_capacity(self.accounts.len());
169        for hashed_address in self.accounts.keys() {
170            targets.insert(*hashed_address, Default::default());
171        }
172        for (hashed_address, storage) in &self.storages {
173            targets.entry(*hashed_address).or_default().extend(storage.storage.keys().copied());
174        }
175        targets
176    }
177
178    /// Create multiproof targets difference for this state,
179    /// i.e., the targets that are in targets create from `self` but not in `excluded`.
180    ///
181    /// This method is preferred to first calling `Self::multi_proof_targets` and the calling
182    /// `MultiProofTargets::retain_difference`, because it does not over allocate the targets map.
183    pub fn multi_proof_targets_difference(
184        &self,
185        excluded: &MultiProofTargets,
186    ) -> MultiProofTargets {
187        let mut targets = MultiProofTargets::default();
188        for hashed_address in self.accounts.keys() {
189            if !excluded.contains_key(hashed_address) {
190                targets.insert(*hashed_address, Default::default());
191            }
192        }
193        for (hashed_address, storage) in &self.storages {
194            let maybe_excluded_storage = excluded.get(hashed_address);
195            let mut hashed_slots_targets = storage
196                .storage
197                .keys()
198                .filter(|slot| !maybe_excluded_storage.is_some_and(|f| f.contains(*slot)))
199                .peekable();
200            if hashed_slots_targets.peek().is_some() {
201                targets.entry(*hashed_address).or_default().extend(hashed_slots_targets);
202            }
203        }
204        targets
205    }
206
207    /// Partition the state update into two state updates:
208    /// - First with accounts and storages slots that are present in the provided targets.
209    /// - Second with all other.
210    ///
211    /// CAUTION: The state updates are expected to be applied in order, so that the storage wipes
212    /// are done correctly.
213    pub fn partition_by_targets(
214        mut self,
215        targets: &MultiProofTargets,
216        added_removed_keys: &MultiAddedRemovedKeys,
217    ) -> (Self, Self) {
218        let mut state_updates_not_in_targets = Self::default();
219
220        self.storages.retain(|&address, storage| {
221            let storage_added_removed_keys = added_removed_keys.get_storage(&address);
222
223            let (retain, storage_not_in_targets) = match targets.get(&address) {
224                Some(storage_in_targets) => {
225                    let mut storage_not_in_targets = HashedStorage::default();
226                    storage.storage.retain(|&slot, value| {
227                        if storage_in_targets.contains(&slot) &&
228                            !storage_added_removed_keys.is_some_and(|k| k.is_removed(&slot))
229                        {
230                            return true
231                        }
232
233                        storage_not_in_targets.storage.insert(slot, *value);
234                        false
235                    });
236
237                    // We do not check the wiped flag here, because targets only contain addresses
238                    // and storage slots. So if there are no storage slots left, the storage update
239                    // can be fully removed.
240                    let retain = !storage.storage.is_empty();
241
242                    // Since state updates are expected to be applied in order, we can only set the
243                    // wiped flag in the second storage update if the first storage update is empty
244                    // and will not be retained.
245                    if !retain {
246                        storage_not_in_targets.wiped = storage.wiped;
247                    }
248
249                    (
250                        retain,
251                        storage_not_in_targets.is_empty().not().then_some(storage_not_in_targets),
252                    )
253                }
254                None => (false, Some(core::mem::take(storage))),
255            };
256
257            if let Some(storage_not_in_targets) = storage_not_in_targets {
258                state_updates_not_in_targets.storages.insert(address, storage_not_in_targets);
259            }
260
261            retain
262        });
263        self.accounts.retain(|&address, account| {
264            if targets.contains_key(&address) {
265                return true
266            }
267
268            state_updates_not_in_targets.accounts.insert(address, *account);
269            false
270        });
271
272        (self, state_updates_not_in_targets)
273    }
274
275    /// Returns an iterator that yields chunks of the specified size.
276    ///
277    /// See [`ChunkedHashedPostState`] for more information.
278    pub fn chunks(self, size: usize) -> ChunkedHashedPostState {
279        ChunkedHashedPostState::new(self, size)
280    }
281
282    /// Returns the number of items that will be considered during chunking in `[Self::chunks]`.
283    pub fn chunking_length(&self) -> usize {
284        self.accounts.len() +
285            self.storages
286                .values()
287                .map(|storage| if storage.wiped { 1 } else { 0 } + storage.storage.len())
288                .sum::<usize>()
289    }
290
291    /// Extend this hashed post state with contents of another.
292    /// Entries in the second hashed post state take precedence.
293    pub fn extend(&mut self, other: Self) {
294        self.extend_inner(Cow::Owned(other));
295    }
296
297    /// Extend this hashed post state with contents of another.
298    /// Entries in the second hashed post state take precedence.
299    ///
300    /// Slightly less efficient than [`Self::extend`], but preferred to `extend(other.clone())`.
301    pub fn extend_ref(&mut self, other: &Self) {
302        self.extend_inner(Cow::Borrowed(other));
303    }
304
305    fn extend_inner(&mut self, other: Cow<'_, Self>) {
306        self.accounts.extend(other.accounts.iter().map(|(&k, &v)| (k, v)));
307
308        self.storages.reserve(other.storages.len());
309        match other {
310            Cow::Borrowed(other) => {
311                self.extend_storages(other.storages.iter().map(|(k, v)| (*k, Cow::Borrowed(v))))
312            }
313            Cow::Owned(other) => {
314                self.extend_storages(other.storages.into_iter().map(|(k, v)| (k, Cow::Owned(v))))
315            }
316        }
317    }
318
319    fn extend_storages<'a>(
320        &mut self,
321        storages: impl IntoIterator<Item = (B256, Cow<'a, HashedStorage>)>,
322    ) {
323        for (hashed_address, storage) in storages {
324            match self.storages.entry(hashed_address) {
325                hash_map::Entry::Vacant(entry) => {
326                    entry.insert(storage.into_owned());
327                }
328                hash_map::Entry::Occupied(mut entry) => {
329                    entry.get_mut().extend(&storage);
330                }
331            }
332        }
333    }
334
335    /// Extend this hashed post state with sorted data, converting directly into the unsorted
336    /// `HashMap` representation. This is more efficient than first converting to `HashedPostState`
337    /// and then extending, as it avoids creating intermediate `HashMap` allocations.
338    pub fn extend_from_sorted(&mut self, sorted: &HashedPostStateSorted) {
339        // Reserve capacity for accounts
340        self.accounts.reserve(sorted.accounts.len());
341
342        // Insert accounts (Some = updated, None = destroyed)
343        for (address, account) in &sorted.accounts {
344            self.accounts.insert(*address, *account);
345        }
346
347        // Reserve capacity for storages
348        self.storages.reserve(sorted.storages.len());
349
350        // Extend storages
351        for (hashed_address, sorted_storage) in &sorted.storages {
352            match self.storages.entry(*hashed_address) {
353                hash_map::Entry::Vacant(entry) => {
354                    let mut new_storage = HashedStorage::new(false);
355                    new_storage.extend_from_sorted(sorted_storage);
356                    entry.insert(new_storage);
357                }
358                hash_map::Entry::Occupied(mut entry) => {
359                    entry.get_mut().extend_from_sorted(sorted_storage);
360                }
361            }
362        }
363    }
364
365    /// Converts hashed post state into [`HashedPostStateSorted`].
366    pub fn into_sorted(self) -> HashedPostStateSorted {
367        let mut accounts: Vec<_> = self.accounts.into_iter().collect();
368        accounts.sort_unstable_by_key(|(address, _)| *address);
369
370        let storages = self
371            .storages
372            .into_iter()
373            .map(|(hashed_address, storage)| (hashed_address, storage.into_sorted()))
374            .collect();
375
376        HashedPostStateSorted { accounts, storages }
377    }
378
379    /// Creates a sorted copy without consuming self.
380    /// More efficient than `.clone().into_sorted()` as it avoids cloning `HashMap` metadata.
381    pub fn clone_into_sorted(&self) -> HashedPostStateSorted {
382        let mut accounts: Vec<_> = self.accounts.iter().map(|(&k, &v)| (k, v)).collect();
383        accounts.sort_unstable_by_key(|(address, _)| *address);
384
385        let storages = self
386            .storages
387            .iter()
388            .map(|(&hashed_address, storage)| (hashed_address, storage.clone_into_sorted()))
389            .collect();
390
391        HashedPostStateSorted { accounts, storages }
392    }
393
394    /// Clears the account and storage maps of this `HashedPostState`.
395    pub fn clear(&mut self) {
396        self.accounts.clear();
397        self.storages.clear();
398    }
399}
400
401/// Representation of in-memory hashed storage.
402#[derive(PartialEq, Eq, Clone, Debug, Default)]
403#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
404pub struct HashedStorage {
405    /// Flag indicating whether the storage was wiped or not.
406    pub wiped: bool,
407    /// Mapping of hashed storage slot to storage value.
408    pub storage: B256Map<U256>,
409}
410
411impl HashedStorage {
412    /// Create new instance of [`HashedStorage`].
413    pub fn new(wiped: bool) -> Self {
414        Self { wiped, storage: HashMap::default() }
415    }
416
417    /// Check if self is empty.
418    pub fn is_empty(&self) -> bool {
419        !self.wiped && self.storage.is_empty()
420    }
421
422    /// Create new hashed storage from iterator.
423    pub fn from_iter(wiped: bool, iter: impl IntoIterator<Item = (B256, U256)>) -> Self {
424        Self { wiped, storage: HashMap::from_iter(iter) }
425    }
426
427    /// Create new hashed storage from account status and plain storage.
428    pub fn from_plain_storage<'a>(
429        status: AccountStatus,
430        storage: impl IntoIterator<Item = (&'a U256, &'a U256)>,
431    ) -> Self {
432        Self::from_iter(
433            status.was_destroyed(),
434            storage.into_iter().map(|(key, value)| (keccak256(B256::from(*key)), *value)),
435        )
436    }
437
438    /// Construct [`PrefixSetMut`] from hashed storage.
439    pub fn construct_prefix_set(&self) -> PrefixSetMut {
440        if self.wiped {
441            PrefixSetMut::all()
442        } else {
443            let mut prefix_set = PrefixSetMut::with_capacity(self.storage.len());
444            for hashed_slot in self.storage.keys() {
445                prefix_set.insert(Nibbles::unpack(hashed_slot));
446            }
447            prefix_set
448        }
449    }
450
451    /// Extend hashed storage with contents of other.
452    /// The entries in second hashed storage take precedence.
453    pub fn extend(&mut self, other: &Self) {
454        if other.wiped {
455            self.wiped = true;
456            self.storage.clear();
457        }
458        self.storage.extend(other.storage.iter().map(|(&k, &v)| (k, v)));
459    }
460
461    /// Extend hashed storage with sorted data, converting directly into the unsorted `HashMap`
462    /// representation. This is more efficient than first converting to `HashedStorage` and
463    /// then extending, as it avoids creating intermediate `HashMap` allocations.
464    pub fn extend_from_sorted(&mut self, sorted: &HashedStorageSorted) {
465        if sorted.wiped {
466            self.wiped = true;
467            self.storage.clear();
468        }
469
470        // Reserve capacity for all slots
471        self.storage.reserve(sorted.storage_slots.len());
472
473        // Insert all storage slots
474        for (slot, value) in &sorted.storage_slots {
475            self.storage.insert(*slot, *value);
476        }
477    }
478
479    /// Converts hashed storage into [`HashedStorageSorted`].
480    pub fn into_sorted(self) -> HashedStorageSorted {
481        let mut storage_slots: Vec<_> = self.storage.into_iter().collect();
482        storage_slots.sort_unstable_by_key(|(key, _)| *key);
483
484        HashedStorageSorted { storage_slots, wiped: self.wiped }
485    }
486
487    /// Creates a sorted copy without consuming self.
488    /// More efficient than `.clone().into_sorted()` as it avoids cloning `HashMap` metadata.
489    pub fn clone_into_sorted(&self) -> HashedStorageSorted {
490        let mut storage_slots: Vec<_> = self.storage.iter().map(|(&k, &v)| (k, v)).collect();
491        storage_slots.sort_unstable_by_key(|(key, _)| *key);
492
493        HashedStorageSorted { storage_slots, wiped: self.wiped }
494    }
495}
496
497/// Sorted hashed post state optimized for iterating during state trie calculation.
498#[derive(PartialEq, Eq, Clone, Default, Debug)]
499pub struct HashedPostStateSorted {
500    /// Sorted collection of account updates. `None` indicates a destroyed account.
501    pub accounts: Vec<(B256, Option<Account>)>,
502    /// Map of hashed addresses to their sorted storage updates.
503    pub storages: B256Map<HashedStorageSorted>,
504}
505
506impl HashedPostStateSorted {
507    /// Create new instance of [`HashedPostStateSorted`]
508    pub const fn new(
509        accounts: Vec<(B256, Option<Account>)>,
510        storages: B256Map<HashedStorageSorted>,
511    ) -> Self {
512        Self { accounts, storages }
513    }
514
515    /// Returns reference to hashed accounts.
516    pub const fn accounts(&self) -> &Vec<(B256, Option<Account>)> {
517        &self.accounts
518    }
519
520    /// Returns reference to hashed account storages.
521    pub const fn account_storages(&self) -> &B256Map<HashedStorageSorted> {
522        &self.storages
523    }
524
525    /// Returns `true` if there are no account or storage updates.
526    pub fn is_empty(&self) -> bool {
527        self.accounts.is_empty() && self.storages.is_empty()
528    }
529
530    /// Returns the total number of updates including all accounts and storage updates.
531    pub fn total_len(&self) -> usize {
532        self.accounts.len() + self.storages.values().map(|s| s.len()).sum::<usize>()
533    }
534
535    /// Construct [`TriePrefixSetsMut`] from hashed post state.
536    ///
537    /// The prefix sets contain the hashed account and storage keys that have been changed in the
538    /// post state.
539    pub fn construct_prefix_sets(&self) -> TriePrefixSetsMut {
540        let mut account_prefix_set = PrefixSetMut::with_capacity(self.accounts.len());
541        let mut destroyed_accounts = HashSet::default();
542        for (hashed_address, account) in &self.accounts {
543            account_prefix_set.insert(Nibbles::unpack(hashed_address));
544            if account.is_none() {
545                destroyed_accounts.insert(*hashed_address);
546            }
547        }
548
549        let mut storage_prefix_sets =
550            B256Map::with_capacity_and_hasher(self.storages.len(), Default::default());
551        for (hashed_address, hashed_storage) in &self.storages {
552            // Ensure account trie covers storage overlays even if account map is empty.
553            account_prefix_set.insert(Nibbles::unpack(hashed_address));
554
555            let prefix_set = if hashed_storage.wiped {
556                PrefixSetMut::all()
557            } else {
558                let mut prefix_set =
559                    PrefixSetMut::with_capacity(hashed_storage.storage_slots.len());
560                prefix_set.extend_keys(
561                    hashed_storage
562                        .storage_slots
563                        .iter()
564                        .map(|(hashed_slot, _)| Nibbles::unpack(hashed_slot)),
565                );
566                prefix_set
567            };
568
569            storage_prefix_sets.insert(*hashed_address, prefix_set);
570        }
571
572        TriePrefixSetsMut { account_prefix_set, storage_prefix_sets, destroyed_accounts }
573    }
574
575    /// Extends this state with contents of another sorted state.
576    /// Entries in `other` take precedence for duplicate keys.
577    pub fn extend_ref(&mut self, other: &Self) {
578        // Extend accounts
579        extend_sorted_vec(&mut self.accounts, &other.accounts);
580
581        // Extend storages
582        for (hashed_address, other_storage) in &other.storages {
583            self.storages
584                .entry(*hashed_address)
585                .and_modify(|existing| existing.extend_ref(other_storage))
586                .or_insert_with(|| other_storage.clone());
587        }
588    }
589
590    /// Clears all accounts and storage data.
591    pub fn clear(&mut self) {
592        self.accounts.clear();
593        self.storages.clear();
594    }
595}
596
597impl AsRef<Self> for HashedPostStateSorted {
598    fn as_ref(&self) -> &Self {
599        self
600    }
601}
602
603/// Sorted hashed storage optimized for iterating during state trie calculation.
604#[derive(Clone, Eq, PartialEq, Debug)]
605pub struct HashedStorageSorted {
606    /// Sorted collection of updated storage slots. [`U256::ZERO`] indicates a deleted value.
607    pub storage_slots: Vec<(B256, U256)>,
608    /// Flag indicating whether the storage was wiped or not.
609    pub wiped: bool,
610}
611
612impl HashedStorageSorted {
613    /// Returns `true` if the account was wiped.
614    pub const fn is_wiped(&self) -> bool {
615        self.wiped
616    }
617
618    /// Returns reference to updated storage slots.
619    pub fn storage_slots_ref(&self) -> &[(B256, U256)] {
620        &self.storage_slots
621    }
622
623    /// Returns the total number of storage slot updates.
624    pub const fn len(&self) -> usize {
625        self.storage_slots.len()
626    }
627
628    /// Returns `true` if there are no storage slot updates.
629    pub const fn is_empty(&self) -> bool {
630        self.storage_slots.is_empty()
631    }
632
633    /// Extends the storage slots updates with another set of sorted updates.
634    ///
635    /// If `other` is marked as deleted, this will be marked as deleted and all slots cleared.
636    /// Otherwise, nodes are merged with `other`'s values taking precedence for duplicates.
637    pub fn extend_ref(&mut self, other: &Self) {
638        if other.wiped {
639            // If other is wiped, clear everything and copy from other
640            self.wiped = true;
641            self.storage_slots.clear();
642            self.storage_slots.extend(other.storage_slots.iter().copied());
643            return;
644        }
645
646        // Extend the sorted non-zero valued slots
647        extend_sorted_vec(&mut self.storage_slots, &other.storage_slots);
648    }
649}
650
651impl From<HashedStorageSorted> for HashedStorage {
652    fn from(sorted: HashedStorageSorted) -> Self {
653        let mut storage = B256Map::default();
654
655        // Add all storage slots (including zero-valued ones which indicate deletion)
656        for (slot, value) in sorted.storage_slots {
657            storage.insert(slot, value);
658        }
659
660        Self { wiped: sorted.wiped, storage }
661    }
662}
663
664impl From<HashedPostStateSorted> for HashedPostState {
665    fn from(sorted: HashedPostStateSorted) -> Self {
666        let mut accounts = B256Map::default();
667
668        // Add all accounts (Some for updated, None for destroyed)
669        for (address, account) in sorted.accounts {
670            accounts.insert(address, account);
671        }
672
673        // Convert storages
674        let storages = sorted
675            .storages
676            .into_iter()
677            .map(|(address, storage)| (address, storage.into()))
678            .collect();
679
680        Self { accounts, storages }
681    }
682}
683
684/// An iterator that yields chunks of the state updates of at most `size` account and storage
685/// targets.
686///
687/// # Notes
688/// 1. Chunks are expected to be applied in order, because of storage wipes. If applied out of
689///    order, it's possible to wipe more storage than in the original state update.
690/// 2. For each account, chunks with storage updates come first, followed by account updates.
691#[derive(Debug)]
692pub struct ChunkedHashedPostState {
693    flattened: alloc::vec::IntoIter<(B256, FlattenedHashedPostStateItem)>,
694    size: usize,
695}
696
697#[derive(Debug)]
698enum FlattenedHashedPostStateItem {
699    Account(Option<Account>),
700    StorageWipe,
701    StorageUpdate { slot: B256, value: U256 },
702}
703
704impl ChunkedHashedPostState {
705    fn new(hashed_post_state: HashedPostState, size: usize) -> Self {
706        let flattened = hashed_post_state
707            .storages
708            .into_iter()
709            .flat_map(|(address, storage)| {
710                // Storage wipes should go first
711                Some((address, FlattenedHashedPostStateItem::StorageWipe))
712                    .filter(|_| storage.wiped)
713                    .into_iter()
714                    .chain(
715                        storage.storage.into_iter().sorted_unstable_by_key(|(slot, _)| *slot).map(
716                            move |(slot, value)| {
717                                (
718                                    address,
719                                    FlattenedHashedPostStateItem::StorageUpdate { slot, value },
720                                )
721                            },
722                        ),
723                    )
724            })
725            .chain(hashed_post_state.accounts.into_iter().map(|(address, account)| {
726                (address, FlattenedHashedPostStateItem::Account(account))
727            }))
728            // We need stable sort here to preserve the order for each address:
729            // 1. Storage wipes
730            // 2. Storage updates
731            // 3. Account update
732            .sorted_by_key(|(address, _)| *address);
733
734        Self { flattened, size }
735    }
736}
737
738impl Iterator for ChunkedHashedPostState {
739    type Item = HashedPostState;
740
741    fn next(&mut self) -> Option<Self::Item> {
742        let mut chunk = HashedPostState::default();
743
744        let mut current_size = 0;
745        while current_size < self.size {
746            let Some((address, item)) = self.flattened.next() else { break };
747
748            match item {
749                FlattenedHashedPostStateItem::Account(account) => {
750                    chunk.accounts.insert(address, account);
751                }
752                FlattenedHashedPostStateItem::StorageWipe => {
753                    chunk.storages.entry(address).or_default().wiped = true;
754                }
755                FlattenedHashedPostStateItem::StorageUpdate { slot, value } => {
756                    chunk.storages.entry(address).or_default().storage.insert(slot, value);
757                }
758            }
759
760            current_size += 1;
761        }
762
763        if chunk.is_empty() {
764            None
765        } else {
766            Some(chunk)
767        }
768    }
769}
770
771#[cfg(test)]
772mod tests {
773    use super::*;
774    use crate::KeccakKeyHasher;
775    use alloy_primitives::Bytes;
776    use revm_database::{states::StorageSlot, StorageWithOriginalValues};
777    use revm_state::{AccountInfo, Bytecode};
778
779    #[test]
780    fn hashed_state_wiped_extension() {
781        let hashed_address = B256::default();
782        let hashed_slot = B256::with_last_byte(64);
783        let hashed_slot2 = B256::with_last_byte(65);
784
785        // Initialize post state storage
786        let original_slot_value = U256::from(123);
787        let mut hashed_state = HashedPostState::default().with_storages([(
788            hashed_address,
789            HashedStorage::from_iter(
790                false,
791                [(hashed_slot, original_slot_value), (hashed_slot2, original_slot_value)],
792            ),
793        )]);
794
795        // Update single slot value
796        let updated_slot_value = U256::from(321);
797        let extension = HashedPostState::default().with_storages([(
798            hashed_address,
799            HashedStorage::from_iter(false, [(hashed_slot, updated_slot_value)]),
800        )]);
801        hashed_state.extend(extension);
802
803        let account_storage = hashed_state.storages.get(&hashed_address);
804        assert_eq!(
805            account_storage.and_then(|st| st.storage.get(&hashed_slot)),
806            Some(&updated_slot_value)
807        );
808        assert_eq!(
809            account_storage.and_then(|st| st.storage.get(&hashed_slot2)),
810            Some(&original_slot_value)
811        );
812        assert_eq!(account_storage.map(|st| st.wiped), Some(false));
813
814        // Wipe account storage
815        let wiped_extension =
816            HashedPostState::default().with_storages([(hashed_address, HashedStorage::new(true))]);
817        hashed_state.extend(wiped_extension);
818
819        let account_storage = hashed_state.storages.get(&hashed_address);
820        assert_eq!(account_storage.map(|st| st.storage.is_empty()), Some(true));
821        assert_eq!(account_storage.map(|st| st.wiped), Some(true));
822
823        // Reinitialize single slot value
824        hashed_state.extend(HashedPostState::default().with_storages([(
825            hashed_address,
826            HashedStorage::from_iter(false, [(hashed_slot, original_slot_value)]),
827        )]));
828        let account_storage = hashed_state.storages.get(&hashed_address);
829        assert_eq!(
830            account_storage.and_then(|st| st.storage.get(&hashed_slot)),
831            Some(&original_slot_value)
832        );
833        assert_eq!(account_storage.and_then(|st| st.storage.get(&hashed_slot2)), None);
834        assert_eq!(account_storage.map(|st| st.wiped), Some(true));
835
836        // Reinitialize single slot value
837        hashed_state.extend(HashedPostState::default().with_storages([(
838            hashed_address,
839            HashedStorage::from_iter(false, [(hashed_slot2, updated_slot_value)]),
840        )]));
841        let account_storage = hashed_state.storages.get(&hashed_address);
842        assert_eq!(
843            account_storage.and_then(|st| st.storage.get(&hashed_slot)),
844            Some(&original_slot_value)
845        );
846        assert_eq!(
847            account_storage.and_then(|st| st.storage.get(&hashed_slot2)),
848            Some(&updated_slot_value)
849        );
850        assert_eq!(account_storage.map(|st| st.wiped), Some(true));
851    }
852
853    #[test]
854    fn test_hashed_post_state_from_bundle_state() {
855        // Prepare a random Ethereum address as a key for the account.
856        let address = Address::random();
857
858        // Create a mock account info object.
859        let account_info = AccountInfo {
860            balance: U256::from(123),
861            nonce: 42,
862            code_hash: B256::random(),
863            code: Some(Bytecode::new_raw(Bytes::from(vec![1, 2]))),
864        };
865
866        let mut storage = StorageWithOriginalValues::default();
867        storage.insert(
868            U256::from(1),
869            StorageSlot { present_value: U256::from(4), ..Default::default() },
870        );
871
872        // Create a `BundleAccount` struct to represent the account and its storage.
873        let account = BundleAccount {
874            status: AccountStatus::Changed,
875            info: Some(account_info.clone()),
876            storage,
877            original_info: None,
878        };
879
880        // Create a vector of tuples representing the bundle state.
881        let state = vec![(&address, &account)];
882
883        // Convert the bundle state into a hashed post state.
884        let hashed_state = HashedPostState::from_bundle_state::<KeccakKeyHasher>(state);
885
886        // Validate the hashed post state.
887        assert_eq!(hashed_state.accounts.len(), 1);
888        assert_eq!(hashed_state.storages.len(), 1);
889
890        // Validate the account info.
891        assert_eq!(
892            *hashed_state.accounts.get(&keccak256(address)).unwrap(),
893            Some(account_info.into())
894        );
895    }
896
897    #[test]
898    fn test_hashed_post_state_with_accounts() {
899        // Prepare random addresses and mock account info.
900        let address_1 = Address::random();
901        let address_2 = Address::random();
902
903        let account_info_1 = AccountInfo {
904            balance: U256::from(1000),
905            nonce: 1,
906            code_hash: B256::random(),
907            code: None,
908        };
909
910        // Create hashed accounts with addresses.
911        let account_1 = (keccak256(address_1), Some(account_info_1.into()));
912        let account_2 = (keccak256(address_2), None);
913
914        // Add accounts to the hashed post state.
915        let hashed_state = HashedPostState::default().with_accounts(vec![account_1, account_2]);
916
917        // Validate the hashed post state.
918        assert_eq!(hashed_state.accounts.len(), 2);
919        assert!(hashed_state.accounts.contains_key(&keccak256(address_1)));
920        assert!(hashed_state.accounts.contains_key(&keccak256(address_2)));
921    }
922
923    #[test]
924    fn test_hashed_post_state_with_storages() {
925        // Prepare random addresses and mock storage entries.
926        let address_1 = Address::random();
927        let address_2 = Address::random();
928
929        let storage_1 = (keccak256(address_1), HashedStorage::new(false));
930        let storage_2 = (keccak256(address_2), HashedStorage::new(true));
931
932        // Add storages to the hashed post state.
933        let hashed_state = HashedPostState::default().with_storages(vec![storage_1, storage_2]);
934
935        // Validate the hashed post state.
936        assert_eq!(hashed_state.storages.len(), 2);
937        assert!(hashed_state.storages.contains_key(&keccak256(address_1)));
938        assert!(hashed_state.storages.contains_key(&keccak256(address_2)));
939    }
940
941    #[test]
942    fn test_hashed_post_state_is_empty() {
943        // Create an empty hashed post state and validate it's empty.
944        let empty_state = HashedPostState::default();
945        assert!(empty_state.is_empty());
946
947        // Add an account and validate the state is no longer empty.
948        let non_empty_state = HashedPostState::default()
949            .with_accounts(vec![(keccak256(Address::random()), Some(Account::default()))]);
950        assert!(!non_empty_state.is_empty());
951    }
952
953    fn create_state_for_multi_proof_targets() -> HashedPostState {
954        let mut state = HashedPostState::default();
955
956        let addr1 = B256::random();
957        let addr2 = B256::random();
958        state.accounts.insert(addr1, Some(Default::default()));
959        state.accounts.insert(addr2, Some(Default::default()));
960
961        let mut storage = HashedStorage::default();
962        let slot1 = B256::random();
963        let slot2 = B256::random();
964        storage.storage.insert(slot1, U256::ZERO);
965        storage.storage.insert(slot2, U256::from(1));
966        state.storages.insert(addr1, storage);
967
968        state
969    }
970
971    #[test]
972    fn test_multi_proof_targets_difference_empty_state() {
973        let state = HashedPostState::default();
974        let excluded = MultiProofTargets::default();
975
976        let targets = state.multi_proof_targets_difference(&excluded);
977        assert!(targets.is_empty());
978    }
979
980    #[test]
981    fn test_multi_proof_targets_difference_new_account_targets() {
982        let state = create_state_for_multi_proof_targets();
983        let excluded = MultiProofTargets::default();
984
985        // should return all accounts as targets since excluded is empty
986        let targets = state.multi_proof_targets_difference(&excluded);
987        assert_eq!(targets.len(), state.accounts.len());
988        for addr in state.accounts.keys() {
989            assert!(targets.contains_key(addr));
990        }
991    }
992
993    #[test]
994    fn test_multi_proof_targets_difference_new_storage_targets() {
995        let state = create_state_for_multi_proof_targets();
996        let excluded = MultiProofTargets::default();
997
998        let targets = state.multi_proof_targets_difference(&excluded);
999
1000        // verify storage slots are included for accounts with storage
1001        for (addr, storage) in &state.storages {
1002            assert!(targets.contains_key(addr));
1003            let target_slots = &targets[addr];
1004            assert_eq!(target_slots.len(), storage.storage.len());
1005            for slot in storage.storage.keys() {
1006                assert!(target_slots.contains(slot));
1007            }
1008        }
1009    }
1010
1011    #[test]
1012    fn test_multi_proof_targets_difference_filter_excluded_accounts() {
1013        let state = create_state_for_multi_proof_targets();
1014        let mut excluded = MultiProofTargets::default();
1015
1016        // select an account that has no storage updates
1017        let excluded_addr = state
1018            .accounts
1019            .keys()
1020            .find(|&&addr| !state.storages.contains_key(&addr))
1021            .expect("Should have an account without storage");
1022
1023        // mark the account as excluded
1024        excluded.insert(*excluded_addr, HashSet::default());
1025
1026        let targets = state.multi_proof_targets_difference(&excluded);
1027
1028        // should not include the already excluded account since it has no storage updates
1029        assert!(!targets.contains_key(excluded_addr));
1030        // other accounts should still be included
1031        assert_eq!(targets.len(), state.accounts.len() - 1);
1032    }
1033
1034    #[test]
1035    fn test_multi_proof_targets_difference_filter_excluded_storage() {
1036        let state = create_state_for_multi_proof_targets();
1037        let mut excluded = MultiProofTargets::default();
1038
1039        // mark one storage slot as excluded
1040        let (addr, storage) = state.storages.iter().next().unwrap();
1041        let mut excluded_slots = HashSet::default();
1042        let excluded_slot = *storage.storage.keys().next().unwrap();
1043        excluded_slots.insert(excluded_slot);
1044        excluded.insert(*addr, excluded_slots);
1045
1046        let targets = state.multi_proof_targets_difference(&excluded);
1047
1048        // should not include the excluded storage slot
1049        let target_slots = &targets[addr];
1050        assert!(!target_slots.contains(&excluded_slot));
1051        assert_eq!(target_slots.len(), storage.storage.len() - 1);
1052    }
1053
1054    #[test]
1055    fn test_multi_proof_targets_difference_mixed_excluded_state() {
1056        let mut state = HashedPostState::default();
1057        let mut excluded = MultiProofTargets::default();
1058
1059        let addr1 = B256::random();
1060        let addr2 = B256::random();
1061        let slot1 = B256::random();
1062        let slot2 = B256::random();
1063
1064        state.accounts.insert(addr1, Some(Default::default()));
1065        state.accounts.insert(addr2, Some(Default::default()));
1066
1067        let mut storage = HashedStorage::default();
1068        storage.storage.insert(slot1, U256::ZERO);
1069        storage.storage.insert(slot2, U256::from(1));
1070        state.storages.insert(addr1, storage);
1071
1072        let mut excluded_slots = HashSet::default();
1073        excluded_slots.insert(slot1);
1074        excluded.insert(addr1, excluded_slots);
1075
1076        let targets = state.multi_proof_targets_difference(&excluded);
1077
1078        assert!(targets.contains_key(&addr2));
1079        assert!(!targets[&addr1].contains(&slot1));
1080        assert!(targets[&addr1].contains(&slot2));
1081    }
1082
1083    #[test]
1084    fn test_multi_proof_targets_difference_unmodified_account_with_storage() {
1085        let mut state = HashedPostState::default();
1086        let excluded = MultiProofTargets::default();
1087
1088        let addr = B256::random();
1089        let slot1 = B256::random();
1090        let slot2 = B256::random();
1091
1092        // don't add the account to state.accounts (simulating unmodified account)
1093        // but add storage updates for this account
1094        let mut storage = HashedStorage::default();
1095        storage.storage.insert(slot1, U256::from(1));
1096        storage.storage.insert(slot2, U256::from(2));
1097        state.storages.insert(addr, storage);
1098
1099        assert!(!state.accounts.contains_key(&addr));
1100        assert!(!excluded.contains_key(&addr));
1101
1102        let targets = state.multi_proof_targets_difference(&excluded);
1103
1104        // verify that we still get the storage slots for the unmodified account
1105        assert!(targets.contains_key(&addr));
1106
1107        let target_slots = &targets[&addr];
1108        assert_eq!(target_slots.len(), 2);
1109        assert!(target_slots.contains(&slot1));
1110        assert!(target_slots.contains(&slot2));
1111    }
1112
1113    #[test]
1114    fn test_partition_by_targets() {
1115        let addr1 = B256::random();
1116        let addr2 = B256::random();
1117        let slot1 = B256::random();
1118        let slot2 = B256::random();
1119
1120        let state = HashedPostState {
1121            accounts: B256Map::from_iter([
1122                (addr1, Some(Default::default())),
1123                (addr2, Some(Default::default())),
1124            ]),
1125            storages: B256Map::from_iter([(
1126                addr1,
1127                HashedStorage {
1128                    wiped: true,
1129                    storage: B256Map::from_iter([(slot1, U256::ZERO), (slot2, U256::from(1))]),
1130                },
1131            )]),
1132        };
1133        let targets = MultiProofTargets::from_iter([(addr1, HashSet::from_iter([slot1]))]);
1134
1135        let (with_targets, without_targets) =
1136            state.partition_by_targets(&targets, &MultiAddedRemovedKeys::new());
1137
1138        assert_eq!(
1139            with_targets,
1140            HashedPostState {
1141                accounts: B256Map::from_iter([(addr1, Some(Default::default()))]),
1142                storages: B256Map::from_iter([(
1143                    addr1,
1144                    HashedStorage {
1145                        wiped: true,
1146                        storage: B256Map::from_iter([(slot1, U256::ZERO)])
1147                    }
1148                )]),
1149            }
1150        );
1151        assert_eq!(
1152            without_targets,
1153            HashedPostState {
1154                accounts: B256Map::from_iter([(addr2, Some(Default::default()))]),
1155                storages: B256Map::from_iter([(
1156                    addr1,
1157                    HashedStorage {
1158                        wiped: false,
1159                        storage: B256Map::from_iter([(slot2, U256::from(1))])
1160                    }
1161                )]),
1162            }
1163        );
1164    }
1165
1166    #[test]
1167    fn test_chunks() {
1168        let addr1 = B256::from([1; 32]);
1169        let addr2 = B256::from([2; 32]);
1170        let slot1 = B256::from([1; 32]);
1171        let slot2 = B256::from([2; 32]);
1172
1173        let state = HashedPostState {
1174            accounts: B256Map::from_iter([
1175                (addr1, Some(Default::default())),
1176                (addr2, Some(Default::default())),
1177            ]),
1178            storages: B256Map::from_iter([(
1179                addr2,
1180                HashedStorage {
1181                    wiped: true,
1182                    storage: B256Map::from_iter([(slot1, U256::ZERO), (slot2, U256::from(1))]),
1183                },
1184            )]),
1185        };
1186
1187        let mut chunks = state.chunks(2);
1188        assert_eq!(
1189            chunks.next(),
1190            Some(HashedPostState {
1191                accounts: B256Map::from_iter([(addr1, Some(Default::default()))]),
1192                storages: B256Map::from_iter([(addr2, HashedStorage::new(true)),])
1193            })
1194        );
1195        assert_eq!(
1196            chunks.next(),
1197            Some(HashedPostState {
1198                accounts: B256Map::default(),
1199                storages: B256Map::from_iter([(
1200                    addr2,
1201                    HashedStorage {
1202                        wiped: false,
1203                        storage: B256Map::from_iter([(slot1, U256::ZERO), (slot2, U256::from(1))]),
1204                    },
1205                )])
1206            })
1207        );
1208        assert_eq!(
1209            chunks.next(),
1210            Some(HashedPostState {
1211                accounts: B256Map::from_iter([(addr2, Some(Default::default()))]),
1212                storages: B256Map::default()
1213            })
1214        );
1215        assert_eq!(chunks.next(), None);
1216    }
1217
1218    #[test]
1219    fn test_hashed_post_state_sorted_extend_ref() {
1220        // Test extending accounts
1221        let mut state1 = HashedPostStateSorted {
1222            accounts: vec![
1223                (B256::from([1; 32]), Some(Account::default())),
1224                (B256::from([3; 32]), Some(Account::default())),
1225                (B256::from([5; 32]), None),
1226            ],
1227            storages: B256Map::default(),
1228        };
1229
1230        let state2 = HashedPostStateSorted {
1231            accounts: vec![
1232                (B256::from([2; 32]), Some(Account::default())),
1233                (B256::from([3; 32]), Some(Account { nonce: 1, ..Default::default() })), /* Override */
1234                (B256::from([4; 32]), Some(Account::default())),
1235                (B256::from([6; 32]), None),
1236            ],
1237            storages: B256Map::default(),
1238        };
1239
1240        state1.extend_ref(&state2);
1241
1242        // Check accounts are merged and sorted
1243        assert_eq!(state1.accounts.len(), 6);
1244        assert_eq!(state1.accounts[0].0, B256::from([1; 32]));
1245        assert_eq!(state1.accounts[1].0, B256::from([2; 32]));
1246        assert_eq!(state1.accounts[2].0, B256::from([3; 32]));
1247        assert_eq!(state1.accounts[2].1.unwrap().nonce, 1); // Should have state2's value
1248        assert_eq!(state1.accounts[3].0, B256::from([4; 32]));
1249        assert_eq!(state1.accounts[4].0, B256::from([5; 32]));
1250        assert_eq!(state1.accounts[4].1, None);
1251        assert_eq!(state1.accounts[5].0, B256::from([6; 32]));
1252        assert_eq!(state1.accounts[5].1, None);
1253    }
1254
1255    #[test]
1256    fn test_hashed_storage_sorted_extend_ref() {
1257        // Test normal extension
1258        let mut storage1 = HashedStorageSorted {
1259            storage_slots: vec![
1260                (B256::from([1; 32]), U256::from(10)),
1261                (B256::from([3; 32]), U256::from(30)),
1262                (B256::from([5; 32]), U256::ZERO),
1263            ],
1264            wiped: false,
1265        };
1266
1267        let storage2 = HashedStorageSorted {
1268            storage_slots: vec![
1269                (B256::from([2; 32]), U256::from(20)),
1270                (B256::from([3; 32]), U256::from(300)), // Override
1271                (B256::from([4; 32]), U256::from(40)),
1272                (B256::from([6; 32]), U256::ZERO),
1273            ],
1274            wiped: false,
1275        };
1276
1277        storage1.extend_ref(&storage2);
1278
1279        assert_eq!(storage1.storage_slots.len(), 6);
1280        assert_eq!(storage1.storage_slots[0].0, B256::from([1; 32]));
1281        assert_eq!(storage1.storage_slots[0].1, U256::from(10));
1282        assert_eq!(storage1.storage_slots[1].0, B256::from([2; 32]));
1283        assert_eq!(storage1.storage_slots[1].1, U256::from(20));
1284        assert_eq!(storage1.storage_slots[2].0, B256::from([3; 32]));
1285        assert_eq!(storage1.storage_slots[2].1, U256::from(300)); // Should have storage2's value
1286        assert_eq!(storage1.storage_slots[3].0, B256::from([4; 32]));
1287        assert_eq!(storage1.storage_slots[3].1, U256::from(40));
1288        assert_eq!(storage1.storage_slots[4].0, B256::from([5; 32]));
1289        assert_eq!(storage1.storage_slots[4].1, U256::ZERO);
1290        assert_eq!(storage1.storage_slots[5].0, B256::from([6; 32]));
1291        assert_eq!(storage1.storage_slots[5].1, U256::ZERO);
1292        assert!(!storage1.wiped);
1293
1294        // Test wiped storage
1295        let mut storage3 = HashedStorageSorted {
1296            storage_slots: vec![
1297                (B256::from([1; 32]), U256::from(10)),
1298                (B256::from([2; 32]), U256::ZERO),
1299            ],
1300            wiped: false,
1301        };
1302
1303        let storage4 = HashedStorageSorted {
1304            storage_slots: vec![
1305                (B256::from([3; 32]), U256::from(30)),
1306                (B256::from([4; 32]), U256::ZERO),
1307            ],
1308            wiped: true,
1309        };
1310
1311        storage3.extend_ref(&storage4);
1312
1313        assert!(storage3.wiped);
1314        // When wiped, should only have storage4's values
1315        assert_eq!(storage3.storage_slots.len(), 2);
1316        assert_eq!(storage3.storage_slots[0].0, B256::from([3; 32]));
1317        assert_eq!(storage3.storage_slots[0].1, U256::from(30));
1318        assert_eq!(storage3.storage_slots[1].0, B256::from([4; 32]));
1319        assert_eq!(storage3.storage_slots[1].1, U256::ZERO);
1320    }
1321
1322    /// Test extending with sorted accounts merges correctly into `HashMap`
1323    #[test]
1324    fn test_hashed_post_state_extend_from_sorted_with_accounts() {
1325        let addr1 = B256::random();
1326        let addr2 = B256::random();
1327
1328        let mut state = HashedPostState::default();
1329        state.accounts.insert(addr1, Some(Default::default()));
1330
1331        let mut sorted_state = HashedPostStateSorted::default();
1332        sorted_state.accounts.push((addr2, Some(Default::default())));
1333
1334        state.extend_from_sorted(&sorted_state);
1335
1336        assert_eq!(state.accounts.len(), 2);
1337        assert!(state.accounts.contains_key(&addr1));
1338        assert!(state.accounts.contains_key(&addr2));
1339    }
1340
1341    /// Test destroyed accounts (None values) are inserted correctly
1342    #[test]
1343    fn test_hashed_post_state_extend_from_sorted_with_destroyed_accounts() {
1344        let addr1 = B256::random();
1345
1346        let mut state = HashedPostState::default();
1347
1348        let mut sorted_state = HashedPostStateSorted::default();
1349        sorted_state.accounts.push((addr1, None));
1350
1351        state.extend_from_sorted(&sorted_state);
1352
1353        assert!(state.accounts.contains_key(&addr1));
1354        assert_eq!(state.accounts.get(&addr1), Some(&None));
1355    }
1356
1357    /// Test non-wiped storage merges both zero and non-zero valued slots
1358    #[test]
1359    fn test_hashed_storage_extend_from_sorted_non_wiped() {
1360        let slot1 = B256::random();
1361        let slot2 = B256::random();
1362        let slot3 = B256::random();
1363
1364        let mut storage = HashedStorage::from_iter(false, [(slot1, U256::from(100))]);
1365
1366        let sorted = HashedStorageSorted {
1367            storage_slots: vec![(slot2, U256::from(200)), (slot3, U256::ZERO)],
1368            wiped: false,
1369        };
1370
1371        storage.extend_from_sorted(&sorted);
1372
1373        assert!(!storage.wiped);
1374        assert_eq!(storage.storage.len(), 3);
1375        assert_eq!(storage.storage.get(&slot1), Some(&U256::from(100)));
1376        assert_eq!(storage.storage.get(&slot2), Some(&U256::from(200)));
1377        assert_eq!(storage.storage.get(&slot3), Some(&U256::ZERO));
1378    }
1379
1380    /// Test wiped=true clears existing storage and only keeps new slots (critical edge case)
1381    #[test]
1382    fn test_hashed_storage_extend_from_sorted_wiped() {
1383        let slot1 = B256::random();
1384        let slot2 = B256::random();
1385
1386        let mut storage = HashedStorage::from_iter(false, [(slot1, U256::from(100))]);
1387
1388        let sorted =
1389            HashedStorageSorted { storage_slots: vec![(slot2, U256::from(200))], wiped: true };
1390
1391        storage.extend_from_sorted(&sorted);
1392
1393        assert!(storage.wiped);
1394        // After wipe, old storage should be cleared and only new storage remains
1395        assert_eq!(storage.storage.len(), 1);
1396        assert_eq!(storage.storage.get(&slot2), Some(&U256::from(200)));
1397    }
1398
1399    #[test]
1400    fn test_hashed_post_state_chunking_length() {
1401        let addr1 = B256::from([1; 32]);
1402        let addr2 = B256::from([2; 32]);
1403        let addr3 = B256::from([3; 32]);
1404        let addr4 = B256::from([4; 32]);
1405        let slot1 = B256::from([1; 32]);
1406        let slot2 = B256::from([2; 32]);
1407        let slot3 = B256::from([3; 32]);
1408
1409        let state = HashedPostState {
1410            accounts: B256Map::from_iter([(addr1, None), (addr2, None), (addr4, None)]),
1411            storages: B256Map::from_iter([
1412                (
1413                    addr1,
1414                    HashedStorage {
1415                        wiped: false,
1416                        storage: B256Map::from_iter([
1417                            (slot1, U256::ZERO),
1418                            (slot2, U256::ZERO),
1419                            (slot3, U256::ZERO),
1420                        ]),
1421                    },
1422                ),
1423                (
1424                    addr2,
1425                    HashedStorage {
1426                        wiped: true,
1427                        storage: B256Map::from_iter([
1428                            (slot1, U256::ZERO),
1429                            (slot2, U256::ZERO),
1430                            (slot3, U256::ZERO),
1431                        ]),
1432                    },
1433                ),
1434                (
1435                    addr3,
1436                    HashedStorage {
1437                        wiped: false,
1438                        storage: B256Map::from_iter([
1439                            (slot1, U256::ZERO),
1440                            (slot2, U256::ZERO),
1441                            (slot3, U256::ZERO),
1442                        ]),
1443                    },
1444                ),
1445            ]),
1446        };
1447
1448        let chunking_length = state.chunking_length();
1449        for size in 1..=state.clone().chunks(1).count() {
1450            let chunk_count = state.clone().chunks(size).count();
1451            let expected_count = chunking_length.div_ceil(size);
1452            assert_eq!(
1453                chunk_count, expected_count,
1454                "chunking_length: {}, size: {}",
1455                chunking_length, size
1456            );
1457        }
1458    }
1459
1460    #[test]
1461    fn test_clone_into_sorted_equivalence() {
1462        let addr1 = B256::from([1; 32]);
1463        let addr2 = B256::from([2; 32]);
1464        let addr3 = B256::from([3; 32]);
1465        let slot1 = B256::from([1; 32]);
1466        let slot2 = B256::from([2; 32]);
1467        let slot3 = B256::from([3; 32]);
1468
1469        let state = HashedPostState {
1470            accounts: B256Map::from_iter([
1471                (addr1, Some(Account { nonce: 1, balance: U256::from(100), bytecode_hash: None })),
1472                (addr2, None),
1473                (addr3, Some(Account::default())),
1474            ]),
1475            storages: B256Map::from_iter([
1476                (
1477                    addr1,
1478                    HashedStorage {
1479                        wiped: false,
1480                        storage: B256Map::from_iter([
1481                            (slot1, U256::from(10)),
1482                            (slot2, U256::from(20)),
1483                        ]),
1484                    },
1485                ),
1486                (
1487                    addr2,
1488                    HashedStorage {
1489                        wiped: true,
1490                        storage: B256Map::from_iter([(slot3, U256::ZERO)]),
1491                    },
1492                ),
1493            ]),
1494        };
1495
1496        // clone_into_sorted should produce the same result as clone().into_sorted()
1497        let sorted_via_clone = state.clone().into_sorted();
1498        let sorted_via_clone_into = state.clone_into_sorted();
1499
1500        assert_eq!(sorted_via_clone, sorted_via_clone_into);
1501
1502        // Verify the original state is not consumed
1503        assert_eq!(state.accounts.len(), 3);
1504        assert_eq!(state.storages.len(), 2);
1505    }
1506
1507    #[test]
1508    fn test_hashed_storage_clone_into_sorted_equivalence() {
1509        let slot1 = B256::from([1; 32]);
1510        let slot2 = B256::from([2; 32]);
1511        let slot3 = B256::from([3; 32]);
1512
1513        let storage = HashedStorage {
1514            wiped: true,
1515            storage: B256Map::from_iter([
1516                (slot1, U256::from(100)),
1517                (slot2, U256::ZERO),
1518                (slot3, U256::from(300)),
1519            ]),
1520        };
1521
1522        // clone_into_sorted should produce the same result as clone().into_sorted()
1523        let sorted_via_clone = storage.clone().into_sorted();
1524        let sorted_via_clone_into = storage.clone_into_sorted();
1525
1526        assert_eq!(sorted_via_clone, sorted_via_clone_into);
1527
1528        // Verify the original storage is not consumed
1529        assert_eq!(storage.storage.len(), 3);
1530        assert!(storage.wiped);
1531    }
1532}