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([
373 Felt::from(5_u32),
374 Felt::from(6_u32),
375 Felt::from(7_u32),
376 Felt::from(8_u32),
377 ]),
378 ),
379 (MOCK_MAP_SLOT.clone(), StorageSlotType::Map, storage_map.root()),
380 ];
381 slots.sort_unstable_by_key(|(slot_name, ..)| slot_name.id());
382
383 let expected_header = AccountStorageHeader::from_tuples(slots).unwrap();
384 let account_storage = AccountStorage::mock();
385
386 assert_eq!(expected_header, AccountStorageHeader::from(&account_storage))
387 }
388
389 #[test]
390 fn test_serde_account_storage_header() {
391 let storage = AccountStorage::mock();
393 let storage_header = AccountStorageHeader::from(&storage);
394
395 let bytes = storage_header.to_bytes();
397 let deserialized = AccountStorageHeader::read_from_bytes(&bytes).unwrap();
398
399 assert_eq!(storage_header, deserialized);
401 }
402
403 #[test]
404 fn test_to_elements_from_elements_empty() {
405 let empty_header = AccountStorageHeader::new(vec![]).unwrap();
407 let empty_elements = empty_header.to_elements();
408
409 let empty_slot_names = BTreeMap::new();
411 let reconstructed_empty =
412 AccountStorageHeader::try_from_elements(&empty_elements, &empty_slot_names).unwrap();
413 assert_eq!(empty_header, reconstructed_empty);
414 }
415
416 #[test]
417 fn test_to_elements_from_elements_single_slot() {
418 let slot_name1 = StorageSlotName::new("test::value::slot1".to_string()).unwrap();
420 let slot1 = StorageSlotHeader::new(
421 slot_name1,
422 StorageSlotType::Value,
423 Word::new([Felt::ONE, Felt::from(2_u32), Felt::from(3_u32), Felt::from(4_u32)]),
424 );
425
426 let single_slot_header = AccountStorageHeader::new(vec![slot1.clone()]).unwrap();
427 let single_elements = single_slot_header.to_elements();
428
429 let slot_names = BTreeMap::from([(slot1.id(), slot1.name().clone())]);
431 let reconstructed_single =
432 AccountStorageHeader::try_from_elements(&single_elements, &slot_names).unwrap();
433
434 assert_eq!(single_slot_header, reconstructed_single);
435 }
436
437 #[test]
438 fn test_to_elements_from_elements_multiple_slot() {
439 let slot_name2 = StorageSlotName::new("test::map::slot2".to_string()).unwrap();
441 let slot_name3 = StorageSlotName::new("test::value::slot3".to_string()).unwrap();
442
443 let slot2 = StorageSlotHeader::new(
444 slot_name2,
445 StorageSlotType::Map,
446 Word::new([Felt::from(5_u32), Felt::from(6_u32), Felt::from(7_u32), Felt::from(8_u32)]),
447 );
448 let slot3 = StorageSlotHeader::new(
449 slot_name3,
450 StorageSlotType::Value,
451 Word::new([
452 Felt::from(9_u32),
453 Felt::from(10_u32),
454 Felt::from(11_u32),
455 Felt::from(12_u32),
456 ]),
457 );
458
459 let mut slots = vec![slot2, slot3];
460 slots.sort_by_key(|slot| slot.id());
461 let multi_slot_header = AccountStorageHeader::new(slots.clone()).unwrap();
462 let multi_elements = multi_slot_header.to_elements();
463
464 let slot_names = BTreeMap::from([
466 (slots[0].id(), slots[0].name.clone()),
467 (slots[1].id(), slots[1].name.clone()),
468 ]);
469 let reconstructed_multi =
470 AccountStorageHeader::try_from_elements(&multi_elements, &slot_names).unwrap();
471
472 assert_eq!(multi_slot_header, reconstructed_multi);
473 }
474
475 #[test]
476 fn test_from_elements_errors() {
477 let invalid_elements = vec![Felt::ONE, Felt::new_unchecked(2), Felt::new_unchecked(3)];
479 let empty_slot_names = BTreeMap::new();
480 assert!(
481 AccountStorageHeader::try_from_elements(&invalid_elements, &empty_slot_names).is_err()
482 );
483
484 let mut invalid_type_elements = vec![crate::ZERO; 8];
486 invalid_type_elements[1] = Felt::new_unchecked(5); assert!(
488 AccountStorageHeader::try_from_elements(&invalid_type_elements, &empty_slot_names)
489 .is_err()
490 );
491 }
492
493 #[test]
494 fn test_from_elements_with_slot_names() {
495 use alloc::collections::BTreeMap;
496
497 let slot_name1 = StorageSlotName::new("test::value::slot1".to_string()).unwrap();
499 let slot1 = StorageSlotHeader::new(
500 slot_name1.clone(),
501 StorageSlotType::Value,
502 Word::new([Felt::ONE, Felt::from(2_u32), Felt::from(3_u32), Felt::from(4_u32)]),
503 );
504
505 let elements = slot1.to_elements();
507
508 let mut slot_names = BTreeMap::new();
510 slot_names.insert(slot1.id(), slot_name1.clone());
511
512 let reconstructed_header =
514 AccountStorageHeader::try_from_elements(&elements, &slot_names).unwrap();
515
516 assert_eq!(reconstructed_header.slots().count(), 1);
518 let reconstructed_slot = reconstructed_header.slots().next().unwrap();
519
520 assert_eq!(slot_name1.as_str(), reconstructed_slot.name().as_str());
521 assert_eq!(slot1.slot_type(), reconstructed_slot.slot_type());
522 assert_eq!(slot1.value(), reconstructed_slot.value());
523 }
524}