Add slippage constraint to borrow instruction (#3423)
* Add slippage limit to instruction * Add slippage error * Return error if below slippage limit * Add tests * Remove TODO * Split instruction * Fix test name * Test limit case * Update test * Revert "Split instruction" This reverts commit 3eba4942bd51f2402bf112edb514882636f0e401. # Conflicts: # token-lending/program/tests/borrow_obligation_liquidity.rs * Allow missing slippage_limit * Fix limits * Remove assertion
This commit is contained in:
parent
e8bafb4b3f
commit
8d88fb6c88
|
@ -160,6 +160,9 @@ pub enum LendingError {
|
||||||
#[error("Not enough liquidity after flash loan")]
|
#[error("Not enough liquidity after flash loan")]
|
||||||
NotEnoughLiquidityAfterFlashLoan,
|
NotEnoughLiquidityAfterFlashLoan,
|
||||||
// 45
|
// 45
|
||||||
|
/// Lending instruction exceeds desired slippage limit
|
||||||
|
#[error("Amount smaller than desired slippage limit")]
|
||||||
|
ExceededSlippage,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<LendingError> for ProgramError {
|
impl From<LendingError> for ProgramError {
|
||||||
|
|
|
@ -219,7 +219,8 @@ pub enum LendingInstruction {
|
||||||
BorrowObligationLiquidity {
|
BorrowObligationLiquidity {
|
||||||
/// Amount of liquidity to borrow - u64::MAX for 100% of borrowing power
|
/// Amount of liquidity to borrow - u64::MAX for 100% of borrowing power
|
||||||
liquidity_amount: u64,
|
liquidity_amount: u64,
|
||||||
// @TODO: slippage constraint - https://git.io/JmV67
|
/// Minimum amount of liquidity to receive, if borrowing 100% of borrowing power
|
||||||
|
slippage_limit: u64,
|
||||||
},
|
},
|
||||||
|
|
||||||
// 11
|
// 11
|
||||||
|
@ -370,8 +371,12 @@ impl LendingInstruction {
|
||||||
Self::WithdrawObligationCollateral { collateral_amount }
|
Self::WithdrawObligationCollateral { collateral_amount }
|
||||||
}
|
}
|
||||||
10 => {
|
10 => {
|
||||||
let (liquidity_amount, _rest) = Self::unpack_u64(rest)?;
|
let (liquidity_amount, rest) = Self::unpack_u64(rest)?;
|
||||||
Self::BorrowObligationLiquidity { liquidity_amount }
|
let (slippage_limit, _rest) = Self::unpack_u64(rest).unwrap_or((0, &[]));
|
||||||
|
Self::BorrowObligationLiquidity {
|
||||||
|
liquidity_amount,
|
||||||
|
slippage_limit,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
11 => {
|
11 => {
|
||||||
let (liquidity_amount, _rest) = Self::unpack_u64(rest)?;
|
let (liquidity_amount, _rest) = Self::unpack_u64(rest)?;
|
||||||
|
@ -525,9 +530,13 @@ impl LendingInstruction {
|
||||||
buf.push(9);
|
buf.push(9);
|
||||||
buf.extend_from_slice(&collateral_amount.to_le_bytes());
|
buf.extend_from_slice(&collateral_amount.to_le_bytes());
|
||||||
}
|
}
|
||||||
Self::BorrowObligationLiquidity { liquidity_amount } => {
|
Self::BorrowObligationLiquidity {
|
||||||
|
liquidity_amount,
|
||||||
|
slippage_limit,
|
||||||
|
} => {
|
||||||
buf.push(10);
|
buf.push(10);
|
||||||
buf.extend_from_slice(&liquidity_amount.to_le_bytes());
|
buf.extend_from_slice(&liquidity_amount.to_le_bytes());
|
||||||
|
buf.extend_from_slice(&slippage_limit.to_le_bytes());
|
||||||
}
|
}
|
||||||
Self::RepayObligationLiquidity { liquidity_amount } => {
|
Self::RepayObligationLiquidity { liquidity_amount } => {
|
||||||
buf.push(11);
|
buf.push(11);
|
||||||
|
@ -860,6 +869,7 @@ pub fn withdraw_obligation_collateral(
|
||||||
pub fn borrow_obligation_liquidity(
|
pub fn borrow_obligation_liquidity(
|
||||||
program_id: Pubkey,
|
program_id: Pubkey,
|
||||||
liquidity_amount: u64,
|
liquidity_amount: u64,
|
||||||
|
slippage_limit: Option<u64>,
|
||||||
source_liquidity_pubkey: Pubkey,
|
source_liquidity_pubkey: Pubkey,
|
||||||
destination_liquidity_pubkey: Pubkey,
|
destination_liquidity_pubkey: Pubkey,
|
||||||
borrow_reserve_pubkey: Pubkey,
|
borrow_reserve_pubkey: Pubkey,
|
||||||
|
@ -888,10 +898,15 @@ pub fn borrow_obligation_liquidity(
|
||||||
if let Some(host_fee_receiver_pubkey) = host_fee_receiver_pubkey {
|
if let Some(host_fee_receiver_pubkey) = host_fee_receiver_pubkey {
|
||||||
accounts.push(AccountMeta::new(host_fee_receiver_pubkey, false));
|
accounts.push(AccountMeta::new(host_fee_receiver_pubkey, false));
|
||||||
}
|
}
|
||||||
|
let slippage_limit = slippage_limit.unwrap_or(0);
|
||||||
Instruction {
|
Instruction {
|
||||||
program_id,
|
program_id,
|
||||||
accounts,
|
accounts,
|
||||||
data: LendingInstruction::BorrowObligationLiquidity { liquidity_amount }.pack(),
|
data: LendingInstruction::BorrowObligationLiquidity {
|
||||||
|
liquidity_amount,
|
||||||
|
slippage_limit,
|
||||||
|
}
|
||||||
|
.pack(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1309,6 +1324,7 @@ mod tests {
|
||||||
let instruction = borrow_obligation_liquidity(
|
let instruction = borrow_obligation_liquidity(
|
||||||
program_id,
|
program_id,
|
||||||
liquidity_amount,
|
liquidity_amount,
|
||||||
|
None,
|
||||||
source_liquidity_pubkey,
|
source_liquidity_pubkey,
|
||||||
destination_liquidity_pubkey,
|
destination_liquidity_pubkey,
|
||||||
borrow_reserve_pubkey,
|
borrow_reserve_pubkey,
|
||||||
|
@ -1322,7 +1338,11 @@ mod tests {
|
||||||
assert_eq!(instruction.accounts.len(), 11);
|
assert_eq!(instruction.accounts.len(), 11);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
instruction.data,
|
instruction.data,
|
||||||
LendingInstruction::BorrowObligationLiquidity { liquidity_amount }.pack()
|
LendingInstruction::BorrowObligationLiquidity {
|
||||||
|
liquidity_amount,
|
||||||
|
slippage_limit: 0
|
||||||
|
}
|
||||||
|
.pack()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -83,9 +83,17 @@ pub fn process_instruction(
|
||||||
msg!("Instruction: Withdraw Obligation Collateral");
|
msg!("Instruction: Withdraw Obligation Collateral");
|
||||||
process_withdraw_obligation_collateral(program_id, collateral_amount, accounts)
|
process_withdraw_obligation_collateral(program_id, collateral_amount, accounts)
|
||||||
}
|
}
|
||||||
LendingInstruction::BorrowObligationLiquidity { liquidity_amount } => {
|
LendingInstruction::BorrowObligationLiquidity {
|
||||||
|
liquidity_amount,
|
||||||
|
slippage_limit,
|
||||||
|
} => {
|
||||||
msg!("Instruction: Borrow Obligation Liquidity");
|
msg!("Instruction: Borrow Obligation Liquidity");
|
||||||
process_borrow_obligation_liquidity(program_id, liquidity_amount, accounts)
|
process_borrow_obligation_liquidity(
|
||||||
|
program_id,
|
||||||
|
liquidity_amount,
|
||||||
|
slippage_limit,
|
||||||
|
accounts,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
LendingInstruction::RepayObligationLiquidity { liquidity_amount } => {
|
LendingInstruction::RepayObligationLiquidity { liquidity_amount } => {
|
||||||
msg!("Instruction: Repay Obligation Liquidity");
|
msg!("Instruction: Repay Obligation Liquidity");
|
||||||
|
@ -1023,6 +1031,7 @@ fn process_withdraw_obligation_collateral(
|
||||||
fn process_borrow_obligation_liquidity(
|
fn process_borrow_obligation_liquidity(
|
||||||
program_id: &Pubkey,
|
program_id: &Pubkey,
|
||||||
liquidity_amount: u64,
|
liquidity_amount: u64,
|
||||||
|
slippage_limit: u64,
|
||||||
accounts: &[AccountInfo],
|
accounts: &[AccountInfo],
|
||||||
) -> ProgramResult {
|
) -> ProgramResult {
|
||||||
if liquidity_amount == 0 {
|
if liquidity_amount == 0 {
|
||||||
|
@ -1141,6 +1150,11 @@ fn process_borrow_obligation_liquidity(
|
||||||
return Err(LendingError::BorrowTooSmall.into());
|
return Err(LendingError::BorrowTooSmall.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if liquidity_amount == u64::MAX && receive_amount < slippage_limit {
|
||||||
|
msg!("Received liquidity would be smaller than the desired slippage limit");
|
||||||
|
return Err(LendingError::ExceededSlippage.into());
|
||||||
|
}
|
||||||
|
|
||||||
borrow_reserve.liquidity.borrow(borrow_amount)?;
|
borrow_reserve.liquidity.borrow(borrow_amount)?;
|
||||||
borrow_reserve.last_update.mark_stale();
|
borrow_reserve.last_update.mark_stale();
|
||||||
Reserve::pack(borrow_reserve, &mut borrow_reserve_info.data.borrow_mut())?;
|
Reserve::pack(borrow_reserve, &mut borrow_reserve_info.data.borrow_mut())?;
|
||||||
|
|
|
@ -102,6 +102,7 @@ async fn test_borrow_usdc_fixed_amount() {
|
||||||
borrow_obligation_liquidity(
|
borrow_obligation_liquidity(
|
||||||
spl_token_lending::id(),
|
spl_token_lending::id(),
|
||||||
USDC_BORROW_AMOUNT_FRACTIONAL,
|
USDC_BORROW_AMOUNT_FRACTIONAL,
|
||||||
|
None,
|
||||||
usdc_test_reserve.liquidity_supply_pubkey,
|
usdc_test_reserve.liquidity_supply_pubkey,
|
||||||
usdc_test_reserve.user_liquidity_pubkey,
|
usdc_test_reserve.user_liquidity_pubkey,
|
||||||
usdc_test_reserve.pubkey,
|
usdc_test_reserve.pubkey,
|
||||||
|
@ -249,6 +250,7 @@ async fn test_borrow_sol_max_amount() {
|
||||||
borrow_obligation_liquidity(
|
borrow_obligation_liquidity(
|
||||||
spl_token_lending::id(),
|
spl_token_lending::id(),
|
||||||
u64::MAX,
|
u64::MAX,
|
||||||
|
None,
|
||||||
sol_test_reserve.liquidity_supply_pubkey,
|
sol_test_reserve.liquidity_supply_pubkey,
|
||||||
sol_test_reserve.user_liquidity_pubkey,
|
sol_test_reserve.user_liquidity_pubkey,
|
||||||
sol_test_reserve.pubkey,
|
sol_test_reserve.pubkey,
|
||||||
|
@ -380,6 +382,7 @@ async fn test_borrow_too_large() {
|
||||||
borrow_obligation_liquidity(
|
borrow_obligation_liquidity(
|
||||||
spl_token_lending::id(),
|
spl_token_lending::id(),
|
||||||
USDC_BORROW_AMOUNT_FRACTIONAL,
|
USDC_BORROW_AMOUNT_FRACTIONAL,
|
||||||
|
None,
|
||||||
usdc_test_reserve.liquidity_supply_pubkey,
|
usdc_test_reserve.liquidity_supply_pubkey,
|
||||||
usdc_test_reserve.user_liquidity_pubkey,
|
usdc_test_reserve.user_liquidity_pubkey,
|
||||||
usdc_test_reserve.pubkey,
|
usdc_test_reserve.pubkey,
|
||||||
|
@ -408,3 +411,353 @@ async fn test_borrow_too_large() {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_borrow_max_receive_minimum() {
|
||||||
|
let mut test = ProgramTest::new(
|
||||||
|
"spl_token_lending",
|
||||||
|
spl_token_lending::id(),
|
||||||
|
processor!(process_instruction),
|
||||||
|
);
|
||||||
|
|
||||||
|
// limit to track compute unit increase
|
||||||
|
test.set_compute_max_units(60_000);
|
||||||
|
|
||||||
|
const FEE_AMOUNT: u64 = 5000;
|
||||||
|
const HOST_FEE_AMOUNT: u64 = 1000;
|
||||||
|
|
||||||
|
const USDC_DEPOSIT_AMOUNT_FRACTIONAL: u64 =
|
||||||
|
2_000 * FRACTIONAL_TO_USDC * INITIAL_COLLATERAL_RATIO;
|
||||||
|
const SOL_BORROW_AMOUNT_LAMPORTS: u64 = 50 * LAMPORTS_TO_SOL;
|
||||||
|
const USDC_RESERVE_COLLATERAL_FRACTIONAL: u64 = 2 * USDC_DEPOSIT_AMOUNT_FRACTIONAL;
|
||||||
|
const SOL_RESERVE_LIQUIDITY_LAMPORTS: u64 = 2 * SOL_BORROW_AMOUNT_LAMPORTS;
|
||||||
|
const SLIPPAGE_LIMIT: u64 = SOL_BORROW_AMOUNT_LAMPORTS - FEE_AMOUNT;
|
||||||
|
|
||||||
|
let user_accounts_owner = Keypair::new();
|
||||||
|
let lending_market = add_lending_market(&mut test);
|
||||||
|
|
||||||
|
let mut reserve_config = TEST_RESERVE_CONFIG;
|
||||||
|
reserve_config.loan_to_value_ratio = 50;
|
||||||
|
|
||||||
|
let usdc_mint = add_usdc_mint(&mut test);
|
||||||
|
let usdc_oracle = add_usdc_oracle(&mut test);
|
||||||
|
let usdc_test_reserve = add_reserve(
|
||||||
|
&mut test,
|
||||||
|
&lending_market,
|
||||||
|
&usdc_oracle,
|
||||||
|
&user_accounts_owner,
|
||||||
|
AddReserveArgs {
|
||||||
|
liquidity_amount: USDC_RESERVE_COLLATERAL_FRACTIONAL,
|
||||||
|
liquidity_mint_pubkey: usdc_mint.pubkey,
|
||||||
|
liquidity_mint_decimals: usdc_mint.decimals,
|
||||||
|
config: reserve_config,
|
||||||
|
mark_fresh: true,
|
||||||
|
..AddReserveArgs::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let sol_oracle = add_sol_oracle(&mut test);
|
||||||
|
let sol_test_reserve = add_reserve(
|
||||||
|
&mut test,
|
||||||
|
&lending_market,
|
||||||
|
&sol_oracle,
|
||||||
|
&user_accounts_owner,
|
||||||
|
AddReserveArgs {
|
||||||
|
liquidity_amount: SOL_RESERVE_LIQUIDITY_LAMPORTS,
|
||||||
|
liquidity_mint_pubkey: spl_token::native_mint::id(),
|
||||||
|
liquidity_mint_decimals: 9,
|
||||||
|
config: reserve_config,
|
||||||
|
mark_fresh: true,
|
||||||
|
..AddReserveArgs::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let test_obligation = add_obligation(
|
||||||
|
&mut test,
|
||||||
|
&lending_market,
|
||||||
|
&user_accounts_owner,
|
||||||
|
AddObligationArgs {
|
||||||
|
deposits: &[(&usdc_test_reserve, USDC_DEPOSIT_AMOUNT_FRACTIONAL)],
|
||||||
|
..AddObligationArgs::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let (mut banks_client, payer, recent_blockhash) = test.start().await;
|
||||||
|
|
||||||
|
let initial_liquidity_supply =
|
||||||
|
get_token_balance(&mut banks_client, sol_test_reserve.liquidity_supply_pubkey).await;
|
||||||
|
|
||||||
|
let mut transaction = Transaction::new_with_payer(
|
||||||
|
&[
|
||||||
|
refresh_obligation(
|
||||||
|
spl_token_lending::id(),
|
||||||
|
test_obligation.pubkey,
|
||||||
|
vec![usdc_test_reserve.pubkey],
|
||||||
|
),
|
||||||
|
borrow_obligation_liquidity(
|
||||||
|
spl_token_lending::id(),
|
||||||
|
u64::MAX,
|
||||||
|
Some(SLIPPAGE_LIMIT),
|
||||||
|
sol_test_reserve.liquidity_supply_pubkey,
|
||||||
|
sol_test_reserve.user_liquidity_pubkey,
|
||||||
|
sol_test_reserve.pubkey,
|
||||||
|
sol_test_reserve.liquidity_fee_receiver_pubkey,
|
||||||
|
test_obligation.pubkey,
|
||||||
|
lending_market.pubkey,
|
||||||
|
test_obligation.owner,
|
||||||
|
Some(sol_test_reserve.liquidity_host_pubkey),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
Some(&payer.pubkey()),
|
||||||
|
);
|
||||||
|
|
||||||
|
transaction.sign(&[&payer, &user_accounts_owner], recent_blockhash);
|
||||||
|
assert!(banks_client.process_transaction(transaction).await.is_ok());
|
||||||
|
|
||||||
|
let sol_reserve = sol_test_reserve.get_state(&mut banks_client).await;
|
||||||
|
let obligation = test_obligation.get_state(&mut banks_client).await;
|
||||||
|
|
||||||
|
let (total_fee, host_fee) = sol_reserve
|
||||||
|
.config
|
||||||
|
.fees
|
||||||
|
.calculate_borrow_fees(SOL_BORROW_AMOUNT_LAMPORTS.into(), FeeCalculation::Inclusive)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(total_fee, FEE_AMOUNT);
|
||||||
|
assert_eq!(host_fee, HOST_FEE_AMOUNT);
|
||||||
|
|
||||||
|
let borrow_amount =
|
||||||
|
get_token_balance(&mut banks_client, sol_test_reserve.user_liquidity_pubkey).await;
|
||||||
|
assert_eq!(borrow_amount, SOL_BORROW_AMOUNT_LAMPORTS - FEE_AMOUNT);
|
||||||
|
|
||||||
|
let liquidity = &obligation.borrows[0];
|
||||||
|
assert_eq!(
|
||||||
|
liquidity.borrowed_amount_wads,
|
||||||
|
Decimal::from(SOL_BORROW_AMOUNT_LAMPORTS)
|
||||||
|
);
|
||||||
|
|
||||||
|
let liquidity_supply =
|
||||||
|
get_token_balance(&mut banks_client, sol_test_reserve.liquidity_supply_pubkey).await;
|
||||||
|
assert_eq!(
|
||||||
|
liquidity_supply,
|
||||||
|
initial_liquidity_supply - SOL_BORROW_AMOUNT_LAMPORTS
|
||||||
|
);
|
||||||
|
|
||||||
|
let fee_balance = get_token_balance(
|
||||||
|
&mut banks_client,
|
||||||
|
sol_test_reserve.liquidity_fee_receiver_pubkey,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(fee_balance, FEE_AMOUNT - HOST_FEE_AMOUNT);
|
||||||
|
|
||||||
|
let host_fee_balance =
|
||||||
|
get_token_balance(&mut banks_client, sol_test_reserve.liquidity_host_pubkey).await;
|
||||||
|
assert_eq!(host_fee_balance, HOST_FEE_AMOUNT);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_borrow_max_receive_less_than_slippage() {
|
||||||
|
let mut test = ProgramTest::new(
|
||||||
|
"spl_token_lending",
|
||||||
|
spl_token_lending::id(),
|
||||||
|
processor!(process_instruction),
|
||||||
|
);
|
||||||
|
|
||||||
|
// limit to track compute unit increase
|
||||||
|
test.set_compute_max_units(60_000);
|
||||||
|
|
||||||
|
const FEE_AMOUNT: u64 = 5000;
|
||||||
|
|
||||||
|
const USDC_DEPOSIT_AMOUNT_FRACTIONAL: u64 =
|
||||||
|
2_000 * FRACTIONAL_TO_USDC * INITIAL_COLLATERAL_RATIO;
|
||||||
|
const SOL_BORROW_AMOUNT_LAMPORTS: u64 = 50 * LAMPORTS_TO_SOL;
|
||||||
|
const USDC_RESERVE_COLLATERAL_FRACTIONAL: u64 = 2 * USDC_DEPOSIT_AMOUNT_FRACTIONAL;
|
||||||
|
const SOL_RESERVE_LIQUIDITY_LAMPORTS: u64 = 2 * SOL_BORROW_AMOUNT_LAMPORTS;
|
||||||
|
const SLIPPAGE_LIMIT: u64 = SOL_BORROW_AMOUNT_LAMPORTS - FEE_AMOUNT + 1;
|
||||||
|
|
||||||
|
let user_accounts_owner = Keypair::new();
|
||||||
|
let lending_market = add_lending_market(&mut test);
|
||||||
|
|
||||||
|
let mut reserve_config = TEST_RESERVE_CONFIG;
|
||||||
|
reserve_config.loan_to_value_ratio = 50;
|
||||||
|
|
||||||
|
let usdc_mint = add_usdc_mint(&mut test);
|
||||||
|
let usdc_oracle = add_usdc_oracle(&mut test);
|
||||||
|
let usdc_test_reserve = add_reserve(
|
||||||
|
&mut test,
|
||||||
|
&lending_market,
|
||||||
|
&usdc_oracle,
|
||||||
|
&user_accounts_owner,
|
||||||
|
AddReserveArgs {
|
||||||
|
liquidity_amount: USDC_RESERVE_COLLATERAL_FRACTIONAL,
|
||||||
|
liquidity_mint_pubkey: usdc_mint.pubkey,
|
||||||
|
liquidity_mint_decimals: usdc_mint.decimals,
|
||||||
|
config: reserve_config,
|
||||||
|
mark_fresh: true,
|
||||||
|
..AddReserveArgs::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let sol_oracle = add_sol_oracle(&mut test);
|
||||||
|
let sol_test_reserve = add_reserve(
|
||||||
|
&mut test,
|
||||||
|
&lending_market,
|
||||||
|
&sol_oracle,
|
||||||
|
&user_accounts_owner,
|
||||||
|
AddReserveArgs {
|
||||||
|
liquidity_amount: SOL_RESERVE_LIQUIDITY_LAMPORTS,
|
||||||
|
liquidity_mint_pubkey: spl_token::native_mint::id(),
|
||||||
|
liquidity_mint_decimals: 9,
|
||||||
|
config: reserve_config,
|
||||||
|
mark_fresh: true,
|
||||||
|
..AddReserveArgs::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let test_obligation = add_obligation(
|
||||||
|
&mut test,
|
||||||
|
&lending_market,
|
||||||
|
&user_accounts_owner,
|
||||||
|
AddObligationArgs {
|
||||||
|
deposits: &[(&usdc_test_reserve, USDC_DEPOSIT_AMOUNT_FRACTIONAL)],
|
||||||
|
..AddObligationArgs::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let (mut banks_client, payer, recent_blockhash) = test.start().await;
|
||||||
|
|
||||||
|
let mut transaction = Transaction::new_with_payer(
|
||||||
|
&[
|
||||||
|
refresh_obligation(
|
||||||
|
spl_token_lending::id(),
|
||||||
|
test_obligation.pubkey,
|
||||||
|
vec![usdc_test_reserve.pubkey],
|
||||||
|
),
|
||||||
|
borrow_obligation_liquidity(
|
||||||
|
spl_token_lending::id(),
|
||||||
|
u64::MAX,
|
||||||
|
Some(SLIPPAGE_LIMIT),
|
||||||
|
sol_test_reserve.liquidity_supply_pubkey,
|
||||||
|
sol_test_reserve.user_liquidity_pubkey,
|
||||||
|
sol_test_reserve.pubkey,
|
||||||
|
sol_test_reserve.liquidity_fee_receiver_pubkey,
|
||||||
|
test_obligation.pubkey,
|
||||||
|
lending_market.pubkey,
|
||||||
|
test_obligation.owner,
|
||||||
|
Some(sol_test_reserve.liquidity_host_pubkey),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
Some(&payer.pubkey()),
|
||||||
|
);
|
||||||
|
|
||||||
|
transaction.sign(&[&payer, &user_accounts_owner], recent_blockhash);
|
||||||
|
assert_eq!(
|
||||||
|
banks_client
|
||||||
|
.process_transaction(transaction)
|
||||||
|
.await
|
||||||
|
.unwrap_err()
|
||||||
|
.unwrap(),
|
||||||
|
TransactionError::InstructionError(
|
||||||
|
1,
|
||||||
|
InstructionError::Custom(LendingError::ExceededSlippage as u32)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_borrow_less_than_max_with_slippage() {
|
||||||
|
let mut test = ProgramTest::new(
|
||||||
|
"spl_token_lending",
|
||||||
|
spl_token_lending::id(),
|
||||||
|
processor!(process_instruction),
|
||||||
|
);
|
||||||
|
|
||||||
|
const USDC_TOTAL_BORROW_FRACTIONAL: u64 = 1_000 * FRACTIONAL_TO_USDC;
|
||||||
|
const FEE_AMOUNT: u64 = 100;
|
||||||
|
|
||||||
|
const SOL_DEPOSIT_AMOUNT_LAMPORTS: u64 = 100 * LAMPORTS_TO_SOL * INITIAL_COLLATERAL_RATIO;
|
||||||
|
const USDC_BORROW_AMOUNT_FRACTIONAL: u64 = USDC_TOTAL_BORROW_FRACTIONAL - FEE_AMOUNT;
|
||||||
|
const SOL_RESERVE_COLLATERAL_LAMPORTS: u64 = 2 * SOL_DEPOSIT_AMOUNT_LAMPORTS;
|
||||||
|
const USDC_RESERVE_LIQUIDITY_FRACTIONAL: u64 = 2 * USDC_TOTAL_BORROW_FRACTIONAL;
|
||||||
|
const SLIPPAGE_LIMIT: u64 = u64::MAX;
|
||||||
|
|
||||||
|
let user_accounts_owner = Keypair::new();
|
||||||
|
let lending_market = add_lending_market(&mut test);
|
||||||
|
|
||||||
|
let mut reserve_config = TEST_RESERVE_CONFIG;
|
||||||
|
reserve_config.loan_to_value_ratio = 50;
|
||||||
|
|
||||||
|
let sol_oracle = add_sol_oracle(&mut test);
|
||||||
|
let sol_test_reserve = add_reserve(
|
||||||
|
&mut test,
|
||||||
|
&lending_market,
|
||||||
|
&sol_oracle,
|
||||||
|
&user_accounts_owner,
|
||||||
|
AddReserveArgs {
|
||||||
|
collateral_amount: SOL_RESERVE_COLLATERAL_LAMPORTS,
|
||||||
|
liquidity_mint_pubkey: spl_token::native_mint::id(),
|
||||||
|
liquidity_mint_decimals: 9,
|
||||||
|
config: reserve_config,
|
||||||
|
mark_fresh: true,
|
||||||
|
..AddReserveArgs::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let usdc_mint = add_usdc_mint(&mut test);
|
||||||
|
let usdc_oracle = add_usdc_oracle(&mut test);
|
||||||
|
let usdc_test_reserve = add_reserve(
|
||||||
|
&mut test,
|
||||||
|
&lending_market,
|
||||||
|
&usdc_oracle,
|
||||||
|
&user_accounts_owner,
|
||||||
|
AddReserveArgs {
|
||||||
|
liquidity_amount: USDC_RESERVE_LIQUIDITY_FRACTIONAL,
|
||||||
|
liquidity_mint_pubkey: usdc_mint.pubkey,
|
||||||
|
liquidity_mint_decimals: usdc_mint.decimals,
|
||||||
|
config: reserve_config,
|
||||||
|
mark_fresh: true,
|
||||||
|
..AddReserveArgs::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let test_obligation = add_obligation(
|
||||||
|
&mut test,
|
||||||
|
&lending_market,
|
||||||
|
&user_accounts_owner,
|
||||||
|
AddObligationArgs {
|
||||||
|
deposits: &[(&sol_test_reserve, SOL_DEPOSIT_AMOUNT_LAMPORTS)],
|
||||||
|
..AddObligationArgs::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let (mut banks_client, payer, recent_blockhash) = test.start().await;
|
||||||
|
|
||||||
|
let mut transaction = Transaction::new_with_payer(
|
||||||
|
&[
|
||||||
|
refresh_obligation(
|
||||||
|
spl_token_lending::id(),
|
||||||
|
test_obligation.pubkey,
|
||||||
|
vec![sol_test_reserve.pubkey],
|
||||||
|
),
|
||||||
|
borrow_obligation_liquidity(
|
||||||
|
spl_token_lending::id(),
|
||||||
|
USDC_BORROW_AMOUNT_FRACTIONAL,
|
||||||
|
Some(SLIPPAGE_LIMIT),
|
||||||
|
usdc_test_reserve.liquidity_supply_pubkey,
|
||||||
|
usdc_test_reserve.user_liquidity_pubkey,
|
||||||
|
usdc_test_reserve.pubkey,
|
||||||
|
usdc_test_reserve.liquidity_fee_receiver_pubkey,
|
||||||
|
test_obligation.pubkey,
|
||||||
|
lending_market.pubkey,
|
||||||
|
test_obligation.owner,
|
||||||
|
Some(usdc_test_reserve.liquidity_host_pubkey),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
Some(&payer.pubkey()),
|
||||||
|
);
|
||||||
|
|
||||||
|
transaction.sign(&[&payer, &user_accounts_owner], recent_blockhash);
|
||||||
|
|
||||||
|
// check that transaction succeeds
|
||||||
|
banks_client.process_transaction(transaction).await.unwrap();
|
||||||
|
}
|
||||||
|
|
|
@ -651,6 +651,7 @@ impl TestLendingMarket {
|
||||||
&[borrow_obligation_liquidity(
|
&[borrow_obligation_liquidity(
|
||||||
spl_token_lending::id(),
|
spl_token_lending::id(),
|
||||||
liquidity_amount,
|
liquidity_amount,
|
||||||
|
None,
|
||||||
borrow_reserve.liquidity_supply_pubkey,
|
borrow_reserve.liquidity_supply_pubkey,
|
||||||
borrow_reserve.user_liquidity_pubkey,
|
borrow_reserve.user_liquidity_pubkey,
|
||||||
borrow_reserve.pubkey,
|
borrow_reserve.pubkey,
|
||||||
|
|
|
@ -165,6 +165,7 @@ async fn test_success() {
|
||||||
borrow_obligation_liquidity(
|
borrow_obligation_liquidity(
|
||||||
spl_token_lending::id(),
|
spl_token_lending::id(),
|
||||||
USDC_BORROW_AMOUNT_FRACTIONAL,
|
USDC_BORROW_AMOUNT_FRACTIONAL,
|
||||||
|
None,
|
||||||
usdc_test_reserve.liquidity_supply_pubkey,
|
usdc_test_reserve.liquidity_supply_pubkey,
|
||||||
usdc_test_reserve.user_liquidity_pubkey,
|
usdc_test_reserve.user_liquidity_pubkey,
|
||||||
usdc_test_reserve.pubkey,
|
usdc_test_reserve.pubkey,
|
||||||
|
|
Loading…
Reference in New Issue