miden_objects/asset/
vault.rs

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