Skip to content

Commit ed2f77c

Browse files
authored
relax staleness requirements when withdrawing with no borrows (#195)
1 parent 9ffd16d commit ed2f77c

File tree

3 files changed

+53
-9
lines changed

3 files changed

+53
-9
lines changed

token-lending/program/src/processor.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,7 @@ fn process_redeem_reserve_collateral(
785785
let clock = &Clock::get()?;
786786
let token_program_id = next_account_info(account_info_iter)?;
787787

788+
_refresh_reserve_interest(program_id, reserve_info, clock)?;
788789
_redeem_reserve_collateral(
789790
program_id,
790791
collateral_amount,
@@ -1467,6 +1468,8 @@ fn _withdraw_obligation_collateral<'a>(
14671468
}
14681469

14691470
let withdraw_reserve = Box::new(Reserve::unpack(&withdraw_reserve_info.data.borrow())?);
1471+
let mut obligation = Obligation::unpack(&obligation_info.data.borrow())?;
1472+
14701473
if withdraw_reserve_info.owner != program_id {
14711474
msg!("Withdraw reserve provided is not owned by the lending program");
14721475
return Err(LendingError::InvalidAccountOwner.into());
@@ -1483,12 +1486,11 @@ fn _withdraw_obligation_collateral<'a>(
14831486
msg!("Withdraw reserve collateral supply cannot be used as the destination collateral provided");
14841487
return Err(LendingError::InvalidAccountInput.into());
14851488
}
1486-
if withdraw_reserve.last_update.is_stale(clock.slot)? {
1489+
if withdraw_reserve.last_update.is_stale(clock.slot)? && !obligation.borrows.is_empty() {
14871490
msg!("Withdraw reserve is stale and must be refreshed in the current slot");
14881491
return Err(LendingError::ReserveStale.into());
14891492
}
14901493

1491-
let mut obligation = Obligation::unpack(&obligation_info.data.borrow())?;
14921494
if obligation_info.owner != program_id {
14931495
msg!("Obligation provided is not owned by the lending program");
14941496
return Err(LendingError::InvalidAccountOwner.into());
@@ -1505,7 +1507,7 @@ fn _withdraw_obligation_collateral<'a>(
15051507
msg!("Obligation owner provided must be a signer");
15061508
return Err(LendingError::InvalidSigner.into());
15071509
}
1508-
if obligation.last_update.is_stale(clock.slot)? {
1510+
if obligation.last_update.is_stale(clock.slot)? && !obligation.borrows.is_empty() {
15091511
msg!("Obligation is stale and must be refreshed in the current slot");
15101512
return Err(LendingError::ObligationStale.into());
15111513
}
@@ -2348,6 +2350,10 @@ fn process_withdraw_obligation_collateral_and_redeem_reserve_liquidity(
23482350
&accounts[12..],
23492351
)?;
23502352

2353+
// Needed in the case where the obligation has no borrows => user doesn't refresh anything
2354+
// if the obligation has borrows, then withdraw_obligation_collateral ensures that the
2355+
// obligation (and as a result, the reserves) were refreshed
2356+
_refresh_reserve_interest(program_id, reserve_info, clock)?;
23512357
_redeem_reserve_collateral(
23522358
program_id,
23532359
liquidity_amount,

token-lending/program/tests/helpers/solend_program_test.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -964,7 +964,7 @@ impl Info<LendingMarket> {
964964
collateral_amount: u64,
965965
) -> Result<(), BanksClientError> {
966966
let instructions = [
967-
ComputeBudgetInstruction::set_compute_unit_limit(58_000),
967+
ComputeBudgetInstruction::set_compute_unit_limit(60_000),
968968
refresh_reserve(
969969
solend_program::id(),
970970
reserve.pubkey,
@@ -1345,14 +1345,16 @@ impl Info<LendingMarket> {
13451345
) -> Result<(), BanksClientError> {
13461346
let obligation = test.load_account::<Obligation>(obligation.pubkey).await;
13471347

1348-
let refresh_ixs = self
1349-
.build_refresh_instructions(test, &obligation, None)
1350-
.await;
1351-
test.process_transaction(&refresh_ixs, None).await.unwrap();
1348+
if !obligation.account.borrows.is_empty() {
1349+
let refresh_ixs = self
1350+
.build_refresh_instructions(test, &obligation, None)
1351+
.await;
1352+
test.process_transaction(&refresh_ixs, None).await.unwrap();
1353+
}
13521354

13531355
test.process_transaction(
13541356
&[
1355-
ComputeBudgetInstruction::set_compute_unit_limit(110_000),
1357+
ComputeBudgetInstruction::set_compute_unit_limit(120_000),
13561358
withdraw_obligation_collateral_and_redeem_reserve_collateral(
13571359
solend_program::id(),
13581360
collateral_amount,

token-lending/program/tests/withdraw_obligation_collateral_and_redeem_reserve_collateral.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,3 +280,39 @@ async fn test_withdraw_max_rate_limiter() {
280280

281281
assert_eq!(balance_changes, expected_balance_changes);
282282
}
283+
284+
#[tokio::test]
285+
async fn test_withdraw_no_borrows() {
286+
let (mut test, lending_market, reserves, obligations, users, _) = custom_scenario(
287+
&[ReserveArgs {
288+
mint: usdc_mint::id(),
289+
config: test_reserve_config(),
290+
liquidity_amount: 100_000 * FRACTIONAL_TO_USDC,
291+
price: PriceArgs {
292+
price: 10,
293+
conf: 0,
294+
expo: -1,
295+
ema_price: 10,
296+
ema_conf: 1,
297+
},
298+
}],
299+
&[ObligationArgs {
300+
deposits: vec![(usdc_mint::id(), 100_000 * FRACTIONAL_TO_USDC)],
301+
borrows: vec![],
302+
}],
303+
)
304+
.await;
305+
306+
test.advance_clock_by_slots(1).await;
307+
308+
lending_market
309+
.withdraw_obligation_collateral_and_redeem_reserve_collateral(
310+
&mut test,
311+
&reserves[0],
312+
&obligations[0],
313+
&users[0],
314+
100_000 * FRACTIONAL_TO_USDC,
315+
)
316+
.await
317+
.unwrap();
318+
}

0 commit comments

Comments
 (0)