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> {}