Skip to content

Commit

Permalink
Release 1.10.0 (#28)
Browse files Browse the repository at this point in the history
- Introduce V2 instructions to avoid transaction introspection.
- Remove global unhealthy borrow value.
- Block certain operations on obligations marked for deleveraging
- Audit: fixes on repay and withdraw
- Introduce deposit and withdraw ix
- Mutable accounts clean-up
  • Loading branch information
oeble authored Feb 21, 2025
1 parent f59fc93 commit 796682e
Show file tree
Hide file tree
Showing 33 changed files with 1,540 additions and 586 deletions.
93 changes: 64 additions & 29 deletions programs/klend/src/handlers/handler_borrow_obligation_liquidity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,72 @@ use anchor_lang::{
use anchor_spl::token_interface::{self, Mint, TokenAccount, TokenInterface};
use lending_checks::validate_referrer_token_state;

use super::handler_refresh_obligation_farms_for_reserve::*;
use crate::{
check_refresh_ixs, gen_signer_seeds,
lending_market::{lending_checks, lending_operations},
refresh_farms,
state::{obligation::Obligation, CalculateBorrowResult, LendingMarket, Reserve},
utils::{seeds, token_transfer, FatAccountLoader},
xmsg, LendingAction, LendingError, ReferrerTokenState, ReserveFarmKind,
};

pub fn process<'info>(
pub fn process_v1<'info>(
ctx: Context<'_, '_, '_, 'info, BorrowObligationLiquidity<'info>>,
liquidity_amount: u64,
) -> Result<()> {
msg!("liquidity_amount {}", liquidity_amount);
check_refresh_ixs!(
ctx.accounts,
ctx.accounts.borrow_reserve,
ReserveFarmKind::Debt
);
lending_checks::borrow_obligation_liquidity_checks(&ctx)?;
process_impl(ctx.accounts, ctx.remaining_accounts, liquidity_amount)
}

pub fn process_v2<'info>(
ctx: Context<'_, '_, '_, 'info, BorrowObligationLiquidityV2<'info>>,
liquidity_amount: u64,
) -> Result<()> {
process_impl(
&ctx.accounts.borrow_accounts,
ctx.remaining_accounts,
liquidity_amount,
)?;
refresh_farms!(
ctx.accounts.borrow_accounts,
[(
ctx.accounts.borrow_accounts.borrow_reserve,
ctx.accounts.farms_accounts,
Debt,
)],
);
Ok(())
}

