miden_objects/asset/vault/
mod.rs

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