lending: Refactor reserve state module (#1083)

* lending: Reorder reserve state module

* lending: Refactor reserve info structs
This commit is contained in:
Justin Starry 2021-01-17 17:38:06 +08:00 committed by GitHub
parent 8004e436f7
commit 3f54645a64
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 499 additions and 439 deletions

View File

@ -214,7 +214,7 @@ impl<'a> TradeSimulator<'a> {
amount: u64, amount: u64,
) -> Result<(u64, u64), ProgramError> { ) -> Result<(u64, u64), ProgramError> {
let deposit_reserve_collateral_exchange_rate = let deposit_reserve_collateral_exchange_rate =
deposit_reserve.state.collateral_exchange_rate()?; deposit_reserve.collateral_exchange_rate()?;
match amount_type { match amount_type {
BorrowAmountType::LiquidityBorrowAmount => { BorrowAmountType::LiquidityBorrowAmount => {
let borrow_amount = amount; let borrow_amount = amount;
@ -224,7 +224,7 @@ impl<'a> TradeSimulator<'a> {
let loan_in_deposit_underlying = self.simulate_trade( let loan_in_deposit_underlying = self.simulate_trade(
TradeAction::Buy, TradeAction::Buy,
Decimal::from(borrow_amount), Decimal::from(borrow_amount),
&borrow_reserve.liquidity_mint, &borrow_reserve.liquidity.mint_pubkey,
false, false,
)?; )?;
@ -256,7 +256,7 @@ impl<'a> TradeSimulator<'a> {
let borrow_amount = self.simulate_trade( let borrow_amount = self.simulate_trade(
TradeAction::Sell, TradeAction::Sell,
loan_in_deposit_underlying, loan_in_deposit_underlying,
&deposit_reserve.liquidity_mint, &deposit_reserve.liquidity.mint_pubkey,
false, false,
)?; )?;

View File

@ -5,7 +5,10 @@ use crate::{
error::LendingError, error::LendingError,
instruction::{BorrowAmountType, LendingInstruction}, instruction::{BorrowAmountType, LendingInstruction},
math::{Decimal, Rate, TryAdd, TryDiv, TryMul, TrySub, WAD}, math::{Decimal, Rate, TryAdd, TryDiv, TryMul, TrySub, WAD},
state::{LendingMarket, Obligation, Reserve, ReserveConfig, ReserveState, PROGRAM_VERSION}, state::{
LendingMarket, NewReserveParams, Obligation, Reserve, ReserveCollateral, ReserveConfig,
ReserveLiquidity, PROGRAM_VERSION,
},
}; };
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
use solana_program::{ use solana_program::{
@ -206,7 +209,28 @@ fn process_init_reserve(
return Err(LendingError::InvalidMarketAuthority.into()); return Err(LendingError::InvalidMarketAuthority.into());
} }
let liquidity_reserve_mint = unpack_mint(&reserve_liquidity_mint_info.data.borrow())?; let reserve_liquidity_mint = unpack_mint(&reserve_liquidity_mint_info.data.borrow())?;
let reserve_liquidity_info = ReserveLiquidity::new(
*reserve_liquidity_mint_info.key,
reserve_liquidity_mint.decimals,
*reserve_liquidity_supply_info.key,
);
let reserve_collateral_info = ReserveCollateral::new(
*reserve_collateral_mint_info.key,
*reserve_collateral_supply_info.key,
*reserve_collateral_fees_receiver_info.key,
);
let mut reserve = Reserve::new(NewReserveParams {
current_slot: clock.slot,
lending_market: *lending_market_info.key,
collateral: reserve_collateral_info,
liquidity: reserve_liquidity_info,
dex_market,
config,
});
let collateral_amount = reserve.deposit_liquidity(liquidity_amount)?;
Reserve::pack(reserve, &mut reserve_info.data.borrow_mut())?;
spl_token_init_account(TokenInitializeAccountParams { spl_token_init_account(TokenInitializeAccountParams {
account: reserve_liquidity_supply_info.clone(), account: reserve_liquidity_supply_info.clone(),
mint: reserve_liquidity_mint_info.clone(), mint: reserve_liquidity_mint_info.clone(),
@ -219,7 +243,7 @@ fn process_init_reserve(
mint: reserve_collateral_mint_info.clone(), mint: reserve_collateral_mint_info.clone(),
authority: lending_market_authority_info.key, authority: lending_market_authority_info.key,
rent: rent_info.clone(), rent: rent_info.clone(),
decimals: liquidity_reserve_mint.decimals, decimals: reserve_liquidity_mint.decimals,
token_program: token_program_id.clone(), token_program: token_program_id.clone(),
})?; })?;
@ -256,33 +280,15 @@ fn process_init_reserve(
token_program: token_program_id.clone(), token_program: token_program_id.clone(),
})?; })?;
let reserve_state = ReserveState::new(clock.slot, liquidity_amount)?;
spl_token_mint_to(TokenMintToParams { spl_token_mint_to(TokenMintToParams {
mint: reserve_collateral_mint_info.clone(), mint: reserve_collateral_mint_info.clone(),
destination: destination_collateral_info.clone(), destination: destination_collateral_info.clone(),
amount: reserve_state.collateral_mint_supply, amount: collateral_amount,
authority: lending_market_authority_info.clone(), authority: lending_market_authority_info.clone(),
authority_signer_seeds, authority_signer_seeds,
token_program: token_program_id.clone(), token_program: token_program_id.clone(),
})?; })?;
Reserve::pack(
Reserve {
version: PROGRAM_VERSION,
lending_market: *lending_market_info.key,
liquidity_mint: *reserve_liquidity_mint_info.key,
liquidity_mint_decimals: liquidity_reserve_mint.decimals,
liquidity_supply: *reserve_liquidity_supply_info.key,
collateral_mint: *reserve_collateral_mint_info.key,
collateral_supply: *reserve_collateral_supply_info.key,
collateral_fees_receiver: *reserve_collateral_fees_receiver_info.key,
dex_market,
state: reserve_state,
config,
},
&mut reserve_info.data.borrow_mut(),
)?;
Ok(()) Ok(())
} }
@ -323,19 +329,19 @@ fn process_deposit(
msg!("Invalid reserve lending market account"); msg!("Invalid reserve lending market account");
return Err(LendingError::InvalidAccountInput.into()); return Err(LendingError::InvalidAccountInput.into());
} }
if &reserve.liquidity_supply != reserve_liquidity_supply_info.key { if &reserve.liquidity.supply_pubkey != reserve_liquidity_supply_info.key {
msg!("Invalid reserve liquidity supply account"); msg!("Invalid reserve liquidity supply account");
return Err(LendingError::InvalidAccountInput.into()); return Err(LendingError::InvalidAccountInput.into());
} }
if &reserve.collateral_mint != reserve_collateral_mint_info.key { if &reserve.collateral.mint_pubkey != reserve_collateral_mint_info.key {
msg!("Invalid reserve collateral mint account"); msg!("Invalid reserve collateral mint account");
return Err(LendingError::InvalidAccountInput.into()); return Err(LendingError::InvalidAccountInput.into());
} }
if &reserve.liquidity_supply == source_liquidity_info.key { if &reserve.liquidity.supply_pubkey == source_liquidity_info.key {
msg!("Cannot use reserve liquidity supply as source account input"); msg!("Cannot use reserve liquidity supply as source account input");
return Err(LendingError::InvalidAccountInput.into()); return Err(LendingError::InvalidAccountInput.into());
} }
if &reserve.collateral_supply == destination_collateral_info.key { if &reserve.collateral.supply_pubkey == destination_collateral_info.key {
msg!("Cannot use reserve collateral supply as destination account input"); msg!("Cannot use reserve collateral supply as destination account input");
return Err(LendingError::InvalidAccountInput.into()); return Err(LendingError::InvalidAccountInput.into());
} }
@ -412,19 +418,19 @@ fn process_withdraw(
msg!("Invalid reserve lending market account"); msg!("Invalid reserve lending market account");
return Err(LendingError::InvalidAccountInput.into()); return Err(LendingError::InvalidAccountInput.into());
} }
if &reserve.liquidity_supply != reserve_liquidity_supply_info.key { if &reserve.liquidity.supply_pubkey != reserve_liquidity_supply_info.key {
msg!("Invalid reserve liquidity supply account"); msg!("Invalid reserve liquidity supply account");
return Err(LendingError::InvalidAccountInput.into()); return Err(LendingError::InvalidAccountInput.into());
} }
if &reserve.collateral_mint != reserve_collateral_mint_info.key { if &reserve.collateral.mint_pubkey != reserve_collateral_mint_info.key {
msg!("Invalid reserve collateral mint account"); msg!("Invalid reserve collateral mint account");
return Err(LendingError::InvalidAccountInput.into()); return Err(LendingError::InvalidAccountInput.into());
} }
if &reserve.liquidity_supply == destination_liquidity_info.key { if &reserve.liquidity.supply_pubkey == destination_liquidity_info.key {
msg!("Cannot use reserve liquidity supply as destination account input"); msg!("Cannot use reserve liquidity supply as destination account input");
return Err(LendingError::InvalidAccountInput.into()); return Err(LendingError::InvalidAccountInput.into());
} }
if &reserve.collateral_supply == source_collateral_info.key { if &reserve.collateral.supply_pubkey == source_collateral_info.key {
msg!("Cannot use reserve collateral supply as source account input"); msg!("Cannot use reserve collateral supply as source account input");
return Err(LendingError::InvalidAccountInput.into()); return Err(LendingError::InvalidAccountInput.into());
} }
@ -529,28 +535,28 @@ fn process_borrow(
if deposit_reserve_info.key == borrow_reserve_info.key { if deposit_reserve_info.key == borrow_reserve_info.key {
return Err(LendingError::DuplicateReserve.into()); return Err(LendingError::DuplicateReserve.into());
} }
if deposit_reserve.liquidity_mint == borrow_reserve.liquidity_mint { if deposit_reserve.liquidity.mint_pubkey == borrow_reserve.liquidity.mint_pubkey {
return Err(LendingError::DuplicateReserveMint.into()); return Err(LendingError::DuplicateReserveMint.into());
} }
if &borrow_reserve.liquidity_supply != borrow_reserve_liquidity_supply_info.key { if &borrow_reserve.liquidity.supply_pubkey != borrow_reserve_liquidity_supply_info.key {
msg!("Invalid borrow reserve liquidity supply account input"); msg!("Invalid borrow reserve liquidity supply account input");
return Err(LendingError::InvalidAccountInput.into()); return Err(LendingError::InvalidAccountInput.into());
} }
if &deposit_reserve.collateral_supply != deposit_reserve_collateral_supply_info.key { if &deposit_reserve.collateral.supply_pubkey != deposit_reserve_collateral_supply_info.key {
msg!("Invalid deposit reserve collateral supply account input"); msg!("Invalid deposit reserve collateral supply account input");
return Err(LendingError::InvalidAccountInput.into()); return Err(LendingError::InvalidAccountInput.into());
} }
if &deposit_reserve.collateral_supply == source_collateral_info.key { if &deposit_reserve.collateral.supply_pubkey == source_collateral_info.key {
msg!("Cannot use deposit reserve collateral supply as source account input"); msg!("Cannot use deposit reserve collateral supply as source account input");
return Err(LendingError::InvalidAccountInput.into()); return Err(LendingError::InvalidAccountInput.into());
} }
if &deposit_reserve.collateral_fees_receiver if &deposit_reserve.collateral.fees_receiver
!= deposit_reserve_collateral_fees_receiver_info.key != deposit_reserve_collateral_fees_receiver_info.key
{ {
msg!("Invalid deposit reserve collateral fees receiver account"); msg!("Invalid deposit reserve collateral fees receiver account");
return Err(LendingError::InvalidAccountInput.into()); return Err(LendingError::InvalidAccountInput.into());
} }
if &borrow_reserve.liquidity_supply == destination_liquidity_info.key { if &borrow_reserve.liquidity.supply_pubkey == destination_liquidity_info.key {
msg!("Cannot use borrow reserve liquidity supply as destination account input"); msg!("Cannot use borrow reserve liquidity supply as destination account input");
return Err(LendingError::InvalidAccountInput.into()); return Err(LendingError::InvalidAccountInput.into());
} }
@ -572,7 +578,7 @@ fn process_borrow(
// accrue interest and update rates // accrue interest and update rates
borrow_reserve.accrue_interest(clock.slot)?; borrow_reserve.accrue_interest(clock.slot)?;
deposit_reserve.accrue_interest(clock.slot)?; deposit_reserve.accrue_interest(clock.slot)?;
let cumulative_borrow_rate = borrow_reserve.state.cumulative_borrow_rate_wads; let cumulative_borrow_rate = borrow_reserve.cumulative_borrow_rate_wads;
let mut trade_simulator = TradeSimulator::new( let mut trade_simulator = TradeSimulator::new(
dex_market_info, dex_market_info,
@ -595,9 +601,9 @@ fn process_borrow(
// update amount actually deposited // update amount actually deposited
collateral_deposit_amount -= borrow_fee; collateral_deposit_amount -= borrow_fee;
borrow_reserve.state.add_borrow(borrow_amount)?; borrow_reserve.liquidity.borrow(borrow_amount)?;
let obligation_mint_decimals = deposit_reserve.liquidity_mint_decimals; let obligation_mint_decimals = deposit_reserve.liquidity.mint_decimals;
Reserve::pack(deposit_reserve, &mut deposit_reserve_info.data.borrow_mut())?; Reserve::pack(deposit_reserve, &mut deposit_reserve_info.data.borrow_mut())?;
Reserve::pack(borrow_reserve, &mut borrow_reserve_info.data.borrow_mut())?; Reserve::pack(borrow_reserve, &mut borrow_reserve_info.data.borrow_mut())?;
@ -816,32 +822,32 @@ fn process_repay(
if repay_reserve_info.key == withdraw_reserve_info.key { if repay_reserve_info.key == withdraw_reserve_info.key {
return Err(LendingError::DuplicateReserve.into()); return Err(LendingError::DuplicateReserve.into());
} }
if repay_reserve.liquidity_mint == withdraw_reserve.liquidity_mint { if repay_reserve.liquidity.mint_pubkey == withdraw_reserve.liquidity.mint_pubkey {
return Err(LendingError::DuplicateReserveMint.into()); return Err(LendingError::DuplicateReserveMint.into());
} }
if &repay_reserve.liquidity_supply != repay_reserve_liquidity_supply_info.key { if &repay_reserve.liquidity.supply_pubkey != repay_reserve_liquidity_supply_info.key {
msg!("Invalid repay reserve liquidity supply account"); msg!("Invalid repay reserve liquidity supply account");
return Err(LendingError::InvalidAccountInput.into()); return Err(LendingError::InvalidAccountInput.into());
} }
if &withdraw_reserve.collateral_supply != withdraw_reserve_collateral_supply_info.key { if &withdraw_reserve.collateral.supply_pubkey != withdraw_reserve_collateral_supply_info.key {
msg!("Invalid withdraw reserve collateral supply account"); msg!("Invalid withdraw reserve collateral supply account");
return Err(LendingError::InvalidAccountInput.into()); return Err(LendingError::InvalidAccountInput.into());
} }
if &repay_reserve.liquidity_supply == source_liquidity_info.key { if &repay_reserve.liquidity.supply_pubkey == source_liquidity_info.key {
msg!("Cannot use repay reserve liquidity supply as source account input"); msg!("Cannot use repay reserve liquidity supply as source account input");
return Err(LendingError::InvalidAccountInput.into()); return Err(LendingError::InvalidAccountInput.into());
} }
if &withdraw_reserve.collateral_supply == destination_collateral_info.key { if &withdraw_reserve.collateral.supply_pubkey == destination_collateral_info.key {
msg!("Cannot use withdraw reserve collateral supply as destination account input"); msg!("Cannot use withdraw reserve collateral supply as destination account input");
return Err(LendingError::InvalidAccountInput.into()); return Err(LendingError::InvalidAccountInput.into());
} }
// accrue interest and update rates // accrue interest and update rates
repay_reserve.accrue_interest(clock.slot)?; repay_reserve.accrue_interest(clock.slot)?;
obligation.accrue_interest(repay_reserve.state.cumulative_borrow_rate_wads)?; obligation.accrue_interest(repay_reserve.cumulative_borrow_rate_wads)?;
let repay_amount = Decimal::from(liquidity_amount).min(obligation.borrowed_liquidity_wads); let repay_amount = Decimal::from(liquidity_amount).min(obligation.borrowed_liquidity_wads);
let rounded_repay_amount = repay_reserve.state.subtract_repay(repay_amount)?; let rounded_repay_amount = repay_reserve.liquidity.repay(repay_amount)?;
Reserve::pack(repay_reserve, &mut repay_reserve_info.data.borrow_mut())?; Reserve::pack(repay_reserve, &mut repay_reserve_info.data.borrow_mut())?;
let repay_pct: Decimal = repay_amount.try_div(obligation.borrowed_liquidity_wads)?; let repay_pct: Decimal = repay_amount.try_div(obligation.borrowed_liquidity_wads)?;
@ -972,22 +978,22 @@ fn process_liquidate(
if repay_reserve_info.key == withdraw_reserve_info.key { if repay_reserve_info.key == withdraw_reserve_info.key {
return Err(LendingError::DuplicateReserve.into()); return Err(LendingError::DuplicateReserve.into());
} }
if repay_reserve.liquidity_mint == withdraw_reserve.liquidity_mint { if repay_reserve.liquidity.mint_pubkey == withdraw_reserve.liquidity.mint_pubkey {
return Err(LendingError::DuplicateReserveMint.into()); return Err(LendingError::DuplicateReserveMint.into());
} }
if &repay_reserve.liquidity_supply != repay_reserve_liquidity_supply_info.key { if &repay_reserve.liquidity.supply_pubkey != repay_reserve_liquidity_supply_info.key {
msg!("Invalid repay reserve liquidity supply account"); msg!("Invalid repay reserve liquidity supply account");
return Err(LendingError::InvalidAccountInput.into()); return Err(LendingError::InvalidAccountInput.into());
} }
if &withdraw_reserve.collateral_supply != withdraw_reserve_collateral_supply_info.key { if &withdraw_reserve.collateral.supply_pubkey != withdraw_reserve_collateral_supply_info.key {
msg!("Invalid withdraw reserve collateral supply account"); msg!("Invalid withdraw reserve collateral supply account");
return Err(LendingError::InvalidAccountInput.into()); return Err(LendingError::InvalidAccountInput.into());
} }
if &repay_reserve.liquidity_supply == source_liquidity_info.key { if &repay_reserve.liquidity.supply_pubkey == source_liquidity_info.key {
msg!("Cannot use repay reserve liquidity supply as source account input"); msg!("Cannot use repay reserve liquidity supply as source account input");
return Err(LendingError::InvalidAccountInput.into()); return Err(LendingError::InvalidAccountInput.into());
} }
if &withdraw_reserve.collateral_supply == destination_collateral_info.key { if &withdraw_reserve.collateral.supply_pubkey == destination_collateral_info.key {
msg!("Cannot use withdraw reserve collateral supply as destination account input"); msg!("Cannot use withdraw reserve collateral supply as destination account input");
return Err(LendingError::InvalidAccountInput.into()); return Err(LendingError::InvalidAccountInput.into());
} }
@ -1009,7 +1015,7 @@ fn process_liquidate(
// accrue interest and update rates // accrue interest and update rates
repay_reserve.accrue_interest(clock.slot)?; repay_reserve.accrue_interest(clock.slot)?;
withdraw_reserve.accrue_interest(clock.slot)?; withdraw_reserve.accrue_interest(clock.slot)?;
obligation.accrue_interest(repay_reserve.state.cumulative_borrow_rate_wads)?; obligation.accrue_interest(repay_reserve.cumulative_borrow_rate_wads)?;
let mut trade_simulator = TradeSimulator::new( let mut trade_simulator = TradeSimulator::new(
dex_market_info, dex_market_info,
@ -1019,13 +1025,12 @@ fn process_liquidate(
)?; )?;
// calculate obligation health // calculate obligation health
let withdraw_reserve_collateral_exchange_rate = let withdraw_reserve_collateral_exchange_rate = withdraw_reserve.collateral_exchange_rate()?;
withdraw_reserve.state.collateral_exchange_rate()?;
let borrow_amount_as_collateral = withdraw_reserve_collateral_exchange_rate let borrow_amount_as_collateral = withdraw_reserve_collateral_exchange_rate
.decimal_liquidity_to_collateral(trade_simulator.simulate_trade( .decimal_liquidity_to_collateral(trade_simulator.simulate_trade(
TradeAction::Sell, TradeAction::Sell,
obligation.borrowed_liquidity_wads, obligation.borrowed_liquidity_wads,
&repay_reserve.liquidity_mint, &repay_reserve.liquidity.mint_pubkey,
true, true,
)?)? )?)?
.try_round_u64()?; .try_round_u64()?;
@ -1040,7 +1045,7 @@ fn process_liquidate(
let close_factor = Rate::from_percent(50); let close_factor = Rate::from_percent(50);
let repay_amount = Decimal::from(liquidity_amount) let repay_amount = Decimal::from(liquidity_amount)
.min(obligation.borrowed_liquidity_wads.try_mul(close_factor)?); .min(obligation.borrowed_liquidity_wads.try_mul(close_factor)?);
let rounded_repay_amount = repay_reserve.state.subtract_repay(repay_amount)?; let rounded_repay_amount = repay_reserve.liquidity.repay(repay_amount)?;
// TODO: check math precision // TODO: check math precision
// calculate the amount of collateral that will be withdrawn // calculate the amount of collateral that will be withdrawn
@ -1048,7 +1053,7 @@ fn process_liquidate(
let withdraw_liquidity_amount = trade_simulator.simulate_trade( let withdraw_liquidity_amount = trade_simulator.simulate_trade(
TradeAction::Sell, TradeAction::Sell,
repay_amount, repay_amount,
&repay_reserve.liquidity_mint, &repay_reserve.liquidity.mint_pubkey,
false, false,
)?; )?;
let withdraw_amount_as_collateral = withdraw_reserve_collateral_exchange_rate let withdraw_amount_as_collateral = withdraw_reserve_collateral_exchange_rate

View File

@ -7,7 +7,6 @@ use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs};
use solana_program::{ use solana_program::{
clock::Slot, clock::Slot,
entrypoint::ProgramResult, entrypoint::ProgramResult,
msg,
program_error::ProgramError, program_error::ProgramError,
program_option::COption, program_option::COption,
program_pack::{IsInitialized, Pack, Sealed}, program_pack::{IsInitialized, Pack, Sealed},
@ -15,6 +14,336 @@ use solana_program::{
}; };
use std::convert::{TryFrom, TryInto}; use std::convert::{TryFrom, TryInto};
/// Lending market reserve state
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Reserve {
/// Version of the struct
pub version: u8,
/// Last slot when supply and rates updated
pub last_update_slot: Slot,
/// Cumulative borrow rate
pub cumulative_borrow_rate_wads: Decimal,
/// Lending market address
pub lending_market: Pubkey,
/// Dex market state account
pub dex_market: COption<Pubkey>,
/// Reserve liquidity info
pub liquidity: ReserveLiquidity,
/// Reserve collateral info
pub collateral: ReserveCollateral,
/// Reserve configuration values
pub config: ReserveConfig,
}
impl Reserve {
/// Initialize new reserve state
pub fn new(params: NewReserveParams) -> Self {
let NewReserveParams {
current_slot,
lending_market,
collateral: collateral_info,
liquidity: liquidity_info,
dex_market,
config,
} = params;
Self {
version: PROGRAM_VERSION,
last_update_slot: current_slot,
cumulative_borrow_rate_wads: Decimal::one(),
lending_market,
collateral: collateral_info,
liquidity: liquidity_info,
dex_market,
config,
}
}
/// Calculate the current borrow rate
pub fn current_borrow_rate(&self) -> Result<Rate, ProgramError> {
let utilization_rate = self.liquidity.utilization_rate()?;
let optimal_utilization_rate = Rate::from_percent(self.config.optimal_utilization_rate);
let low_utilization = utilization_rate < optimal_utilization_rate;
if low_utilization || self.config.optimal_utilization_rate == 100 {
let normalized_rate = utilization_rate.try_div(optimal_utilization_rate)?;
let min_rate = Rate::from_percent(self.config.min_borrow_rate);
let rate_range =
Rate::from_percent(self.config.optimal_borrow_rate - self.config.min_borrow_rate);
Ok(normalized_rate.try_mul(rate_range)?.try_add(min_rate)?)
} else {
let normalized_rate = utilization_rate
.try_sub(optimal_utilization_rate)?
.try_div(Rate::from_percent(
100 - self.config.optimal_utilization_rate,
))?;
let min_rate = Rate::from_percent(self.config.optimal_borrow_rate);
let rate_range =
Rate::from_percent(self.config.max_borrow_rate - self.config.optimal_borrow_rate);
Ok(normalized_rate.try_mul(rate_range)?.try_add(min_rate)?)
}
}
/// Record deposited liquidity and return amount of collateral tokens to mint
pub fn deposit_liquidity(&mut self, liquidity_amount: u64) -> Result<u64, ProgramError> {
let collateral_exchange_rate = self.collateral_exchange_rate()?;
let collateral_amount =
collateral_exchange_rate.liquidity_to_collateral(liquidity_amount)?;
self.liquidity.available_amount += liquidity_amount;
self.collateral.mint_total_supply += collateral_amount;
Ok(collateral_amount)
}
/// Record redeemed collateral and return amount of liquidity to withdraw
pub fn redeem_collateral(&mut self, collateral_amount: u64) -> Result<u64, ProgramError> {
let collateral_exchange_rate = self.collateral_exchange_rate()?;
let liquidity_amount =
collateral_exchange_rate.collateral_to_liquidity(collateral_amount)?;
if liquidity_amount > self.liquidity.available_amount {
return Err(LendingError::InsufficientLiquidity.into());
}
self.liquidity.available_amount -= liquidity_amount;
self.collateral.mint_total_supply -= collateral_amount;
Ok(liquidity_amount)
}
/// Update borrow rate and accrue interest
pub fn accrue_interest(&mut self, current_slot: Slot) -> Result<(), ProgramError> {
let slots_elapsed = self.update_slot(current_slot);
if slots_elapsed > 0 {
let current_borrow_rate = self.current_borrow_rate()?;
let compounded_interest_rate =
self.compound_interest(current_borrow_rate, slots_elapsed)?;
self.liquidity.borrowed_amount_wads = self
.liquidity
.borrowed_amount_wads
.try_mul(compounded_interest_rate)?;
}
Ok(())
}
/// Collateral exchange rate
pub fn collateral_exchange_rate(&self) -> Result<CollateralExchangeRate, ProgramError> {
let total_liquidity = self.liquidity.total_supply()?;
self.collateral.exchange_rate(total_liquidity)
}
/// Return slots elapsed since last update
fn update_slot(&mut self, slot: Slot) -> u64 {
let slots_elapsed = slot - self.last_update_slot;
self.last_update_slot = slot;
slots_elapsed
}
/// Compound current borrow rate over elapsed slots
fn compound_interest(
&mut self,
current_borrow_rate: Rate,
slots_elapsed: u64,
) -> Result<Rate, ProgramError> {
let slot_interest_rate: Rate = current_borrow_rate.try_div(SLOTS_PER_YEAR)?;
let compounded_interest_rate = Rate::one()
.try_add(slot_interest_rate)?
.try_pow(slots_elapsed)?;
self.cumulative_borrow_rate_wads = self
.cumulative_borrow_rate_wads
.try_mul(compounded_interest_rate)?;
Ok(compounded_interest_rate)
}
}
/// Create new reserve
pub struct NewReserveParams {
/// Current slot
pub current_slot: Slot,
/// Lending market address
pub lending_market: Pubkey,
/// Reserve collateral info
pub collateral: ReserveCollateral,
/// Reserve liquidity info
pub liquidity: ReserveLiquidity,
/// Optional dex market address
pub dex_market: COption<Pubkey>,
/// Reserve configuration values
pub config: ReserveConfig,
}
/// Reserve liquidity
#[derive(Clone, Debug, Default, PartialEq)]
pub struct ReserveLiquidity {
/// Reserve liquidity mint address
pub mint_pubkey: Pubkey,
/// Reserve liquidity mint decimals
pub mint_decimals: u8,
/// Reserve liquidity supply address
pub supply_pubkey: Pubkey,
/// Reserve liquidity available
pub available_amount: u64,
/// Reserve liquidity borrowed
pub borrowed_amount_wads: Decimal,
}
impl ReserveLiquidity {
/// New reserve liquidity info
pub fn new(mint_pubkey: Pubkey, mint_decimals: u8, supply_pubkey: Pubkey) -> Self {
Self {
mint_pubkey,
mint_decimals,
supply_pubkey,
available_amount: 0,
borrowed_amount_wads: Decimal::zero(),
}
}
/// Calculate the total reserve supply including active loans
pub fn total_supply(&self) -> Result<Decimal, ProgramError> {
Decimal::from(self.available_amount).try_add(self.borrowed_amount_wads)
}
/// Add new borrow amount to total borrows
pub fn borrow(&mut self, borrow_amount: u64) -> ProgramResult {
if borrow_amount > self.available_amount {
return Err(LendingError::InsufficientLiquidity.into());
}
self.available_amount -= borrow_amount;
self.borrowed_amount_wads = self
.borrowed_amount_wads
.try_add(Decimal::from(borrow_amount))?;
Ok(())
}
/// Subtract repay amount from total borrows and return rounded repay value
pub fn repay(&mut self, repay_amount: Decimal) -> Result<u64, ProgramError> {
let rounded_repay_amount = repay_amount.try_round_u64()?;
if rounded_repay_amount == 0 {
return Err(LendingError::ObligationTooSmall.into());
}
self.available_amount = self
.available_amount
.checked_add(rounded_repay_amount)
.ok_or(LendingError::MathOverflow)?;
self.borrowed_amount_wads = self.borrowed_amount_wads.try_sub(repay_amount)?;
Ok(rounded_repay_amount)
}
/// Calculate the liquidity utilization rate of the reserve
pub fn utilization_rate(&self) -> Result<Rate, ProgramError> {
let total_supply = self.total_supply()?;
if total_supply == Decimal::zero() {
return Ok(Rate::zero());
}
self.borrowed_amount_wads.try_div(total_supply)?.try_into()
}
}
/// Reserve collateral
#[derive(Clone, Debug, Default, PartialEq)]
pub struct ReserveCollateral {
/// Reserve collateral mint address
pub mint_pubkey: Pubkey,
/// Reserve collateral mint supply, used for exchange rate
pub mint_total_supply: u64,
/// Reserve collateral supply address
pub supply_pubkey: Pubkey,
/// Reserve collateral fees receiver address
pub fees_receiver: Pubkey,
}
impl ReserveCollateral {
/// New reserve collateral info
pub fn new(mint_pubkey: Pubkey, supply_pubkey: Pubkey, fees_receiver: Pubkey) -> Self {
Self {
mint_pubkey,
supply_pubkey,
fees_receiver,
..Self::default()
}
}
/// Return the current collateral exchange rate.
fn exchange_rate(
&self,
total_liquidity: Decimal,
) -> Result<CollateralExchangeRate, ProgramError> {
let rate = if self.mint_total_supply == 0 || total_liquidity == Decimal::zero() {
Rate::from_scaled_val(INITIAL_COLLATERAL_RATE)
} else {
let collateral_supply = Decimal::from(self.mint_total_supply);
Rate::try_from(collateral_supply.try_div(total_liquidity)?)?
};
Ok(CollateralExchangeRate(rate))
}
}
/// Collateral exchange rate
pub struct CollateralExchangeRate(Rate);
impl CollateralExchangeRate {
/// Convert reserve collateral to liquidity
pub fn collateral_to_liquidity(&self, collateral_amount: u64) -> Result<u64, ProgramError> {
Decimal::from(collateral_amount)
.try_div(self.0)?
.try_round_u64()
}
/// Convert reserve collateral to liquidity
pub fn decimal_collateral_to_liquidity(
&self,
collateral_amount: Decimal,
) -> Result<Decimal, ProgramError> {
collateral_amount.try_div(self.0)
}
/// Convert reserve liquidity to collateral
pub fn liquidity_to_collateral(&self, liquidity_amount: u64) -> Result<u64, ProgramError> {
self.0.try_mul(liquidity_amount)?.try_round_u64()
}
/// Convert reserve liquidity to collateral
pub fn decimal_liquidity_to_collateral(
&self,
liquidity_amount: Decimal,
) -> Result<Decimal, ProgramError> {
liquidity_amount.try_mul(self.0)
}
}
impl From<CollateralExchangeRate> for Rate {
fn from(exchange_rate: CollateralExchangeRate) -> Self {
exchange_rate.0
}
}
/// Reserve configuration values
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct ReserveConfig {
/// Optimal utilization rate as a percent
pub optimal_utilization_rate: u8,
/// The ratio of the loan to the value of the collateral as a percent
pub loan_to_value_ratio: u8,
/// The percent discount the liquidator gets when buying collateral for an unhealthy obligation
pub liquidation_bonus: u8,
/// The percent at which an obligation is considered unhealthy
pub liquidation_threshold: u8,
/// Min borrow APY
pub min_borrow_rate: u8,
/// Optimal (utilization) borrow APY
pub optimal_borrow_rate: u8,
/// Max borrow APY
pub max_borrow_rate: u8,
/// Program owner fees assessed, separate from gains due to interest accrual
pub fees: ReserveFees,
}
/// Additional fee information on a reserve /// Additional fee information on a reserve
/// ///
/// These exist separately from interest accrual fees, and are specifically for /// These exist separately from interest accrual fees, and are specifically for
@ -71,291 +400,6 @@ impl ReserveFees {
} }
} }
/// Reserve configuration values
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct ReserveConfig {
/// Optimal utilization rate as a percent
pub optimal_utilization_rate: u8,
/// The ratio of the loan to the value of the collateral as a percent
pub loan_to_value_ratio: u8,
/// The percent discount the liquidator gets when buying collateral for an unhealthy obligation
pub liquidation_bonus: u8,
/// The percent at which an obligation is considered unhealthy
pub liquidation_threshold: u8,
/// Min borrow APY
pub min_borrow_rate: u8,
/// Optimal (utilization) borrow APY
pub optimal_borrow_rate: u8,
/// Max borrow APY
pub max_borrow_rate: u8,
/// Program owner fees assessed, separate from gains due to interest accrual
pub fees: ReserveFees,
}
/// Reserve state
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct ReserveState {
/// Last slot when supply and rates updated
pub last_update_slot: Slot,
/// Cumulative borrow rate
pub cumulative_borrow_rate_wads: Decimal,
/// Borrowed liquidity, plus interest
pub borrowed_liquidity_wads: Decimal,
/// Available liquidity currently held in reserve
pub available_liquidity: u64,
/// Total collateral mint supply, used to calculate exchange rate
pub collateral_mint_supply: u64,
}
impl ReserveState {
/// Initialize new reserve state
pub fn new(current_slot: Slot, liquidity_amount: u64) -> Result<Self, ProgramError> {
let collateral_mint_supply = liquidity_amount
.checked_mul(INITIAL_COLLATERAL_RATIO)
.ok_or_else(|| {
msg!("Collateral token supply overflow");
ProgramError::from(LendingError::MathOverflow)
})?;
Ok(Self {
last_update_slot: current_slot,
cumulative_borrow_rate_wads: Decimal::one(),
available_liquidity: liquidity_amount,
collateral_mint_supply,
borrowed_liquidity_wads: Decimal::zero(),
})
}
}
/// Lending market reserve state
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Reserve {
/// Version of the struct
pub version: u8,
/// Lending market address
pub lending_market: Pubkey,
/// Reserve liquidity mint
pub liquidity_mint: Pubkey,
/// Reserve liquidity supply
pub liquidity_mint_decimals: u8,
/// Reserve liquidity supply
pub liquidity_supply: Pubkey,
/// Collateral tokens are minted when liquidity is deposited in the reserve.
/// Collateral tokens can be withdrawn back to the underlying liquidity token.
pub collateral_mint: Pubkey,
/// Reserve collateral supply
/// Collateral is stored rather than burned to keep an accurate total collateral supply
pub collateral_supply: Pubkey,
/// Collateral account receiving owner fees on liquidate and repay
pub collateral_fees_receiver: Pubkey,
/// Dex market state account
pub dex_market: COption<Pubkey>,
/// Reserve state
pub state: ReserveState,
/// Reserve configuration values
pub config: ReserveConfig,
}
/// Collateral exchange rate
pub struct CollateralExchangeRate(Rate);
impl CollateralExchangeRate {
/// Convert reserve collateral to liquidity
pub fn collateral_to_liquidity(&self, collateral_amount: u64) -> Result<u64, ProgramError> {
Decimal::from(collateral_amount)
.try_div(self.0)?
.try_round_u64()
}
/// Convert reserve collateral to liquidity
pub fn decimal_collateral_to_liquidity(
&self,
collateral_amount: Decimal,
) -> Result<Decimal, ProgramError> {
collateral_amount.try_div(self.0)
}
/// Convert reserve liquidity to collateral
pub fn liquidity_to_collateral(&self, liquidity_amount: u64) -> Result<u64, ProgramError> {
self.0.try_mul(liquidity_amount)?.try_round_u64()
}
/// Convert reserve liquidity to collateral
pub fn decimal_liquidity_to_collateral(
&self,
liquidity_amount: Decimal,
) -> Result<Decimal, ProgramError> {
liquidity_amount.try_mul(self.0)
}
}
impl From<CollateralExchangeRate> for Rate {
fn from(exchange_rate: CollateralExchangeRate) -> Self {
exchange_rate.0
}
}
impl ReserveState {
/// Add new borrow amount to total borrows
pub fn add_borrow(&mut self, borrow_amount: u64) -> ProgramResult {
if borrow_amount > self.available_liquidity {
return Err(LendingError::InsufficientLiquidity.into());
}
self.available_liquidity -= borrow_amount;
self.borrowed_liquidity_wads = self
.borrowed_liquidity_wads
.try_add(Decimal::from(borrow_amount))?;
Ok(())
}
/// Subtract repay amount from total borrows and return rounded repay value
pub fn subtract_repay(&mut self, repay_amount: Decimal) -> Result<u64, ProgramError> {
let rounded_repay_amount = repay_amount.try_round_u64()?;
if rounded_repay_amount == 0 {
return Err(LendingError::ObligationTooSmall.into());
}
self.available_liquidity = self
.available_liquidity
.checked_add(rounded_repay_amount)
.ok_or(LendingError::MathOverflow)?;
self.borrowed_liquidity_wads = self.borrowed_liquidity_wads.try_sub(repay_amount)?;
Ok(rounded_repay_amount)
}
/// Calculate the current utilization rate of the reserve
pub fn current_utilization_rate(&self) -> Result<Rate, ProgramError> {
let available_liquidity = Decimal::from(self.available_liquidity);
let total_supply = self.borrowed_liquidity_wads.try_add(available_liquidity)?;
let zero = Decimal::zero();
if total_supply == zero {
return Ok(Rate::zero());
}
self.borrowed_liquidity_wads
.try_div(total_supply)?
.try_into()
}
/// Return the current collateral exchange rate.
pub fn collateral_exchange_rate(&self) -> Result<CollateralExchangeRate, ProgramError> {
let rate = if self.collateral_mint_supply == 0 {
Rate::from_scaled_val(INITIAL_COLLATERAL_RATE)
} else {
let collateral_supply = Decimal::from(self.collateral_mint_supply);
let total_supply = self
.borrowed_liquidity_wads
.try_add(Decimal::from(self.available_liquidity))?;
if total_supply == Decimal::zero() {
Rate::from_scaled_val(INITIAL_COLLATERAL_RATE)
} else {
Rate::try_from(collateral_supply.try_div(total_supply)?)?
}
};
Ok(CollateralExchangeRate(rate))
}
/// Return slots elapsed since last update
fn update_slot(&mut self, slot: Slot) -> u64 {
let slots_elapsed = slot - self.last_update_slot;
self.last_update_slot = slot;
slots_elapsed
}
/// Compound current borrow rate over elapsed slots
fn compound_interest(
&mut self,
current_borrow_rate: Rate,
slots_elapsed: u64,
) -> Result<Rate, ProgramError> {
let slot_interest_rate: Rate = current_borrow_rate.try_div(SLOTS_PER_YEAR)?;
let compounded_interest_rate = Rate::one()
.try_add(slot_interest_rate)?
.try_pow(slots_elapsed)?;
self.cumulative_borrow_rate_wads = self
.cumulative_borrow_rate_wads
.try_mul(compounded_interest_rate)?;
Ok(compounded_interest_rate)
}
}
impl Reserve {
/// Calculate the current borrow rate
pub fn current_borrow_rate(&self) -> Result<Rate, ProgramError> {
let utilization_rate = self.state.current_utilization_rate()?;
let optimal_utilization_rate = Rate::from_percent(self.config.optimal_utilization_rate);
let low_utilization = utilization_rate < optimal_utilization_rate;
if low_utilization || self.config.optimal_utilization_rate == 100 {
let normalized_rate = utilization_rate.try_div(optimal_utilization_rate)?;
let min_rate = Rate::from_percent(self.config.min_borrow_rate);
let rate_range =
Rate::from_percent(self.config.optimal_borrow_rate - self.config.min_borrow_rate);
Ok(normalized_rate.try_mul(rate_range)?.try_add(min_rate)?)
} else {
let normalized_rate = utilization_rate
.try_sub(optimal_utilization_rate)?
.try_div(Rate::from_percent(
100 - self.config.optimal_utilization_rate,
))?;
let min_rate = Rate::from_percent(self.config.optimal_borrow_rate);
let rate_range =
Rate::from_percent(self.config.max_borrow_rate - self.config.optimal_borrow_rate);
Ok(normalized_rate.try_mul(rate_range)?.try_add(min_rate)?)
}
}
/// Record deposited liquidity and return amount of collateral tokens to mint
pub fn deposit_liquidity(&mut self, liquidity_amount: u64) -> Result<u64, ProgramError> {
let collateral_exchange_rate = self.state.collateral_exchange_rate()?;
let collateral_amount =
collateral_exchange_rate.liquidity_to_collateral(liquidity_amount)?;
self.state.available_liquidity += liquidity_amount;
self.state.collateral_mint_supply += collateral_amount;
Ok(collateral_amount)
}
/// Record redeemed collateral and return amount of liquidity to withdraw
pub fn redeem_collateral(&mut self, collateral_amount: u64) -> Result<u64, ProgramError> {
let collateral_exchange_rate = self.state.collateral_exchange_rate()?;
let liquidity_amount =
collateral_exchange_rate.collateral_to_liquidity(collateral_amount)?;
if liquidity_amount > self.state.available_liquidity {
return Err(LendingError::InsufficientLiquidity.into());
}
self.state.available_liquidity -= liquidity_amount;
self.state.collateral_mint_supply -= collateral_amount;
Ok(liquidity_amount)
}
/// Update borrow rate and accrue interest
pub fn accrue_interest(&mut self, current_slot: Slot) -> Result<(), ProgramError> {
let slots_elapsed = self.state.update_slot(current_slot);
if slots_elapsed > 0 {
let current_borrow_rate = self.current_borrow_rate()?;
let compounded_interest_rate = self
.state
.compound_interest(current_borrow_rate, slots_elapsed)?;
self.state.borrowed_liquidity_wads = self
.state
.borrowed_liquidity_wads
.try_mul(compounded_interest_rate)?;
}
Ok(())
}
}
impl Sealed for Reserve {} impl Sealed for Reserve {}
impl IsInitialized for Reserve { impl IsInitialized for Reserve {
fn is_initialized(&self) -> bool { fn is_initialized(&self) -> bool {
@ -402,14 +446,23 @@ impl Pack for Reserve {
]; ];
Ok(Self { Ok(Self {
version: u8::from_le_bytes(*version), version: u8::from_le_bytes(*version),
last_update_slot: u64::from_le_bytes(*last_update_slot),
cumulative_borrow_rate_wads: unpack_decimal(cumulative_borrow_rate),
lending_market: Pubkey::new_from_array(*lending_market), lending_market: Pubkey::new_from_array(*lending_market),
liquidity_mint: Pubkey::new_from_array(*liquidity_mint),
liquidity_mint_decimals: u8::from_le_bytes(*liquidity_mint_decimals),
liquidity_supply: Pubkey::new_from_array(*liquidity_supply),
collateral_mint: Pubkey::new_from_array(*collateral_mint),
collateral_supply: Pubkey::new_from_array(*collateral_supply),
collateral_fees_receiver: Pubkey::new_from_array(*collateral_fees_receiver),
dex_market: unpack_coption_key(dex_market)?, dex_market: unpack_coption_key(dex_market)?,
liquidity: ReserveLiquidity {
mint_pubkey: Pubkey::new_from_array(*liquidity_mint),
mint_decimals: u8::from_le_bytes(*liquidity_mint_decimals),
supply_pubkey: Pubkey::new_from_array(*liquidity_supply),
available_amount: u64::from_le_bytes(*available_liquidity),
borrowed_amount_wads: unpack_decimal(total_borrows),
},
collateral: ReserveCollateral {
mint_pubkey: Pubkey::new_from_array(*collateral_mint),
mint_total_supply: u64::from_le_bytes(*collateral_mint_supply),
supply_pubkey: Pubkey::new_from_array(*collateral_supply),
fees_receiver: Pubkey::new_from_array(*collateral_fees_receiver),
},
config: ReserveConfig { config: ReserveConfig {
optimal_utilization_rate: u8::from_le_bytes(*optimal_utilization_rate), optimal_utilization_rate: u8::from_le_bytes(*optimal_utilization_rate),
loan_to_value_ratio: u8::from_le_bytes(*loan_to_value_ratio), loan_to_value_ratio: u8::from_le_bytes(*loan_to_value_ratio),
@ -423,13 +476,6 @@ impl Pack for Reserve {
host_fee_percentage: u8::from_le_bytes(*host_fee_percentage), host_fee_percentage: u8::from_le_bytes(*host_fee_percentage),
}, },
}, },
state: ReserveState {
last_update_slot: u64::from_le_bytes(*last_update_slot),
cumulative_borrow_rate_wads: unpack_decimal(cumulative_borrow_rate),
borrowed_liquidity_wads: unpack_decimal(total_borrows),
available_liquidity: u64::from_le_bytes(*available_liquidity),
collateral_mint_supply: u64::from_le_bytes(*collateral_mint_supply),
},
}) })
} }
@ -465,15 +511,25 @@ impl Pack for Reserve {
300 300
]; ];
*version = self.version.to_le_bytes(); *version = self.version.to_le_bytes();
*last_update_slot = self.state.last_update_slot.to_le_bytes(); *last_update_slot = self.last_update_slot.to_le_bytes();
pack_decimal(self.cumulative_borrow_rate_wads, cumulative_borrow_rate);
lending_market.copy_from_slice(self.lending_market.as_ref()); lending_market.copy_from_slice(self.lending_market.as_ref());
liquidity_mint.copy_from_slice(self.liquidity_mint.as_ref());
*liquidity_mint_decimals = self.liquidity_mint_decimals.to_le_bytes();
liquidity_supply.copy_from_slice(self.liquidity_supply.as_ref());
collateral_mint.copy_from_slice(self.collateral_mint.as_ref());
collateral_supply.copy_from_slice(self.collateral_supply.as_ref());
collateral_fees_receiver.copy_from_slice(self.collateral_fees_receiver.as_ref());
pack_coption_key(&self.dex_market, dex_market); pack_coption_key(&self.dex_market, dex_market);
// liquidity info
liquidity_mint.copy_from_slice(self.liquidity.mint_pubkey.as_ref());
*liquidity_mint_decimals = self.liquidity.mint_decimals.to_le_bytes();
liquidity_supply.copy_from_slice(self.liquidity.supply_pubkey.as_ref());
*available_liquidity = self.liquidity.available_amount.to_le_bytes();
pack_decimal(self.liquidity.borrowed_amount_wads, total_borrows);
// collateral info
collateral_mint.copy_from_slice(self.collateral.mint_pubkey.as_ref());
collateral_supply.copy_from_slice(self.collateral.supply_pubkey.as_ref());
collateral_fees_receiver.copy_from_slice(self.collateral.fees_receiver.as_ref());
*collateral_mint_supply = self.collateral.mint_total_supply.to_le_bytes();
// config
*optimal_utilization_rate = self.config.optimal_utilization_rate.to_le_bytes(); *optimal_utilization_rate = self.config.optimal_utilization_rate.to_le_bytes();
*loan_to_value_ratio = self.config.loan_to_value_ratio.to_le_bytes(); *loan_to_value_ratio = self.config.loan_to_value_ratio.to_le_bytes();
*liquidation_bonus = self.config.liquidation_bonus.to_le_bytes(); *liquidation_bonus = self.config.liquidation_bonus.to_le_bytes();
@ -483,13 +539,6 @@ impl Pack for Reserve {
*max_borrow_rate = self.config.max_borrow_rate.to_le_bytes(); *max_borrow_rate = self.config.max_borrow_rate.to_le_bytes();
*borrow_fee_wad = self.config.fees.borrow_fee_wad.to_le_bytes(); *borrow_fee_wad = self.config.fees.borrow_fee_wad.to_le_bytes();
*host_fee_percentage = self.config.fees.host_fee_percentage.to_le_bytes(); *host_fee_percentage = self.config.fees.host_fee_percentage.to_le_bytes();
pack_decimal(
self.state.cumulative_borrow_rate_wads,
cumulative_borrow_rate,
);
pack_decimal(self.state.borrowed_liquidity_wads, total_borrows);
*available_liquidity = self.state.available_liquidity.to_le_bytes();
*collateral_mint_supply = self.state.collateral_mint_supply.to_le_bytes();
} }
} }
@ -507,15 +556,14 @@ mod test {
total_liquidity in 0..=MAX_LIQUIDITY, total_liquidity in 0..=MAX_LIQUIDITY,
borrowed_percent in 0..=WAD, borrowed_percent in 0..=WAD,
) { ) {
let borrowed_liquidity_wads = Decimal::from(total_liquidity).try_mul(Rate::from_scaled_val(borrowed_percent))?; let borrowed_amount_wads = Decimal::from(total_liquidity).try_mul(Rate::from_scaled_val(borrowed_percent))?;
let available_liquidity = total_liquidity - borrowed_liquidity_wads.try_round_u64()?; let liquidity = ReserveLiquidity {
let state = ReserveState { borrowed_amount_wads,
borrowed_liquidity_wads, available_amount: total_liquidity - borrowed_amount_wads.try_round_u64()?,
available_liquidity, ..ReserveLiquidity::default()
..ReserveState::default()
}; };
let current_rate = state.current_utilization_rate()?; let current_rate = liquidity.utilization_rate()?;
assert!(current_rate <= Rate::one()); assert!(current_rate <= Rate::one());
} }
@ -528,13 +576,17 @@ mod test {
) { ) {
let borrowed_liquidity_wads = Decimal::from(total_liquidity).try_mul(Rate::from_scaled_val(borrowed_percent))?; let borrowed_liquidity_wads = Decimal::from(total_liquidity).try_mul(Rate::from_scaled_val(borrowed_percent))?;
let available_liquidity = total_liquidity - borrowed_liquidity_wads.try_round_u64()?; let available_liquidity = total_liquidity - borrowed_liquidity_wads.try_round_u64()?;
let collateral_mint_supply = Decimal::from(total_liquidity).try_mul(Rate::from_scaled_val(collateral_multiplier))?.try_round_u64()?; let mint_total_supply = Decimal::from(total_liquidity).try_mul(Rate::from_scaled_val(collateral_multiplier))?.try_round_u64()?;
let mut reserve = Reserve { let mut reserve = Reserve {
state: ReserveState { collateral: ReserveCollateral {
borrowed_liquidity_wads, mint_total_supply,
available_liquidity, ..ReserveCollateral::default()
collateral_mint_supply, },
..ReserveState::default() liquidity: ReserveLiquidity {
borrowed_amount_wads: borrowed_liquidity_wads,
available_amount: available_liquidity,
..ReserveLiquidity::default()
}, },
config: ReserveConfig { config: ReserveConfig {
min_borrow_rate: borrow_rate, min_borrow_rate: borrow_rate,
@ -545,13 +597,13 @@ mod test {
..Reserve::default() ..Reserve::default()
}; };
let exchange_rate = reserve.state.collateral_exchange_rate()?; let exchange_rate = reserve.collateral_exchange_rate()?;
assert!(exchange_rate.0.to_scaled_val() <= 5u128 * WAD as u128); assert!(exchange_rate.0.to_scaled_val() <= 5u128 * WAD as u128);
// After interest accrual, collateral tokens should be worth more // After interest accrual, total liquidity increases and collateral are worth more
reserve.accrue_interest(SLOTS_PER_YEAR)?; reserve.accrue_interest(1)?;
let new_exchange_rate = reserve.state.collateral_exchange_rate()?; let new_exchange_rate = reserve.collateral_exchange_rate()?;
if borrow_rate > 0 && total_liquidity > 0 && borrowed_percent > 0 { if borrow_rate > 0 && total_liquidity > 0 && borrowed_percent > 0 {
assert!(new_exchange_rate.0 < exchange_rate.0); assert!(new_exchange_rate.0 < exchange_rate.0);
} else { } else {
@ -564,14 +616,14 @@ mod test {
slots_elapsed in 0..=SLOTS_PER_YEAR, slots_elapsed in 0..=SLOTS_PER_YEAR,
borrow_rate in 0..=u8::MAX, borrow_rate in 0..=u8::MAX,
) { ) {
let mut state = ReserveState::default(); let mut reserve = Reserve::default();
let borrow_rate = Rate::from_percent(borrow_rate); let borrow_rate = Rate::from_percent(borrow_rate);
// Simulate running for max 1000 years, assuming that interest is // Simulate running for max 1000 years, assuming that interest is
// compounded at least once a year // compounded at least once a year
for _ in 0..1000 { for _ in 0..1000 {
state.compound_interest(borrow_rate, slots_elapsed)?; reserve.compound_interest(borrow_rate, slots_elapsed)?;
state.cumulative_borrow_rate_wads.to_scaled_val()?; reserve.cumulative_borrow_rate_wads.to_scaled_val()?;
} }
} }
@ -581,11 +633,11 @@ mod test {
borrowed_liquidity in 0..=u64::MAX, borrowed_liquidity in 0..=u64::MAX,
borrow_rate in 0..=u8::MAX, borrow_rate in 0..=u8::MAX,
) { ) {
let borrowed_liquidity_wads = Decimal::from(borrowed_liquidity); let borrowed_amount_wads = Decimal::from(borrowed_liquidity);
let mut reserve = Reserve { let mut reserve = Reserve {
state: ReserveState { liquidity: ReserveLiquidity {
borrowed_liquidity_wads, borrowed_amount_wads,
..ReserveState::default() ..ReserveLiquidity::default()
}, },
config: ReserveConfig { config: ReserveConfig {
max_borrow_rate: borrow_rate, max_borrow_rate: borrow_rate,
@ -597,9 +649,9 @@ mod test {
reserve.accrue_interest(slots_elapsed)?; reserve.accrue_interest(slots_elapsed)?;
if borrow_rate > 0 && slots_elapsed > 0 { if borrow_rate > 0 && slots_elapsed > 0 {
assert!(reserve.state.borrowed_liquidity_wads > borrowed_liquidity_wads); assert!(reserve.liquidity.borrowed_amount_wads > borrowed_amount_wads);
} else { } else {
assert!(reserve.state.borrowed_liquidity_wads == borrowed_liquidity_wads); assert!(reserve.liquidity.borrowed_amount_wads == borrowed_amount_wads);
} }
} }

View File

@ -21,8 +21,8 @@ use spl_token_lending::{
math::Decimal, math::Decimal,
processor::process_instruction, processor::process_instruction,
state::{ state::{
LendingMarket, Obligation, Reserve, ReserveConfig, ReserveFees, ReserveState, LendingMarket, NewReserveParams, Obligation, Reserve, ReserveCollateral, ReserveConfig,
INITIAL_COLLATERAL_RATIO, PROGRAM_VERSION, ReserveFees, ReserveLiquidity, INITIAL_COLLATERAL_RATIO, PROGRAM_VERSION,
}, },
}; };
use std::str::FromStr; use std::str::FromStr;
@ -335,25 +335,31 @@ pub fn add_reserve(
let reserve_keypair = Keypair::new(); let reserve_keypair = Keypair::new();
let reserve_pubkey = reserve_keypair.pubkey(); let reserve_pubkey = reserve_keypair.pubkey();
let mut reserve_state = let reserve_liquidity = ReserveLiquidity::new(
ReserveState::new(1u64.wrapping_sub(slots_elapsed), liquidity_amount).unwrap(); liquidity_mint_pubkey,
reserve_state.add_borrow(borrow_amount).unwrap(); liquidity_mint_decimals,
liquidity_supply_pubkey,
);
let reserve_collateral = ReserveCollateral::new(
collateral_mint_pubkey,
collateral_supply_pubkey,
collateral_fees_receiver_pubkey,
);
let mut reserve = Reserve::new(NewReserveParams {
// intentionally wrapped to simulate elapsed slots
current_slot: 1u64.wrapping_sub(slots_elapsed),
lending_market: lending_market.pubkey,
dex_market: dex_market_pubkey.into(),
liquidity: reserve_liquidity,
collateral: reserve_collateral,
config,
});
reserve.deposit_liquidity(liquidity_amount).unwrap();
reserve.liquidity.borrow(borrow_amount).unwrap();
test.add_packable_account( test.add_packable_account(
reserve_pubkey, reserve_pubkey,
u32::MAX as u64, u32::MAX as u64,
&Reserve { &reserve,
version: PROGRAM_VERSION,
lending_market: lending_market.pubkey,
liquidity_mint: liquidity_mint_pubkey,
liquidity_mint_decimals,
liquidity_supply: liquidity_supply_pubkey,
collateral_mint: collateral_mint_pubkey,
collateral_supply: collateral_supply_pubkey,
collateral_fees_receiver: collateral_fees_receiver_pubkey,
dex_market: dex_market_pubkey.into(),
config,
state: reserve_state,
},
&spl_token_lending::id(), &spl_token_lending::id(),
); );
@ -1006,15 +1012,15 @@ impl TestReserve {
} }
pub async fn validate_state(&self, banks_client: &mut BanksClient) { pub async fn validate_state(&self, banks_client: &mut BanksClient) {
let reserve_state = self.get_state(banks_client).await; let reserve = self.get_state(banks_client).await;
assert!(reserve_state.state.last_update_slot > 0); assert!(reserve.last_update_slot > 0);
assert_eq!(PROGRAM_VERSION, reserve_state.version); assert_eq!(PROGRAM_VERSION, reserve.version);
assert_eq!(self.lending_market, reserve_state.lending_market); assert_eq!(self.lending_market, reserve.lending_market);
assert_eq!(self.liquidity_mint, reserve_state.liquidity_mint); assert_eq!(self.liquidity_mint, reserve.liquidity.mint_pubkey);
assert_eq!(self.liquidity_supply, reserve_state.liquidity_supply); assert_eq!(self.liquidity_supply, reserve.liquidity.supply_pubkey);
assert_eq!(self.collateral_mint, reserve_state.collateral_mint); assert_eq!(self.collateral_mint, reserve.collateral.mint_pubkey);
assert_eq!(self.collateral_supply, reserve_state.collateral_supply); assert_eq!(self.collateral_supply, reserve.collateral.supply_pubkey);
assert_eq!(self.config, reserve_state.config); assert_eq!(self.config, reserve.config);
let dex_market_coption = if let Some(dex_market_pubkey) = self.dex_market { let dex_market_coption = if let Some(dex_market_pubkey) = self.dex_market {
COption::Some(dex_market_pubkey) COption::Some(dex_market_pubkey)
@ -1022,14 +1028,11 @@ impl TestReserve {
COption::None COption::None
}; };
assert_eq!(dex_market_coption, reserve_state.dex_market); assert_eq!(dex_market_coption, reserve.dex_market);
assert_eq!( assert_eq!(reserve.cumulative_borrow_rate_wads, Decimal::one());
reserve_state.state.cumulative_borrow_rate_wads, assert_eq!(reserve.liquidity.borrowed_amount_wads, Decimal::zero());
Decimal::one() assert!(reserve.liquidity.available_amount > 0);
); assert!(reserve.collateral.mint_total_supply > 0);
assert_eq!(reserve_state.state.borrowed_liquidity_wads, Decimal::zero());
assert!(reserve_state.state.available_liquidity > 0);
assert!(reserve_state.state.collateral_mint_supply > 0);
} }
} }

View File

@ -26,7 +26,7 @@ async fn test_success() {
); );
// limit to track compute unit increase // limit to track compute unit increase
test.set_bpf_compute_max_units(66_000); test.set_bpf_compute_max_units(70_000);
let user_accounts_owner = Keypair::new(); let user_accounts_owner = Keypair::new();
let sol_usdc_dex_market = TestDexMarket::setup(&mut test, TestDexMarketPair::SOL_USDC); let sol_usdc_dex_market = TestDexMarket::setup(&mut test, TestDexMarketPair::SOL_USDC);

View File

@ -26,7 +26,7 @@ async fn test_success() {
); );
// limit to track compute unit increase // limit to track compute unit increase
test.set_bpf_compute_max_units(165_000); test.set_bpf_compute_max_units(163_000);
// set loan values to about 90% of collateral value so that it gets liquidated // set loan values to about 90% of collateral value so that it gets liquidated
const USDC_LOAN: u64 = 2 * FRACTIONAL_TO_USDC; const USDC_LOAN: u64 = 2 * FRACTIONAL_TO_USDC;

View File

@ -144,16 +144,16 @@ async fn test_success() {
assert!(collateral_received > 0); assert!(collateral_received > 0);
let borrow_reserve_state = usdc_reserve.get_state(&mut banks_client).await; let borrow_reserve_state = usdc_reserve.get_state(&mut banks_client).await;
assert!(borrow_reserve_state.state.cumulative_borrow_rate_wads > Decimal::one()); assert!(borrow_reserve_state.cumulative_borrow_rate_wads > Decimal::one());
let obligation_state = obligation.get_state(&mut banks_client).await; let obligation_state = obligation.get_state(&mut banks_client).await;
assert_eq!( assert_eq!(
obligation_state.cumulative_borrow_rate_wads, obligation_state.cumulative_borrow_rate_wads,
borrow_reserve_state.state.cumulative_borrow_rate_wads borrow_reserve_state.cumulative_borrow_rate_wads
); );
assert_eq!( assert_eq!(
obligation_state.borrowed_liquidity_wads, obligation_state.borrowed_liquidity_wads,
borrow_reserve_state.state.borrowed_liquidity_wads borrow_reserve_state.liquidity.borrowed_amount_wads
); );
// use cumulative borrow rate directly since test rate starts at 1.0 // use cumulative borrow rate directly since test rate starts at 1.0