Skip to main content

thruput_sdk/storage/
map.rs

1use super::deletable::Deletable;
2use super::keypath::Keypath;
3use super::node::StorageType;
4use super::slot::{SlotMut, SlotRef};
5use core::fmt::Debug;
6use core::marker::PhantomData;
7use core::ops::{Deref, DerefMut};
8use xdrs::WriteXDR;
9
10/// A persistent map from keys of type `K` to storage values of type `V`.
11///
12/// # Key identity is the XDR encoding
13///
14/// An entry's storage slot is addressed by `sha256(XDR-encode(key))` — the
15/// key's [`WriteXDR`] encoding *is* its identity. Two keys address the same
16/// entry if and only if they encode to the same bytes; the in-memory `K` value
17/// (and any `PartialEq`/`Eq`/`Hash` it has) is never consulted by the storage
18/// layer. In particular, no `Map` operation consults `K`'s `PartialEq`/`Eq`:
19/// even the duplicate-key check in [`at_disjoint_mut`](Self::at_disjoint_mut)
20/// compares the derived storage keypaths, not the keys themselves.
21///
22/// # Key encoding invariant
23///
24/// A key's storage slot is `sha256` of its framed `WriteXDR` encoding, so the
25/// encoding **is** the key's identity. Key types MUST encode injectively:
26/// distinct keys must produce distinct bytes. All SDK-provided and
27/// `#[derive(XDR)]` types satisfy this. A hand-written, non-canonical
28/// `WriteXDR` where `a != b` but `encode(a) == encode(b)` will silently alias
29/// the two keys onto one slot.
30#[derive(Debug)]
31pub struct Map<K, V> {
32    keypath: Keypath,
33
34    // The type should be covariant in K and V, but (for drop
35    // purposes) doesn't actually own values of either type
36    _marker: PhantomData<fn() -> (K, V)>,
37}
38
39impl<K, V> From<Keypath> for Map<K, V> {
40    fn from(keypath: Keypath) -> Self {
41        let _marker = PhantomData;
42        Self { keypath, _marker }
43    }
44}
45
46impl<K, V> Map<K, V> {
47    pub fn keypath(&self) -> &Keypath {
48        &self.keypath
49    }
50
51    pub fn at(&self, entry_key: &K) -> SlotRef<'_, Self, V>
52    where
53        K: WriteXDR,
54        V: StorageType,
55    {
56        let keypath = self.make_entry_keypath(entry_key);
57        let slot = V::from(keypath);
58        SlotRef::new(self, slot)
59    }
60
61    pub fn at_mut(&mut self, entry_key: &K) -> SlotMut<'_, Self, V>
62    where
63        K: WriteXDR,
64        V: StorageType,
65    {
66        let keypath = self.make_entry_keypath(entry_key);
67        let slot = V::from(keypath);
68        SlotMut::new(self, slot)
69    }
70
71    /// Like [`at_mut`](Self::at_mut), but every successful write through the
72    /// returned handle is followed by a request for an exclusive lock on the
73    /// entry, so concurrent writers to it conflict. The lock lasts for the
74    /// rest of the transaction: later writes through ordinary handles keep
75    /// it, and only `clear_flags` removes it. Building, reading or dropping
76    /// the handle requests nothing.
77    pub fn at_mut_exclusive(&mut self, entry_key: &K) -> SlotMut<'_, Self, V>
78    where
79        K: WriteXDR,
80        V: StorageType,
81    {
82        let mut keypath = self.make_entry_keypath(entry_key);
83        keypath.set_exclusive();
84        let slot = V::from(keypath);
85        SlotMut::new(self, slot)
86    }
87
88    #[inline(always)]
89    pub fn delete_at(&mut self, entry_key: &K)
90    where
91        K: WriteXDR,
92        V: StorageType + Deletable,
93    {
94        self.at_mut(entry_key).delete();
95    }
96
97    /// Mutably borrow N elements at once. Panics if any keys are equal.
98    ///
99    /// Returns a `DisjointMut` guard that borrows the map exclusively
100    /// and provides `DerefMut` access to the `[V; N]` array.
101    ///
102    /// Mirrors `HashMap::get_disjoint_mut`.
103    ///
104    /// The duplicate-key check compares the derived storage keypaths — the
105    /// actual slot identity — so it does not consult `K`'s `PartialEq` and is
106    /// exact regardless of how (or whether) `K` implements it.
107    pub fn at_disjoint_mut<const N: usize>(
108        &mut self,
109        keys: [&K; N],
110    ) -> DisjointMut<'_, Self, V, N>
111    where
112        K: WriteXDR,
113        V: StorageType,
114    {
115        let keypaths = keys.map(|k| self.make_entry_keypath(k));
116
117        // O(n^2) check for keys that resolve to the same storage slot.
118        for i in 0..N {
119            for j in (i + 1)..N {
120                assert!(
121                    !keypaths[i].addresses_same_slot(&keypaths[j]),
122                    "at_disjoint_mut: duplicate keys at indices {i} and {j}"
123                );
124            }
125        }
126
127        let slots = keypaths.map(V::from);
128        DisjointMut {
129            slots,
130            _borrow: PhantomData,
131        }
132    }
133
134    /// Mutably borrow N elements at once without checking for duplicates.
135    ///
136    /// # Safety
137    ///
138    /// The caller must ensure all keys are disjoint. Passing duplicate
139    /// keys creates aliased handles to the same storage slot across the
140    /// syscall boundary, which may silently corrupt data.
141    pub unsafe fn at_disjoint_unchecked_mut<const N: usize>(
142        &mut self,
143        keys: [&K; N],
144    ) -> DisjointMut<'_, Self, V, N>
145    where
146        K: WriteXDR,
147        V: StorageType,
148    {
149        let slots = keys.map(|k| V::from(self.make_entry_keypath(k)));
150        DisjointMut {
151            slots,
152            _borrow: PhantomData,
153        }
154    }
155
156    fn make_entry_keypath(&self, entry_key: &K) -> Keypath
157    where
158        K: WriteXDR,
159    {
160        self.keypath.child(entry_key)
161    }
162}
163
164/// Borrow guard holding N mutable storage slots from a Map.
165/// Borrows the map exclusively for its lifetime.
166pub struct DisjointMut<'a, Owner, S, const N: usize> {
167    slots: [S; N],
168    _borrow: PhantomData<&'a mut Owner>,
169}
170
171impl<Owner, S, const N: usize> DisjointMut<'_, Owner, S, N> {
172    /// The caller must hold `&mut Owner` for the guard's lifetime and ensure
173    /// the slots are pairwise disjoint.
174    pub(super) fn new(slots: [S; N]) -> Self {
175        Self {
176            slots,
177            _borrow: PhantomData,
178        }
179    }
180}
181
182impl<Owner, S, const N: usize> Deref for DisjointMut<'_, Owner, S, N> {
183    type Target = [S; N];
184
185    fn deref(&self) -> &[S; N] {
186        &self.slots
187    }
188}
189
190impl<Owner, S, const N: usize> DerefMut for DisjointMut<'_, Owner, S, N> {
191    fn deref_mut(&mut self) -> &mut [S; N] {
192        &mut self.slots
193    }
194}
195
196impl<K, V> super::sealed::Sealed for Map<K, V> {}
197impl<K, V> super::node::StorageType for Map<K, V> {}