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    SlotChanges, balance_change::BalanceChange, code_change::CodeChange, nonce_change::NonceChange,
7};
8use alloc::vec::Vec;
9use alloy_primitives::{
10    Address, U256,
11    map::{HashMap, HashSet},
12};
13
14/// This struct is used to track the changes across accounts in a block.
15#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
16#[cfg_attr(feature = "rlp", derive(alloy_rlp::RlpEncodable, alloy_rlp::RlpDecodable))]
17#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
18#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
19#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
20#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
21pub struct AccountChanges {
22    /// The address of the account whose changes are stored.
23    pub address: Address,
24    /// List of slot changes for this account.
25    pub storage_changes: Vec<SlotChanges>,
26    /// List of storage reads for this account.
27    pub storage_reads: Vec<U256>,
28    /// List of balance changes for this account.
29    pub balance_changes: Vec<BalanceChange>,
30    /// List of nonce changes for this account.
31    pub nonce_changes: Vec<NonceChange>,
32    /// List of code changes for this account.
33    pub code_changes: Vec<CodeChange>,
34}
35
36impl AccountChanges {
37    /// Creates a new [`AccountChanges`] instance for the given address with empty vectors.
38    pub const fn new(address: Address) -> Self {
39        Self {
40            address,
41            storage_changes: Vec::new(),
42            storage_reads: Vec::new(),
43            balance_changes: Vec::new(),
44            nonce_changes: Vec::new(),
45            code_changes: Vec::new(),
46        }
47    }
48
49    /// Creates a new [`AccountChanges`] instance for the given address with specified capacity.
50    pub fn with_capacity(address: Address, capacity: usize) -> Self {
51        Self {
52            address,
53            storage_changes: Vec::with_capacity(capacity),
54            storage_reads: Vec::with_capacity(capacity),
55            balance_changes: Vec::with_capacity(capacity),
56            nonce_changes: Vec::with_capacity(capacity),
57            code_changes: Vec::with_capacity(capacity),
58        }
59    }
60
61    /// Returns the address of the account.
62    #[inline]
63    pub const fn address(&self) -> Address {
64        self.address
65    }
66
67    /// Returns the storage changes for this account.
68    #[inline]
69    pub fn storage_changes(&self) -> &[SlotChanges] {
70        &self.storage_changes
71    }
72
73    /// Returns an iterator over the post-state value for each changed storage slot.
74    ///
75    /// The post-state value is taken from the last recorded change for each slot.
76    #[inline]
77    pub fn storage_post_states(&self) -> impl Iterator<Item = (U256, U256)> + '_ {
78        self.storage_changes.iter().filter_map(|changes| {
79            changes.changes.last().map(|change| (changes.slot, change.new_value))
80        })
81    }
82
83    /// Merges another account change set into this one.
84    ///
85    /// Storage changes for matching slots are grouped together. Storage reads are normalized after
86    /// merging so that written slots are represented by `storage_changes`, while `storage_reads`
87    /// only contains unique read-only slots in first-seen order. This preserves the EIP-7928
88    /// invariant that a storage slot appears in either reads or changes, but not both, after
89    /// independently valid account change sets are combined.
90    ///
91    /// This preserves relative ordering by appending incoming changes to existing changes. Call
92    /// [`Self::sort`] after merging if canonical EIP-7928 ordering is required.
93    ///
94    /// # Panics
95    ///
96    /// Panics if the two account change sets have different addresses.
97    pub fn merge(&mut self, incoming: Self) {
98        assert_eq!(
99            self.address, incoming.address,
100            "cannot merge account changes for different addresses"
101        );
102
103        merge_slot_changes(&mut self.storage_changes, incoming.storage_changes);
104        self.storage_reads.extend(incoming.storage_reads);
105        self.balance_changes.extend(incoming.balance_changes);
106        self.nonce_changes.extend(incoming.nonce_changes);
107        self.code_changes.extend(incoming.code_changes);
108
109        let written = self
110            .storage_changes
111            .iter()
112            .map(|slot_changes| slot_changes.slot)
113            .collect::<HashSet<_>>();
114        self.storage_reads.retain(|slot| !written.contains(slot));
115
116        let mut seen = HashSet::with_capacity(self.storage_reads.len());
117        self.storage_reads.retain(|slot| seen.insert(*slot));
118    }
119
120    /// Returns the storage reads for this account.
121    #[inline]
122    pub fn storage_reads(&self) -> &[U256] {
123        &self.storage_reads
124    }
125
126    /// Returns the balance changes for this account.
127    #[inline]
128    pub fn balance_changes(&self) -> &[BalanceChange] {
129        &self.balance_changes
130    }
131
132    /// Returns the nonce changes for this account.
133    #[inline]
134    pub fn nonce_changes(&self) -> &[NonceChange] {
135        &self.nonce_changes
136    }
137
138    /// Returns the code changes for this account.
139    #[inline]
140    pub fn code_changes(&self) -> &[CodeChange] {
141        &self.code_changes
142    }
143
144    /// Sorts this account's changes in-place according to the account-local EIP-7928 ordering
145    /// rules.
146    ///
147    /// This applies the account-local ordering required by the "Ordering, Uniqueness and
148    /// Determinism" section of EIP-7928:
149    ///
150    /// - `storage_changes` are sorted lexicographically by storage key
151    /// - each per-slot `StorageChange` list is sorted by block access index in ascending order
152    /// - `storage_reads` are sorted lexicographically by storage key
153    /// - `balance_changes`, `nonce_changes`, and `code_changes` are sorted by block access index in
154    ///   ascending order
155    ///
156    /// Per-slot storage change ordering is delegated to [`SlotChanges::sort`].
157    ///
158    /// This method only canonicalizes ordering for a single account. It does not enforce the
159    /// EIP-7928 uniqueness constraints for storage keys or block access indexes.
160    pub fn sort(&mut self) {
161        self.storage_changes.sort_unstable_by_key(|changes| changes.slot);
162        for slot_changes in &mut self.storage_changes {
163            slot_changes.sort();
164        }
165
166        self.storage_reads.sort_unstable();
167        self.balance_changes.sort_unstable_by_key(|change| change.block_access_index);
168        self.nonce_changes.sort_unstable_by_key(|change| change.block_access_index);
169        self.code_changes.sort_unstable_by_key(|change| change.block_access_index);
170    }
171
172    /// Set the address.
173    pub const fn with_address(mut self, address: Address) -> Self {
174        self.address = address;
175        self
176    }
177
178    /// Add a storage read slot.
179    pub fn with_storage_read(mut self, key: U256) -> Self {
180        self.storage_reads.push(key);
181        self
182    }
183
184    /// Add a storage change (multiple writes to a slot grouped in `SlotChanges`).
185    pub fn with_storage_change(mut self, change: SlotChanges) -> Self {
186        self.storage_changes.push(change);
187        self
188    }
189
190    /// Add a balance change.
191    pub fn with_balance_change(mut self, change: BalanceChange) -> Self {
192        self.balance_changes.push(change);
193        self
194    }
195
196    /// Add a nonce change.
197    pub fn with_nonce_change(mut self, change: NonceChange) -> Self {
198        self.nonce_changes.push(change);
199        self
200    }
201
202    /// Add a code change.
203    pub fn with_code_change(mut self, change: CodeChange) -> Self {
204        self.code_changes.push(change);
205        self
206    }
207
208    /// Add multiple storage reads at once.
209    pub fn extend_storage_reads<I>(mut self, iter: I) -> Self
210    where
211        I: IntoIterator<Item = U256>,
212    {
213        self.storage_reads.extend(iter);
214        self
215    }
216
217    /// Add multiple slot changes at once.
218    pub fn extend_storage_changes<I>(mut self, iter: I) -> Self
219    where
220        I: IntoIterator<Item = SlotChanges>,
221    {
222        self.storage_changes.extend(iter);
223        self
224    }
225}
226
227fn merge_slot_changes(existing: &mut Vec<SlotChanges>, incoming: Vec<SlotChanges>) {
228    let mut slot_positions = existing
229        .iter()
230        .enumerate()
231        .map(|(idx, slot_changes)| (slot_changes.slot, idx))
232        .collect::<HashMap<_, _>>();
233
234    for slot_changes in incoming {
235        if let Some(&idx) = slot_positions.get(&slot_changes.slot) {
236            existing[idx].changes.extend(slot_changes.changes);
237        } else {
238            slot_positions.insert(slot_changes.slot, existing.len());
239            existing.push(slot_changes);
240        }
241    }
242}
243
244#[cfg(test)]
245mod merge_tests {
246    use crate::{BlockAccessIndex, StorageChange};
247
248    use super::*;
249    use alloy_primitives::Bytes;
250
251    #[test]
252    fn merge_groups_slot_changes_and_appends_account_changes() {
253        let address = Address::from([0x11; 20]);
254        let mut existing = AccountChanges {
255            address,
256            storage_changes: vec![SlotChanges::new(
257                U256::from(1),
258                vec![StorageChange::new(BlockAccessIndex::new(0), U256::from(10))],
259            )],
260            storage_reads: vec![U256::from(3)],
261            balance_changes: vec![BalanceChange::new(BlockAccessIndex::new(1), U256::from(100))],
262            nonce_changes: vec![NonceChange::new(BlockAccessIndex::new(2), 7)],
263            code_changes: vec![],
264        };
265        let incoming = AccountChanges {
266            address,
267            storage_changes: vec![
268                SlotChanges::new(
269                    U256::from(1),
270                    vec![StorageChange::new(BlockAccessIndex::new(3), U256::from(20))],
271                ),
272                SlotChanges::new(
273                    U256::from(2),
274                    vec![StorageChange::new(BlockAccessIndex::new(4), U256::from(30))],
275                ),
276            ],
277            storage_reads: vec![U256::from(4)],
278            balance_changes: vec![BalanceChange::new(BlockAccessIndex::new(5), U256::from(150))],
279            nonce_changes: vec![NonceChange::new(BlockAccessIndex::new(6), 8)],
280            code_changes: vec![CodeChange::new(
281                BlockAccessIndex::new(7),
282                Bytes::from_static(&[0xaa]),
283            )],
284        };
285
286        existing.merge(incoming);
287
288        assert_eq!(existing.storage_reads, vec![U256::from(3), U256::from(4)]);
289        assert_eq!(
290            existing.storage_changes.iter().map(|changes| changes.slot).collect::<Vec<_>>(),
291            vec![U256::from(1), U256::from(2)]
292        );
293        assert_eq!(
294            existing.storage_changes[0]
295                .changes
296                .iter()
297                .map(|change| change.new_value)
298                .collect::<Vec<_>>(),
299            vec![U256::from(10), U256::from(20)]
300        );
301        assert_eq!(existing.balance_changes.len(), 2);
302        assert_eq!(existing.nonce_changes.len(), 2);
303        assert_eq!(existing.code_changes.len(), 1);
304    }
305
306    #[test]
307    fn merge_normalizes_storage_reads_after_cross_block_merge() {
308        let address = Address::from([0x33; 20]);
309        const A: U256 = U256::from_limbs([1, 0, 0, 0]);
310        const B: U256 = U256::from_limbs([2, 0, 0, 0]);
311        const C: U256 = U256::from_limbs([3, 0, 0, 0]);
312        const D: U256 = U256::from_limbs([4, 0, 0, 0]);
313
314        let mut existing = AccountChanges {
315            address,
316            storage_changes: vec![SlotChanges::new(
317                A,
318                vec![StorageChange::new(BlockAccessIndex::new(0), U256::from(10))],
319            )],
320            storage_reads: vec![B, C],
321            balance_changes: vec![],
322            nonce_changes: vec![],
323            code_changes: vec![],
324        };
325        let incoming = AccountChanges {
326            address,
327            storage_changes: vec![SlotChanges::new(
328                B,
329                vec![StorageChange::new(BlockAccessIndex::new(1), U256::from(20))],
330            )],
331            storage_reads: vec![A, C, D],
332            balance_changes: vec![],
333            nonce_changes: vec![],
334            code_changes: vec![],
335        };
336
337        existing.merge(incoming);
338
339        assert_eq!(
340            existing
341                .storage_changes
342                .iter()
343                .map(|slot_changes| slot_changes.slot)
344                .collect::<Vec<_>>(),
345            vec![A, B]
346        );
347        assert_eq!(existing.storage_reads, vec![C, D]);
348        assert!(existing.storage_reads.iter().all(|read_slot| {
349            !existing.storage_changes.iter().any(|slot_changes| slot_changes.slot == *read_slot)
350        }));
351    }
352
353    #[test]
354    #[should_panic(expected = "cannot merge account changes for different addresses")]
355    fn merge_rejects_different_addresses() {
356        let mut existing = AccountChanges::new(Address::from([0x11; 20]));
357        let incoming = AccountChanges::new(Address::from([0x22; 20]));
358
359        existing.merge(incoming);
360    }
361}
362
363#[cfg(test)]
364mod sort_tests {
365    use crate::{BlockAccessIndex, StorageChange};
366
367    use super::*;
368    use alloy_primitives::Bytes;
369
370    #[test]
371    fn sort_orders_account_local_eip7928_lists() {
372        let mut account = AccountChanges {
373            address: Address::from([0x11; 20]),
374            storage_changes: vec![
375                SlotChanges::new(
376                    U256::from(3),
377                    vec![
378                        StorageChange::new(BlockAccessIndex::new(8), U256::from(0x80)),
379                        StorageChange::new(BlockAccessIndex::new(2), U256::from(0x20)),
380                    ],
381                ),
382                SlotChanges::new(
383                    U256::from(1),
384                    vec![
385                        StorageChange::new(BlockAccessIndex::new(5), U256::from(0x50)),
386                        StorageChange::new(BlockAccessIndex::new(1), U256::from(0x10)),
387                    ],
388                ),
389            ],
390            storage_reads: vec![U256::from(4), U256::from(2)],
391            balance_changes: vec![
392                BalanceChange::new(BlockAccessIndex::new(6), U256::from(600)),
393                BalanceChange::new(BlockAccessIndex::new(3), U256::from(300)),
394            ],
395            nonce_changes: vec![
396                NonceChange::new(BlockAccessIndex::new(7), 70),
397                NonceChange::new(BlockAccessIndex::new(4), 40),
398            ],
399            code_changes: vec![
400                CodeChange::new(BlockAccessIndex::new(9), Bytes::from_static(&[0x60, 0x09])),
401                CodeChange::new(BlockAccessIndex::new(5), Bytes::from_static(&[0x60, 0x05])),
402            ],
403        };
404
405        account.sort();
406
407        assert_eq!(
408            account.storage_changes.iter().map(|changes| changes.slot).collect::<Vec<_>>(),
409            vec![U256::from(1), U256::from(3)]
410        );
411        assert_eq!(
412            account.storage_changes[0]
413                .changes
414                .iter()
415                .map(|change| change.block_access_index)
416                .collect::<Vec<_>>(),
417            vec![BlockAccessIndex::new(1), BlockAccessIndex::new(5)]
418        );
419        assert_eq!(
420            account.storage_changes[1]
421                .changes
422                .iter()
423                .map(|change| change.block_access_index)
424                .collect::<Vec<_>>(),
425            vec![BlockAccessIndex::new(2), BlockAccessIndex::new(8)]
426        );
427        assert_eq!(account.storage_reads, vec![U256::from(2), U256::from(4)]);
428        assert_eq!(
429            account
430                .balance_changes
431                .iter()
432                .map(|change| change.block_access_index)
433                .collect::<Vec<_>>(),
434            vec![BlockAccessIndex::new(3), BlockAccessIndex::new(6)]
435        );
436        assert_eq!(
437            account
438                .nonce_changes
439                .iter()
440                .map(|change| change.block_access_index)
441                .collect::<Vec<_>>(),
442            vec![BlockAccessIndex::new(4), BlockAccessIndex::new(7)]
443        );
444        assert_eq!(
445            account.code_changes.iter().map(|change| change.block_access_index).collect::<Vec<_>>(),
446            vec![BlockAccessIndex::new(5), BlockAccessIndex::new(9)]
447        );
448    }
449}
450
451#[cfg(test)]
452mod post_state_tests {
453    use crate::{BlockAccessIndex, StorageChange};
454
455    use super::*;
456
457    #[test]
458    fn storage_post_states_yields_last_change_per_slot() {
459        let account = AccountChanges::new(Address::from([0x11; 20]))
460            .with_storage_change(SlotChanges::new(
461                U256::from(1),
462                vec![
463                    StorageChange::new(BlockAccessIndex::new(0), U256::from(0xaa)),
464                    StorageChange::new(BlockAccessIndex::new(2), U256::from(0xbb)),
465                ],
466            ))
467            .with_storage_change(SlotChanges::new(
468                U256::from(3),
469                vec![
470                    StorageChange::new(BlockAccessIndex::new(1), U256::from(0xcc)),
471                    StorageChange::new(BlockAccessIndex::new(3), U256::from(0xdd)),
472                ],
473            ));
474
475        let post_states = account.storage_post_states().collect::<Vec<_>>();
476
477        assert_eq!(
478            post_states,
479            vec![(U256::from(1), U256::from(0xbb)), (U256::from(3), U256::from(0xdd))]
480        );
481    }
482}
483
484#[cfg(all(test, feature = "serde"))]
485mod tests {
486    use crate::{BlockAccessIndex, StorageChange};
487
488    use super::*;
489    use alloy_primitives::Bytes;
490    use serde_json;
491
492    #[test]
493    fn test_account_changes_serde() {
494        let acc = AccountChanges {
495            address: Address::from([0x11; 20]),
496            storage_changes: vec![SlotChanges {
497                slot: U256::from(1),
498                changes: vec![StorageChange {
499                    block_access_index: BlockAccessIndex::new(0),
500                    new_value: U256::from(100),
501                }],
502            }],
503            storage_reads: vec![U256::from(2)],
504            balance_changes: vec![BalanceChange {
505                block_access_index: BlockAccessIndex::new(1),
506                post_balance: U256::from(1000),
507            }],
508            nonce_changes: vec![NonceChange {
509                block_access_index: BlockAccessIndex::new(2),
510                new_nonce: 42,
511            }],
512            code_changes: vec![CodeChange {
513                block_access_index: BlockAccessIndex::new(3),
514                new_code: Bytes::from(vec![0x60, 0x00]),
515            }],
516        };
517
518        let json = serde_json::to_string(&acc).unwrap();
519        let decoded: AccountChanges = serde_json::from_str(&json).unwrap();
520
521        assert_eq!(acc, decoded);
522    }
523
524    #[test]
525    fn test_vec_account_changes_serde() {
526        let acc1 = AccountChanges::new(Address::from([0x11; 20]))
527            .with_storage_read(U256::from(1))
528            .with_balance_change(BalanceChange {
529                block_access_index: BlockAccessIndex::new(0),
530                post_balance: U256::from(100),
531            });
532
533        let acc2 = AccountChanges::new(Address::from([0x22; 20]))
534            .with_storage_change(SlotChanges {
535                slot: U256::from(2),
536                changes: vec![StorageChange {
537                    block_access_index: BlockAccessIndex::new(1),
538                    new_value: U256::from(200),
539                }],
540            })
541            .with_nonce_change(NonceChange {
542                block_access_index: BlockAccessIndex::new(2),
543                new_nonce: 42,
544            });
545
546        let acc3 = AccountChanges::new(Address::from([0x33; 20])).with_code_change(CodeChange {
547            block_access_index: BlockAccessIndex::new(3),
548            new_code: Bytes::from(vec![0x60, 0x00]),
549        });
550
551        let vec_acc = vec![acc1, acc2, acc3];
552
553        let json = serde_json::to_string(&vec_acc).unwrap();
554        let decoded: Vec<AccountChanges> = serde_json::from_str(&json).unwrap();
555
556        assert_eq!(vec_acc, decoded);
557    }
558}