lending: Split interest accrual into new instruction (#1115)
This commit is contained in:
parent
14daf0d370
commit
62e6a67eb2
|
@ -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")]
|
||||
|
|
|
@ -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<Pubkey>) -> 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(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>(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<T: Pack + IsInitialized>(
|
||||
account_info: &AccountInfo,
|
||||
) -> Result<T, ProgramError> {
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<Pubkey>,
|
||||
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
},
|
||||
|
|
|
@ -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()
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue