tempo_precompiles/storage/types/
mod.rs

1mod slot;
2pub use slot::*;
3
4pub mod mapping;
5pub use mapping::*;
6
7mod bytes_like;
8mod primitives;
9pub mod vec;
10
11use crate::{error::Result, storage::StorageOps};
12use alloy::primitives::U256;
13
14/// Describes how a type is laid out in EVM storage.
15///
16/// This determines whether a type can be packed with other fields
17/// and how many storage slots it occupies.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum Layout {
20    /// Single slot, N bytes (1-32). Can be packed with other fields if N < 32.
21    ///
22    /// Used for primitive types like integers, booleans, and addresses.
23    Bytes(usize),
24
25    /// Occupies N full slots (each 32 bytes). Cannot be packed.
26    ///
27    /// Used for structs, fixed-size arrays, and dynamic types.
28    Slots(usize),
29}
30
31impl Layout {
32    /// Returns true if this field can be packed with adjacent fields.
33    pub const fn is_packable(&self) -> bool {
34        match self {
35            // TODO(rusowsky): use `Self::Bytes(n) => *n < 32` to reduce gas usage.
36            // Note that this requires a hardfork and must be properly coordinated.
37            Self::Bytes(_) => true,
38            Self::Slots(_) => false,
39        }
40    }
41
42    /// Returns the number of storage slots this type occupies.
43    pub const fn slots(&self) -> usize {
44        match self {
45            Self::Bytes(_) => 1,
46            Self::Slots(n) => *n,
47        }
48    }
49
50    /// Returns the number of bytes this type occupies.
51    ///
52    /// For `Bytes(n)`, returns n.
53    /// For `Slots(n)`, returns n * 32 (each slot is 32 bytes).
54    pub const fn bytes(&self) -> usize {
55        match self {
56            Self::Bytes(n) => *n,
57            Self::Slots(n) => {
58                // Compute n * 32 using repeated addition for const compatibility
59                let (mut i, mut result) = (0, 0);
60                while i < *n {
61                    result += 32;
62                    i += 1;
63                }
64                result
65            }
66        }
67    }
68}
69
70/// Describes the context in which a `Storable` value is being loaded or stored.
71///
72/// Determines whether the value occupies an entire storage slot or is packed
73/// with other values at a specific byte offset within a slot.
74///
75/// **NOTE:** This type is not an enum to minimize its memory size, but its
76/// implementation is equivalent to:
77/// ```rs
78/// enum LayoutCtx {
79///    Full,
80///    Packed(usize)
81/// }
82/// ```
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84#[repr(transparent)]
85pub struct LayoutCtx(usize);
86
87impl LayoutCtx {
88    /// Load/store the entire value at `base_slot`.
89    ///
90    /// For writes, this directly overwrites the entire slot without needing SLOAD.
91    /// All `Storable` types support this context.
92    pub const FULL: Self = Self(usize::MAX);
93
94    /// Load/store a packed primitive at the given byte offset within a slot.
95    ///
96    /// For writes, this requires a read-modify-write: SLOAD the current slot value,
97    /// modify the bytes at the offset, then SSTORE back. This preserves other
98    /// packed fields in the same slot.
99    ///
100    /// Only primitive types with `Layout::Bytes(n)` where `n < 32` support this context.
101    pub const fn packed(offset: usize) -> Self {
102        debug_assert!(offset < 32);
103        Self(offset)
104    }
105
106    /// Get the packed offset, returns `None` for `Full`
107    #[inline]
108    pub const fn packed_offset(&self) -> Option<usize> {
109        if self.0 == usize::MAX {
110            None
111        } else {
112            Some(self.0)
113        }
114    }
115}
116
117/// Helper trait to access storage layout information without requiring const generic parameter.
118///
119/// This trait exists to allow the derive macro to query the layout and size of field types
120/// during layout computation, before the slot count is known.
121pub trait StorableType {
122    /// Describes how this type is laid out in storage.
123    ///
124    /// - Primitives use `Layout::Bytes(N)` where N is their size
125    /// - Dynamic types (String, Bytes, Vec) use `Layout::Slots(1)`
126    /// - Structs and arrays use `Layout::Slots(N)` where N is the slot count
127    const LAYOUT: Layout;
128
129    /// Number of storage slots this type takes.
130    const SLOTS: usize = Self::LAYOUT.slots();
131
132    /// Number of bytes this type takes.
133    const BYTES: usize = Self::LAYOUT.bytes();
134
135    /// Whether this type can be packed with adjacent fields.
136    const IS_PACKABLE: bool = Self::LAYOUT.is_packable();
137}
138
139/// Trait for types that can be stored/loaded from EVM storage.
140///
141/// This trait provides a flexible abstraction for reading and writing Rust types
142/// to EVM storage. Types can occupy one or more consecutive storage slots, enabling
143/// support for both simple values (Address, U256, bool) and complex multi-slot types
144/// (structs, fixed arrays).
145///
146/// # Type Parameter
147///
148/// - `SLOTS`: The number of consecutive storage slots this type occupies.
149///   For single-word types (Address, U256, bool), this is `1`.
150///   For fixed-size arrays, this equals the number of elements.
151///   For user-defined structs, this a number between `1` and the number of fields, which depends on slot packing.
152///
153/// # Storage Layout
154///
155/// For a type with `SLOTS = 3` starting at `base_slot`:
156/// - Slot 0: `base_slot + 0`
157/// - Slot 1: `base_slot + 1`
158/// - Slot 2: `base_slot + 2`
159///
160/// # Safety
161///
162/// Implementations must ensure that:
163/// - Round-trip conversions preserve data: `load(store(x)) == Ok(x)`
164/// - `SLOTS` accurately reflects the number of slots used
165/// - `store` and `load` access exactly `SLOTS` consecutive slots
166/// - `to_evm_words` and `from_evm_words` produce/consume exactly `SLOTS` words
167pub trait Storable<const SLOTS: usize>: Sized + StorableType {
168    /// Load this type from storage starting at the given base slot.
169    ///
170    /// Reads `SLOTS` consecutive slots starting from `base_slot`.
171    ///
172    /// # Context
173    ///
174    /// - `LayoutCtx::FULL`: Load the entire value from `base_slot` (and subsequent slots if multi-slot)
175    /// - `LayoutCtx::packed(offset)`: Load a packed primitive from byte `offset` within `base_slot`
176    ///
177    /// # Errors
178    ///
179    /// Returns an error if:
180    /// - Storage read fails
181    /// - Data cannot be decoded into this type
182    /// - Context is invalid for this type (e.g., `Packed` for a multi-slot type)
183    fn load<S: StorageOps>(storage: &mut S, base_slot: U256, ctx: LayoutCtx) -> Result<Self>;
184
185    /// Store this type to storage starting at the given base slot.
186    ///
187    /// Writes `SLOTS` consecutive slots starting from `base_slot`.
188    ///
189    /// # Context
190    ///
191    /// - `LayoutCtx::FULL`: Write the entire value to `base_slot` (overwrites full slot)
192    /// - `LayoutCtx::packed(offset)`: Write a packed primitive at byte `offset` (read-modify-write)
193    ///
194    /// # Errors
195    ///
196    /// Returns an error if:
197    /// - Storage write fails
198    /// - Context is invalid for this type (e.g., `Packed` for a multi-slot type)
199    fn store<S: StorageOps>(&self, storage: &mut S, base_slot: U256, ctx: LayoutCtx) -> Result<()>;
200
201    /// Delete this type from storage (set all slots to zero).
202    ///
203    /// Sets `SLOTS` consecutive slots to zero, starting from `base_slot`.
204    ///
205    /// # Context
206    ///
207    /// - `LayoutCtx::FULL`: Clear entire slot(s) by writing zero
208    /// - `LayoutCtx::packed(offset)`: Clear only the bytes at the offset (read-modify-write)
209    ///
210    /// The default implementation handles both contexts appropriately.
211    ///
212    /// # Errors
213    ///
214    /// Returns an error if:
215    /// - Storage write fails
216    /// - Context is invalid for this type
217    fn delete<S: StorageOps>(storage: &mut S, base_slot: U256, ctx: LayoutCtx) -> Result<()> {
218        match ctx.packed_offset() {
219            None => {
220                for offset in 0..SLOTS {
221                    storage.sstore(base_slot + U256::from(offset), U256::ZERO)?;
222                }
223                Ok(())
224            }
225            Some(offset) => {
226                // For packed context, we need to preserve other fields in the slot
227                let bytes = Self::BYTES;
228                let current = storage.sload(base_slot)?;
229                let cleared = crate::storage::packing::zero_packed_value(current, offset, bytes)?;
230                storage.sstore(base_slot, cleared)
231            }
232        }
233    }
234
235    /// Encode this type to an array of U256 words.
236    ///
237    /// Returns exactly `SLOTS` words, where each word represents one storage slot.
238    /// For single-slot types (`SLOTS = 1`), returns a single-element array.
239    /// For multi-slot types, each array element corresponds to one slot's data.
240    ///
241    /// # Packed Storage
242    ///
243    /// When multiple small fields are packed into a single slot, they are
244    /// positioned and combined into a single U256 word according to their
245    /// byte offsets. The derive macro handles this automatically.
246    fn to_evm_words(&self) -> Result<[U256; SLOTS]>;
247
248    /// Decode this type from an array of U256 words.
249    ///
250    /// Accepts exactly `N` words, where each word represents one storage slot.
251    /// Constructs the complete type from all provided words.
252    ///
253    /// # Packed Storage
254    ///
255    /// When multiple small fields are packed into a single slot, they are
256    /// extracted from the appropriate word using bit shifts and masks.
257    /// The derive macro handles this automatically.
258    fn from_evm_words(words: [U256; SLOTS]) -> Result<Self>;
259
260    /// Test helper to ensure `LAYOUT` and `SLOTS` are in sync.
261    fn validate_layout() {
262        debug_assert_eq!(<Self as StorableType>::SLOTS, SLOTS)
263    }
264}
265
266/// Trait for types that can be used as storage mapping keys.
267///
268/// Keys are hashed using keccak256 along with the mapping's base slot
269/// to determine the final storage location. This trait provides the
270/// byte representation used in that hash.
271pub trait StorageKey {
272    fn as_storage_bytes(&self) -> impl AsRef<[u8]>;
273}