let borrow_reserve = &mut ctx.accounts.borrow_reserve.load_mut()?;
let lending_market = &ctx.accounts.lending_market.load()?;
let obligation = &mut ctx.accounts.obligation.load_mut()?;
let lending_market_key = ctx.accounts.lending_market.key();
fn process_impl<'info>(
accounts: &BorrowObligationLiquidity<'info>,
remaining_accounts: &[AccountInfo<'info>],
liquidity_amount: u64,
) -> Result<()> {
msg!("liquidity_amount {}", liquidity_amount);
lending_checks::borrow_obligation_liquidity_checks(accounts)?;

let borrow_reserve = &mut accounts.borrow_reserve.load_mut()?;
let lending_market = &accounts.lending_market.load()?;
let obligation = &mut accounts.obligation.load_mut()?;
let lending_market_key = accounts.lending_market.key();
let clock = &Clock::get()?;

let authority_signer_seeds =
gen_signer_seeds!(lending_market_key.as_ref(), lending_market.bump_seed as u8);

let deposit_reserves_iter = ctx
.remaining_accounts
let deposit_reserves_iter = remaining_accounts
.iter()
.map(|account_info| FatAccountLoader::<Reserve>::try_from(account_info).unwrap());

let referrer_token_state_option: Option<RefMut<ReferrerTokenState>> =
if obligation.has_referrer() {
match &ctx.accounts.referrer_token_state {
match &accounts.referrer_token_state {
Some(referrer_token_state_loader) => {
let referrer_token_state = referrer_token_state_loader.load_mut()?;

Expand All @@ -54,7 +83,7 @@ pub fn process<'info>(
referrer_token_state_loader.key(),
borrow_reserve.liquidity.mint_pubkey,
obligation.referrer,
ctx.accounts.borrow_reserve.key(),
accounts.borrow_reserve.key(),
)?;

Some(referrer_token_state)
Expand All @@ -65,9 +94,8 @@ pub fn process<'info>(
None
};

let initial_reserve_token_balance = token_interface::accessor::amount(
&ctx.accounts.reserve_source_liquidity.to_account_info(),
)?;
let initial_reserve_token_balance =
token_interface::accessor::amount(&accounts.reserve_source_liquidity.to_account_info())?;
let initial_reserve_available_liquidity = borrow_reserve.liquidity.available_amount;

let CalculateBorrowResult {
Expand All @@ -80,7 +108,7 @@ pub fn process<'info>(
obligation,
liquidity_amount,
clock,
ctx.accounts.borrow_reserve.key(),
accounts.borrow_reserve.key(),
referrer_token_state_option,
deposit_reserves_iter,
)?;
Expand All @@ -89,32 +117,32 @@ pub fn process<'info>(

if borrow_fee > 0 {
token_transfer::send_origination_fees_transfer(
ctx.accounts.token_program.to_account_info(),
ctx.accounts.borrow_reserve_liquidity_mint.to_account_info(),
ctx.accounts.reserve_source_liquidity.to_account_info(),
ctx.accounts
accounts.token_program.to_account_info(),
accounts.borrow_reserve_liquidity_mint.to_account_info(),
accounts.reserve_source_liquidity.to_account_info(),
accounts
.borrow_reserve_liquidity_fee_receiver
.to_account_info(),
ctx.accounts.lending_market_authority.to_account_info(),
accounts.lending_market_authority.to_account_info(),
authority_signer_seeds,
borrow_fee,
ctx.accounts.borrow_reserve_liquidity_mint.decimals,
accounts.borrow_reserve_liquidity_mint.decimals,
)?;
}

token_transfer::borrow_obligation_liquidity_transfer(
ctx.accounts.token_program.to_account_info(),
ctx.accounts.borrow_reserve_liquidity_mint.to_account_info(),
ctx.accounts.reserve_source_liquidity.to_account_info(),
ctx.accounts.user_destination_liquidity.to_account_info(),
ctx.accounts.lending_market_authority.to_account_info(),
accounts.token_program.to_account_info(),
accounts.borrow_reserve_liquidity_mint.to_account_info(),
accounts.reserve_source_liquidity.to_account_info(),
accounts.user_destination_liquidity.to_account_info(),
accounts.lending_market_authority.to_account_info(),
authority_signer_seeds,
receive_amount,
ctx.accounts.borrow_reserve_liquidity_mint.decimals,
accounts.borrow_reserve_liquidity_mint.decimals,
)?;

lending_checks::post_transfer_vault_balance_liquidity_reserve_checks(
token_interface::accessor::amount(&ctx.accounts.reserve_source_liquidity.to_account_info())
token_interface::accessor::amount(&accounts.reserve_source_liquidity.to_account_info())
.unwrap(),
borrow_reserve.liquidity.available_amount,
initial_reserve_token_balance,
Expand Down Expand Up @@ -147,7 +175,7 @@ pub struct BorrowObligationLiquidity<'info> {
)]
pub borrow_reserve: AccountLoader<'info, Reserve>,

#[account(mut,
#[account(
address = borrow_reserve.load()?.liquidity.mint_pubkey,
mint::token_program = token_program,
)]
Expand Down Expand Up @@ -177,3 +205,10 @@ pub struct BorrowObligationLiquidity<'info> {
#[account(address = SysInstructions::id())]
pub instruction_sysvar_account: AccountInfo<'info>,
}

#[derive(Accounts)]
pub struct BorrowObligationLiquidityV2<'info> {
pub borrow_accounts: BorrowObligationLiquidity<'info>,
pub farms_accounts: OptionalObligationFarmsAccounts<'info>,
pub farms_program: Program<'info, farms::program::Farms>,
}
174 changes: 174 additions & 0 deletions programs/klend/src/handlers/handler_deposit_and_withdraw.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
use anchor_lang::{prelude::*, Accounts};

use crate::{
handler_deposit_reserve_liquidity_and_obligation_collateral::{self, *},
handler_refresh_obligation,
handler_refresh_obligation_farms_for_reserve::*,
handler_withdraw_obligation_collateral_and_redeem_reserve_collateral::{self, *},
lending_market::lending_operations,
refresh_farms, LendingError, LtvMaxWithdrawalCheck, MaxReservesAsCollateralCheck,
RefreshObligation, RefreshObligationBumps, ReserveFarmKind,
};

