miden_objects/account/storage/
partial.rs

1use alloc::collections::{BTreeMap, BTreeSet};
2
3use miden_core::utils::{Deserializable, Serializable};
4use miden_crypto::Word;
5use miden_crypto::merkle::{InnerNodeInfo, SmtLeaf};
6
7use super::{AccountStorage, AccountStorageHeader, StorageSlot};
8use crate::AccountError;
9use crate::account::PartialStorageMap;
10
11/// A partial representation of an account storage, containing only a subset of the storage data.
12///
13/// Partial storage is used to provide verifiable access to specific segments of account storage
14/// without the need to provide the full storage data. It contains all needed parts for loading
15/// account storage data into the transaction kernel.
16#[derive(Clone, Debug, PartialEq, Eq)]
17pub struct PartialStorage {
18    /// Commitment of the account's storage slots.
19    commitment: Word,
20    /// Account storage header.
21    header: AccountStorageHeader,
22    /// Storage partial storage maps indexed by their root, containing a subset of the elements
23    /// from the complete storage map.
24    maps: BTreeMap<Word, PartialStorageMap>,
25}
26
27impl PartialStorage {
28    /// Returns a new instance of partial storage with the specified header and storage map SMTs.
29    ///
30    /// The storage commitment is computed during instantiation based on the provided header.
31    /// Additionally, this function validates that the passed SMTs correspond to one of the map
32    /// roots in the storage header.
33    pub fn new(
34        storage_header: AccountStorageHeader,
35        storage_maps: impl IntoIterator<Item = PartialStorageMap>,
36    ) -> Result<Self, AccountError> {
37        let storage_map_roots: BTreeSet<_> = storage_header.map_slot_roots().collect();
38        let mut maps = BTreeMap::new();
39        for smt in storage_maps {
40            // Check that the passed storage map partial SMT has a matching map slot root
41            if !storage_map_roots.contains(&smt.root()) {
42                return Err(AccountError::StorageMapRootNotFound(smt.root()));
43            }
44            maps.insert(smt.root(), smt);
45        }
46
47        let commitment = storage_header.compute_commitment();
48        Ok(Self { commitment, header: storage_header, maps })
49    }
50
51    /// Returns a reference to the header of this partial storage.
52    pub fn header(&self) -> &AccountStorageHeader {
53        &self.header
54    }
55
56    /// Returns the commitment of this partial storage.
57    pub fn commitment(&self) -> Word {
58        self.commitment
59    }
60
61    // TODO: Add from account storage with (slot/[key])?
62
63    // ITERATORS
64    // --------------------------------------------------------------------------------------------
65
66    /// Returns an iterator over inner nodes of all storage map proofs contained in this
67    /// partial storage.
68    pub fn inner_nodes(&self) -> impl Iterator<Item = InnerNodeInfo> {
69        self.maps.iter().flat_map(|(_, map)| map.inner_nodes())
70    }
71
72    /// Iterator over every [`PartialStorageMap`] in this partial storage.
73    pub fn maps(&self) -> impl Iterator<Item = &PartialStorageMap> + '_ {
74        self.maps.values()
75    }
76
77    /// Iterator over all tracked, non‑empty leaves across every map.
78    pub fn leaves(&self) -> impl Iterator<Item = &SmtLeaf> + '_ {
79        self.maps().flat_map(|map| map.leaves()).map(|(_, leaf)| leaf)
80    }
81}
82
83impl From<&AccountStorage> for PartialStorage {
84    /// Converts a full account storage into a partial storage representation.
85    ///
86    /// This creates a partial storage that contains proofs for all key-value pairs
87    /// in all map slots of the account storage.
88    fn from(account_storage: &AccountStorage) -> Self {
89        let mut map_smts = BTreeMap::new();
90        for slot in account_storage.slots() {
91            if let StorageSlot::Map(map) = slot {
92                let smt: PartialStorageMap = map.clone().into();
93                map_smts.insert(smt.root(), smt);
94            }
95        }
96
97        let header: AccountStorageHeader = account_storage.to_header();
98        let commitment = header.compute_commitment();
99        PartialStorage { header, maps: map_smts, commitment }
100    }
101}
102
103impl Serializable for PartialStorage {
104    fn write_into<W: miden_core::utils::ByteWriter>(&self, target: &mut W) {
105        target.write(&self.header);
106        target.write(&self.maps);
107    }
108}
109
110impl Deserializable for PartialStorage {
111    fn read_from<R: miden_core::utils::ByteReader>(
112        source: &mut R,
113    ) -> Result<Self, miden_processor::DeserializationError> {
114        let header: AccountStorageHeader = source.read()?;
115        let map_smts: BTreeMap<Word, PartialStorageMap> = source.read()?;
116
117        let commitment = header.compute_commitment();
118
119        Ok(PartialStorage { header, maps: map_smts, commitment })
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use anyhow::Context;
126    use miden_core::Word;
127    use miden_crypto::merkle::PartialSmt;
128
129    use crate::account::{
130        AccountStorage,
131        AccountStorageHeader,
132        PartialStorage,
133        StorageMap,
134        StorageSlot,
135    };
136
137    #[test]
138    pub fn new_partial_storage() -> anyhow::Result<()> {
139        let map_key_present: Word = [1u64, 2, 3, 4].try_into()?;
140        let map_key_absent: Word = [9u64, 12, 18, 3].try_into()?;
141
142        let mut map_1 = StorageMap::new();
143        map_1.insert(map_key_absent, Word::try_from([1u64, 2, 3, 2])?);
144        map_1.insert(map_key_present, Word::try_from([5u64, 4, 3, 2])?);
145        assert_eq!(map_1.get(&map_key_present), [5u64, 4, 3, 2].try_into()?);
146
147        let storage = AccountStorage::new(vec![StorageSlot::Map(map_1.clone())]).unwrap();
148
149        // Create partial storage with validation of one map key
150        let storage_header = AccountStorageHeader::from(&storage);
151        let proof = map_1.open(&map_key_present);
152        let partial_smt = PartialSmt::from_proofs([proof])?;
153
154        let partial_storage = PartialStorage::new(storage_header, [partial_smt.into()])
155            .context("creating partial storage")?;
156
157        let retrieved_map = partial_storage.maps.get(&partial_storage.header.slot(0)?.1).unwrap();
158        assert!(retrieved_map.open(&map_key_absent).is_err());
159        assert!(retrieved_map.open(&map_key_present).is_ok());
160        Ok(())
161    }
162}