miden_protocol/account/storage/
header.rs

1use alloc::collections::BTreeMap;
2use alloc::format;
3use alloc::string::ToString;
4use alloc::vec::Vec;
5
6use super::map::EMPTY_STORAGE_MAP_ROOT;
7use super::{AccountStorage, Felt, StorageSlotType, Word};
8use crate::account::{StorageSlot, StorageSlotId, StorageSlotName};
9use crate::crypto::SequentialCommit;
10use crate::errors::AccountError;
11use crate::utils::serde::{
12    ByteReader,
13    ByteWriter,
14    Deserializable,
15    DeserializationError,
16    Serializable,
17};
18use crate::{FieldElement, ZERO};
19
20// ACCOUNT STORAGE HEADER
21// ================================================================================================
22
23/// The header of an [`AccountStorage`], storing only the slot name, slot type and value of each
24/// storage slot.
25///
26/// The stored value differs based on the slot type:
27/// - [`StorageSlotType::Value`]: The value of the slot itself.
28/// - [`StorageSlotType::Map`]: The root of the SMT that represents the storage map.
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct AccountStorageHeader {
31    slots: Vec<StorageSlotHeader>,
32}
33
34impl AccountStorageHeader {
35    // CONSTRUCTOR
36    // --------------------------------------------------------------------------------------------
37
38    /// Returns a new instance of account storage header initialized with the provided slots.
39    ///
40    /// # Errors
41    ///
42    /// Returns an error if:
43    /// - The number of provided slots is greater than [`AccountStorage::MAX_NUM_STORAGE_SLOTS`].
44    /// - The slots are not sorted by [`StorageSlotId`].
45    /// - There are multiple storage slots with the same [`StorageSlotName`].
46    pub fn new(slots: Vec<StorageSlotHeader>) -> Result<Self, AccountError> {
47        if slots.len() > AccountStorage::MAX_NUM_STORAGE_SLOTS {
48            return Err(AccountError::StorageTooManySlots(slots.len() as u64));
49        }
50
51        if !slots.is_sorted_by_key(|slot| slot.id()) {
52            return Err(AccountError::UnsortedStorageSlots);
53        }
54
55        // Check for slot name uniqueness by checking each neighboring slot's IDs. This is
56        // sufficient because the slots are sorted.
57        for slots in slots.windows(2) {
58            if slots[0].id() == slots[1].id() {
59                return Err(AccountError::DuplicateStorageSlotName(slots[0].name().clone()));
60            }
61        }
62
63        Ok(Self { slots })
64    }
65
66    /// Returns a new instance of account storage header initialized with the provided slot tuples.
67    ///
68    /// This is a convenience method that converts tuples to [`StorageSlotHeader`]s.
69    ///
70    /// # Errors
71    ///
72    /// Returns an error if:
73    /// - The number of provided slots is greater than [`AccountStorage::MAX_NUM_STORAGE_SLOTS`].
74    /// - The slots are not sorted by [`StorageSlotId`].
75    #[cfg(any(feature = "testing", test))]
76    pub fn from_tuples(
77        slots: Vec<(StorageSlotName, StorageSlotType, Word)>,
78    ) -> Result<Self, AccountError> {
79        let slots = slots
80            .into_iter()
81            .map(|(name, slot_type, value)| StorageSlotHeader::new(name, slot_type, value))
82            .collect();
83
84        Self::new(slots)
85    }
86
87    // PUBLIC ACCESSORS
88    // --------------------------------------------------------------------------------------------
89
90    /// Returns an iterator over the storage header slots.
91    pub fn slots(&self) -> impl Iterator<Item = &StorageSlotHeader> {
92        self.slots.iter()
93    }
94
95    /// Returns an iterator over the storage header map slots.
96    pub fn map_slot_roots(&self) -> impl Iterator<Item = Word> + '_ {
97        self.slots.iter().filter_map(|slot| match slot.slot_type() {
98            StorageSlotType::Value => None,
99            StorageSlotType::Map => Some(slot.value()),
100        })
101    }
102
103    /// Returns the number of slots contained in the storage header.
104    pub fn num_slots(&self) -> u8 {
105        // SAFETY: The constructors of this type ensure this value fits in a u8.
106        self.slots.len() as u8
107    }
108
109    /// Returns the storage slot header for the slot with the given name.
110    ///
111    /// Returns `None` if a slot with the provided name does not exist.
112    pub fn find_slot_header_by_name(
113        &self,
114        slot_name: &StorageSlotName,
115    ) -> Option<&StorageSlotHeader> {
116        self.find_slot_header_by_id(slot_name.id())
117    }
118
119    /// Returns the storage slot header for the slot with the given ID.
120    ///
121    /// Returns `None` if a slot with the provided slot ID does not exist.
122    pub fn find_slot_header_by_id(&self, slot_id: StorageSlotId) -> Option<&StorageSlotHeader> {
123        self.slots.iter().find(|slot| slot.id() == slot_id)
124    }
125
126    /// Indicates whether the slot with the given `name` is a map slot.
127    ///
128    /// # Errors
129    ///
130    /// Returns an error if:
131    /// - a slot with the provided name does not exist.
132    pub fn is_map_slot(&self, name: &StorageSlotName) -> Result<bool, AccountError> {
133        match self
134            .find_slot_header_by_name(name)
135            .ok_or(AccountError::StorageSlotNameNotFound { slot_name: name.clone() })?
136            .slot_type()
137        {
138            StorageSlotType::Map => Ok(true),
139            StorageSlotType::Value => Ok(false),
140        }
141    }
142
143    /// Converts storage slots of this account storage header into a vector of field elements.
144    ///
145    /// This is done by first converting each storage slot into exactly 8 elements as follows:
146    ///
147    /// ```text
148    /// [[0, slot_type, slot_id_suffix, slot_id_prefix], SLOT_VALUE]
149    /// ```
150    ///
151    /// And then concatenating the resulting elements into a single vector.
152    pub fn to_elements(&self) -> Vec<Felt> {
153        <Self as SequentialCommit>::to_elements(self)
154    }
155
156    /// Reconstructs an [`AccountStorageHeader`] from field elements with provided slot names.
157    ///
158    /// The elements are expected to be groups of 8 elements per slot:
159    /// `[[0, slot_type, slot_id_suffix, slot_id_prefix], SLOT_VALUE]`
160    pub fn try_from_elements(
161        elements: &[Felt],
162        slot_names: &BTreeMap<StorageSlotId, StorageSlotName>,
163    ) -> Result<Self, AccountError> {
164        if !elements.len().is_multiple_of(StorageSlot::NUM_ELEMENTS) {
165            return Err(AccountError::other(
166                "storage header elements length must be divisible by 8",
167            ));
168        }
169
170        let mut slots = Vec::new();
171        for chunk in elements.chunks_exact(StorageSlot::NUM_ELEMENTS) {
172            // Parse slot type from second element.
173            let slot_type_felt = chunk[1];
174            let slot_type = slot_type_felt.try_into()?;
175
176            // Parse slot ID from third and fourth elements.
177            let slot_id_suffix = chunk[2];
178            let slot_id_prefix = chunk[3];
179            let parsed_slot_id = StorageSlotId::new(slot_id_suffix, slot_id_prefix);
180
181            // Retrieve slot name from the map.
182            let slot_name = slot_names.get(&parsed_slot_id).cloned().ok_or(AccountError::other(
183                format!("slot name not found for slot ID {}", parsed_slot_id),
184            ))?;
185
186            // Parse slot value from last 4 elements.
187            let slot_value = Word::new([chunk[4], chunk[5], chunk[6], chunk[7]]);
188
189            let slot_header = StorageSlotHeader::new(slot_name, slot_type, slot_value);
190            slots.push(slot_header);
191        }
192
193        // Sort slots by ID.
194        slots.sort_by_key(|slot| slot.id());
195
196        Self::new(slots)
197    }
198
199    /// Returns the commitment to the [`AccountStorage`] this header represents.
200    pub fn to_commitment(&self) -> Word {
201        <Self as SequentialCommit>::to_commitment(self)
202    }
203}
204
205impl From<&AccountStorage> for AccountStorageHeader {
206    fn from(value: &AccountStorage) -> Self {
207        value.to_header()
208    }
209}
210
211// SEQUENTIAL COMMIT
212// ================================================================================================
213
214impl SequentialCommit for AccountStorageHeader {
215    type Commitment = Word;
216
217    fn to_elements(&self) -> Vec<Felt> {
218        self.slots().flat_map(|slot| slot.to_elements()).collect()
219    }
220}
221
222// SERIALIZATION
223// ================================================================================================
224
225impl Serializable for AccountStorageHeader {
226    fn write_into<W: ByteWriter>(&self, target: &mut W) {
227        let len = self.slots.len() as u8;
228        target.write_u8(len);
229        target.write_many(self.slots())
230    }
231}
232
233impl Deserializable for AccountStorageHeader {
234    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
235        let len = source.read_u8()?;
236        let slots: Vec<StorageSlotHeader> = source.read_many(len as usize)?;
237        Self::new(slots).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
238    }
239}
240
241// STORAGE SLOT HEADER
242// ================================================================================================
243
244/// The header of a [`StorageSlot`], storing only the slot name (or ID), slot type and value of the
245/// slot.
246///
247/// The stored value differs based on the slot type:
248/// - [`StorageSlotType::Value`]: The value of the slot itself.
249/// - [`StorageSlotType::Map`]: The root of the SMT that represents the storage map.
250#[derive(Debug, Clone, PartialEq, Eq)]
251pub struct StorageSlotHeader {
252    name: StorageSlotName,
253    r#type: StorageSlotType,
254    value: Word,
255}
256
257impl StorageSlotHeader {
258    // CONSTRUCTORS
259    // --------------------------------------------------------------------------------------------
260
261    /// Returns a new instance of storage slot header.
262    pub fn new(name: StorageSlotName, r#type: StorageSlotType, value: Word) -> Self {
263        Self { name, r#type, value }
264    }
265
266    /// Returns a new instance of storage slot header with an empty value slot.
267    pub fn with_empty_value(name: StorageSlotName) -> StorageSlotHeader {
268        StorageSlotHeader::new(name, StorageSlotType::Value, Word::default())
269    }
270
271    /// Returns a new instance of storage slot header with an empty map slot.
272    pub fn with_empty_map(name: StorageSlotName) -> StorageSlotHeader {
273        StorageSlotHeader::new(name, StorageSlotType::Map, EMPTY_STORAGE_MAP_ROOT)
274    }
275
276    // ACCESSORS
277    // --------------------------------------------------------------------------------------------
278
279    /// Returns a reference to the slot name.
280    pub fn name(&self) -> &StorageSlotName {
281        &self.name
282    }
283
284    /// Returns the slot ID.
285    pub fn id(&self) -> StorageSlotId {
286        self.name.id()
287    }
288
289    /// Returns the slot type.
290    pub fn slot_type(&self) -> StorageSlotType {
291        self.r#type
292    }
293
294    /// Returns the slot value.
295    pub fn value(&self) -> Word {
296        self.value
297    }
298
299    /// Returns this storage slot header as field elements.
300    ///
301    /// This is done by converting this storage slot into 8 field elements as follows:
302    /// ```text
303    /// [[0, slot_type, slot_id_suffix, slot_id_prefix], SLOT_VALUE]
304    /// ```
305    pub(crate) fn to_elements(&self) -> [Felt; StorageSlot::NUM_ELEMENTS] {
306        let id = self.id();
307        let mut elements = [ZERO; StorageSlot::NUM_ELEMENTS];
308        elements[0..4].copy_from_slice(&[
309            Felt::ZERO,
310            self.r#type.as_felt(),
311            id.suffix(),
312            id.prefix(),
313        ]);
314        elements[4..8].copy_from_slice(self.value.as_elements());
315        elements
316    }
317}
318
319impl From<&StorageSlot> for StorageSlotHeader {
320    fn from(slot: &StorageSlot) -> Self {
321        StorageSlotHeader::new(slot.name().clone(), slot.slot_type(), slot.value())
322    }
323}
324
325// SERIALIZATION
326// ================================================================================================
327
328impl Serializable for StorageSlotHeader {
329    fn write_into<W: ByteWriter>(&self, target: &mut W) {
330        self.name.write_into(target);
331        self.r#type.write_into(target);
332        self.value.write_into(target);
333    }
334}
335
336impl Deserializable for StorageSlotHeader {
337    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
338        let name = StorageSlotName::read_from(source)?;
339        let slot_type = StorageSlotType::read_from(source)?;
340        let value = Word::read_from(source)?;
341        Ok(Self::new(name, slot_type, value))
342    }
343}
344
345// TESTS
346// ================================================================================================
347
348#[cfg(test)]
349mod tests {
350    use alloc::collections::BTreeMap;
351    use alloc::string::ToString;
352
353    use miden_core::Felt;
354    use miden_core::utils::{Deserializable, Serializable};
355
356    use super::AccountStorageHeader;
357    use crate::Word;
358    use crate::account::{AccountStorage, StorageSlotHeader, StorageSlotName, StorageSlotType};
359    use crate::testing::storage::{MOCK_MAP_SLOT, MOCK_VALUE_SLOT0, MOCK_VALUE_SLOT1};
360
361    #[test]
362    fn test_from_account_storage() {
363        let storage_map = AccountStorage::mock_map();
364
365        // create new storage header from AccountStorage
366        let mut slots = vec![
367            (MOCK_VALUE_SLOT0.clone(), StorageSlotType::Value, Word::from([1, 2, 3, 4u32])),
368            (
369                MOCK_VALUE_SLOT1.clone(),
370                StorageSlotType::Value,
371                Word::from([Felt::new(5), Felt::new(6), Felt::new(7), Felt::new(8)]),
372            ),
373            (MOCK_MAP_SLOT.clone(), StorageSlotType::Map, storage_map.root()),
374        ];
375        slots.sort_unstable_by_key(|(slot_name, ..)| slot_name.id());
376
377        let expected_header = AccountStorageHeader::from_tuples(slots).unwrap();
378        let account_storage = AccountStorage::mock();
379
380        assert_eq!(expected_header, AccountStorageHeader::from(&account_storage))
381    }
382
383    #[test]
384    fn test_serde_account_storage_header() {
385        // create new storage header
386        let storage = AccountStorage::mock();
387        let storage_header = AccountStorageHeader::from(&storage);
388
389        // serde storage header
390        let bytes = storage_header.to_bytes();
391        let deserialized = AccountStorageHeader::read_from_bytes(&bytes).unwrap();
392
393        // assert deserialized == storage header
394        assert_eq!(storage_header, deserialized);
395    }
396
397    #[test]
398    fn test_to_elements_from_elements_empty() {
399        // Construct empty header.
400        let empty_header = AccountStorageHeader::new(vec![]).unwrap();
401        let empty_elements = empty_header.to_elements();
402
403        // Call from_elements.
404        let empty_slot_names = BTreeMap::new();
405        let reconstructed_empty =
406            AccountStorageHeader::try_from_elements(&empty_elements, &empty_slot_names).unwrap();
407        assert_eq!(empty_header, reconstructed_empty);
408    }
409
410    #[test]
411    fn test_to_elements_from_elements_single_slot() {
412        // Construct single slot header.
413        let slot_name1 = StorageSlotName::new("test::value::slot1".to_string()).unwrap();
414        let slot1 = StorageSlotHeader::new(
415            slot_name1,
416            StorageSlotType::Value,
417            Word::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]),
418        );
419
420        let single_slot_header = AccountStorageHeader::new(vec![slot1.clone()]).unwrap();
421        let single_elements = single_slot_header.to_elements();
422
423        // Call from_elements.
424        let slot_names = BTreeMap::from([(slot1.id(), slot1.name().clone())]);
425        let reconstructed_single =
426            AccountStorageHeader::try_from_elements(&single_elements, &slot_names).unwrap();
427
428        assert_eq!(single_slot_header, reconstructed_single);
429    }
430
431    #[test]
432    fn test_to_elements_from_elements_multiple_slot() {
433        // Construct multi slot header.
434        let slot_name2 = StorageSlotName::new("test::map::slot2".to_string()).unwrap();
435        let slot_name3 = StorageSlotName::new("test::value::slot3".to_string()).unwrap();
436
437        let slot2 = StorageSlotHeader::new(
438            slot_name2,
439            StorageSlotType::Map,
440            Word::new([Felt::new(5), Felt::new(6), Felt::new(7), Felt::new(8)]),
441        );
442        let slot3 = StorageSlotHeader::new(
443            slot_name3,
444            StorageSlotType::Value,
445            Word::new([Felt::new(9), Felt::new(10), Felt::new(11), Felt::new(12)]),
446        );
447
448        let mut slots = vec![slot2, slot3];
449        slots.sort_by_key(|slot| slot.id());
450        let multi_slot_header = AccountStorageHeader::new(slots.clone()).unwrap();
451        let multi_elements = multi_slot_header.to_elements();
452
453        // Call from_elements.
454        let slot_names = BTreeMap::from([
455            (slots[0].id(), slots[0].name.clone()),
456            (slots[1].id(), slots[1].name.clone()),
457        ]);
458        let reconstructed_multi =
459            AccountStorageHeader::try_from_elements(&multi_elements, &slot_names).unwrap();
460
461        assert_eq!(multi_slot_header, reconstructed_multi);
462    }
463
464    #[test]
465    fn test_from_elements_errors() {
466        // Test with invalid length (not divisible by 8).
467        let invalid_elements = vec![Felt::new(1), Felt::new(2), Felt::new(3)];
468        let empty_slot_names = BTreeMap::new();
469        assert!(
470            AccountStorageHeader::try_from_elements(&invalid_elements, &empty_slot_names).is_err()
471        );
472
473        // Test with invalid slot type.
474        let mut invalid_type_elements = vec![crate::ZERO; 8];
475        invalid_type_elements[1] = Felt::new(5); // Invalid slot type.
476        assert!(
477            AccountStorageHeader::try_from_elements(&invalid_type_elements, &empty_slot_names)
478                .is_err()
479        );
480    }
481
482    #[test]
483    fn test_from_elements_with_slot_names() {
484        use alloc::collections::BTreeMap;
485
486        // Create original slot with known name.
487        let slot_name1 = StorageSlotName::new("test::value::slot1".to_string()).unwrap();
488        let slot1 = StorageSlotHeader::new(
489            slot_name1.clone(),
490            StorageSlotType::Value,
491            Word::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]),
492        );
493
494        // Serialize the single slot to elements
495        let elements = slot1.to_elements();
496
497        // Create slot names map using the slot's ID
498        let mut slot_names = BTreeMap::new();
499        slot_names.insert(slot1.id(), slot_name1.clone());
500
501        // Test from_elements with provided slot names on raw slot elements.
502        let reconstructed_header =
503            AccountStorageHeader::try_from_elements(&elements, &slot_names).unwrap();
504
505        // Verify that the original slot names are preserved.
506        assert_eq!(reconstructed_header.slots().count(), 1);
507        let reconstructed_slot = reconstructed_header.slots().next().unwrap();
508
509        assert_eq!(slot_name1.as_str(), reconstructed_slot.name().as_str());
510        assert_eq!(slot1.slot_type(), reconstructed_slot.slot_type());
511        assert_eq!(slot1.value(), reconstructed_slot.value());
512    }
513}