lending: Add InitObligation instruction (#1088)
* lending: Add InitObligation instruction * fix: accrue interest on deposit to correct exchange rate
This commit is contained in:
parent
fdf2f1f909
commit
bbd4c63b83
|
@ -63,6 +63,21 @@ pub enum LendingInstruction {
|
|||
config: ReserveConfig,
|
||||
},
|
||||
|
||||
/// Initializes a new loan obligation.
|
||||
///
|
||||
/// 0. `[]` Deposit reserve account.
|
||||
/// 1. `[writable]` Borrow reserve account.
|
||||
/// 2. `[writable]` Obligation
|
||||
/// 3. `[writable]` Obligation token mint
|
||||
/// 4. `[writable]` Obligation token output
|
||||
/// 5. `[]` Obligation token owner
|
||||
/// 6. `[]` Lending market account.
|
||||
/// 7. `[]` Derived lending market authority.
|
||||
/// 8. `[]` Clock sysvar
|
||||
/// 9. `[]` Rent sysvar
|
||||
/// 10 '[]` Token program id
|
||||
InitObligation,
|
||||
|
||||
/// Deposit liquidity into a reserve. The output is a collateral token representing ownership
|
||||
/// of the reserve liquidity pool.
|
||||
///
|
||||
|
@ -113,17 +128,15 @@ pub enum LendingInstruction {
|
|||
/// 7. `[writable]` Obligation
|
||||
/// 8. `[writable]` Obligation token mint
|
||||
/// 9. `[writable]` Obligation token output
|
||||
/// 10 `[]` Obligation token owner
|
||||
/// 11 `[]` Lending market account.
|
||||
/// 12 `[]` Derived lending market authority.
|
||||
/// 13 `[]` User transfer authority ($authority).
|
||||
/// 14 `[]` Dex market
|
||||
/// 15 `[]` Dex market order book side
|
||||
/// 16 `[]` Temporary memory
|
||||
/// 17 `[]` Clock sysvar
|
||||
/// 18 `[]` Rent sysvar
|
||||
/// 19 '[]` Token program id
|
||||
/// 20 `[optional, writable]` Deposit reserve collateral host fee receiver account.
|
||||
/// 10 `[]` Lending market account.
|
||||
/// 11 `[]` Derived lending market authority.
|
||||
/// 12 `[]` User transfer authority ($authority).
|
||||
/// 13 `[]` Dex market
|
||||
/// 14 `[]` Dex market order book side
|
||||
/// 15 `[]` Temporary memory
|
||||
/// 16 `[]` Clock sysvar
|
||||
/// 17 '[]` Token program id
|
||||
/// 18 `[optional, writable]` Deposit reserve collateral host fee receiver account.
|
||||
BorrowReserveLiquidity {
|
||||
// TODO: slippage constraint
|
||||
/// Amount whose usage depends on `amount_type`
|
||||
|
@ -218,15 +231,16 @@ impl LendingInstruction {
|
|||
},
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
2 => Self::InitObligation,
|
||||
3 => {
|
||||
let (liquidity_amount, _rest) = Self::unpack_u64(rest)?;
|
||||
Self::DepositReserveLiquidity { liquidity_amount }
|
||||
}
|
||||
3 => {
|
||||
4 => {
|
||||
let (collateral_amount, _rest) = Self::unpack_u64(rest)?;
|
||||
Self::WithdrawReserveLiquidity { collateral_amount }
|
||||
}
|
||||
4 => {
|
||||
5 => {
|
||||
let (amount, rest) = Self::unpack_u64(rest)?;
|
||||
let (amount_type, _rest) = Self::unpack_u8(rest)?;
|
||||
let amount_type = BorrowAmountType::from_u8(amount_type)
|
||||
|
@ -236,11 +250,11 @@ impl LendingInstruction {
|
|||
amount_type,
|
||||
}
|
||||
}
|
||||
5 => {
|
||||
6 => {
|
||||
let (liquidity_amount, _rest) = Self::unpack_u64(rest)?;
|
||||
Self::RepayReserveLiquidity { liquidity_amount }
|
||||
}
|
||||
6 => {
|
||||
7 => {
|
||||
let (liquidity_amount, _rest) = Self::unpack_u64(rest)?;
|
||||
Self::LiquidateObligation { liquidity_amount }
|
||||
}
|
||||
|
@ -324,28 +338,31 @@ impl LendingInstruction {
|
|||
buf.extend_from_slice(&borrow_fee_wad.to_le_bytes());
|
||||
buf.extend_from_slice(&host_fee_percentage.to_le_bytes());
|
||||
}
|
||||
Self::DepositReserveLiquidity { liquidity_amount } => {
|
||||
Self::InitObligation => {
|
||||
buf.push(2);
|
||||
}
|
||||
Self::DepositReserveLiquidity { liquidity_amount } => {
|
||||
buf.push(3);
|
||||
buf.extend_from_slice(&liquidity_amount.to_le_bytes());
|
||||
}
|
||||
Self::WithdrawReserveLiquidity { collateral_amount } => {
|
||||
buf.push(3);
|
||||
buf.push(4);
|
||||
buf.extend_from_slice(&collateral_amount.to_le_bytes());
|
||||
}
|
||||
Self::BorrowReserveLiquidity {
|
||||
amount,
|
||||
amount_type,
|
||||
} => {
|
||||
buf.push(4);
|
||||
buf.push(5);
|
||||
buf.extend_from_slice(&amount.to_le_bytes());
|
||||
buf.extend_from_slice(&amount_type.to_u8().unwrap().to_le_bytes());
|
||||
}
|
||||
Self::RepayReserveLiquidity { liquidity_amount } => {
|
||||
buf.push(5);
|
||||
buf.push(6);
|
||||
buf.extend_from_slice(&liquidity_amount.to_le_bytes());
|
||||
}
|
||||
Self::LiquidateObligation { liquidity_amount } => {
|
||||
buf.push(6);
|
||||
buf.push(7);
|
||||
buf.extend_from_slice(&liquidity_amount.to_le_bytes());
|
||||
}
|
||||
}
|
||||
|
@ -429,6 +446,40 @@ pub fn init_reserve(
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates an 'InitObligation' instruction.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn init_obligation(
|
||||
program_id: Pubkey,
|
||||
deposit_reserve_pubkey: Pubkey,
|
||||
borrow_reserve_pubkey: Pubkey,
|
||||
lending_market_pubkey: Pubkey,
|
||||
obligation_pubkey: Pubkey,
|
||||
obligation_token_mint_pubkey: Pubkey,
|
||||
obligation_token_output_pubkey: Pubkey,
|
||||
obligation_token_owner_pubkey: Pubkey,
|
||||
) -> Instruction {
|
||||
let (lending_market_authority_pubkey, _bump_seed) =
|
||||
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(obligation_pubkey, false),
|
||||
AccountMeta::new(obligation_token_mint_pubkey, false),
|
||||
AccountMeta::new(obligation_token_output_pubkey, false),
|
||||
AccountMeta::new_readonly(obligation_token_owner_pubkey, false),
|
||||
AccountMeta::new_readonly(lending_market_pubkey, false),
|
||||
AccountMeta::new_readonly(lending_market_authority_pubkey, false),
|
||||
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
||||
AccountMeta::new_readonly(sysvar::rent::id(), false),
|
||||
AccountMeta::new_readonly(spl_token::id(), false),
|
||||
];
|
||||
Instruction {
|
||||
program_id,
|
||||
accounts,
|
||||
data: LendingInstruction::InitObligation.pack(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a 'DepositReserveLiquidity' instruction.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn deposit_reserve_liquidity(
|
||||
|
@ -512,7 +563,6 @@ pub fn borrow_reserve_liquidity(
|
|||
obligation_pubkey: Pubkey,
|
||||
obligation_token_mint_pubkey: Pubkey,
|
||||
obligation_token_output_pubkey: Pubkey,
|
||||
obligation_token_owner_pubkey: Pubkey,
|
||||
dex_market_pubkey: Pubkey,
|
||||
dex_market_order_book_side_pubkey: Pubkey,
|
||||
memory_pubkey: Pubkey,
|
||||
|
@ -529,7 +579,6 @@ pub fn borrow_reserve_liquidity(
|
|||
AccountMeta::new(obligation_pubkey, false),
|
||||
AccountMeta::new(obligation_token_mint_pubkey, false),
|
||||
AccountMeta::new(obligation_token_output_pubkey, false),
|
||||
AccountMeta::new_readonly(obligation_token_owner_pubkey, false),
|
||||
AccountMeta::new_readonly(lending_market_pubkey, false),
|
||||
AccountMeta::new_readonly(lending_market_authority_pubkey, false),
|
||||
AccountMeta::new_readonly(user_transfer_authority_pubkey, true),
|
||||
|
@ -537,7 +586,6 @@ pub fn borrow_reserve_liquidity(
|
|||
AccountMeta::new_readonly(dex_market_order_book_side_pubkey, false),
|
||||
AccountMeta::new_readonly(memory_pubkey, false),
|
||||
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
||||
AccountMeta::new_readonly(sysvar::rent::id(), false),
|
||||
AccountMeta::new_readonly(spl_token::id(), false),
|
||||
];
|
||||
if let Some(deposit_reserve_collateral_host_pubkey) = deposit_reserve_collateral_host_pubkey {
|
||||
|
|
|
@ -6,8 +6,8 @@ use crate::{
|
|||
instruction::{BorrowAmountType, LendingInstruction},
|
||||
math::{Decimal, Rate, TryAdd, TryMul, TrySub, WAD},
|
||||
state::{
|
||||
LendingMarket, NewReserveParams, Obligation, RepayResult, Reserve, ReserveCollateral,
|
||||
ReserveConfig, ReserveLiquidity, PROGRAM_VERSION,
|
||||
LendingMarket, NewObligationParams, NewReserveParams, Obligation, RepayResult, Reserve,
|
||||
ReserveCollateral, ReserveConfig, ReserveLiquidity, PROGRAM_VERSION,
|
||||
},
|
||||
};
|
||||
use num_traits::FromPrimitive;
|
||||
|
@ -44,6 +44,10 @@ pub fn process_instruction(
|
|||
msg!("Instruction: Init Reserve");
|
||||
process_init_reserve(program_id, liquidity_amount, config, accounts)
|
||||
}
|
||||
LendingInstruction::InitObligation => {
|
||||
msg!("Instruction: Init Obligation");
|
||||
process_init_obligation(program_id, accounts)
|
||||
}
|
||||
LendingInstruction::DepositReserveLiquidity { liquidity_amount } => {
|
||||
msg!("Instruction: Deposit");
|
||||
process_deposit(program_id, liquidity_amount, accounts)
|
||||
|
@ -292,6 +296,105 @@ fn process_init_reserve(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(never)] // avoid stack frame limit
|
||||
fn process_init_obligation(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
|
||||
let account_info_iter = &mut accounts.iter();
|
||||
let deposit_reserve_info = next_account_info(account_info_iter)?;
|
||||
let borrow_reserve_info = next_account_info(account_info_iter)?;
|
||||
let obligation_info = next_account_info(account_info_iter)?;
|
||||
let obligation_token_mint_info = next_account_info(account_info_iter)?;
|
||||
let obligation_token_output_info = next_account_info(account_info_iter)?;
|
||||
let obligation_token_owner_info = next_account_info(account_info_iter)?;
|
||||
let lending_market_info = next_account_info(account_info_iter)?;
|
||||
let lending_market_authority_info = next_account_info(account_info_iter)?;
|
||||
let clock = &Clock::from_account_info(next_account_info(account_info_iter)?)?;
|
||||
let rent_info = next_account_info(account_info_iter)?;
|
||||
let rent = &Rent::from_account_info(rent_info)?;
|
||||
let token_program_id = next_account_info(account_info_iter)?;
|
||||
|
||||
let lending_market = LendingMarket::unpack(&lending_market_info.data.borrow())?;
|
||||
if lending_market_info.owner != program_id {
|
||||
return Err(LendingError::InvalidAccountOwner.into());
|
||||
}
|
||||
if &lending_market.token_program_id != token_program_id.key {
|
||||
return Err(LendingError::InvalidTokenProgram.into());
|
||||
}
|
||||
|
||||
let deposit_reserve = Reserve::unpack(&deposit_reserve_info.data.borrow())?;
|
||||
if deposit_reserve_info.owner != program_id {
|
||||
return Err(LendingError::InvalidAccountOwner.into());
|
||||
}
|
||||
if &deposit_reserve.lending_market != lending_market_info.key {
|
||||
msg!("Invalid reserve lending market account");
|
||||
return Err(LendingError::InvalidAccountInput.into());
|
||||
}
|
||||
|
||||
let mut borrow_reserve = Reserve::unpack(&borrow_reserve_info.data.borrow())?;
|
||||
if borrow_reserve_info.owner != program_id {
|
||||
return Err(LendingError::InvalidAccountOwner.into());
|
||||
}
|
||||
if borrow_reserve.lending_market != deposit_reserve.lending_market {
|
||||
return Err(LendingError::LendingMarketMismatch.into());
|
||||
}
|
||||
|
||||
if deposit_reserve.config.loan_to_value_ratio == 0 {
|
||||
return Err(LendingError::ReserveCollateralDisabled.into());
|
||||
}
|
||||
if deposit_reserve_info.key == borrow_reserve_info.key {
|
||||
return Err(LendingError::DuplicateReserve.into());
|
||||
}
|
||||
if deposit_reserve.liquidity.mint_pubkey == borrow_reserve.liquidity.mint_pubkey {
|
||||
return Err(LendingError::DuplicateReserveMint.into());
|
||||
}
|
||||
|
||||
assert_rent_exempt(rent, obligation_info)?;
|
||||
assert_uninitialized::<Obligation>(obligation_info)?;
|
||||
|
||||
// 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,
|
||||
cumulative_borrow_rate_wads: cumulative_borrow_rate,
|
||||
borrow_reserve: *borrow_reserve_info.key,
|
||||
token_mint: *obligation_token_mint_info.key,
|
||||
});
|
||||
Obligation::pack(obligation, &mut obligation_info.data.borrow_mut())?;
|
||||
|
||||
let authority_signer_seeds = &[
|
||||
lending_market_info.key.as_ref(),
|
||||
&[lending_market.bump_seed],
|
||||
];
|
||||
let lending_market_authority_pubkey =
|
||||
Pubkey::create_program_address(authority_signer_seeds, program_id)?;
|
||||
if lending_market_authority_info.key != &lending_market_authority_pubkey {
|
||||
return Err(LendingError::InvalidMarketAuthority.into());
|
||||
}
|
||||
|
||||
// init obligation token mint
|
||||
spl_token_init_mint(TokenInitializeMintParams {
|
||||
mint: obligation_token_mint_info.clone(),
|
||||
authority: lending_market_authority_info.key,
|
||||
rent: rent_info.clone(),
|
||||
decimals: obligation_mint_decimals,
|
||||
token_program: token_program_id.clone(),
|
||||
})?;
|
||||
|
||||
// init obligation token output account
|
||||
spl_token_init_account(TokenInitializeAccountParams {
|
||||
account: obligation_token_output_info.clone(),
|
||||
mint: obligation_token_mint_info.clone(),
|
||||
owner: obligation_token_owner_info.clone(),
|
||||
rent: rent_info.clone(),
|
||||
token_program: token_program_id.clone(),
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_deposit(
|
||||
program_id: &Pubkey,
|
||||
liquidity_amount: u64,
|
||||
|
@ -492,7 +595,6 @@ fn process_borrow(
|
|||
let obligation_info = next_account_info(account_info_iter)?;
|
||||
let obligation_token_mint_info = next_account_info(account_info_iter)?;
|
||||
let obligation_token_output_info = next_account_info(account_info_iter)?;
|
||||
let obligation_token_owner_info = next_account_info(account_info_iter)?;
|
||||
let lending_market_info = next_account_info(account_info_iter)?;
|
||||
let lending_market_authority_info = next_account_info(account_info_iter)?;
|
||||
let user_transfer_authority_info = next_account_info(account_info_iter)?;
|
||||
|
@ -500,8 +602,6 @@ fn process_borrow(
|
|||
let dex_market_orders_info = next_account_info(account_info_iter)?;
|
||||
let memory = next_account_info(account_info_iter)?;
|
||||
let clock = &Clock::from_account_info(next_account_info(account_info_iter)?)?;
|
||||
let rent_info = next_account_info(account_info_iter)?;
|
||||
let rent = &Rent::from_account_info(rent_info)?;
|
||||
let token_program_id = next_account_info(account_info_iter)?;
|
||||
|
||||
let lending_market = LendingMarket::unpack(&lending_market_info.data.borrow())?;
|
||||
|
@ -575,10 +675,37 @@ fn process_borrow(
|
|||
}
|
||||
}
|
||||
|
||||
let mut obligation = Obligation::unpack(&obligation_info.data.borrow())?;
|
||||
if obligation_info.owner != program_id {
|
||||
return Err(LendingError::InvalidAccountOwner.into());
|
||||
}
|
||||
if &obligation.borrow_reserve != borrow_reserve_info.key {
|
||||
msg!("Borrow reserve input doesn't match existing obligation borrow reserve");
|
||||
return Err(LendingError::InvalidAccountInput.into());
|
||||
}
|
||||
if &obligation.collateral_reserve != deposit_reserve_info.key {
|
||||
msg!("Collateral reserve input doesn't match existing obligation collateral reserve");
|
||||
return Err(LendingError::InvalidAccountInput.into());
|
||||
}
|
||||
|
||||
unpack_mint(&obligation_token_mint_info.data.borrow())?;
|
||||
if &obligation.token_mint != obligation_token_mint_info.key {
|
||||
msg!("Obligation token mint input doesn't match existing obligation token mint");
|
||||
return Err(LendingError::InvalidTokenMint.into());
|
||||
}
|
||||
|
||||
let obligation_token_output = Token::unpack(&obligation_token_output_info.data.borrow())?;
|
||||
if obligation_token_output_info.owner != token_program_id.key {
|
||||
return Err(LendingError::InvalidTokenOwner.into());
|
||||
}
|
||||
if &obligation_token_output.mint != obligation_token_mint_info.key {
|
||||
return Err(LendingError::InvalidTokenMint.into());
|
||||
}
|
||||
|
||||
// accrue interest and update rates
|
||||
borrow_reserve.accrue_interest(clock.slot)?;
|
||||
deposit_reserve.accrue_interest(clock.slot)?;
|
||||
let cumulative_borrow_rate = borrow_reserve.cumulative_borrow_rate_wads;
|
||||
obligation.accrue_interest(borrow_reserve.cumulative_borrow_rate_wads)?;
|
||||
|
||||
let mut trade_simulator = TradeSimulator::new(
|
||||
dex_market_info,
|
||||
|
@ -603,45 +730,14 @@ fn process_borrow(
|
|||
|
||||
borrow_reserve.liquidity.borrow(borrow_amount)?;
|
||||
|
||||
let obligation_mint_decimals = deposit_reserve.liquidity.mint_decimals;
|
||||
obligation.borrowed_liquidity_wads = obligation
|
||||
.borrowed_liquidity_wads
|
||||
.try_add(Decimal::from(borrow_amount))?;
|
||||
obligation.deposited_collateral_tokens += collateral_deposit_amount;
|
||||
|
||||
Reserve::pack(deposit_reserve, &mut deposit_reserve_info.data.borrow_mut())?;
|
||||
Obligation::pack(obligation, &mut obligation_info.data.borrow_mut())?;
|
||||
Reserve::pack(borrow_reserve, &mut borrow_reserve_info.data.borrow_mut())?;
|
||||
|
||||
let mut obligation = Obligation::unpack_unchecked(&obligation_info.data.borrow())?;
|
||||
let reusing_obligation = obligation.is_initialized();
|
||||
if reusing_obligation {
|
||||
if &obligation.token_mint != obligation_token_mint_info.key {
|
||||
msg!("Obligation token mint input doesn't match existing obligation token mint");
|
||||
return Err(LendingError::InvalidAccountInput.into());
|
||||
}
|
||||
if &obligation.borrow_reserve != borrow_reserve_info.key {
|
||||
msg!("Borrow reserve input doesn't match existing obligation borrow reserve");
|
||||
return Err(LendingError::InvalidAccountInput.into());
|
||||
}
|
||||
if &obligation.collateral_reserve != deposit_reserve_info.key {
|
||||
msg!("Collateral reserve input doesn't match existing obligation collateral reserve");
|
||||
return Err(LendingError::InvalidAccountInput.into());
|
||||
}
|
||||
|
||||
obligation.accrue_interest(cumulative_borrow_rate)?;
|
||||
obligation.borrowed_liquidity_wads = obligation
|
||||
.borrowed_liquidity_wads
|
||||
.try_add(Decimal::from(borrow_amount))?;
|
||||
obligation.deposited_collateral_tokens += collateral_deposit_amount;
|
||||
Obligation::pack(obligation, &mut obligation_info.data.borrow_mut())?;
|
||||
} else {
|
||||
assert_rent_exempt(rent, obligation_info)?;
|
||||
let mut new_obligation = obligation;
|
||||
new_obligation.version = PROGRAM_VERSION;
|
||||
new_obligation.deposited_collateral_tokens = collateral_deposit_amount;
|
||||
new_obligation.collateral_reserve = *deposit_reserve_info.key;
|
||||
new_obligation.cumulative_borrow_rate_wads = cumulative_borrow_rate;
|
||||
new_obligation.borrowed_liquidity_wads = Decimal::from(borrow_amount);
|
||||
new_obligation.borrow_reserve = *borrow_reserve_info.key;
|
||||
new_obligation.token_mint = *obligation_token_mint_info.key;
|
||||
Obligation::pack(new_obligation, &mut obligation_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(),
|
||||
|
@ -700,44 +796,6 @@ fn process_borrow(
|
|||
token_program: token_program_id.clone(),
|
||||
})?;
|
||||
|
||||
if !reusing_obligation {
|
||||
// init obligation token mint
|
||||
spl_token_init_mint(TokenInitializeMintParams {
|
||||
mint: obligation_token_mint_info.clone(),
|
||||
authority: lending_market_authority_info.key,
|
||||
rent: rent_info.clone(),
|
||||
decimals: obligation_mint_decimals,
|
||||
token_program: token_program_id.clone(),
|
||||
})?;
|
||||
}
|
||||
|
||||
let obligation_token_output = if reusing_obligation {
|
||||
let obligation_token_output =
|
||||
Token::unpack_unchecked(&obligation_token_output_info.data.borrow())?;
|
||||
if obligation_token_output.is_initialized() {
|
||||
Some(obligation_token_output)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(token_output) = obligation_token_output {
|
||||
if &token_output.owner != obligation_token_owner_info.key {
|
||||
return Err(LendingError::ObligationTokenOwnerMismatch.into());
|
||||
}
|
||||
} else {
|
||||
// init obligation token output account
|
||||
spl_token_init_account(TokenInitializeAccountParams {
|
||||
account: obligation_token_output_info.clone(),
|
||||
mint: obligation_token_mint_info.clone(),
|
||||
owner: obligation_token_owner_info.clone(),
|
||||
rent: rent_info.clone(),
|
||||
token_program: token_program_id.clone(),
|
||||
})?;
|
||||
}
|
||||
|
||||
// mint obligation tokens to output account
|
||||
spl_token_mint_to(TokenMintToParams {
|
||||
mint: obligation_token_mint_info.clone(),
|
||||
|
|
|
@ -31,6 +31,26 @@ pub struct Obligation {
|
|||
}
|
||||
|
||||
impl Obligation {
|
||||
/// Create new obligation
|
||||
pub fn new(params: NewObligationParams) -> Self {
|
||||
let NewObligationParams {
|
||||
collateral_reserve,
|
||||
borrow_reserve,
|
||||
token_mint,
|
||||
cumulative_borrow_rate_wads,
|
||||
} = params;
|
||||
|
||||
Self {
|
||||
version: PROGRAM_VERSION,
|
||||
deposited_collateral_tokens: 0,
|
||||
collateral_reserve,
|
||||
cumulative_borrow_rate_wads,
|
||||
borrowed_liquidity_wads: Decimal::zero(),
|
||||
borrow_reserve,
|
||||
token_mint,
|
||||
}
|
||||
}
|
||||
|
||||
/// Accrue interest
|
||||
pub fn accrue_interest(&mut self, cumulative_borrow_rate: Decimal) -> Result<(), ProgramError> {
|
||||
if cumulative_borrow_rate < self.cumulative_borrow_rate_wads {
|
||||
|
@ -104,6 +124,18 @@ pub struct RepayResult {
|
|||
pub integer_repay_amount: u64,
|
||||
}
|
||||
|
||||
/// Create new obligation
|
||||
pub struct NewObligationParams {
|
||||
/// Collateral reserve address
|
||||
pub collateral_reserve: Pubkey,
|
||||
/// Borrow reserve address
|
||||
pub borrow_reserve: Pubkey,
|
||||
/// Obligation token mint address
|
||||
pub token_mint: Pubkey,
|
||||
/// Borrow rate used for calculating interest.
|
||||
pub cumulative_borrow_rate_wads: Decimal,
|
||||
}
|
||||
|
||||
impl Sealed for Obligation {}
|
||||
impl IsInitialized for Obligation {
|
||||
fn is_initialized(&self) -> bool {
|
||||
|
|
|
@ -7,6 +7,7 @@ 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},
|
||||
};
|
||||
|
@ -34,6 +35,9 @@ async fn test_borrow_quote_currency() {
|
|||
processor!(process_instruction),
|
||||
);
|
||||
|
||||
// limit to track compute unit increase
|
||||
test.set_bpf_compute_max_units(185_000);
|
||||
|
||||
let user_accounts_owner = Keypair::new();
|
||||
let sol_usdc_dex_market = TestDexMarket::setup(&mut test, TestDexMarketPair::SOL_USDC);
|
||||
let usdc_mint = add_usdc_mint(&mut test);
|
||||
|
@ -42,6 +46,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,
|
||||
|
@ -62,6 +71,7 @@ async fn test_borrow_quote_currency() {
|
|||
&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(),
|
||||
|
@ -71,6 +81,18 @@ async fn test_borrow_quote_currency() {
|
|||
},
|
||||
);
|
||||
|
||||
let usdc_obligation = add_obligation(
|
||||
&mut test,
|
||||
&user_accounts_owner,
|
||||
&lending_market,
|
||||
AddObligationArgs {
|
||||
borrow_reserve: &usdc_reserve,
|
||||
collateral_reserve: &sol_reserve,
|
||||
collateral_amount: 0,
|
||||
borrowed_liquidity_wads: Decimal::zero(),
|
||||
},
|
||||
);
|
||||
|
||||
let (mut banks_client, payer, _recent_blockhash) = test.start().await;
|
||||
|
||||
let borrow_amount =
|
||||
|
@ -82,7 +104,7 @@ async fn test_borrow_quote_currency() {
|
|||
assert_eq!(collateral_supply, 0);
|
||||
|
||||
let collateral_deposit_amount = INITIAL_COLLATERAL_RATIO * SOL_COLLATERAL_AMOUNT_LAMPORTS;
|
||||
let obligation = lending_market
|
||||
lending_market
|
||||
.borrow(
|
||||
&mut banks_client,
|
||||
&payer,
|
||||
|
@ -93,7 +115,7 @@ async fn test_borrow_quote_currency() {
|
|||
borrow_amount_type: BorrowAmountType::CollateralDepositAmount,
|
||||
amount: collateral_deposit_amount,
|
||||
user_accounts_owner: &user_accounts_owner,
|
||||
obligation: None,
|
||||
obligation: &usdc_obligation,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
@ -121,9 +143,9 @@ async fn test_borrow_quote_currency() {
|
|||
borrow_reserve: &usdc_reserve,
|
||||
dex_market: &sol_usdc_dex_market,
|
||||
borrow_amount_type: BorrowAmountType::LiquidityBorrowAmount,
|
||||
amount: USDC_BORROW_AMOUNT_FRACTIONAL,
|
||||
amount: borrow_amount,
|
||||
user_accounts_owner: &user_accounts_owner,
|
||||
obligation: Some(obligation),
|
||||
obligation: &usdc_obligation,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
@ -132,9 +154,17 @@ 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);
|
||||
|
||||
let collateral_deposited = 2 * collateral_deposit_amount - COLLATERAL_EPSILON;
|
||||
let (total_fee, host_fee) = TEST_RESERVE_CONFIG
|
||||
.fees
|
||||
.calculate_borrow_fees(2 * collateral_deposit_amount)
|
||||
.calculate_borrow_fees(collateral_deposited)
|
||||
.unwrap();
|
||||
|
||||
assert!(total_fee > 0);
|
||||
|
@ -142,7 +172,7 @@ async fn test_borrow_quote_currency() {
|
|||
|
||||
let collateral_supply =
|
||||
get_token_balance(&mut banks_client, sol_reserve.collateral_supply).await;
|
||||
assert_eq!(collateral_supply, 2 * collateral_deposit_amount - total_fee);
|
||||
assert_eq!(collateral_supply, collateral_deposited - total_fee);
|
||||
|
||||
let fee_balance =
|
||||
get_token_balance(&mut banks_client, sol_reserve.collateral_fees_receiver).await;
|
||||
|
@ -160,18 +190,25 @@ async fn test_borrow_base_currency() {
|
|||
// $2.210, 212.5 SOL
|
||||
//
|
||||
// Borrow amount = 600 SOL
|
||||
// Collateral amount = 2.21 * 212.5 + 2.211 * 300 + 2.212 * 87.5 = 1,329.475 USDC
|
||||
// Collateral amount = 2.21 * 212.5 + 2.211 * 300 + 2.212 * 87.5 = 1,326.475 USDC
|
||||
const SOL_BORROW_AMOUNT_LAMPORTS: u64 = 600 * LAMPORTS_TO_SOL;
|
||||
const USDC_COLLATERAL_LAMPORTS: u64 = 1_326_475_000;
|
||||
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(),
|
||||
processor!(process_instruction),
|
||||
);
|
||||
|
||||
// limit to track compute unit increase
|
||||
test.set_bpf_compute_max_units(188_000);
|
||||
|
||||
let user_accounts_owner = Keypair::new();
|
||||
let sol_usdc_dex_market = TestDexMarket::setup(&mut test, TestDexMarketPair::SOL_USDC);
|
||||
let usdc_mint = add_usdc_mint(&mut test);
|
||||
|
@ -180,12 +217,18 @@ 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,
|
||||
|
@ -209,6 +252,18 @@ async fn test_borrow_base_currency() {
|
|||
},
|
||||
);
|
||||
|
||||
let sol_obligation = add_obligation(
|
||||
&mut test,
|
||||
&user_accounts_owner,
|
||||
&lending_market,
|
||||
AddObligationArgs {
|
||||
borrow_reserve: &sol_reserve,
|
||||
collateral_reserve: &usdc_reserve,
|
||||
collateral_amount: 0,
|
||||
borrowed_liquidity_wads: Decimal::zero(),
|
||||
},
|
||||
);
|
||||
|
||||
let (mut banks_client, payer, _recent_blockhash) = test.start().await;
|
||||
|
||||
let borrow_amount =
|
||||
|
@ -220,7 +275,7 @@ async fn test_borrow_base_currency() {
|
|||
assert_eq!(collateral_supply, 0);
|
||||
|
||||
let collateral_deposit_amount = INITIAL_COLLATERAL_RATIO * USDC_COLLATERAL_LAMPORTS;
|
||||
let obligation = lending_market
|
||||
lending_market
|
||||
.borrow(
|
||||
&mut banks_client,
|
||||
&payer,
|
||||
|
@ -231,14 +286,14 @@ async fn test_borrow_base_currency() {
|
|||
borrow_amount_type: BorrowAmountType::CollateralDepositAmount,
|
||||
amount: collateral_deposit_amount,
|
||||
user_accounts_owner: &user_accounts_owner,
|
||||
obligation: None,
|
||||
obligation: &sol_obligation,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
let borrow_amount =
|
||||
get_token_balance(&mut banks_client, sol_reserve.user_liquidity_account).await;
|
||||
assert_eq!(borrow_amount, SOL_BORROW_AMOUNT_LAMPORTS);
|
||||
assert_eq!(borrow_amount, SOL_BORROW_AMOUNT_LAMPORTS + INTEREST_EPSILON);
|
||||
|
||||
let borrow_fees = TEST_RESERVE_CONFIG
|
||||
.fees
|
||||
|
@ -259,16 +314,19 @@ async fn test_borrow_base_currency() {
|
|||
borrow_reserve: &sol_reserve,
|
||||
dex_market: &sol_usdc_dex_market,
|
||||
borrow_amount_type: BorrowAmountType::LiquidityBorrowAmount,
|
||||
amount: borrow_amount,
|
||||
amount: borrow_amount - INTEREST_EPSILON,
|
||||
user_accounts_owner: &user_accounts_owner,
|
||||
obligation: Some(obligation),
|
||||
obligation: &sol_obligation,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
let borrow_amount =
|
||||
get_token_balance(&mut banks_client, sol_reserve.user_liquidity_account).await;
|
||||
assert_eq!(borrow_amount, 2 * SOL_BORROW_AMOUNT_LAMPORTS);
|
||||
assert_eq!(
|
||||
borrow_amount,
|
||||
2 * SOL_BORROW_AMOUNT_LAMPORTS + INTEREST_EPSILON
|
||||
);
|
||||
|
||||
let (mut total_fee, mut host_fee) = TEST_RESERVE_CONFIG
|
||||
.fees
|
||||
|
|
|
@ -6,6 +6,7 @@ use helpers::*;
|
|||
use solana_sdk::signature::Keypair;
|
||||
use spl_token_lending::{
|
||||
instruction::BorrowAmountType,
|
||||
math::Decimal,
|
||||
state::{INITIAL_COLLATERAL_RATIO, PROGRAM_VERSION},
|
||||
};
|
||||
|
||||
|
@ -89,6 +90,42 @@ async fn test_success() {
|
|||
},
|
||||
);
|
||||
|
||||
let usdc_obligation = add_obligation(
|
||||
&mut test,
|
||||
&user_accounts_owner,
|
||||
&lending_market,
|
||||
AddObligationArgs {
|
||||
collateral_reserve: &sol_reserve,
|
||||
borrow_reserve: &usdc_reserve,
|
||||
collateral_amount: 0,
|
||||
borrowed_liquidity_wads: Decimal::zero(),
|
||||
},
|
||||
);
|
||||
|
||||
let sol_obligation = add_obligation(
|
||||
&mut test,
|
||||
&user_accounts_owner,
|
||||
&lending_market,
|
||||
AddObligationArgs {
|
||||
collateral_reserve: &usdc_reserve,
|
||||
borrow_reserve: &sol_reserve,
|
||||
collateral_amount: 0,
|
||||
borrowed_liquidity_wads: Decimal::zero(),
|
||||
},
|
||||
);
|
||||
|
||||
let srm_obligation = add_obligation(
|
||||
&mut test,
|
||||
&user_accounts_owner,
|
||||
&lending_market,
|
||||
AddObligationArgs {
|
||||
collateral_reserve: &usdc_reserve,
|
||||
borrow_reserve: &srm_reserve,
|
||||
collateral_amount: 0,
|
||||
borrowed_liquidity_wads: Decimal::zero(),
|
||||
},
|
||||
);
|
||||
|
||||
let (mut banks_client, payer, _recent_blockhash) = test.start().await;
|
||||
|
||||
// Verify lending market
|
||||
|
@ -156,7 +193,7 @@ async fn test_success() {
|
|||
);
|
||||
|
||||
// Borrow USDC with SOL collateral
|
||||
let obligation = lending_market
|
||||
lending_market
|
||||
.borrow(
|
||||
&mut banks_client,
|
||||
&payer,
|
||||
|
@ -167,7 +204,7 @@ async fn test_success() {
|
|||
borrow_amount_type: BorrowAmountType::CollateralDepositAmount,
|
||||
amount: INITIAL_COLLATERAL_RATIO * USER_SOL_COLLATERAL_LAMPORTS,
|
||||
user_accounts_owner: &user_accounts_owner,
|
||||
obligation: None,
|
||||
obligation: &usdc_obligation,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
@ -187,7 +224,7 @@ async fn test_success() {
|
|||
/ 100,
|
||||
),
|
||||
user_accounts_owner: &user_accounts_owner,
|
||||
obligation: Some(obligation),
|
||||
obligation: &usdc_obligation,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
@ -224,7 +261,7 @@ async fn test_success() {
|
|||
/ 100,
|
||||
),
|
||||
user_accounts_owner: &user_accounts_owner,
|
||||
obligation: None,
|
||||
obligation: &sol_obligation,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
@ -246,7 +283,7 @@ async fn test_success() {
|
|||
/ 100,
|
||||
),
|
||||
user_accounts_owner: &user_accounts_owner,
|
||||
obligation: None,
|
||||
obligation: &srm_obligation,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
|
|
@ -15,8 +15,8 @@ use spl_token::{
|
|||
};
|
||||
use spl_token_lending::{
|
||||
instruction::{
|
||||
borrow_reserve_liquidity, deposit_reserve_liquidity, init_lending_market, init_reserve,
|
||||
liquidate_obligation, BorrowAmountType,
|
||||
borrow_reserve_liquidity, deposit_reserve_liquidity, init_lending_market, init_obligation,
|
||||
init_reserve, liquidate_obligation, BorrowAmountType,
|
||||
},
|
||||
math::Decimal,
|
||||
processor::process_instruction,
|
||||
|
@ -209,9 +209,11 @@ pub fn add_obligation(
|
|||
);
|
||||
|
||||
TestObligation {
|
||||
keypair: obligation_keypair,
|
||||
pubkey: obligation_pubkey,
|
||||
token_mint: token_mint_pubkey,
|
||||
token_account: token_account_pubkey,
|
||||
borrow_reserve: borrow_reserve.pubkey,
|
||||
collateral_reserve: collateral_reserve.pubkey,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -429,7 +431,7 @@ pub struct BorrowArgs<'a> {
|
|||
pub amount: u64,
|
||||
pub dex_market: &'a TestDexMarket,
|
||||
pub user_accounts_owner: &'a Keypair,
|
||||
pub obligation: Option<TestObligation>,
|
||||
pub obligation: &'a TestObligation,
|
||||
}
|
||||
|
||||
pub struct LiquidateArgs<'a> {
|
||||
|
@ -580,7 +582,7 @@ impl TestLendingMarket {
|
|||
repay_reserve.liquidity_supply,
|
||||
withdraw_reserve.pubkey,
|
||||
withdraw_reserve.collateral_supply,
|
||||
obligation.keypair.pubkey(),
|
||||
obligation.pubkey,
|
||||
self.pubkey,
|
||||
self.authority,
|
||||
user_transfer_authority.pubkey(),
|
||||
|
@ -610,8 +612,7 @@ impl TestLendingMarket {
|
|||
banks_client: &mut BanksClient,
|
||||
payer: &Keypair,
|
||||
args: BorrowArgs<'_>,
|
||||
) -> TestObligation {
|
||||
let rent = banks_client.get_rent().await.unwrap();
|
||||
) {
|
||||
let memory_keypair = Keypair::new();
|
||||
let user_transfer_authority = Keypair::new();
|
||||
|
||||
|
@ -637,60 +638,6 @@ impl TestLendingMarket {
|
|||
get_token_balance(banks_client, deposit_reserve.user_collateral_account).await
|
||||
};
|
||||
|
||||
let obligation = if let Some(obligation) = obligation {
|
||||
obligation
|
||||
} else {
|
||||
let obligation_token_mint_keypair = Keypair::new();
|
||||
let obligation_token_account_keypair = Keypair::new();
|
||||
let obligation = TestObligation {
|
||||
keypair: Keypair::new(),
|
||||
token_mint: obligation_token_mint_keypair.pubkey(),
|
||||
token_account: obligation_token_account_keypair.pubkey(),
|
||||
};
|
||||
|
||||
let mut transaction = Transaction::new_with_payer(
|
||||
&[
|
||||
create_account(
|
||||
&payer.pubkey(),
|
||||
&obligation_token_mint_keypair.pubkey(),
|
||||
rent.minimum_balance(Mint::LEN),
|
||||
Mint::LEN as u64,
|
||||
&spl_token::id(),
|
||||
),
|
||||
create_account(
|
||||
&payer.pubkey(),
|
||||
&obligation_token_account_keypair.pubkey(),
|
||||
rent.minimum_balance(Token::LEN),
|
||||
Token::LEN as u64,
|
||||
&spl_token::id(),
|
||||
),
|
||||
create_account(
|
||||
&payer.pubkey(),
|
||||
&obligation.keypair.pubkey(),
|
||||
rent.minimum_balance(Obligation::LEN),
|
||||
Obligation::LEN as u64,
|
||||
&spl_token_lending::id(),
|
||||
),
|
||||
],
|
||||
Some(&payer.pubkey()),
|
||||
);
|
||||
|
||||
let recent_blockhash = banks_client.get_recent_blockhash().await.unwrap();
|
||||
transaction.sign(
|
||||
&vec![
|
||||
payer,
|
||||
&obligation.keypair,
|
||||
&obligation_token_account_keypair,
|
||||
&obligation_token_mint_keypair,
|
||||
],
|
||||
recent_blockhash,
|
||||
);
|
||||
|
||||
assert_matches!(banks_client.process_transaction(transaction).await, Ok(()));
|
||||
|
||||
obligation
|
||||
};
|
||||
|
||||
let mut transaction = Transaction::new_with_payer(
|
||||
&[
|
||||
approve(
|
||||
|
@ -723,10 +670,9 @@ impl TestLendingMarket {
|
|||
self.pubkey,
|
||||
self.authority,
|
||||
user_transfer_authority.pubkey(),
|
||||
obligation.keypair.pubkey(),
|
||||
obligation.pubkey,
|
||||
obligation.token_mint,
|
||||
obligation.token_account,
|
||||
user_accounts_owner.pubkey(),
|
||||
dex_market.pubkey,
|
||||
dex_market_orders_pubkey,
|
||||
memory_keypair.pubkey(),
|
||||
|
@ -748,7 +694,6 @@ impl TestLendingMarket {
|
|||
);
|
||||
|
||||
assert_matches!(banks_client.process_transaction(transaction).await, Ok(()));
|
||||
obligation
|
||||
}
|
||||
|
||||
pub async fn get_state(&self, banks_client: &mut BanksClient) -> LendingMarket {
|
||||
|
@ -1036,21 +981,110 @@ impl TestReserve {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TestObligation {
|
||||
pub keypair: Keypair,
|
||||
pub pubkey: Pubkey,
|
||||
pub token_mint: Pubkey,
|
||||
pub token_account: Pubkey,
|
||||
pub collateral_reserve: Pubkey,
|
||||
pub borrow_reserve: Pubkey,
|
||||
}
|
||||
|
||||
impl TestObligation {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn init(
|
||||
banks_client: &mut BanksClient,
|
||||
lending_market: &TestLendingMarket,
|
||||
deposit_reserve: &TestReserve,
|
||||
borrow_reserve: &TestReserve,
|
||||
payer: &Keypair,
|
||||
user_accounts_owner: &Keypair,
|
||||
) -> Result<Self, TransactionError> {
|
||||
let obligation_keypair = Keypair::new();
|
||||
let obligation_token_mint_keypair = Keypair::new();
|
||||
let obligation_token_account_keypair = Keypair::new();
|
||||
let obligation = TestObligation {
|
||||
pubkey: obligation_keypair.pubkey(),
|
||||
token_mint: obligation_token_mint_keypair.pubkey(),
|
||||
token_account: obligation_token_account_keypair.pubkey(),
|
||||
collateral_reserve: deposit_reserve.pubkey,
|
||||
borrow_reserve: borrow_reserve.pubkey,
|
||||
};
|
||||
|
||||
let rent = banks_client.get_rent().await.unwrap();
|
||||
let mut transaction = Transaction::new_with_payer(
|
||||
&[
|
||||
create_account(
|
||||
&payer.pubkey(),
|
||||
&obligation_token_mint_keypair.pubkey(),
|
||||
rent.minimum_balance(Mint::LEN),
|
||||
Mint::LEN as u64,
|
||||
&spl_token::id(),
|
||||
),
|
||||
create_account(
|
||||
&payer.pubkey(),
|
||||
&obligation_token_account_keypair.pubkey(),
|
||||
rent.minimum_balance(Token::LEN),
|
||||
Token::LEN as u64,
|
||||
&spl_token::id(),
|
||||
),
|
||||
create_account(
|
||||
&payer.pubkey(),
|
||||
&obligation_keypair.pubkey(),
|
||||
rent.minimum_balance(Obligation::LEN),
|
||||
Obligation::LEN as u64,
|
||||
&spl_token_lending::id(),
|
||||
),
|
||||
init_obligation(
|
||||
spl_token_lending::id(),
|
||||
deposit_reserve.pubkey,
|
||||
borrow_reserve.pubkey,
|
||||
lending_market.pubkey,
|
||||
obligation.pubkey,
|
||||
obligation.token_mint,
|
||||
obligation.token_account,
|
||||
user_accounts_owner.pubkey(),
|
||||
),
|
||||
],
|
||||
Some(&payer.pubkey()),
|
||||
);
|
||||
|
||||
let recent_blockhash = banks_client.get_recent_blockhash().await.unwrap();
|
||||
transaction.sign(
|
||||
&vec![
|
||||
payer,
|
||||
&obligation_keypair,
|
||||
&obligation_token_account_keypair,
|
||||
&obligation_token_mint_keypair,
|
||||
],
|
||||
recent_blockhash,
|
||||
);
|
||||
|
||||
banks_client
|
||||
.process_transaction(transaction)
|
||||
.await
|
||||
.map_err(|e| e.unwrap())?;
|
||||
|
||||
Ok(obligation)
|
||||
}
|
||||
|
||||
pub async fn get_state(&self, banks_client: &mut BanksClient) -> Obligation {
|
||||
let obligation_account: Account = banks_client
|
||||
.get_account(self.keypair.pubkey())
|
||||
.get_account(self.pubkey)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
Obligation::unpack(&obligation_account.data[..]).unwrap()
|
||||
}
|
||||
|
||||
pub async fn validate_state(&self, banks_client: &mut BanksClient) {
|
||||
let obligation = self.get_state(banks_client).await;
|
||||
assert_eq!(obligation.version, PROGRAM_VERSION);
|
||||
assert_eq!(obligation.collateral_reserve, self.collateral_reserve);
|
||||
assert!(obligation.cumulative_borrow_rate_wads >= Decimal::one());
|
||||
assert_eq!(obligation.borrow_reserve, self.borrow_reserve);
|
||||
assert_eq!(obligation.token_mint, self.token_mint);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestQuoteMint {
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
#![cfg(feature = "test-bpf")]
|
||||
|
||||
mod helpers;
|
||||
|
||||
use helpers::*;
|
||||
use solana_program_test::*;
|
||||
use solana_sdk::{
|
||||
instruction::InstructionError,
|
||||
pubkey::Pubkey,
|
||||
signature::{Keypair, Signer},
|
||||
transaction::{Transaction, TransactionError},
|
||||
};
|
||||
use spl_token_lending::{
|
||||
error::LendingError, instruction::init_obligation, math::Decimal,
|
||||
processor::process_instruction, state::SLOTS_PER_YEAR,
|
||||
};
|
||||
|
||||
#[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(60_000);
|
||||
|
||||
let user_accounts_owner = Keypair::new();
|
||||
let sol_usdc_dex_market = TestDexMarket::setup(&mut test, TestDexMarketPair::SOL_USDC);
|
||||
let usdc_mint = add_usdc_mint(&mut test);
|
||||
let lending_market = add_lending_market(&mut test, usdc_mint.pubkey);
|
||||
|
||||
let usdc_reserve = add_reserve(
|
||||
&mut test,
|
||||
&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,
|
||||
..AddReserveArgs::default()
|
||||
},
|
||||
);
|
||||
|
||||
let sol_reserve = add_reserve(
|
||||
&mut test,
|
||||
&user_accounts_owner,
|
||||
&lending_market,
|
||||
AddReserveArgs {
|
||||
dex_market_pubkey: Some(sol_usdc_dex_market.pubkey),
|
||||
liquidity_mint_pubkey: spl_token::native_mint::id(),
|
||||
liquidity_mint_decimals: 9,
|
||||
config: TEST_RESERVE_CONFIG,
|
||||
..AddReserveArgs::default()
|
||||
},
|
||||
);
|
||||
|
||||
let (mut banks_client, payer, _recent_blockhash) = test.start().await;
|
||||
let obligation = TestObligation::init(
|
||||
&mut banks_client,
|
||||
&lending_market,
|
||||
&sol_reserve,
|
||||
&usdc_reserve,
|
||||
&payer,
|
||||
&user_accounts_owner,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
obligation.validate_state(&mut banks_client).await;
|
||||
let obligation_token_balance =
|
||||
get_token_balance(&mut banks_client, obligation.token_account).await;
|
||||
assert_eq!(obligation_token_balance, 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_already_initialized() {
|
||||
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(14_000);
|
||||
|
||||
let user_accounts_owner = Keypair::new();
|
||||
let sol_usdc_dex_market = TestDexMarket::setup(&mut test, TestDexMarketPair::SOL_USDC);
|
||||
let usdc_mint = add_usdc_mint(&mut test);
|
||||
let lending_market = add_lending_market(&mut test, usdc_mint.pubkey);
|
||||
|
||||
let usdc_reserve = add_reserve(
|
||||
&mut test,
|
||||
&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,
|
||||
..AddReserveArgs::default()
|
||||
},
|
||||
);
|
||||
|
||||
let sol_reserve = add_reserve(
|
||||
&mut test,
|
||||
&user_accounts_owner,
|
||||
&lending_market,
|
||||
AddReserveArgs {
|
||||
dex_market_pubkey: Some(sol_usdc_dex_market.pubkey),
|
||||
liquidity_mint_pubkey: spl_token::native_mint::id(),
|
||||
liquidity_mint_decimals: 9,
|
||||
config: TEST_RESERVE_CONFIG,
|
||||
..AddReserveArgs::default()
|
||||
},
|
||||
);
|
||||
|
||||
let usdc_obligation = add_obligation(
|
||||
&mut test,
|
||||
&user_accounts_owner,
|
||||
&lending_market,
|
||||
AddObligationArgs {
|
||||
borrow_reserve: &usdc_reserve,
|
||||
collateral_reserve: &sol_reserve,
|
||||
collateral_amount: 0,
|
||||
borrowed_liquidity_wads: Decimal::zero(),
|
||||
},
|
||||
);
|
||||
|
||||
let (mut banks_client, payer, recent_blockhash) = test.start().await;
|
||||
let mut transaction = Transaction::new_with_payer(
|
||||
&[init_obligation(
|
||||
spl_token_lending::id(),
|
||||
sol_reserve.pubkey,
|
||||
usdc_reserve.pubkey,
|
||||
lending_market.pubkey,
|
||||
usdc_obligation.pubkey,
|
||||
usdc_obligation.token_mint,
|
||||
usdc_obligation.token_account,
|
||||
user_accounts_owner.pubkey(),
|
||||
)],
|
||||
Some(&payer.pubkey()),
|
||||
);
|
||||
transaction.sign(&[&payer], recent_blockhash);
|
||||
|
||||
assert_eq!(
|
||||
banks_client
|
||||
.process_transaction(transaction)
|
||||
.await
|
||||
.unwrap_err()
|
||||
.unwrap(),
|
||||
TransactionError::InstructionError(
|
||||
0,
|
||||
InstructionError::Custom(LendingError::AlreadyInitialized as u32)
|
||||
)
|
||||
);
|
||||
}
|
|
@ -121,7 +121,7 @@ async fn test_success() {
|
|||
usdc_reserve.liquidity_supply,
|
||||
sol_reserve.pubkey,
|
||||
sol_reserve.collateral_supply,
|
||||
obligation.keypair.pubkey(),
|
||||
obligation.pubkey,
|
||||
obligation.token_mint,
|
||||
obligation.token_account,
|
||||
lending_market.pubkey,
|
||||
|
|
Loading…
Reference in New Issue