use alloc::string::ToString;
use miden_crypto::merkle::InnerNodeInfo;
use miden_processor::SMT_DEPTH;
use super::{
AccountType,
Asset,
ByteReader,
ByteWriter,
Deserializable,
DeserializationError,
FungibleAsset,
NonFungibleAsset,
Serializable,
};
use crate::account::{AccountId, AccountVaultDelta, NonFungibleDeltaAction};
use crate::crypto::merkle::Smt;
use crate::{AssetVaultError, Word};
mod partial;
pub use partial::PartialVault;
mod asset_witness;
pub use asset_witness::AssetWitness;
mod vault_key;
pub use vault_key::AssetVaultKey;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct AssetVault {
asset_tree: Smt,
}
impl AssetVault {
pub const DEPTH: u8 = SMT_DEPTH;
pub fn new(assets: &[Asset]) -> Result<Self, AssetVaultError> {
Ok(Self {
asset_tree: Smt::with_entries(
assets.iter().map(|asset| (asset.vault_key().into(), (*asset).into())),
)
.map_err(AssetVaultError::DuplicateAsset)?,
})
}
pub fn root(&self) -> Word {
self.asset_tree.root()
}
pub fn has_non_fungible_asset(&self, asset: NonFungibleAsset) -> Result<bool, AssetVaultError> {
match self.asset_tree.get_value(&asset.vault_key().into()) {
asset if asset == Smt::EMPTY_VALUE => Ok(false),
_ => Ok(true),
}
}
pub fn get_balance(&self, faucet_id: AccountId) -> Result<u64, AssetVaultError> {
if !matches!(faucet_id.account_type(), AccountType::FungibleFaucet) {
return Err(AssetVaultError::NotAFungibleFaucetId(faucet_id));
}
match self.asset_tree.get_value(
&AssetVaultKey::from_account_id(faucet_id)
.expect("faucet ID should be of type fungible")
.into(),
) {
asset if asset == Smt::EMPTY_VALUE => Ok(0),
asset => Ok(FungibleAsset::new_unchecked(asset).amount()),
}
}
pub fn assets(&self) -> impl Iterator<Item = Asset> + '_ {
self.asset_tree.entries().map(|(_key, value)| Asset::new_unchecked(*value))
}
pub fn inner_nodes(&self) -> impl Iterator<Item = InnerNodeInfo> + '_ {
self.asset_tree.inner_nodes()
}
pub fn open(&self, vault_key: AssetVaultKey) -> AssetWitness {
let smt_proof = self.asset_tree.open(&vault_key.into());
AssetWitness::new_unchecked(smt_proof)
}
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_delta(&mut self, delta: &AccountVaultDelta) -> Result<(), AssetVaultError> {
for (&faucet_id, &delta) in delta.fungible().iter() {
let asset = FungibleAsset::new(faucet_id, delta.unsigned_abs())
.expect("Not a fungible faucet ID or delta is too large");
match delta >= 0 {
true => self.add_fungible_asset(asset),
false => self.remove_fungible_asset(asset),
}?;
}
for (&asset, &action) in delta.non_fungible().iter() {
match action {
NonFungibleDeltaAction::Add => self.add_non_fungible_asset(asset),
NonFungibleDeltaAction::Remove => self.remove_non_fungible_asset(asset),
}?;
}
Ok(())
}
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,
asset: FungibleAsset,
) -> Result<FungibleAsset, AssetVaultError> {
let new: FungibleAsset = match self.asset_tree.get_value(&asset.vault_key().into()) {
current if current == Smt::EMPTY_VALUE => asset,
current => {
let current = FungibleAsset::new_unchecked(current);
current.add(asset).map_err(AssetVaultError::AddFungibleAssetBalanceError)?
},
};
self.asset_tree
.insert(new.vault_key().into(), new.into())
.map_err(AssetVaultError::MaxLeafEntriesExceeded)?;
Ok(new)
}
fn add_non_fungible_asset(
&mut self,
asset: NonFungibleAsset,
) -> Result<NonFungibleAsset, AssetVaultError> {
let old = self
.asset_tree
.insert(asset.vault_key().into(), asset.into())
.map_err(AssetVaultError::MaxLeafEntriesExceeded)?;
if old != Smt::EMPTY_VALUE {
return Err(AssetVaultError::DuplicateNonFungibleAsset(asset));
}
Ok(asset)
}
pub fn remove_asset(&mut self, asset: Asset) -> Result<Asset, AssetVaultError> {
match asset {
Asset::Fungible(asset) => {
let asset = self.remove_fungible_asset(asset)?;
Ok(Asset::Fungible(asset))
},
Asset::NonFungible(asset) => {
let asset = self.remove_non_fungible_asset(asset)?;
Ok(Asset::NonFungible(asset))
},
}
}
fn remove_fungible_asset(
&mut self,
asset: FungibleAsset,
) -> Result<FungibleAsset, AssetVaultError> {
let new: FungibleAsset = match self.asset_tree.get_value(&asset.vault_key().into()) {
current if current == Smt::EMPTY_VALUE => {
return Err(AssetVaultError::FungibleAssetNotFound(asset));
},
current => {
let current = FungibleAsset::new_unchecked(current);
current.sub(asset).map_err(AssetVaultError::SubtractFungibleAssetBalanceError)?
},
};
let value = match new.amount() {
0 => Smt::EMPTY_VALUE,
_ => new.into(),
};
self.asset_tree
.insert(new.vault_key().into(), value)
.map_err(AssetVaultError::MaxLeafEntriesExceeded)?;
Ok(asset)
}
fn remove_non_fungible_asset(
&mut self,
asset: NonFungibleAsset,
) -> Result<NonFungibleAsset, AssetVaultError> {
let old = self
.asset_tree
.insert(asset.vault_key().into(), Smt::EMPTY_VALUE)
.map_err(AssetVaultError::MaxLeafEntriesExceeded)?;
if old == Smt::EMPTY_VALUE {
return Err(AssetVaultError::NonFungibleAssetNotFound(asset));
}
Ok(asset)
}
}
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::<Asset>(num_assets)?;
Self::new(&assets).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
}
}