Skip to main content

tempo_precompiles/storage/types/
slot.rs

1use alloy::primitives::{Address, U256};
2use std::marker::PhantomData;
3
4use crate::{
5    error::Result,
6    storage::{FieldLocation, Handler, LayoutCtx, Storable, StorableType, StorageCtx, StorageOps},
7};
8
9/// Type-safe wrapper for a single EVM storage slot.
10///
11/// # Type Parameters
12///
13/// - `T`: The Rust type stored in this slot (must implement `Storable`)
14///
15/// # Example
16///
17/// ```ignore
18/// // Generated by #[contract] macro:
19/// pub mod slots {
20///     pub const NAME: U256 = uint!(2_U256);
21/// }
22/// let name_slot = Slot::<String>::new(slots::NAME);
23/// ```
24///
25/// The actual storage operations are handled by generated accessor methods
26/// that read/write values using the `PrecompileStorageProvider` trait.
27#[derive(Debug, Clone)]
28pub struct Slot<T> {
29    slot: U256,
30    ctx: LayoutCtx,
31    address: Address,
32    _ty: PhantomData<T>,
33}
34
35impl<T> Slot<T> {
36    /// Creates a new `Slot` with the given slot number and address.
37    ///
38    /// This is typically called with slot constants generated by the `#[contract]` macro.
39    /// Creates a full-slot accessor. For packed fields, use `new_at_loc` instead.
40    #[inline]
41    pub fn new(slot: U256, address: Address) -> Self {
42        Self {
43            slot,
44            ctx: LayoutCtx::FULL,
45            address,
46            _ty: PhantomData,
47        }
48    }
49
50    /// Creates a new `Slot` with the given slot number, layout context, and address.
51    ///
52    /// This is used by the handler system to create slots with specific packing contexts.
53    #[inline]
54    pub fn new_with_ctx(slot: U256, ctx: LayoutCtx, address: Address) -> Self {
55        Self {
56            slot,
57            ctx,
58            address,
59            _ty: PhantomData,
60        }
61    }
62
63    /// Creates a new `Slot` with the given base slot number with the given offset and address.
64    ///
65    /// This is a convenience method for accessing struct fields.
66    /// Creates a full-slot accessor. For packed fields, use `new_at_loc` instead.
67    #[inline]
68    pub fn new_at_offset(base_slot: U256, offset_slots: usize, address: Address) -> Self {
69        Self {
70            slot: base_slot.saturating_add(U256::from_limbs([offset_slots as u64, 0, 0, 0])),
71            ctx: LayoutCtx::FULL,
72            address,
73            _ty: PhantomData,
74        }
75    }
76
77    /// Creates a new `Slot` from a `FieldLocation` generated by the `#[derive(Storable)]` macro.
78    ///
79    /// This is the recommended way to access packed struct fields, combining slot offset
80    /// and byte offset information in a type-safe manner.
81    ///
82    /// This method should only be used with packable types (size < 32 bytes).
83    #[inline]
84    pub fn new_at_loc(base_slot: U256, loc: FieldLocation, address: Address) -> Self
85    where
86        T: StorableType,
87    {
88        debug_assert!(
89            T::IS_PACKABLE,
90            "`fn new_at_loc` can only be used with packable types"
91        );
92        Self {
93            slot: base_slot.saturating_add(U256::from_limbs([loc.offset_slots as u64, 0, 0, 0])),
94            ctx: LayoutCtx::packed(loc.offset_bytes),
95            address,
96            _ty: PhantomData,
97        }
98    }
99
100    /// Returns the storage slot number where the underlying type is stored.
101    ///
102    /// Multi-slot types use consecutive slots from this base slot.
103    #[inline]
104    pub const fn slot(&self) -> U256 {
105        self.slot
106    }
107
108    /// Returns the byte offset within the slot (for packed fields).
109    ///
110    /// Returns `Some(offset)` if this is a packed slot, `None` if it's a full slot.
111    #[inline]
112    pub const fn offset(&self) -> Option<usize> {
113        self.ctx.packed_offset()
114    }
115}
116
117impl<T> StorageOps for Slot<T> {
118    fn load(&self, slot: U256) -> Result<U256> {
119        let storage = StorageCtx;
120        storage.sload(self.address, slot)
121    }
122
123    fn store(&mut self, slot: U256, value: U256) -> Result<()> {
124        let mut storage = StorageCtx;
125        storage.sstore(self.address, slot, value)
126    }
127}
128
129/// Wrapper that routes storage operations through transient storage (TLOAD/TSTORE).
130///
131/// Created via `Slot::transient()` and used by `t_read()`, `t_write()`, `t_delete()`.
132struct TransientOps {
133    address: Address,
134}
135
136impl StorageOps for TransientOps {
137    fn load(&self, slot: U256) -> Result<U256> {
138        let storage = StorageCtx;
139        storage.tload(self.address, slot)
140    }
141
142    fn store(&mut self, slot: U256, value: U256) -> Result<()> {
143        let mut storage = StorageCtx;
144        storage.tstore(self.address, slot, value)
145    }
146}
147
148impl<T: Storable> Slot<T> {
149    /// Returns a transient storage operations wrapper for this slot's address.
150    fn transient(&self) -> TransientOps {
151        TransientOps {
152            address: self.address,
153        }
154    }
155}
156
157impl<T: Storable> Handler<T> for Slot<T> {
158    /// Reads a value from storage at this slot.
159    ///
160    /// This method delegates to the `Storable::load` implementation,
161    /// which may read one or more consecutive slots depending on the type.
162    ///
163    /// Uses thread-local storage context initialized by [`StorageCtx`].
164    ///
165    /// # Example
166    ///
167    /// ```ignore
168    /// let name_slot = Slot::<String>::new(slots::NAME, address_rc);
169    /// let name = name_slot.read().unwrap();
170    /// ```
171    #[inline]
172    fn read(&self) -> Result<T> {
173        T::load(self, self.slot, self.ctx)
174    }
175
176    /// Writes a value to storage at this slot.
177    ///
178    /// This method delegates to the `Storable::store` implementation,
179    /// which may write one or more consecutive slots depending on the type.
180    ///
181    /// Uses thread-local storage context initialized by [`StorageCtx`].
182    ///
183    /// # Example
184    ///
185    /// ```ignore
186    /// let mut name_slot = Slot::<String>::new(slots::NAME, address_rc);
187    /// name_slot.write("MyToken".to_string()).unwrap();
188    /// ```
189    #[inline]
190    fn write(&mut self, value: T) -> Result<()> {
191        value.store(self, self.slot, self.ctx)
192    }
193
194    /// Deletes the value at this slot (sets all slots to zero).
195    ///
196    /// This method delegates to the `Storable::delete` implementation,
197    /// which sets the appropriate slots to zero.
198    ///
199    /// Uses thread-local storage context initialized by [`StorageCtx`].
200    ///
201    /// # Example
202    ///
203    /// ```ignore
204    /// let mut name_slot = Slot::<String>::new(slots::NAME, address_rc);
205    /// name_slot.delete().unwrap();
206    /// ```
207    #[inline]
208    fn delete(&mut self) -> Result<()> {
209        T::delete(self, self.slot, self.ctx)
210    }
211
212    /// Reads a value from transient storage at this slot.
213    #[inline]
214    fn t_read(&self) -> Result<T> {
215        T::load(&self.transient(), self.slot, self.ctx)
216    }
217
218    /// Writes a value to transient storage at this slot.
219    #[inline]
220    fn t_write(&mut self, value: T) -> Result<()> {
221        value.store(&mut self.transient(), self.slot, self.ctx)
222    }
223
224    /// Deletes the value at this slot in transient storage (sets to zero).
225    #[inline]
226    fn t_delete(&mut self) -> Result<()> {
227        T::delete(&mut self.transient(), self.slot, self.ctx)
228    }
229}
230
231#[cfg(test)]
232mod tests {
233    use super::*;
234    use crate::{
235        storage::{Handler, PrecompileStorageProvider, StorageKey},
236        test_util::setup_storage,
237    };
238    use alloy::primitives::{Address, B256};
239    use proptest::prelude::*;
240
241    // Property test strategies
242    fn arb_address() -> impl Strategy<Value = Address> {
243        any::<[u8; 20]>().prop_map(Address::from)
244    }
245
246    fn arb_u256() -> impl Strategy<Value = U256> {
247        any::<[u64; 4]>().prop_map(U256::from_limbs)
248    }
249
250    // -- BASIC TESTS -----------------------------------------------------------
251
252    #[test]
253    fn test_slot_size() {
254        // slot (U256) 32 bytes + LayoutCtx (usize) 8 bytes + Address 20 bytes (+4 for byte alignment)
255        assert_eq!(size_of::<Slot<U256>>(), 64);
256        assert_eq!(size_of::<Slot<Address>>(), 64);
257        assert_eq!(size_of::<Slot<bool>>(), 64);
258    }
259
260    #[test]
261    fn test_slot_number_extraction() -> eyre::Result<()> {
262        let (mut storage, address) = setup_storage();
263        StorageCtx::enter(&mut storage, || {
264            let slot_0 = Slot::<U256>::new(U256::ZERO, address);
265            let slot_1 = Slot::<Address>::new(U256::ONE, address);
266            let slot_max = Slot::<bool>::new(U256::MAX, address);
267            assert_eq!(slot_0.slot(), U256::ZERO);
268            assert_eq!(slot_1.slot(), U256::ONE);
269            assert_eq!(slot_max.slot(), U256::MAX);
270            Ok(())
271        })
272    }
273
274    #[test]
275    fn test_slot_edge_cases() -> eyre::Result<()> {
276        let (mut storage, address) = setup_storage();
277        StorageCtx::enter(&mut storage, || {
278            // U256::ZERO slot
279            let mut slot_zero = Slot::<U256>::new(U256::ZERO, address);
280            assert_eq!(slot_zero.slot(), U256::ZERO);
281            let value_zero = U256::random();
282            slot_zero.write(value_zero)?;
283            assert_eq!(slot_zero.read()?, value_zero);
284
285            // U256::MAX slot
286            let mut slot_max = Slot::<U256>::new(U256::MAX, address);
287            assert_eq!(slot_max.slot(), U256::MAX);
288            let value_max = U256::random();
289            slot_max.write(value_max)?;
290            assert_eq!(slot_max.read()?, value_max);
291
292            Ok(())
293        })
294    }
295
296    #[test]
297    fn test_slot_read_write_types() -> eyre::Result<()> {
298        let (mut storage, address) = setup_storage();
299        let slot_num = U256::random();
300        let test_value = U256::random();
301
302        StorageCtx::enter(&mut storage, || -> eyre::Result<()> {
303            // U256
304            let mut u256_slot = Slot::<U256>::new(slot_num, address);
305            u256_slot.write(test_value)?;
306            assert_eq!(u256_slot.read()?, test_value);
307
308            // Address
309            let test_addr = Address::random();
310            let mut addr_slot = Slot::<Address>::new(U256::from(1), address);
311            addr_slot.write(test_addr)?;
312            assert_eq!(addr_slot.read()?, test_addr);
313
314            // bool
315            let mut bool_slot = Slot::<bool>::new(U256::from(2), address);
316            bool_slot.write(true)?;
317            assert!(bool_slot.read()?);
318            bool_slot.write(false)?;
319            assert!(!bool_slot.read()?);
320
321            // String
322            let mut str_slot = Slot::<String>::new(U256::from(3), address);
323            str_slot.write("TestToken".to_string())?;
324            assert_eq!(str_slot.read()?, "TestToken");
325
326            Ok(())
327        })?;
328
329        // Verify U256 actually wrote to slot
330        let raw = storage.sload(address, slot_num)?;
331        assert_eq!(raw, test_value);
332        Ok(())
333    }
334
335    #[test]
336    fn test_slot_default_and_overwrite() -> eyre::Result<()> {
337        let (mut storage, address) = setup_storage();
338        StorageCtx::enter(&mut storage, || {
339            // Default value is zero
340            let mut slot = Slot::<u64>::new(U256::random(), address);
341            assert_eq!(slot.read()?, 0);
342
343            // Write and overwrite
344            slot.write(100)?;
345            assert_eq!(slot.read()?, 100);
346            slot.write(200)?;
347            assert_eq!(slot.read()?, 200);
348
349            Ok(())
350        })
351    }
352
353    proptest! {
354        #![proptest_config(ProptestConfig::with_cases(500))]
355
356        #[test]
357        fn proptest_slot_read_write_u256(slot in arb_u256(),value in arb_u256()) {
358            let (mut storage, address) = setup_storage();
359            StorageCtx::enter(&mut storage, || -> std::result::Result<(), TestCaseError> {
360                let mut slot = Slot::<U256>::new(slot, address);
361
362                // Write and read back
363                slot.write(value).unwrap();
364                let loaded = slot.read().unwrap();
365                prop_assert_eq!(loaded, value, "roundtrip failed");
366
367                // Delete and verify
368                slot.delete().unwrap();
369                let after_delete = slot.read().unwrap();
370                prop_assert_eq!(after_delete, U256::ZERO, "not zero after delete");
371                Ok(())
372            })?;
373        }
374
375        #[test]
376        fn proptest_slot_read_write_address(slot in arb_u256(),addr_value in arb_address()) {
377            let (mut storage, address) = setup_storage();
378            StorageCtx::enter(&mut storage, || -> std::result::Result<(), TestCaseError> {
379                let mut slot = Slot::<Address>::new(slot, address);
380
381                // Write and read back
382                slot.write(addr_value).unwrap();
383                let loaded = slot.read().unwrap();
384                prop_assert_eq!(loaded, addr_value, "address roundtrip failed");
385                Ok(())
386            })?;
387        }
388
389        #[test]
390        fn proptest_slot_isolation(slot1 in arb_u256(), slot2 in arb_u256(), value1 in arb_u256(), value2 in arb_u256()) {
391            let (mut storage, address) = setup_storage();
392            StorageCtx::enter(&mut storage, || -> std::result::Result<(), TestCaseError> {
393                let mut slot1 = Slot::<U256>::new(slot1, address);
394                let mut slot2 = Slot::<U256>::new(slot2, address);
395
396                slot1.write(value1).unwrap();
397                slot2.write(value2).unwrap();
398
399                // Verify both slots retain their independent values
400                let loaded1 = slot1.read().unwrap();
401                let loaded2 = slot2.read().unwrap();
402
403                prop_assert_eq!(loaded1, value1, "slot 1 value changed");
404                prop_assert_eq!(loaded2, value2, "slot 2 value changed");
405
406                // Delete slot 1, verify slot 2 unaffected
407                slot1.delete().unwrap();
408                let after_delete1 = slot1.read().unwrap();
409                let after_delete2 = slot2.read().unwrap();
410
411                prop_assert_eq!(after_delete1, U256::ZERO, "slot 1 not deleted");
412                prop_assert_eq!(after_delete2, value2, "slot 2 affected by slot 1 delete");
413                Ok(())
414            })?;
415        }
416    }
417
418    // -- RUNTIME SLOT OFFSET TESTS --------------------------------------------
419
420    #[test]
421    fn test_slot_at_offset() -> eyre::Result<()> {
422        let (mut storage, address) = setup_storage();
423        StorageCtx::enter(&mut storage, || {
424            let pair_key = B256::random();
425            let base = pair_key.mapping_slot(U256::ZERO);
426            let test_addr = Address::random();
427
428            // Write, read, delete
429            let mut slot = Slot::<Address>::new_at_offset(base, 0, address);
430            slot.write(test_addr)?;
431            assert_eq!(slot.read()?, test_addr);
432            slot.delete()?;
433            assert_eq!(slot.read()?, Address::ZERO);
434
435            Ok(())
436        })
437    }
438
439    #[test]
440    fn test_multiple_primitive_fields() -> eyre::Result<()> {
441        let (mut storage, address) = setup_storage();
442        StorageCtx::enter(&mut storage, || {
443            let key = B256::random();
444            let base = key.mapping_slot(U256::ZERO);
445
446            let field_0 = Address::random();
447            let field_1: u64 = (U256::random() % U256::from(u64::MAX)).to();
448            let field_2 = U256::random();
449
450            Slot::<Address>::new_at_offset(base, 0, address).write(field_0)?;
451            Slot::<u64>::new_at_offset(base, 1, address).write(field_1)?;
452            Slot::<U256>::new_at_offset(base, 2, address).write(field_2)?;
453
454            assert_eq!(
455                Slot::<Address>::new_at_offset(base, 0, address).read()?,
456                field_0
457            );
458            assert_eq!(
459                Slot::<u64>::new_at_offset(base, 1, address).read()?,
460                field_1
461            );
462            assert_eq!(
463                Slot::<U256>::new_at_offset(base, 2, address).read()?,
464                field_2
465            );
466
467            Ok(())
468        })
469    }
470
471    // -- TRANSIENT STORAGE TESTS ------------------------------------------------
472
473    #[test]
474    fn test_transient_ops() -> eyre::Result<()> {
475        let (mut storage, address) = setup_storage();
476        StorageCtx::enter(&mut storage, || {
477            // U256: default, roundtrip, overwrite, delete
478            let mut u256_slot = Slot::<U256>::new(U256::from(1), address);
479            assert_eq!(u256_slot.t_read()?, U256::ZERO);
480
481            let num1 = U256::random();
482            let num2 = U256::random();
483            u256_slot.t_write(num1)?;
484            assert_eq!(u256_slot.t_read()?, num1);
485            u256_slot.t_write(num2)?;
486            assert_eq!(u256_slot.t_read()?, num2);
487            u256_slot.t_delete()?;
488            assert_eq!(u256_slot.t_read()?, U256::ZERO);
489
490            // Address: default, roundtrip, overwrite, delete
491            let mut addr_slot = Slot::<Address>::new(U256::from(2), address);
492            assert_eq!(addr_slot.t_read()?, Address::ZERO);
493
494            let addr1 = Address::random();
495            let addr2 = Address::random();
496            addr_slot.t_write(addr1)?;
497            assert_eq!(addr_slot.t_read()?, addr1);
498            addr_slot.t_write(addr2)?;
499            assert_eq!(addr_slot.t_read()?, addr2);
500            addr_slot.t_delete()?;
501            assert_eq!(addr_slot.t_read()?, Address::ZERO);
502
503            // bool: default, roundtrip, overwrite, delete
504            let mut bool_slot = Slot::<bool>::new(U256::from(3), address);
505            assert!(!bool_slot.t_read()?);
506
507            bool_slot.t_write(true)?;
508            assert!(bool_slot.t_read()?);
509            bool_slot.t_write(false)?;
510            assert!(!bool_slot.t_read()?);
511            bool_slot.t_delete()?;
512            assert!(!bool_slot.t_read()?);
513
514            Ok(())
515        })
516    }
517
518    #[test]
519    fn test_transient_persistence_isolation() -> eyre::Result<()> {
520        let (mut storage, address) = setup_storage();
521        let slot_num = U256::random();
522        let t_value = U256::random();
523        let s_value = U256::random();
524
525        StorageCtx::enter(&mut storage, || -> eyre::Result<()> {
526            let mut slot = Slot::<U256>::new(slot_num, address);
527
528            // Write different values to each storage type
529            slot.write(s_value)?;
530            slot.t_write(t_value)?;
531            assert_eq!(slot.read()?, s_value);
532            assert_eq!(slot.t_read()?, t_value);
533
534            // Delete transient, persistent remains
535            slot.t_delete()?;
536            assert_eq!(slot.read()?, s_value);
537            assert_eq!(slot.t_read()?, U256::ZERO);
538
539            // Restore transient value
540            slot.t_write(t_value)?;
541            assert_eq!(slot.t_read()?, t_value);
542
543            Ok(())
544        })?;
545
546        // Simulate new block
547        storage.clear_transient();
548
549        // Transient cleared, persistent remains
550        StorageCtx::enter(&mut storage, || {
551            let slot = Slot::<U256>::new(slot_num, address);
552            assert_eq!(slot.read()?, s_value);
553            assert_eq!(slot.t_read()?, U256::ZERO);
554            Ok(())
555        })
556    }
557}