miden_objects/account/storage/
header.rs

1use alloc::vec::Vec;
2
3use super::{AccountStorage, Felt, Hasher, StorageSlot, StorageSlotType, Word};
4use crate::utils::serde::{
5    ByteReader,
6    ByteWriter,
7    Deserializable,
8    DeserializationError,
9    Serializable,
10};
11use crate::{AccountError, ZERO};
12
13// ACCOUNT STORAGE HEADER
14// ================================================================================================
15
16/// Storage slot header is a lighter version of the [StorageSlot] storing only the type and the
17/// top-level value for the slot, and being, in fact, just a thin wrapper around a tuple.
18///
19/// That is, for complex storage slot (e.g., storage map), the header contains only the commitment
20/// to the underlying data.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub struct StorageSlotHeader(StorageSlotType, Word);
23
24impl StorageSlotHeader {
25    /// Returns a new instance of storage slot header from the provided storage slot type and value.
26    pub fn new(value: &(StorageSlotType, Word)) -> Self {
27        Self(value.0, value.1)
28    }
29
30    /// Returns this storage slot header as field elements.
31    ///
32    /// This is done by converting this storage slot into 8 field elements as follows:
33    /// ```text
34    /// [SLOT_VALUE, slot_type, 0, 0, 0]
35    /// ```
36    pub fn as_elements(&self) -> [Felt; StorageSlot::NUM_ELEMENTS_PER_STORAGE_SLOT] {
37        let mut elements = [ZERO; StorageSlot::NUM_ELEMENTS_PER_STORAGE_SLOT];
38        elements[0..4].copy_from_slice(self.1.as_elements());
39        elements[4..8].copy_from_slice(self.0.as_word().as_elements());
40        elements
41    }
42}
43
44impl From<&StorageSlot> for StorageSlotHeader {
45    fn from(value: &StorageSlot) -> Self {
46        Self(value.slot_type(), value.value())
47    }
48}
49
50/// Account storage header is a lighter version of the [AccountStorage] storing only the type and
51/// the top-level value for each storage slot.
52///
53/// That is, for complex storage slots (e.g., storage maps), the header contains only the commitment
54/// to the underlying data.
55#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct AccountStorageHeader {
57    slots: Vec<(StorageSlotType, Word)>,
58}
59
60impl AccountStorageHeader {
61    // CONSTRUCTOR
62    // --------------------------------------------------------------------------------------------
63
64    /// Returns a new instance of account storage header initialized with the provided slots.
65    ///
66    /// # Panics
67    /// - If the number of provided slots is greater than [AccountStorage::MAX_NUM_STORAGE_SLOTS].
68    pub fn new(slots: Vec<(StorageSlotType, Word)>) -> Self {
69        assert!(slots.len() <= AccountStorage::MAX_NUM_STORAGE_SLOTS);
70        Self { slots }
71    }
72
73    // PUBLIC ACCESSORS
74    // --------------------------------------------------------------------------------------------
75
76    /// Returns an iterator over the storage header slots.
77    pub fn slots(&self) -> impl Iterator<Item = &(StorageSlotType, Word)> {
78        self.slots.iter()
79    }
80
81    /// Returns an iterator over the storage header map slots.
82    pub fn map_slot_roots(&self) -> impl Iterator<Item = Word> {
83        self.slots
84            .iter()
85            .filter(|(slot_type, _)| matches!(slot_type, StorageSlotType::Map))
86            .map(|x| x.1)
87    }
88
89    /// Returns the number of slots contained in the storage header.
90    pub fn num_slots(&self) -> u8 {
91        // SAFETY: The constructors of this type ensure this value fits in a u8.
92        self.slots.len() as u8
93    }
94
95    /// Returns a slot contained in the storage header at a given index.
96    ///
97    /// # Errors
98    /// - If the index is out of bounds.
99    pub fn slot(&self, index: usize) -> Result<&(StorageSlotType, Word), AccountError> {
100        self.slots.get(index).ok_or(AccountError::StorageIndexOutOfBounds {
101            slots_len: self.slots.len() as u8,
102            index: index as u8,
103        })
104    }
105
106    // NOTE: The way of computing the commitment should be kept in sync with `AccountStorage`
107    /// Computes the account storage header commitment.
108    pub fn compute_commitment(&self) -> Word {
109        Hasher::hash_elements(&self.as_elements())
110    }
111
112    /// Indicates whether the slot at `index` is a map slot.
113    ///
114    /// # Errors
115    /// - If `index` exceeds the slot count.
116    pub fn is_map_slot(&self, index: usize) -> Result<bool, AccountError> {
117        match self.slot(index)?.0 {
118            StorageSlotType::Map => Ok(true),
119            StorageSlotType::Value => Ok(false),
120        }
121    }
122
123    /// Converts storage slots of this account storage header into a vector of field elements.
124    ///
125    /// This is done by first converting each storage slot into exactly 8 elements as follows:
126    /// ```text
127    /// [STORAGE_SLOT_VALUE, storage_slot_type, 0, 0, 0]
128    /// ```
129    /// And then concatenating the resulting elements into a single vector.
130    pub fn as_elements(&self) -> Vec<Felt> {
131        self.slots
132            .iter()
133            .flat_map(|slot| StorageSlotHeader::new(slot).as_elements())
134            .collect()
135    }
136}
137
138impl From<&AccountStorage> for AccountStorageHeader {
139    fn from(value: &AccountStorage) -> Self {
140        value.to_header()
141    }
142}
143
144// SERIALIZATION
145// ================================================================================================
146
147impl Serializable for AccountStorageHeader {
148    fn write_into<W: ByteWriter>(&self, target: &mut W) {
149        let len = self.slots.len() as u8;
150        target.write_u8(len);
151        target.write_many(self.slots())
152    }
153}
154
155impl Deserializable for AccountStorageHeader {
156    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
157        let len = source.read_u8()?;
158        let slots = source.read_many(len as usize)?;
159        // number of storage slots is guaranteed to be smaller than or equal to 255
160        Ok(Self::new(slots))
161    }
162}
163
164// TESTS
165// ================================================================================================
166
167#[cfg(test)]
168mod tests {
169    use miden_core::Felt;
170    use miden_core::utils::{Deserializable, Serializable};
171
172    use super::AccountStorageHeader;
173    use crate::Word;
174    use crate::account::{AccountStorage, StorageSlotType};
175
176    #[test]
177    fn test_from_account_storage() {
178        let storage_map = AccountStorage::mock_map();
179
180        // create new storage header from AccountStorage
181        let slots = vec![
182            (StorageSlotType::Value, Word::from([1, 2, 3, 4u32])),
183            (
184                StorageSlotType::Value,
185                Word::from([Felt::new(5), Felt::new(6), Felt::new(7), Felt::new(8)]),
186            ),
187            (StorageSlotType::Map, storage_map.root()),
188        ];
189
190        let expected_header = AccountStorageHeader { slots };
191        let account_storage = AccountStorage::mock();
192
193        assert_eq!(expected_header, AccountStorageHeader::from(&account_storage))
194    }
195
196    #[test]
197    fn test_serde_account_storage_header() {
198        // create new storage header
199        let storage = AccountStorage::mock();
200        let storage_header = AccountStorageHeader::from(&storage);
201
202        // serde storage header
203        let bytes = storage_header.to_bytes();
204        let deserialized = AccountStorageHeader::read_from_bytes(&bytes).unwrap();
205
206        // assert deserialized == storage header
207        assert_eq!(storage_header, deserialized);
208    }
209}