miden_objects/asset/
vault.rs

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