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}