miden_objects/asset/vault/
partial.rs1use alloc::string::ToString;
2
3use miden_crypto::merkle::{InnerNodeInfo, MerkleError, PartialSmt, SmtLeaf, SmtProof};
4
5use super::{AssetVault, AssetVaultKey};
6use crate::Word;
7use crate::asset::{Asset, AssetWitness};
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(root: Word) -> Self {
30 PartialVault { partial_smt: PartialSmt::new(root) }
31 }
32
33 pub fn new_full(vault: AssetVault) -> Self {
38 let partial_smt = PartialSmt::from(vault.asset_tree);
39
40 PartialVault { partial_smt }
41 }
42
43 pub fn new_minimal(vault: &AssetVault) -> Self {
48 PartialVault::new(vault.root())
49 }
50
51 pub fn root(&self) -> Word {
56 self.partial_smt.root()
57 }
58
59 pub fn inner_nodes(&self) -> impl Iterator<Item = InnerNodeInfo> + '_ {
64 self.partial_smt.inner_nodes()
65 }
66
67 pub fn leaves(&self) -> impl Iterator<Item = &SmtLeaf> {
71 self.partial_smt.leaves().map(|(_, leaf)| leaf)
72 }
73
74 pub fn open(&self, vault_key: AssetVaultKey) -> Result<AssetWitness, PartialAssetVaultError> {
83 let smt_proof = self
84 .partial_smt
85 .open(&vault_key.into())
86 .map_err(PartialAssetVaultError::UntrackedAsset)?;
87 Ok(AssetWitness::new_unchecked(smt_proof))
89 }
90
91 pub fn get(&self, vault_key: AssetVaultKey) -> Result<Option<Asset>, MerkleError> {
100 self.partial_smt.get_value(&vault_key.into()).map(|word| {
101 if word.is_empty() {
102 None
103 } else {
104 Some(Asset::try_from(word).expect("partial vault should only track valid assets"))
107 }
108 })
109 }
110
111 pub fn add(&mut self, witness: AssetWitness) -> Result<(), PartialAssetVaultError> {
122 let proof = SmtProof::from(witness);
123 self.partial_smt
124 .add_proof(proof)
125 .map_err(PartialAssetVaultError::FailedToAddProof)
126 }
127
128 fn validate_entries<'a>(
136 entries: impl IntoIterator<Item = &'a (Word, Word)>,
137 ) -> Result<(), PartialAssetVaultError> {
138 for (vault_key, asset) in entries {
139 let asset = Asset::try_from(asset).map_err(|source| {
140 PartialAssetVaultError::InvalidAssetInSmt { entry: *asset, source }
141 })?;
142
143 if *vault_key != asset.vault_key().into() {
144 return Err(PartialAssetVaultError::AssetVaultKeyMismatch {
145 expected: asset.vault_key(),
146 actual: *vault_key,
147 });
148 }
149 }
150
151 Ok(())
152 }
153}
154
155impl TryFrom<PartialSmt> for PartialVault {
156 type Error = PartialAssetVaultError;
157
158 fn try_from(partial_smt: PartialSmt) -> Result<Self, Self::Error> {
167 Self::validate_entries(partial_smt.entries())?;
168
169 Ok(PartialVault { partial_smt })
170 }
171}
172
173impl Serializable for PartialVault {
174 fn write_into<W: ByteWriter>(&self, target: &mut W) {
175 target.write(&self.partial_smt)
176 }
177}
178
179impl Deserializable for PartialVault {
180 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
181 let partial_smt: PartialSmt = source.read()?;
182
183 PartialVault::try_from(partial_smt)
184 .map_err(|err| DeserializationError::InvalidValue(err.to_string()))
185 }
186}
187
188#[cfg(test)]
192mod tests {
193 use assert_matches::assert_matches;
194 use miden_crypto::merkle::Smt;
195
196 use super::*;
197 use crate::asset::FungibleAsset;
198
199 #[test]
200 fn partial_vault_ensures_asset_validity() -> anyhow::Result<()> {
201 let invalid_asset = Word::from([0, 0, 0, 5u32]);
202 let smt = Smt::with_entries([(invalid_asset, invalid_asset)])?;
203 let proof = smt.open(&invalid_asset);
204 let partial_smt = PartialSmt::from_proofs([proof.clone()])?;
205
206 let err = PartialVault::try_from(partial_smt).unwrap_err();
207 assert_matches!(err, PartialAssetVaultError::InvalidAssetInSmt { entry, .. } => {
208 assert_eq!(entry, invalid_asset);
209 });
210
211 Ok(())
212 }
213
214 #[test]
215 fn partial_vault_ensures_asset_vault_key_matches() -> anyhow::Result<()> {
216 let asset = FungibleAsset::mock(500);
217 let invalid_vault_key = Word::from([0, 1, 2, 3u32]);
218 let smt = Smt::with_entries([(invalid_vault_key, asset.into())])?;
219 let proof = smt.open(&invalid_vault_key);
220 let partial_smt = PartialSmt::from_proofs([proof.clone()])?;
221
222 let err = PartialVault::try_from(partial_smt).unwrap_err();
223 assert_matches!(err, PartialAssetVaultError::AssetVaultKeyMismatch { expected, actual } => {
224 assert_eq!(actual, invalid_vault_key);
225 assert_eq!(expected, asset.vault_key());
226 });
227
228 Ok(())
229 }
230}