use alloc::collections::BTreeMap;
use alloc::string::ToString;
use alloc::vec::Vec;
use miden_crypto::merkle::InnerNodeInfo;
use super::{
Asset,
AssetAmount,
AssetComposition,
ByteReader,
ByteWriter,
Deserializable,
DeserializationError,
FungibleAsset,
NonFungibleAsset,
Serializable,
};
use crate::Word;
use crate::account::AccountVaultPatch;
use crate::crypto::merkle::smt::{SMT_DEPTH, Smt};
use crate::errors::{AssetError, AssetVaultError};
mod partial;
pub use partial::PartialVault;
mod asset_witness;
pub use asset_witness::AssetWitness;
mod asset_id;
pub use asset_id::{AssetId, AssetIdHash};
mod asset_class;
pub use asset_class::AssetClass;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct AssetVault {
asset_tree: Smt,
entries: BTreeMap<AssetId, Word>,
}
impl AssetVault {
pub const DEPTH: u8 = SMT_DEPTH;
pub fn new(assets: &[Asset]) -> Result<Self, AssetVaultError> {
let asset_tree = Smt::with_entries(
assets.iter().map(|asset| (asset.id().hash().as_word(), asset.to_value_word())),
)
.map_err(AssetVaultError::DuplicateAsset)?;
let entries = assets
.iter()
.filter(|asset| !asset.to_value_word().is_empty())
.map(|asset| (asset.id(), asset.to_value_word()))
.collect();
Ok(Self { asset_tree, entries })
}
pub fn root(&self) -> Word {
self.asset_tree.root()
}
pub fn get(&self, asset_id: AssetId) -> Option<Asset> {
let asset_value = self.entries.get(&asset_id).copied().unwrap_or_default();
if asset_value.is_empty() {
None
} else {
Some(
Asset::from_id_and_value(asset_id, asset_value)
.expect("asset vault should only store valid assets"),
)
}
}
pub fn has_non_fungible_asset(&self, asset: NonFungibleAsset) -> Result<bool, AssetVaultError> {
Ok(self.entries.contains_key(&asset.id()))
}
pub fn get_balance(&self, asset_id: AssetId) -> Result<AssetAmount, AssetError> {
if !asset_id.composition().is_fungible() {
return Err(AssetError::AssetCompositionMismatch {
faucet_id: asset_id.faucet_id(),
expected: AssetComposition::Fungible,
actual: asset_id.composition(),
});
}
let asset_value = self.entries.get(&asset_id).copied().unwrap_or_default();
let asset = FungibleAsset::from_id_and_value(asset_id, asset_value)
.expect("asset vault should only store valid assets");
Ok(asset.amount())
}
pub fn assets(&self) -> impl Iterator<Item = Asset> + '_ {
self.entries.iter().map(|(id, value)| {
Asset::from_id_and_value(*id, *value)
.expect("asset vault should only store valid assets")
})
}
pub fn inner_nodes(&self) -> impl Iterator<Item = InnerNodeInfo> + '_ {
self.asset_tree.inner_nodes()
}
pub fn open(&self, asset_id: AssetId) -> AssetWitness {
let smt_proof = self.asset_tree.open(&asset_id.hash().as_word());
let value = self.entries.get(&asset_id).copied().unwrap_or_default();
AssetWitness::new_unchecked(smt_proof, [(asset_id, value)])
}
pub fn is_empty(&self) -> bool {
self.asset_tree.is_empty()
}
pub fn num_leaves(&self) -> usize {
self.asset_tree.num_leaves()
}
pub fn num_assets(&self) -> usize {
self.asset_tree.num_entries()
}
pub fn apply_patch(&mut self, patch: &AccountVaultPatch) -> Result<(), AssetVaultError> {
for (&asset_id, &value) in patch.iter() {
self.insert_entry(asset_id, value)?;
}
Ok(())
}
pub fn insert_asset(&mut self, asset: Asset) -> Result<Word, AssetVaultError> {
self.insert_entry(asset.id(), asset.to_value_word())
}
pub fn add_asset(&mut self, asset: Asset) -> Result<Asset, AssetVaultError> {
Ok(match asset {
Asset::Fungible(asset) => Asset::Fungible(self.add_fungible_asset(asset)?),
Asset::NonFungible(asset) => Asset::NonFungible(self.add_non_fungible_asset(asset)?),
})
}
fn add_fungible_asset(
&mut self,
other_asset: FungibleAsset,
) -> Result<FungibleAsset, AssetVaultError> {
let asset_id = other_asset.id();
let current_asset_value = self.entries.get(&asset_id).copied().unwrap_or_default();
let current_asset = FungibleAsset::from_id_and_value(asset_id, current_asset_value)
.expect("asset vault should store valid assets");
let new_asset = current_asset
.add(other_asset)
.map_err(AssetVaultError::AddFungibleAssetBalanceError)?;
self.insert_entry(new_asset.id(), new_asset.to_value_word())?;
Ok(new_asset)
}
fn add_non_fungible_asset(
&mut self,
asset: NonFungibleAsset,
) -> Result<NonFungibleAsset, AssetVaultError> {
let old = self.insert_entry(asset.id(), asset.to_value_word())?;
if old != Smt::EMPTY_VALUE {
return Err(AssetVaultError::DuplicateNonFungibleAsset(asset));
}
Ok(asset)
}
pub fn remove_asset(&mut self, asset: Asset) -> Result<Option<Asset>, AssetVaultError> {
match asset {
Asset::Fungible(asset) => {
let remaining = self.remove_fungible_asset(asset)?;
Ok(Some(Asset::Fungible(remaining)))
},
Asset::NonFungible(asset) => {
self.remove_non_fungible_asset(asset)?;
Ok(None)
},
}
}
fn remove_fungible_asset(
&mut self,
other_asset: FungibleAsset,
) -> Result<FungibleAsset, AssetVaultError> {
let asset_id = other_asset.id();
let current_asset_value = self.entries.get(&asset_id).copied().unwrap_or_default();
let current_asset = FungibleAsset::from_id_and_value(asset_id, current_asset_value)
.expect("asset vault should store valid assets");
if current_asset.amount() == AssetAmount::ZERO {
return Err(AssetVaultError::FungibleAssetNotFound(other_asset));
}
let new_asset = current_asset
.sub(other_asset)
.map_err(AssetVaultError::SubtractFungibleAssetBalanceError)?;
#[cfg(debug_assertions)]
{
if new_asset.amount() == AssetAmount::ZERO {
assert!(new_asset.to_value_word().is_empty())
}
}
self.insert_entry(new_asset.id(), new_asset.to_value_word())?;
Ok(new_asset)
}
fn remove_non_fungible_asset(
&mut self,
asset: NonFungibleAsset,
) -> Result<(), AssetVaultError> {
let old = self.insert_entry(asset.id(), Smt::EMPTY_VALUE)?;
if old == Smt::EMPTY_VALUE {
return Err(AssetVaultError::NonFungibleAssetNotFound(asset));
}
Ok(())
}
fn insert_entry(&mut self, asset_id: AssetId, value: Word) -> Result<Word, AssetVaultError> {
let old_value = self
.asset_tree
.insert(asset_id.hash().into(), value)
.map_err(AssetVaultError::MaxLeafEntriesExceeded)?;
if value == Smt::EMPTY_VALUE {
self.entries.remove(&asset_id);
} else {
self.entries.insert(asset_id, value);
}
Ok(old_value)
}
}
impl Serializable for AssetVault {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
let num_assets = self.asset_tree.num_entries();
target.write_usize(num_assets);
target.write_many(self.assets());
}
fn get_size_hint(&self) -> usize {
let mut size = 0;
let mut count: usize = 0;
for asset in self.assets() {
size += asset.get_size_hint();
count += 1;
}
size += count.get_size_hint();
size
}
}
impl Deserializable for AssetVault {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let num_assets = source.read_usize()?;
let assets = source.read_many_iter::<Asset>(num_assets)?.collect::<Result<Vec<_>, _>>()?;
Self::new(&assets).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
}
}
#[cfg(test)]
mod tests {
use assert_matches::assert_matches;
use super::*;
#[test]
fn vault_fails_on_absent_fungible_asset() {
let mut vault = AssetVault::default();
let err = vault.remove_asset(FungibleAsset::mock(50)).unwrap_err();
assert_matches!(err, AssetVaultError::FungibleAssetNotFound(_));
}
#[test]
fn two_non_fungible_assets_from_same_faucet_use_different_leaves() -> anyhow::Result<()> {
let asset0 = NonFungibleAsset::mock(&[1, 2, 3]);
let asset1 = NonFungibleAsset::mock(&[4, 5, 6]);
assert_eq!(asset0.id().faucet_id(), asset1.id().faucet_id());
assert_ne!(asset0.id(), asset1.id());
assert_eq!(asset0.id().to_word()[2], asset1.id().to_word()[2]);
assert_eq!(asset0.id().to_word()[3], asset1.id().to_word()[3]);
assert_ne!(asset0.id().hash().to_leaf_index(), asset1.id().hash().to_leaf_index());
let vault = AssetVault::new(&[asset0, asset1])?;
assert_eq!(vault.num_leaves(), 2);
assert_eq!(vault.num_assets(), 2);
Ok(())
}
}