miden_objects/account/storage/map/
mod.rs

1use alloc::collections::BTreeMap;
2
3use miden_core::EMPTY_WORD;
4use miden_crypto::merkle::EmptySubtreeRoots;
5
6use super::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, Word};
7use crate::account::StorageMapDelta;
8use crate::crypto::merkle::{InnerNodeInfo, LeafIndex, SMT_DEPTH, Smt, SmtLeaf};
9use crate::errors::StorageMapError;
10use crate::{AccountError, Felt, Hasher};
11
12mod partial;
13pub use partial::PartialStorageMap;
14
15mod witness;
16pub use witness::StorageMapWitness;
17
18// ACCOUNT STORAGE MAP
19// ================================================================================================
20
21/// Empty storage map root.
22pub const EMPTY_STORAGE_MAP_ROOT: Word = *EmptySubtreeRoots::entry(StorageMap::DEPTH, 0);
23
24/// An account storage map is a sparse merkle tree of depth [`Self::DEPTH`].
25///
26/// It can be used to store a large amount of data in an account than would be otherwise possible
27/// using just the account's storage slots. This works by storing the root of the map's underlying
28/// SMT in one account storage slot. Each map entry is a leaf in the tree and its inclusion is
29/// proven while retrieving it (e.g. via `active_account::get_map_item`).
30///
31/// As a side-effect, this also means that _not all_ entries of the map have to be present at
32/// transaction execution time in order to access or modify the map. It is sufficient if _just_ the
33/// accessed/modified items are present in the advice provider.
34///
35/// Because the keys of the map are user-chosen and thus not necessarily uniformly distributed, the
36/// tree could be imbalanced and made less efficient. To mitigate that, the keys used in the
37/// storage map are hashed before they are inserted into the SMT, which creates a uniform
38/// distribution. The original keys are retained in a separate map. This causes redundancy but
39/// allows for introspection of the map, e.g. by querying the set of stored (original) keys which is
40/// useful in debugging and explorer scenarios.
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct StorageMap {
43    /// The SMT where each key is the hashed original key.
44    smt: Smt,
45    /// The entries of the map where the key is the raw user-chosen one.
46    ///
47    /// It is an invariant of this type that the map's entries are always consistent with the SMT's
48    /// entries and vice-versa.
49    entries: BTreeMap<Word, Word>,
50}
51
52impl StorageMap {
53    // CONSTANTS
54    // --------------------------------------------------------------------------------------------
55
56    /// The depth of the SMT that represents the storage map.
57    pub const DEPTH: u8 = SMT_DEPTH;
58
59    /// The default value of empty leaves.
60    pub const EMPTY_VALUE: Word = Smt::EMPTY_VALUE;
61
62    // CONSTRUCTOR
63    // --------------------------------------------------------------------------------------------
64
65    /// Returns a new [StorageMap].
66    ///
67    /// All leaves in the returned tree are set to [Self::EMPTY_VALUE].
68    pub fn new() -> Self {
69        StorageMap {
70            smt: Smt::new(),
71            entries: BTreeMap::new(),
72        }
73    }
74
75    /// Creates a new [`StorageMap`] from the provided key-value entries.
76    ///
77    /// # Errors
78    ///
79    /// Returns an error if:
80    /// - the provided entries contain multiple values for the same key.
81    pub fn with_entries<I: ExactSizeIterator<Item = (Word, Word)>>(
82        entries: impl IntoIterator<Item = (Word, Word), IntoIter = I>,
83    ) -> Result<Self, StorageMapError> {
84        let mut map = BTreeMap::new();
85
86        for (key, value) in entries {
87            if let Some(prev_value) = map.insert(key, value) {
88                return Err(StorageMapError::DuplicateKey {
89                    key,
90                    value0: prev_value,
91                    value1: value,
92                });
93            }
94        }
95
96        Ok(Self::from_btree_map(map))
97    }
98
99    /// Creates a new [`StorageMap`] from the given map. For internal use.
100    fn from_btree_map(entries: BTreeMap<Word, Word>) -> Self {
101        let hashed_keys_iter = entries.iter().map(|(key, value)| (Self::hash_key(*key), *value));
102        let smt = Smt::with_entries(hashed_keys_iter)
103            .expect("btree maps should not contain duplicate keys");
104
105        StorageMap { smt, entries }
106    }
107
108    // PUBLIC ACCESSORS
109    // --------------------------------------------------------------------------------------------
110
111    /// Returns the root of the underlying sparse merkle tree.
112    pub fn root(&self) -> Word {
113        self.smt.root()
114    }
115
116    /// Returns the number of non-empty leaves in this storage map.
117    ///
118    /// Note that this may return a different value from [Self::num_entries()] as a single leaf may
119    /// contain more than one key-value pair.
120    pub fn num_leaves(&self) -> usize {
121        self.smt.num_leaves()
122    }
123
124    /// Returns the number of key-value pairs with non-default values in this storage map.
125    ///
126    /// Note that this may return a different value from [Self::num_leaves()] as a single leaf may
127    /// contain more than one key-value pair.
128    pub fn num_entries(&self) -> usize {
129        self.smt.num_entries()
130    }
131
132    /// Returns the value corresponding to the key or [`Self::EMPTY_VALUE`] if the key is not
133    /// associated with a value.
134    pub fn get(&self, raw_key: &Word) -> Word {
135        self.entries.get(raw_key).copied().unwrap_or_default()
136    }
137
138    /// Returns an opening of the leaf associated with raw key.
139    ///
140    /// Conceptually, an opening is a Merkle path to the leaf, as well as the leaf itself.
141    pub fn open(&self, raw_key: &Word) -> StorageMapWitness {
142        let hashed_map_key = Self::hash_key(*raw_key);
143        let smt_proof = self.smt.open(&hashed_map_key);
144        let value = self.entries.get(raw_key).copied().unwrap_or_default();
145
146        // SAFETY: The key value pair is guaranteed to be present in the provided proof since we
147        // open its hashed version and because of the guarantees of the storage map.
148        StorageMapWitness::new_unchecked(smt_proof, [(*raw_key, value)])
149    }
150
151    // ITERATORS
152    // --------------------------------------------------------------------------------------------
153
154    /// Returns an iterator over the leaves of the underlying [`Smt`].
155    pub fn leaves(&self) -> impl Iterator<Item = (LeafIndex<SMT_DEPTH>, &SmtLeaf)> {
156        self.smt.leaves() // Delegate to Smt's leaves method
157    }
158
159    /// Returns an iterator over the key-value pairs in this storage map.
160    ///
161    /// Note that the returned key is the raw map key.
162    pub fn entries(&self) -> impl Iterator<Item = (&Word, &Word)> {
163        self.entries.iter()
164    }
165
166    /// Returns an iterator over the inner nodes of the underlying [`Smt`].
167    pub fn inner_nodes(&self) -> impl Iterator<Item = InnerNodeInfo> + '_ {
168        self.smt.inner_nodes() // Delegate to Smt's inner_nodes method
169    }
170
171    // DATA MUTATORS
172    // --------------------------------------------------------------------------------------------
173
174    /// Inserts or updates the given key value pair and returns the previous value, or
175    /// [`Self::EMPTY_VALUE`] if no entry was previously present.
176    ///
177    /// If the provided `value` is [`Self::EMPTY_VALUE`] the entry will be removed.
178    pub fn insert(&mut self, raw_key: Word, value: Word) -> Result<Word, AccountError> {
179        if value == EMPTY_WORD {
180            self.entries.remove(&raw_key);
181        } else {
182            self.entries.insert(raw_key, value);
183        }
184
185        let hashed_key = Self::hash_key(raw_key);
186        self.smt
187            .insert(hashed_key, value)
188            .map_err(AccountError::MaxNumStorageMapLeavesExceeded)
189    }
190
191    /// Applies the provided delta to this account storage.
192    pub fn apply_delta(&mut self, delta: &StorageMapDelta) -> Result<Word, AccountError> {
193        // apply the updated and cleared leaves to the storage map
194        for (&key, &value) in delta.entries().iter() {
195            self.insert(key.into_inner(), value)?;
196        }
197
198        Ok(self.root())
199    }
200
201    /// Consumes the map and returns the underlying map of entries.
202    pub fn into_entries(self) -> BTreeMap<Word, Word> {
203        self.entries
204    }
205
206    /// Hashes the given key to get the key of the SMT.
207    pub fn hash_key(raw_key: Word) -> Word {
208        Hasher::hash_elements(raw_key.as_elements())
209    }
210
211    // TODO: Replace with https://github.com/0xMiden/crypto/issues/515 once implemented.
212    /// Returns the leaf index of a map key.
213    pub fn hashed_map_key_to_leaf_index(hashed_map_key: Word) -> Felt {
214        // The third element in an SMT key is the index.
215        hashed_map_key[3]
216    }
217}
218
219impl Default for StorageMap {
220    fn default() -> Self {
221        Self::new()
222    }
223}
224
225// SERIALIZATION
226// ================================================================================================
227
228impl Serializable for StorageMap {
229    fn write_into<W: ByteWriter>(&self, target: &mut W) {
230        self.entries.write_into(target);
231    }
232
233    fn get_size_hint(&self) -> usize {
234        self.smt.get_size_hint()
235    }
236}
237
238impl Deserializable for StorageMap {
239    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
240        let map = BTreeMap::read_from(source)?;
241        Ok(Self::from_btree_map(map))
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use assert_matches::assert_matches;
248
249    use super::{Deserializable, EMPTY_STORAGE_MAP_ROOT, Serializable, StorageMap, Word};
250    use crate::errors::StorageMapError;
251
252    #[test]
253    fn account_storage_serialization() {
254        // StorageMap for default types (empty map)
255        let storage_map_default = StorageMap::default();
256        let bytes = storage_map_default.to_bytes();
257        assert_eq!(storage_map_default, StorageMap::read_from_bytes(&bytes).unwrap());
258
259        // StorageMap with values
260        let storage_map_leaves_2: [(Word, Word); 2] = [
261            (Word::from([101, 102, 103, 104u32]), Word::from([1, 2, 3, 4u32])),
262            (Word::from([105, 106, 107, 108u32]), Word::from([5, 6, 7, 8u32])),
263        ];
264        let storage_map = StorageMap::with_entries(storage_map_leaves_2).unwrap();
265        assert_eq!(storage_map.num_entries(), 2);
266        assert_eq!(storage_map.num_leaves(), 2);
267
268        let bytes = storage_map.to_bytes();
269        let deserialized_map = StorageMap::read_from_bytes(&bytes).unwrap();
270
271        assert_eq!(storage_map.root(), deserialized_map.root());
272
273        assert_eq!(storage_map, deserialized_map);
274    }
275
276    #[test]
277    fn test_empty_storage_map_constants() {
278        // If these values don't match, update the constants.
279        assert_eq!(StorageMap::default().root(), EMPTY_STORAGE_MAP_ROOT);
280    }
281
282    #[test]
283    fn account_storage_map_fails_on_duplicate_entries() {
284        // StorageMap with values
285        let storage_map_leaves_2: [(Word, Word); 2] = [
286            (Word::from([101, 102, 103, 104u32]), Word::from([1, 2, 3, 4u32])),
287            (Word::from([101, 102, 103, 104u32]), Word::from([5, 6, 7, 8u32])),
288        ];
289
290        let error = StorageMap::with_entries(storage_map_leaves_2).unwrap_err();
291        assert_matches!(error, StorageMapError::DuplicateKey { .. });
292    }
293}