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