From f4285f0fb43187d0c94a0db4a0ac053005f34438 Mon Sep 17 00:00:00 2001 From: vanity Date: Tue, 22 Apr 2025 09:33:58 +0200 Subject: [PATCH 1/6] We want to support withdraws for users that only have borrows, no deposits, but are deposited in a reserve with bad oracles. In such case, we skip liveness checks. However, while calculating max outflow the math depended on a value that was set upon refresh. That caused the max outflow to be zero and blocked withdrawals. --- token-lending/program/src/processor.rs | 63 ++++++++++--------- .../program/tests/attributed_borrows.rs | 6 ++ .../tests/borrow_obligation_liquidity.rs | 1 + token-lending/program/tests/forgive_debt.rs | 4 ++ .../tests/helpers/solend_program_test.rs | 41 +++++++----- .../program/tests/isolated_tier_assets.rs | 5 ++ ...uidate_obligation_and_redeem_collateral.rs | 2 + .../tests/mark_obligation_as_closeable.rs | 2 + .../program/tests/refresh_obligation.rs | 1 + token-lending/program/tests/two_prices.rs | 3 + ...ollateral_and_redeem_reserve_collateral.rs | 42 ++++++++++++- 11 files changed, 124 insertions(+), 46 deletions(-) diff --git a/token-lending/program/src/processor.rs b/token-lending/program/src/processor.rs index acf51d8e037..8c41151db80 100644 --- a/token-lending/program/src/processor.rs +++ b/token-lending/program/src/processor.rs @@ -1534,37 +1534,38 @@ fn _withdraw_obligation_collateral<'a>( // account for lending market and reserve rate limiter when withdrawing. this is needed to // support max withdraws. - let max_outflow_collateral_amount = if account_for_rate_limiter { - let max_outflow_usd = lending_market - .rate_limiter - .clone() // remaining_outflow is a mutable call, but we don't have mutable access here - .remaining_outflow(clock.slot)?; - - let max_lending_market_outflow_liquidity_amount = withdraw_reserve - .usd_to_liquidity_amount_lower_bound(min( - max_outflow_usd, - // min here bc this function can overflow if max_outflow_usd is u64::MAX - // the actual value doesn't matter too much as long as its sensible - obligation.deposited_value.try_mul(2)?, - ))?; - - let max_reserve_outflow_liquidity_amount = withdraw_reserve - .rate_limiter - .clone() - .remaining_outflow(clock.slot)?; - - let max_outflow_liquidity_amount = min( - max_lending_market_outflow_liquidity_amount, - max_reserve_outflow_liquidity_amount, - ); + let max_outflow_collateral_amount = + if account_for_rate_limiter && !obligation.borrows.is_empty() { + let max_outflow_usd = lending_market + .rate_limiter + .clone() // remaining_outflow is a mutable call, but we don't have mutable access here + .remaining_outflow(clock.slot)?; + + let max_lending_market_outflow_liquidity_amount = withdraw_reserve + .usd_to_liquidity_amount_lower_bound(min( + max_outflow_usd, + // min here bc this function can overflow if max_outflow_usd is u64::MAX + // the actual value doesn't matter too much as long as its sensible + obligation.deposited_value.try_mul(2)?, + ))?; + + let max_reserve_outflow_liquidity_amount = withdraw_reserve + .rate_limiter + .clone() + .remaining_outflow(clock.slot)?; + + let max_outflow_liquidity_amount = min( + max_lending_market_outflow_liquidity_amount, + max_reserve_outflow_liquidity_amount, + ); - withdraw_reserve - .collateral_exchange_rate()? - .decimal_liquidity_to_collateral(max_outflow_liquidity_amount)? - .try_floor_u64()? - } else { - u64::MAX - }; + withdraw_reserve + .collateral_exchange_rate()? + .decimal_liquidity_to_collateral(max_outflow_liquidity_amount)? + .try_floor_u64()? + } else { + u64::MAX + }; let max_withdraw_amount = obligation.max_withdraw_amount(collateral, &withdraw_reserve)?; let withdraw_amount = min( @@ -2351,7 +2352,7 @@ fn process_withdraw_obligation_collateral_and_redeem_reserve_liquidity( )?; // 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 + // 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( diff --git a/token-lending/program/tests/attributed_borrows.rs b/token-lending/program/tests/attributed_borrows.rs index 6d62ccfb657..049694882e9 100644 --- a/token-lending/program/tests/attributed_borrows.rs +++ b/token-lending/program/tests/attributed_borrows.rs @@ -92,6 +92,7 @@ async fn test_refresh_obligation() { (usdc_mint::id(), 10 * FRACTIONAL_TO_USDC), (wsol_mint::id(), LAMPORTS_PER_SOL), ], + ..Default::default() }, ObligationArgs { deposits: vec![ @@ -102,6 +103,7 @@ async fn test_refresh_obligation() { (usdc_mint::id(), 100 * FRACTIONAL_TO_USDC), (wsol_mint::id(), 2 * LAMPORTS_PER_SOL), ], + ..Default::default() }, ], ) @@ -230,6 +232,7 @@ async fn test_calculations() { (usdc_mint::id(), 10 * FRACTIONAL_TO_USDC), (wsol_mint::id(), LAMPORTS_PER_SOL), ], + ..Default::default() }, ObligationArgs { deposits: vec![ @@ -240,6 +243,7 @@ async fn test_calculations() { (usdc_mint::id(), 100 * FRACTIONAL_TO_USDC), (wsol_mint::id(), 2 * LAMPORTS_PER_SOL), ], + ..Default::default() }, ], ) @@ -599,6 +603,7 @@ async fn test_withdraw() { (wsol_mint::id(), 2 * LAMPORTS_PER_SOL), ], borrows: vec![(usdc_mint::id(), 10 * FRACTIONAL_TO_USDC)], + ..Default::default() }], ) .await; @@ -796,6 +801,7 @@ async fn test_liquidate() { &[ObligationArgs { deposits: vec![(usdc_mint::id(), FRACTIONAL_TO_USDC / 2)], borrows: vec![(wsol_mint::id(), LAMPORTS_PER_SOL / 40)], + ..Default::default() }], ) .await; diff --git a/token-lending/program/tests/borrow_obligation_liquidity.rs b/token-lending/program/tests/borrow_obligation_liquidity.rs index 9f23189461b..f8284f627df 100644 --- a/token-lending/program/tests/borrow_obligation_liquidity.rs +++ b/token-lending/program/tests/borrow_obligation_liquidity.rs @@ -534,6 +534,7 @@ async fn test_borrow_max_rate_limiter() { &[ObligationArgs { deposits: vec![(usdc_mint::id(), 100 * FRACTIONAL_TO_USDC)], borrows: vec![], + ..Default::default() }], ) .await; diff --git a/token-lending/program/tests/forgive_debt.rs b/token-lending/program/tests/forgive_debt.rs index 63c5c0fb702..e439cfbc6b8 100644 --- a/token-lending/program/tests/forgive_debt.rs +++ b/token-lending/program/tests/forgive_debt.rs @@ -78,10 +78,12 @@ async fn test_forgive_debt_success_easy() { ObligationArgs { deposits: vec![(usdc_mint::id(), 20 * FRACTIONAL_TO_USDC)], borrows: vec![(wsol_mint::id(), LAMPORTS_PER_SOL)], + ..Default::default() }, ObligationArgs { deposits: vec![(wsol_mint::id(), LAMPORTS_PER_SOL)], borrows: vec![], + ..Default::default() }, ], ) @@ -272,6 +274,7 @@ async fn test_forgive_debt_fail_invalid_signer() { &[ObligationArgs { deposits: vec![(usdc_mint::id(), 200 * FRACTIONAL_TO_USDC)], borrows: vec![(wsol_mint::id(), 10 * LAMPORTS_PER_SOL)], + ..Default::default() }], ) .await; @@ -373,6 +376,7 @@ async fn test_forgive_debt_fail_no_signer() { &[ObligationArgs { deposits: vec![(usdc_mint::id(), 200 * FRACTIONAL_TO_USDC)], borrows: vec![(wsol_mint::id(), 10 * LAMPORTS_PER_SOL)], + ..Default::default() }], ) .await; diff --git a/token-lending/program/tests/helpers/solend_program_test.rs b/token-lending/program/tests/helpers/solend_program_test.rs index 83ddd5c1b12..029dcefea46 100644 --- a/token-lending/program/tests/helpers/solend_program_test.rs +++ b/token-lending/program/tests/helpers/solend_program_test.rs @@ -1875,6 +1875,17 @@ pub struct ReserveArgs { pub struct ObligationArgs { pub deposits: Vec<(Pubkey, u64)>, pub borrows: Vec<(Pubkey, u64)>, + pub should_refresh: bool, +} + +impl Default for ObligationArgs { + fn default() -> Self { + ObligationArgs { + deposits: vec![], + borrows: vec![], + should_refresh: true, + } + } } pub async fn custom_scenario( @@ -1987,17 +1998,19 @@ pub async fn custom_scenario( } } - for (i, obligation_arg) in obligation_args.iter().enumerate() { + for ((obligation, obligation_owner), obligation_arg) in obligations + .iter_mut() + .zip(obligation_owners.iter_mut()) + .zip(obligation_args.iter()) + { for (mint, amount) in obligation_arg.borrows.iter() { let reserve = reserves .iter() .find(|reserve| reserve.account.liquidity.mint_pubkey == *mint) .unwrap(); - obligation_owners[i] - .create_token_account(mint, &mut test) - .await; - obligation_owners[i] + obligation_owner.create_token_account(mint, &mut test).await; + obligation_owner .create_token_account(&reserve.account.collateral.mint_pubkey, &mut test) .await; @@ -2007,23 +2020,23 @@ pub async fn custom_scenario( .borrow_obligation_liquidity( &mut test, reserve, - &obligations[i], - &obligation_owners[i], + obligation, + obligation_owner, fee_receiver.get_account(mint), *amount, ) .await .unwrap(); } - } - for obligation in obligations.iter_mut() { - lending_market - .refresh_obligation(&mut test, obligation) - .await - .unwrap(); + if obligation_arg.should_refresh { + lending_market + .refresh_obligation(&mut test, obligation) + .await + .unwrap(); - *obligation = test.load_account::(obligation.pubkey).await; + *obligation = test.load_account::(obligation.pubkey).await; + } } // load accounts into reserve diff --git a/token-lending/program/tests/isolated_tier_assets.rs b/token-lending/program/tests/isolated_tier_assets.rs index e6615ce95cc..657b0821321 100644 --- a/token-lending/program/tests/isolated_tier_assets.rs +++ b/token-lending/program/tests/isolated_tier_assets.rs @@ -67,6 +67,7 @@ async fn test_refresh_obligation() { &[ObligationArgs { deposits: vec![(usdc_mint::id(), 100 * FRACTIONAL_TO_USDC)], borrows: vec![], + ..Default::default() }], ) .await; @@ -173,6 +174,7 @@ async fn borrow_isolated_asset() { &[ObligationArgs { deposits: vec![(usdc_mint::id(), 100 * FRACTIONAL_TO_USDC)], borrows: vec![], + ..Default::default() }], ) .await; @@ -269,6 +271,7 @@ async fn borrow_isolated_asset_invalid() { &[ObligationArgs { deposits: vec![(usdc_mint::id(), 100 * FRACTIONAL_TO_USDC)], borrows: vec![(wsol_mint::id(), 1)], + ..Default::default() }], ) .await; @@ -354,6 +357,7 @@ async fn borrow_regular_asset_invalid() { &[ObligationArgs { deposits: vec![(usdc_mint::id(), 100 * FRACTIONAL_TO_USDC)], borrows: vec![(bonk_mint::id(), 1)], + ..Default::default() }], ) .await; @@ -449,6 +453,7 @@ async fn invalid_borrow_due_to_reserve_config_change() { &[ObligationArgs { deposits: vec![(usdc_mint::id(), 100 * FRACTIONAL_TO_USDC)], borrows: vec![(bonk_mint::id(), 1), (wsol_mint::id(), LAMPORTS_PER_SOL)], + ..Default::default() }], ) .await; diff --git a/token-lending/program/tests/liquidate_obligation_and_redeem_collateral.rs b/token-lending/program/tests/liquidate_obligation_and_redeem_collateral.rs index 009fe817e8c..de6aa759d93 100644 --- a/token-lending/program/tests/liquidate_obligation_and_redeem_collateral.rs +++ b/token-lending/program/tests/liquidate_obligation_and_redeem_collateral.rs @@ -576,6 +576,7 @@ async fn test_liquidity_ordering() { (wsol_mint::id(), LAMPORTS_PER_SOL), (usdc_mint::id(), FRACTIONAL_TO_USDC), ], + ..Default::default() }], ) .await; @@ -705,6 +706,7 @@ async fn test_liquidate_closeable_obligation() { &[ObligationArgs { deposits: vec![(usdc_mint::id(), 20 * FRACTIONAL_TO_USDC)], borrows: vec![(wsol_mint::id(), LAMPORTS_PER_SOL)], + ..Default::default() }], ) .await; diff --git a/token-lending/program/tests/mark_obligation_as_closeable.rs b/token-lending/program/tests/mark_obligation_as_closeable.rs index 16b81c45ff5..23032dd4d8a 100644 --- a/token-lending/program/tests/mark_obligation_as_closeable.rs +++ b/token-lending/program/tests/mark_obligation_as_closeable.rs @@ -61,6 +61,7 @@ async fn test_mark_obligation_as_closeable_success() { &[ObligationArgs { deposits: vec![(usdc_mint::id(), 20 * FRACTIONAL_TO_USDC)], borrows: vec![(wsol_mint::id(), LAMPORTS_PER_SOL)], + ..Default::default() }], ) .await; @@ -176,6 +177,7 @@ async fn invalid_signer() { &[ObligationArgs { deposits: vec![(usdc_mint::id(), 20 * FRACTIONAL_TO_USDC)], borrows: vec![(wsol_mint::id(), LAMPORTS_PER_SOL)], + ..Default::default() }], ) .await; diff --git a/token-lending/program/tests/refresh_obligation.rs b/token-lending/program/tests/refresh_obligation.rs index b16a0f3a519..80258ef0cbb 100644 --- a/token-lending/program/tests/refresh_obligation.rs +++ b/token-lending/program/tests/refresh_obligation.rs @@ -397,6 +397,7 @@ async fn test_obligation_liquidity_ordering() { (usdc_mint::id(), 1), (bonk_mint::id(), 1), ], + ..Default::default() }], ) .await; diff --git a/token-lending/program/tests/two_prices.rs b/token-lending/program/tests/two_prices.rs index 463b562fb06..2334807d2ff 100644 --- a/token-lending/program/tests/two_prices.rs +++ b/token-lending/program/tests/two_prices.rs @@ -66,6 +66,7 @@ async fn test_borrow() { &[ObligationArgs { deposits: vec![(usdc_mint::id(), 100 * FRACTIONAL_TO_USDC)], borrows: vec![(wsol_mint::id(), LAMPORTS_PER_SOL)], + ..Default::default() }], ) .await; @@ -204,6 +205,7 @@ async fn test_withdraw() { (usdt_mint::id(), 20 * FRACTIONAL_TO_USDC), ], borrows: vec![(wsol_mint::id(), LAMPORTS_PER_SOL)], + ..Default::default() }], ) .await; @@ -340,6 +342,7 @@ async fn test_liquidation_doesnt_use_smoothed_price() { &[ObligationArgs { deposits: vec![(usdc_mint::id(), 100 * FRACTIONAL_TO_USDC)], borrows: vec![(wsol_mint::id(), LAMPORTS_PER_SOL)], + ..Default::default() }], ) .await; 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 14b6b3026c1..7b516104953 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 @@ -191,6 +191,7 @@ async fn test_withdraw_max_rate_limiter() { &[ObligationArgs { deposits: vec![(wsol_mint::id(), 50 * LAMPORTS_PER_SOL)], borrows: vec![], + ..Default::default() }], ) .await; @@ -299,11 +300,50 @@ async fn test_withdraw_no_borrows() { &[ObligationArgs { deposits: vec![(usdc_mint::id(), 100_000 * FRACTIONAL_TO_USDC)], borrows: vec![], + ..Default::default() }], ) .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(); +} + +/// If someone creates an obligation, deposits collateral, then they should be +/// able to withdraw that collateral without ever refreshing the obligation. +#[tokio::test] +async fn test_withdraw_no_borrows_no_refresh() { + 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![], + should_refresh: false, + }], + ) + .await; + + test.advance_clock_by_slots(100).await; lending_market .withdraw_obligation_collateral_and_redeem_reserve_collateral( @@ -315,4 +355,4 @@ async fn test_withdraw_no_borrows() { ) .await .unwrap(); -} \ No newline at end of file +} From 0a3d39fd316f2a4f7a51b1a6e892cc2003bbfa38 Mon Sep 17 00:00:00 2001 From: vanity Date: Tue, 22 Apr 2025 09:47:11 +0200 Subject: [PATCH 2/6] Fixing outdated CI --- .github/workflows/pull-request-token-lending.yml | 14 +++++++------- .github/workflows/pull-request.yml | 14 +++++++------- ci/install-build-deps.sh | 1 - 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/.github/workflows/pull-request-token-lending.yml b/.github/workflows/pull-request-token-lending.yml index f1c1c1ff844..5e88e428059 100644 --- a/.github/workflows/pull-request-token-lending.yml +++ b/.github/workflows/pull-request-token-lending.yml @@ -3,13 +3,13 @@ name: Token Lending Pull Request on: pull_request: paths: - - 'token-lending/**' - - 'token/**' + - "token-lending/**" + - "token/**" push: branches: [master] paths: - - 'token-lending/**' - - 'token/**' + - "token-lending/**" + - "token/**" jobs: cargo-test-bpf: @@ -30,20 +30,20 @@ jobs: override: true profile: minimal - - uses: actions/cache@v2 + - uses: actions/cache@v4 with: path: | ~/.cargo/registry ~/.cargo/git key: cargo-build-${{ hashFiles('**/Cargo.lock') }}-${{ env.RUST_STABLE}} - - uses: actions/cache@v2 + - uses: actions/cache@v4 with: path: | ~/.cargo/bin/rustfilt key: cargo-bpf-bins-${{ runner.os }} - - uses: actions/cache@v2 + - uses: actions/cache@v4 with: path: | ~/.cache diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 2221f07590c..badf172312b 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -3,11 +3,11 @@ name: Pull Request on: pull_request: paths-ignore: - - 'docs/**' + - "docs/**" push: branches: [master, upcoming] paths-ignore: - - 'docs/**' + - "docs/**" jobs: all_github_action_checks: @@ -59,7 +59,7 @@ jobs: profile: minimal components: clippy - - uses: actions/cache@v2 + - uses: actions/cache@v4 with: path: | ~/.cargo/registry @@ -96,7 +96,7 @@ jobs: override: true profile: minimal - - uses: actions/cache@v2 + - uses: actions/cache@v4 with: path: | ~/.cargo/registry @@ -104,13 +104,13 @@ jobs: # target # Removed due to build dependency caching conflicts key: cargo-build-${{ hashFiles('**/Cargo.lock') }}-${{ env.RUST_STABLE}} - - uses: actions/cache@v2 + - uses: actions/cache@v4 with: path: | ~/.cargo/bin/rustfilt key: cargo-bpf-bins-${{ runner.os }} - - uses: actions/cache@v2 + - uses: actions/cache@v4 with: path: | ~/.cache @@ -143,7 +143,7 @@ jobs: override: true profile: minimal - - uses: actions/cache@v2 + - uses: actions/cache@v4 with: path: | ~/.cargo/registry diff --git a/ci/install-build-deps.sh b/ci/install-build-deps.sh index cd9c815df08..3962591173a 100755 --- a/ci/install-build-deps.sh +++ b/ci/install-build-deps.sh @@ -7,7 +7,6 @@ sudo apt-add-repository "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-1 sudo apt-get update sudo apt-get install -y openssl --allow-unauthenticated sudo apt-get install -y libssl-dev --allow-unauthenticated -sudo apt-get install -y libssl1.1 --allow-unauthenticated sudo apt-get install -y libudev-dev sudo apt-get install -y binutils-dev sudo apt-get install -y libunwind-dev From 8ed0fdc902efe7471ac7bbd33b45c753c842870c Mon Sep 17 00:00:00 2001 From: vanity Date: Sun, 4 May 2025 11:28:20 +0200 Subject: [PATCH 3/6] Fixing the calculation so that it still works as intended for other tests --- token-lending/program/src/processor.rs | 62 +++++++++---------- .../tests/helpers/solend_program_test.rs | 18 +++--- 2 files changed, 42 insertions(+), 38 deletions(-) diff --git a/token-lending/program/src/processor.rs b/token-lending/program/src/processor.rs index 8c41151db80..8ad380d217d 100644 --- a/token-lending/program/src/processor.rs +++ b/token-lending/program/src/processor.rs @@ -1534,39 +1534,39 @@ fn _withdraw_obligation_collateral<'a>( // account for lending market and reserve rate limiter when withdrawing. this is needed to // support max withdraws. - let max_outflow_collateral_amount = - if account_for_rate_limiter && !obligation.borrows.is_empty() { - let max_outflow_usd = lending_market - .rate_limiter - .clone() // remaining_outflow is a mutable call, but we don't have mutable access here - .remaining_outflow(clock.slot)?; - - let max_lending_market_outflow_liquidity_amount = withdraw_reserve - .usd_to_liquidity_amount_lower_bound(min( - max_outflow_usd, - // min here bc this function can overflow if max_outflow_usd is u64::MAX - // the actual value doesn't matter too much as long as its sensible - obligation.deposited_value.try_mul(2)?, - ))?; - - let max_reserve_outflow_liquidity_amount = withdraw_reserve - .rate_limiter - .clone() - .remaining_outflow(clock.slot)?; - - let max_outflow_liquidity_amount = min( - max_lending_market_outflow_liquidity_amount, - max_reserve_outflow_liquidity_amount, - ); + let max_outflow_collateral_amount = if account_for_rate_limiter { + let max_reserve_outflow_liquidity_amount = withdraw_reserve + .rate_limiter + .clone() + .remaining_outflow(clock.slot)?; - withdraw_reserve - .collateral_exchange_rate()? - .decimal_liquidity_to_collateral(max_outflow_liquidity_amount)? - .try_floor_u64()? - } else { - u64::MAX - }; + let max_outflow_usd = lending_market + .rate_limiter + .clone() // remaining_outflow is a mutable call, but we don't have mutable access here + .remaining_outflow(clock.slot)?; + + // Because the max_outflow_usd can be an large number we can get an overflow error. + // However, we don't actually care about large values because we're taking `min`. + // So just default to `max_reserve_outflow_liquidity_amount` if we overflow. + let max_lending_market_outflow_liquidity_amount = withdraw_reserve + .usd_to_liquidity_amount_lower_bound(max_outflow_usd) + .unwrap_or(max_reserve_outflow_liquidity_amount); + + let max_outflow_liquidity_amount = min( + max_lending_market_outflow_liquidity_amount, + max_reserve_outflow_liquidity_amount, + ); + + withdraw_reserve + .collateral_exchange_rate()? + .decimal_liquidity_to_collateral(max_outflow_liquidity_amount)? + .try_floor_u64() + .unwrap_or(u64::MAX) + } else { + u64::MAX + }; + msg!("G"); let max_withdraw_amount = obligation.max_withdraw_amount(collateral, &withdraw_reserve)?; let withdraw_amount = min( collateral_amount, diff --git a/token-lending/program/tests/helpers/solend_program_test.rs b/token-lending/program/tests/helpers/solend_program_test.rs index 029dcefea46..769695e6d06 100644 --- a/token-lending/program/tests/helpers/solend_program_test.rs +++ b/token-lending/program/tests/helpers/solend_program_test.rs @@ -2028,15 +2028,19 @@ pub async fn custom_scenario( .await .unwrap(); } + } - if obligation_arg.should_refresh { - lending_market - .refresh_obligation(&mut test, obligation) - .await - .unwrap(); + for obligation in obligations + .iter_mut() + .zip(obligation_args.iter()) + .filter_map(|(obligation, arg)| arg.should_refresh.then_some(obligation)) + { + lending_market + .refresh_obligation(&mut test, obligation) + .await + .unwrap(); - *obligation = test.load_account::(obligation.pubkey).await; - } + *obligation = test.load_account::(obligation.pubkey).await; } // load accounts into reserve From aa9b02475cb2ec10396e2d2b55a2250e4d4aba06 Mon Sep 17 00:00:00 2001 From: vanity Date: Sun, 4 May 2025 11:29:04 +0200 Subject: [PATCH 4/6] Remove forgotten msg --- token-lending/program/src/processor.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/token-lending/program/src/processor.rs b/token-lending/program/src/processor.rs index 8ad380d217d..65304b79463 100644 --- a/token-lending/program/src/processor.rs +++ b/token-lending/program/src/processor.rs @@ -1566,7 +1566,6 @@ fn _withdraw_obligation_collateral<'a>( u64::MAX }; - msg!("G"); let max_withdraw_amount = obligation.max_withdraw_amount(collateral, &withdraw_reserve)?; let withdraw_amount = min( collateral_amount, From 29ae1c38743d546dfcd07e38d90f3a4b6779b5aa Mon Sep 17 00:00:00 2001 From: vanity Date: Thu, 15 May 2025 12:58:26 +0200 Subject: [PATCH 5/6] Reverting to previous logic but with a better static value --- token-lending/program/src/processor.rs | 28 ++++++++++++++------------ 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/token-lending/program/src/processor.rs b/token-lending/program/src/processor.rs index 65304b79463..529735615c1 100644 --- a/token-lending/program/src/processor.rs +++ b/token-lending/program/src/processor.rs @@ -1535,22 +1535,25 @@ fn _withdraw_obligation_collateral<'a>( // account for lending market and reserve rate limiter when withdrawing. this is needed to // support max withdraws. let max_outflow_collateral_amount = if account_for_rate_limiter { - let max_reserve_outflow_liquidity_amount = withdraw_reserve - .rate_limiter - .clone() - .remaining_outflow(clock.slot)?; - let max_outflow_usd = lending_market .rate_limiter .clone() // remaining_outflow is a mutable call, but we don't have mutable access here .remaining_outflow(clock.slot)?; - // Because the max_outflow_usd can be an large number we can get an overflow error. - // However, we don't actually care about large values because we're taking `min`. - // So just default to `max_reserve_outflow_liquidity_amount` if we overflow. - let max_lending_market_outflow_liquidity_amount = withdraw_reserve - .usd_to_liquidity_amount_lower_bound(max_outflow_usd) - .unwrap_or(max_reserve_outflow_liquidity_amount); + // min here bc this function can overflow if max_outflow_usd is u64::MAX + // the actual value doesn't matter too much as long as its sensible + let max_outflow_usd_capped = min( + max_outflow_usd, + Decimal::from(10_000_000_000_000u64), // enough USD to cover all requests + ); + + let max_lending_market_outflow_liquidity_amount = + withdraw_reserve.usd_to_liquidity_amount_lower_bound(max_outflow_usd_capped)?; + + let max_reserve_outflow_liquidity_amount = withdraw_reserve + .rate_limiter + .clone() + .remaining_outflow(clock.slot)?; let max_outflow_liquidity_amount = min( max_lending_market_outflow_liquidity_amount, @@ -1560,8 +1563,7 @@ fn _withdraw_obligation_collateral<'a>( withdraw_reserve .collateral_exchange_rate()? .decimal_liquidity_to_collateral(max_outflow_liquidity_amount)? - .try_floor_u64() - .unwrap_or(u64::MAX) + .try_floor_u64()? } else { u64::MAX }; From 2ecd02c83a47afd569b4cd21467dc55f88bf0d75 Mon Sep 17 00:00:00 2001 From: vanity Date: Thu, 15 May 2025 12:59:31 +0200 Subject: [PATCH 6/6] Reverting to previous logic but with a better static value --- token-lending/program/src/processor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/token-lending/program/src/processor.rs b/token-lending/program/src/processor.rs index 529735615c1..eda9beb686d 100644 --- a/token-lending/program/src/processor.rs +++ b/token-lending/program/src/processor.rs @@ -1544,7 +1544,7 @@ fn _withdraw_obligation_collateral<'a>( // the actual value doesn't matter too much as long as its sensible let max_outflow_usd_capped = min( max_outflow_usd, - Decimal::from(10_000_000_000_000u64), // enough USD to cover all requests + Decimal::from(100_000_000_000u64), // enough USD to cover all requests ); let max_lending_market_outflow_liquidity_amount =