From 26f4f3bb13f5ab95ff9aba6e043873248a3ad7e5 Mon Sep 17 00:00:00 2001 From: 0xripleys <0xripleys@solend.fi> Date: Thu, 24 Oct 2024 13:49:08 -0400 Subject: [PATCH] relax staleness requirements when withdrawing with no borrows --- token-lending/program/src/processor.rs | 12 +++++-- .../tests/helpers/solend_program_test.rs | 14 ++++---- ...ollateral_and_redeem_reserve_collateral.rs | 36 +++++++++++++++++++ 3 files changed, 53 insertions(+), 9 deletions(-) diff --git a/token-lending/program/src/processor.rs b/token-lending/program/src/processor.rs index b92aea55911..acf51d8e037 100644 --- a/token-lending/program/src/processor.rs +++ b/token-lending/program/src/processor.rs @@ -785,6 +785,7 @@ fn process_redeem_reserve_collateral( let clock = &Clock::get()?; let token_program_id = next_account_info(account_info_iter)?; + _refresh_reserve_interest(program_id, reserve_info, clock)?; _redeem_reserve_collateral( program_id, collateral_amount, @@ -1467,6 +1468,8 @@ fn _withdraw_obligation_collateral<'a>( } let withdraw_reserve = Box::new(Reserve::unpack(&withdraw_reserve_info.data.borrow())?); + let mut obligation = Obligation::unpack(&obligation_info.data.borrow())?; + if withdraw_reserve_info.owner != program_id { msg!("Withdraw reserve provided is not owned by the lending program"); return Err(LendingError::InvalidAccountOwner.into()); @@ -1483,12 +1486,11 @@ fn _withdraw_obligation_collateral<'a>( msg!("Withdraw reserve collateral supply cannot be used as the destination collateral provided"); return Err(LendingError::InvalidAccountInput.into()); } - if withdraw_reserve.last_update.is_stale(clock.slot)? { + if withdraw_reserve.last_update.is_stale(clock.slot)? && !obligation.borrows.is_empty() { msg!("Withdraw reserve is stale and must be refreshed in the current slot"); return Err(LendingError::ReserveStale.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()); @@ -1505,7 +1507,7 @@ fn _withdraw_obligation_collateral<'a>( msg!("Obligation owner provided must be a signer"); return Err(LendingError::InvalidSigner.into()); } - if obligation.last_update.is_stale(clock.slot)? { + if obligation.last_update.is_stale(clock.slot)? && !obligation.borrows.is_empty() { msg!("Obligation is stale and must be refreshed in the current slot"); return Err(LendingError::ObligationStale.into()); } @@ -2348,6 +2350,10 @@ fn process_withdraw_obligation_collateral_and_redeem_reserve_liquidity( &accounts[12..], )?; + // Needed in the case where the obligation has no borrows => user doesn't refresh anything + // if the obligation has borrows, then withdraw_obligation_collateral ensures that the + // obligation (and as a result, the reserves) were refreshed + _refresh_reserve_interest(program_id, reserve_info, clock)?; _redeem_reserve_collateral( program_id, liquidity_amount, diff --git a/token-lending/program/tests/helpers/solend_program_test.rs b/token-lending/program/tests/helpers/solend_program_test.rs index 05e1705d350..83ddd5c1b12 100644 --- a/token-lending/program/tests/helpers/solend_program_test.rs +++ b/token-lending/program/tests/helpers/solend_program_test.rs @@ -964,7 +964,7 @@ impl Info { collateral_amount: u64, ) -> Result<(), BanksClientError> { let instructions = [ - ComputeBudgetInstruction::set_compute_unit_limit(58_000), + ComputeBudgetInstruction::set_compute_unit_limit(60_000), refresh_reserve( solend_program::id(), reserve.pubkey, @@ -1345,14 +1345,16 @@ impl Info { ) -> Result<(), BanksClientError> { let obligation = test.load_account::(obligation.pubkey).await; - let refresh_ixs = self - .build_refresh_instructions(test, &obligation, None) - .await; - test.process_transaction(&refresh_ixs, None).await.unwrap(); + if !obligation.account.borrows.is_empty() { + let refresh_ixs = self + .build_refresh_instructions(test, &obligation, None) + .await; + test.process_transaction(&refresh_ixs, None).await.unwrap(); + } test.process_transaction( &[ - ComputeBudgetInstruction::set_compute_unit_limit(110_000), + ComputeBudgetInstruction::set_compute_unit_limit(120_000), withdraw_obligation_collateral_and_redeem_reserve_collateral( solend_program::id(), collateral_amount, diff --git a/token-lending/program/tests/withdraw_obligation_collateral_and_redeem_reserve_collateral.rs b/token-lending/program/tests/withdraw_obligation_collateral_and_redeem_reserve_collateral.rs index 8ce586d8b14..14b6b3026c1 100644 --- a/token-lending/program/tests/withdraw_obligation_collateral_and_redeem_reserve_collateral.rs +++ b/token-lending/program/tests/withdraw_obligation_collateral_and_redeem_reserve_collateral.rs @@ -280,3 +280,39 @@ async fn test_withdraw_max_rate_limiter() { assert_eq!(balance_changes, expected_balance_changes); } + +#[tokio::test] +async fn test_withdraw_no_borrows() { + let (mut test, lending_market, reserves, obligations, users, _) = custom_scenario( + &[ReserveArgs { + mint: usdc_mint::id(), + config: test_reserve_config(), + liquidity_amount: 100_000 * FRACTIONAL_TO_USDC, + price: PriceArgs { + price: 10, + conf: 0, + expo: -1, + ema_price: 10, + ema_conf: 1, + }, + }], + &[ObligationArgs { + deposits: vec![(usdc_mint::id(), 100_000 * FRACTIONAL_TO_USDC)], + borrows: vec![], + }], + ) + .await; + + test.advance_clock_by_slots(1).await; + + lending_market + .withdraw_obligation_collateral_and_redeem_reserve_collateral( + &mut test, + &reserves[0], + &obligations[0], + &users[0], + 100_000 * FRACTIONAL_TO_USDC, + ) + .await + .unwrap(); +} \ No newline at end of file