Skip to main content

alloy_eip7928/
account_changes.rs

1//! Contains the [`AccountChanges`] struct, which represents storage writes, balance, nonce, code
2//! changes and read for the account. All changes for a single account, grouped by field type.
3//! This eliminates address redundancy across different change types.
4
5use crate::{
6    BalAccountInfo, BlockAccessIndex, SlotChanges, balance_change::BalanceChange,
7    code_change::CodeChange, nonce_change::NonceChange,
8};
9use alloc::vec::Vec;
10use alloy_primitives::{
11    Address, B256, Bytes, KECCAK256_EMPTY, U256, keccak256,
12    map::{HashMap, HashSet},
13};
14
15/// This struct is used to track the changes across accounts in a block.
16#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
17#[cfg_attr(feature = "rlp", derive(alloy_rlp::RlpEncodable, alloy_rlp::RlpDecodable))]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
20#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
21#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
22pub struct AccountChanges {
23    /// The address of the account whose changes are stored.
24    pub address: Address,
25    /// List of slot changes for this account.
26    pub storage_changes: Vec<SlotChanges>,
27    /// List of storage reads for this account.
28    pub storage_reads: Vec<U256>,
29    /// List of balance changes for this account.
30    pub balance_changes: Vec<BalanceChange>,
31    /// List of nonce changes for this account.
32    pub nonce_changes: Vec<NonceChange>,
33    /// List of code changes for this account.
34    pub code_changes: Vec<CodeChange>,
35}
36
37impl AccountChanges {
38    /// Creates a new [`AccountChanges`] instance for the given address with empty vectors.
39    pub const fn new(address: Address) -> Self {
40        Self {
41            address,
42            storage_changes: Vec::new(),
43            storage_reads: Vec::new(),
44            balance_changes: Vec::new(),
45            nonce_changes: Vec::new(),
46            code_changes: Vec::new(),
47        }
48    }
49
50    /// Creates a new [`AccountChanges`] instance for the given address with specified capacity.
51    pub fn with_capacity(address: Address, capacity: usize) -> Self {
52        Self {
53            address,
54            storage_changes: Vec::with_capacity(capacity),
55            storage_reads: Vec::with_capacity(capacity),
56            balance_changes: Vec::with_capacity(capacity),
57            nonce_changes: Vec::with_capacity(capacity),
58            code_changes: Vec::with_capacity(capacity),
59        }
60    }
61
62    /// Returns the address of the account.
63    #[inline]
64    pub const fn address(&self) -> Address {
65        self.address
66    }
67
68    /// Returns `true` if this account change set contains no changes or reads.
69    ///
70    /// A [`SlotChanges`] entry with an empty change list does not count as data, so an entry
71    /// that only carries the address (or empty slot entries) is considered empty.
72    pub fn is_empty(&self) -> bool {
73        let Self {
74            address: _,
75            storage_changes,
76            storage_reads,
77            balance_changes,
78            nonce_changes,
79            code_changes,
80        } = self;
81        storage_changes.iter().all(SlotChanges::is_empty)
82            && storage_reads.is_empty()
83            && balance_changes.is_empty()
84            && nonce_changes.is_empty()
85            && code_changes.is_empty()
86    }
87
88    /// Returns the storage changes for this account.
89    #[inline]
90    pub fn storage_changes(&self) -> &[SlotChanges] {
91        &self.storage_changes
92    }
93
94    /// Returns an iterator over storage slots present in this account's changes and reads.
95    ///
96    /// Changed slots are yielded first, followed by read slots.
97    #[inline]
98    pub fn storage_slots(&self) -> impl Iterator<Item = U256> + '_ {
99        self.storage_changes
100            .iter()
101            .map(|changes| changes.slot)
102            .chain(self.storage_reads.iter().copied())
103    }
104
105    /// Returns an iterator over the post-state value for each changed storage slot.
106    ///
107    /// The post-state value is taken from the last recorded change for each slot.
108    #[inline]
109    pub fn storage_post_states(&self) -> impl Iterator<Item = (U256, U256)> + '_ {
110        self.storage_changes.iter().filter_map(|changes| {
111            changes.changes.last().map(|change| (changes.slot, change.new_value))
112        })
113    }
114
115    /// Returns the balance from the last recorded change, or `None` if unchanged.
116    #[inline]
117    pub fn balance_post_state(&self) -> Option<U256> {
118        self.balance_changes.last().map(|change| change.post_balance)
119    }
120
121    /// Returns the nonce from the last recorded change, or `None` if unchanged.
122    #[inline]
123    pub fn nonce_post_state(&self) -> Option<u64> {
124        self.nonce_changes.last().map(|change| change.new_nonce)
125    }
126
127    /// Returns the code from the last recorded change, or `None` if unchanged.
128    #[inline]
129    pub fn code_post_state(&self) -> Option<&Bytes> {
130        self.code_changes.last().map(|change| &change.new_code)
131    }
132
133    /// Returns the hash of the code from the last recorded change, or `None` if unchanged.
134    ///
135    /// [`KECCAK256_EMPTY`] is returned when the code was set to empty.
136    #[inline]
137    pub fn code_hash_post_state(&self) -> Option<B256> {
138        self.code_post_state().map(|code| code_hash(code))
139    }
140
141    /// Returns the code from the last recorded change together with its hash, or `None` if
142    /// unchanged.
143    ///
144    /// Use this over hashing [`Self::code_post_state`] separately when both the code and its hash
145    /// are needed, for example when storing the deployed bytecode by hash.
146    #[inline]
147    pub fn code_post_state_with_hash(&self) -> Option<(B256, &Bytes)> {
148        self.code_post_state().map(|code| (code_hash(code), code))
149    }
150
151    /// Returns the account-level fields this entry changed, see [`BalAccountInfo`].
152    #[inline]
153    pub fn account_info(&self) -> BalAccountInfo {
154        BalAccountInfo::from_changes(self)
155    }
156
157    /// Returns `true` if this entry writes at least one storage slot.
158    ///
159    /// [`SlotChanges`] entries without changes are ignored, mirroring [`Self::is_empty`].
160    pub fn has_storage_changes(&self) -> bool {
161        self.storage_changes.iter().any(|changes| !changes.is_empty())
162    }
163
164    /// Returns `true` if this entry records at least one state change.
165    ///
166    /// Entries that only record reads leave the account untouched and do not contribute to the
167    /// block's post-state.
168    pub fn has_changes(&self) -> bool {
169        !self.balance_changes.is_empty()
170            || !self.nonce_changes.is_empty()
171            || !self.code_changes.is_empty()
172            || self.has_storage_changes()
173    }
174
175    /// Merges another account change set into this one.
176    ///
177    /// Storage changes for matching slots are grouped together. Storage reads are normalized after
178    /// merging so that written slots are represented by `storage_changes`, while `storage_reads`
179    /// only contains unique read-only slots in first-seen order. This preserves the EIP-7928
180    /// invariant that a storage slot appears in either reads or changes, but not both, after
181    /// independently valid account change sets are combined.
182    ///
183    /// This preserves relative ordering by appending incoming changes to existing changes. Call
184    /// [`Self::sort`] after merging if canonical EIP-7928 ordering is required.
185    ///
186    /// # Panics
187    ///
188    /// Panics if the two account change sets have different addresses.
189    pub fn merge(&mut self, incoming: Self) {
190        assert_eq!(
191            self.address, incoming.address,
192            "cannot merge account changes for different addresses"
193        );
194
195        merge_slot_changes(&mut self.storage_changes, incoming.storage_changes);
196        self.storage_reads.extend(incoming.storage_reads);
197        self.balance_changes.extend(incoming.balance_changes);
198        self.nonce_changes.extend(incoming.nonce_changes);
199        self.code_changes.extend(incoming.code_changes);
200
201        let written = self
202            .storage_changes
203            .iter()
204            .map(|slot_changes| slot_changes.slot)
205            .collect::<HashSet<_>>();
206        self.storage_reads.retain(|slot| !written.contains(slot));
207
208        let mut seen = HashSet::with_capacity(self.storage_reads.len());
209        self.storage_reads.retain(|slot| seen.insert(*slot));
210    }
211
212    /// Returns the storage reads for this account.
213    #[inline]
214    pub fn storage_reads(&self) -> &[U256] {
215        &self.storage_reads
216    }
217
218    /// Returns the balance changes for this account.
219    #[inline]
220    pub fn balance_changes(&self) -> &[BalanceChange] {
221        &self.balance_changes
222    }
223
224    /// Returns the nonce changes for this account.
225    #[inline]
226    pub fn nonce_changes(&self) -> &[NonceChange] {
227        &self.nonce_changes
228    }
229
230    /// Returns the code changes for this account.
231    #[inline]
232    pub fn code_changes(&self) -> &[CodeChange] {
233        &self.code_changes
234    }
235
236    /// Sorts this account's changes in-place according to the account-local EIP-7928 ordering
237    /// rules.
238    ///
239    /// This applies the account-local ordering required by the "Ordering, Uniqueness and
240    /// Determinism" section of EIP-7928:
241    ///
242    /// - `storage_changes` are sorted lexicographically by storage key
243    /// - each per-slot `StorageChange` list is sorted by block access index in ascending order
244    /// - `storage_reads` are sorted lexicographically by storage key
245    /// - `balance_changes`, `nonce_changes`, and `code_changes` are sorted by block access index in
246    ///   ascending order
247    ///
248    /// Per-slot storage change ordering is delegated to [`SlotChanges::sort`].
249    ///
250    /// This method only canonicalizes ordering for a single account. It does not enforce the
251    /// EIP-7928 uniqueness constraints for storage keys or block access indexes.
252    pub fn sort(&mut self) {
253        self.storage_changes.sort_unstable_by_key(|changes| changes.slot);
254        for slot_changes in &mut self.storage_changes {
255            slot_changes.sort();
256        }
257
258        self.storage_reads.sort_unstable();
259        self.balance_changes.sort_unstable_by_key(|change| change.block_access_index);
260        self.nonce_changes.sort_unstable_by_key(|change| change.block_access_index);
261        self.code_changes.sort_unstable_by_key(|change| change.block_access_index);
262    }
263
264    /// Renormalizes this account change set in place.
265    ///
266    /// Empty [`SlotChanges`] entries are dropped, duplicate slot entries are folded together
267    /// (preserving the relative order of their changes), and storage reads are deduplicated and
268    /// pruned against written slots, restoring the EIP-7928 invariant that a slot appears in
269    /// either reads or changes but not both.
270    pub fn normalize(&mut self) {
271        self.storage_changes.retain(|slot_changes| !slot_changes.is_empty());
272        let incoming = core::mem::replace(self, Self::new(self.address));
273        self.merge(incoming);
274    }
275
276    /// Collapses each change list to its last entry and assigns that entry to `at`.
277    ///
278    /// The last entry of each list is treated as the effective value ("last write wins"),
279    /// matching [`Self::storage_post_states`]. Storage reads carry no block access index and are
280    /// left untouched.
281    pub fn collapse_changes_at(&mut self, at: BlockAccessIndex) {
282        let Self {
283            address: _,
284            storage_changes,
285            storage_reads: _,
286            balance_changes,
287            nonce_changes,
288            code_changes,
289        } = self;
290        for slot_changes in storage_changes.iter_mut() {
291            keep_last(&mut slot_changes.changes, |change| change.block_access_index = at);
292        }
293        keep_last(balance_changes, |change| change.block_access_index = at);
294        keep_last(nonce_changes, |change| change.block_access_index = at);
295        keep_last(code_changes, |change| change.block_access_index = at);
296    }
297
298    /// Shifts every recorded block access index at or after `from` forward by one.
299    ///
300    /// Indices saturate at `u64::MAX` instead of overflowing.
301    pub fn shift_indices_from(&mut self, from: BlockAccessIndex) {
302        let Self {
303            address: _,
304            storage_changes,
305            storage_reads: _,
306            balance_changes,
307            nonce_changes,
308            code_changes,
309        } = self;
310        let storage = storage_changes
311            .iter_mut()
312            .flat_map(|slot_changes| slot_changes.changes.iter_mut())
313            .map(|change| &mut change.block_access_index);
314        let balances = balance_changes.iter_mut().map(|change| &mut change.block_access_index);
315        let nonces = nonce_changes.iter_mut().map(|change| &mut change.block_access_index);
316        let codes = code_changes.iter_mut().map(|change| &mut change.block_access_index);
317        for index in storage.chain(balances).chain(nonces).chain(codes) {
318            if *index >= from {
319                index.saturating_increment();
320            }
321        }
322    }
323
324    /// Set the address.
325    pub const fn with_address(mut self, address: Address) -> Self {
326        self.address = address;
327        self
328    }
329
330    /// Add a storage read slot.
331    pub fn with_storage_read(mut self, key: U256) -> Self {
332        self.storage_reads.push(key);
333        self
334    }
335
336    /// Add a storage change (multiple writes to a slot grouped in `SlotChanges`).
337    pub fn with_storage_change(mut self, change: SlotChanges) -> Self {
338        self.storage_changes.push(change);
339        self
340    }
341
342    /// Add a balance change.
343    pub fn with_balance_change(mut self, change: BalanceChange) -> Self {
344        self.balance_changes.push(change);
345        self
346    }
347
348    /// Add a nonce change.
349    pub fn with_nonce_change(mut self, change: NonceChange) -> Self {
350        self.nonce_changes.push(change);
351        self
352    }
353
354    /// Add a code change.
355    pub fn with_code_change(mut self, change: CodeChange) -> Self {
356        self.code_changes.push(change);
357        self
358    }
359
360    /// Add multiple storage reads at once.
361    pub fn extend_storage_reads<I>(mut self, iter: I) -> Self
362    where
363        I: IntoIterator<Item = U256>,
364    {
365        self.storage_reads.extend(iter);
366        self
367    }
368
369    /// Add multiple slot changes at once.
370    pub fn extend_storage_changes<I>(mut self, iter: I) -> Self
371    where
372        I: IntoIterator<Item = SlotChanges>,
373    {
374        self.storage_changes.extend(iter);
375        self
376    }
377}
378
379/// Hashes the given code, avoiding the hash of the empty code.
380fn code_hash(code: &[u8]) -> B256 {
381    if code.is_empty() { KECCAK256_EMPTY } else { keccak256(code) }
382}
383
384/// Keeps only the last entry of the list, applying `stamp` to it.
385fn keep_last<T>(changes: &mut Vec<T>, stamp: impl FnOnce(&mut T)) {
386    if let Some(mut change) = changes.pop() {
387        stamp(&mut change);
388        changes.clear();
389        changes.push(change);
390    }
391}
392
393fn merge_slot_changes(existing: &mut Vec<SlotChanges>, incoming: Vec<SlotChanges>) {
394    let mut slot_positions = existing
395        .iter()
396        .enumerate()
397        .map(|(idx, slot_changes)| (slot_changes.slot, idx))
398        .collect::<HashMap<_, _>>();
399
400    for slot_changes in incoming {
401        if let Some(&idx) = slot_positions.get(&slot_changes.slot) {
402            existing[idx].changes.extend(slot_changes.changes);
403        } else {
404            slot_positions.insert(slot_changes.slot, existing.len());
405            existing.push(slot_changes);
406        }
407    }
408}
409
410#[cfg(test)]
411mod merge_tests {
412    use crate::{BlockAccessIndex, StorageChange};
413
414    use super::*;
415    use alloy_primitives::Bytes;
416
417    #[test]
418    fn merge_groups_slot_changes_and_appends_account_changes() {
419        let address = Address::from([0x11; 20]);
420        let mut existing = AccountChanges {
421            address,
422            storage_changes: vec![SlotChanges::new(
423                U256::from(1),
424                vec![StorageChange::new(BlockAccessIndex::new(0), U256::from(10))],
425            )],
426            storage_reads: vec![U256::from(3)],
427            balance_changes: vec![BalanceChange::new(BlockAccessIndex::new(1), U256::from(100))],
428            nonce_changes: vec![NonceChange::new(BlockAccessIndex::new(2), 7)],
429            code_changes: vec![],
430        };
431        let incoming = AccountChanges {
432            address,
433            storage_changes: vec![
434                SlotChanges::new(
435                    U256::from(1),
436                    vec![StorageChange::new(BlockAccessIndex::new(3), U256::from(20))],
437                ),
438                SlotChanges::new(
439                    U256::from(2),
440                    vec![StorageChange::new(BlockAccessIndex::new(4), U256::from(30))],
441                ),
442            ],
443            storage_reads: vec![U256::from(4)],
444            balance_changes: vec![BalanceChange::new(BlockAccessIndex::new(5), U256::from(150))],
445            nonce_changes: vec![NonceChange::new(BlockAccessIndex::new(6), 8)],
446            code_changes: vec![CodeChange::new(
447                BlockAccessIndex::new(7),
448                Bytes::from_static(&[0xaa]),
449            )],
450        };
451
452        existing.merge(incoming);
453
454        assert_eq!(existing.storage_reads, vec![U256::from(3), U256::from(4)]);
455        assert_eq!(
456            existing.storage_changes.iter().map(|changes| changes.slot).collect::<Vec<_>>(),
457            vec![U256::from(1), U256::from(2)]
458        );
459        assert_eq!(
460            existing.storage_changes[0]
461                .changes
462                .iter()
463                .map(|change| change.new_value)
464                .collect::<Vec<_>>(),
465            vec![U256::from(10), U256::from(20)]
466        );
467        assert_eq!(existing.balance_changes.len(), 2);
468        assert_eq!(existing.nonce_changes.len(), 2);
469        assert_eq!(existing.code_changes.len(), 1);
470    }
471
472    #[test]
473    fn merge_normalizes_storage_reads_after_cross_block_merge() {
474        let address = Address::from([0x33; 20]);
475        const A: U256 = U256::from_limbs([1, 0, 0, 0]);
476        const B: U256 = U256::from_limbs([2, 0, 0, 0]);
477        const C: U256 = U256::from_limbs([3, 0, 0, 0]);
478        const D: U256 = U256::from_limbs([4, 0, 0, 0]);
479
480        let mut existing = AccountChanges {
481            address,
482            storage_changes: vec![SlotChanges::new(
483                A,
484                vec![StorageChange::new(BlockAccessIndex::new(0), U256::from(10))],
485            )],
486            storage_reads: vec![B, C],
487            balance_changes: vec![],
488            nonce_changes: vec![],
489            code_changes: vec![],
490        };
491        let incoming = AccountChanges {
492            address,
493            storage_changes: vec![SlotChanges::new(
494                B,
495                vec![StorageChange::new(BlockAccessIndex::new(1), U256::from(20))],
496            )],
497            storage_reads: vec![A, C, D],
498            balance_changes: vec![],
499            nonce_changes: vec![],
500            code_changes: vec![],
501        };
502
503        existing.merge(incoming);
504
505        assert_eq!(
506            existing
507                .storage_changes
508                .iter()
509                .map(|slot_changes| slot_changes.slot)
510                .collect::<Vec<_>>(),
511            vec![A, B]
512        );
513        assert_eq!(existing.storage_reads, vec![C, D]);
514        assert!(existing.storage_reads.iter().all(|read_slot| {
515            !existing.storage_changes.iter().any(|slot_changes| slot_changes.slot == *read_slot)
516        }));
517    }
518
519    #[test]
520    #[should_panic(expected = "cannot merge account changes for different addresses")]
521    fn merge_rejects_different_addresses() {
522        let mut existing = AccountChanges::new(Address::from([0x11; 20]));
523        let incoming = AccountChanges::new(Address::from([0x22; 20]));
524
525        existing.merge(incoming);
526    }
527}
528
529#[cfg(test)]
530mod sort_tests {
531    use crate::{BlockAccessIndex, StorageChange};
532
533    use super::*;
534    use alloy_primitives::Bytes;
535
536    #[test]
537    fn sort_orders_account_local_eip7928_lists() {
538        let mut account = AccountChanges {
539            address: Address::from([0x11; 20]),
540            storage_changes: vec![
541                SlotChanges::new(
542                    U256::from(3),
543                    vec![
544                        StorageChange::new(BlockAccessIndex::new(8), U256::from(0x80)),
545                        StorageChange::new(BlockAccessIndex::new(2), U256::from(0x20)),
546                    ],
547                ),
548                SlotChanges::new(
549                    U256::from(1),
550                    vec![
551                        StorageChange::new(BlockAccessIndex::new(5), U256::from(0x50)),
552                        StorageChange::new(BlockAccessIndex::new(1), U256::from(0x10)),
553                    ],
554                ),
555            ],
556            storage_reads: vec![U256::from(4), U256::from(2)],
557            balance_changes: vec![
558                BalanceChange::new(BlockAccessIndex::new(6), U256::from(600)),
559                BalanceChange::new(BlockAccessIndex::new(3), U256::from(300)),
560            ],
561            nonce_changes: vec![
562                NonceChange::new(BlockAccessIndex::new(7), 70),
563                NonceChange::new(BlockAccessIndex::new(4), 40),
564            ],
565            code_changes: vec![
566                CodeChange::new(BlockAccessIndex::new(9), Bytes::from_static(&[0x60, 0x09])),
567                CodeChange::new(BlockAccessIndex::new(5), Bytes::from_static(&[0x60, 0x05])),
568            ],
569        };
570
571        account.sort();
572
573        assert_eq!(
574            account.storage_changes.iter().map(|changes| changes.slot).collect::<Vec<_>>(),
575            vec![U256::from(1), U256::from(3)]
576        );
577        assert_eq!(
578            account.storage_changes[0]
579                .changes
580                .iter()
581                .map(|change| change.block_access_index)
582                .collect::<Vec<_>>(),
583            vec![BlockAccessIndex::new(1), BlockAccessIndex::new(5)]
584        );
585        assert_eq!(
586            account.storage_changes[1]
587                .changes
588                .iter()
589                .map(|change| change.block_access_index)
590                .collect::<Vec<_>>(),
591            vec![BlockAccessIndex::new(2), BlockAccessIndex::new(8)]
592        );
593        assert_eq!(account.storage_reads, vec![U256::from(2), U256::from(4)]);
594        assert_eq!(
595            account
596                .balance_changes
597                .iter()
598                .map(|change| change.block_access_index)
599                .collect::<Vec<_>>(),
600            vec![BlockAccessIndex::new(3), BlockAccessIndex::new(6)]
601        );
602        assert_eq!(
603            account
604                .nonce_changes
605                .iter()
606                .map(|change| change.block_access_index)
607                .collect::<Vec<_>>(),
608            vec![BlockAccessIndex::new(4), BlockAccessIndex::new(7)]
609        );
610        assert_eq!(
611            account.code_changes.iter().map(|change| change.block_access_index).collect::<Vec<_>>(),
612            vec![BlockAccessIndex::new(5), BlockAccessIndex::new(9)]
613        );
614    }
615}
616
617#[cfg(test)]
618mod post_state_tests {
619    use crate::{BlockAccessIndex, StorageChange};
620
621    use super::*;
622
623    #[test]
624    fn account_post_states_are_absent_for_unchanged_fields() {
625        let account = AccountChanges::new(Address::ZERO)
626            .with_storage_read(U256::from(1))
627            .with_storage_change(SlotChanges::new(
628                U256::from(2),
629                vec![StorageChange::new(BlockAccessIndex::new(1), U256::from(3))],
630            ));
631
632        assert_eq!(account.balance_post_state(), None);
633        assert_eq!(account.nonce_post_state(), None);
634        assert_eq!(account.code_post_state(), None);
635    }
636
637    #[test]
638    fn account_post_states_use_last_recorded_change() {
639        for indices in [&[0][..], &[0, 1, 2][..], &[2, 1, 0][..]] {
640            let mut account = AccountChanges::new(Address::ZERO);
641            for (position, &index) in indices.iter().enumerate() {
642                let index = BlockAccessIndex::new(index);
643                let value = (position + 1) as u64;
644                account.balance_changes.push(BalanceChange::new(index, U256::from(value)));
645                account.nonce_changes.push(NonceChange::new(index, value));
646                account.code_changes.push(CodeChange::new(index, Bytes::from(vec![value as u8])));
647            }
648
649            let expected = indices.len() as u64;
650            assert_eq!(account.balance_post_state(), Some(U256::from(expected)));
651            assert_eq!(account.nonce_post_state(), Some(expected));
652            assert_eq!(account.code_post_state(), Some(&Bytes::from(vec![expected as u8])));
653        }
654    }
655
656    #[test]
657    fn account_post_states_preserve_zero_values_and_cleared_code() {
658        let mut account = AccountChanges::new(Address::ZERO)
659            .with_balance_change(BalanceChange::new(BlockAccessIndex::new(0), U256::from(10)))
660            .with_nonce_change(NonceChange::new(BlockAccessIndex::new(0), 1))
661            .with_code_change(CodeChange::new(
662                BlockAccessIndex::new(0),
663                Bytes::from_static(&[0x60]),
664            ));
665        let index = BlockAccessIndex::new(1);
666        account.balance_changes.push(BalanceChange::new(index, U256::ZERO));
667        account.nonce_changes.push(NonceChange::new(index, 0));
668        account.code_changes.push(CodeChange::new(index, Bytes::new()));
669
670        assert_eq!(account.balance_post_state(), Some(U256::ZERO));
671        assert_eq!(account.nonce_post_state(), Some(0));
672        assert_eq!(account.code_post_state(), Some(&Bytes::new()));
673    }
674
675    #[test]
676    fn storage_post_states_yields_last_change_per_slot() {
677        let account = AccountChanges::new(Address::from([0x11; 20]))
678            .with_storage_change(SlotChanges::new(
679                U256::from(1),
680                vec![
681                    StorageChange::new(BlockAccessIndex::new(0), U256::from(0xaa)),
682                    StorageChange::new(BlockAccessIndex::new(2), U256::from(0xbb)),
683                ],
684            ))
685            .with_storage_change(SlotChanges::new(
686                U256::from(3),
687                vec![
688                    StorageChange::new(BlockAccessIndex::new(1), U256::from(0xcc)),
689                    StorageChange::new(BlockAccessIndex::new(3), U256::from(0xdd)),
690                ],
691            ));
692
693        let post_states = account.storage_post_states().collect::<Vec<_>>();
694
695        assert_eq!(
696            post_states,
697            vec![(U256::from(1), U256::from(0xbb)), (U256::from(3), U256::from(0xdd))]
698        );
699    }
700
701    #[test]
702    fn code_post_state_hash_matches_the_last_recorded_code() {
703        let code = Bytes::from_static(&[0x60, 0x00, 0x56]);
704        let account = AccountChanges::new(Address::ZERO)
705            .with_code_change(CodeChange::new(
706                BlockAccessIndex::new(0),
707                Bytes::from_static(&[0x00]),
708            ))
709            .with_code_change(CodeChange::new(BlockAccessIndex::new(1), code.clone()));
710
711        assert_eq!(account.code_hash_post_state(), Some(keccak256(&code)));
712        assert_eq!(account.code_post_state_with_hash(), Some((keccak256(&code), &code)));
713    }
714
715    #[test]
716    fn cleared_code_post_state_hashes_to_the_empty_code_hash() {
717        let account = AccountChanges::new(Address::ZERO)
718            .with_code_change(CodeChange::new(BlockAccessIndex::new(0), Bytes::new()));
719
720        assert_eq!(account.code_hash_post_state(), Some(KECCAK256_EMPTY));
721        assert_eq!(account.code_post_state_with_hash(), Some((KECCAK256_EMPTY, &Bytes::new())));
722    }
723
724    #[test]
725    fn unchanged_code_has_no_post_state_hash() {
726        let account = AccountChanges::new(Address::ZERO).with_storage_read(U256::from(1));
727
728        assert_eq!(account.code_hash_post_state(), None);
729        assert_eq!(account.code_post_state_with_hash(), None);
730    }
731}
732
733#[cfg(test)]
734mod has_changes_tests {
735    use super::*;
736    use crate::{BlockAccessIndex, StorageChange};
737
738    #[test]
739    fn read_only_entries_have_no_changes() {
740        let account = AccountChanges::new(Address::ZERO).with_storage_read(U256::from(1));
741
742        assert!(!account.has_changes());
743        assert!(!account.is_empty());
744        assert!(account.account_info().is_empty());
745    }
746
747    #[test]
748    fn empty_slot_entries_have_no_changes() {
749        let account = AccountChanges::new(Address::ZERO)
750            .with_storage_change(SlotChanges::new(U256::from(1), Vec::new()));
751
752        assert!(!account.has_changes());
753    }
754
755    #[test]
756    fn every_change_kind_counts_as_a_change() {
757        let index = BlockAccessIndex::new(0);
758        let entries = [
759            AccountChanges::new(Address::ZERO).with_storage_change(SlotChanges::new(
760                U256::from(1),
761                vec![StorageChange::new(index, U256::from(2))],
762            )),
763            AccountChanges::new(Address::ZERO)
764                .with_balance_change(BalanceChange::new(index, U256::from(1))),
765            AccountChanges::new(Address::ZERO).with_nonce_change(NonceChange::new(index, 1)),
766            AccountChanges::new(Address::ZERO)
767                .with_code_change(CodeChange::new(index, Bytes::new())),
768        ];
769
770        for account in entries {
771            assert!(account.has_changes());
772        }
773    }
774
775    #[test]
776    fn account_info_matches_the_post_state_accessors() {
777        let account = AccountChanges::new(Address::ZERO)
778            .with_balance_change(BalanceChange::new(BlockAccessIndex::new(0), U256::from(7)));
779
780        assert_eq!(account.account_info(), BalAccountInfo::from_changes(&account));
781        assert_eq!(account.account_info().balance, account.balance_post_state());
782    }
783}
784
785#[cfg(test)]
786mod storage_slots_tests {
787    use crate::{BlockAccessIndex, StorageChange};
788
789    use super::*;
790
791    #[test]
792    fn storage_slots_yields_changed_then_read_slots() {
793        let account = AccountChanges::new(Address::ZERO)
794            .with_storage_change(SlotChanges::new(
795                U256::from(1),
796                vec![StorageChange::new(BlockAccessIndex::new(0), U256::ZERO)],
797            ))
798            .with_storage_change(SlotChanges::new(
799                U256::from(2),
800                vec![StorageChange::new(BlockAccessIndex::new(1), U256::ZERO)],
801            ))
802            .extend_storage_reads([U256::from(3), U256::from(4)]);
803
804        assert_eq!(
805            account.storage_slots().collect::<Vec<_>>(),
806            vec![U256::from(1), U256::from(2), U256::from(3), U256::from(4)]
807        );
808    }
809}
810
811#[cfg(all(test, feature = "serde"))]
812mod tests {
813    use crate::{BlockAccessIndex, BlockAccessList, StorageChange};
814
815    use super::*;
816    use alloy_primitives::Bytes;
817    use serde_json;
818
819    #[test]
820    fn test_account_changes_serde() {
821        let acc = AccountChanges {
822            address: Address::from([0x11; 20]),
823            storage_changes: vec![SlotChanges {
824                slot: U256::from(1),
825                changes: vec![StorageChange {
826                    block_access_index: BlockAccessIndex::new(0),
827                    new_value: U256::from(100),
828                }],
829            }],
830            storage_reads: vec![U256::from(2)],
831            balance_changes: vec![BalanceChange {
832                block_access_index: BlockAccessIndex::new(1),
833                post_balance: U256::from(1000),
834            }],
835            nonce_changes: vec![NonceChange {
836                block_access_index: BlockAccessIndex::new(2),
837                new_nonce: 42,
838            }],
839            code_changes: vec![CodeChange {
840                block_access_index: BlockAccessIndex::new(3),
841                new_code: Bytes::from(vec![0x60, 0x00]),
842            }],
843        };
844
845        let json = serde_json::to_string(&acc).unwrap();
846        let value: serde_json::Value = serde_json::from_str(&json).unwrap();
847        assert_eq!(value["storageChanges"][0]["key"], "0x1");
848        assert_eq!(value["storageChanges"][0]["changes"][0]["value"], "0x64");
849        assert_eq!(value["storageReads"][0], "0x2");
850        assert_eq!(value["balanceChanges"][0]["value"], "0x3e8");
851        assert_eq!(value["nonceChanges"][0]["value"], "0x2a");
852        assert_eq!(value["codeChanges"][0]["code"], "0x6000");
853        let decoded: AccountChanges = serde_json::from_str(&json).unwrap();
854
855        assert_eq!(acc, decoded);
856    }
857
858    #[test]
859    fn test_storage_reads_deserialize_compact_quantities() {
860        let fixture = r#"
861        {
862            "address": "0x1111111111111111111111111111111111111111",
863            "storageChanges": [],
864            "storageReads": [
865                "0x00",
866                "0x01",
867                "0x02"
868            ],
869            "balanceChanges": [],
870            "nonceChanges": [],
871            "codeChanges": []
872        }
873        "#;
874
875        let decoded: AccountChanges = serde_json::from_str(fixture).unwrap();
876        assert_eq!(decoded.storage_reads, vec![U256::ZERO, U256::from(1), U256::from(2)]);
877
878        assert_eq!(
879            serde_json::to_value(decoded).unwrap()["storageReads"],
880            serde_json::json!(["0x0", "0x1", "0x2"])
881        );
882    }
883
884    #[test]
885    fn test_eest_storage_fields_deserialize_compact_quantities() {
886        // Extracted from tests-glamsterdam-devnet@v7.2.0's precompile_warming.json fixture.
887        let fixture = r#"
888        [
889          {
890            "address": "0x0000f90827f1c53a10cb7a02335b175320002935",
891            "storageChanges": [
892              {
893                "slot": "0x01",
894                "slotChanges": [
895                  {
896                    "blockAccessIndex": "0x00",
897                    "postValue": "0x27330b1c525088b9b5ed2ced86b42d53378c8f6b384e8c3897493e851bc026df"
898                  }
899                ]
900              }
901            ],
902            "storageReads": [],
903            "balanceChanges": [],
904            "nonceChanges": [],
905            "codeChanges": []
906          },
907          {
908            "address": "0x000f3df6d732807ef1319fb7b8bb8522d0beac02",
909            "storageChanges": [
910              {
911                "slot": "0x0e22",
912                "slotChanges": [
913                  {
914                    "blockAccessIndex": "0x00",
915                    "postValue": "0x4e20"
916                  }
917                ]
918              }
919            ],
920            "storageReads": ["0x2e21"],
921            "balanceChanges": [],
922            "nonceChanges": [],
923            "codeChanges": []
924          }
925        ]
926        "#;
927
928        let decoded: BlockAccessList = serde_json::from_str(fixture).unwrap();
929        assert_eq!(decoded[0].storage_changes[0].slot, U256::from(1));
930        assert_eq!(decoded[1].storage_changes[0].slot, U256::from(0x0e22));
931        assert_eq!(decoded[1].storage_changes[0].changes[0].new_value, U256::from(0x4e20));
932        assert_eq!(decoded[1].storage_reads, vec![U256::from(0x2e21)]);
933    }
934
935    #[test]
936    fn test_vec_account_changes_serde() {
937        let acc1 = AccountChanges::new(Address::from([0x11; 20]))
938            .with_storage_read(U256::from(1))
939            .with_balance_change(BalanceChange {
940                block_access_index: BlockAccessIndex::new(0),
941                post_balance: U256::from(100),
942            });
943
944        let acc2 = AccountChanges::new(Address::from([0x22; 20]))
945            .with_storage_change(SlotChanges {
946                slot: U256::from(2),
947                changes: vec![StorageChange {
948                    block_access_index: BlockAccessIndex::new(1),
949                    new_value: U256::from(200),
950                }],
951            })
952            .with_nonce_change(NonceChange {
953                block_access_index: BlockAccessIndex::new(2),
954                new_nonce: 42,
955            });
956
957        let acc3 = AccountChanges::new(Address::from([0x33; 20])).with_code_change(CodeChange {
958            block_access_index: BlockAccessIndex::new(3),
959            new_code: Bytes::from(vec![0x60, 0x00]),
960        });
961
962        let vec_acc = vec![acc1, acc2, acc3];
963
964        let json = serde_json::to_string(&vec_acc).unwrap();
965        let decoded: Vec<AccountChanges> = serde_json::from_str(&json).unwrap();
966
967        assert_eq!(vec_acc, decoded);
968    }
969
970    #[test]
971    fn test_block_access_list_serde_roundtrip_from_populated_fixture() {
972        let fixture = r#"
973[
974  {
975    "address": "0x1111111111111111111111111111111111111111",
976    "storageChanges": [
977      {
978        "key": "0x1",
979        "changes": [
980          {
981            "index": "0x1",
982            "value": "0x10"
983          },
984          {
985            "index": "0x2",
986            "value": "0x20"
987          }
988        ]
989      }
990    ],
991    "storageReads": [
992      "0x2"
993    ],
994    "balanceChanges": [
995      {
996        "index": "0x3",
997        "value": "0x3e8"
998      }
999    ],
1000    "nonceChanges": [
1001      {
1002        "index": "0x4",
1003        "value": "0x2a"
1004      }
1005    ],
1006    "codeChanges": [
1007      {
1008        "index": "0x5",
1009        "code": "0x6000"
1010      }
1011    ]
1012  }
1013]
1014"#;
1015
1016        let decoded: BlockAccessList = serde_json::from_str(fixture).unwrap();
1017        let serialized = serde_json::to_string(&decoded).unwrap();
1018        let fixture_value: serde_json::Value = serde_json::from_str(fixture).unwrap();
1019        let serialized_value: serde_json::Value = serde_json::from_str(&serialized).unwrap();
1020
1021        assert!(fixture_value.is_array());
1022        assert_eq!(fixture_value, serialized_value);
1023    }
1024
1025    #[test]
1026    fn test_block_access_list_serde_roundtrip_from_empty_fixture() {
1027        let fixture = r#"
1028[
1029  {
1030    "address": "0x2222222222222222222222222222222222222222",
1031    "storageChanges": [],
1032    "storageReads": [],
1033    "balanceChanges": [],
1034    "nonceChanges": [],
1035    "codeChanges": []
1036  }
1037]
1038"#;
1039
1040        let decoded: BlockAccessList = serde_json::from_str(fixture).unwrap();
1041        let serialized = serde_json::to_string(&decoded).unwrap();
1042        let fixture_value: serde_json::Value = serde_json::from_str(fixture).unwrap();
1043        let serialized_value: serde_json::Value = serde_json::from_str(&serialized).unwrap();
1044
1045        assert!(fixture_value.is_array());
1046        assert_eq!(fixture_value, serialized_value);
1047        assert_eq!(serialized_value[0]["storageChanges"], serde_json::json!([]));
1048        assert_eq!(serialized_value[0]["storageReads"], serde_json::json!([]));
1049        assert_eq!(serialized_value[0]["balanceChanges"], serde_json::json!([]));
1050        assert_eq!(serialized_value[0]["nonceChanges"], serde_json::json!([]));
1051        assert_eq!(serialized_value[0]["codeChanges"], serde_json::json!([]));
1052    }
1053}