Skip to main content

miden_base/types/
storage.rs

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