Skip to main content

miden_base/types/
storage.rs

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