Skip to main content

miden_protocol/asset/vault/
mod.rs

1use alloc::string::ToString;
2use alloc::vec::Vec;
3
4use miden_crypto::merkle::InnerNodeInfo;
5
6use super::{
7    AccountType,
8    Asset,
9    ByteReader,
10    ByteWriter,
11    Deserializable,
12    DeserializationError,
13    FungibleAsset,
14    NonFungibleAsset,
15    Serializable,
16};
17use crate::Word;
18use crate::account::{AccountId, AccountVaultDelta, NonFungibleDeltaAction};
19use crate::crypto::merkle::smt::{SMT_DEPTH, Smt};
20use crate::errors::AssetVaultError;
21
22mod partial;
23pub use partial::PartialVault;
24
25mod asset_witness;
26pub use asset_witness::AssetWitness;
27
28mod vault_key;
29pub use vault_key::AssetVaultKey;
30
31mod asset_id;
32pub use asset_id::AssetId;
33
34// ASSET VAULT
35// ================================================================================================
36
37/// A container for an unlimited number of assets.
38///
39/// An asset vault can contain an unlimited number of assets. The assets are stored in a Sparse
40/// Merkle tree as follows:
41/// - For fungible assets, the index of a node is defined by the issuing faucet ID, and the value of
42///   the node is the asset itself. Thus, for any fungible asset there will be only one node in the
43///   tree.
44/// - For non-fungible assets, the index is defined by the asset itself, and the asset is also the
45///   value of the node.
46///
47/// An asset vault can be reduced to a single hash which is the root of the Sparse Merkle Tree.
48#[derive(Debug, Clone, Default, PartialEq, Eq)]
49pub struct AssetVault {
50    asset_tree: Smt,
51}
52
53impl AssetVault {
54    // CONSTANTS
55    // --------------------------------------------------------------------------------------------
56
57    /// The depth of the SMT that represents the asset vault.
58    pub const DEPTH: u8 = SMT_DEPTH;
59
60    // CONSTRUCTOR
61    // --------------------------------------------------------------------------------------------
62
63    /// Returns a new [AssetVault] initialized with the provided assets.
64    pub fn new(assets: &[Asset]) -> Result<Self, AssetVaultError> {
65        Ok(Self {
66            asset_tree: Smt::with_entries(
67                assets.iter().map(|asset| (asset.vault_key().to_word(), asset.to_value_word())),
68            )
69            .map_err(AssetVaultError::DuplicateAsset)?,
70        })
71    }
72
73    // PUBLIC ACCESSORS
74    // --------------------------------------------------------------------------------------------
75
76    /// Returns the tree root of this vault.
77    pub fn root(&self) -> Word {
78        self.asset_tree.root()
79    }
80
81    /// Returns the asset corresponding to the provided asset vault key, or `None` if the asset
82    /// doesn't exist.
83    pub fn get(&self, asset_vault_key: AssetVaultKey) -> Option<Asset> {
84        let asset_value = self.asset_tree.get_value(&asset_vault_key.to_word());
85
86        if asset_value.is_empty() {
87            None
88        } else {
89            Some(
90                Asset::from_key_value(asset_vault_key, asset_value)
91                    .expect("asset vault should only store valid assets"),
92            )
93        }
94    }
95
96    /// Returns true if the specified non-fungible asset is stored in this vault.
97    pub fn has_non_fungible_asset(&self, asset: NonFungibleAsset) -> Result<bool, AssetVaultError> {
98        // check if the asset is stored in the vault
99        match self.asset_tree.get_value(&asset.vault_key().to_word()) {
100            asset if asset == Smt::EMPTY_VALUE => Ok(false),
101            _ => Ok(true),
102        }
103    }
104
105    /// Returns the balance of the asset issued by the specified faucet. If the vault does not
106    /// contain such an asset, 0 is returned.
107    ///
108    /// # Errors
109    /// Returns an error if the specified ID is not an ID of a fungible asset faucet.
110    pub fn get_balance(&self, faucet_id: AccountId) -> Result<u64, AssetVaultError> {
111        if !matches!(faucet_id.account_type(), AccountType::FungibleFaucet) {
112            return Err(AssetVaultError::NotAFungibleFaucetId(faucet_id));
113        }
114
115        let vault_key =
116            AssetVaultKey::new_fungible(faucet_id).expect("faucet ID should be of type fungible");
117        let asset_value = self.asset_tree.get_value(&vault_key.to_word());
118        let asset = FungibleAsset::from_key_value(vault_key, asset_value)
119            .expect("asset vault should only store valid assets");
120
121        Ok(asset.amount())
122    }
123
124    /// Returns an iterator over the assets stored in the vault.
125    pub fn assets(&self) -> impl Iterator<Item = Asset> + '_ {
126        // SAFETY: The asset tree tracks only valid assets.
127        self.asset_tree.entries().map(|(key, value)| {
128            Asset::from_key_value_words(*key, *value)
129                .expect("asset vault should only store valid assets")
130        })
131    }
132
133    /// Returns an iterator over the inner nodes of the underlying [`Smt`].
134    pub fn inner_nodes(&self) -> impl Iterator<Item = InnerNodeInfo> + '_ {
135        self.asset_tree.inner_nodes()
136    }
137
138    /// Returns an opening of the leaf associated with `vault_key`.
139    ///
140    /// The `vault_key` can be obtained with [`Asset::vault_key`].
141    pub fn open(&self, vault_key: AssetVaultKey) -> AssetWitness {
142        let smt_proof = self.asset_tree.open(&vault_key.to_word());
143        // SAFETY: The asset vault should only contain valid assets.
144        AssetWitness::new_unchecked(smt_proof)
145    }
146
147    /// Returns a bool indicating whether the vault is empty.
148    pub fn is_empty(&self) -> bool {
149        self.asset_tree.is_empty()
150    }
151
152    /// Returns the number of non-empty leaves in the underlying [`Smt`].
153    ///
154    /// Note that this may return a different value from [Self::num_assets()] as a single leaf may
155    /// contain more than one asset.
156    pub fn num_leaves(&self) -> usize {
157        self.asset_tree.num_leaves()
158    }
159
160    /// Returns the number of assets in this vault.
161    ///
162    /// Note that this may return a different value from [Self::num_leaves()] as a single leaf may
163    /// contain more than one asset.
164    pub fn num_assets(&self) -> usize {
165        self.asset_tree.num_entries()
166    }
167
168    // PUBLIC MODIFIERS
169    // --------------------------------------------------------------------------------------------
170
171    /// Applies the specified delta to the asset vault.
172    ///
173    /// # Errors
174    /// Returns an error:
175    /// - If the total value of the added assets is greater than [`FungibleAsset::MAX_AMOUNT`].
176    /// - If the delta contains an addition/subtraction for a fungible asset that is not stored in
177    ///   the vault.
178    /// - If the delta contains a non-fungible asset removal that is not stored in the vault.
179    /// - If the delta contains a non-fungible asset addition that is already stored in the vault.
180    /// - The maximum number of leaves per asset is exceeded.
181    pub fn apply_delta(&mut self, delta: &AccountVaultDelta) -> Result<(), AssetVaultError> {
182        for (&faucet_id, &delta) in delta.fungible().iter() {
183            let asset = FungibleAsset::new(faucet_id, delta.unsigned_abs())
184                .expect("Not a fungible faucet ID or delta is too large");
185            match delta >= 0 {
186                true => self.add_fungible_asset(asset),
187                false => self.remove_fungible_asset(asset),
188            }?;
189        }
190
191        for (&asset, &action) in delta.non_fungible().iter() {
192            match action {
193                NonFungibleDeltaAction::Add => self.add_non_fungible_asset(asset),
194                NonFungibleDeltaAction::Remove => self.remove_non_fungible_asset(asset),
195            }?;
196        }
197
198        Ok(())
199    }
200
201    // ADD ASSET
202    // --------------------------------------------------------------------------------------------
203    /// Add the specified asset to the vault.
204    ///
205    /// # Errors
206    /// - If the total value of the added assets is greater than [`FungibleAsset::MAX_AMOUNT`].
207    /// - If the vault already contains the same non-fungible asset.
208    /// - The maximum number of leaves per asset is exceeded.
209    pub fn add_asset(&mut self, asset: Asset) -> Result<Asset, AssetVaultError> {
210        Ok(match asset {
211            Asset::Fungible(asset) => Asset::Fungible(self.add_fungible_asset(asset)?),
212            Asset::NonFungible(asset) => Asset::NonFungible(self.add_non_fungible_asset(asset)?),
213        })
214    }
215
216    /// Add the specified fungible asset to the vault. If the vault already contains an asset
217    /// issued by the same faucet, the amounts are added together.
218    ///
219    /// # Errors
220    /// - If the total value of the added assets is greater than [`FungibleAsset::MAX_AMOUNT`].
221    /// - The maximum number of leaves per asset is exceeded.
222    fn add_fungible_asset(
223        &mut self,
224        other_asset: FungibleAsset,
225    ) -> Result<FungibleAsset, AssetVaultError> {
226        let current_asset_value = self.asset_tree.get_value(&other_asset.vault_key().to_word());
227        let current_asset =
228            FungibleAsset::from_key_value(other_asset.vault_key(), current_asset_value)
229                .expect("asset vault should store valid assets");
230
231        let new_asset = current_asset
232            .add(other_asset)
233            .map_err(AssetVaultError::AddFungibleAssetBalanceError)?;
234
235        self.asset_tree
236            .insert(new_asset.vault_key().to_word(), new_asset.to_value_word())
237            .map_err(AssetVaultError::MaxLeafEntriesExceeded)?;
238
239        Ok(new_asset)
240    }
241
242    /// Add the specified non-fungible asset to the vault.
243    ///
244    /// # Errors
245    /// - If the vault already contains the same non-fungible asset.
246    /// - The maximum number of leaves per asset is exceeded.
247    fn add_non_fungible_asset(
248        &mut self,
249        asset: NonFungibleAsset,
250    ) -> Result<NonFungibleAsset, AssetVaultError> {
251        // add non-fungible asset to the vault
252        let old = self
253            .asset_tree
254            .insert(asset.vault_key().to_word(), asset.to_value_word())
255            .map_err(AssetVaultError::MaxLeafEntriesExceeded)?;
256
257        // if the asset already exists, return an error
258        if old != Smt::EMPTY_VALUE {
259            return Err(AssetVaultError::DuplicateNonFungibleAsset(asset));
260        }
261
262        Ok(asset)
263    }
264
265    // REMOVE ASSET
266    // --------------------------------------------------------------------------------------------
267    /// Remove the specified asset from the vault and returns the asset that was just removed.
268    ///
269    /// # Errors
270    /// - The fungible asset is not found in the vault.
271    /// - The amount of the fungible asset in the vault is less than the amount to be removed.
272    /// - The non-fungible asset is not found in the vault.
273    pub fn remove_asset(&mut self, asset: Asset) -> Result<Asset, AssetVaultError> {
274        match asset {
275            Asset::Fungible(asset) => {
276                let asset = self.remove_fungible_asset(asset)?;
277                Ok(Asset::Fungible(asset))
278            },
279            Asset::NonFungible(asset) => {
280                let asset = self.remove_non_fungible_asset(asset)?;
281                Ok(Asset::NonFungible(asset))
282            },
283        }
284    }
285
286    /// Remove the specified fungible asset from the vault and returns the asset that was just
287    /// removed. If the final amount of the asset is zero, the asset is removed from the vault.
288    ///
289    /// # Errors
290    /// - The asset is not found in the vault.
291    /// - The amount of the asset in the vault is less than the amount to be removed.
292    /// - The maximum number of leaves per asset is exceeded.
293    fn remove_fungible_asset(
294        &mut self,
295        other_asset: FungibleAsset,
296    ) -> Result<FungibleAsset, AssetVaultError> {
297        let current_asset_value = self.asset_tree.get_value(&other_asset.vault_key().to_word());
298        let current_asset =
299            FungibleAsset::from_key_value(other_asset.vault_key(), current_asset_value)
300                .expect("asset vault should store valid assets");
301
302        // If the asset's amount is 0, we consider it absent from the vault.
303        if current_asset.amount() == 0 {
304            return Err(AssetVaultError::FungibleAssetNotFound(other_asset));
305        }
306
307        let new_asset = current_asset
308            .sub(other_asset)
309            .map_err(AssetVaultError::SubtractFungibleAssetBalanceError)?;
310
311        // Note that if new_asset's amount is 0, its value's word representation is equal to
312        // the empty word, which results in the removal of the entire entry from the corresponding
313        // leaf.
314        #[cfg(debug_assertions)]
315        {
316            if new_asset.amount() == 0 {
317                assert!(new_asset.to_value_word().is_empty())
318            }
319        }
320
321        self.asset_tree
322            .insert(new_asset.vault_key().to_word(), new_asset.to_value_word())
323            .map_err(AssetVaultError::MaxLeafEntriesExceeded)?;
324
325        Ok(other_asset)
326    }
327
328    /// Remove the specified non-fungible asset from the vault and returns the asset that was just
329    /// removed.
330    ///
331    /// # Errors
332    /// - The non-fungible asset is not found in the vault.
333    /// - The maximum number of leaves per asset is exceeded.
334    fn remove_non_fungible_asset(
335        &mut self,
336        asset: NonFungibleAsset,
337    ) -> Result<NonFungibleAsset, AssetVaultError> {
338        // remove the asset from the vault.
339        let old = self
340            .asset_tree
341            .insert(asset.vault_key().to_word(), Smt::EMPTY_VALUE)
342            .map_err(AssetVaultError::MaxLeafEntriesExceeded)?;
343
344        // return an error if the asset did not exist in the vault.
345        if old == Smt::EMPTY_VALUE {
346            return Err(AssetVaultError::NonFungibleAssetNotFound(asset));
347        }
348
349        // return the asset that was removed.
350        Ok(asset)
351    }
352}
353
354// SERIALIZATION
355// ================================================================================================
356
357impl Serializable for AssetVault {
358    fn write_into<W: ByteWriter>(&self, target: &mut W) {
359        let num_assets = self.asset_tree.num_entries();
360        target.write_usize(num_assets);
361        target.write_many(self.assets());
362    }
363
364    fn get_size_hint(&self) -> usize {
365        let mut size = 0;
366        let mut count: usize = 0;
367
368        for asset in self.assets() {
369            size += asset.get_size_hint();
370            count += 1;
371        }
372
373        size += count.get_size_hint();
374
375        size
376    }
377}
378
379impl Deserializable for AssetVault {
380    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
381        let num_assets = source.read_usize()?;
382        let assets = source.read_many_iter::<Asset>(num_assets)?.collect::<Result<Vec<_>, _>>()?;
383        Self::new(&assets).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
384    }
385}
386
387// TESTS
388// ================================================================================================
389
390#[cfg(test)]
391mod tests {
392    use assert_matches::assert_matches;
393
394    use super::*;
395
396    #[test]
397    fn vault_fails_on_absent_fungible_asset() {
398        let mut vault = AssetVault::default();
399        let err = vault.remove_asset(FungibleAsset::mock(50)).unwrap_err();
400        assert_matches!(err, AssetVaultError::FungibleAssetNotFound(_));
401    }
402}