Skip to main content

miden_protocol/account/storage/
mod.rs

1use alloc::string::ToString;
2use alloc::vec::Vec;
3
4use super::{
5    AccountError,
6    AccountStoragePatch,
7    ByteReader,
8    ByteWriter,
9    Deserializable,
10    DeserializationError,
11    Felt,
12    Serializable,
13    Word,
14};
15use crate::account::{
16    AccountComponent,
17    StorageMapPatch,
18    StorageMapPatchEntries,
19    StorageSlotPatch,
20    StorageValuePatch,
21};
22use crate::crypto::SequentialCommit;
23
24pub(crate) mod slot;
25pub use slot::{StorageSlot, StorageSlotContent, StorageSlotId, StorageSlotName, StorageSlotType};
26
27mod map;
28pub use map::{PartialStorageMap, StorageMap, StorageMapKey, StorageMapKeyHash, StorageMapWitness};
29
30mod header;
31pub use header::{AccountStorageHeader, StorageSlotHeader};
32
33mod partial;
34pub use partial::PartialStorage;
35
36// ACCOUNT STORAGE
37// ================================================================================================
38
39/// Account storage is composed of a variable number of name-addressable [`StorageSlot`]s up to
40/// 255 slots in total.
41///
42/// Each slot consists of a [`StorageSlotName`] and [`StorageSlotContent`] which defines its size
43/// and structure. Currently, the following content types are supported:
44/// - [`StorageSlotContent::Value`]: contains a single [`Word`] of data (i.e., 32 bytes).
45/// - [`StorageSlotContent::Map`]: contains a [`StorageMap`] which is a key-value map where both
46///   keys and values are [Word]s. The value of a storage slot containing a map is the commitment to
47///   the underlying map.
48///
49/// Slots are sorted by [`StorageSlotName`] (or [`StorageSlotId`] equivalently). This order is
50/// necessary to:
51/// - Simplify lookups of slots in the transaction kernel (using `std::collections::sorted_array`
52///   from the miden core library)
53/// - Allow the [`AccountStoragePatch`] to work only with slot names instead of slot indices.
54/// - Make it simple to check for duplicates by iterating the slots and checking that no two
55///   adjacent items have the same slot name.
56#[derive(Debug, Clone, Default, PartialEq, Eq)]
57pub struct AccountStorage {
58    slots: Vec<StorageSlot>,
59}
60
61impl AccountStorage {
62    /// The maximum number of storage slots allowed in an account storage.
63    pub const MAX_NUM_STORAGE_SLOTS: usize = 255;
64
65    // CONSTRUCTOR
66    // --------------------------------------------------------------------------------------------
67
68    /// Returns a new instance of account storage initialized with the provided storage slots.
69    ///
70    /// This function sorts the slots by [`StorageSlotName`].
71    ///
72    /// # Errors
73    ///
74    /// Returns an error if:
75    /// - The number of [`StorageSlot`]s exceeds 255.
76    /// - There are multiple storage slots with the same [`StorageSlotName`].
77    pub fn new(mut slots: Vec<StorageSlot>) -> Result<AccountStorage, AccountError> {
78        let num_slots = slots.len();
79
80        if num_slots > Self::MAX_NUM_STORAGE_SLOTS {
81            return Err(AccountError::StorageTooManySlots(num_slots as u64));
82        }
83
84        // Unstable sort is fine because we require all names to be unique.
85        slots.sort_unstable_by(|a, b| a.name().cmp(b.name()));
86
87        // Check for slot name uniqueness by checking each neighboring slot's IDs. This is
88        // sufficient because the slots are sorted.
89        for slots in slots.windows(2) {
90            if slots[0].id() == slots[1].id() {
91                return Err(AccountError::DuplicateStorageSlotName(slots[0].name().clone()));
92            }
93        }
94
95        Ok(Self { slots })
96    }
97
98    /// Creates an [`AccountStorage`] from the provided components' storage slots.
99    ///
100    /// # Errors
101    ///
102    /// Returns an error if:
103    /// - The number of [`StorageSlot`]s of all components exceeds 255.
104    /// - There are multiple storage slots with the same [`StorageSlotName`].
105    pub(super) fn from_components(
106        components: Vec<AccountComponent>,
107    ) -> Result<AccountStorage, AccountError> {
108        let storage_slots = components
109            .into_iter()
110            .flat_map(|component| {
111                let AccountComponent { storage_slots, .. } = component;
112                storage_slots.into_iter()
113            })
114            .collect();
115
116        Self::new(storage_slots)
117    }
118
119    // PUBLIC ACCESSORS
120    // --------------------------------------------------------------------------------------------
121
122    /// Converts storage slots of this account storage into a vector of field elements.
123    ///
124    /// Each storage slot is represented by exactly 8 elements:
125    ///
126    /// ```text
127    /// [[0, slot_type, slot_id_suffix, slot_id_prefix], SLOT_VALUE]
128    /// ```
129    pub fn to_elements(&self) -> Vec<Felt> {
130        <Self as SequentialCommit>::to_elements(self)
131    }
132
133    /// Returns the commitment to the [`AccountStorage`].
134    pub fn to_commitment(&self) -> Word {
135        <Self as SequentialCommit>::to_commitment(self)
136    }
137
138    /// Returns the number of slots in the account's storage.
139    pub fn num_slots(&self) -> u8 {
140        // SAFETY: The constructors of account storage ensure that the number of slots fits into a
141        // u8.
142        self.slots.len() as u8
143    }
144
145    /// Returns a reference to the storage slots.
146    pub fn slots(&self) -> &[StorageSlot] {
147        &self.slots
148    }
149
150    /// Consumes self and returns the storage slots of the account storage.
151    pub fn into_slots(self) -> Vec<StorageSlot> {
152        self.slots
153    }
154
155    /// Returns an [AccountStorageHeader] for this account storage.
156    pub fn to_header(&self) -> AccountStorageHeader {
157        AccountStorageHeader::new(self.slots.iter().map(StorageSlotHeader::from).collect())
158            .expect("slots should be valid as ensured by AccountStorage")
159    }
160
161    /// Returns a reference to the storage slot with the provided name, if it exists, `None`
162    /// otherwise.
163    pub fn get(&self, slot_name: &StorageSlotName) -> Option<&StorageSlot> {
164        self.slots.iter().find(|slot| slot.name().id() == slot_name.id())
165    }
166
167    /// Returns a mutable reference to the storage slot with the provided name, if it exists, `None`
168    /// otherwise.
169    fn get_mut(&mut self, slot_name: &StorageSlotName) -> Option<&mut StorageSlot> {
170        self.slots.iter_mut().find(|slot| slot.name().id() == slot_name.id())
171    }
172
173    /// Returns an item from the storage slot with the given name.
174    ///
175    /// # Errors
176    ///
177    /// Returns an error if:
178    /// - A slot with the provided name does not exist.
179    pub fn get_item(&self, slot_name: &StorageSlotName) -> Result<Word, AccountError> {
180        self.get(slot_name)
181            .map(|slot| slot.content().value())
182            .ok_or_else(|| AccountError::StorageSlotNameNotFound { slot_name: slot_name.clone() })
183    }
184
185    /// Returns a map item from the map in the storage slot with the given name.
186    ///
187    /// # Errors
188    ///
189    /// Returns an error if:
190    /// - A slot with the provided name does not exist.
191    /// - If the [`StorageSlot`] is not [`StorageSlotType::Map`].
192    pub fn get_map_item(
193        &self,
194        slot_name: &StorageSlotName,
195        key: StorageMapKey,
196    ) -> Result<Word, AccountError> {
197        self.get(slot_name)
198            .ok_or_else(|| AccountError::StorageSlotNameNotFound { slot_name: slot_name.clone() })
199            .and_then(|slot| match slot.content() {
200                StorageSlotContent::Map(map) => Ok(map.get(&key)),
201                _ => Err(AccountError::StorageSlotNotMap(slot_name.clone())),
202            })
203    }
204
205    // STATE MUTATORS
206    // --------------------------------------------------------------------------------------------
207
208    /// Applies the provided delta to this account storage.
209    ///
210    /// # Errors
211    ///
212    /// Returns an error if:
213    /// - The updates violate storage constraints.
214    pub(super) fn apply_patch(&mut self, patch: &AccountStoragePatch) -> Result<(), AccountError> {
215        for (slot_name, slot_patch) in patch.slots() {
216            match slot_patch {
217                StorageSlotPatch::Value(value_patch) => {
218                    self.apply_value_patch(slot_name, value_patch)?
219                },
220                StorageSlotPatch::Map(map_patch) => self.apply_map_patch(slot_name, map_patch)?,
221            }
222        }
223
224        Ok(())
225    }
226
227    /// Applies a value slot patch: creates, updates, or removes the value slot.
228    fn apply_value_patch(
229        &mut self,
230        slot_name: &StorageSlotName,
231        value_patch: &StorageValuePatch,
232    ) -> Result<(), AccountError> {
233        match value_patch {
234            StorageValuePatch::Create { value } => {
235                self.create_value_slot(slot_name.clone(), *value)?;
236            },
237            StorageValuePatch::Update { value } => {
238                self.set_item(slot_name, *value)?;
239            },
240            StorageValuePatch::Remove => {
241                self.remove_slot(slot_name)?;
242            },
243        }
244
245        Ok(())
246    }
247
248    /// Applies a map slot patch: creates, updates, or removes the map slot.
249    fn apply_map_patch(
250        &mut self,
251        slot_name: &StorageSlotName,
252        map_patch: &StorageMapPatch,
253    ) -> Result<(), AccountError> {
254        match map_patch {
255            StorageMapPatch::Create { entries } => {
256                self.create_map_slot(slot_name.clone(), entries)?;
257            },
258            StorageMapPatch::Update { entries } => {
259                let slot = self.get_mut(slot_name).ok_or_else(|| {
260                    AccountError::StorageSlotNameNotFound { slot_name: slot_name.clone() }
261                })?;
262
263                let storage_map = match slot.content_mut() {
264                    StorageSlotContent::Map(map) => map,
265                    _ => return Err(AccountError::StorageSlotNotMap(slot_name.clone())),
266                };
267
268                storage_map.apply_patch(entries)?;
269            },
270            StorageMapPatch::Remove => {
271                self.remove_slot(slot_name)?;
272            },
273        }
274
275        Ok(())
276    }
277
278    /// Updates the value of the storage slot with the given name.
279    ///
280    /// This method should be used only to update value slots. For updating values
281    /// in storage maps, please see [`AccountStorage::set_map_item`].
282    ///
283    /// # Errors
284    ///
285    /// Returns an error if:
286    /// - A slot with the provided name does not exist.
287    /// - The [`StorageSlot`] is not [`StorageSlotType::Value`].
288    pub fn set_item(
289        &mut self,
290        slot_name: &StorageSlotName,
291        value: Word,
292    ) -> Result<Word, AccountError> {
293        let slot = self.get_mut(slot_name).ok_or_else(|| {
294            AccountError::StorageSlotNameNotFound { slot_name: slot_name.clone() }
295        })?;
296
297        let StorageSlotContent::Value(old_value) = slot.content() else {
298            return Err(AccountError::StorageSlotNotValue(slot_name.clone()));
299        };
300        let old_value = *old_value;
301
302        let mut new_slot = StorageSlotContent::Value(value);
303        core::mem::swap(slot.content_mut(), &mut new_slot);
304
305        Ok(old_value)
306    }
307
308    /// Updates the value of a key-value pair of a storage map with the given name.
309    ///
310    /// This method should be used only to update storage maps. For updating values
311    /// in storage slots, please see [AccountStorage::set_item()].
312    ///
313    /// # Errors
314    ///
315    /// Returns an error if:
316    /// - A slot with the provided name does not exist.
317    /// - If the [`StorageSlot`] is not [`StorageSlotType::Map`].
318    pub fn set_map_item(
319        &mut self,
320        slot_name: &StorageSlotName,
321        key: StorageMapKey,
322        value: Word,
323    ) -> Result<(Word, Word), AccountError> {
324        let slot = self.get_mut(slot_name).ok_or_else(|| {
325            AccountError::StorageSlotNameNotFound { slot_name: slot_name.clone() }
326        })?;
327
328        let StorageSlotContent::Map(storage_map) = slot.content_mut() else {
329            return Err(AccountError::StorageSlotNotMap(slot_name.clone()));
330        };
331
332        let old_root = storage_map.root();
333
334        let old_value = storage_map.insert(key, value)?;
335
336        Ok((old_root, old_value))
337    }
338
339    /// Creates a new value slot with the given name and value.
340    ///
341    /// # Errors
342    ///
343    /// Returns an error if:
344    /// - Adding the slot would exceed [`AccountStorage::MAX_NUM_STORAGE_SLOTS`].
345    fn create_value_slot(
346        &mut self,
347        slot_name: StorageSlotName,
348        value: Word,
349    ) -> Result<(), AccountError> {
350        self.create_slot(StorageSlot::with_value(slot_name, value))
351    }
352
353    /// Creates a new map slot with the given name and the provided patch entries as its contents.
354    ///
355    /// # Errors
356    ///
357    /// Returns an error if:
358    /// - Adding the slot would exceed [`AccountStorage::MAX_NUM_STORAGE_SLOTS`].
359    fn create_map_slot(
360        &mut self,
361        slot_name: StorageSlotName,
362        entries: &StorageMapPatchEntries,
363    ) -> Result<(), AccountError> {
364        let storage_map =
365            StorageMap::with_entries(entries.as_map().iter().map(|(key, value)| (*key, *value)))
366                .expect("map should contain only unique entries");
367
368        self.create_slot(StorageSlot::with_map(slot_name, storage_map))
369    }
370
371    /// Removes the storage slot with the given name.
372    ///
373    /// # Errors
374    ///
375    /// Returns an error if a slot with the provided name does not exist.
376    fn remove_slot(&mut self, slot_name: &StorageSlotName) -> Result<(), AccountError> {
377        match self.slots.iter().position(|slot| slot.name().id() == slot_name.id()) {
378            Some(index) => {
379                self.slots.remove(index);
380                Ok(())
381            },
382            None => Err(AccountError::StorageSlotNameNotFound { slot_name: slot_name.clone() }),
383        }
384    }
385
386    /// Creates the provided slot, maintaining the slots' sort order by [`StorageSlotName`].
387    ///
388    /// If a slot with the same name already exists, it is replaced in place. This re-creation is
389    /// equivalent to removing the existing slot and creating the new one. See also
390    /// [`AccountStoragePatch::merge`].
391    ///
392    /// # Errors
393    ///
394    /// Returns an error if adding a new slot would exceed
395    /// [`AccountStorage::MAX_NUM_STORAGE_SLOTS`].
396    fn create_slot(&mut self, slot: StorageSlot) -> Result<(), AccountError> {
397        match self.slots.binary_search_by(|existing| existing.name().cmp(slot.name())) {
398            Ok(index) => {
399                self.slots[index] = slot;
400                Ok(())
401            },
402            Err(index) => {
403                if self.slots.len() >= Self::MAX_NUM_STORAGE_SLOTS {
404                    return Err(AccountError::StorageTooManySlots(self.slots.len() as u64 + 1));
405                }
406
407                self.slots.insert(index, slot);
408                Ok(())
409            },
410        }
411    }
412}
413
414// ITERATORS
415// ================================================================================================
416
417impl IntoIterator for AccountStorage {
418    type Item = StorageSlot;
419    type IntoIter = alloc::vec::IntoIter<StorageSlot>;
420
421    fn into_iter(self) -> Self::IntoIter {
422        self.slots.into_iter()
423    }
424}
425
426// SEQUENTIAL COMMIT
427// ================================================================================================
428
429impl SequentialCommit for AccountStorage {
430    type Commitment = Word;
431
432    fn to_elements(&self) -> Vec<Felt> {
433        self.slots()
434            .iter()
435            .flat_map(|slot| {
436                StorageSlotHeader::new(
437                    slot.name().clone(),
438                    slot.content().slot_type(),
439                    slot.content().value(),
440                )
441                .to_elements()
442            })
443            .collect()
444    }
445}
446
447// SERIALIZATION
448// ================================================================================================
449
450impl Serializable for AccountStorage {
451    fn write_into<W: ByteWriter>(&self, target: &mut W) {
452        target.write_u8(self.slots().len() as u8);
453        target.write_many(self.slots());
454    }
455
456    fn get_size_hint(&self) -> usize {
457        // Size of the serialized slot length.
458        let u8_size = 0u8.get_size_hint();
459        let mut size = u8_size;
460
461        for slot in self.slots() {
462            size += slot.get_size_hint();
463        }
464
465        size
466    }
467}
468
469impl Deserializable for AccountStorage {
470    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
471        let num_slots = source.read_u8()? as usize;
472        let slots = source.read_many_iter::<StorageSlot>(num_slots)?.collect::<Result<_, _>>()?;
473
474        Self::new(slots).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
475    }
476}
477
478// TESTS
479// ================================================================================================
480
481#[cfg(test)]
482mod tests {
483    use std::collections::BTreeMap;
484
485    use assert_matches::assert_matches;
486
487    use super::{AccountStorage, Deserializable, Serializable};
488    use crate::Word;
489    use crate::account::{
490        AccountStorageHeader,
491        AccountStoragePatch,
492        StorageSlot,
493        StorageSlotHeader,
494        StorageSlotName,
495        StorageSlotPatch,
496        StorageValuePatch,
497    };
498    use crate::errors::AccountError;
499
500    #[test]
501    fn test_serde_account_storage() -> anyhow::Result<()> {
502        // empty storage
503        let storage = AccountStorage::new(vec![]).unwrap();
504        let bytes = storage.to_bytes();
505        assert_eq!(storage, AccountStorage::read_from_bytes(&bytes).unwrap());
506
507        // storage with values for default types
508        let storage = AccountStorage::new(vec![
509            StorageSlot::with_empty_value(StorageSlotName::new("miden::test::value")?),
510            StorageSlot::with_empty_map(StorageSlotName::new("miden::test::map")?),
511        ])
512        .unwrap();
513        let bytes = storage.to_bytes();
514        assert_eq!(storage, AccountStorage::read_from_bytes(&bytes).unwrap());
515
516        Ok(())
517    }
518
519    #[test]
520    fn test_get_slot_by_name() -> anyhow::Result<()> {
521        let counter_slot = StorageSlotName::new("miden::test::counter")?;
522        let map_slot = StorageSlotName::new("miden::test::map")?;
523
524        let slots = vec![
525            StorageSlot::with_empty_value(counter_slot.clone()),
526            StorageSlot::with_empty_map(map_slot.clone()),
527        ];
528        let storage = AccountStorage::new(slots.clone())?;
529
530        assert_eq!(storage.get(&counter_slot).unwrap(), &slots[0]);
531        assert_eq!(storage.get(&map_slot).unwrap(), &slots[1]);
532
533        Ok(())
534    }
535
536    #[test]
537    fn test_account_storage_and_header_fail_on_duplicate_slot_name() -> anyhow::Result<()> {
538        let slot_name0 = StorageSlotName::mock(0);
539        let slot_name1 = StorageSlotName::mock(1);
540        let slot_name2 = StorageSlotName::mock(2);
541
542        let mut slots = vec![
543            StorageSlot::with_empty_value(slot_name0.clone()),
544            StorageSlot::with_empty_value(slot_name1.clone()),
545            StorageSlot::with_empty_map(slot_name0.clone()),
546            StorageSlot::with_empty_value(slot_name2.clone()),
547        ];
548
549        // Set up a test where the slots we pass are not already sorted
550        // This ensures the duplicate is correctly found
551        let err = AccountStorage::new(slots.clone()).unwrap_err();
552
553        assert_matches!(err, AccountError::DuplicateStorageSlotName(name) => {
554            assert_eq!(name, slot_name0);
555        });
556
557        slots.sort_unstable_by(|a, b| a.name().cmp(b.name()));
558        let err = AccountStorageHeader::new(slots.iter().map(StorageSlotHeader::from).collect())
559            .unwrap_err();
560
561        assert_matches!(err, AccountError::DuplicateStorageSlotName(name) => {
562            assert_eq!(name, slot_name0);
563        });
564
565        Ok(())
566    }
567
568    #[test]
569    fn create_value_slot_recreates_existing() -> anyhow::Result<()> {
570        let slot_name = StorageSlotName::mock(4);
571        let mut storage = AccountStorage::new(vec![StorageSlot::with_value(
572            slot_name.clone(),
573            Word::from([1u32, 2, 3, 4]),
574        )])?;
575
576        // Creating a slot that already exists re-creates it, replacing the previous value.
577        let new_value = Word::from([5u32, 6, 7, 8]);
578        storage.create_value_slot(slot_name.clone(), new_value)?;
579
580        assert_eq!(storage.num_slots(), 1);
581        assert_eq!(storage.get_item(&slot_name)?, new_value);
582
583        Ok(())
584    }
585
586    #[test]
587    fn remove_slot_rejects_absent() -> anyhow::Result<()> {
588        let absent = StorageSlotName::new("miden::test::absent")?;
589        let mut storage = AccountStorage::default();
590
591        let err = storage.remove_slot(&absent).unwrap_err();
592        assert_matches!(err, AccountError::StorageSlotNameNotFound { slot_name } => {
593            assert_eq!(slot_name, absent);
594        });
595
596        Ok(())
597    }
598
599    #[test]
600    fn create_and_remove_value_slot_roundtrip() -> anyhow::Result<()> {
601        // Setup slot names so that the created slot is in the middle.
602        let existing0 = StorageSlotName::mock(1);
603        let existing1 = StorageSlotName::mock(7);
604        let created = StorageSlotName::mock(20);
605        assert!(existing0 < created);
606        assert!(created < existing1);
607
608        let value = Word::from([9u32, 8, 7, 6]);
609
610        let mut storage = AccountStorage::new(vec![
611            StorageSlot::with_value(existing0.clone(), value),
612            StorageSlot::with_value(existing1.clone(), value),
613        ])?;
614
615        storage.create_value_slot(created.clone(), value)?;
616        assert_eq!(storage.num_slots(), 3);
617        assert_eq!(storage.get_item(&created)?, value);
618        assert!(
619            storage.slots().is_sorted_by_key(|slot| slot.name()),
620            "slots should remain sorted after insertion"
621        );
622
623        assert_eq!(storage.get_item(&existing0)?, value, "existing slot should remain accessible");
624        assert_eq!(storage.get_item(&existing1)?, value, "existing slot should remain accessible");
625
626        storage.remove_slot(&created)?;
627        assert_eq!(storage.num_slots(), 2);
628        assert_matches!(
629            storage.get_item(&created).unwrap_err(),
630            AccountError::StorageSlotNameNotFound { .. }
631        );
632
633        Ok(())
634    }
635
636    #[test]
637    fn apply_storage_patch() -> anyhow::Result<()> {
638        let updated = StorageSlotName::mock(1);
639        let created = StorageSlotName::mock(2);
640        let removed = StorageSlotName::mock(3);
641
642        let init_value = Word::from([1u32, 2, 3, 4]);
643        let final_value = Word::from([6u32, 7, 8, 9]);
644
645        let mut storage = AccountStorage::new(vec![
646            StorageSlot::with_value(updated.clone(), init_value),
647            StorageSlot::with_value(removed.clone(), init_value),
648        ])?;
649
650        let patches = BTreeMap::from_iter([
651            (
652                created.clone(),
653                StorageSlotPatch::Value(StorageValuePatch::Create { value: final_value }),
654            ),
655            (
656                updated.clone(),
657                StorageSlotPatch::Value(StorageValuePatch::Update { value: final_value }),
658            ),
659            (removed.clone(), StorageSlotPatch::Value(StorageValuePatch::Remove)),
660        ]);
661        let patch = AccountStoragePatch::from_raw(patches)?;
662
663        storage.apply_patch(&patch)?;
664
665        assert_eq!(storage.num_slots(), 2);
666        assert_eq!(storage.get_item(&created)?, final_value);
667        assert_eq!(storage.get_item(&updated)?, final_value);
668        assert_eq!(storage.get(&removed), None);
669
670        Ok(())
671    }
672}