#![cfg_attr(not(feature = "std"), no_std)]
pub mod balance_adapter;
mod benchmarking;
mod benchmarks;
pub mod imbalances;
mod mock;
mod tests;
use codec::{Codec, Decode, Encode, FullCodec};
use eq_oracle::PriceGetter;
pub use eq_primitives::currency as currency;
pub use eq_primitives::signed_balance::{SignedBalance, SignedBalance::*};
use eq_primitives::{currency::Currency, Aggregates, UserGroup, TransferReason};
use eq_utils::{eq_ensure, log::eq_log, ok_or_error};
use frame_support::{
debug, decl_error, decl_event, decl_module, decl_storage,
dispatch::{DispatchError, DispatchResult},
storage::IterableStorageDoubleMap,
storage::IterableStorageMap,
traits::{
ExistenceRequirement, Get, Imbalance, OnKilledAccount, SignedImbalance, TryDrop,
WithdrawReasons,
},
weights::Weight,
Parameter,
};
pub use imbalances::{NegativeImbalance, PositiveImbalance};
use impl_trait_for_tuples::impl_for_tuples;
#[cfg(feature = "std")]
use serde::{Deserialize, Serialize};
use sp_arithmetic::{FixedI128, FixedI64, FixedPointNumber};
use sp_runtime::traits::{AtLeast32BitUnsigned, MaybeSerializeDeserialize, Member, Zero};
use sp_std::prelude::*;
use sp_std::{collections::btree_map::BTreeMap, fmt::Debug, result};
use system as frame_system;
use system::{ensure_root, ensure_signed};
pub trait WeightInfo {
fn transfer(b: u32) -> Weight;
fn deposit(b: u32) -> Weight;
fn burn(b: u32) -> Weight;
}
pub trait Trait: system::Trait {
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
type WeightInfo: WeightInfo;
type Balance: Parameter
+ Member
+ AtLeast32BitUnsigned
+ Codec
+ Default
+ Copy
+ MaybeSerializeDeserialize
+ Debug
+ From<u64>
+ Into<u64>;
type ExistentialDeposit: Get<Self::Balance>;
type BalanceChecker: BalanceChecker<Self::Balance, Self::AccountId>;
type PriceGetter: PriceGetter;
type Aggregates: Aggregates<Self::AccountId, Self::Balance>;
}
decl_storage! {
trait Store for Module<T: Trait> as EqBalances {
pub Account: double_map hasher(blake2_128_concat) T::AccountId, hasher(blake2_128_concat) currency::Currency => SignedBalance<T::Balance>;
}
add_extra_genesis {
config(balances): Vec<(T::AccountId, T::Balance, u8)>;
build(|config: &GenesisConfig<T>| {
for &(ref who, free, currency) in config.balances.iter() {
let currency_typed: currency::Currency = currency.into();
<Module<T>>::deposit_creating(currency_typed, who, free, true);
}
});
}
}
decl_event!(
pub enum Event<T> where
<T as system::Trait>::AccountId,
<T as Trait>::Balance
{
Transfer(AccountId, AccountId, Currency, Balance, TransferReason),
}
);
decl_error! {
pub enum Error for Module<T: Trait> {
Overflow,
ExistingVestingSchedule,
DeadAccount,
NotAllowedToChangeBalance,
}
}
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin
{
type Error = Error<T>;
fn deposit_event() = default;
#[weight = T::WeightInfo::transfer(1)]
pub fn transfer(origin, currency: currency::Currency, to: <T as system::Trait>::AccountId, value: T::Balance) -> DispatchResult
{
let from = ensure_signed(origin)?;
Self::currency_transfer(currency, &from, &to, value, ExistenceRequirement::AllowDeath, TransferReason::Common, true)
}
#[weight = T::WeightInfo::deposit(1)]
pub fn deposit(origin, currency: currency::Currency, to: <T as system::Trait>::AccountId, value: T::Balance) -> DispatchResult
{
ensure_root(origin)?;
Self::deposit_creating(currency, &to, value, true);
Ok(())
}
#[weight = T::WeightInfo::burn(1)]
pub fn burn(origin, currency: currency::Currency, from: <T as system::Trait>::AccountId, value: T::Balance) -> DispatchResult
{
ensure_root(origin)?;
#[allow(unused_must_use)]
{
Self::withdraw(currency, &from, value, WithdrawReasons::all(), ExistenceRequirement::AllowDeath, true)?;
}
Ok(())
}
}
}
impl<T: Trait> Module<T> {
pub fn set_balance_with_agg_unsafe(
who: &T::AccountId,
currency: ¤cy::Currency,
value: SignedBalance<T::Balance>,
) {
let delta = value - <Account<T>>::get(who, currency);
match delta {
Positive(d) => {
Self::deposit_creating(*currency, who, d, false);
}
Negative(d) => {
Self::withdraw(*currency, who, d, WithdrawReasons::all(), ExistenceRequirement::AllowDeath, false);
}
}
}
}
pub trait BalanceGetter<AccountId, Balance>
where
Balance: Debug + Member + Into<u64>,
{
type PriceGetter: eq_oracle::PriceGetter;
fn get_balance(who: &AccountId, currency: ¤cy::Currency) -> SignedBalance<Balance>;
fn get_total_collateral_value() -> FixedI64;
fn get_total_debt_value() -> FixedI64;
fn iterate_balances() -> BTreeMap<AccountId, Vec<(currency::Currency, SignedBalance<Balance>)>>;
fn iterate_account_balances(
account: &AccountId,
) -> Vec<(currency::Currency, SignedBalance<Balance>)>;
fn get_debt_and_colaterall(who: &AccountId) -> (FixedI128, FixedI128) {
let mut debt = FixedI128::zero();
let mut collaterall = FixedI128::zero();
let accuracy = FixedI128::accuracy() / FixedI64::accuracy() as i128;
for abalance in Self::iterate_account_balances(who) {
let (currency, balance) = abalance;
let price = Self::PriceGetter::get_price(¤cy).unwrap_or(FixedI64::zero());
let price = FixedI128::from_inner((price.into_inner() as i128) * accuracy);
match balance {
Positive(value) => {
let value = Into::<u64>::into(value);
collaterall =
collaterall + FixedI128::from_inner(value as i128 * accuracy) * price;
}
Negative(value) => {
if price.is_zero() {
debug::warn!("No price for {:?} !", currency);
(FixedI128::from_inner(0), FixedI128::from_inner(0));
}
let value = Into::<u64>::into(value);
debt = debt + FixedI128::from_inner(value as i128 * accuracy) * price;
}
};
}
(debt, collaterall)
}
}
impl<T: Trait> BalanceGetter<T::AccountId, T::Balance> for Module<T> {
type PriceGetter = T::PriceGetter;
fn get_balance(who: &T::AccountId, currency: ¤cy::Currency) -> SignedBalance<T::Balance> {
<Account<T>>::get(who, currency)
}
fn get_total_collateral_value() -> FixedI64 {
let mut total_usd = FixedI64::zero();
for balance in T::Aggregates::iter_total(&UserGroup::Balances) {
let issuance = FixedI64::from_inner(Into::<u64>::into(balance.1.collateral) as i64);
let price = T::PriceGetter::get_price(&balance.0).unwrap_or(FixedI64::zero());
total_usd = total_usd + issuance * price;
}
total_usd
}
fn iterate_balances(
) -> BTreeMap<T::AccountId, Vec<(currency::Currency, SignedBalance<T::Balance>)>> {
let mut res: BTreeMap<T::AccountId, Vec<(currency::Currency, SignedBalance<T::Balance>)>> =
BTreeMap::new();
<Account<T>>::iter().for_each(|(acc, curr, balance)| {
res.entry(acc.clone())
.and_modify(|items| items.push((curr, balance.clone())))
.or_insert(vec![(curr, balance)]);
});
res
}
fn iterate_account_balances(
account: &T::AccountId,
) -> Vec<(currency::Currency, SignedBalance<T::Balance>)> {
<Account<T>>::iter_prefix(account).collect::<Vec<_>>()
}
fn get_total_debt_value() -> FixedI64 {
let mut total_usd = FixedI64::zero();
for balance in T::Aggregates::iter_total(&UserGroup::Balances) {
let debt = FixedI64::from_inner(Into::<u64>::into(balance.1.debt) as i64);
let price = T::PriceGetter::get_price(&balance.0).unwrap_or(FixedI64::zero());
total_usd = total_usd + debt * price;
}
total_usd
}
}
pub trait BalanceChecker<Balance, AccountId>
where
Balance: Member + Debug,
{
fn can_change_balance(
_who: &AccountId,
_currency: ¤cy::Currency,
_change: &SignedBalance<Balance>,
reason: Option<WithdrawReasons>,
) -> Result<bool, sp_runtime::DispatchError>;
}
#[impl_for_tuples(5)]
impl<Balance: Member + Debug, AccountId> BalanceChecker<Balance, AccountId> for Tuple {
fn can_change_balance(
who: &AccountId,
currency: ¤cy::Currency,
change: &SignedBalance<Balance>,
reason: Option<WithdrawReasons>,
) -> Result<bool, sp_runtime::DispatchError> {
let mut res: bool = true;
for_tuples!( #( res &= Tuple::can_change_balance(&who, ¤cy, &change, reason)?; )* );
Ok(res)
}
}
pub trait EqCurrency<AccountId, Balance>
where
Balance: Member
+ AtLeast32BitUnsigned
+ FullCodec
+ Copy
+ MaybeSerializeDeserialize
+ Debug
+ Default,
{
fn total_balance(currency: currency::Currency, who: &AccountId) -> Balance;
fn debt(currency: currency::Currency, who: &AccountId) -> Balance;
fn can_slash(currency: currency::Currency, who: &AccountId, value: Balance) -> bool;
fn currency_total_issuance(currency: currency::Currency) -> Balance;
fn currency_minimum_balance(currency: currency::Currency) -> Balance;
fn burn(currency: currency::Currency, amount: Balance) -> PositiveImbalance<Balance>;
fn issue(currency: currency::Currency, amount: Balance) -> NegativeImbalance<Balance>;
fn free_balance(currency: currency::Currency, who: &AccountId) -> Balance;
fn ensure_can_withdraw(
currency: currency::Currency,
who: &AccountId,
amount: Balance,
reasons: WithdrawReasons,
new_balance: Balance,
) -> DispatchResult;
fn currency_transfer(
currency: currency::Currency,
transactor: &AccountId,
dest: &AccountId,
value: Balance,
existence_requirement: ExistenceRequirement,
transfer_reason: TransferReason,
ensure_can_change: bool,
) -> DispatchResult;
fn slash(
currency: currency::Currency,
who: &AccountId,
value: Balance,
) -> (NegativeImbalance<Balance>, Balance);
fn deposit_into_existing(
currency: currency::Currency,
who: &AccountId,
value: Balance,
) -> Result<PositiveImbalance<Balance>, DispatchError>;
fn deposit_creating(
currency: currency::Currency,
who: &AccountId,
value: Balance,
ensure_can_change: bool,
) -> PositiveImbalance<Balance>;
fn resolve_creating(
currency: currency::Currency,
who: &AccountId,
value: NegativeImbalance<Balance>,
) {
let v = value.peek();
drop(value.offset(Self::deposit_creating(currency, who, v, true)));
}
fn withdraw(
currency: currency::Currency,
who: &AccountId,
value: Balance,
reasons: WithdrawReasons,
liveness: ExistenceRequirement,
ensure_can_change: bool,
) -> Result<NegativeImbalance<Balance>, DispatchError>;
fn make_free_balance_be(
currency: currency::Currency,
who: &AccountId,
value: Balance,
) -> SignedImbalance<Balance, PositiveImbalance<Balance>>;
}
impl<T: Trait> EqCurrency<T::AccountId, T::Balance> for Module<T> {
fn total_balance(currency: currency::Currency, who: &T::AccountId) -> T::Balance {
let balance = <Account<T>>::get(&who, ¤cy);
match balance {
SignedBalance::Positive(balance) => balance,
SignedBalance::Negative(_) => T::Balance::zero(),
}
}
fn debt(currency: currency::Currency, who: &T::AccountId) -> T::Balance {
let balance = <Account<T>>::get(&who, ¤cy);
match balance {
SignedBalance::Negative(balance) => balance,
SignedBalance::Positive(_) => T::Balance::zero(),
}
}
fn can_slash(_currency: currency::Currency, _who: &T::AccountId, _value: T::Balance) -> bool {
unimplemented!("fn can_slash")
}
fn currency_total_issuance(currency: currency::Currency) -> T::Balance {
T::Aggregates::get_total(&UserGroup::Balances, ¤cy).collateral
}
fn currency_minimum_balance(_currency: currency::Currency) -> T::Balance {
T::ExistentialDeposit::get()
}
fn burn(_currency: currency::Currency, _amount: T::Balance) -> PositiveImbalance<T::Balance> {
unimplemented!("fn burn");
}
fn issue(_currency: currency::Currency, _amount: T::Balance) -> NegativeImbalance<T::Balance> {
unimplemented!("fn issue");
}
fn free_balance(currency: currency::Currency, who: &T::AccountId) -> T::Balance {
let balance = <Account<T>>::get(&who, ¤cy);
match balance {
SignedBalance::Positive(balance) => balance,
SignedBalance::Negative(_) => T::Balance::zero(),
}
}
fn ensure_can_withdraw(
currency: currency::Currency,
who: &T::AccountId,
amount: T::Balance,
reasons: WithdrawReasons,
_new_balance: T::Balance,
) -> DispatchResult {
eq_log!(
"ensure_can_withdraw: who: {:?}, amount: {:?}",
&who,
&amount
);
eq_ensure!(
T::BalanceChecker::can_change_balance(
&who,
¤cy,
&SignedBalance::Negative(amount),
Option::Some(reasons),
)?,
Error::<T>::NotAllowedToChangeBalance,
"{}:{}. Cannot change balance. Who: {:?}, amount: {:?}, currency: {:?}.",
file!(),
line!(),
who,
amount,
currency
);
Ok(())
}
fn currency_transfer(
currency: currency::Currency,
transactor: &T::AccountId,
dest: &T::AccountId,
value: T::Balance,
_existence_requirement: ExistenceRequirement,
transfer_reason: TransferReason,
ensure_can_change: bool,
) -> DispatchResult {
if value.is_zero() || transactor == dest {
return Ok(());
}
eq_log!(
"transfer: currency: {:?}, transactor: {:?}, dest: {:?}, value: {:?}",
currency,
&transactor,
&dest,
value
);
<Account<T>>::mutate(transactor, ¤cy, |from_account| -> DispatchResult {
<Account<T>>::mutate(dest, ¤cy, |to_account| -> DispatchResult {
eq_ensure!(!ensure_can_change ||
T::BalanceChecker::can_change_balance(
&transactor,
¤cy,
&SignedBalance::Negative(value),
Option::None,
)?,
Error::<T>::NotAllowedToChangeBalance,
"{}:{}. Cannot change balance. Who: {:?}, amount: {:?}, currency: {:?}.",
file!(),
line!(),
transactor,
value,
currency
);
eq_ensure!(!ensure_can_change ||
T::BalanceChecker::can_change_balance(
&dest,
¤cy,
&SignedBalance::Positive(value),
Option::None,
)?,
Error::<T>::NotAllowedToChangeBalance,
"{}:{}. Cannot change balance. Who: {:?}, amount: {:?}, currency: {:?}.",
file!(),
line!(),
dest,
value,
currency
);
T::Aggregates::update_total(&transactor, ¤cy, from_account, &SignedBalance::Negative(value));
T::Aggregates::set_usergroup(&dest, &UserGroup::Balances, &true);
T::Aggregates::update_total(&dest, ¤cy, to_account, &SignedBalance::Positive(value));
let mut option_signed_balance = from_account.sub_balance(value);
*from_account = ok_or_error!(option_signed_balance, Error::<T>::Overflow,
"{}:{}. Overflow sub balance. Who: {:?}, balance: {:?}, amount: {:?}, currency: {:?}.",
file!(), line!(), transactor, from_account, value, currency)?;
option_signed_balance = to_account.add_balance(value);
*to_account = ok_or_error!(option_signed_balance, Error::<T>::Overflow,
"{}:{}. Overflow add balance. Who: {:?}, balance: {:?}, amount: {:?}, currency: {:?}.",
file!(), line!(), dest, to_account, value, currency)?;
Ok(())
})?;
Self::deposit_event(RawEvent::Transfer(
transactor.clone(),
dest.clone(),
currency.clone(),
value,
transfer_reason,
));
Ok(())
})
}
fn slash(
_currency: currency::Currency,
_who: &T::AccountId,
_value: T::Balance,
) -> (NegativeImbalance<T::Balance>, T::Balance) {
unimplemented!("fn slash")
}
fn deposit_into_existing(
currency: currency::Currency,
who: &T::AccountId,
value: T::Balance,
) -> Result<PositiveImbalance<T::Balance>, DispatchError> {
if value.is_zero() {
return Ok(PositiveImbalance::zero());
}
eq_ensure!(<Account<T>>::iter_prefix(
&who).any(|bal| !bal.1.is_zero()),
Error::<T>::DeadAccount,
"{}:{}. Cannot deposit dead balance. Who: {:?}.",
file!(),
line!(),
who);
<Account<T>>::mutate(
&who,
¤cy,
|bal| -> Result<PositiveImbalance<T::Balance>, DispatchError> {
eq_log!(
"deposit_into_existing: who: {:?}, value: {:?}",
&who,
&value
);
eq_ensure!(
T::BalanceChecker::can_change_balance(
&who,
¤cy,
&SignedBalance::Positive(value),
Option::None,
)?,
Error::<T>::NotAllowedToChangeBalance,
"{}:{}. Cannot change balance. Who: {:?}, amount: {:?}, currency: {:?}.",
file!(),
line!(),
who,
value,
currency
);
let option_signed_balance = bal.add_balance(value);
T::Aggregates::update_total(&who, ¤cy, bal, &SignedBalance::Positive(value));
*bal = ok_or_error!(option_signed_balance, Error::<T>::Overflow,
"{}:{}. Overflow add balance. Who: {:?}, balance: {:?}, amount: {:?}, currency: {:?}.",
file!(), line!(), who, bal, value, currency)?;
Ok(PositiveImbalance::new(value))
},
)
}
fn deposit_creating(
currency: currency::Currency,
who: &T::AccountId,
value: T::Balance,
ensure_can_change: bool,
) -> PositiveImbalance<T::Balance> {
if value.is_zero() {
return PositiveImbalance::zero();
}
<Account<T>>::mutate(
&who,
¤cy,
|bal| -> Result<PositiveImbalance<T::Balance>, PositiveImbalance<T::Balance>> {
if !ensure_can_change || T::BalanceChecker::can_change_balance(
&who,
¤cy,
&SignedBalance::Positive(value),
Option::None,
).unwrap_or(false) {
T::Aggregates::set_usergroup(&who, &UserGroup::Balances, &true);
let option_signed_balance = bal.add_balance(value);
T::Aggregates::update_total(&who, ¤cy, bal, &SignedBalance::Positive(value));
*bal = ok_or_error!(option_signed_balance, PositiveImbalance::zero(),
"{}:{}. Add balance error. Who: {:?}, balance: {:?}, amount: {:?}, currency: {:?}.",
file!(), line!(), who, bal, value, currency)?;
Ok(PositiveImbalance::new(value))
} else {
eq_log!("deposit_creating error who:{:?}, currency:{:?}, value:{:?}, ensure_can_change:{:?}",
*who,
currency,
value,
ensure_can_change);
Ok(PositiveImbalance::zero())
}
},
)
.unwrap_or_else(|x| x)
}
fn withdraw(
currency: currency::Currency,
who: &T::AccountId,
value: T::Balance,
reasons: WithdrawReasons,
_liveness: ExistenceRequirement,
ensure_can_change: bool,
) -> result::Result<NegativeImbalance<T::Balance>, DispatchError> {
if value.is_zero() {
return Ok(NegativeImbalance::zero());
}
<Account<T>>::mutate(
&who,
¤cy,
|bal| -> Result<NegativeImbalance<T::Balance>, DispatchError> {
eq_log!("withdraw: who: {:?}, value: {:?}", &who, &value);
eq_ensure!(!ensure_can_change ||
T::BalanceChecker::can_change_balance(
&who,
¤cy,
&SignedBalance::Negative(value),
Option::Some(reasons)
)?,
Error::<T>::NotAllowedToChangeBalance,
"{}:{}. Cannot change balance. Who: {:?}, amount: {:?}, currency: {:?}.",
file!(),
line!(),
who,
value,
currency
);
let option_signed_balance = bal.sub_balance(value);
T::Aggregates::update_total(&who, ¤cy, bal, &SignedBalance::Negative(value));
*bal = ok_or_error!(option_signed_balance, Error::<T>::Overflow,
"{}:{}. Overflow sub balance. Who: {:?}, balance: {:?}, amount: {:?}, currency: {:?}.",
file!(), line!(), who, bal, value, currency)?;
Ok(NegativeImbalance::new(value))
},
)
}
fn make_free_balance_be(
currency: currency::Currency,
who: &T::AccountId,
value: T::Balance,
) -> SignedImbalance<T::Balance, PositiveImbalance<T::Balance>> {
<Account<T>>::mutate(
who,
¤cy,
|account| -> Result<SignedImbalance<T::Balance, PositiveImbalance<T::Balance>>, ()> {
let imbalance = match account {
SignedBalance::Positive(balance) => {
let a_balance = balance.clone();
if value > a_balance {
SignedImbalance::Positive(PositiveImbalance::new(value - a_balance))
} else {
SignedImbalance::Negative(NegativeImbalance::new(a_balance - value))
}
}
SignedBalance::Negative(balance) => {
let a_balance = balance.clone();
SignedImbalance::Positive(PositiveImbalance::new(value + a_balance))
}
};
let signed_balance = SignedBalance::from(&imbalance);
let balance = match signed_balance {
SignedBalance::Positive(balance) => balance,
SignedBalance::Negative(balance) => balance,
};
eq_ensure!(
T::BalanceChecker::can_change_balance(
&who,
¤cy,
&SignedBalance::from(&imbalance),
Option::None,
)
.unwrap_or(false),
(),
"{}:{}. Cannot change balance. Who: {:?}, amount: {:?}, currency: {:?}.",
file!(),
line!(),
who,
balance,
currency
);
*account = SignedBalance::Positive(value);
Ok(imbalance)
},
)
.unwrap_or(SignedImbalance::Positive(PositiveImbalance::zero()))
}
}
impl<T: Trait> OnKilledAccount<T::AccountId> for Module<T> {
fn on_killed_account(who: &T::AccountId) {
Account::<T>::remove_prefix(who);
}
}