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

Collateralization Limits Part 2 #172

Merged
merged 7 commits into from
Dec 16, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 6 additions & 2 deletions token-lending/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1153,7 +1153,10 @@ fn main() {

let added_borrow_weight_bps = value_of(arg_matches, "added_borrow_weight_bps").unwrap();
let reserve_type = value_of(arg_matches, "reserve_type").unwrap();
let attributed_borrow_limit = value_of(arg_matches, "attributed_borrow_limit").unwrap();
let attributed_borrow_limit_open =
value_of(arg_matches, "attributed_borrow_limit_open").unwrap();
let attributed_borrow_limit_close =
value_of(arg_matches, "attributed_borrow_limit_close").unwrap();

let borrow_fee_wad = (borrow_fee * WAD as f64) as u64;
let flash_loan_fee_wad = (flash_loan_fee * WAD as f64) as u64;
Expand Down Expand Up @@ -1207,7 +1210,8 @@ fn main() {
protocol_take_rate,
added_borrow_weight_bps,
reserve_type,
attributed_borrow_limit,
attributed_borrow_limit_open,
attributed_borrow_limit_close,
},
source_liquidity_pubkey,
source_liquidity_owner_keypair,
Expand Down
164 changes: 134 additions & 30 deletions token-lending/program/src/processor.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Program state processor

use crate::state::Bonus;
use crate::{
self as solend_program,
error::LendingError,
Expand All @@ -15,6 +16,7 @@ use crate::{
};
use bytemuck::bytes_of;
use pyth_sdk_solana::{self, state::ProductAccount};

use solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult,
Expand Down Expand Up @@ -205,6 +207,10 @@ pub fn process_instruction(
msg!("Instruction: Resize Reserve");
process_resize_reserve(program_id, accounts)
}
LendingInstruction::SetObligationCloseabilityStatus { closeable } => {
msg!("Instruction: Mark Obligation As Closable");
process_set_obligation_closeability_status(program_id, closeable, accounts)
}
}
}

Expand Down Expand Up @@ -1081,7 +1087,10 @@ fn process_refresh_obligation(program_id: &Pubkey, accounts: &[AccountInfo]) ->

obligation.last_update.update_slot(clock.slot);

update_borrow_attribution_values(&mut obligation, &accounts[1..], false)?;
let (_, close_exceeded) = update_borrow_attribution_values(&mut obligation, &accounts[1..])?;
if close_exceeded.is_none() {
obligation.closeable = false;
}

