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}