diff --git a/token-lending/program/src/processor.rs b/token-lending/program/src/processor.rs index e64f223322f..62eec563e6e 100644 --- a/token-lending/program/src/processor.rs +++ b/token-lending/program/src/processor.rs @@ -34,7 +34,7 @@ use solana_program::{ }; use solend_sdk::state::{LendingMarketMetadata, RateLimiter, RateLimiterConfig, ReserveType}; use solend_sdk::{switchboard_v2_devnet, switchboard_v2_mainnet}; -use spl_token::state::Mint; +use spl_token::state::{Account, Mint}; use std::{cmp::min, result::Result}; use switchboard_program::{ get_aggregator, get_aggregator_result, AggregatorState, RoundResult, SwitchboardAccountType, @@ -660,6 +660,19 @@ fn _deposit_reserve_liquidity<'a>( return Err(LendingError::InvalidMarketAuthority.into()); } + let liquidity_amount = if liquidity_amount == u64::MAX { + let user_token_balance = Account::unpack(&source_liquidity_info.data.borrow()) + .map_err(|_| { + msg!("Failed to deserialize user token account"); + LendingError::InvalidAccountInput + })? + .amount; + + min(liquidity_amount, user_token_balance) + } else { + liquidity_amount + }; + if Decimal::from(liquidity_amount) .try_add(reserve.liquidity.total_supply()?)? .try_floor_u64()? @@ -1192,6 +1205,19 @@ fn _deposit_obligation_collateral<'a>( return Err(LendingError::InvalidSigner.into()); } + let collateral_amount = if collateral_amount == u64::MAX { + let user_token_balance = Account::unpack(&source_collateral_info.data.borrow()) + .map_err(|_| { + msg!("Failed to deserialize user token account"); + LendingError::InvalidAccountInput + })? + .amount; + + min(collateral_amount, user_token_balance) + } else { + collateral_amount + }; + obligation .find_or_add_collateral_to_deposits(*deposit_reserve_info.key)? .deposit(collateral_amount)?; @@ -1806,6 +1832,19 @@ fn process_repay_obligation_liquidity( // refreshing specific borrow instead of checking obligation stale liquidity.accrue_interest(repay_reserve.liquidity.cumulative_borrow_rate_wads)?; + let liquidity_amount = if liquidity_amount == u64::MAX { + let user_token_balance = Account::unpack(&source_liquidity_info.data.borrow()) + .map_err(|_| { + msg!("Failed to deserialize user token account"); + LendingError::InvalidAccountInput + })? + .amount; + + min(liquidity_amount, user_token_balance) + } else { + liquidity_amount + }; + let CalculateRepayResult { settle_amount, repay_amount, diff --git a/token-lending/program/tests/deposit_obligation_collateral.rs b/token-lending/program/tests/deposit_obligation_collateral.rs index 8f318fc4c00..5089772002e 100644 --- a/token-lending/program/tests/deposit_obligation_collateral.rs +++ b/token-lending/program/tests/deposit_obligation_collateral.rs @@ -117,3 +117,34 @@ async fn test_fail_deposit_too_much() { e => panic!("unexpected error: {:#?}", e), }; } + +#[tokio::test] +async fn test_success_deposit_max() { + let (mut test, lending_market, usdc_reserve, user, obligation) = setup().await; + + let balance_checker = BalanceChecker::start(&mut test, &[&usdc_reserve, &user]).await; + + lending_market + .deposit_obligation_collateral(&mut test, &usdc_reserve, &obligation, &user, u64::MAX) + .await + .unwrap(); + + let (balance_changes, _) = balance_checker.find_balance_changes(&mut test).await; + + let expected_balance_changes = HashSet::from([ + TokenBalanceChange { + token_account: user + .get_account(&usdc_reserve.account.collateral.mint_pubkey) + .unwrap(), + mint: usdc_reserve.account.collateral.mint_pubkey, + diff: -1_000_000, + }, + TokenBalanceChange { + token_account: usdc_reserve.account.collateral.supply_pubkey, + mint: usdc_reserve.account.collateral.mint_pubkey, + diff: 1_000_000, + }, + ]); + + assert_eq!(balance_changes, expected_balance_changes); +} diff --git a/token-lending/program/tests/deposit_reserve_liquidity.rs b/token-lending/program/tests/deposit_reserve_liquidity.rs index e5ee0e9cdad..0fd617c7874 100644 --- a/token-lending/program/tests/deposit_reserve_liquidity.rs +++ b/token-lending/program/tests/deposit_reserve_liquidity.rs @@ -163,3 +163,50 @@ async fn test_fail_deposit_too_much() { e => panic!("unexpected error: {:#?}", e), }; } + +#[tokio::test] +async fn test_success_deposit_max() { + let (mut test, lending_market, usdc_reserve, _) = setup().await; + + let user = User::new_with_balances( + &mut test, + &[ + (&usdc_mint::id(), 50_000 * FRACTIONAL_TO_USDC), + (&usdc_reserve.account.collateral.mint_pubkey, 0), + ], + ) + .await; + + let balance_checker = BalanceChecker::start(&mut test, &[&usdc_reserve, &user]).await; + + lending_market + .deposit(&mut test, &usdc_reserve, &user, u64::MAX) + .await + .unwrap(); + + let (balance_changes, _) = balance_checker.find_balance_changes(&mut test).await; + + let expected_balance_changes = HashSet::from([ + TokenBalanceChange { + token_account: user + .get_account(&usdc_reserve.account.liquidity.mint_pubkey) + .unwrap(), + mint: usdc_reserve.account.liquidity.mint_pubkey, + diff: -(50_000 * FRACTIONAL_TO_USDC as i128), + }, + TokenBalanceChange { + token_account: user + .get_account(&usdc_reserve.account.collateral.mint_pubkey) + .unwrap(), + mint: usdc_reserve.account.collateral.mint_pubkey, + diff: 50_000 * FRACTIONAL_TO_USDC as i128, + }, + TokenBalanceChange { + token_account: usdc_reserve.account.liquidity.supply_pubkey, + mint: usdc_reserve.account.liquidity.mint_pubkey, + diff: 50_000 * FRACTIONAL_TO_USDC as i128, + }, + ]); + + assert_eq!(balance_changes, expected_balance_changes); +} diff --git a/token-lending/program/tests/repay_obligation_liquidity.rs b/token-lending/program/tests/repay_obligation_liquidity.rs index c3e9cd7a958..59f3f0aa8f5 100644 --- a/token-lending/program/tests/repay_obligation_liquidity.rs +++ b/token-lending/program/tests/repay_obligation_liquidity.rs @@ -2,7 +2,14 @@ mod helpers; +use crate::solend_program_test::custom_scenario; use crate::solend_program_test::scenario_1; +use crate::solend_program_test::ObligationArgs; +use crate::solend_program_test::PriceArgs; +use crate::solend_program_test::ReserveArgs; +use crate::solend_program_test::User; +use solend_sdk::state::ReserveConfig; +use solend_sdk::state::ReserveFees; use std::collections::HashSet; use helpers::solend_program_test::{BalanceChecker, TokenBalanceChange}; @@ -110,3 +117,78 @@ async fn test_success() { } ); } + +#[tokio::test] +async fn test_repay_max() { + 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, + }, + }, + ReserveArgs { + mint: wsol_mint::id(), + config: ReserveConfig { + loan_to_value_ratio: 50, + liquidation_threshold: 55, + fees: ReserveFees::default(), + optimal_borrow_rate: 0, + max_borrow_rate: 0, + ..test_reserve_config() + }, + liquidity_amount: 100 * LAMPORTS_PER_SOL, + price: PriceArgs { + price: 10, + conf: 0, + expo: 0, + ema_price: 10, + ema_conf: 0, + }, + }, + ], + &[ObligationArgs { + deposits: vec![(usdc_mint::id(), 100 * FRACTIONAL_TO_USDC)], + borrows: vec![(wsol_mint::id(), LAMPORTS_PER_SOL)], + }], + ) + .await; + + let repayooor = + User::new_with_balances(&mut test, &[(&wsol_mint::id(), LAMPORTS_PER_SOL / 10)]).await; + + let balance_checker = BalanceChecker::start(&mut test, &[&repayooor, &reserves[1]]).await; + + lending_market + .repay_obligation_liquidity( + &mut test, + &reserves[1], + &obligations[0], + &repayooor, + u64::MAX, + ) + .await + .unwrap(); + + let (balance_changes, _) = balance_checker.find_balance_changes(&mut test).await; + let expected_balance_changes = HashSet::from([ + TokenBalanceChange { + token_account: repayooor.get_account(&wsol_mint::id()).unwrap(), + mint: wsol_mint::id(), + diff: -((LAMPORTS_PER_SOL / 10) as i128), + }, + TokenBalanceChange { + token_account: reserves[1].account.liquidity.supply_pubkey, + mint: wsol_mint::id(), + diff: (LAMPORTS_PER_SOL / 10) as i128, + }, + ]); + assert_eq!(balance_changes, expected_balance_changes); +}