lending: Handle the lost funds from defaulted loans (#1118)

This commit is contained in:
Justin Starry 2021-01-22 20:04:20 +08:00 committed by GitHub
parent d6571949a6
commit 0834e01d90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 52 additions and 36 deletions

View File

@ -1106,21 +1106,19 @@ fn process_liquidate(
let LiquidateResult {
bonus_amount,
withdraw_amount,
integer_repay_amount,
decimal_repay_amount,
repay_amount,
settle_amount,
} = withdraw_reserve.liquidate_obligation(
&obligation,
liquidity_amount,
&repay_reserve.liquidity.mint_pubkey,
trade_simulator,
)?;
repay_reserve
.liquidity
.repay(integer_repay_amount, decimal_repay_amount)?;
repay_reserve.liquidity.repay(repay_amount, settle_amount)?;
Reserve::pack(repay_reserve, &mut repay_reserve_info.data.borrow_mut())?;
obligation.liquidate(decimal_repay_amount, withdraw_amount + bonus_amount)?;
obligation.liquidate(settle_amount, withdraw_amount, bonus_amount)?;
Obligation::pack(obligation, &mut obligation_info.data.borrow_mut())?;
let authority_signer_seeds = &[
@ -1137,7 +1135,7 @@ fn process_liquidate(
spl_token_transfer(TokenTransferParams {
source: source_liquidity_info.clone(),
destination: repay_reserve_liquidity_supply_info.clone(),
amount: integer_repay_amount,
amount: repay_amount,
authority: user_transfer_authority_info.clone(),
authority_signer_seeds: &[],
token_program: token_program_id.clone(),

View File

@ -5,6 +5,7 @@ use crate::{
};
use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs};
use solana_program::{
entrypoint::ProgramResult,
program_error::ProgramError,
program_pack::{IsInitialized, Pack, Sealed},
pubkey::Pubkey,
@ -52,7 +53,7 @@ impl Obligation {
}
/// Accrue interest
pub fn accrue_interest(&mut self, cumulative_borrow_rate: Decimal) -> Result<(), ProgramError> {
pub fn accrue_interest(&mut self, cumulative_borrow_rate: Decimal) -> ProgramResult {
if cumulative_borrow_rate < self.cumulative_borrow_rate_wads {
return Err(LendingError::NegativeInterestRate.into());
}
@ -73,12 +74,13 @@ impl Obligation {
/// Liquidate part of obligation
pub fn liquidate(
&mut self,
repay_amount: Decimal,
settle_amount: Decimal,
withdraw_amount: u64,
) -> Result<(), ProgramError> {
self.borrowed_liquidity_wads = self.borrowed_liquidity_wads.try_sub(repay_amount)?;
bonus_amount: u64,
) -> ProgramResult {
self.borrowed_liquidity_wads = self.borrowed_liquidity_wads.try_sub(settle_amount)?;
self.deposited_collateral_tokens
.checked_sub(withdraw_amount)
.checked_sub(withdraw_amount + bonus_amount)
.ok_or(LendingError::MathOverflow)?;
Ok(())
}

View File

@ -132,25 +132,39 @@ impl Reserve {
}
// Don't pay out bonus if withdraw amount covers full collateral balance
let (withdraw_amount, bonus_amount) =
let (withdraw_amount, bonus_amount, remaining_amount) =
if withdraw_amount >= obligation.deposited_collateral_tokens.into() {
(obligation.deposited_collateral_tokens, 0)
(obligation.deposited_collateral_tokens, 0, 0)
} else {
let withdraw_amount = withdraw_amount.try_floor_u64()?;
let liquidation_bonus_rate = Rate::from_percent(self.config.liquidation_bonus);
let bonus_amount = Decimal::from(liquidation_bonus_rate)
.try_mul(withdraw_amount)?
.try_floor_u64()?;
let remaining_collateral = obligation.deposited_collateral_tokens - withdraw_amount;
let bonus_amount = bonus_amount.min(remaining_collateral);
(withdraw_amount, bonus_amount)
let remaining_amount = obligation.deposited_collateral_tokens - withdraw_amount;
if bonus_amount >= remaining_amount {
(withdraw_amount, remaining_amount, 0)
} else {
(
withdraw_amount,
bonus_amount,
remaining_amount - bonus_amount,
)
}
};
// Determine if the loan has defaulted
let settle_amount = if remaining_amount == 0 {
obligation.borrowed_liquidity_wads
} else {
decimal_repay_amount
};
Ok(LiquidateResult {
bonus_amount,
withdraw_amount,
integer_repay_amount,
decimal_repay_amount,
repay_amount: integer_repay_amount,
settle_amount,
})
}
@ -266,7 +280,7 @@ impl Reserve {
}
/// Update borrow rate and accrue interest
pub fn accrue_interest(&mut self, current_slot: Slot) -> Result<(), ProgramError> {
pub fn accrue_interest(&mut self, current_slot: Slot) -> ProgramResult {
let slots_elapsed = self.update_slot(current_slot);
if slots_elapsed > 0 {
let current_borrow_rate = self.current_borrow_rate()?;
@ -345,10 +359,11 @@ pub struct LiquidateResult {
pub bonus_amount: u64,
/// Amount of collateral to withdraw in exchange for repay amount
pub withdraw_amount: u64,
/// Amount that will be repaid as precise decimal
pub decimal_repay_amount: Decimal,
/// Amount of liquidity that is settled from the obligation. It includes
/// the amount of loan that was defaulted if collateral is depleted.
pub settle_amount: Decimal,
/// Amount that will be repaid as u64
pub integer_repay_amount: u64,
pub repay_amount: u64,
}
/// Reserve liquidity
@ -397,16 +412,12 @@ impl ReserveLiquidity {
}
/// Subtract repay amount from total borrows and add to available liquidity
pub fn repay(
&mut self,
integer_amount: u64,
decimal_amount: Decimal,
) -> Result<(), ProgramError> {
pub fn repay(&mut self, repay_amount: u64, settle_amount: Decimal) -> ProgramResult {
self.available_amount = self
.available_amount
.checked_add(integer_amount)
.checked_add(repay_amount)
.ok_or(LendingError::MathOverflow)?;
self.borrowed_amount_wads = self.borrowed_amount_wads.try_sub(decimal_amount)?;
self.borrowed_amount_wads = self.borrowed_amount_wads.try_sub(settle_amount)?;
Ok(())
}
@ -834,23 +845,28 @@ mod test {
assert!(
Decimal::from(liquidate_result.withdraw_amount) <=
liquidity_in_other_collateral(
liquidate_result.integer_repay_amount,
liquidate_result.repay_amount,
collateral_exchange_rate,
conversion_rate,
)?
);
assert!(
Decimal::from(liquidate_result.integer_repay_amount) <=
Decimal::from(liquidate_result.repay_amount) <=
obligation.borrowed_liquidity_wads.try_mul(Rate::from_percent(LIQUIDATION_CLOSE_FACTOR))?
);
assert!(
Decimal::from(liquidate_result.bonus_amount) <=
Decimal::from(liquidate_result.withdraw_amount).try_mul(Rate::from_percent(liquidation_bonus))?
);
assert!(
liquidate_result.withdraw_amount + liquidate_result.bonus_amount <=
obligation.deposited_collateral_tokens
);
let total_withdraw = liquidate_result.withdraw_amount + liquidate_result.bonus_amount;
let defaulted = total_withdraw == obligation.deposited_collateral_tokens;
if defaulted {
assert_eq!(liquidate_result.settle_amount, borrowed_liquidity_wads);
} else {
assert_eq!(liquidate_result.settle_amount.try_ceil_u64()?, liquidate_result.repay_amount);
assert!(total_withdraw < obligation.deposited_collateral_tokens);
}
}
}