use core::marker::PhantomData;
use crate::{
ensure,
traits::{
tokens::{
misc::{
Balance, DepositConsequence,
Fortitude::{self, Force, Polite},
Precision::{self, BestEffort, Exact},
Preservation::{self, Expendable},
Provenance::{self, Extant},
WithdrawConsequence,
},
AssetId,
},
SameOrOther, TryDrop,
},
};
use subsoil::arithmetic::traits::{CheckedAdd, CheckedSub, One};
use subsoil::runtime::{traits::Saturating, ArithmeticError, DispatchError, TokenError};
use super::{Credit, Debt, HandleImbalanceDrop, Imbalance};
pub trait Inspect<AccountId>: Sized {
type AssetId: AssetId;
type Balance: Balance;
fn total_issuance(asset: Self::AssetId) -> Self::Balance;
fn active_issuance(asset: Self::AssetId) -> Self::Balance {
Self::total_issuance(asset)
}
fn minimum_balance(asset: Self::AssetId) -> Self::Balance;
fn total_balance(asset: Self::AssetId, who: &AccountId) -> Self::Balance;
fn balance(asset: Self::AssetId, who: &AccountId) -> Self::Balance;
fn reducible_balance(
asset: Self::AssetId,
who: &AccountId,
preservation: Preservation,
force: Fortitude,
) -> Self::Balance;
fn can_deposit(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
provenance: Provenance,
) -> DepositConsequence;
fn can_withdraw(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
) -> WithdrawConsequence<Self::Balance>;
fn asset_exists(asset: Self::AssetId) -> bool;
}
#[must_use]
pub struct Dust<A, T: Unbalanced<A>>(pub T::AssetId, pub T::Balance);
impl<A, T: Balanced<A>> Dust<A, T> {
pub fn into_credit(self) -> Credit<A, T> {
Credit::<A, T>::new(self.0, self.1)
}
}
pub trait Unbalanced<AccountId>: Inspect<AccountId> {
fn handle_raw_dust(asset: Self::AssetId, amount: Self::Balance) {
Self::handle_dust(Dust(
asset.clone(),
amount.min(Self::minimum_balance(asset).saturating_sub(One::one())),
))
}
fn handle_dust(dust: Dust<AccountId, Self>);
fn write_balance(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
) -> Result<Option<Self::Balance>, DispatchError>;
fn set_total_issuance(asset: Self::AssetId, amount: Self::Balance);
fn decrease_balance(
asset: Self::AssetId,
who: &AccountId,
mut amount: Self::Balance,
precision: Precision,
preservation: Preservation,
force: Fortitude,
) -> Result<Self::Balance, DispatchError> {
let old_balance = Self::balance(asset.clone(), who);
let reducible = Self::reducible_balance(asset.clone(), who, preservation, force);
match precision {
BestEffort => amount = amount.min(reducible),
Exact => ensure!(reducible >= amount, TokenError::FundsUnavailable),
}
let new_balance = old_balance.checked_sub(&amount).ok_or(TokenError::FundsUnavailable)?;
if let Some(dust) = Self::write_balance(asset.clone(), who, new_balance)? {
Self::handle_dust(Dust(asset, dust));
}
Ok(old_balance.saturating_sub(new_balance))
}
fn increase_balance(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
precision: Precision,
) -> Result<Self::Balance, DispatchError> {
let old_balance = Self::balance(asset.clone(), who);
let new_balance = if let BestEffort = precision {
old_balance.saturating_add(amount)
} else {
old_balance.checked_add(&amount).ok_or(ArithmeticError::Overflow)?
};
if new_balance < Self::minimum_balance(asset.clone()) {
if let BestEffort = precision {
Ok(Self::Balance::default())
} else {
Err(TokenError::BelowMinimum.into())
}
} else {
if new_balance == old_balance {
Ok(Self::Balance::default())
} else {
if let Some(dust) = Self::write_balance(asset.clone(), who, new_balance)? {
Self::handle_dust(Dust(asset, dust));
}
Ok(new_balance.saturating_sub(old_balance))
}
}
}
fn deactivate(_asset: Self::AssetId, _: Self::Balance) {}
fn reactivate(_asset: Self::AssetId, _: Self::Balance) {}
}
pub trait Mutate<AccountId>: Inspect<AccountId> + Unbalanced<AccountId>
where
AccountId: Eq,
{
fn mint_into(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError> {
Self::total_issuance(asset.clone())
.checked_add(&amount)
.ok_or(ArithmeticError::Overflow)?;
let actual = Self::increase_balance(asset.clone(), who, amount, Exact)?;
Self::set_total_issuance(
asset.clone(),
Self::total_issuance(asset.clone()).saturating_add(actual),
);
Self::done_mint_into(asset, who, amount);
Ok(actual)
}
fn burn_from(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
preservation: Preservation,
precision: Precision,
force: Fortitude,
) -> Result<Self::Balance, DispatchError> {
let actual = Self::reducible_balance(asset.clone(), who, preservation, force).min(amount);
ensure!(actual == amount || precision == BestEffort, TokenError::FundsUnavailable);
Self::total_issuance(asset.clone())
.checked_sub(&actual)
.ok_or(ArithmeticError::Overflow)?;
let actual =
Self::decrease_balance(asset.clone(), who, actual, BestEffort, preservation, force)?;
Self::set_total_issuance(
asset.clone(),
Self::total_issuance(asset.clone()).saturating_sub(actual),
);
Self::done_burn_from(asset, who, actual);
Ok(actual)
}
fn shelve(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError> {
let actual = Self::reducible_balance(asset.clone(), who, Expendable, Polite).min(amount);
ensure!(actual == amount, TokenError::FundsUnavailable);
Self::total_issuance(asset.clone())
.checked_sub(&actual)
.ok_or(ArithmeticError::Overflow)?;
let actual =
Self::decrease_balance(asset.clone(), who, actual, BestEffort, Expendable, Polite)?;
Self::set_total_issuance(
asset.clone(),
Self::total_issuance(asset.clone()).saturating_sub(actual),
);
Self::done_shelve(asset, who, actual);
Ok(actual)
}
fn restore(
asset: Self::AssetId,
who: &AccountId,
amount: Self::Balance,
) -> Result<Self::Balance, DispatchError> {
Self::total_issuance(asset.clone())
.checked_add(&amount)
.ok_or(ArithmeticError::Overflow)?;
let actual = Self::increase_balance(asset.clone(), who, amount, Exact)?;
Self::set_total_issuance(
asset.clone(),
Self::total_issuance(asset.clone()).saturating_add(actual),
);
Self::done_restore(asset, who, amount);
Ok(actual)
}
fn transfer(
asset: Self::AssetId,
source: &AccountId,
dest: &AccountId,
amount: Self::Balance,
preservation: Preservation,
) -> Result<Self::Balance, DispatchError> {
let _extra = Self::can_withdraw(asset.clone(), source, amount)
.into_result(preservation != Expendable)?;
Self::can_deposit(asset.clone(), dest, amount, Extant).into_result()?;
if source == dest {
return Ok(amount);
}
Self::decrease_balance(asset.clone(), source, amount, BestEffort, preservation, Polite)?;
let _ = Self::increase_balance(asset.clone(), dest, amount, BestEffort);
Self::done_transfer(asset, source, dest, amount);
Ok(amount)
}
fn set_balance(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) -> Self::Balance {
let b = Self::balance(asset.clone(), who);
if b > amount {
Self::burn_from(asset, who, b - amount, Expendable, BestEffort, Force)
.map(|d| b.saturating_sub(d))
} else {
Self::mint_into(asset, who, amount - b).map(|d| b.saturating_add(d))
}
.unwrap_or(b)
}
fn done_mint_into(_asset: Self::AssetId, _who: &AccountId, _amount: Self::Balance) {}
fn done_burn_from(_asset: Self::AssetId, _who: &AccountId, _amount: Self::Balance) {}
fn done_shelve(_asset: Self::AssetId, _who: &AccountId, _amount: Self::Balance) {}
fn done_restore(_asset: Self::AssetId, _who: &AccountId, _amount: Self::Balance) {}
fn done_transfer(
_asset: Self::AssetId,
_source: &AccountId,
_dest: &AccountId,
_amount: Self::Balance,
) {
}
}
pub struct IncreaseIssuance<AccountId, U>(PhantomData<(AccountId, U)>);
impl<AccountId, U: Unbalanced<AccountId>> HandleImbalanceDrop<U::AssetId, U::Balance>
for IncreaseIssuance<AccountId, U>
{
fn handle(asset: U::AssetId, amount: U::Balance) {
U::set_total_issuance(asset.clone(), U::total_issuance(asset).saturating_add(amount))
}
}
pub struct DecreaseIssuance<AccountId, U>(PhantomData<(AccountId, U)>);
impl<AccountId, U: Unbalanced<AccountId>> HandleImbalanceDrop<U::AssetId, U::Balance>
for DecreaseIssuance<AccountId, U>
{
fn handle(asset: U::AssetId, amount: U::Balance) {
U::set_total_issuance(asset.clone(), U::total_issuance(asset).saturating_sub(amount))
}
}
pub trait Balanced<AccountId>: Inspect<AccountId> + Unbalanced<AccountId> {
type OnDropDebt: HandleImbalanceDrop<Self::AssetId, Self::Balance>;
type OnDropCredit: HandleImbalanceDrop<Self::AssetId, Self::Balance>;
fn rescind(asset: Self::AssetId, amount: Self::Balance) -> Debt<AccountId, Self> {
let old = Self::total_issuance(asset.clone());
let new = old.saturating_sub(amount);
Self::set_total_issuance(asset.clone(), new);
let delta = old - new;
Self::done_rescind(asset.clone(), delta);
Imbalance::<Self::AssetId, Self::Balance, Self::OnDropDebt, Self::OnDropCredit>::new(
asset, delta,
)
}
fn issue(asset: Self::AssetId, amount: Self::Balance) -> Credit<AccountId, Self> {
let old = Self::total_issuance(asset.clone());
let new = old.saturating_add(amount);
Self::set_total_issuance(asset.clone(), new);
let delta = new - old;
Self::done_issue(asset.clone(), delta);
Imbalance::<Self::AssetId, Self::Balance, Self::OnDropCredit, Self::OnDropDebt>::new(
asset, delta,
)
}
fn pair(
asset: Self::AssetId,
amount: Self::Balance,
) -> Result<(Debt<AccountId, Self>, Credit<AccountId, Self>), DispatchError> {
let issued = Self::issue(asset.clone(), amount);
let rescinded = Self::rescind(asset, amount);
if issued.peek() != rescinded.peek() || issued.peek() != amount {
Err("Failed to issue and rescind equal amounts".into())
} else {
Ok((rescinded, issued))
}
}
fn deposit(
asset: Self::AssetId,
who: &AccountId,
value: Self::Balance,
precision: Precision,
) -> Result<Debt<AccountId, Self>, DispatchError> {
let increase = Self::increase_balance(asset.clone(), who, value, precision)?;
Self::done_deposit(asset.clone(), who, increase);
Ok(Imbalance::<Self::AssetId, Self::Balance, Self::OnDropDebt, Self::OnDropCredit>::new(
asset, increase,
))
}
fn withdraw(
asset: Self::AssetId,
who: &AccountId,
value: Self::Balance,
precision: Precision,
preservation: Preservation,
force: Fortitude,
) -> Result<Credit<AccountId, Self>, DispatchError> {
let decrease =
Self::decrease_balance(asset.clone(), who, value, precision, preservation, force)?;
Self::done_withdraw(asset.clone(), who, decrease);
Ok(Imbalance::<Self::AssetId, Self::Balance, Self::OnDropCredit, Self::OnDropDebt>::new(
asset, decrease,
))
}
fn resolve(
who: &AccountId,
credit: Credit<AccountId, Self>,
) -> Result<(), Credit<AccountId, Self>> {
let v = credit.peek();
let debt = match Self::deposit(credit.asset(), who, v, Exact) {
Err(_) => return Err(credit),
Ok(d) => d,
};
if let Ok(result) = credit.offset(debt) {
let result = result.try_drop();
debug_assert!(result.is_ok(), "ok deposit return must be equal to credit value; qed");
} else {
debug_assert!(false, "debt.asset is credit.asset; qed");
}
Ok(())
}
fn settle(
who: &AccountId,
debt: Debt<AccountId, Self>,
preservation: Preservation,
) -> Result<Credit<AccountId, Self>, Debt<AccountId, Self>> {
let amount = debt.peek();
let asset = debt.asset();
let credit = match Self::withdraw(asset.clone(), who, amount, Exact, preservation, Polite) {
Err(_) => return Err(debt),
Ok(d) => d,
};
match credit.offset(debt) {
Ok(SameOrOther::None) => Ok(Credit::<AccountId, Self>::zero(asset)),
Ok(SameOrOther::Same(dust)) => Ok(dust),
Ok(SameOrOther::Other(rest)) => {
debug_assert!(false, "ok withdraw return must be at least debt value; qed");
Err(rest)
},
Err(_) => {
debug_assert!(false, "debt.asset is credit.asset; qed");
Ok(Credit::<AccountId, Self>::zero(asset))
},
}
}
fn done_rescind(_asset: Self::AssetId, _amount: Self::Balance) {}
fn done_issue(_asset: Self::AssetId, _amount: Self::Balance) {}
fn done_deposit(_asset: Self::AssetId, _who: &AccountId, _amount: Self::Balance) {}
fn done_withdraw(_asset: Self::AssetId, _who: &AccountId, _amount: Self::Balance) {}
}
#[cfg(feature = "std")]
impl<AccountId> Inspect<AccountId> for () {
type AssetId = u32;
type Balance = u32;
fn total_issuance(_: Self::AssetId) -> Self::Balance {
0
}
fn minimum_balance(_: Self::AssetId) -> Self::Balance {
0
}
fn total_balance(_: Self::AssetId, _: &AccountId) -> Self::Balance {
0
}
fn balance(_: Self::AssetId, _: &AccountId) -> Self::Balance {
0
}
fn reducible_balance(
_: Self::AssetId,
_: &AccountId,
_: Preservation,
_: Fortitude,
) -> Self::Balance {
0
}
fn can_deposit(
_: Self::AssetId,
_: &AccountId,
_: Self::Balance,
_: Provenance,
) -> DepositConsequence {
DepositConsequence::Success
}
fn can_withdraw(
_: Self::AssetId,
_: &AccountId,
_: Self::Balance,
) -> WithdrawConsequence<Self::Balance> {
WithdrawConsequence::Success
}
fn asset_exists(_: Self::AssetId) -> bool {
false
}
}
#[cfg(feature = "std")]
impl<AccountId> Unbalanced<AccountId> for () {
fn handle_dust(_: Dust<AccountId, Self>) {}
fn write_balance(
_: Self::AssetId,
_: &AccountId,
_: Self::Balance,
) -> Result<Option<Self::Balance>, DispatchError> {
Ok(None)
}
fn set_total_issuance(_: Self::AssetId, _: Self::Balance) {}
}
#[cfg(feature = "std")]
impl<AccountId: Eq> Mutate<AccountId> for () {}