Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add incentive reserved ratio & Add liquidation threshold #1588

Merged
merged 21 commits into from
Apr 25, 2022
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions pallets/loans/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ fn market_mock<T: Config>() -> Market<BalanceOf<T>> {
Market {
close_factor: Ratio::from_percent(50),
collateral_factor: Ratio::from_percent(50),
liquidation_threshold: Ratio::from_percent(55),
liquidate_incentive: Rate::from_inner(Rate::DIV / 100 * 110),
state: MarketState::Active,
rate_model: InterestRateModel::Jump(JumpModel {
Expand All @@ -36,6 +37,7 @@ fn market_mock<T: Config>() -> Market<BalanceOf<T>> {
jump_utilization: Ratio::from_percent(80),
}),
reserve_factor: Ratio::from_percent(15),
liquidate_incentive_reserved_factor: Ratio::from_percent(3),
supply_cap: 1_000_000_000_000_000_000_000u128, // set to 1B
borrow_cap: 1_000_000_000_000_000_000_000u128, // set to 1B
ptoken_id: 1200,
Expand Down Expand Up @@ -181,8 +183,10 @@ benchmarks! {
SystemOrigin::Root,
KSM,
Ratio::from_percent(50),
Ratio::from_percent(55),
Ratio::from_percent(50),
Ratio::from_percent(15),
Ratio::from_percent(3),
Rate::from_inner(Rate::DIV / 100 * 110),
1_000_000_000_000_000_000_000u128,
1_000_000_000_000_000_000_000u128
Expand Down
144 changes: 142 additions & 2 deletions pallets/loans/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ use sp_runtime::{
};
use sp_std::result::Result;

use sp_io::hashing::blake2_256;
pub use types::{BorrowSnapshot, Deposits, EarnedSnapshot, Market, MarketState, RewardMarketState};
pub use weights::WeightInfo;

Expand All @@ -72,6 +73,8 @@ pub const MIN_INTEREST_CALCULATING_INTERVAL: u64 = 100; // 100 seconds

pub const MAX_EXCHANGE_RATE: u128 = 1_000_000_000_000_000_000; // 1
pub const MIN_EXCHANGE_RATE: u128 = 20_000_000_000_000_000; // 0.02
pub const DEFAULT_LIQUIDATE_INCENTIVE_RESERVED_FACTOR: Ratio = Ratio::from_percent(3);
pub const DEFAULT_LIQUIDATE_THRESHOLD: Ratio = Ratio::from_percent(3);

type AssetIdOf<T> =
<<T as Config>::Assets as Inspect<<T as frame_system::Config>::AccountId>>::AssetId;
Expand Down Expand Up @@ -248,6 +251,9 @@ pub mod pallet {
DistributedBorrowerReward(AssetIdOf<T>, T::AccountId, BalanceOf<T>, BalanceOf<T>),
/// Reward Paid for user
RewardPaid(T::AccountId, BalanceOf<T>),
/// Event emitted when the incentive reserves are redeemed and transfer to receiver's account
/// [receive_account_id, asset_id, reduced_amount]
IncentiveReservesReduced(T::AccountId, AssetIdOf<T>, BalanceOf<T>),
}

/// The timestamp of the last calculation of accrued interest
Expand Down Expand Up @@ -476,10 +482,20 @@ pub mod pallet {
&& market.collateral_factor < Ratio::one(),
Error::<T>::InvalidFactor,
);
ensure!(
market.liquidation_threshold < Ratio::one()
&& market.liquidation_threshold >= market.collateral_factor,
Error::<T>::InvalidFactor
);
ensure!(
market.reserve_factor > Ratio::zero() && market.reserve_factor < Ratio::one(),
Error::<T>::InvalidFactor,
);
ensure!(
market.liquidate_incentive_reserved_factor > Ratio::zero()
&& market.liquidate_incentive_reserved_factor < Ratio::one(),
Error::<T>::InvalidFactor,
);
ensure!(
market.supply_cap > Zero::zero(),
Error::<T>::InvalidSupplyCap,
Expand Down Expand Up @@ -559,8 +575,10 @@ pub mod pallet {
origin: OriginFor<T>,
asset_id: AssetIdOf<T>,
collateral_factor: Ratio,
liquidation_threshold: Ratio,
reserve_factor: Ratio,
close_factor: Ratio,
liquidate_incentive_reserved_factor: Ratio,
liquidate_incentive: Rate,
#[pallet::compact] supply_cap: BalanceOf<T>,
#[pallet::compact] borrow_cap: BalanceOf<T>,
Expand All @@ -571,6 +589,10 @@ pub mod pallet {
collateral_factor >= Ratio::zero() && collateral_factor < Ratio::one(),
Error::<T>::InvalidFactor
);
ensure!(
liquidation_threshold >= collateral_factor && liquidation_threshold < Ratio::one(),
Error::<T>::InvalidFactor
);
ensure!(
reserve_factor > Ratio::zero() && reserve_factor < Ratio::one(),
Error::<T>::InvalidFactor
Expand All @@ -583,9 +605,11 @@ pub mod pallet {
ptoken_id: stored_market.ptoken_id,
rate_model: stored_market.rate_model,
collateral_factor,
liquidation_threshold,
reserve_factor,
close_factor,
liquidate_incentive,
liquidate_incentive_reserved_factor,
supply_cap,
borrow_cap,
};
Expand Down Expand Up @@ -1085,6 +1109,37 @@ pub mod pallet {

Ok(().into())
}

/// Sender redeems some of internal supplies in exchange for the underlying asset.
///
/// - `asset_id`: the asset to be redeemed.
/// - `redeem_amount`: the amount to be redeemed.
#[pallet::weight(T::WeightInfo::redeem()+T::WeightInfo::reduce_reserves())]
#[transactional]
pub fn reduce_incentive_reserves(
origin: OriginFor<T>,
receiver: <T::Lookup as StaticLookup>::Source,
asset_id: AssetIdOf<T>,
#[pallet::compact] redeem_amount: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
T::ReserveOrigin::ensure_origin(origin)?;
ensure!(!redeem_amount.is_zero(), Error::<T>::InvalidAmount);
let receiver = T::Lookup::lookup(receiver)?;
let from = Self::incentive_reward_account_id()?;
Self::ensure_active_market(asset_id)?;
Self::accrue_interest(asset_id)?;
Self::update_earned_stored(&from, asset_id)?;
yrong marked this conversation as resolved.
Show resolved Hide resolved
let exchange_rate = Self::exchange_rate(asset_id);
let voucher_amount = Self::calc_collateral_amount(redeem_amount, exchange_rate)?;
let redeem_amount = Self::do_redeem(&from, asset_id, voucher_amount)?;
T::Assets::transfer(asset_id, &from, &receiver, redeem_amount, false)?;
Self::deposit_event(Event::<T>::IncentiveReservesReduced(
receiver,
asset_id,
redeem_amount,
));
Ok(().into())
}
}
}

Expand Down Expand Up @@ -1118,6 +1173,31 @@ impl<T: Config> Pallet<T> {
}
}

pub fn get_account_liquidation_threshold_liquidity(
account: &T::AccountId,
) -> Result<(Liquidity, Shortfall), DispatchError> {
let total_borrow_value = Self::total_borrowed_value(account)?;
let total_collateral_value = Self::total_liquidation_threshold_value(account)?;
log::trace!(
target: "loans::get_account_liquidation_threshold_liquidity",
"account: {:?}, total_borrow_value: {:?}, total_collateral_value: {:?}",
account,
total_borrow_value.into_inner(),
total_collateral_value.into_inner(),
);
if total_collateral_value > total_borrow_value {
Ok((
total_collateral_value - total_borrow_value,
FixedU128::zero(),
))
} else {
Ok((
FixedU128::zero(),
total_borrow_value - total_collateral_value,
))
}
}

fn total_borrowed_value(borrower: &T::AccountId) -> Result<FixedU128, DispatchError> {
let mut total_borrow_value: FixedU128 = FixedU128::zero();
for (asset_id, _) in Self::active_markets() {
Expand Down Expand Up @@ -1156,6 +1236,29 @@ impl<T: Config> Pallet<T> {
Self::get_asset_value(asset_id, effects_amount)
}

fn liquidation_threshold_asset_value(
borrower: &T::AccountId,
asset_id: AssetIdOf<T>,
) -> Result<FixedU128, DispatchError> {
if !AccountDeposits::<T>::contains_key(asset_id, borrower) {
return Ok(FixedU128::zero());
}
let deposits = Self::account_deposits(asset_id, borrower);
if !deposits.is_collateral {
return Ok(FixedU128::zero());
}
if deposits.voucher_balance.is_zero() {
return Ok(FixedU128::zero());
}
let exchange_rate = Self::exchange_rate(asset_id);
let underlying_amount =
Self::calc_underlying_amount(deposits.voucher_balance, exchange_rate)?;
let market = Self::market(asset_id)?;
let effects_amount = market.liquidation_threshold.mul_ceil(underlying_amount);

Self::get_asset_value(asset_id, effects_amount)
}

fn total_collateral_value(borrower: &T::AccountId) -> Result<FixedU128, DispatchError> {
let mut total_asset_value: FixedU128 = FixedU128::zero();
for (asset_id, _market) in Self::active_markets() {
Expand All @@ -1167,6 +1270,21 @@ impl<T: Config> Pallet<T> {
Ok(total_asset_value)
}

fn total_liquidation_threshold_value(
borrower: &T::AccountId,
) -> Result<FixedU128, DispatchError> {
let mut total_asset_value: FixedU128 = FixedU128::zero();
for (asset_id, _market) in Self::active_markets() {
total_asset_value = total_asset_value
.checked_add(&Self::liquidation_threshold_asset_value(
borrower, asset_id,
)?)
.ok_or(ArithmeticError::Overflow)?;
}

Ok(total_asset_value)
}

/// Checks if the redeemer should be allowed to redeem tokens in given market
fn redeem_allowed(
asset_id: AssetIdOf<T>,
Expand Down Expand Up @@ -1367,7 +1485,7 @@ impl<T: Config> Pallet<T> {
repay_amount,
market
);
let (_, shortfall) = Self::get_account_liquidity(borrower)?;
let (_, shortfall) = Self::get_account_liquidation_threshold_liquidity(borrower)?;
if shortfall.is_zero() {
return Err(Error::<T>::InsufficientShortfall.into());
}
Expand Down Expand Up @@ -1450,6 +1568,7 @@ impl<T: Config> Pallet<T> {
collateral_asset_id,
repay_amount,
real_collateral_underlying_amount,
market.liquidate_incentive_reserved_factor,
)?;

Ok(())
Expand All @@ -1463,6 +1582,7 @@ impl<T: Config> Pallet<T> {
collateral_asset_id: AssetIdOf<T>,
repay_amount: BalanceOf<T>,
collateral_underlying_amount: BalanceOf<T>,
incentive_ratio: Ratio,
) -> DispatchResult {
log::trace!(
target: "loans::liquidated_transfer",
Expand Down Expand Up @@ -1529,14 +1649,27 @@ impl<T: Config> Pallet<T> {
Ok(())
},
)?;
let incentive_reserved_amount = incentive_ratio.mul_floor(collateral_amount);
yrong marked this conversation as resolved.
Show resolved Hide resolved
// increase liquidator's voucher_balance
AccountDeposits::<T>::try_mutate(
collateral_asset_id,
liquidator,
|deposits| -> DispatchResult {
deposits.voucher_balance = deposits
.voucher_balance
.checked_add(collateral_amount)
.checked_add(collateral_amount - incentive_reserved_amount)
.ok_or(ArithmeticError::Overflow)?;
Ok(())
},
)?;
// increase reserve's voucher_balance
AccountDeposits::<T>::try_mutate(
collateral_asset_id,
Self::incentive_reward_account_id()?,
|deposits| -> DispatchResult {
deposits.voucher_balance = deposits
.voucher_balance
.checked_add(incentive_reserved_amount)
.ok_or(ArithmeticError::Overflow)?;
Ok(())
},
Expand Down Expand Up @@ -1746,4 +1879,11 @@ impl<T: Config> Pallet<T> {
Err(Error::<T>::MarketDoesNotExist.into())
}
}

// Returns the incentive reward account
pub fn incentive_reward_account_id() -> Result<T::AccountId, DispatchError> {
let account_id: T::AccountId = T::PalletId::get().into_account();
let entropy = (b"loans/incentive", &[account_id]).using_encoded(blake2_256);
Ok(T::AccountId::decode(&mut &entropy[..]).map_err(|_| Error::<T>::CodecError)?)
}
}
Loading