diff --git a/token-lending/program/src/error.rs b/token-lending/program/src/error.rs index 126703c1..9377f35a 100644 --- a/token-lending/program/src/error.rs +++ b/token-lending/program/src/error.rs @@ -86,6 +86,9 @@ pub enum LendingError { /// Borrow amount too small #[error("Borrow amount too small")] BorrowTooSmall, + /// Reserve state stale + #[error("Reserve state needs to be updated for the current slot")] + ReserveStale, /// Trade simulation error #[error("Trade simulation error")] diff --git a/token-lending/program/src/instruction.rs b/token-lending/program/src/instruction.rs index 9031505e..cfa934e4 100644 --- a/token-lending/program/src/instruction.rs +++ b/token-lending/program/src/instruction.rs @@ -66,7 +66,7 @@ pub enum LendingInstruction { /// Initializes a new loan obligation. /// /// 0. `[]` Deposit reserve account. - /// 1. `[writable]` Borrow reserve account. + /// 1. `[]` Borrow reserve account. /// 2. `[writable]` Obligation /// 3. `[writable]` Obligation token mint /// 4. `[writable]` Obligation token output @@ -119,7 +119,7 @@ pub enum LendingInstruction { /// 0. `[writable]` Source collateral token account, minted by deposit reserve collateral mint, /// $authority can transfer $collateral_amount /// 1. `[writable]` Destination liquidity token account, minted by borrow reserve liquidity mint - /// 2. `[writable]` Deposit reserve account. + /// 2. `[]` Deposit reserve account. /// 3. `[writable]` Deposit reserve collateral supply SPL Token account /// 4. `[writable]` Deposit reserve collateral fee receiver account. /// Must be the fee account specified at InitReserve. @@ -175,7 +175,7 @@ pub enum LendingInstruction { /// 1. `[writable]` Destination collateral token account, minted by withdraw reserve collateral mint /// 2. `[writable]` Repay reserve account. /// 3. `[writable]` Repay reserve liquidity supply SPL Token account - /// 4. `[writable]` Withdraw reserve account. + /// 4. `[]` Withdraw reserve account. /// 5. `[writable]` Withdraw reserve collateral supply SPL Token account /// 6. `[writable]` Obligation - initialized /// 7. `[]` Lending market account. @@ -190,6 +190,13 @@ pub enum LendingInstruction { /// Amount of loan to repay liquidity_amount: u64, }, + + /// Accrue interest on reserves + /// + /// 0. `[]` Clock sysvar + /// 1. `[writable]` Reserve account. + /// .. `[writable]` Additional reserve accounts. + AccrueReserveInterest, } impl LendingInstruction { @@ -258,6 +265,7 @@ impl LendingInstruction { let (liquidity_amount, _rest) = Self::unpack_u64(rest)?; Self::LiquidateObligation { liquidity_amount } } + 8 => Self::AccrueReserveInterest, _ => return Err(LendingError::InstructionUnpackError.into()), }) } @@ -365,6 +373,9 @@ impl LendingInstruction { buf.push(7); buf.extend_from_slice(&liquidity_amount.to_le_bytes()); } + Self::AccrueReserveInterest => { + buf.push(8); + } } buf } @@ -462,7 +473,7 @@ pub fn init_obligation( Pubkey::find_program_address(&[&lending_market_pubkey.to_bytes()[..32]], &program_id); let accounts = vec![ AccountMeta::new_readonly(deposit_reserve_pubkey, false), - AccountMeta::new(borrow_reserve_pubkey, false), + AccountMeta::new_readonly(borrow_reserve_pubkey, false), AccountMeta::new(obligation_pubkey, false), AccountMeta::new(obligation_token_mint_pubkey, false), AccountMeta::new(obligation_token_output_pubkey, false), @@ -571,7 +582,7 @@ pub fn borrow_reserve_liquidity( let mut accounts = vec![ AccountMeta::new(source_collateral_pubkey, false), AccountMeta::new(destination_liquidity_pubkey, false), - AccountMeta::new(deposit_reserve_pubkey, false), + AccountMeta::new_readonly(deposit_reserve_pubkey, false), AccountMeta::new(deposit_reserve_collateral_supply_pubkey, false), AccountMeta::new(deposit_reserve_collateral_fees_receiver_pubkey, false), AccountMeta::new(borrow_reserve_pubkey, false), @@ -671,7 +682,7 @@ pub fn liquidate_obligation( AccountMeta::new(destination_collateral_pubkey, false), AccountMeta::new(repay_reserve_pubkey, false), AccountMeta::new(repay_reserve_liquidity_supply_pubkey, false), - AccountMeta::new(withdraw_reserve_pubkey, false), + AccountMeta::new_readonly(withdraw_reserve_pubkey, false), AccountMeta::new(withdraw_reserve_collateral_supply_pubkey, false), AccountMeta::new(obligation_pubkey, false), AccountMeta::new_readonly(lending_market_pubkey, false), @@ -686,3 +697,18 @@ pub fn liquidate_obligation( data: LendingInstruction::LiquidateObligation { liquidity_amount }.pack(), } } + +/// Creates an `AccrueReserveInterest` instruction +pub fn accrue_reserve_interest(program_id: Pubkey, reserve_pubkeys: Vec) -> Instruction { + let mut accounts = vec![AccountMeta::new_readonly(sysvar::clock::id(), false)]; + accounts.extend( + reserve_pubkeys + .into_iter() + .map(|reserve_pubkey| AccountMeta::new(reserve_pubkey, false)), + ); + Instruction { + program_id, + accounts, + data: LendingInstruction::AccrueReserveInterest.pack(), + } +} diff --git a/token-lending/program/src/processor.rs b/token-lending/program/src/processor.rs index b6d1d587..d5af35f3 100644 --- a/token-lending/program/src/processor.rs +++ b/token-lending/program/src/processor.rs @@ -13,6 +13,7 @@ use crate::{ use num_traits::FromPrimitive; use solana_program::{ account_info::{next_account_info, AccountInfo}, + clock::Slot, decode_error::DecodeError, entrypoint::ProgramResult, msg, @@ -71,6 +72,10 @@ pub fn process_instruction( msg!("Instruction: Liquidate"); process_liquidate(program_id, liquidity_amount, accounts) } + LendingInstruction::AccrueReserveInterest => { + msg!("Instruction: Accrue Interest"); + process_accrue_interest(program_id, accounts) + } } } @@ -339,7 +344,7 @@ fn process_init_obligation(program_id: &Pubkey, accounts: &[AccountInfo]) -> Pro return Err(LendingError::InvalidAccountInput.into()); } - let mut borrow_reserve = Reserve::unpack(&borrow_reserve_info.data.borrow())?; + let borrow_reserve = Reserve::unpack(&borrow_reserve_info.data.borrow())?; if borrow_reserve_info.owner != program_id { return Err(LendingError::InvalidAccountOwner.into()); } @@ -359,12 +364,9 @@ fn process_init_obligation(program_id: &Pubkey, accounts: &[AccountInfo]) -> Pro assert_rent_exempt(rent, obligation_info)?; assert_uninitialized::(obligation_info)?; + assert_last_update_slot(&borrow_reserve, clock.slot)?; - // accrue interest and update rates - borrow_reserve.accrue_interest(clock.slot)?; let cumulative_borrow_rate = borrow_reserve.cumulative_borrow_rate_wads; - Reserve::pack(borrow_reserve, &mut borrow_reserve_info.data.borrow_mut())?; - let obligation_mint_decimals = deposit_reserve.liquidity.mint_decimals; let obligation = Obligation::new(NewObligationParams { collateral_reserve: *deposit_reserve_info.key, @@ -459,7 +461,7 @@ fn process_deposit( return Err(LendingError::InvalidAccountInput.into()); } - reserve.accrue_interest(clock.slot)?; + assert_last_update_slot(&reserve, clock.slot)?; let collateral_amount = reserve.deposit_liquidity(liquidity_amount)?; Reserve::pack(reserve, &mut reserve_info.data.borrow_mut())?; @@ -548,7 +550,7 @@ fn process_withdraw( return Err(LendingError::InvalidAccountInput.into()); } - reserve.accrue_interest(clock.slot)?; + assert_last_update_slot(&reserve, clock.slot)?; let liquidity_withdraw_amount = reserve.redeem_collateral(collateral_amount)?; Reserve::pack(reserve, &mut reserve_info.data.borrow_mut())?; @@ -627,7 +629,7 @@ fn process_borrow( return Err(LendingError::InvalidTokenProgram.into()); } - let mut deposit_reserve = Reserve::unpack(&deposit_reserve_info.data.borrow())?; + let deposit_reserve = Reserve::unpack(&deposit_reserve_info.data.borrow())?; if deposit_reserve_info.owner != program_id { return Err(LendingError::InvalidAccountOwner.into()); } @@ -721,9 +723,8 @@ fn process_borrow( return Err(LendingError::InvalidTokenMint.into()); } - // accrue interest and update rates - borrow_reserve.accrue_interest(clock.slot)?; - deposit_reserve.accrue_interest(clock.slot)?; + assert_last_update_slot(&borrow_reserve, clock.slot)?; + assert_last_update_slot(&deposit_reserve, clock.slot)?; obligation.accrue_interest(borrow_reserve.cumulative_borrow_rate_wads)?; let mut trade_simulator = TradeSimulator::new( @@ -756,7 +757,6 @@ fn process_borrow( Obligation::pack(obligation, &mut obligation_info.data.borrow_mut())?; Reserve::pack(borrow_reserve, &mut borrow_reserve_info.data.borrow_mut())?; - Reserve::pack(deposit_reserve, &mut deposit_reserve_info.data.borrow_mut())?; let authority_signer_seeds = &[ lending_market_info.key.as_ref(), @@ -922,7 +922,7 @@ fn process_repay( } // accrue interest and update rates - repay_reserve.accrue_interest(clock.slot)?; + assert_last_update_slot(&repay_reserve, clock.slot)?; obligation.accrue_interest(repay_reserve.cumulative_borrow_rate_wads)?; let RepayResult { @@ -1043,7 +1043,7 @@ fn process_liquidate( return Err(LendingError::InvalidAccountInput.into()); } - let mut withdraw_reserve = Reserve::unpack(&withdraw_reserve_info.data.borrow())?; + let withdraw_reserve = Reserve::unpack(&withdraw_reserve_info.data.borrow())?; if withdraw_reserve_info.owner != program_id { return Err(LendingError::InvalidAccountOwner.into()); } @@ -1093,8 +1093,8 @@ fn process_liquidate( } // accrue interest and update rates - repay_reserve.accrue_interest(clock.slot)?; - withdraw_reserve.accrue_interest(clock.slot)?; + assert_last_update_slot(&repay_reserve, clock.slot)?; + assert_last_update_slot(&withdraw_reserve, clock.slot)?; obligation.accrue_interest(repay_reserve.cumulative_borrow_rate_wads)?; let mut trade_simulator = TradeSimulator::new( @@ -1152,10 +1152,6 @@ fn process_liquidate( .min(withdraw_amount_as_collateral + liquidation_bonus_amount); Reserve::pack(repay_reserve, &mut repay_reserve_info.data.borrow_mut())?; - Reserve::pack( - withdraw_reserve, - &mut withdraw_reserve_info.data.borrow_mut(), - )?; obligation.borrowed_liquidity_wads = obligation .borrowed_liquidity_wads @@ -1196,6 +1192,23 @@ fn process_liquidate( Ok(()) } +#[inline(never)] // avoid stack frame limit +fn process_accrue_interest(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { + let account_info_iter = &mut accounts.iter(); + let clock = &Clock::from_account_info(next_account_info(account_info_iter)?)?; + for reserve_info in account_info_iter { + let mut reserve = Reserve::unpack(&reserve_info.data.borrow())?; + if reserve_info.owner != program_id { + return Err(LendingError::InvalidAccountOwner.into()); + } + + reserve.accrue_interest(clock.slot)?; + Reserve::pack(reserve, &mut reserve_info.data.borrow_mut())?; + } + + Ok(()) +} + fn assert_rent_exempt(rent: &Rent, account_info: &AccountInfo) -> ProgramResult { if !rent.is_exempt(account_info.lamports(), account_info.data_len()) { msg!(&rent.minimum_balance(account_info.data_len()).to_string()); @@ -1205,6 +1218,14 @@ fn assert_rent_exempt(rent: &Rent, account_info: &AccountInfo) -> ProgramResult } } +fn assert_last_update_slot(reserve: &Reserve, slot: Slot) -> ProgramResult { + if !reserve.last_update_slot == slot { + Err(LendingError::ReserveStale.into()) + } else { + Ok(()) + } +} + fn assert_uninitialized( account_info: &AccountInfo, ) -> Result { diff --git a/token-lending/program/tests/accrue_interest.rs b/token-lending/program/tests/accrue_interest.rs new file mode 100644 index 00000000..6775d70d --- /dev/null +++ b/token-lending/program/tests/accrue_interest.rs @@ -0,0 +1,108 @@ +#![cfg(feature = "test-bpf")] + +mod helpers; + +use helpers::*; +use solana_program_test::*; +use solana_sdk::{ + pubkey::Pubkey, + signature::{Keypair, Signer}, + transaction::Transaction, +}; +use spl_token_lending::{ + instruction::accrue_reserve_interest, + math::{Decimal, Rate, TryMul}, + processor::process_instruction, + state::SLOTS_PER_YEAR, +}; + +const LAMPORTS_TO_SOL: u64 = 1_000_000_000; +const FRACTIONAL_TO_USDC: u64 = 1_000_000; + +const INITIAL_SOL_RESERVE_SUPPLY_LAMPORTS: u64 = 100 * LAMPORTS_TO_SOL; +const INITIAL_USDC_RESERVE_SUPPLY_FRACTIONAL: u64 = 100 * FRACTIONAL_TO_USDC; + +#[tokio::test] +async fn test_success() { + let mut test = ProgramTest::new( + "spl_token_lending", + spl_token_lending::id(), + processor!(process_instruction), + ); + + // limit to track compute unit increase + test.set_bpf_compute_max_units(80_000); + + let user_accounts_owner = Keypair::new(); + let usdc_mint = add_usdc_mint(&mut test); + let lending_market = add_lending_market(&mut test, usdc_mint.pubkey); + + let mut reserve_config = TEST_RESERVE_CONFIG; + reserve_config.loan_to_value_ratio = 80; + + // Configure reserve to a fixed borrow rate of 1% + const BORROW_RATE: u8 = 1; + reserve_config.min_borrow_rate = BORROW_RATE; + reserve_config.optimal_borrow_rate = BORROW_RATE; + reserve_config.optimal_utilization_rate = 100; + + let usdc_reserve = add_reserve( + &mut test, + &user_accounts_owner, + &lending_market, + AddReserveArgs { + borrow_amount: 100, + liquidity_amount: INITIAL_USDC_RESERVE_SUPPLY_FRACTIONAL, + liquidity_mint_decimals: usdc_mint.decimals, + liquidity_mint_pubkey: usdc_mint.pubkey, + slots_elapsed: SLOTS_PER_YEAR, + config: reserve_config, + ..AddReserveArgs::default() + }, + ); + + let sol_reserve = add_reserve( + &mut test, + &user_accounts_owner, + &lending_market, + AddReserveArgs { + borrow_amount: 100, + liquidity_amount: INITIAL_SOL_RESERVE_SUPPLY_LAMPORTS, + liquidity_mint_decimals: 9, + liquidity_mint_pubkey: spl_token::native_mint::id(), + slots_elapsed: SLOTS_PER_YEAR, + config: reserve_config, + ..AddReserveArgs::default() + }, + ); + + let (mut banks_client, payer, recent_blockhash) = test.start().await; + let mut transaction = Transaction::new_with_payer( + &[accrue_reserve_interest( + spl_token_lending::id(), + vec![usdc_reserve.pubkey, sol_reserve.pubkey], + )], + Some(&payer.pubkey()), + ); + + transaction.sign(&[&payer], recent_blockhash); + assert!(banks_client.process_transaction(transaction).await.is_ok()); + + let sol_reserve = sol_reserve.get_state(&mut banks_client).await; + let usdc_reserve = usdc_reserve.get_state(&mut banks_client).await; + + let borrow_rate = Rate::from_percent(100u8 + BORROW_RATE); + assert!(sol_reserve.cumulative_borrow_rate_wads > borrow_rate.into()); + assert_eq!( + sol_reserve.cumulative_borrow_rate_wads, + usdc_reserve.cumulative_borrow_rate_wads + ); + assert!( + sol_reserve.liquidity.borrowed_amount_wads + > Decimal::from(100u64).try_mul(borrow_rate).unwrap() + ); + assert_eq!( + sol_reserve.liquidity.borrowed_amount_wads, + usdc_reserve.liquidity.borrowed_amount_wads + ); +} diff --git a/token-lending/program/tests/borrow.rs b/token-lending/program/tests/borrow.rs index 6766102e..54827df8 100644 --- a/token-lending/program/tests/borrow.rs +++ b/token-lending/program/tests/borrow.rs @@ -6,10 +6,8 @@ use helpers::*; use solana_program_test::*; use solana_sdk::{pubkey::Pubkey, signature::Keypair}; use spl_token_lending::{ - instruction::BorrowAmountType, - math::Decimal, - processor::process_instruction, - state::{INITIAL_COLLATERAL_RATIO, SLOTS_PER_YEAR}, + instruction::BorrowAmountType, math::Decimal, processor::process_instruction, + state::INITIAL_COLLATERAL_RATIO, }; const LAMPORTS_TO_SOL: u64 = 1_000_000_000; @@ -36,7 +34,7 @@ async fn test_borrow_quote_currency() { ); // limit to track compute unit increase - test.set_bpf_compute_max_units(185_000); + test.set_bpf_compute_max_units(118_000); let user_accounts_owner = Keypair::new(); let sol_usdc_dex_market = TestDexMarket::setup(&mut test, TestDexMarketPair::SOL_USDC); @@ -46,17 +44,11 @@ async fn test_borrow_quote_currency() { let mut reserve_config = TEST_RESERVE_CONFIG; reserve_config.loan_to_value_ratio = 80; - // Configure reserve to a fixed borrow rate of 1% - reserve_config.min_borrow_rate = 1; - reserve_config.optimal_borrow_rate = 1; - reserve_config.optimal_utilization_rate = 100; - let usdc_reserve = add_reserve( &mut test, &user_accounts_owner, &lending_market, AddReserveArgs { - slots_elapsed: SLOTS_PER_YEAR, liquidity_amount: INITIAL_USDC_RESERVE_SUPPLY_FRACTIONAL, liquidity_mint_pubkey: usdc_mint.pubkey, liquidity_mint_decimals: usdc_mint.decimals, @@ -70,8 +62,6 @@ async fn test_borrow_quote_currency() { &user_accounts_owner, &lending_market, AddReserveArgs { - slots_elapsed: SLOTS_PER_YEAR, - borrow_amount: 20, // slightly increase collateral value w/ interest accrual dex_market_pubkey: Some(sol_usdc_dex_market.pubkey), liquidity_amount: INITIAL_SOL_RESERVE_SUPPLY_LAMPORTS, liquidity_mint_pubkey: spl_token::native_mint::id(), @@ -154,14 +144,11 @@ async fn test_borrow_quote_currency() { get_token_balance(&mut banks_client, usdc_reserve.user_liquidity_account).await; assert_eq!(borrow_amount, 2 * USDC_BORROW_AMOUNT_FRACTIONAL); - // The SOL reserve accumulates interest which slightly increases the value - // of collateral, resulting in slightly less collateral required for new loans - const COLLATERAL_EPSILON: u64 = 1; let user_collateral_balance = get_token_balance(&mut banks_client, sol_reserve.user_collateral_account).await; - assert_eq!(user_collateral_balance, COLLATERAL_EPSILON); + assert_eq!(user_collateral_balance, 0); - let collateral_deposited = 2 * collateral_deposit_amount - COLLATERAL_EPSILON; + let collateral_deposited = 2 * collateral_deposit_amount; let (total_fee, host_fee) = TEST_RESERVE_CONFIG .fees .calculate_borrow_fees(collateral_deposited) @@ -196,10 +183,6 @@ async fn test_borrow_base_currency() { const INITIAL_SOL_RESERVE_SUPPLY_LAMPORTS: u64 = 5000 * LAMPORTS_TO_SOL; const INITIAL_USDC_RESERVE_SUPPLY_FRACTIONAL: u64 = 2 * USDC_COLLATERAL_LAMPORTS; - // The USDC collateral reserve accumulates interest which slightly increases - // the value of collateral, resulting in additional borrow power - const INTEREST_EPSILON: u64 = 2; - let mut test = ProgramTest::new( "spl_token_lending", spl_token_lending::id(), @@ -207,7 +190,7 @@ async fn test_borrow_base_currency() { ); // limit to track compute unit increase - test.set_bpf_compute_max_units(188_000); + test.set_bpf_compute_max_units(118_000); let user_accounts_owner = Keypair::new(); let sol_usdc_dex_market = TestDexMarket::setup(&mut test, TestDexMarketPair::SOL_USDC); @@ -217,18 +200,11 @@ async fn test_borrow_base_currency() { let mut reserve_config = TEST_RESERVE_CONFIG; reserve_config.loan_to_value_ratio = 100; - // Configure reserve to a fixed borrow rate of 1% - reserve_config.min_borrow_rate = 1; - reserve_config.optimal_borrow_rate = 1; - reserve_config.optimal_utilization_rate = 100; - let usdc_reserve = add_reserve( &mut test, &user_accounts_owner, &lending_market, AddReserveArgs { - slots_elapsed: SLOTS_PER_YEAR, - borrow_amount: 1, // slightly increase collateral value w/ interest accrual liquidity_amount: INITIAL_USDC_RESERVE_SUPPLY_FRACTIONAL, liquidity_mint_pubkey: usdc_mint.pubkey, liquidity_mint_decimals: usdc_mint.decimals, @@ -242,7 +218,6 @@ async fn test_borrow_base_currency() { &user_accounts_owner, &lending_market, AddReserveArgs { - slots_elapsed: SLOTS_PER_YEAR, dex_market_pubkey: Some(sol_usdc_dex_market.pubkey), liquidity_amount: INITIAL_SOL_RESERVE_SUPPLY_LAMPORTS, liquidity_mint_pubkey: spl_token::native_mint::id(), @@ -293,7 +268,7 @@ async fn test_borrow_base_currency() { let borrow_amount = get_token_balance(&mut banks_client, sol_reserve.user_liquidity_account).await; - assert_eq!(borrow_amount, SOL_BORROW_AMOUNT_LAMPORTS + INTEREST_EPSILON); + assert_eq!(borrow_amount, SOL_BORROW_AMOUNT_LAMPORTS); let borrow_fees = TEST_RESERVE_CONFIG .fees @@ -314,7 +289,7 @@ async fn test_borrow_base_currency() { borrow_reserve: &sol_reserve, dex_market: &sol_usdc_dex_market, borrow_amount_type: BorrowAmountType::LiquidityBorrowAmount, - amount: borrow_amount - INTEREST_EPSILON, + amount: borrow_amount, user_accounts_owner: &user_accounts_owner, obligation: &sol_obligation, }, @@ -323,10 +298,7 @@ async fn test_borrow_base_currency() { let borrow_amount = get_token_balance(&mut banks_client, sol_reserve.user_liquidity_account).await; - assert_eq!( - borrow_amount, - 2 * SOL_BORROW_AMOUNT_LAMPORTS + INTEREST_EPSILON - ); + assert_eq!(borrow_amount, 2 * SOL_BORROW_AMOUNT_LAMPORTS); let (mut total_fee, mut host_fee) = TEST_RESERVE_CONFIG .fees diff --git a/token-lending/program/tests/deposit.rs b/token-lending/program/tests/deposit.rs index a4ed8631..52eecd6b 100644 --- a/token-lending/program/tests/deposit.rs +++ b/token-lending/program/tests/deposit.rs @@ -5,7 +5,7 @@ mod helpers; use helpers::*; use solana_program_test::*; use solana_sdk::{pubkey::Pubkey, signature::Keypair}; -use spl_token_lending::{processor::process_instruction, state::SLOTS_PER_YEAR}; +use spl_token_lending::processor::process_instruction; const FRACTIONAL_TO_USDC: u64 = 1_000_000; @@ -18,7 +18,7 @@ async fn test_success() { ); // limit to track compute unit increase - test.set_bpf_compute_max_units(65_000); + test.set_bpf_compute_max_units(31_000); let user_accounts_owner = Keypair::new(); let usdc_mint = add_usdc_mint(&mut test); @@ -29,7 +29,6 @@ async fn test_success() { &user_accounts_owner, &lending_market, AddReserveArgs { - slots_elapsed: SLOTS_PER_YEAR, user_liquidity_amount: 100 * FRACTIONAL_TO_USDC, liquidity_amount: 10_000 * FRACTIONAL_TO_USDC, liquidity_mint_decimals: usdc_mint.decimals, diff --git a/token-lending/program/tests/helpers/mod.rs b/token-lending/program/tests/helpers/mod.rs index 570254c9..2c913a69 100644 --- a/token-lending/program/tests/helpers/mod.rs +++ b/token-lending/program/tests/helpers/mod.rs @@ -18,7 +18,7 @@ use spl_token_lending::{ borrow_reserve_liquidity, deposit_reserve_liquidity, init_lending_market, init_obligation, init_reserve, liquidate_obligation, BorrowAmountType, }, - math::Decimal, + math::{Decimal, Rate, TryAdd, TryMul}, processor::process_instruction, state::{ LendingMarket, NewReserveParams, Obligation, Reserve, ReserveCollateral, ReserveConfig, @@ -227,6 +227,7 @@ pub struct AddReserveArgs { pub liquidity_mint_decimals: u8, pub user_liquidity_amount: u64, pub borrow_amount: u64, + pub initial_borrow_rate: u8, pub collateral_amount: u64, pub fees_amount: u64, pub dex_market_pubkey: Option, @@ -247,6 +248,7 @@ pub fn add_reserve( liquidity_mint_decimals, user_liquidity_amount, borrow_amount, + initial_borrow_rate, collateral_amount, fees_amount, dex_market_pubkey, @@ -358,6 +360,10 @@ pub fn add_reserve( }); reserve.deposit_liquidity(liquidity_amount).unwrap(); reserve.liquidity.borrow(borrow_amount).unwrap(); + let borrow_rate_multiplier = Rate::one() + .try_add(Rate::from_percent(initial_borrow_rate)) + .unwrap(); + reserve.cumulative_borrow_rate_wads = Decimal::one().try_mul(borrow_rate_multiplier).unwrap(); test.add_packable_account( reserve_pubkey, u32::MAX as u64, diff --git a/token-lending/program/tests/init_obligation.rs b/token-lending/program/tests/init_obligation.rs index b8efc995..c1e53af0 100644 --- a/token-lending/program/tests/init_obligation.rs +++ b/token-lending/program/tests/init_obligation.rs @@ -12,7 +12,7 @@ use solana_sdk::{ }; use spl_token_lending::{ error::LendingError, instruction::init_obligation, math::Decimal, - processor::process_instruction, state::SLOTS_PER_YEAR, + processor::process_instruction, }; #[tokio::test] @@ -24,7 +24,7 @@ async fn test_success() { ); // limit to track compute unit increase - test.set_bpf_compute_max_units(60_000); + test.set_bpf_compute_max_units(28_000); let user_accounts_owner = Keypair::new(); let sol_usdc_dex_market = TestDexMarket::setup(&mut test, TestDexMarketPair::SOL_USDC); @@ -36,7 +36,6 @@ async fn test_success() { &user_accounts_owner, &lending_market, AddReserveArgs { - slots_elapsed: SLOTS_PER_YEAR, liquidity_mint_pubkey: usdc_mint.pubkey, liquidity_mint_decimals: usdc_mint.decimals, config: TEST_RESERVE_CONFIG, @@ -84,7 +83,7 @@ async fn test_already_initialized() { ); // limit to track compute unit increase - test.set_bpf_compute_max_units(14_000); + test.set_bpf_compute_max_units(13_000); let user_accounts_owner = Keypair::new(); let sol_usdc_dex_market = TestDexMarket::setup(&mut test, TestDexMarketPair::SOL_USDC); @@ -96,7 +95,6 @@ async fn test_already_initialized() { &user_accounts_owner, &lending_market, AddReserveArgs { - slots_elapsed: SLOTS_PER_YEAR, liquidity_mint_pubkey: usdc_mint.pubkey, liquidity_mint_decimals: usdc_mint.decimals, config: TEST_RESERVE_CONFIG, diff --git a/token-lending/program/tests/liquidate.rs b/token-lending/program/tests/liquidate.rs index ab8da228..e50659cb 100644 --- a/token-lending/program/tests/liquidate.rs +++ b/token-lending/program/tests/liquidate.rs @@ -6,9 +6,7 @@ use helpers::*; use solana_program_test::*; use solana_sdk::{pubkey::Pubkey, signature::Keypair}; use spl_token_lending::{ - math::Decimal, - processor::process_instruction, - state::{INITIAL_COLLATERAL_RATIO, SLOTS_PER_YEAR}, + math::Decimal, processor::process_instruction, state::INITIAL_COLLATERAL_RATIO, }; const LAMPORTS_TO_SOL: u64 = 1_000_000_000; @@ -26,7 +24,7 @@ async fn test_success() { ); // limit to track compute unit increase - test.set_bpf_compute_max_units(163_000); + test.set_bpf_compute_max_units(90_000); // set loan values to about 90% of collateral value so that it gets liquidated const USDC_LOAN: u64 = 2 * FRACTIONAL_TO_USDC; @@ -50,11 +48,11 @@ async fn test_success() { &lending_market, AddReserveArgs { config: reserve_config, - slots_elapsed: SLOTS_PER_YEAR, + initial_borrow_rate: 1, liquidity_amount: INITIAL_USDC_RESERVE_SUPPLY_FRACTIONAL, liquidity_mint_pubkey: usdc_mint.pubkey, liquidity_mint_decimals: usdc_mint.decimals, - borrow_amount: USDC_LOAN, + borrow_amount: USDC_LOAN * 101 / 100, user_liquidity_amount: USDC_LOAN, collateral_amount: SOL_LOAN_USDC_COLLATERAL, ..AddReserveArgs::default() @@ -67,13 +65,13 @@ async fn test_success() { &lending_market, AddReserveArgs { config: reserve_config, - slots_elapsed: SLOTS_PER_YEAR, + initial_borrow_rate: 1, liquidity_amount: INITIAL_SOL_RESERVE_SUPPLY_LAMPORTS, liquidity_mint_decimals: 9, liquidity_mint_pubkey: spl_token::native_mint::id(), dex_market_pubkey: Some(sol_usdc_dex_market.pubkey), collateral_amount: USDC_LOAN_SOL_COLLATERAL, - borrow_amount: SOL_LOAN, + borrow_amount: SOL_LOAN * 101 / 100, user_liquidity_amount: SOL_LOAN, ..AddReserveArgs::default() }, diff --git a/token-lending/program/tests/repay.rs b/token-lending/program/tests/repay.rs index a536947c..8a9f0106 100644 --- a/token-lending/program/tests/repay.rs +++ b/token-lending/program/tests/repay.rs @@ -29,7 +29,7 @@ async fn test_success() { ); // limit to track compute unit increase - test.set_bpf_compute_max_units(87_000); + test.set_bpf_compute_max_units(51_000); const INITIAL_SOL_RESERVE_SUPPLY_LAMPORTS: u64 = 100 * LAMPORTS_TO_SOL; const INITIAL_USDC_RESERVE_SUPPLY_FRACTIONAL: u64 = 100 * FRACTIONAL_TO_USDC; @@ -49,11 +49,11 @@ async fn test_success() { &lending_market, AddReserveArgs { config: TEST_RESERVE_CONFIG, - slots_elapsed: SLOTS_PER_YEAR, + initial_borrow_rate: 1, liquidity_amount: INITIAL_USDC_RESERVE_SUPPLY_FRACTIONAL, liquidity_mint_pubkey: usdc_mint.pubkey, liquidity_mint_decimals: usdc_mint.decimals, - borrow_amount: OBLIGATION_LOAN, + borrow_amount: OBLIGATION_LOAN * 101 / 100, user_liquidity_amount: OBLIGATION_LOAN, ..AddReserveArgs::default() }, diff --git a/token-lending/program/tests/withdraw.rs b/token-lending/program/tests/withdraw.rs index a9d0a207..0cf66829 100644 --- a/token-lending/program/tests/withdraw.rs +++ b/token-lending/program/tests/withdraw.rs @@ -11,13 +11,12 @@ use solana_sdk::{ }; use spl_token::instruction::approve; use spl_token_lending::{ - instruction::withdraw_reserve_liquidity, - processor::process_instruction, - state::{INITIAL_COLLATERAL_RATIO, SLOTS_PER_YEAR}, + instruction::withdraw_reserve_liquidity, processor::process_instruction, + state::INITIAL_COLLATERAL_RATIO, }; const FRACTIONAL_TO_USDC: u64 = 1_000_000; -const INITIAL_USDC_RESERVE_SUPPLY_LAMPORTS: u64 = 10 * FRACTIONAL_TO_USDC; +const INITIAL_USDC_RESERVE_SUPPLY_FRACTIONAL: u64 = 10 * FRACTIONAL_TO_USDC; #[tokio::test] async fn test_success() { @@ -28,22 +27,21 @@ async fn test_success() { ); // limit to track compute unit increase - test.set_bpf_compute_max_units(66_000); + test.set_bpf_compute_max_units(33_000); let user_accounts_owner = Keypair::new(); let usdc_mint = add_usdc_mint(&mut test); let lending_market = add_lending_market(&mut test, usdc_mint.pubkey); const WITHDRAW_COLLATERAL_AMOUNT: u64 = - INITIAL_COLLATERAL_RATIO * INITIAL_USDC_RESERVE_SUPPLY_LAMPORTS; + INITIAL_COLLATERAL_RATIO * INITIAL_USDC_RESERVE_SUPPLY_FRACTIONAL; let usdc_reserve = add_reserve( &mut test, &user_accounts_owner, &lending_market, AddReserveArgs { - slots_elapsed: SLOTS_PER_YEAR, - liquidity_amount: INITIAL_USDC_RESERVE_SUPPLY_LAMPORTS, + liquidity_amount: INITIAL_USDC_RESERVE_SUPPLY_FRACTIONAL, liquidity_mint_decimals: usdc_mint.decimals, liquidity_mint_pubkey: usdc_mint.pubkey, collateral_amount: WITHDRAW_COLLATERAL_AMOUNT,