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    // CONSTRUCTORS
29    // --------------------------------------------------------------------------------------------
30
31    /// Returns a new instance of partial storage with the specified header and storage map SMTs.
32    ///
33    /// The storage commitment is computed during instantiation based on the provided header.
34    /// Additionally, this function validates that the passed SMTs correspond to one of the map
35    /// roots in the storage header.
36    pub fn new(
37        storage_header: AccountStorageHeader,
38        storage_maps: impl IntoIterator<Item = PartialStorageMap>,
39    ) -> Result<Self, AccountError> {
40        let storage_map_roots: BTreeSet<_> = storage_header.map_slot_roots().collect();
41        let mut maps = BTreeMap::new();
42        for smt in storage_maps {
43            // Check that the passed storage map partial SMT has a matching map slot root
44            if !storage_map_roots.contains(&smt.root()) {
45                return Err(AccountError::StorageMapRootNotFound(smt.root()));
46            }
47            maps.insert(smt.root(), smt);
48        }
49
50        let commitment = storage_header.compute_commitment();
51        Ok(Self { commitment, header: storage_header, maps })
52    }
53
54    /// Converts an [`AccountStorage`] into a partial storage representation.
55    ///
56    /// This creates a partial storage that contains the _full_ proofs for all key-value pairs
57    /// in all map slots of the account storage.
58    pub fn new_full(account_storage: AccountStorage) -> Self {
59        let header: AccountStorageHeader = account_storage.to_header();
60        let commitment = header.compute_commitment();
61
62        let mut maps = BTreeMap::new();
63        for slot in account_storage {
64            if let StorageSlot::Map(storage_map) = slot {
65                let partial_map = PartialStorageMap::new_full(storage_map);
66                maps.insert(partial_map.root(), partial_map);
67            }
68        }
69
70        PartialStorage { header, maps, commitment }
71    }
72
73    /// Converts an [`AccountStorage`] into a partial storage representation.
74    ///
75    /// For every storage map, a single unspecified key-value pair is tracked so that the
76    /// [`PartialStorageMap`] represents the correct root.
77    pub fn new_minimal(account_storage: &AccountStorage) -> Self {
78        let header: AccountStorageHeader = account_storage.to_header();
79        let commitment = header.compute_commitment();
80
81        let mut maps = BTreeMap::new();
82        for slot in account_storage.slots() {
83            if let StorageSlot::Map(storage_map) = slot {
84                let partial_map = PartialStorageMap::new_minimal(storage_map);
85                maps.insert(partial_map.root(), partial_map);
86            }
87        }
88
89        PartialStorage { header, maps, commitment }
90    }
91
92    // ACCESSORS
93    // --------------------------------------------------------------------------------------------
94
95    /// Returns a reference to the header of this partial storage.
96    pub fn header(&self) -> &AccountStorageHeader {
97        &self.header
98    }
99
100    /// Returns the commitment of this partial storage.
101    pub fn commitment(&self) -> Word {
102        self.commitment
103    }
104
105    // TODO: Consider removing once no longer needed so we don't commit to the underlying BTreeMap
106    // type.
107    /// Consumes self and returns the underlying parts.
108    pub fn into_parts(self) -> (Word, AccountStorageHeader, BTreeMap<Word, PartialStorageMap>) {
109        (self.commitment, self.header, self.maps)
110    }
111
112    // TODO: Add from account storage with (slot/[key])?
113
114    // ITERATORS
115    // --------------------------------------------------------------------------------------------
116
117    /// Returns an iterator over inner nodes of all storage map proofs contained in this
118    /// partial storage.
119    pub fn inner_nodes(&self) -> impl Iterator<Item = InnerNodeInfo> {
120        self.maps.iter().flat_map(|(_, map)| map.inner_nodes())
121    }
122
123    /// Iterator over every [`PartialStorageMap`] in this partial storage.
124    pub fn maps(&self) -> impl Iterator<Item = &PartialStorageMap> + '_ {
125        self.maps.values()
126    }
127
128    /// Iterator over all tracked, non‑empty leaves across every map.
129    pub fn leaves(&self) -> impl Iterator<Item = &SmtLeaf> + '_ {
130        self.maps().flat_map(|map| map.leaves()).map(|(_, leaf)| leaf)
131    }
132}
133
134impl Serializable for PartialStorage {
135    fn write_into<W: miden_core::utils::ByteWriter>(&self, target: &mut W) {
136        target.write(&self.header);
137        target.write(&self.maps);
138    }
139}
140
141impl Deserializable for PartialStorage {
142    fn read_from<R: miden_core::utils::ByteReader>(
143        source: &mut R,
144    ) -> Result<Self, miden_processor::DeserializationError> {
145        let header: AccountStorageHeader = source.read()?;
146        let map_smts: BTreeMap<Word, PartialStorageMap> = source.read()?;
147
148        let commitment = header.compute_commitment();
149
150        Ok(PartialStorage { header, maps: map_smts, commitment })
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use anyhow::Context;
157    use miden_core::Word;
158
159    use crate::account::{
160        AccountStorage,
161        AccountStorageHeader,
162        PartialStorage,
163        PartialStorageMap,
164        StorageMap,
165        StorageSlot,
166    };
167
168    #[test]
169    pub fn new_partial_storage() -> anyhow::Result<()> {
170        let map_key_present: Word = [1u64, 2, 3, 4].try_into()?;
171        let map_key_absent: Word = [9u64, 12, 18, 3].try_into()?;
172
173        let mut map_1 = StorageMap::new();
174        map_1.insert(map_key_absent, Word::try_from([1u64, 2, 3, 2])?).unwrap();
175        map_1.insert(map_key_present, Word::try_from([5u64, 4, 3, 2])?).unwrap();
176        assert_eq!(map_1.get(&map_key_present), [5u64, 4, 3, 2].try_into()?);
177
178        let storage = AccountStorage::new(vec![StorageSlot::Map(map_1.clone())]).unwrap();
179
180        // Create partial storage with validation of one map key
181        let storage_header = AccountStorageHeader::from(&storage);
182        let witness = map_1.open(&map_key_present);
183
184        let partial_storage =
185            PartialStorage::new(storage_header, [PartialStorageMap::with_witnesses([witness])?])
186                .context("creating partial storage")?;
187
188        let retrieved_map = partial_storage.maps.get(&partial_storage.header.slot(0)?.1).unwrap();
189        assert!(retrieved_map.open(&map_key_absent).is_err());
190        assert!(retrieved_map.open(&map_key_present).is_ok());
191        Ok(())
192    }
193}