#![cfg_attr(not(feature = "std"), no_std)]
mod mock;
mod tests;
use codec::Codec;
use eq_balances::{BalanceGetter, EqCurrency};
use eq_oracle::PriceGetter;
use eq_primitives::{currency::Currency, FeeManager, InterestRateError};
use eq_utils::{balance_from_fixedi64, eq_ensure, eq_log, fixedi64_from_balance, ok_or_error};
#[allow(unused_imports)]
use frame_support::debug;
use frame_support::{
decl_error, decl_event, decl_module, decl_storage,
dispatch::DispatchResult,
traits::{ExistenceRequirement, Get, UnixTime, WithdrawReasons},
Parameter,
};
use sp_arithmetic::{traits::Saturating, FixedI64, FixedPointNumber};
use sp_runtime::traits::{
AccountIdConversion, AtLeast32BitUnsigned, CheckedSub, MaybeSerializeDeserialize, Member, Zero,
};
use sp_runtime::{sp_std::prelude::*, FixedI128, ModuleId, Permill};
use system as frame_system;
use system::ensure_signed;
pub trait Trait: authorship::Trait {
type ModuleId: Get<ModuleId>;
type Balance: Member
+ AtLeast32BitUnsigned
+ MaybeSerializeDeserialize
+ Codec
+ Copy
+ Parameter
+ Default
+ From<u64>
+ Into<u64>;
type BalanceGetter: BalanceGetter<Self::AccountId, Self::Balance>;
type EqCurrency: EqCurrency<Self::AccountId, Self::Balance>;
type PriceGetter: PriceGetter;
type UnixTime: UnixTime;
type BuyFee: Get<Permill>;
type SellFee: Get<Permill>;
type Fee: Get<Permill>;
type WeightFeeTreasury: Get<u32>;
type WeightFeeValidator: Get<u32>;
}
decl_storage! {
trait Store for Module<T: Trait> as EqTreasury {}
}
decl_error! {
pub enum Error for Module<T: Trait> {
NothingToSell,
BuyEqTokensRestricted,
NoPrice,
BadPrice,
InsufficientTreasuryBalance,
InsufficientAccountBalance,
}
}
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
type Error = Error<T>;
#[weight = 10_000]
pub fn colaterall_buyout(
origin,
currency: Currency,
amount: T::Balance,
max_price: FixedI64)
{
let who = ensure_signed(origin)?;
Self::colaterall_buyout_inner(&who, currency, amount, max_price)?
}
}
}
impl<T: Trait> Module<T> {
pub fn account_id() -> T::AccountId {
T::ModuleId::get().into_account()
}
fn colaterall_buyout_inner(
who: &T::AccountId,
currency: Currency,
amount: T::Balance,
max_price: FixedI64,
) -> DispatchResult {
eq_ensure!(
currency != Currency::Eq,
Error::<T>::BuyEqTokensRestricted,
"{}:{}. Eq currency not allowed for operation. Currency: {:?}.",
file!(),
line!(),
currency
);
let _client_eq_balance = if let eq_balances::SignedBalance::Positive(value) =
T::BalanceGetter::get_balance(who, &Currency::Eq)
{
value
} else {
T::Balance::zero()
};
let self_account_id = Self::account_id();
let self_currency_balance = if let eq_balances::SignedBalance::Positive(value) =
T::BalanceGetter::get_balance(&self_account_id, ¤cy)
{
value
} else {
T::Balance::zero()
};
let amount_to_sell = if self_currency_balance < amount {
self_currency_balance
} else {
amount
};
eq_ensure!(
amount_to_sell > T::Balance::zero(),
Error::<T>::NothingToSell,
"{}:{}. Amount to sell less than or equal to zero. Amount: {:?}, currency: {:?}.",
file!(),
line!(),
amount_to_sell,
currency
);
let currency_price = T::PriceGetter::get_price(¤cy).map_err(|_| {
debug::error!("{}:{}.", file!(), line!());
Error::<T>::NoPrice
})?;
let eq_price = T::PriceGetter::get_price(&Currency::Eq).map_err(|_| {
debug::error!("{}:{}.", file!(), line!());
Error::<T>::NoPrice
})?;
let amount_to_sell_fixed = FixedI64::from_inner(Into::into(amount_to_sell) as i64);
let fact_fixed: FixedI64 = amount_to_sell_fixed * currency_price / eq_price
* (FixedI64::from(T::BuyFee::get()) + FixedI64::one());
let fact: T::Balance = From::<u64>::from(fact_fixed.into_inner() as u64);
let actual_price = fact_fixed / amount_to_sell_fixed;
eq_ensure!(
max_price >= actual_price,
Error::<T>::BadPrice,
"{}:{}. Max price is less than actual price. Currency: {:?}, max price: {:?},
actual_price: {:?} = fact_fixed ({:?}) / amount_to_sell_fixed ({:?}).",
file!(),
line!(),
currency,
max_price,
actual_price,
fact_fixed,
amount_to_sell_fixed
);
let amount_to_sell = From::<u64>::from(amount_to_sell_fixed.into_inner() as u64);
let option_balance = self_currency_balance.checked_sub(&amount_to_sell);
let new_balance =
ok_or_error!(option_balance, Error::<T>::InsufficientTreasuryBalance,
"{}:{}. Couldn't sub balance. Who: {:?}, balance: {:?}, amount: {:?}, currency: {:?}.",
file!(), line!(), self_account_id, self_currency_balance, amount_to_sell, currency)?;
T::EqCurrency::ensure_can_withdraw(
currency,
&self_account_id,
amount_to_sell,
WithdrawReasons::all(),
new_balance,
)
.map_err(|_| {
debug::error!("{}:{}.", file!(), line!());
Error::<T>::InsufficientTreasuryBalance
})?;
T::EqCurrency::currency_transfer(
Currency::Eq,
who,
&self_account_id,
fact,
ExistenceRequirement::KeepAlive,
eq_primitives::TransferReason::TreasuryBuyEq,
true,
)
.map_err(|_| {
debug::error!("{}:{}.", file!(), line!());
Error::<T>::InsufficientAccountBalance
})?;
T::EqCurrency::currency_transfer(
currency,
&self_account_id,
who,
amount_to_sell,
ExistenceRequirement::KeepAlive,
eq_primitives::TransferReason::TreasuryBuyEq,
true,
)
.map_err(|_| {
debug::error!("{}:{}.", file!(), line!());
Error::<T>::InsufficientTreasuryBalance
})?;
Ok(())
}
fn split(value: T::Balance, amount: T::Balance) -> (T::Balance, T::Balance) {
let first = value.min(amount);
let second = value - first;
(first, second)
}
fn ration(value: T::Balance, first: u32, second: u32) -> (T::Balance, T::Balance) {
let total = first.saturating_add(second);
let amount1 = value.saturating_mul(first.into()) / total.into();
Self::split(value, amount1)
}
}
fn get_currency_priority(acurrency: Currency) -> u8 {
match acurrency {
Currency::Btc => 1,
Currency::Eth => 2,
Currency::Eos => 3,
_ => 4,
}
}
pub trait EqBuyout<AccountId, Balance> {
fn eq_buyout(who: &AccountId, amount: Balance) -> DispatchResult;
}
impl<T: Trait> EqBuyout<T::AccountId, T::Balance> for Module<T> {
fn eq_buyout(who: &T::AccountId, amount: T::Balance) -> DispatchResult {
let self_account_id = Self::account_id();
let self_eq_balance = T::BalanceGetter::get_balance(&self_account_id, &Currency::Eq);
if let eq_balances::SignedBalance::Positive(self_eq_balance_value) = self_eq_balance {
#[allow(unused_must_use)]
if self_eq_balance_value < amount {
T::EqCurrency::deposit_into_existing(
Currency::Eq,
&self_account_id,
amount - self_eq_balance_value,
)
.unwrap();
}
} else {
panic!("Cant be negative balance in treasury acc!");
}
let mut account_balances = T::BalanceGetter::iterate_account_balances(who);
account_balances
.sort_by(|a, b| get_currency_priority(a.0).cmp(&get_currency_priority(b.0)));
let mut eq_price = T::PriceGetter::get_price(&Currency::Eq).map_err(|_| {
debug::error!("{}:{}.", file!(), line!());
Error::<T>::NoPrice
})?;
eq_price = eq_price * (FixedI64::from(T::SellFee::get()) + FixedI64::one());
let mut amount_left = Into::<u64>::into(amount);
for account_balance in account_balances {
if account_balance.0 == Currency::Eq {
continue;
}
match account_balance.1 {
eq_balances::SignedBalance::Positive(balance) => {
let currency_price =
T::PriceGetter::get_price(&account_balance.0).map_err(|_| {
debug::error!("{}:{}.", file!(), line!());
Error::<T>::NoPrice
})?;
let balance_in_eq_fixed =
FixedI64::from_inner(Into::<u64>::into(balance) as i64) * currency_price
/ eq_price;
let balance_in_eq = balance_in_eq_fixed.into_inner() as u64;
#[allow(unused_must_use)]
if balance_in_eq < amount_left {
T::EqCurrency::currency_transfer(
account_balance.0,
who,
&self_account_id,
balance,
ExistenceRequirement::KeepAlive,
eq_primitives::TransferReason::TreasuryEqBuyout,
false,
);
amount_left = amount_left - balance_in_eq;
} else {
let balance_to_change = balance_from_fixedi64(
fixedi64_from_balance(amount_left) * eq_price / currency_price,
)
.unwrap();
#[allow(unused_must_use)]
T::EqCurrency::currency_transfer(
account_balance.0,
who,
&self_account_id,
balance_to_change,
ExistenceRequirement::KeepAlive,
eq_primitives::TransferReason::TreasuryEqBuyout,
false,
);
break;
}
}
_ => {
}
}
}
T::EqCurrency::currency_transfer(
Currency::Eq,
&self_account_id,
who,
amount,
ExistenceRequirement::KeepAlive,
eq_primitives::TransferReason::TreasuryEqBuyout,
false,
)
.unwrap();
Ok(())
}
}
impl<T: Trait> FeeManager<T::AccountId, T::Balance> for Module<T> {
fn calc_fee(owner: &T::AccountId, last_update: &u64) -> Result<T::Balance, InterestRateError> {
let balance = T::BalanceGetter::get_balance(owner, &Currency::Usd);
match balance {
eq_balances::SignedBalance::Positive(_) => Ok(T::Balance::zero()),
eq_balances::SignedBalance::Negative(_) => {
let accuracy = FixedI128::accuracy() / FixedI64::accuracy() as i128;
let fee = FixedI128::from(T::Fee::get());
let (debt, _) = T::BalanceGetter::get_debt_and_colaterall(owner);
let current_time = T::UnixTime::now().as_secs();
let elapsed = FixedI128::saturating_from_integer(current_time - *last_update);
let debt_amount_usd = fee * debt * elapsed
/ FixedI128::saturating_from_integer(365 as i128 * 24 * 60 * 60);
let price = T::PriceGetter::get_price(&Currency::Eq).map_err(|_| {
debug::error!("{}:{}.", file!(), line!());
InterestRateError::ExternalError
})?;
let price = FixedI128::from_inner((price.into_inner() as i128) * accuracy);
let debt_amount = debt_amount_usd / price;
eq_log!(
"Treasury fee for owner {:?}: fee = {:?}, elapsed = {:?}, fee in usd = {:?}, fee in eq = {:?}",
owner,
fee,
elapsed,
debt_amount_usd,
debt_amount
);
Ok(From::<u64>::from(
(debt_amount.into_inner() / accuracy) as u64,
))
}
}
}
#[allow(unused_must_use)]
fn charge_fee_inner(owner: &T::AccountId, debt_amount: &T::Balance) {
let account = Self::account_id();
let author = authorship::Module::<T>::author();
let (fee_for_account, fee_for_author) = Self::ration(
*debt_amount,
T::WeightFeeTreasury::get(),
T::WeightFeeValidator::get(),
);
T::EqCurrency::currency_transfer(
Currency::Eq,
owner,
&account,
fee_for_account,
ExistenceRequirement::KeepAlive,
eq_primitives::TransferReason::InterestFee,
false,
);
T::EqCurrency::currency_transfer(
Currency::Eq,
owner,
&author,
fee_for_author,
ExistenceRequirement::KeepAlive,
eq_primitives::TransferReason::InterestFee,
false,
);
}
}