Skip to main content

alloy_eip7928/
block_access_index.rs

1//! Contains the [`BlockAccessIndex`] newtype and its [`BlockAccessPhase`] classification.
2
3use core::fmt;
4
5/// Block access index within a block.
6///
7/// A block's indices are laid out as:
8/// - `0` — pre-execution (system contract calls, block-level setup, ...)
9/// - `1..=tx_len` — transaction execution (transaction index `i` maps to index `i + 1`)
10/// - `tx_len + 1` — post-execution (block rewards, withdrawals, ...)
11///
12/// Stored as a `u64` internally, but wrapped as a newtype so it cannot be accidentally
13/// confused with other `u64` indices (for example, an `account_id` passed alongside it).
14///
15/// RLP, borsh, and arbitrary representations are identical to the wrapped `u64`. Serde
16/// serializes as a hex `"quantity"` string (e.g. `"0x1a"`), matching the previous
17/// `BlockAccessIndex = u64` alias behavior when paired with the `crate::quantity` module.
18#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
19#[cfg_attr(feature = "rlp", derive(alloy_rlp::RlpEncodableWrapper, alloy_rlp::RlpDecodableWrapper))]
20#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
21#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
22#[repr(transparent)]
23pub struct BlockAccessIndex(pub u64);
24
25impl BlockAccessIndex {
26    /// Pre-execution slot (index `0`).
27    pub const PRE_EXECUTION: Self = Self(0);
28
29    /// Constructs a new [`BlockAccessIndex`] from a raw `u64`.
30    #[inline]
31    pub const fn new(value: u64) -> Self {
32        Self(value)
33    }
34
35    /// Constructs a new [`BlockAccessIndex`] from a 0-based transaction index.
36    ///
37    /// Transaction changes start at index `1` in a block access list, so transaction
38    /// index `0` maps to block access index `1`.
39    #[inline]
40    pub const fn from_tx_index(tx_index: u64) -> Self {
41        Self(tx_index + 1)
42    }
43
44    /// Returns the raw `u64` value.
45    #[inline]
46    pub const fn get(self) -> u64 {
47        self.0
48    }
49
50    /// Bumps the index by 1.
51    #[inline]
52    pub const fn increment(&mut self) {
53        self.0 += 1;
54    }
55
56    /// Classifies this index into a [`BlockAccessPhase`], given the number of transactions
57    /// in the block.
58    ///
59    /// Returns:
60    /// - `Some(BlockAccessPhase::PreExecution)` when the index is `0`.
61    /// - `Some(BlockAccessPhase::Transaction(i))` when the index is in `1..=tx_len`, with `i =
62    ///   index - 1` as a 0-based transaction index.
63    /// - `Some(BlockAccessPhase::PostExecution)` when the index is exactly `tx_len + 1`.
64    /// - `None` when the index is strictly greater than `tx_len + 1` (out of range for a block with
65    ///   `tx_len` transactions).
66    #[inline]
67    pub const fn phase(self, tx_len: usize) -> Option<BlockAccessPhase> {
68        // Widen `tx_len` to `u64` to compare against the index without risking
69        // truncation on 32-bit targets.
70        let tx_len_u64 = tx_len as u64;
71        if self.0 == 0 {
72            Some(BlockAccessPhase::PreExecution)
73        } else if self.0 <= tx_len_u64 {
74            // `self.0 >= 1` here, so the subtraction cannot underflow.
75            // Casting back to `usize` is safe because `self.0 - 1 < tx_len <= usize::MAX`.
76            Some(BlockAccessPhase::Transaction((self.0 - 1) as usize))
77        } else if self.0 == tx_len_u64 + 1 {
78            Some(BlockAccessPhase::PostExecution)
79        } else {
80            None
81        }
82    }
83}
84
85impl fmt::Display for BlockAccessIndex {
86    #[inline]
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88        fmt::Display::fmt(&self.0, f)
89    }
90}
91
92impl fmt::LowerHex for BlockAccessIndex {
93    #[inline]
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        fmt::LowerHex::fmt(&self.0, f)
96    }
97}
98
99#[cfg(feature = "serde")]
100impl serde::Serialize for BlockAccessIndex {
101    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
102        alloy_primitives::U64::from(self.0).serialize(serializer)
103    }
104}
105
106#[cfg(feature = "serde")]
107impl<'de> serde::Deserialize<'de> for BlockAccessIndex {
108    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
109        alloy_primitives::U64::deserialize(deserializer).map(|value| Self(value.to()))
110    }
111}
112
113/// Classification of a [`BlockAccessIndex`] within a block.
114///
115/// See [`BlockAccessIndex::phase`] for how indices map to phases.
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
117#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
118pub enum BlockAccessPhase {
119    /// Pre-execution slot (index `0`).
120    PreExecution,
121    /// Transaction execution slot. The inner value is the 0-based transaction index
122    /// within the block (i.e. `block_access_index - 1`).
123    Transaction(usize),
124    /// Post-execution slot (index `tx_len + 1`).
125    PostExecution,
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131
132    #[test]
133    fn pre_execution_is_index_zero() {
134        assert_eq!(BlockAccessIndex::new(0).phase(0), Some(BlockAccessPhase::PreExecution));
135        assert_eq!(BlockAccessIndex::new(0).phase(5), Some(BlockAccessPhase::PreExecution));
136        assert_eq!(BlockAccessIndex::PRE_EXECUTION.phase(5), Some(BlockAccessPhase::PreExecution));
137    }
138
139    #[test]
140    fn transaction_indices_are_one_based() {
141        assert_eq!(BlockAccessIndex::new(1).phase(3), Some(BlockAccessPhase::Transaction(0)));
142        assert_eq!(BlockAccessIndex::new(2).phase(3), Some(BlockAccessPhase::Transaction(1)));
143        assert_eq!(BlockAccessIndex::new(3).phase(3), Some(BlockAccessPhase::Transaction(2)));
144    }
145
146    #[test]
147    fn from_tx_index_offsets_by_one() {
148        assert_eq!(BlockAccessIndex::from_tx_index(0), BlockAccessIndex::new(1));
149        assert_eq!(BlockAccessIndex::from_tx_index(1), BlockAccessIndex::new(2));
150    }
151
152    #[test]
153    fn post_execution_is_tx_len_plus_one() {
154        assert_eq!(BlockAccessIndex::new(4).phase(3), Some(BlockAccessPhase::PostExecution));
155        assert_eq!(BlockAccessIndex::new(1).phase(0), Some(BlockAccessPhase::PostExecution));
156    }
157
158    #[test]
159    fn out_of_range_returns_none() {
160        assert_eq!(BlockAccessIndex::new(5).phase(3), None);
161        assert_eq!(BlockAccessIndex::new(u64::MAX).phase(3), None);
162    }
163
164    #[test]
165    fn empty_block_has_only_pre_and_post() {
166        assert_eq!(BlockAccessIndex::new(0).phase(0), Some(BlockAccessPhase::PreExecution));
167        assert_eq!(BlockAccessIndex::new(1).phase(0), Some(BlockAccessPhase::PostExecution));
168        assert_eq!(BlockAccessIndex::new(2).phase(0), None);
169    }
170
171    #[test]
172    fn increment_bumps_by_one() {
173        let mut idx = BlockAccessIndex::new(3);
174        idx.increment();
175        assert_eq!(idx, BlockAccessIndex::new(4));
176    }
177
178    #[test]
179    fn new_and_get_roundtrip() {
180        let idx = BlockAccessIndex::new(42);
181        assert_eq!(idx.get(), 42);
182    }
183
184    #[test]
185    fn display_matches_inner() {
186        extern crate alloc;
187        assert_eq!(alloc::format!("{}", BlockAccessIndex::new(7)), "7");
188        assert_eq!(alloc::format!("{:x}", BlockAccessIndex::new(255)), "ff");
189    }
190
191    #[cfg(feature = "serde")]
192    #[test]
193    fn serde_hex_quantity_roundtrip() {
194        let idx = BlockAccessIndex::new(26);
195        let json = serde_json::to_string(&idx).unwrap();
196        assert_eq!(json, "\"0x1a\"");
197        let back: BlockAccessIndex = serde_json::from_str(&json).unwrap();
198        assert_eq!(back, idx);
199    }
200
201    #[cfg(feature = "rlp")]
202    #[test]
203    fn rlp_matches_raw_u64() {
204        use alloy_rlp::Decodable;
205        let idx = BlockAccessIndex::new(300);
206        let encoded = alloy_rlp::encode(idx);
207        assert_eq!(encoded, alloy_rlp::encode(300u64));
208        let decoded = BlockAccessIndex::decode(&mut encoded.as_slice()).unwrap();
209        assert_eq!(decoded, idx);
210    }
211}