// move the ObligationLiquidity with the max borrow weight to the front
if let Some((_, max_borrow_weight_index)) = max_borrow_weight {
Expand Down Expand Up @@ -1113,10 +1122,12 @@ fn process_refresh_obligation(program_id: &Pubkey, accounts: &[AccountInfo]) ->
fn update_borrow_attribution_values(
obligation: &mut Obligation,
deposit_reserve_infos: &[AccountInfo],
error_if_limit_exceeded: bool,
) -> ProgramResult {
) -> Result<(Option<Pubkey>, Option<Pubkey>), ProgramError> {
let deposit_infos = &mut deposit_reserve_infos.iter();

let mut open_exceeded = None;
let mut close_exceeded = None;

for collateral in obligation.deposits.iter_mut() {
let deposit_reserve_info = next_account_info(deposit_infos)?;
let mut deposit_reserve = Reserve::unpack(&deposit_reserve_info.data.borrow())?;
Expand All @@ -1127,11 +1138,9 @@ fn update_borrow_attribution_values(
return Err(LendingError::InvalidAccountInput.into());
}

if obligation.updated_borrow_attribution_after_upgrade {
deposit_reserve.attributed_borrow_value = deposit_reserve
.attributed_borrow_value
.saturating_sub(collateral.attributed_borrow_value);
}
deposit_reserve.attributed_borrow_value = deposit_reserve
.attributed_borrow_value
.saturating_sub(collateral.attributed_borrow_value);

if obligation.deposited_value > Decimal::zero() {
collateral.attributed_borrow_value = collateral
Expand All @@ -1146,24 +1155,21 @@ fn update_borrow_attribution_values(
.attributed_borrow_value
.try_add(collateral.attributed_borrow_value)?;

if error_if_limit_exceeded
&& deposit_reserve.attributed_borrow_value
> Decimal::from(deposit_reserve.config.attributed_borrow_limit)
if deposit_reserve.attributed_borrow_value
> Decimal::from(deposit_reserve.config.attributed_borrow_limit_open)
{
msg!(
"Attributed borrow value is over the limit for reserve {} and mint {}",
deposit_reserve_info.key,
deposit_reserve.liquidity.mint_pubkey
);
return Err(LendingError::BorrowAttributionLimitExceeded.into());
open_exceeded = Some(*deposit_reserve_info.key);
}
if deposit_reserve.attributed_borrow_value
> Decimal::from(deposit_reserve.config.attributed_borrow_limit_close)
{
close_exceeded = Some(*deposit_reserve_info.key);
}

Reserve::pack(deposit_reserve, &mut deposit_reserve_info.data.borrow_mut())?;
}

obligation.updated_borrow_attribution_after_upgrade = true;

Ok(())
Ok((open_exceeded, close_exceeded))
}

#[inline(never)] // avoid stack frame limit
Expand Down Expand Up @@ -1550,7 +1556,15 @@ fn _withdraw_obligation_collateral<'a>(
.market_value
.saturating_sub(withdraw_value);

update_borrow_attribution_values(&mut obligation, deposit_reserve_infos, true)?;
let (open_exceeded, _) =
update_borrow_attribution_values(&mut obligation, deposit_reserve_infos)?;
if let Some(reserve_pubkey) = open_exceeded {
msg!(
"Open borrow attribution limit exceeded for reserve {:?}",
reserve_pubkey
);
return Err(LendingError::BorrowAttributionLimitExceeded.into());
}

// obligation.withdraw must be called after updating borrow attribution values, since we can
// lose information if an entire deposit is removed, making the former calculation incorrect
Expand Down Expand Up @@ -1805,8 +1819,16 @@ fn process_borrow_obligation_liquidity(
obligation_liquidity.borrow(borrow_amount)?;
obligation.last_update.mark_stale();

update_borrow_attribution_values(&mut obligation, &accounts[9..], true)?;
// HACK: fast forward through the used account info's
let (open_exceeded, _) = update_borrow_attribution_values(&mut obligation, &accounts[9..])?;
if let Some(reserve_pubkey) = open_exceeded {
msg!(
"Open borrow attribution limit exceeded for reserve {:?}",
reserve_pubkey
);
return Err(LendingError::BorrowAttributionLimitExceeded.into());
}

// HACK: fast forward through the deposit reserve infos
for _ in 0..obligation.deposits.len() {
next_account_info(account_info_iter)?;
}
Expand Down Expand Up @@ -1975,7 +1997,7 @@ fn _liquidate_obligation<'a>(
user_transfer_authority_info: &AccountInfo<'a>,
clock: &Clock,
token_program_id: &AccountInfo<'a>,
) -> Result<(u64, Decimal), ProgramError> {
) -> Result<(u64, Bonus), ProgramError> {
let lending_market = LendingMarket::unpack(&lending_market_info.data.borrow())?;
if lending_market_info.owner != program_id {
msg!("Lending market provided is not owned by the lending program");
Expand Down Expand Up @@ -2061,8 +2083,9 @@ fn _liquidate_obligation<'a>(
msg!("Obligation borrowed value is zero");
return Err(LendingError::ObligationBorrowsZero.into());
}
if obligation.borrowed_value < obligation.unhealthy_borrow_value {
msg!("Obligation is healthy and cannot be liquidated");

if obligation.borrowed_value < obligation.unhealthy_borrow_value && !obligation.closeable {
msg!("Obligation must be unhealthy or marked as closeable to be liquidated");
return Err(LendingError::ObligationHealthy.into());
}

Expand Down Expand Up @@ -2104,16 +2127,17 @@ fn _liquidate_obligation<'a>(
return Err(LendingError::InvalidMarketAuthority.into());
}

let bonus = withdraw_reserve.calculate_bonus(&obligation)?;
let CalculateLiquidationResult {
settle_amount,
repay_amount,
withdraw_amount,
bonus_rate,
} = withdraw_reserve.calculate_liquidation(
liquidity_amount,
&obligation,
liquidity,
collateral,
&bonus,
)?;

if repay_amount == 0 {
Expand Down Expand Up @@ -2168,7 +2192,7 @@ fn _liquidate_obligation<'a>(
token_program: token_program_id.clone(),
})?;

Ok((withdraw_amount, bonus_rate))
Ok((withdraw_amount, bonus))
}

#[inline(never)] // avoid stack frame limit
Expand Down Expand Up @@ -2200,7 +2224,7 @@ fn process_liquidate_obligation_and_redeem_reserve_collateral(
let token_program_id = next_account_info(account_info_iter)?;
let clock = &Clock::get()?;

let (withdrawn_collateral_amount, bonus_rate) = _liquidate_obligation(
let (withdrawn_collateral_amount, bonus) = _liquidate_obligation(
program_id,
liquidity_amount,
source_liquidity_info,
Expand Down Expand Up @@ -2247,7 +2271,7 @@ fn process_liquidate_obligation_and_redeem_reserve_collateral(
return Err(LendingError::InvalidAccountInput.into());
}
let protocol_fee = withdraw_reserve
.calculate_protocol_liquidation_fee(withdraw_liquidity_amount, bonus_rate)?;
.calculate_protocol_liquidation_fee(withdraw_liquidity_amount, &bonus)?;

spl_token_transfer(TokenTransferParams {
source: destination_liquidity_info.clone(),
Expand Down Expand Up @@ -3115,6 +3139,86 @@ pub fn process_resize_reserve(_program_id: &Pubkey, accounts: &[AccountInfo]) ->
Ok(())
}

/// process mark obligation as closable
pub fn process_set_obligation_closeability_status(
program_id: &Pubkey,
closeable: bool,
accounts: &[AccountInfo],
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let obligation_info = next_account_info(account_info_iter)?;
let lending_market_info = next_account_info(account_info_iter)?;
let reserve_info = next_account_info(account_info_iter)?;
let signer_info = next_account_info(account_info_iter)?;
let clock = Clock::get()?;

let lending_market = LendingMarket::unpack(&lending_market_info.data.borrow())?;
if lending_market_info.owner != program_id {
msg!("Lending market provided is not owned by the lending program");
return Err(LendingError::InvalidAccountOwner.into());
}

let reserve = Reserve::unpack(&reserve_info.data.borrow())?;
if reserve_info.owner != program_id {
msg!("Reserve provided is not owned by the lending program");
return Err(LendingError::InvalidAccountOwner.into());
}
if &reserve.lending_market != lending_market_info.key {
msg!("Reserve lending market does not match the lending market provided");
return Err(LendingError::InvalidAccountInput.into());
}

if reserve.attributed_borrow_value < Decimal::from(reserve.config.attributed_borrow_limit_close)
{
msg!("Reserve attributed borrow value is below the attributed borrow limit");
return Err(LendingError::BorrowAttributionLimitNotExceeded.into());
}

let mut obligation = Obligation::unpack(&obligation_info.data.borrow())?;
if obligation_info.owner != program_id {
msg!("Obligation provided is not owned by the lending program");
return Err(LendingError::InvalidAccountOwner.into());
}

if &obligation.lending_market != lending_market_info.key {
msg!("Obligation lending market does not match the lending market provided");
return Err(LendingError::InvalidAccountInput.into());
}
if obligation.last_update.is_stale(clock.slot)? {
msg!("Obligation is stale and must be refreshed");
return Err(LendingError::ObligationStale.into());
}

if &lending_market.risk_authority != signer_info.key && &lending_market.owner != signer_info.key
{
msg!("Signer must be risk authority or lending market owner");
return Err(LendingError::InvalidAccountInput.into());
}

if !signer_info.is_signer {
msg!("Risk authority or lending market owner must be a signer");
return Err(LendingError::InvalidSigner.into());
}

if obligation.borrowed_value == Decimal::zero() {
msg!("Obligation borrowed value is zero");
return Err(LendingError::ObligationBorrowsZero.into());
}

obligation
.find_collateral_in_deposits(*reserve_info.key)
.map_err(|_| {
msg!("Obligation does not have a deposit for the reserve provided");
LendingError::ObligationCollateralEmpty
})?;

obligation.closeable = closeable;

Obligation::pack(obligation, &mut obligation_info.data.borrow_mut())?;

Ok(())
}

fn assert_uninitialized<T: Pack + IsInitialized>(
account_info: &AccountInfo,
) -> Result<T, ProgramError> {
Expand Down
Loading
Loading