pub fn process(
ctx: Context<DepositAndWithdraw>,
liquidity_amount: u64,
withdraw_collateral_amount: u64,
) -> Result<()> {
let initial_ltv = {
let obligation = ctx.accounts.deposit_accounts.obligation.load()?;
obligation.loan_to_value()
};

{
handler_deposit_reserve_liquidity_and_obligation_collateral::process_impl(
&ctx.accounts.deposit_accounts,
liquidity_amount,
MaxReservesAsCollateralCheck::Skip,
)?;
}

{
let clock = Clock::get()?;

let lending_market = ctx.accounts.deposit_accounts.lending_market.load()?;
let mut reserve = ctx.accounts.deposit_accounts.reserve.load_mut()?;
lending_operations::refresh_reserve(
&mut reserve,
&clock,
None,
lending_market.referral_fee_bps,
)?;
let timestamp = u64::try_from(clock.unix_timestamp).unwrap();
lending_operations::refresh_reserve_limit_timestamps(&mut reserve, timestamp);
}

{
let refresh_obligation_ctx = Context {
program_id: ctx.program_id,
accounts: &mut RefreshObligation {
obligation: ctx.accounts.deposit_accounts.obligation.clone(),
lending_market: ctx.accounts.deposit_accounts.lending_market.clone(),
},
remaining_accounts: ctx.remaining_accounts,
bumps: RefreshObligationBumps {},
};

handler_refresh_obligation::process(
refresh_obligation_ctx,
MaxReservesAsCollateralCheck::Skip,
)?;
}

let is_obligation_closed = {
handler_withdraw_obligation_collateral_and_redeem_reserve_collateral::process_impl(
&ctx.accounts.withdraw_accounts,
withdraw_collateral_amount,
LtvMaxWithdrawalCheck::LiquidationThreshold,
)?
};

{
let clock = Clock::get()?;

let lending_market = ctx.accounts.withdraw_accounts.lending_market.load()?;
let mut reserve = ctx.accounts.withdraw_accounts.withdraw_reserve.load_mut()?;
lending_operations::refresh_reserve(
&mut reserve,
&clock,
None,
lending_market.referral_fee_bps,
)?;
let timestamp = u64::try_from(clock.unix_timestamp).unwrap();
lending_operations::refresh_reserve_limit_timestamps(&mut reserve, timestamp);
}

if !is_obligation_closed {
let is_full_withdrawal = {
let obligation = ctx.accounts.withdraw_accounts.obligation.load()?;
let final_deposit_amount = obligation
.find_collateral_in_deposits(ctx.accounts.withdraw_accounts.withdraw_reserve.key())
.map_or(0, |collateral| collateral.deposited_amount);
final_deposit_amount == 0
};

let remaining_accounts: Vec<AccountInfo> = if is_full_withdrawal {
let mut withdraw_reserve_found = false;
ctx.remaining_accounts
.iter()
.filter_map(|account| {
if account.key() == ctx.accounts.withdraw_accounts.withdraw_reserve.key()
&& !withdraw_reserve_found
{
withdraw_reserve_found = true;
None
} else {
Some(account.clone())
}
})
.collect()
} else {
ctx.remaining_accounts.to_vec()
};

let refresh_obligation_ctx = Context {
program_id: ctx.program_id,
accounts: &mut RefreshObligation {
obligation: ctx.accounts.deposit_accounts.obligation.clone(),
lending_market: ctx.accounts.deposit_accounts.lending_market.clone(),
},
remaining_accounts: remaining_accounts.as_slice(),
bumps: RefreshObligationBumps {},
};

handler_refresh_obligation::process(
refresh_obligation_ctx,
MaxReservesAsCollateralCheck::Perform,
)?;

let mut obligation = ctx.accounts.withdraw_accounts.obligation.load_mut()?;
obligation.last_update.mark_stale();

let withdraw_reserve = ctx.accounts.withdraw_accounts.withdraw_reserve.load()?;
let lending_market = ctx.accounts.withdraw_accounts.lending_market.load()?;

lending_operations::utils::post_deposit_and_withdraw_obligation_enforcements(
&obligation,
&withdraw_reserve,
&lending_market,
initial_ltv,
)?;
}

refresh_farms!(
ctx.accounts.deposit_accounts,
[
(
ctx.accounts.deposit_accounts.reserve,
ctx.accounts.deposit_farms_accounts,
Collateral,
),
(
ctx.accounts.withdraw_accounts.withdraw_reserve,
ctx.accounts.withdraw_farms_accounts,
Collateral,
),
],
);

Ok(())
}

#[derive(Accounts)]
pub struct DepositAndWithdraw<'info> {
#[account(
constraint = deposit_accounts.owner.key() == withdraw_accounts.owner.key() @ LendingError::ObligationOwnersMustMatch,
constraint = deposit_accounts.obligation.key() == withdraw_accounts.obligation.key() @ LendingError::ObligationsMustMatch,
constraint = deposit_accounts.lending_market.key() == withdraw_accounts.lending_market.key() @ LendingError::LendingMarketsMustMatch,
)]
pub deposit_accounts: DepositReserveLiquidityAndObligationCollateral<'info>,
pub withdraw_accounts: WithdrawObligationCollateralAndRedeemReserveCollateral<'info>,
pub deposit_farms_accounts: OptionalObligationFarmsAccounts<'info>,
pub withdraw_farms_accounts: OptionalObligationFarmsAccounts<'info>,
pub farms_program: Program<'info, farms::program::Farms>,
}
Loading

0 comments on commit 796682e

Please sign in to comment.