use alloc::string::ToString;
use alloc::vec::Vec;
use miden_crypto::merkle::InnerNodeInfo;
use super::{
AccountType,
Asset,
ByteReader,
ByteWriter,
Deserializable,
DeserializationError,
FungibleAsset,
NonFungibleAsset,
Serializable,
};
use crate::Word;
use crate::account::{AccountId, AccountVaultDelta, NonFungibleDeltaAction};
use crate::crypto::merkle::smt::{SMT_DEPTH, Smt};
use crate::errors::AssetVaultError;
mod partial;
pub use partial::PartialVault;
mod asset_witness;
pub use asset_witness::AssetWitness;
mod vault_key;
pub use vault_key::AssetVaultKey;
mod asset_id;
pub use asset_id::AssetId;
#[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().to_word(), asset.to_value_word())),
)
.map_err(AssetVaultError::DuplicateAsset)?,
})
}
pub fn root(&self) -> Word {
self.asset_tree.root()
}
pub fn get(&self, asset_vault_key: AssetVaultKey) -> Option<Asset> {
let asset_value = self.asset_tree.get_value(&asset_vault_key.to_word());
if asset_value.is_empty() {
None
} else {
Some(
Asset::from_key_value(asset_vault_key, asset_value)
.expect("asset vault should only store valid assets"),
)
}
}
pub fn has_non_fungible_asset(&self, asset: NonFungibleAsset) -> Result<bool, AssetVaultError> {
match self.asset_tree.get_value(&asset.vault_key().to_word()) {
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));
}
let vault_key =
AssetVaultKey::new_fungible(faucet_id).expect("faucet ID should be of type fungible");
let asset_value = self.asset_tree.get_value(&vault_key.to_word());
let asset = FungibleAsset::from_key_value(vault_key, asset_value)
.expect("asset vault should only store valid assets");
Ok(asset.amount())
}
pub fn assets(&self) -> impl Iterator<Item = Asset> + '_ {
self.asset_tree.entries().map(|(key, value)| {
Asset::from_key_value_words(*key, *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, vault_key: AssetVaultKey) -> AssetWitness {
let smt_proof = self.asset_tree.open(&vault_key.to_word());
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 (vault_key, &delta) in delta.fungible().iter() {
let asset = FungibleAsset::new(vault_key.faucet_id(), delta.unsigned_abs())
.expect("fungible asset delta should be valid")
.with_callbacks(vault_key.callback_flag());
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,
other_asset: FungibleAsset,
) -> Result<FungibleAsset, AssetVaultError> {
let current_asset_value = self.asset_tree.get_value(&other_asset.vault_key().to_word());
let current_asset =
FungibleAsset::from_key_value(other_asset.vault_key(), current_asset_value)
.expect("asset vault should store valid assets");
let new_asset = current_asset
.add(other_asset)
.map_err(AssetVaultError::AddFungibleAssetBalanceError)?;
self.asset_tree
.insert(new_asset.vault_key().to_word(), new_asset.to_value_word())
.map_err(AssetVaultError::MaxLeafEntriesExceeded)?;
Ok(new_asset)
}
fn add_non_fungible_asset(
&mut self,
asset: NonFungibleAsset,
) -> Result<NonFungibleAsset, AssetVaultError> {
let old = self
.asset_tree
.insert(asset.vault_key().to_word(), asset.to_value_word())
.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<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 current_asset_value = self.asset_tree.get_value(&other_asset.vault_key().to_word());
let current_asset =
FungibleAsset::from_key_value(other_asset.vault_key(), current_asset_value)
.expect("asset vault should store valid assets");
if current_asset.amount() == 0 {
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() == 0 {
assert!(new_asset.to_value_word().is_empty())
}
}
self.asset_tree
.insert(new_asset.vault_key().to_word(), new_asset.to_value_word())
.map_err(AssetVaultError::MaxLeafEntriesExceeded)?;
Ok(new_asset)
}
fn remove_non_fungible_asset(
&mut self,
asset: NonFungibleAsset,
) -> Result<(), AssetVaultError> {
let old = self
.asset_tree
.insert(asset.vault_key().to_word(), Smt::EMPTY_VALUE)
.map_err(AssetVaultError::MaxLeafEntriesExceeded)?;
if old == Smt::EMPTY_VALUE {
return Err(AssetVaultError::NonFungibleAssetNotFound(asset));
}
Ok(())
}
}
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(_));
}
}