Skip to main content

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