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