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 2 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
90 changes: 49 additions & 41 deletions token-lending/program/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +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,
Expand Down Expand Up @@ -206,9 +206,9 @@ pub fn process_instruction(
msg!("Instruction: Resize Reserve");
process_resize_reserve(program_id, accounts)
}
LendingInstruction::MarkObligationAsClosable { closeable_by } => {
LendingInstruction::SetObligationCloseabilityStatus { closeable } => {
msg!("Instruction: Mark Obligation As Closable");
process_mark_obligation_as_closeable(program_id, closeable_by, accounts)
process_set_obligation_closeability_status(program_id, closeable, accounts)
}
}
}
Expand Down Expand Up @@ -1086,12 +1086,9 @@ fn process_refresh_obligation(program_id: &Pubkey, accounts: &[AccountInfo]) ->

obligation.last_update.update_slot(clock.slot);

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;
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
Expand Down Expand Up @@ -1120,16 +1117,15 @@ 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,
) -> Result<bool, ProgramError> {
) -> Result<(Option<Pubkey>, Option<Pubkey>), ProgramError> {
let deposit_infos = &mut deposit_reserve_infos.iter();
let mut any_attribution_limit_exceeded = false;

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)?;
Expand Down Expand Up @@ -1161,26 +1157,22 @@ fn update_borrow_attribution_values(
.try_add(collateral.attributed_borrow_value)?;

if deposit_reserve.attributed_borrow_value
> Decimal::from(deposit_reserve.config.attributed_borrow_limit)
> Decimal::from(deposit_reserve.config.attributed_borrow_limit_open)
{
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());
}
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(any_attribution_limit_exceeded)
Ok((open_exceeded, close_exceeded))
}

#[inline(never)] // avoid stack frame limit
Expand Down Expand Up @@ -1567,7 +1559,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 @@ -1822,8 +1822,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 @@ -2079,9 +2087,7 @@ fn _liquidate_obligation<'a>(
return Err(LendingError::ObligationBorrowsZero.into());
}

if obligation.borrowed_value < obligation.unhealthy_borrow_value
&& !obligation.is_closeable(clock.slot)
{
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 @@ -2124,7 +2130,7 @@ fn _liquidate_obligation<'a>(
return Err(LendingError::InvalidMarketAuthority.into());
}

let bonus_rate = withdraw_reserve.calculate_bonus(&obligation, clock.slot)?;
let bonus_rate = withdraw_reserve.calculate_bonus(&obligation)?;
let CalculateLiquidationResult {
settle_amount,
repay_amount,
Expand Down Expand Up @@ -3137,16 +3143,16 @@ pub fn process_resize_reserve(_program_id: &Pubkey, accounts: &[AccountInfo]) ->
}

/// process mark obligation as closable
pub fn process_mark_obligation_as_closeable(
pub fn process_set_obligation_closeability_status(
program_id: &Pubkey,
closeable_by: Slot,
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 risk_authority_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())?;
Expand All @@ -3165,7 +3171,8 @@ pub fn process_mark_obligation_as_closeable(
return Err(LendingError::InvalidAccountInput.into());
}

if reserve.attributed_borrow_value < Decimal::from(reserve.config.attributed_borrow_limit) {
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());
}
Expand All @@ -3185,13 +3192,14 @@ pub fn process_mark_obligation_as_closeable(
return Err(LendingError::ObligationStale.into());
}

