Skip to main content

alloy_eip7928/
slot_changes.rs

1//! Contains the [`SlotChanges`] struct, which represents all changes made to a single storage slot
2//! across multiple transactions.
3
4use crate::StorageChange;
5use alloc::vec::Vec;
6use alloy_primitives::U256;
7
8/// Represents all changes made to a single storage slot across multiple transactions.
9#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
10#[cfg_attr(feature = "rlp", derive(alloy_rlp::RlpEncodable, alloy_rlp::RlpDecodable))]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
13#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
14#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
15pub struct SlotChanges {
16    /// The storage slot key being modified.
17    #[cfg_attr(feature = "serde", serde(rename = "key", alias = "slot"))]
18    pub slot: U256,
19    /// A list of write operations to this slot, ordered by transaction index.
20    #[cfg_attr(feature = "serde", serde(alias = "slotChanges"))]
21    pub changes: Vec<StorageChange>,
22}
23
24impl SlotChanges {
25    /// Creates a new [`SlotChanges`] instance for the given slot key and changes.
26    #[inline]
27    pub const fn new(slot: U256, changes: Vec<StorageChange>) -> Self {
28        Self { slot, changes }
29    }
30
31    /// Creates a new [`SlotChanges`] with preallocated capacity for the given number of changes.
32    #[inline]
33    pub fn with_capacity(slot: U256, capacity: usize) -> Self {
34        Self { slot, changes: Vec::with_capacity(capacity) }
35    }
36
37    /// Appends a storage change to the list.
38    #[inline]
39    pub fn push(&mut self, change: StorageChange) {
40        self.changes.push(change)
41    }
42
43    /// Returns `true` if no changes have been recorded.
44    #[inline]
45    pub const fn is_empty(&self) -> bool {
46        self.changes.is_empty()
47    }
48
49    /// Returns the number of changes recorded for this slot.
50    #[inline]
51    pub const fn len(&self) -> usize {
52        self.changes.len()
53    }
54
55    /// Sorts this slot's storage changes by block access index in ascending order.
56    ///
57    /// This applies the per-slot ordering required by the "Ordering, Uniqueness and Determinism"
58    /// section of EIP-7928. It only canonicalizes ordering and does not enforce uniqueness of block
59    /// access indexes.
60    pub fn sort(&mut self) {
61        self.changes.sort_unstable_by_key(|change| change.block_access_index);
62    }
63
64    /// Creates a new `SlotChanges` for the given slot.
65    #[inline]
66    pub const fn with_slot(mut self, slot: U256) -> Self {
67        self.slot = slot;
68        self
69    }
70
71    /// Creates a new `SlotChanges` with the given change appended.
72    #[inline]
73    pub fn with_change(mut self, change: StorageChange) -> Self {
74        self.changes.push(change);
75        self
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82    use crate::BlockAccessIndex;
83
84    #[test]
85    fn sort_orders_changes_by_block_access_index() {
86        let mut slot_changes = SlotChanges::new(
87            U256::from(1),
88            vec![
89                StorageChange::new(BlockAccessIndex::new(8), U256::from(0x80)),
90                StorageChange::new(BlockAccessIndex::new(2), U256::from(0x20)),
91                StorageChange::new(BlockAccessIndex::new(5), U256::from(0x50)),
92            ],
93        );
94
95        slot_changes.sort();
96
97        assert_eq!(
98            slot_changes.changes.iter().map(|change| change.block_access_index).collect::<Vec<_>>(),
99            vec![BlockAccessIndex::new(2), BlockAccessIndex::new(5), BlockAccessIndex::new(8)]
100        );
101    }
102}