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::{Address, U256};
10
11/// This struct is used to track the changes across accounts in a block.
12#[derive(Debug, Clone, Default, PartialEq, Eq)]
13#[cfg_attr(feature = "rlp", derive(alloy_rlp::RlpEncodable, alloy_rlp::RlpDecodable))]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
16#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
17#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
18pub struct AccountChanges {
19    /// The address of the account whoose changes are stored.
20    pub address: Address,
21    /// List of slot changes for this account.
22    pub storage_changes: Vec<SlotChanges>,
23    /// List of storage reads for this account.
24    pub storage_reads: Vec<U256>,
25    /// List of balance changes for this account.
26    pub balance_changes: Vec<BalanceChange>,
27    /// List of nonce changes for this account.
28    pub nonce_changes: Vec<NonceChange>,
29    /// List of code changes for this account.
30    pub code_changes: Vec<CodeChange>,
31}
32
33impl AccountChanges {
34    /// Creates a new [`AccountChanges`] instance for the given address with empty vectors.
35    pub const fn new(address: Address) -> Self {
36        Self {
37            address,
38            storage_changes: Vec::new(),
39            storage_reads: Vec::new(),
40            balance_changes: Vec::new(),
41            nonce_changes: Vec::new(),
42            code_changes: Vec::new(),
43        }
44    }
45
46    /// Creates a new [`AccountChanges`] instance for the given address with specified capacity.
47    pub fn with_capacity(address: Address, capacity: usize) -> Self {
48        Self {
49            address,
50            storage_changes: Vec::with_capacity(capacity),
51            storage_reads: Vec::with_capacity(capacity),
52            balance_changes: Vec::with_capacity(capacity),
53            nonce_changes: Vec::with_capacity(capacity),
54            code_changes: Vec::with_capacity(capacity),
55        }
56    }
57
58    /// Returns the address of the account.
59    #[inline]
60    pub const fn address(&self) -> Address {
61        self.address
62    }
63
64    /// Returns the storage changes for this account.
65    #[inline]
66    pub fn storage_changes(&self) -> &[SlotChanges] {
67        &self.storage_changes
68    }
69
70    /// Returns an iterator over the post-state value for each changed storage slot.
71    ///
72    /// The post-state value is taken from the last recorded change for each slot.
73    #[inline]
74    pub fn storage_post_states(&self) -> impl Iterator<Item = (U256, U256)> + '_ {
75        self.storage_changes.iter().filter_map(|changes| {
76            changes.changes.last().map(|change| (changes.slot, change.new_value))
77        })
78    }
79
80    /// Returns the storage reads for this account.
81    #[inline]
82    pub fn storage_reads(&self) -> &[U256] {
83        &self.storage_reads
84    }
85
86    /// Returns the balance changes for this account.
87    #[inline]
88    pub fn balance_changes(&self) -> &[BalanceChange] {
89        &self.balance_changes
90    }
91
92    /// Returns the nonce changes for this account.
93    #[inline]
94    pub fn nonce_changes(&self) -> &[NonceChange] {
95        &self.nonce_changes
96    }
97
98    /// Returns the code changes for this account.
99    #[inline]
100    pub fn code_changes(&self) -> &[CodeChange] {
101        &self.code_changes
102    }
103
104    /// Sorts this account's changes in-place according to the account-local EIP-7928 ordering
105    /// rules.
106    ///
107    /// This applies the account-local ordering required by the "Ordering, Uniqueness and
108    /// Determinism" section of EIP-7928:
109    ///
110    /// - `storage_changes` are sorted lexicographically by storage key
111    /// - each per-slot `StorageChange` list is sorted by block access index in ascending order
112    /// - `storage_reads` are sorted lexicographically by storage key
113    /// - `balance_changes`, `nonce_changes`, and `code_changes` are sorted by block access index in
114    ///   ascending order
115    ///
116    /// Per-slot storage change ordering is delegated to [`SlotChanges::sort`].
117    ///
118    /// This method only canonicalizes ordering for a single account. It does not enforce the
119    /// EIP-7928 uniqueness constraints for storage keys or block access indexes.
120    pub fn sort(&mut self) {
121        self.storage_changes.sort_unstable_by_key(|changes| changes.slot);
122        for slot_changes in &mut self.storage_changes {
123            slot_changes.sort();
124        }
125
126        self.storage_reads.sort_unstable();
127        self.balance_changes.sort_unstable_by_key(|change| change.block_access_index);
128        self.nonce_changes.sort_unstable_by_key(|change| change.block_access_index);
129        self.code_changes.sort_unstable_by_key(|change| change.block_access_index);
130    }
131
132    /// Set the address.
133    pub const fn with_address(mut self, address: Address) -> Self {
134        self.address = address;
135        self
136    }
137
138    /// Add a storage read slot.
139    pub fn with_storage_read(mut self, key: U256) -> Self {
140        self.storage_reads.push(key);
141        self
142    }
143
144    /// Add a storage change (multiple writes to a slot grouped in `SlotChanges`).
145    pub fn with_storage_change(mut self, change: SlotChanges) -> Self {
146        self.storage_changes.push(change);
147        self
148    }
149
150    /// Add a balance change.
151    pub fn with_balance_change(mut self, change: BalanceChange) -> Self {
152        self.balance_changes.push(change);
153        self
154    }
155
156    /// Add a nonce change.
157    pub fn with_nonce_change(mut self, change: NonceChange) -> Self {
158        self.nonce_changes.push(change);
159        self
160    }
161
162    /// Add a code change.
163    pub fn with_code_change(mut self, change: CodeChange) -> Self {
164        self.code_changes.push(change);
165        self
166    }
167
168    /// Add multiple storage reads at once.
169    pub fn extend_storage_reads<I>(mut self, iter: I) -> Self
170    where
171        I: IntoIterator<Item = U256>,
172    {
173        self.storage_reads.extend(iter);
174        self
175    }
176
177    /// Add multiple slot changes at once.
178    pub fn extend_storage_changes<I>(mut self, iter: I) -> Self
179    where
180        I: IntoIterator<Item = SlotChanges>,
181    {
182        self.storage_changes.extend(iter);
183        self
184    }
185}
186
187#[cfg(test)]
188mod sort_tests {
189    use crate::{BlockAccessIndex, StorageChange};
190
191    use super::*;
192    use alloy_primitives::Bytes;
193
194    #[test]
195    fn sort_orders_account_local_eip7928_lists() {
196        let mut account = AccountChanges {
197            address: Address::from([0x11; 20]),
198            storage_changes: vec![
199                SlotChanges::new(
200                    U256::from(3),
201                    vec![
202                        StorageChange::new(BlockAccessIndex::new(8), U256::from(0x80)),
203                        StorageChange::new(BlockAccessIndex::new(2), U256::from(0x20)),
204                    ],
205                ),
206                SlotChanges::new(
207                    U256::from(1),
208                    vec![
209                        StorageChange::new(BlockAccessIndex::new(5), U256::from(0x50)),
210                        StorageChange::new(BlockAccessIndex::new(1), U256::from(0x10)),
211                    ],
212                ),
213            ],
214            storage_reads: vec![U256::from(4), U256::from(2)],
215            balance_changes: vec![
216                BalanceChange::new(BlockAccessIndex::new(6), U256::from(600)),
217                BalanceChange::new(BlockAccessIndex::new(3), U256::from(300)),
218            ],
219            nonce_changes: vec![
220                NonceChange::new(BlockAccessIndex::new(7), 70),
221                NonceChange::new(BlockAccessIndex::new(4), 40),
222            ],
223            code_changes: vec![
224                CodeChange::new(BlockAccessIndex::new(9), Bytes::from_static(&[0x60, 0x09])),
225                CodeChange::new(BlockAccessIndex::new(5), Bytes::from_static(&[0x60, 0x05])),
226            ],
227        };
228
229        account.sort();
230
231        assert_eq!(
232            account.storage_changes.iter().map(|changes| changes.slot).collect::<Vec<_>>(),
233            vec![U256::from(1), U256::from(3)]
234        );
235        assert_eq!(
236            account.storage_changes[0]
237                .changes
238                .iter()
239                .map(|change| change.block_access_index)
240                .collect::<Vec<_>>(),
241            vec![BlockAccessIndex::new(1), BlockAccessIndex::new(5)]
242        );
243        assert_eq!(
244            account.storage_changes[1]
245                .changes
246                .iter()
247                .map(|change| change.block_access_index)
248                .collect::<Vec<_>>(),
249            vec![BlockAccessIndex::new(2), BlockAccessIndex::new(8)]
250        );
251        assert_eq!(account.storage_reads, vec![U256::from(2), U256::from(4)]);
252        assert_eq!(
253            account
254                .balance_changes
255                .iter()
256                .map(|change| change.block_access_index)
257                .collect::<Vec<_>>(),
258            vec![BlockAccessIndex::new(3), BlockAccessIndex::new(6)]
259        );
260        assert_eq!(
261            account
262                .nonce_changes
263                .iter()
264                .map(|change| change.block_access_index)
265                .collect::<Vec<_>>(),
266            vec![BlockAccessIndex::new(4), BlockAccessIndex::new(7)]
267        );
268        assert_eq!(
269            account.code_changes.iter().map(|change| change.block_access_index).collect::<Vec<_>>(),
270            vec![BlockAccessIndex::new(5), BlockAccessIndex::new(9)]
271        );
272    }
273}
274
275#[cfg(test)]
276mod post_state_tests {
277    use crate::{BlockAccessIndex, StorageChange};
278
279    use super::*;
280
281    #[test]
282    fn storage_post_states_yields_last_change_per_slot() {
283        let account = AccountChanges::new(Address::from([0x11; 20]))
284            .with_storage_change(SlotChanges::new(
285                U256::from(1),
286                vec![
287                    StorageChange::new(BlockAccessIndex::new(0), U256::from(0xaa)),
288                    StorageChange::new(BlockAccessIndex::new(2), U256::from(0xbb)),
289                ],
290            ))
291            .with_storage_change(SlotChanges::new(
292                U256::from(3),
293                vec![
294                    StorageChange::new(BlockAccessIndex::new(1), U256::from(0xcc)),
295                    StorageChange::new(BlockAccessIndex::new(3), U256::from(0xdd)),
296                ],
297            ));
298
299        let post_states = account.storage_post_states().collect::<Vec<_>>();
300
301        assert_eq!(
302            post_states,
303            vec![(U256::from(1), U256::from(0xbb)), (U256::from(3), U256::from(0xdd))]
304        );
305    }
306}
307
308#[cfg(all(test, feature = "serde"))]
309mod tests {
310    use crate::{BlockAccessIndex, StorageChange};
311
312    use super::*;
313    use alloy_primitives::Bytes;
314    use serde_json;
315
316    #[test]
317    fn test_account_changes_serde() {
318        let acc = AccountChanges {
319            address: Address::from([0x11; 20]),
320            storage_changes: vec![SlotChanges {
321                slot: U256::from(1),
322                changes: vec![StorageChange {
323                    block_access_index: BlockAccessIndex::new(0),
324                    new_value: U256::from(100),
325                }],
326            }],
327            storage_reads: vec![U256::from(2)],
328            balance_changes: vec![BalanceChange {
329                block_access_index: BlockAccessIndex::new(1),
330                post_balance: U256::from(1000),
331            }],
332            nonce_changes: vec![NonceChange {
333                block_access_index: BlockAccessIndex::new(2),
334                new_nonce: 42,
335            }],
336            code_changes: vec![CodeChange {
337                block_access_index: BlockAccessIndex::new(3),
338                new_code: Bytes::from(vec![0x60, 0x00]),
339            }],
340        };
341
342        let json = serde_json::to_string(&acc).unwrap();
343        let decoded: AccountChanges = serde_json::from_str(&json).unwrap();
344
345        assert_eq!(acc, decoded);
346    }
347
348    #[test]
349    fn test_vec_account_changes_serde() {
350        let acc1 = AccountChanges::new(Address::from([0x11; 20]))
351            .with_storage_read(U256::from(1))
352            .with_balance_change(BalanceChange {
353                block_access_index: BlockAccessIndex::new(0),
354                post_balance: U256::from(100),
355            });
356
357        let acc2 = AccountChanges::new(Address::from([0x22; 20]))
358            .with_storage_change(SlotChanges {
359                slot: U256::from(2),
360                changes: vec![StorageChange {
361                    block_access_index: BlockAccessIndex::new(1),
362                    new_value: U256::from(200),
363                }],
364            })
365            .with_nonce_change(NonceChange {
366                block_access_index: BlockAccessIndex::new(2),
367                new_nonce: 42,
368            });
369
370        let acc3 = AccountChanges::new(Address::from([0x33; 20])).with_code_change(CodeChange {
371            block_access_index: BlockAccessIndex::new(3),
372            new_code: Bytes::from(vec![0x60, 0x00]),
373        });
374
375        let vec_acc = vec![acc1, acc2, acc3];
376
377        let json = serde_json::to_string(&vec_acc).unwrap();
378        let decoded: Vec<AccountChanges> = serde_json::from_str(&json).unwrap();
379
380        assert_eq!(vec_acc, decoded);
381    }
382}