miden_objects/asset/vault/
mod.rs

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