1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
use alloc::string::ToString;
use core::fmt;
use super::{
is_not_a_non_fungible_asset, parse_word, AccountId, AccountType, Asset, AssetError, Felt, Word,
ZERO,
};
// FUNGIBLE ASSET
// ================================================================================================
/// A fungible asset.
///
/// A fungible asset consists of a faucet ID of the faucet which issued the asset as well as the
/// asset amount. Asset amount is guaranteed to be 2^63 - 1 or smaller.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct FungibleAsset {
faucet_id: AccountId,
amount: u64,
}
impl FungibleAsset {
// CONSTANTS
// --------------------------------------------------------------------------------------------
/// Specifies a maximum amount value for fungible assets which can be at most a 63-bit value.
pub const MAX_AMOUNT: u64 = (1_u64 << 63) - 1;
// CONSTRUCTOR
// --------------------------------------------------------------------------------------------
/// Returns a fungible asset instantiated with the provided faucet ID and amount.
///
/// # Errors
/// Returns an error if:
/// - The faucet_id is not a valid fungible faucet ID.
/// - The provided amount is greater than 2^63 - 1.
pub const fn new(faucet_id: AccountId, amount: u64) -> Result<Self, AssetError> {
let asset = Self { faucet_id, amount };
asset.validate()
}
/// Creates a new [FungibleAsset] without checking its validity.
pub(crate) fn new_unchecked(value: Word) -> FungibleAsset {
FungibleAsset {
faucet_id: AccountId::new_unchecked(value[3]),
amount: value[0].as_int(),
}
}
// PUBLIC ACCESSORS
// --------------------------------------------------------------------------------------------
/// Return ID of the faucet which issued this asset.
pub fn faucet_id(&self) -> AccountId {
self.faucet_id
}
/// Returns the amount of this asset.
pub fn amount(&self) -> u64 {
self.amount
}
/// Returns true if this and the other assets were issued from the same faucet.
pub fn is_from_same_faucet(&self, other: &Self) -> bool {
self.faucet_id == other.faucet_id
}
/// Returns the key which is used to store this asset in the account vault.
pub fn vault_key(&self) -> Word {
let mut key = Word::default();
key[3] = self.faucet_id.into();
key
}
// OPERATIONS
// --------------------------------------------------------------------------------------------
/// Adds two fungible assets together and returns the result.
///
/// # Errors
/// Returns an error if:
/// - The assets were not issued by the same faucet.
/// - The total value of assets is greater than or equal to 2^63.
#[allow(clippy::should_implement_trait)]
pub fn add(self, other: Self) -> Result<Self, AssetError> {
if self.faucet_id != other.faucet_id {
return Err(AssetError::InconsistentFaucetIds(self.faucet_id, other.faucet_id));
}
let amount = self.amount.checked_add(other.amount).expect("overflow!");
if amount > Self::MAX_AMOUNT {
return Err(AssetError::AmountTooBig(amount));
}
Ok(Self { faucet_id: self.faucet_id, amount })
}
/// Subtracts the specified amount from this asset and returns the resulting asset.
///
/// # Errors
/// Returns an error if this asset's amount is smaller than the requested amount.
pub fn sub(&mut self, amount: u64) -> Result<Self, AssetError> {
self.amount = self
.amount
.checked_sub(amount)
.ok_or(AssetError::AssetAmountNotSufficient(self.amount, amount))?;
Ok(FungibleAsset { faucet_id: self.faucet_id, amount })
}
// HELPER FUNCTIONS
// --------------------------------------------------------------------------------------------
/// Validates this fungible asset.
/// # Errors
/// Returns an error if:
/// - The faucet_id is not a valid fungible faucet ID.
/// - The provided amount is greater than 2^63 - 1.
const fn validate(self) -> Result<Self, AssetError> {
let account_type = self.faucet_id.account_type();
if !matches!(account_type, AccountType::FungibleFaucet) {
return Err(AssetError::NotAFungibleFaucetId(self.faucet_id, account_type));
}
if self.amount > Self::MAX_AMOUNT {
return Err(AssetError::AmountTooBig(self.amount));
}
Ok(self)
}
}
impl From<FungibleAsset> for Word {
fn from(asset: FungibleAsset) -> Self {
let mut result = Word::default();
result[0] = Felt::new(asset.amount);
result[3] = asset.faucet_id.into();
debug_assert!(is_not_a_non_fungible_asset(result));
result
}
}
impl From<FungibleAsset> for [u8; 32] {
fn from(asset: FungibleAsset) -> Self {
let mut result = [0_u8; 32];
let id_bytes: [u8; 8] = asset.faucet_id.into();
result[..8].copy_from_slice(&asset.amount.to_le_bytes());
result[24..].copy_from_slice(&id_bytes);
result
}
}
impl From<FungibleAsset> for Asset {
fn from(asset: FungibleAsset) -> Self {
Asset::Fungible(asset)
}
}
impl TryFrom<Word> for FungibleAsset {
type Error = AssetError;
fn try_from(value: Word) -> Result<Self, Self::Error> {
if (value[1], value[2]) != (ZERO, ZERO) {
return Err(AssetError::FungibleAssetInvalidWord(value));
}
let faucet_id = AccountId::try_from(value[3])
.map_err(|e| AssetError::InvalidAccountId(e.to_string()))?;
let amount = value[0].as_int();
Self::new(faucet_id, amount)
}
}
impl TryFrom<[u8; 32]> for FungibleAsset {
type Error = AssetError;
fn try_from(value: [u8; 32]) -> Result<Self, Self::Error> {
let word = parse_word(value)?;
Self::try_from(word)
}
}
impl fmt::Display for FungibleAsset {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}