miden_protocol/account/storage/
header.rs1use 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::ZERO;
9use crate::account::{StorageSlot, StorageSlotId, StorageSlotName};
10use crate::crypto::SequentialCommit;
11use crate::errors::AccountError;
12use crate::utils::serde::{
13 ByteReader,
14 ByteWriter,
15 Deserializable,
16 DeserializationError,
17 Serializable,
18};
19
20#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct AccountStorageHeader {
31 slots: Vec<StorageSlotHeader>,
32}
33
34impl AccountStorageHeader {
35 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 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 #[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 pub fn slots(&self) -> impl Iterator<Item = &StorageSlotHeader> {
92 self.slots.iter()
93 }
94
95 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 pub fn num_slots(&self) -> u8 {
105 self.slots.len() as u8
107 }
108
109 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 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 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 pub fn to_elements(&self) -> Vec<Felt> {
153 <Self as SequentialCommit>::to_elements(self)
154 }
155
156 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 let slot_type_felt = chunk[1];
174 let slot_type = slot_type_felt.try_into()?;
175
176 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 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 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 slots.sort_by_key(|slot| slot.id());
195
196 Self::new(slots)
197 }
198
199 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
211impl 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
222impl 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> =
237 source.read_many_iter(len as usize)?.collect::<Result<_, _>>()?;
238 Self::new(slots).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
239 }
240}
241
242#[derive(Debug, Clone, PartialEq, Eq)]
252pub struct StorageSlotHeader {
253 name: StorageSlotName,
254 r#type: StorageSlotType,
255 value: Word,
256}
257
258impl StorageSlotHeader {
259 pub fn new(name: StorageSlotName, r#type: StorageSlotType, value: Word) -> Self {
264 Self { name, r#type, value }
265 }
266
267 pub fn with_empty_value(name: StorageSlotName) -> StorageSlotHeader {
269 StorageSlotHeader::new(name, StorageSlotType::Value, Word::default())
270 }
271
272 pub fn with_empty_map(name: StorageSlotName) -> StorageSlotHeader {
274 StorageSlotHeader::new(name, StorageSlotType::Map, EMPTY_STORAGE_MAP_ROOT)
275 }
276
277 pub fn name(&self) -> &StorageSlotName {
282 &self.name
283 }
284
285 pub fn id(&self) -> StorageSlotId {
287 self.name.id()
288 }
289
290 pub fn slot_type(&self) -> StorageSlotType {
292 self.r#type
293 }
294
295 pub fn value(&self) -> Word {
297 self.value
298 }
299
300 pub(crate) fn to_elements(&self) -> [Felt; StorageSlot::NUM_ELEMENTS] {
307 let id = self.id();
308 let mut elements = [ZERO; StorageSlot::NUM_ELEMENTS];
309 elements[0..4].copy_from_slice(&[
310 Felt::ZERO,
311 self.r#type.as_felt(),
312 id.suffix(),
313 id.prefix(),
314 ]);
315 elements[4..8].copy_from_slice(self.value.as_elements());
316 elements
317 }
318}
319
320impl From<&StorageSlot> for StorageSlotHeader {
321 fn from(slot: &StorageSlot) -> Self {
322 StorageSlotHeader::new(slot.name().clone(), slot.slot_type(), slot.value())
323 }
324}
325
326impl Serializable for StorageSlotHeader {
330 fn write_into<W: ByteWriter>(&self, target: &mut W) {
331 self.name.write_into(target);
332 self.r#type.write_into(target);
333 self.value.write_into(target);
334 }
335}
336
337impl Deserializable for StorageSlotHeader {
338 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
339 let name = StorageSlotName::read_from(source)?;
340 let slot_type = StorageSlotType::read_from(source)?;
341 let value = Word::read_from(source)?;
342 Ok(Self::new(name, slot_type, value))
343 }
344}
345
346#[cfg(test)]
350mod tests {
351 use alloc::collections::BTreeMap;
352 use alloc::string::ToString;
353
354 use miden_core::Felt;
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 use crate::utils::serde::{Deserializable, Serializable};
361
362 #[test]
363 fn test_from_account_storage() {
364 let storage_map = AccountStorage::mock_map();
365
366 let mut slots = vec![
368 (MOCK_VALUE_SLOT0.clone(), StorageSlotType::Value, Word::from([1, 2, 3, 4u32])),
369 (
370 MOCK_VALUE_SLOT1.clone(),
371 StorageSlotType::Value,
372 Word::from([Felt::new(5), Felt::new(6), Felt::new(7), Felt::new(8)]),
373 ),
374 (MOCK_MAP_SLOT.clone(), StorageSlotType::Map, storage_map.root()),
375 ];
376 slots.sort_unstable_by_key(|(slot_name, ..)| slot_name.id());
377
378 let expected_header = AccountStorageHeader::from_tuples(slots).unwrap();
379 let account_storage = AccountStorage::mock();
380
381 assert_eq!(expected_header, AccountStorageHeader::from(&account_storage))
382 }
383
384 #[test]
385 fn test_serde_account_storage_header() {
386 let storage = AccountStorage::mock();
388 let storage_header = AccountStorageHeader::from(&storage);
389
390 let bytes = storage_header.to_bytes();
392 let deserialized = AccountStorageHeader::read_from_bytes(&bytes).unwrap();
393
394 assert_eq!(storage_header, deserialized);
396 }
397
398 #[test]
399 fn test_to_elements_from_elements_empty() {
400 let empty_header = AccountStorageHeader::new(vec![]).unwrap();
402 let empty_elements = empty_header.to_elements();
403
404 let empty_slot_names = BTreeMap::new();
406 let reconstructed_empty =
407 AccountStorageHeader::try_from_elements(&empty_elements, &empty_slot_names).unwrap();
408 assert_eq!(empty_header, reconstructed_empty);
409 }
410
411 #[test]
412 fn test_to_elements_from_elements_single_slot() {
413 let slot_name1 = StorageSlotName::new("test::value::slot1".to_string()).unwrap();
415 let slot1 = StorageSlotHeader::new(
416 slot_name1,
417 StorageSlotType::Value,
418 Word::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]),
419 );
420
421 let single_slot_header = AccountStorageHeader::new(vec![slot1.clone()]).unwrap();
422 let single_elements = single_slot_header.to_elements();
423
424 let slot_names = BTreeMap::from([(slot1.id(), slot1.name().clone())]);
426 let reconstructed_single =
427 AccountStorageHeader::try_from_elements(&single_elements, &slot_names).unwrap();
428
429 assert_eq!(single_slot_header, reconstructed_single);
430 }
431
432 #[test]
433 fn test_to_elements_from_elements_multiple_slot() {
434 let slot_name2 = StorageSlotName::new("test::map::slot2".to_string()).unwrap();
436 let slot_name3 = StorageSlotName::new("test::value::slot3".to_string()).unwrap();
437
438 let slot2 = StorageSlotHeader::new(
439 slot_name2,
440 StorageSlotType::Map,
441 Word::new([Felt::new(5), Felt::new(6), Felt::new(7), Felt::new(8)]),
442 );
443 let slot3 = StorageSlotHeader::new(
444 slot_name3,
445 StorageSlotType::Value,
446 Word::new([Felt::new(9), Felt::new(10), Felt::new(11), Felt::new(12)]),
447 );
448
449 let mut slots = vec![slot2, slot3];
450 slots.sort_by_key(|slot| slot.id());
451 let multi_slot_header = AccountStorageHeader::new(slots.clone()).unwrap();
452 let multi_elements = multi_slot_header.to_elements();
453
454 let slot_names = BTreeMap::from([
456 (slots[0].id(), slots[0].name.clone()),
457 (slots[1].id(), slots[1].name.clone()),
458 ]);
459 let reconstructed_multi =
460 AccountStorageHeader::try_from_elements(&multi_elements, &slot_names).unwrap();
461
462 assert_eq!(multi_slot_header, reconstructed_multi);
463 }
464
465 #[test]
466 fn test_from_elements_errors() {
467 let invalid_elements = vec![Felt::new(1), Felt::new(2), Felt::new(3)];
469 let empty_slot_names = BTreeMap::new();
470 assert!(
471 AccountStorageHeader::try_from_elements(&invalid_elements, &empty_slot_names).is_err()
472 );
473
474 let mut invalid_type_elements = vec![crate::ZERO; 8];
476 invalid_type_elements[1] = Felt::new(5); assert!(
478 AccountStorageHeader::try_from_elements(&invalid_type_elements, &empty_slot_names)
479 .is_err()
480 );
481 }
482
483 #[test]
484 fn test_from_elements_with_slot_names() {
485 use alloc::collections::BTreeMap;
486
487 let slot_name1 = StorageSlotName::new("test::value::slot1".to_string()).unwrap();
489 let slot1 = StorageSlotHeader::new(
490 slot_name1.clone(),
491 StorageSlotType::Value,
492 Word::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]),
493 );
494
495 let elements = slot1.to_elements();
497
498 let mut slot_names = BTreeMap::new();
500 slot_names.insert(slot1.id(), slot_name1.clone());
501
502 let reconstructed_header =
504 AccountStorageHeader::try_from_elements(&elements, &slot_names).unwrap();
505
506 assert_eq!(reconstructed_header.slots().count(), 1);
508 let reconstructed_slot = reconstructed_header.slots().next().unwrap();
509
510 assert_eq!(slot_name1.as_str(), reconstructed_slot.name().as_str());
511 assert_eq!(slot1.slot_type(), reconstructed_slot.slot_type());
512 assert_eq!(slot1.value(), reconstructed_slot.value());
513 }
514}