Skip to main content

miden_base/types/
storage.rs

1use miden_base_sys::bindings::{StorageSlotId, storage};
2use miden_stdlib_sys::{Digest, Felt, Word, felt};
3
4/// Packs a scalar felt into the low limb of a storage word.
5fn padded_word_from_felt(value: Felt) -> Word {
6    Word::new([felt!(0), felt!(0), felt!(0), value])
7}
8
9/// Extracts a scalar felt from a storage word with zero-padded high limbs.
10fn felt_from_padded_word(value: Word) -> Result<Felt, &'static str> {
11    if value[0] != felt!(0) || value[1] != felt!(0) || value[2] != felt!(0) {
12        return Err("expected zero padding in the upper three felts");
13    }
14
15    Ok(value[3])
16}
17
18/// A type that can be stored in (or loaded from) account storage.
19///
20/// Storage slots and map items store a single [`Word`]. Implementations must define a reversible
21/// conversion between the Rust type and a [`Word`].
22pub trait WordValue: Sized {
23    /// Converts the value into the single storage word used by the host.
24    fn try_into_word(self) -> Result<Word, &'static str>;
25
26    /// Reconstructs the value from the single storage word returned by the host.
27    fn try_from_word(word: Word) -> Result<Self, &'static str>;
28}
29
30impl WordValue for Word {
31    fn try_into_word(self) -> Result<Word, &'static str> {
32        Ok(self)
33    }
34
35    fn try_from_word(word: Word) -> Result<Self, &'static str> {
36        Ok(word)
37    }
38}
39
40impl WordValue for Felt {
41    fn try_into_word(self) -> Result<Word, &'static str> {
42        Ok(padded_word_from_felt(self))
43    }
44
45    fn try_from_word(word: Word) -> Result<Self, &'static str> {
46        felt_from_padded_word(word)
47    }
48}
49
50impl WordValue for Digest {
51    fn try_into_word(self) -> Result<Word, &'static str> {
52        Ok(self.into())
53    }
54
55    fn try_from_word(word: Word) -> Result<Self, &'static str> {
56        Ok(word.try_into().unwrap())
57    }
58}
59
60impl WordValue for miden_base_sys::bindings::AccountId {
61    fn try_into_word(self) -> Result<Word, &'static str> {
62        Ok(self.into())
63    }
64
65    fn try_from_word(word: Word) -> Result<Self, &'static str> {
66        word.try_into()
67    }
68}
69
70impl WordValue for miden_base_sys::bindings::Recipient {
71    fn try_into_word(self) -> Result<Word, &'static str> {
72        Ok(self.into())
73    }
74
75    fn try_from_word(word: Word) -> Result<Self, &'static str> {
76        Ok(word.into())
77    }
78}
79
80impl WordValue for miden_base_sys::bindings::Tag {
81    fn try_into_word(self) -> Result<Word, &'static str> {
82        Ok(self.into())
83    }
84
85    fn try_from_word(word: Word) -> Result<Self, &'static str> {
86        word.try_into()
87    }
88}
89
90impl WordValue for miden_base_sys::bindings::NoteIdx {
91    fn try_into_word(self) -> Result<Word, &'static str> {
92        Ok(self.into())
93    }
94
95    fn try_from_word(word: Word) -> Result<Self, &'static str> {
96        word.try_into()
97    }
98}
99
100impl WordValue for miden_base_sys::bindings::NoteType {
101    fn try_into_word(self) -> Result<Word, &'static str> {
102        Ok(self.into())
103    }
104
105    fn try_from_word(word: Word) -> Result<Self, &'static str> {
106        word.try_into()
107    }
108}
109
110/// A type that can be used as a key in a storage map.
111///
112/// Map keys are passed by value for lookups to avoid requiring `Clone` just to materialize a
113/// [`Word`] for the host call.
114pub trait WordKey: Copy {
115    /// Converts the key into the single storage word passed to the host.
116    fn try_into_word(self) -> Result<Word, &'static str>;
117}
118
119impl WordKey for Word {
120    fn try_into_word(self) -> Result<Word, &'static str> {
121        Ok(self)
122    }
123}
124
125impl WordKey for Felt {
126    fn try_into_word(self) -> Result<Word, &'static str> {
127        Ok(padded_word_from_felt(self))
128    }
129}
130
131impl WordKey for miden_base_sys::bindings::AccountId {
132    fn try_into_word(self) -> Result<Word, &'static str> {
133        Ok(self.into())
134    }
135}
136
137impl WordKey for miden_base_sys::bindings::Tag {
138    fn try_into_word(self) -> Result<Word, &'static str> {
139        Ok(self.into())
140    }
141}
142
143impl WordKey for miden_base_sys::bindings::NoteIdx {
144    fn try_into_word(self) -> Result<Word, &'static str> {
145        Ok(self.into())
146    }
147}
148
149impl WordKey for miden_base_sys::bindings::NoteType {
150    fn try_into_word(self) -> Result<Word, &'static str> {
151        Ok(self.into())
152    }
153}
154
155/// Typed access to a single account storage value.
156#[derive(Debug, Copy, Clone, PartialEq, Eq)]
157pub struct StorageValue<T: WordValue> {
158    /// The underlying storage slot id.
159    pub slot: StorageSlotId,
160    _marker: core::marker::PhantomData<T>,
161}
162
163impl<T: WordValue> StorageValue<T> {
164    /// Creates a new typed storage-value handle for `slot`.
165    pub const fn new(slot: StorageSlotId) -> Self {
166        Self {
167            slot,
168            _marker: core::marker::PhantomData,
169        }
170    }
171}
172
173impl<T: WordValue> From<StorageSlotId> for StorageValue<T> {
174    fn from(slot: StorageSlotId) -> Self {
175        Self::new(slot)
176    }
177}
178
179impl<T: WordValue> StorageValue<T> {
180    /// Reads the current value from account storage.
181    #[inline(always)]
182    pub fn get(&self) -> T {
183        T::try_from_word(storage::get_item(self.slot))
184            .unwrap_or_else(|_| panic!("storage slot {:?} contained an invalid word", self.slot))
185    }
186
187    /// Sets an item `value` in the account storage and returns the previous value.
188    #[inline(always)]
189    pub fn set(&mut self, value: T) -> T {
190        let value = value
191            .try_into_word()
192            .unwrap_or_else(|_| panic!("failed to convert value for storage slot {:?}", self.slot));
193        T::try_from_word(storage::set_item(self.slot, value))
194            .unwrap_or_else(|_| panic!("storage slot {:?} contained an invalid word", self.slot))
195    }
196}
197
198/// Typed access to an account storage map.
199#[derive(Debug, Copy, Clone, PartialEq, Eq)]
200pub struct StorageMap<K: WordKey, V: WordValue> {
201    /// The underlying storage slot id.
202    pub slot: StorageSlotId,
203    _marker: core::marker::PhantomData<(K, V)>,
204}
205
206impl<K: WordKey, V: WordValue> StorageMap<K, V> {
207    /// Creates a new typed storage map handle for `slot`.
208    pub const fn new(slot: StorageSlotId) -> Self {
209        Self {
210            slot,
211            _marker: core::marker::PhantomData,
212        }
213    }
214}
215
216impl<K: WordKey, V: WordValue> From<StorageSlotId> for StorageMap<K, V> {
217    fn from(slot: StorageSlotId) -> Self {
218        Self::new(slot)
219    }
220}
221
222impl<K: WordKey, V: WordValue> StorageMap<K, V> {
223    /// Returns the value associated with `key` from the account storage map.
224    ///
225    /// Note: Unlike `HashMap::get`, this returns `V` by value.
226    /// At the protocol layer, absent keys read as the default word value.
227    #[inline(always)]
228    pub fn get(&self, key: K) -> V {
229        let key = key.try_into_word().unwrap_or_else(|_| {
230            panic!("failed to convert key for storage map slot {:?}", self.slot)
231        });
232        V::try_from_word(storage::get_map_item(self.slot, &key)).unwrap_or_else(|_| {
233            panic!("storage map slot {:?} contained an invalid word", self.slot)
234        })
235    }
236
237    /// Sets `value` for `key` in the account storage map and returns the previous value.
238    ///
239    /// This is analogous to `HashMap::insert`, except it always returns a value (the protocol does
240    /// not distinguish "missing" from "default").
241    #[inline(always)]
242    pub fn set(&mut self, key: K, value: V) -> V {
243        let key = key.try_into_word().unwrap_or_else(|_| {
244            panic!("failed to convert key for storage map slot {:?}", self.slot)
245        });
246        let value = value.try_into_word().unwrap_or_else(|_| {
247            panic!("failed to convert value for storage map slot {:?}", self.slot)
248        });
249        V::try_from_word(storage::set_map_item(self.slot, key, value)).unwrap_or_else(|_| {
250            panic!("storage map slot {:?} contained an invalid word", self.slot)
251        })
252    }
253}