diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dba6c36b..cd593c6d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Refactored `AccountStorage` from `Smt` to `sequential hash` (#846). - [BREAKING] Refactored batch/block note trees (#834). - Set all procedures storage offsets of faucet accounts to `1` (#875). +- Added `AccountStorageHeader` (#876). ## 0.5.1 (2024-08-28) - `miden-objects` crate only diff --git a/objects/src/accounts/mod.rs b/objects/src/accounts/mod.rs index c5bcfacdc..b33bd06ac 100644 --- a/objects/src/accounts/mod.rs +++ b/objects/src/accounts/mod.rs @@ -26,7 +26,7 @@ mod seed; pub use seed::{get_account_seed, get_account_seed_single}; mod storage; -pub use storage::{AccountStorage, StorageMap, StorageSlot, StorageSlotType}; +pub use storage::{AccountStorage, AccountStorageHeader, StorageMap, StorageSlot, StorageSlotType}; mod header; pub use header::AccountHeader; diff --git a/objects/src/accounts/storage/header.rs b/objects/src/accounts/storage/header.rs new file mode 100644 index 000000000..27709959e --- /dev/null +++ b/objects/src/accounts/storage/header.rs @@ -0,0 +1,135 @@ +use alloc::vec::Vec; + +use vm_core::utils::{ByteReader, ByteWriter, Deserializable, Serializable}; +use vm_processor::DeserializationError; + +use super::{AccountStorage, StorageSlotType, Word}; +use crate::AccountError; + +// ACCOUNT STORAGE HEADER +// ================================================================================================ + +/// Account storage header is a lighter version of the [AccountStorage] storing only the type and +/// the top-level value for each storage slot. +/// +/// That is, for complex storage slots (e.g., storage maps), the header contains only the commitment +/// to the underlying data. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AccountStorageHeader { + slots: Vec<(StorageSlotType, Word)>, +} + +impl AccountStorageHeader { + // CONSTRUCTOR + // -------------------------------------------------------------------------------------------- + + /// Returns a new instance of account storage header initialized with the provided slots. + /// + /// # Panics + /// - If the number of provided slots is greater than [AccountStorage::MAX_NUM_STORAGE_SLOTS]. + pub fn new(slots: Vec<(StorageSlotType, Word)>) -> Self { + assert!(slots.len() <= AccountStorage::MAX_NUM_STORAGE_SLOTS); + Self { slots } + } + + // PUBLIC ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Returns an iterator over the storage header slots. + pub fn slots(&self) -> impl Iterator { + self.slots.iter() + } + + /// Returns the number of slots contained in the storage header. + pub fn num_slots(&self) -> usize { + self.slots.len() + } + + /// Returns a slot contained in the storage header at a given index. + /// + /// # Errors + /// - If the index is out of bounds. + pub fn slot(&self, index: usize) -> Result<&(StorageSlotType, Word), AccountError> { + self.slots.get(index).ok_or(AccountError::StorageIndexOutOfBounds { + max: self.slots.len() as u8, + actual: index as u8, + }) + } +} + +impl From for AccountStorageHeader { + fn from(value: AccountStorage) -> Self { + value.get_header() + } +} + +// SERIALIZATION +// ================================================================================================ + +impl Serializable for AccountStorageHeader { + fn write_into(&self, target: &mut W) { + let len = self.slots.len() as u8; + target.write_u8(len); + target.write_many(self.slots()) + } +} + +impl Deserializable for AccountStorageHeader { + fn read_from(source: &mut R) -> Result { + let len = source.read_u8()?; + let slots = source.read_many(len as usize)?; + // number of storage slots is guaranteed to be smaller than or equal to 255 + Ok(Self::new(slots)) + } +} + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + use vm_core::{ + utils::{Deserializable, Serializable}, + Felt, + }; + + use super::AccountStorageHeader; + use crate::accounts::{AccountStorage, StorageSlotType}; + + #[test] + fn test_from_account_storage() { + // create new storage header from AccountStorage + let slots = vec![ + (StorageSlotType::Value, [Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]), + (StorageSlotType::Value, [Felt::new(5), Felt::new(6), Felt::new(7), Felt::new(8)]), + ( + StorageSlotType::Map, + [ + Felt::new(12405212884040084310), + Felt::new(17614307840949763446), + Felt::new(6101527485586301500), + Felt::new(14442045877206841081), + ], + ), + ]; + + let expected_header = AccountStorageHeader { slots }; + let account_storage = AccountStorage::mock(); + + assert_eq!(expected_header, AccountStorageHeader::from(account_storage)) + } + + #[test] + fn test_serde_account_storage_header() { + // create new storage header + let storage = AccountStorage::mock(); + let storage_header = AccountStorageHeader::from(storage); + + // serde storage header + let bytes = storage_header.to_bytes(); + let deserialized = AccountStorageHeader::read_from_bytes(&bytes).unwrap(); + + // assert deserialized == storage header + assert_eq!(storage_header, deserialized); + } +} diff --git a/objects/src/accounts/storage/mod.rs b/objects/src/accounts/storage/mod.rs index 7daddc247..631c97f2d 100644 --- a/objects/src/accounts/storage/mod.rs +++ b/objects/src/accounts/storage/mod.rs @@ -11,6 +11,9 @@ pub use slot::{StorageSlot, StorageSlotType}; mod map; pub use map::StorageMap; +mod header; +pub use header::AccountStorageHeader; + // ACCOUNT STORAGE // ================================================================================================ @@ -29,7 +32,7 @@ pub struct AccountStorage { } impl AccountStorage { - /// The maximum number of storage slots allowed in an [AccountStorage] + /// The maximum number of storage slots allowed in an account storage. pub const MAX_NUM_STORAGE_SLOTS: usize = 255; // CONSTRUCTOR @@ -59,7 +62,7 @@ impl AccountStorage { build_slots_commitment(&self.slots) } - /// Converts storage slots of this [AccountStorage] into a vector of field elements. + /// Converts storage slots of this account storage into a vector of field elements. /// /// This is done by first converting each procedure into exactly 8 elements as follows: /// ```text @@ -99,6 +102,13 @@ impl AccountStorage { } } + /// Returns an [AccountStorageHeader] for this account storage. + pub fn get_header(&self) -> AccountStorageHeader { + AccountStorageHeader::new( + self.slots.iter().map(|slot| (slot.slot_type(), slot.value())).collect(), + ) + } + // DATA MUTATORS // --------------------------------------------------------------------------------------------