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, address);
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    fn sinc(&mut self, slot: U256, delta: U256) -> Result<()> {
129        let mut storage = StorageCtx;
130        storage.sinc(self.address, slot, delta)
131    }
132
133    fn sdec(&mut self, slot: U256, delta: U256) -> Result<()> {
134        let mut storage = StorageCtx;
135        storage.sdec(self.address, slot, delta)
136    }
137}
138
139impl Slot<U256> {
140    /// Increments this slot by `delta`.
141    #[inline]
142    pub fn sinc(&mut self, delta: U256) -> Result<()> {
143        <Self as StorageOps>::sinc(self, self.slot, delta)
144    }
145
146    /// Decrements this slot by `delta`.
147    #[inline]
148    pub fn sdec(&mut self, delta: U256) -> Result<()> {
149        <Self as StorageOps>::sdec(self, self.slot, delta)
150    }
151}
152
153/// Wrapper that routes storage operations through transient storage (TLOAD/TSTORE).
154///
155/// Created via `Slot::transient()` and used by `t_read()`, `t_write()`, `t_delete()`.
156struct TransientOps {
157    address: Address,
158}
159
160impl StorageOps for TransientOps {
161    fn load(&self, slot: U256) -> Result<U256> {
162        let storage = StorageCtx;
163        storage.tload(self.address, slot)
164    }
165
166    fn store(&mut self, slot: U256, value: U256) -> Result<()> {
167        let mut storage = StorageCtx;
168        storage.tstore(self.address, slot, value)
169    }
170}
171
172impl<T: Storable> Slot<T> {
173    /// Returns a transient storage operations wrapper for this slot's address.
174    fn transient(&self) -> TransientOps {
175        TransientOps {
176            address: self.address,
177        }
178    }
179}
180
181impl<T: Storable> Handler<T> for Slot<T> {
182    /// Reads a value from storage at this slot.
183    ///
184    /// This method delegates to the `Storable::load` implementation,
185    /// which may read one or more consecutive slots depending on the type.
186    ///
187    /// Uses thread-local storage context initialized by [`StorageCtx`].
188    ///
189    /// # Example
190    ///
191    /// ```ignore
192    /// let name_slot = Slot::<String>::new(slots::NAME, address_rc);
193    /// let name = name_slot.read().unwrap();
194    /// ```
195    #[inline]
196    fn read(&self) -> Result<T> {
197        T::load(self, self.slot, self.ctx)
198    }
199
200    /// Writes a value to storage at this slot.
201    ///
202    /// This method delegates to the `Storable::store` implementation,
203    /// which may write one or more consecutive slots depending on the type.
204    ///
205    /// Uses thread-local storage context initialized by [`StorageCtx`].
206    ///
207    /// # Example
208    ///
209    /// ```ignore
210    /// let mut name_slot = Slot::<String>::new(slots::NAME, address_rc);
211    /// name_slot.write("MyToken".to_string()).unwrap();
212    /// ```
213    #[inline]
214    fn write(&mut self, value: T) -> Result<()> {
215        value.store(self, self.slot, self.ctx)
216    }
217
218    /// Deletes the value at this slot (sets all slots to zero).
219    ///
220    /// This method delegates to the `Storable::delete` implementation,
221    /// which sets the appropriate slots to zero.
222    ///
223    /// Uses thread-local storage context initialized by [`StorageCtx`].
224    ///
225    /// # Example
226    ///
227    /// ```ignore
228    /// let mut name_slot = Slot::<String>::new(slots::NAME, address_rc);
229    /// name_slot.delete().unwrap();
230    /// ```
231    #[inline]
232    fn delete(&mut self) -> Result<()> {
233        T::delete(self, self.slot, self.ctx)
234    }
235
236    /// Reads a value from transient storage at this slot.
237    #[inline]
238    fn t_read(&self) -> Result<T> {
239        T::load(&self.transient(), self.slot, self.ctx)
240    }
241
242    /// Writes a value to transient storage at this slot.
243    #[inline]
244    fn t_write(&mut self, value: T) -> Result<()> {
245        value.store(&mut self.transient(), self.slot, self.ctx)
246    }
247
248    /// Deletes the value at this slot in transient storage (sets to zero).
249    #[inline]
250    fn t_delete(&mut self) -> Result<()> {
251        T::delete(&mut self.transient(), self.slot, self.ctx)
252    }
253}
254
255#[cfg(test)]
256mod tests {
257    use super::*;
258    use crate::{
259        storage::{Handler, PrecompileStorageProvider, StorageKey},
260        test_util::setup_storage,
261    };
262    use alloy::primitives::{Address, B256};
263    use proptest::prelude::*;
264
265    // Property test strategies
266    fn arb_address() -> impl Strategy<Value = Address> {
267        any::<[u8; 20]>().prop_map(Address::from)
268    }
269
270    fn arb_u256() -> impl Strategy<Value = U256> {
271        any::<[u64; 4]>().prop_map(U256::from_limbs)
272    }
273
274    // -- BASIC TESTS -----------------------------------------------------------
275
276    #[test]
277    fn test_slot_size() {
278        // slot (U256) 32 bytes + LayoutCtx (usize) 8 bytes + Address 20 bytes (+4 for byte alignment)
279        assert_eq!(size_of::<Slot<U256>>(), 64);
280        assert_eq!(size_of::<Slot<Address>>(), 64);
281        assert_eq!(size_of::<Slot<bool>>(), 64);
282    }
283
284    #[test]
285    fn test_slot_number_extraction() -> eyre::Result<()> {
286        let (mut storage, address) = setup_storage();
287        StorageCtx::enter(&mut storage, || {
288            let slot_0 = Slot::<U256>::new(U256::ZERO, address);
289            let slot_1 = Slot::<Address>::new(U256::ONE, address);
290            let slot_max = Slot::<bool>::new(U256::MAX, address);
291            assert_eq!(slot_0.slot(), U256::ZERO);
292            assert_eq!(slot_1.slot(), U256::ONE);
293            assert_eq!(slot_max.slot(), U256::MAX);
294            Ok(())
295        })
296    }
297
298    #[test]
299    fn test_slot_edge_cases() -> eyre::Result<()> {
300        let (mut storage, address) = setup_storage();
301        StorageCtx::enter(&mut storage, || {
302            // U256::ZERO slot
303            let mut slot_zero = Slot::<U256>::new(U256::ZERO, address);
304            assert_eq!(slot_zero.slot(), U256::ZERO);
305            let value_zero = U256::random();
306            slot_zero.write(value_zero)?;
307            assert_eq!(slot_zero.read()?, value_zero);
308
309            // U256::MAX slot
310            let mut slot_max = Slot::<U256>::new(U256::MAX, address);
311            assert_eq!(slot_max.slot(), U256::MAX);
312            let value_max = U256::random();
313            slot_max.write(value_max)?;
314            assert_eq!(slot_max.read()?, value_max);
315
316            Ok(())
317        })
318    }
319
320    #[test]
321    fn test_slot_read_write_types() -> eyre::Result<()> {
322        let (mut storage, address) = setup_storage();
323        let slot_num = U256::random();
324        let test_value = U256::random();
325
326        StorageCtx::enter(&mut storage, || -> eyre::Result<()> {
327            // U256
328            let mut u256_slot = Slot::<U256>::new(slot_num, address);
329            u256_slot.write(test_value)?;
330            assert_eq!(u256_slot.read()?, test_value);
331
332            // Address
333            let test_addr = Address::random();
334            let mut addr_slot = Slot::<Address>::new(U256::from(1), address);
335            addr_slot.write(test_addr)?;
336            assert_eq!(addr_slot.read()?, test_addr);
337
338            // bool
339            let mut bool_slot = Slot::<bool>::new(U256::from(2), address);
340            bool_slot.write(true)?;
341            assert!(bool_slot.read()?);
342            bool_slot.write(false)?;
343            assert!(!bool_slot.read()?);
344
345            // String
346            let mut str_slot = Slot::<String>::new(U256::from(3), address);
347            str_slot.write("TestToken".to_string())?;
348            assert_eq!(str_slot.read()?, "TestToken");
349
350            Ok(())
351        })?;
352
353        // Verify U256 actually wrote to slot
354        let raw = storage.sload(address, slot_num)?;
355        assert_eq!(raw, test_value);
356        Ok(())
357    }
358
359    #[test]
360    fn test_u256_slot_sinc_sdec() -> eyre::Result<()> {
361        let (mut storage, address) = setup_storage();
362
363        StorageCtx::enter(&mut storage, || -> eyre::Result<()> {
364            let mut slot = Slot::<U256>::new(U256::from(7), address);
365
366            slot.sinc(U256::from(10))?;
367            assert_eq!(slot.read()?, U256::from(10));
368
369            slot.sdec(U256::from(3))?;
370            assert_eq!(slot.read()?, U256::from(7));
371
372            Ok(())
373        })
374    }
375
376    #[test]
377    fn test_slot_default_and_overwrite() -> eyre::Result<()> {
378        let (mut storage, address) = setup_storage();
379        StorageCtx::enter(&mut storage, || {
380            // Default value is zero
381            let mut slot = Slot::<u64>::new(U256::random(), address);
382            assert_eq!(slot.read()?, 0);
383
384            // Write and overwrite
385            slot.write(100)?;
386            assert_eq!(slot.read()?, 100);
387            slot.write(200)?;
388            assert_eq!(slot.read()?, 200);
389
390            Ok(())
391        })
392    }
393
394    proptest! {
395        #![proptest_config(ProptestConfig::with_cases(500))]
396
397        #[test]
398        fn proptest_slot_read_write_u256(slot in arb_u256(),value in arb_u256()) {
399            let (mut storage, address) = setup_storage();
400            StorageCtx::enter(&mut storage, || -> std::result::Result<(), TestCaseError> {
401                let mut slot = Slot::<U256>::new(slot, address);
402
403                // Write and read back
404                slot.write(value).unwrap();
405                let loaded = slot.read().unwrap();
406                prop_assert_eq!(loaded, value, "roundtrip failed");
407
408                // Delete and verify
409                slot.delete().unwrap();
410                let after_delete = slot.read().unwrap();
411                prop_assert_eq!(after_delete, U256::ZERO, "not zero after delete");
412                Ok(())
413            })?;
414        }
415
416        #[test]
417        fn proptest_slot_read_write_address(slot in arb_u256(),addr_value in arb_address()) {
418            let (mut storage, address) = setup_storage();
419            StorageCtx::enter(&mut storage, || -> std::result::Result<(), TestCaseError> {
420                let mut slot = Slot::<Address>::new(slot, address);
421
422                // Write and read back
423                slot.write(addr_value).unwrap();
424                let loaded = slot.read().unwrap();
425                prop_assert_eq!(loaded, addr_value, "address roundtrip failed");
426                Ok(())
427            })?;
428        }
429
430        #[test]
431        fn proptest_slot_isolation(slot1 in arb_u256(), slot2 in arb_u256(), value1 in arb_u256(), value2 in arb_u256()) {
432            let (mut storage, address) = setup_storage();
433            StorageCtx::enter(&mut storage, || -> std::result::Result<(), TestCaseError> {
434                let mut slot1 = Slot::<U256>::new(slot1, address);
435                let mut slot2 = Slot::<U256>::new(slot2, address);
436
437                slot1.write(value1).unwrap();
438                slot2.write(value2).unwrap();
439
440                // Verify both slots retain their independent values
441                let loaded1 = slot1.read().unwrap();
442                let loaded2 = slot2.read().unwrap();
443
444                prop_assert_eq!(loaded1, value1, "slot 1 value changed");
445                prop_assert_eq!(loaded2, value2, "slot 2 value changed");
446
447                // Delete slot 1, verify slot 2 unaffected
448                slot1.delete().unwrap();
449                let after_delete1 = slot1.read().unwrap();
450                let after_delete2 = slot2.read().unwrap();
451
452                prop_assert_eq!(after_delete1, U256::ZERO, "slot 1 not deleted");
453                prop_assert_eq!(after_delete2, value2, "slot 2 affected by slot 1 delete");
454                Ok(())
455            })?;
456        }
457    }
458
459    // -- RUNTIME SLOT OFFSET TESTS --------------------------------------------
460
461    #[test]
462    fn test_slot_at_offset() -> eyre::Result<()> {
463        let (mut storage, address) = setup_storage();
464        StorageCtx::enter(&mut storage, || {
465            let pair_key = B256::random();
466            let base = pair_key.mapping_slot(U256::ZERO);
467            let test_addr = Address::random();
468
469            // Write, read, delete
470            let mut slot = Slot::<Address>::new_at_offset(base, 0, address);
471            slot.write(test_addr)?;
472            assert_eq!(slot.read()?, test_addr);
473            slot.delete()?;
474            assert_eq!(slot.read()?, Address::ZERO);
475
476            Ok(())
477        })
478    }
479
480    #[test]
481    fn test_multiple_primitive_fields() -> eyre::Result<()> {
482        let (mut storage, address) = setup_storage();
483        StorageCtx::enter(&mut storage, || {
484            let key = B256::random();
485            let base = key.mapping_slot(U256::ZERO);
486
487            let field_0 = Address::random();
488            let field_1: u64 = (U256::random() % U256::from(u64::MAX)).to();
489            let field_2 = U256::random();
490
491            Slot::<Address>::new_at_offset(base, 0, address).write(field_0)?;
492            Slot::<u64>::new_at_offset(base, 1, address).write(field_1)?;
493            Slot::<U256>::new_at_offset(base, 2, address).write(field_2)?;
494
495            assert_eq!(
496                Slot::<Address>::new_at_offset(base, 0, address).read()?,
497                field_0
498            );
499            assert_eq!(
500                Slot::<u64>::new_at_offset(base, 1, address).read()?,
501                field_1
502            );
503            assert_eq!(
504                Slot::<U256>::new_at_offset(base, 2, address).read()?,
505                field_2
506            );
507
508            Ok(())
509        })
510    }
511
512    // -- TRANSIENT STORAGE TESTS ------------------------------------------------
513
514    #[test]
515    fn test_transient_ops() -> eyre::Result<()> {
516        let (mut storage, address) = setup_storage();
517        StorageCtx::enter(&mut storage, || {
518            // U256: default, roundtrip, overwrite, delete
519            let mut u256_slot = Slot::<U256>::new(U256::from(1), address);
520            assert_eq!(u256_slot.t_read()?, U256::ZERO);
521
522            let num1 = U256::random();
523            let num2 = U256::random();
524            u256_slot.t_write(num1)?;
525            assert_eq!(u256_slot.t_read()?, num1);
526            u256_slot.t_write(num2)?;
527            assert_eq!(u256_slot.t_read()?, num2);
528            u256_slot.t_delete()?;
529            assert_eq!(u256_slot.t_read()?, U256::ZERO);
530
531            // Address: default, roundtrip, overwrite, delete
532            let mut addr_slot = Slot::<Address>::new(U256::from(2), address);
533            assert_eq!(addr_slot.t_read()?, Address::ZERO);
534
535            let addr1 = Address::random();
536            let addr2 = Address::random();
537            addr_slot.t_write(addr1)?;
538            assert_eq!(addr_slot.t_read()?, addr1);
539            addr_slot.t_write(addr2)?;
540            assert_eq!(addr_slot.t_read()?, addr2);
541            addr_slot.t_delete()?;
542            assert_eq!(addr_slot.t_read()?, Address::ZERO);
543
544            // bool: default, roundtrip, overwrite, delete
545            let mut bool_slot = Slot::<bool>::new(U256::from(3), address);
546            assert!(!bool_slot.t_read()?);
547
548            bool_slot.t_write(true)?;
549            assert!(bool_slot.t_read()?);
550            bool_slot.t_write(false)?;
551            assert!(!bool_slot.t_read()?);
552            bool_slot.t_delete()?;
553            assert!(!bool_slot.t_read()?);
554
555            Ok(())
556        })
557    }
558
559    #[test]
560    fn test_transient_persistence_isolation() -> eyre::Result<()> {
561        let (mut storage, address) = setup_storage();
562        let slot_num = U256::random();
563        let t_value = U256::random();
564        let s_value = U256::random();
565
566        StorageCtx::enter(&mut storage, || -> eyre::Result<()> {
567            let mut slot = Slot::<U256>::new(slot_num, address);
568
569            // Write different values to each storage type
570            slot.write(s_value)?;
571            slot.t_write(t_value)?;
572            assert_eq!(slot.read()?, s_value);
573            assert_eq!(slot.t_read()?, t_value);
574
575            // Delete transient, persistent remains
576            slot.t_delete()?;
577            assert_eq!(slot.read()?, s_value);
578            assert_eq!(slot.t_read()?, U256::ZERO);
579
580            // Restore transient value
581            slot.t_write(t_value)?;
582            assert_eq!(slot.t_read()?, t_value);
583
584            Ok(())
585        })?;
586
587        // Simulate new block
588        storage.clear_transient();
589
590        // Transient cleared, persistent remains
591        StorageCtx::enter(&mut storage, || {
592            let slot = Slot::<U256>::new(slot_num, address);
593            assert_eq!(slot.read()?, s_value);
594            assert_eq!(slot.t_read()?, U256::ZERO);
595            Ok(())
596        })
597    }
598}