miden_objects/asset/vault/
partial.rs1use alloc::string::ToString;
2
3use miden_crypto::merkle::{InnerNodeInfo, MerkleError, PartialSmt, SmtLeaf, SmtProof};
4
5use super::AssetVault;
6use crate::Word;
7use crate::asset::Asset;
8use crate::errors::PartialAssetVaultError;
9use crate::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable};
10
11#[derive(Clone, Debug, PartialEq, Eq, Default)]
17pub struct PartialVault {
18    partial_smt: PartialSmt,
20}
21
22impl PartialVault {
23    pub fn new(partial_smt: PartialSmt) -> Result<Self, PartialAssetVaultError> {
35        Self::validate_entries(partial_smt.entries())?;
36
37        Ok(PartialVault { partial_smt })
38    }
39
40    pub fn root(&self) -> Word {
45        self.partial_smt.root()
46    }
47
48    pub fn inner_nodes(&self) -> impl Iterator<Item = InnerNodeInfo> + '_ {
53        self.partial_smt.inner_nodes()
54    }
55
56    pub fn leaves(&self) -> impl Iterator<Item = &SmtLeaf> {
60        self.partial_smt.leaves().map(|(_, leaf)| leaf)
61    }
62
63    pub fn get(&self, vault_key: Word) -> Result<Option<Asset>, MerkleError> {
72        self.partial_smt.get_value(&vault_key).map(|word| {
73            if word.is_empty() {
74                None
75            } else {
76                Some(Asset::try_from(word).expect("partial vault should only track valid assets"))
79            }
80        })
81    }
82
83    pub fn add(&mut self, proof: SmtProof) -> Result<(), PartialAssetVaultError> {
96        Self::validate_entries(proof.leaf().entries())?;
97
98        self.partial_smt
99            .add_proof(proof)
100            .map_err(PartialAssetVaultError::FailedToAddProof)
101    }
102
103    fn validate_entries<'a>(
111        entries: impl IntoIterator<Item = &'a (Word, Word)>,
112    ) -> Result<(), PartialAssetVaultError> {
113        for (vault_key, asset) in entries {
114            let asset = Asset::try_from(asset).map_err(|source| {
115                PartialAssetVaultError::InvalidAssetInSmt { entry: *asset, source }
116            })?;
117
118            if asset.vault_key() != *vault_key {
119                return Err(PartialAssetVaultError::VaultKeyMismatch {
120                    expected: asset.vault_key(),
121                    actual: *vault_key,
122                });
123            }
124        }
125
126        Ok(())
127    }
128}
129
130impl From<&AssetVault> for PartialVault {
131    fn from(value: &AssetVault) -> Self {
132        let vault_partial_smt = value.asset_tree.clone().into();
133
134        PartialVault { partial_smt: vault_partial_smt }
135    }
136}
137
138impl Serializable for PartialVault {
139    fn write_into<W: ByteWriter>(&self, target: &mut W) {
140        target.write(&self.partial_smt)
141    }
142}
143
144impl Deserializable for PartialVault {
145    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
146        let vault_partial_smt = source.read()?;
147
148        PartialVault::new(vault_partial_smt)
149            .map_err(|err| DeserializationError::InvalidValue(err.to_string()))
150    }
151}
152
153#[cfg(test)]
157mod tests {
158    use assert_matches::assert_matches;
159    use miden_crypto::merkle::Smt;
160
161    use super::*;
162    use crate::asset::FungibleAsset;
163
164    #[test]
165    fn partial_vault_ensures_asset_validity() -> anyhow::Result<()> {
166        let invalid_asset = Word::from([0, 0, 0, 5u32]);
167        let smt = Smt::with_entries([(invalid_asset, invalid_asset)])?;
168        let proof = smt.open(&invalid_asset);
169        let partial_smt = PartialSmt::from_proofs([proof.clone()])?;
170
171        let err = PartialVault::new(partial_smt).unwrap_err();
172        assert_matches!(err, PartialAssetVaultError::InvalidAssetInSmt { entry, .. } => {
173            assert_eq!(entry, invalid_asset);
174        });
175
176        let err = PartialVault::default().add(proof).unwrap_err();
177        assert_matches!(err, PartialAssetVaultError::InvalidAssetInSmt { entry, .. } => {
178            assert_eq!(entry, invalid_asset);
179        });
180
181        Ok(())
182    }
183
184    #[test]
185    fn partial_vault_ensures_asset_vault_key_matches() -> anyhow::Result<()> {
186        let asset = FungibleAsset::mock(500);
187        let invalid_vault_key = Word::from([0, 1, 2, 3u32]);
188        let smt = Smt::with_entries([(invalid_vault_key, asset.into())])?;
189        let proof = smt.open(&invalid_vault_key);
190        let partial_smt = PartialSmt::from_proofs([proof.clone()])?;
191
192        let err = PartialVault::new(partial_smt).unwrap_err();
193        assert_matches!(err, PartialAssetVaultError::VaultKeyMismatch { expected, actual } => {
194            assert_eq!(actual, invalid_vault_key);
195            assert_eq!(expected, asset.vault_key());
196        });
197
198        let err = PartialVault::default().add(proof).unwrap_err();
199        assert_matches!(err, PartialAssetVaultError::VaultKeyMismatch { expected, actual } => {
200            assert_eq!(actual, invalid_vault_key);
201            assert_eq!(expected, asset.vault_key());
202        });
203
204        Ok(())
205    }
206}