diff --git a/.github/workflows/pull-request-token-lending.yml b/.github/workflows/pull-request-token-lending.yml index d07b8473868..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: diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 9f3babb7076..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: diff --git a/token-lending/program/src/processor.rs b/token-lending/program/src/processor.rs index a932ba65ca4..eda9beb686d 100644 --- a/token-lending/program/src/processor.rs +++ b/token-lending/program/src/processor.rs @@ -1540,13 +1540,15 @@ fn _withdraw_obligation_collateral<'a>( .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)?, - ))?; + // 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(100_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 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..769695e6d06 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,8 +2020,8 @@ 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, ) @@ -2017,7 +2030,11 @@ pub async fn custom_scenario( } } - for obligation in obligations.iter_mut() { + 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 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 7206f25006a..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(