Skip to main content

reth_primitives_traits/
storage.rs

1use alloy_primitives::{keccak256, B256, U256};
2
3/// Trait for `DupSort` table values that contain a subkey.
4///
5/// This trait allows extracting the subkey from a value during database iteration,
6/// enabling proper range queries and filtering on `DupSort` tables.
7pub trait ValueWithSubKey {
8    /// The type of the subkey.
9    type SubKey;
10
11    /// Extract the subkey from the value.
12    fn get_subkey(&self) -> Self::SubKey;
13}
14
15/// A storage slot key that tracks whether it holds a plain (unhashed) EVM slot
16/// or a keccak256-hashed slot.
17///
18/// This enum replaces the `use_hashed_state: bool` parameter pattern by carrying
19/// provenance with the key itself. Once tagged at a read/write boundary, downstream
20/// code can call [`Self::to_hashed`] without risk of double-hashing — hashing a
21/// [`StorageSlotKey::Hashed`] is a no-op.
22///
23/// The on-disk encoding is unchanged (raw 32-byte [`B256`]). The variant is set
24/// by the code that knows the context (which table, which storage mode).
25#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
26pub enum StorageSlotKey {
27    /// An unhashed EVM storage slot, as produced by REVM execution.
28    Plain(B256),
29    /// A keccak256-hashed storage slot, as stored in `HashedStorages` and
30    /// in v2-mode `StorageChangeSets`.
31    Hashed(B256),
32}
33
34impl Default for StorageSlotKey {
35    fn default() -> Self {
36        Self::Plain(B256::ZERO)
37    }
38}
39
40impl StorageSlotKey {
41    /// Create a plain slot key from a REVM [`U256`] storage index.
42    pub const fn from_u256(slot: U256) -> Self {
43        Self::Plain(B256::new(slot.to_be_bytes()))
44    }
45
46    /// Create a plain slot key from a raw [`B256`].
47    pub const fn plain(key: B256) -> Self {
48        Self::Plain(key)
49    }
50
51    /// Create a hashed slot key from a raw [`B256`].
52    pub const fn hashed(key: B256) -> Self {
53        Self::Hashed(key)
54    }
55
56    /// Tag a raw [`B256`] based on the storage mode.
57    ///
58    /// When `use_hashed_state` is true the key is assumed already hashed.
59    /// When false it is assumed to be a plain slot.
60    pub const fn from_raw(key: B256, use_hashed_state: bool) -> Self {
61        if use_hashed_state {
62            Self::Hashed(key)
63        } else {
64            Self::Plain(key)
65        }
66    }
67
68    /// Returns the raw [`B256`] regardless of variant.
69    pub const fn as_b256(&self) -> B256 {
70        match *self {
71            Self::Plain(b) | Self::Hashed(b) => b,
72        }
73    }
74
75    /// Returns `true` if this key is already hashed.
76    pub const fn is_hashed(&self) -> bool {
77        matches!(self, Self::Hashed(_))
78    }
79
80    /// Returns `true` if this key is plain (unhashed).
81    pub const fn is_plain(&self) -> bool {
82        matches!(self, Self::Plain(_))
83    }
84
85    /// Produce the keccak256-hashed form of this slot key.
86    ///
87    /// - If already [`Hashed`](Self::Hashed), returns the inner value as-is (no double-hash).
88    /// - If [`Plain`](Self::Plain), applies keccak256 and returns the result.
89    pub fn to_hashed(&self) -> B256 {
90        match *self {
91            Self::Hashed(b) => b,
92            Self::Plain(b) => keccak256(b),
93        }
94    }
95
96    /// Convert a plain slot to its changeset representation.
97    ///
98    /// In v2 mode (`use_hashed_state = true`), the changeset stores hashed keys,
99    /// so the plain key is hashed. In v1 mode, the plain key is stored as-is.
100    ///
101    /// Panics (debug) if called on an already-hashed key.
102    pub fn to_changeset_key(self, use_hashed_state: bool) -> B256 {
103        debug_assert!(self.is_plain(), "to_changeset_key called on already-hashed key");
104        if use_hashed_state {
105            self.to_hashed()
106        } else {
107            self.as_b256()
108        }
109    }
110
111    /// Like [`to_changeset_key`](Self::to_changeset_key) but returns a tagged
112    /// [`StorageSlotKey`] instead of a raw [`B256`].
113    ///
114    /// Panics (debug) if called on an already-hashed key.
115    pub fn to_changeset(self, use_hashed_state: bool) -> Self {
116        Self::from_raw(self.to_changeset_key(use_hashed_state), use_hashed_state)
117    }
118}
119
120impl From<StorageSlotKey> for B256 {
121    fn from(key: StorageSlotKey) -> Self {
122        key.as_b256()
123    }
124}
125
126/// Account storage entry.
127///
128/// `key` is the subkey when used as a value in the `StorageChangeSets` table.
129#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
130#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
131#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
132#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(compact))]
133pub struct StorageEntry {
134    /// Storage key.
135    pub key: B256,
136    /// Value on storage key.
137    pub value: U256,
138}
139
140impl StorageEntry {
141    /// Create a new `StorageEntry` with given key and value.
142    pub const fn new(key: B256, value: U256) -> Self {
143        Self { key, value }
144    }
145
146    /// Tag this entry's key as a [`StorageSlotKey`] based on the storage mode.
147    ///
148    /// When `use_hashed_state` is true, the key is tagged as already-hashed.
149    /// When false, it is tagged as plain.
150    pub const fn slot_key(&self, use_hashed_state: bool) -> StorageSlotKey {
151        StorageSlotKey::from_raw(self.key, use_hashed_state)
152    }
153}
154
155impl ValueWithSubKey for StorageEntry {
156    type SubKey = B256;
157
158    fn get_subkey(&self) -> Self::SubKey {
159        self.key
160    }
161}
162
163impl From<(B256, U256)> for StorageEntry {
164    fn from((key, value): (B256, U256)) -> Self {
165        Self { key, value }
166    }
167}
168
169// NOTE: Removing reth_codec and manually encode subkey
170// and compress second part of the value. If we have compression
171// over whole value (Even SubKey) that would mess up fetching of values with seek_by_key_subkey
172#[cfg(any(test, feature = "reth-codec"))]
173impl reth_codecs::Compact for StorageEntry {
174    fn to_compact<B>(&self, buf: &mut B) -> usize
175    where
176        B: bytes::BufMut + AsMut<[u8]>,
177    {
178        // for now put full bytes and later compress it.
179        buf.put_slice(&self.key[..]);
180        self.value.to_compact(buf) + 32
181    }
182
183    fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) {
184        let key = B256::from_slice(&buf[..32]);
185        let (value, out) = U256::from_compact(&buf[32..], len - 32);
186        (Self { key, value }, out)
187    }
188}