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