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