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.as_chunks::<{ StorageSlot::NUM_ELEMENTS }>().0 {
172 if chunk[0] != Felt::ZERO {
174 return Err(AccountError::StorageSlotReservedElementNotZero(chunk[0]));
175 }
176
177 let slot_type_felt = chunk[1];
179 let slot_type = slot_type_felt.try_into()?;
180
181 let slot_id_suffix = chunk[2];
183 let slot_id_prefix = chunk[3];
184 let parsed_slot_id = StorageSlotId::new(slot_id_suffix, slot_id_prefix);
185
186 let slot_name = slot_names.get(&parsed_slot_id).cloned().ok_or(AccountError::other(
188 format!("slot name not found for slot ID {}", parsed_slot_id),
189 ))?;
190
191 let slot_value = Word::new([chunk[4], chunk[5], chunk[6], chunk[7]]);
193
194 let slot_header = StorageSlotHeader::new(slot_name, slot_type, slot_value);
195 slots.push(slot_header);
196 }
197
198 slots.sort_by_key(|slot| slot.id());
200
201 Self::new(slots)
202 }
203
204 pub fn to_commitment(&self) -> Word {
206 <Self as SequentialCommit>::to_commitment(self)
207 }
208}
209
210impl From<&AccountStorage> for AccountStorageHeader {
211 fn from(value: &AccountStorage) -> Self {
212 value.to_header()
213 }
214}
215
216impl SequentialCommit for AccountStorageHeader {
220 type Commitment = Word;
221
222 fn to_elements(&self) -> Vec<Felt> {
223 self.slots().flat_map(|slot| slot.to_elements()).collect()
224 }
225}
226
227impl Serializable for AccountStorageHeader {
231 fn write_into<W: ByteWriter>(&self, target: &mut W) {
232 let len = self.slots.len() as u8;
233 target.write_u8(len);
234 target.write_many(self.slots())
235 }
236}
237
238impl Deserializable for AccountStorageHeader {
239 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
240 let len = source.read_u8()?;
241 let slots: Vec<StorageSlotHeader> =
242 source.read_many_iter(len as usize)?.collect::<Result<_, _>>()?;
243 Self::new(slots).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
244 }
245}
246
247#[derive(Debug, Clone, PartialEq, Eq)]
257pub struct StorageSlotHeader {
258 name: StorageSlotName,
259 r#type: StorageSlotType,
260 value: Word,
261}
262
263impl StorageSlotHeader {
264 pub fn new(name: StorageSlotName, r#type: StorageSlotType, value: Word) -> Self {
269 Self { name, r#type, value }
270 }
271
272 pub fn with_empty_value(name: StorageSlotName) -> StorageSlotHeader {
274 StorageSlotHeader::new(name, StorageSlotType::Value, Word::default())
275 }
276
277 pub fn with_empty_map(name: StorageSlotName) -> StorageSlotHeader {
279 StorageSlotHeader::new(name, StorageSlotType::Map, EMPTY_STORAGE_MAP_ROOT)
280 }
281
282 pub fn name(&self) -> &StorageSlotName {
287 &self.name
288 }
289
290 pub fn id(&self) -> StorageSlotId {
292 self.name.id()
293 }
294
295 pub fn slot_type(&self) -> StorageSlotType {
297 self.r#type
298 }
299
300 pub fn value(&self) -> Word {
302 self.value
303 }
304
305 pub(crate) fn to_elements(&self) -> [Felt; StorageSlot::NUM_ELEMENTS] {
312 let id = self.id();
313 let mut elements = [ZERO; StorageSlot::NUM_ELEMENTS];
314 elements[0..4].copy_from_slice(&[
315 Felt::ZERO,
316 self.r#type.as_felt(),
317 id.suffix(),
318 id.prefix(),
319 ]);
320 elements[4..8].copy_from_slice(self.value.as_elements());
321 elements
322 }
323}
324
325impl From<&StorageSlot> for StorageSlotHeader {
326 fn from(slot: &StorageSlot) -> Self {
327 StorageSlotHeader::new(slot.name().clone(), slot.slot_type(), slot.value())
328 }
329}
330
331impl Serializable for StorageSlotHeader {
335 fn write_into<W: ByteWriter>(&self, target: &mut W) {
336 self.name.write_into(target);
337 self.r#type.write_into(target);
338 self.value.write_into(target);
339 }
340}
341
342impl Deserializable for StorageSlotHeader {
343 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
344 let name = StorageSlotName::read_from(source)?;
345 let slot_type = StorageSlotType::read_from(source)?;
346 let value = Word::read_from(source)?;
347 Ok(Self::new(name, slot_type, value))
348 }
349}
350
351#[cfg(test)]
355mod tests {
356 use alloc::collections::BTreeMap;
357 use alloc::string::ToString;
358
359 use assert_matches::assert_matches;
360 use miden_core::Felt;
361
362 use super::AccountStorageHeader;
363 use crate::Word;
364 use crate::account::{AccountStorage, StorageSlotHeader, StorageSlotName, StorageSlotType};
365 use crate::errors::AccountError;
366 use crate::testing::storage::{MOCK_MAP_SLOT, MOCK_VALUE_SLOT0, MOCK_VALUE_SLOT1};
367 use crate::utils::serde::{Deserializable, Serializable};
368
369 #[test]
370 fn test_from_account_storage() {
371 let storage_map = AccountStorage::mock_map();
372
373 let mut slots = vec![
375 (MOCK_VALUE_SLOT0.clone(), StorageSlotType::Value, Word::from([1, 2, 3, 4u32])),
376 (
377 MOCK_VALUE_SLOT1.clone(),
378 StorageSlotType::Value,
379 Word::from([
380 Felt::from(5_u32),
381 Felt::from(6_u32),
382 Felt::from(7_u32),
383 Felt::from(8_u32),
384 ]),
385 ),
386 (MOCK_MAP_SLOT.clone(), StorageSlotType::Map, storage_map.root()),
387 ];
388 slots.sort_unstable_by_key(|(slot_name, ..)| slot_name.id());
389
390 let expected_header = AccountStorageHeader::from_tuples(slots).unwrap();
391 let account_storage = AccountStorage::mock();
392
393 assert_eq!(expected_header, AccountStorageHeader::from(&account_storage))
394 }
395
396 #[test]
397 fn test_serde_account_storage_header() {
398 let storage = AccountStorage::mock();
400 let storage_header = AccountStorageHeader::from(&storage);
401
402 let bytes = storage_header.to_bytes();
404 let deserialized = AccountStorageHeader::read_from_bytes(&bytes).unwrap();
405
406 assert_eq!(storage_header, deserialized);
408 }
409
410 #[test]
411 fn test_to_elements_from_elements_empty() {
412 let empty_header = AccountStorageHeader::new(vec![]).unwrap();
414 let empty_elements = empty_header.to_elements();
415
416 let empty_slot_names = BTreeMap::new();
418 let reconstructed_empty =
419 AccountStorageHeader::try_from_elements(&empty_elements, &empty_slot_names).unwrap();
420 assert_eq!(empty_header, reconstructed_empty);
421 }
422
423 #[test]
424 fn test_to_elements_from_elements_single_slot() {
425 let slot_name1 = StorageSlotName::new("test::value::slot1".to_string()).unwrap();
427 let slot1 = StorageSlotHeader::new(
428 slot_name1,
429 StorageSlotType::Value,
430 Word::new([Felt::ONE, Felt::from(2_u32), Felt::from(3_u32), Felt::from(4_u32)]),
431 );
432
433 let single_slot_header = AccountStorageHeader::new(vec![slot1.clone()]).unwrap();
434 let single_elements = single_slot_header.to_elements();
435
436 let slot_names = BTreeMap::from([(slot1.id(), slot1.name().clone())]);
438 let reconstructed_single =
439 AccountStorageHeader::try_from_elements(&single_elements, &slot_names).unwrap();
440
441 assert_eq!(single_slot_header, reconstructed_single);
442 }
443
444 #[test]
445 fn test_to_elements_from_elements_multiple_slot() {
446 let slot_name2 = StorageSlotName::new("test::map::slot2".to_string()).unwrap();
448 let slot_name3 = StorageSlotName::new("test::value::slot3".to_string()).unwrap();
449
450 let slot2 = StorageSlotHeader::new(
451 slot_name2,
452 StorageSlotType::Map,
453 Word::new([Felt::from(5_u32), Felt::from(6_u32), Felt::from(7_u32), Felt::from(8_u32)]),
454 );
455 let slot3 = StorageSlotHeader::new(
456 slot_name3,
457 StorageSlotType::Value,
458 Word::new([
459 Felt::from(9_u32),
460 Felt::from(10_u32),
461 Felt::from(11_u32),
462 Felt::from(12_u32),
463 ]),
464 );
465
466 let mut slots = vec![slot2, slot3];
467 slots.sort_by_key(|slot| slot.id());
468 let multi_slot_header = AccountStorageHeader::new(slots.clone()).unwrap();
469 let multi_elements = multi_slot_header.to_elements();
470
471 let slot_names = BTreeMap::from([
473 (slots[0].id(), slots[0].name.clone()),
474 (slots[1].id(), slots[1].name.clone()),
475 ]);
476 let reconstructed_multi =
477 AccountStorageHeader::try_from_elements(&multi_elements, &slot_names).unwrap();
478
479 assert_eq!(multi_slot_header, reconstructed_multi);
480 }
481
482 #[test]
483 fn test_from_elements_errors() {
484 let invalid_elements = vec![Felt::ONE, Felt::new_unchecked(2), Felt::new_unchecked(3)];
486 let empty_slot_names = BTreeMap::new();
487 assert!(
488 AccountStorageHeader::try_from_elements(&invalid_elements, &empty_slot_names).is_err()
489 );
490
491 let mut invalid_type_elements = vec![crate::ZERO; 8];
493 invalid_type_elements[1] = Felt::new_unchecked(5); assert!(
495 AccountStorageHeader::try_from_elements(&invalid_type_elements, &empty_slot_names)
496 .is_err()
497 );
498
499 let mut reserved_elements = vec![crate::ZERO; 8];
501 reserved_elements[0] = Felt::ONE; let err = AccountStorageHeader::try_from_elements(&reserved_elements, &empty_slot_names)
503 .unwrap_err();
504 assert_matches!(err, AccountError::StorageSlotReservedElementNotZero(value) if value == Felt::ONE);
505 }
506
507 #[test]
508 fn test_from_elements_with_slot_names() {
509 use alloc::collections::BTreeMap;
510
511 let slot_name1 = StorageSlotName::new("test::value::slot1".to_string()).unwrap();
513 let slot1 = StorageSlotHeader::new(
514 slot_name1.clone(),
515 StorageSlotType::Value,
516 Word::new([Felt::ONE, Felt::from(2_u32), Felt::from(3_u32), Felt::from(4_u32)]),
517 );
518
519 let elements = slot1.to_elements();
521
522 let mut slot_names = BTreeMap::new();
524 slot_names.insert(slot1.id(), slot_name1.clone());
525
526 let reconstructed_header =
528 AccountStorageHeader::try_from_elements(&elements, &slot_names).unwrap();
529
530 assert_eq!(reconstructed_header.slots().count(), 1);
532 let reconstructed_slot = reconstructed_header.slots().next().unwrap();
533
534 assert_eq!(slot_name1.as_str(), reconstructed_slot.name().as_str());
535 assert_eq!(slot1.slot_type(), reconstructed_slot.slot_type());
536 assert_eq!(slot1.value(), reconstructed_slot.value());
537 }
538}