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::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#[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> = source.read_many(len as usize)?;
237 Self::new(slots).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
238 }
239}
240
241#[derive(Debug, Clone, PartialEq, Eq)]
251pub struct StorageSlotHeader {
252 name: StorageSlotName,
253 r#type: StorageSlotType,
254 value: Word,
255}
256
257impl StorageSlotHeader {
258 pub fn new(name: StorageSlotName, r#type: StorageSlotType, value: Word) -> Self {
263 Self { name, r#type, value }
264 }
265
266 pub fn with_empty_value(name: StorageSlotName) -> StorageSlotHeader {
268 StorageSlotHeader::new(name, StorageSlotType::Value, Word::default())
269 }
270
271 pub fn with_empty_map(name: StorageSlotName) -> StorageSlotHeader {
273 StorageSlotHeader::new(name, StorageSlotType::Map, EMPTY_STORAGE_MAP_ROOT)
274 }
275
276 pub fn name(&self) -> &StorageSlotName {
281 &self.name
282 }
283
284 pub fn id(&self) -> StorageSlotId {
286 self.name.id()
287 }
288
289 pub fn slot_type(&self) -> StorageSlotType {
291 self.r#type
292 }
293
294 pub fn value(&self) -> Word {
296 self.value
297 }
298
299 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
325impl 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#[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 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 let storage = AccountStorage::mock();
387 let storage_header = AccountStorageHeader::from(&storage);
388
389 let bytes = storage_header.to_bytes();
391 let deserialized = AccountStorageHeader::read_from_bytes(&bytes).unwrap();
392
393 assert_eq!(storage_header, deserialized);
395 }
396
397 #[test]
398 fn test_to_elements_from_elements_empty() {
399 let empty_header = AccountStorageHeader::new(vec![]).unwrap();
401 let empty_elements = empty_header.to_elements();
402
403 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 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 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 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 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 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 let mut invalid_type_elements = vec![crate::ZERO; 8];
475 invalid_type_elements[1] = Felt::new(5); 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 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 let elements = slot1.to_elements();
496
497 let mut slot_names = BTreeMap::new();
499 slot_names.insert(slot1.id(), slot_name1.clone());
500
501 let reconstructed_header =
503 AccountStorageHeader::try_from_elements(&elements, &slot_names).unwrap();
504
505 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}