-
Notifications
You must be signed in to change notification settings - Fork 69
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
Changes from 4 commits
a227f69
3f68f83
009e13a
51afee6
20ecdc2
b5c88a3
81e25ff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,7 @@ use crate::{ | |
}; | ||
use bytemuck::bytes_of; | ||
use pyth_sdk_solana::{self, state::ProductAccount}; | ||
use solana_program::slot_history::Slot; | ||
use solana_program::{ | ||
account_info::{next_account_info, AccountInfo}, | ||
entrypoint::ProgramResult, | ||
|
@@ -205,6 +206,10 @@ pub fn process_instruction( | |
msg!("Instruction: Resize Reserve"); | ||
process_resize_reserve(program_id, accounts) | ||
} | ||
LendingInstruction::MarkObligationAsClosable { closeable_by } => { | ||
msg!("Instruction: Mark Obligation As Closable"); | ||
process_mark_obligation_as_closeable(program_id, closeable_by, accounts) | ||
} | ||
} | ||
} | ||
|
||
|
@@ -1081,7 +1086,13 @@ 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 any_borrow_attribution_limit_exceeded = | ||
update_borrow_attribution_values(&mut obligation, &accounts[1..], false)?; | ||
|
||
// unmark obligation as closable after it's been liquidated enough times | ||
if obligation.is_closeable(clock.slot) && !any_borrow_attribution_limit_exceeded { | ||
obligation.closeable_by = 0; | ||
} | ||
|
||
// move the ObligationLiquidity with the max borrow weight to the front | ||
if let Some((_, max_borrow_weight_index)) = max_borrow_weight { | ||
|
@@ -1109,13 +1120,16 @@ fn process_refresh_obligation(program_id: &Pubkey, accounts: &[AccountInfo]) -> | |
/// - the obligation's deposited_value must be refreshed | ||
/// - the obligation's true_borrowed_value must be refreshed | ||
/// | ||
/// Returns true if any of the borrow attribution limits were exceeded | ||
/// | ||
/// Note that this function packs and unpacks deposit reserves. | ||
fn update_borrow_attribution_values( | ||
obligation: &mut Obligation, | ||
deposit_reserve_infos: &[AccountInfo], | ||
error_if_limit_exceeded: bool, | ||
) -> ProgramResult { | ||
) -> Result<bool, ProgramError> { | ||
let deposit_infos = &mut deposit_reserve_infos.iter(); | ||
let mut any_attribution_limit_exceeded = false; | ||
|
||
for collateral in obligation.deposits.iter_mut() { | ||
let deposit_reserve_info = next_account_info(deposit_infos)?; | ||
|
@@ -1146,24 +1160,27 @@ 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) | ||
{ | ||
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()); | ||
any_attribution_limit_exceeded = true; | ||
|
||
if error_if_limit_exceeded { | ||
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()); | ||
} | ||
} | ||
|
||
Reserve::pack(deposit_reserve, &mut deposit_reserve_info.data.borrow_mut())?; | ||
} | ||
|
||
obligation.updated_borrow_attribution_after_upgrade = true; | ||
|
||
Ok(()) | ||
Ok(any_attribution_limit_exceeded) | ||
} | ||
|
||
#[inline(never)] // avoid stack frame limit | ||
|
@@ -2061,8 +2078,11 @@ 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.is_closeable(clock.slot) | ||
{ | ||
msg!("Obligation must be unhealthy or marked as closeable to be liquidated"); | ||
return Err(LendingError::ObligationHealthy.into()); | ||
} | ||
|
||
|
@@ -2104,16 +2124,17 @@ fn _liquidate_obligation<'a>( | |
return Err(LendingError::InvalidMarketAuthority.into()); | ||
} | ||
|
||
let bonus_rate = withdraw_reserve.calculate_bonus(&obligation, clock.slot)?; | ||
let CalculateLiquidationResult { | ||
settle_amount, | ||
repay_amount, | ||
withdraw_amount, | ||
bonus_rate, | ||
} = withdraw_reserve.calculate_liquidation( | ||
liquidity_amount, | ||
&obligation, | ||
liquidity, | ||
collateral, | ||
bonus_rate, | ||
)?; | ||
|
||
if repay_amount == 0 { | ||
|
@@ -3115,6 +3136,84 @@ pub fn process_resize_reserve(_program_id: &Pubkey, accounts: &[AccountInfo]) -> | |
Ok(()) | ||
} | ||
|
||
/// process mark obligation as closable | ||
pub fn process_mark_obligation_as_closeable( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ig remove expiry time and change this to "set obligation close ability status" |
||
program_id: &Pubkey, | ||
closeable_by: Slot, | ||
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 risk_authority_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) { | ||
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 != risk_authority_info.key { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. or owner |
||
msg!("Lending market risk authority does not match the risk authority provided"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. update msg |
||
return Err(LendingError::InvalidAccountInput.into()); | ||
} | ||
|
||
if !risk_authority_info.is_signer { | ||
msg!("Risk authority provided 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_by = closeable_by; | ||
|
||
Obligation::pack(obligation, &mut obligation_info.data.borrow_mut())?; | ||
|
||
Ok(()) | ||
} | ||
|
||
fn assert_uninitialized<T: Pack + IsInitialized>( | ||
account_info: &AccountInfo, | ||
) -> Result<T, ProgramError> { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
close vs open attribution limits