if &lending_market.risk_authority != risk_authority_info.key {
if &lending_market.risk_authority != signer_info.key && &lending_market.owner != signer_info.key
{
msg!("Lending market risk authority does not match the risk authority provided");
Copy link
Member

Choose a reason for hiding this comment

The 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");
if !signer_info.is_signer {
msg!("Risk authority or lending market owner must be a signer");
return Err(LendingError::InvalidSigner.into());
}

Expand All @@ -3207,7 +3215,7 @@ pub fn process_mark_obligation_as_closeable(
LendingError::ObligationCollateralEmpty
})?;

obligation.closeable_by = closeable_by;
obligation.closeable = closeable;

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

Expand Down
12 changes: 6 additions & 6 deletions token-lending/program/tests/attributed_borrows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ async fn test_refresh_obligation() {
&lending_market_owner,
&reserves[0],
ReserveConfig {
attributed_borrow_limit: 1,
attributed_borrow_limit_open: 1,
..reserves[0].account.config
},
reserves[0].account.rate_limiter.config,
Expand Down Expand Up @@ -295,7 +295,7 @@ async fn test_calculations() {
&lending_market_owner,
&reserves[0],
ReserveConfig {
attributed_borrow_limit: 113,
attributed_borrow_limit_open: 113,
..reserves[0].account.config
},
reserves[0].account.rate_limiter.config,
Expand Down Expand Up @@ -333,7 +333,7 @@ async fn test_calculations() {
&lending_market_owner,
&reserves[0],
ReserveConfig {
attributed_borrow_limit: 120,
attributed_borrow_limit_open: 120,
..reserves[0].account.config
},
reserves[0].account.rate_limiter.config,
Expand Down Expand Up @@ -386,7 +386,7 @@ async fn test_calculations() {
},
attributed_borrow_value: Decimal::from(120u64),
config: ReserveConfig {
attributed_borrow_limit: 120,
attributed_borrow_limit_open: 120,
..usdc_reserve.config
},
..usdc_reserve
Expand Down Expand Up @@ -619,7 +619,7 @@ async fn test_withdraw() {
&lending_market_owner,
&reserves[0],
ReserveConfig {
attributed_borrow_limit: 6,
attributed_borrow_limit_open: 6,
..reserves[0].account.config
},
reserves[0].account.rate_limiter.config,
Expand Down Expand Up @@ -656,7 +656,7 @@ async fn test_withdraw() {
&lending_market_owner,
&reserves[0],
ReserveConfig {
attributed_borrow_limit: 10,
attributed_borrow_limit_open: 10,
..reserves[0].account.config
},
reserves[0].account.rate_limiter.config,
Expand Down
6 changes: 4 additions & 2 deletions token-lending/program/tests/helpers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ pub fn reserve_config_no_fees() -> ReserveConfig {
protocol_take_rate: 0,
added_borrow_weight_bps: 0,
reserve_type: ReserveType::Regular,
attributed_borrow_limit: u64::MAX,
attributed_borrow_limit_open: u64::MAX,
attributed_borrow_limit_close: u64::MAX,
}
}

Expand Down Expand Up @@ -81,7 +82,8 @@ pub fn test_reserve_config() -> ReserveConfig {
protocol_take_rate: 0,
added_borrow_weight_bps: 0,
reserve_type: ReserveType::Regular,
attributed_borrow_limit: u64::MAX,
attributed_borrow_limit_open: u64::MAX,
attributed_borrow_limit_close: u64::MAX,
}
}

Expand Down
10 changes: 5 additions & 5 deletions token-lending/program/tests/helpers/solend_program_test.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use bytemuck::checked::from_bytes;
use solana_sdk::slot_history::Slot;

use solend_sdk::instruction::*;
use solend_sdk::state::*;

Expand Down Expand Up @@ -680,26 +680,26 @@ pub struct SwitchboardPriceArgs {
}

impl Info<LendingMarket> {
pub async fn mark_obligation_as_closable(
pub async fn set_obligation_closeability_status(
&self,
test: &mut SolendProgramTest,
obligation: &Info<Obligation>,
reserve: &Info<Reserve>,
risk_authority: &User,
closeable_by: Slot,
closeable: bool,
) -> Result<(), BanksClientError> {
let refresh_ixs = self
.build_refresh_instructions(test, obligation, None)
.await;
test.process_transaction(&refresh_ixs, None).await.unwrap();

let ix = vec![mark_obligation_as_closeable(
let ix = vec![set_obligation_closeability_status(
solend_program::id(),
obligation.pubkey,
reserve.pubkey,
self.pubkey,
risk_authority.keypair.pubkey(),
closeable_by,
closeable,
)];

test.process_transaction(&ix, Some(&[&risk_authority.keypair]))
Expand Down
2 changes: 1 addition & 1 deletion token-lending/program/tests/init_obligation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ async fn test_success() {
super_unhealthy_borrow_value: Decimal::zero(),
borrowing_isolated_asset: false,
updated_borrow_attribution_after_upgrade: false,
closeable_by: 0,
closeable: false,
}
);
}
Expand Down
Loading
Loading