From fe19117d38e38925d7e0407dfe49caba2f7b4997 Mon Sep 17 00:00:00 2001 From: nope <83512286+nope-finance@users.noreply.github.com> Date: Fri, 21 Jan 2022 19:47:04 -0800 Subject: [PATCH 1/3] allow oracle to be more stale (#57) --- token-lending/program/src/processor.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/token-lending/program/src/processor.rs b/token-lending/program/src/processor.rs index 2a348ee06e2..d8c4e3faf3d 100644 --- a/token-lending/program/src/processor.rs +++ b/token-lending/program/src/processor.rs @@ -2115,7 +2115,7 @@ fn get_price( } fn get_pyth_price(pyth_price_info: &AccountInfo, clock: &Clock) -> Result { - const STALE_AFTER_SLOTS_ELAPSED: u64 = 20; + const STALE_AFTER_SLOTS_ELAPSED: u64 = 240; if *pyth_price_info.key == spl_token_lending::NULL_PUBKEY { return Err(LendingError::NullOracleConfig.into()); @@ -2196,7 +2196,7 @@ fn get_switchboard_price( switchboard_feed_info: &AccountInfo, clock: &Clock, ) -> Result { - const STALE_AFTER_SLOTS_ELAPSED: u64 = 100; + const STALE_AFTER_SLOTS_ELAPSED: u64 = 240; if *switchboard_feed_info.key == spl_token_lending::NULL_PUBKEY { return Err(LendingError::NullOracleConfig.into()); From bb6e18a376fe8f4938136236e6468e9f5031806e Mon Sep 17 00:00:00 2001 From: Nope X Date: Fri, 21 Jan 2022 19:32:59 -0800 Subject: [PATCH 2/3] whitelist liquidations --- token-lending/program/src/error.rs | 4 +++ token-lending/program/src/lib.rs | 26 +++++++++++++++++++ token-lending/program/src/processor.rs | 8 ++++++ .../program/tests/liquidate_obligation.rs | 5 ++-- 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/token-lending/program/src/error.rs b/token-lending/program/src/error.rs index 27706f65365..72b94b29339 100644 --- a/token-lending/program/src/error.rs +++ b/token-lending/program/src/error.rs @@ -159,10 +159,14 @@ pub enum LendingError { /// Not enough liquidity after flash loan #[error("Not enough liquidity after flash loan")] NotEnoughLiquidityAfterFlashLoan, + // 45 /// Null oracle config #[error("Null oracle config")] NullOracleConfig, + /// Non whitelist liquidator + #[error("Not a whitelisted liquidator")] + NonWhitelistLiquidator, } impl From for ProgramError { diff --git a/token-lending/program/src/lib.rs b/token-lending/program/src/lib.rs index d9d1f5265bc..1cddd5c927d 100644 --- a/token-lending/program/src/lib.rs +++ b/token-lending/program/src/lib.rs @@ -12,6 +12,7 @@ pub mod state; // Export current sdk types for downstream users building with a different sdk version pub use solana_program; +pub use std::str::FromStr; solana_program::declare_id!("So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo"); @@ -21,3 +22,28 @@ pub const NULL_PUBKEY: solana_program::pubkey::Pubkey = 11, 193, 238, 216, 208, 116, 241, 195, 55, 212, 76, 22, 75, 202, 40, 216, 76, 206, 27, 169, 138, 64, 177, 28, 19, 90, 156, 0, 0, 0, 0, 0, ]); + +/// whitelist liquidator wallets +pub const WHITELIST_LIQUIDATORS : [solana_program::pubkey::Pubkey; 3] = [ + // F4q2bm6k4AW2rgQMBfvoNnfn1xFkoD98We6uJd6tV7tq + solana_program::pubkey::Pubkey::new_from_array([ + 208, 254, 170, 18, 176, 113, 47, 78, + 0, 176, 134, 93, 161, 19, 98, 161, + 114, 185, 140, 222, 45, 158, 143, 251, + 53, 114, 83, 154, 201, 207, 178, 198 + ]), + // owoD8aRRvKZXRfDiGvXYc18gfQ5cQBcKgEXFk6PCTva + solana_program::pubkey::Pubkey::new_from_array([ + 12, 6, 173, 17, 230, 35, 81, 149, + 182, 240, 121, 19, 165, 180, 38, 237, + 34, 50, 245, 8, 230, 123, 18, 122, + 114, 60, 166, 205, 191, 247, 88, 195 + ]), + // 8i3ufSbnCDi3ZGyGJxDco26AQTGB5G81G1SBsUD55mK6 this is for tests + solana_program::pubkey::Pubkey::new_from_array([ + 114, 133, 232, 227, 86, 67, 182, 15, + 253, 36, 214, 87, 201, 19, 105, 189, + 111, 157, 211, 250, 12, 167, 115, 73, + 3, 116, 254, 73, 245, 75, 104, 105 + ]) +]; \ No newline at end of file diff --git a/token-lending/program/src/processor.rs b/token-lending/program/src/processor.rs index d8c4e3faf3d..97baace7db3 100644 --- a/token-lending/program/src/processor.rs +++ b/token-lending/program/src/processor.rs @@ -1526,6 +1526,7 @@ fn process_repay_obligation_liquidity( Ok(()) } + #[inline(never)] // avoid stack frame limit fn process_liquidate_obligation( program_id: &Pubkey, @@ -1551,6 +1552,13 @@ fn process_liquidate_obligation( let clock = &Clock::from_account_info(next_account_info(account_info_iter)?)?; let token_program_id = next_account_info(account_info_iter)?; + + if !spl_token_lending::WHITELIST_LIQUIDATORS.contains(&user_transfer_authority_info.key) { + msg!("Liquidator not part of whitelist"); + return Err(LendingError::NonWhitelistLiquidator.into()); + }; + + let lending_market = LendingMarket::unpack(&lending_market_info.data.borrow())?; if lending_market_info.owner != program_id { msg!("Lending market provided is not owned by the lending program"); diff --git a/token-lending/program/tests/liquidate_obligation.rs b/token-lending/program/tests/liquidate_obligation.rs index 9a7d930b30a..dc6bf697628 100644 --- a/token-lending/program/tests/liquidate_obligation.rs +++ b/token-lending/program/tests/liquidate_obligation.rs @@ -6,7 +6,7 @@ use helpers::*; use solana_program_test::*; use solana_sdk::{ pubkey::Pubkey, - signature::{Keypair, Signer}, + signature::{read_keypair_file, Keypair, Signer}, transaction::Transaction, }; use spl_token::instruction::approve; @@ -40,7 +40,8 @@ async fn test_success() { const USDC_RESERVE_LIQUIDITY_FRACTIONAL: u64 = 2 * USDC_BORROW_AMOUNT_FRACTIONAL; let user_accounts_owner = Keypair::new(); - let user_transfer_authority = Keypair::new(); + let user_transfer_authority = + read_keypair_file("tests/fixtures/lending_market_owner.json").unwrap(); let lending_market = add_lending_market(&mut test); let mut reserve_config = test_reserve_config(); From aae7e90db3f3835dae61f8b28f3ef79048e0a4a8 Mon Sep 17 00:00:00 2001 From: Nope X Date: Sat, 22 Jan 2022 19:30:30 -0800 Subject: [PATCH 3/3] added error case for non whitelisted --- .../program/tests/liquidate_obligation.rs | 148 +++++++++++++++++- 1 file changed, 146 insertions(+), 2 deletions(-) diff --git a/token-lending/program/tests/liquidate_obligation.rs b/token-lending/program/tests/liquidate_obligation.rs index dc6bf697628..b41fbe3b17c 100644 --- a/token-lending/program/tests/liquidate_obligation.rs +++ b/token-lending/program/tests/liquidate_obligation.rs @@ -7,13 +7,17 @@ use solana_program_test::*; use solana_sdk::{ pubkey::Pubkey, signature::{read_keypair_file, Keypair, Signer}, - transaction::Transaction, + transaction::{Transaction, TransactionError}, +}; +use spl_token::{ + instruction::approve, + solana_program::instruction::InstructionError, }; -use spl_token::instruction::approve; use spl_token_lending::{ instruction::{liquidate_obligation, refresh_obligation}, processor::process_instruction, state::INITIAL_COLLATERAL_RATIO, + error::LendingError, }; #[tokio::test] @@ -183,3 +187,143 @@ async fn test_success() { (USDC_BORROW_AMOUNT_FRACTIONAL - USDC_LIQUIDATION_AMOUNT_FRACTIONAL).into() ) } + +#[tokio::test] +async fn test_not_whitelist() { + 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(51_000); + + // 100 SOL collateral + const SOL_DEPOSIT_AMOUNT_LAMPORTS: u64 = 100 * LAMPORTS_TO_SOL * INITIAL_COLLATERAL_RATIO; + // 100 SOL * 80% LTV -> 80 SOL * 20 USDC -> 1600 USDC borrow + const USDC_BORROW_AMOUNT_FRACTIONAL: u64 = 1_600 * FRACTIONAL_TO_USDC; + // 1600 USDC * 50% -> 800 USDC liquidation + const USDC_LIQUIDATION_AMOUNT_FRACTIONAL: u64 = USDC_BORROW_AMOUNT_FRACTIONAL / 2; + // 800 USDC / 20 USDC per SOL -> 40 SOL + 10% bonus -> 44 SOL + const _SOL_LIQUIDATION_AMOUNT_LAMPORTS: u64 = 44 * LAMPORTS_TO_SOL * INITIAL_COLLATERAL_RATIO; + + const SOL_RESERVE_COLLATERAL_LAMPORTS: u64 = 2 * SOL_DEPOSIT_AMOUNT_LAMPORTS; + const USDC_RESERVE_LIQUIDITY_FRACTIONAL: u64 = 2 * USDC_BORROW_AMOUNT_FRACTIONAL; + + let user_accounts_owner = Keypair::new(); + let user_transfer_authority = Keypair::new(); + let lending_market = add_lending_market(&mut test); + + let mut reserve_config = test_reserve_config(); + reserve_config.loan_to_value_ratio = 50; + reserve_config.liquidation_threshold = 80; + reserve_config.liquidation_bonus = 10; + + 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 { + borrow_amount: USDC_BORROW_AMOUNT_FRACTIONAL, + user_liquidity_amount: USDC_BORROW_AMOUNT_FRACTIONAL, + 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)], + borrows: &[(&usdc_test_reserve, USDC_BORROW_AMOUNT_FRACTIONAL)], + ..AddObligationArgs::default() + }, + ); + + let (mut banks_client, payer, recent_blockhash) = test.start().await; + + let _initial_user_liquidity_balance = + get_token_balance(&mut banks_client, usdc_test_reserve.user_liquidity_pubkey).await; + let _initial_liquidity_supply_balance = + get_token_balance(&mut banks_client, usdc_test_reserve.liquidity_supply_pubkey).await; + let _initial_user_collateral_balance = + get_token_balance(&mut banks_client, sol_test_reserve.user_collateral_pubkey).await; + let _initial_collateral_supply_balance = + get_token_balance(&mut banks_client, sol_test_reserve.collateral_supply_pubkey).await; + + let mut transaction = Transaction::new_with_payer( + &[ + approve( + &spl_token::id(), + &usdc_test_reserve.user_liquidity_pubkey, + &user_transfer_authority.pubkey(), + &user_accounts_owner.pubkey(), + &[], + USDC_LIQUIDATION_AMOUNT_FRACTIONAL, + ) + .unwrap(), + refresh_obligation( + spl_token_lending::id(), + test_obligation.pubkey, + vec![sol_test_reserve.pubkey, usdc_test_reserve.pubkey], + ), + liquidate_obligation( + spl_token_lending::id(), + USDC_LIQUIDATION_AMOUNT_FRACTIONAL, + usdc_test_reserve.user_liquidity_pubkey, + sol_test_reserve.user_collateral_pubkey, + usdc_test_reserve.pubkey, + usdc_test_reserve.liquidity_supply_pubkey, + sol_test_reserve.pubkey, + sol_test_reserve.collateral_supply_pubkey, + test_obligation.pubkey, + lending_market.pubkey, + user_transfer_authority.pubkey(), + ), + ], + Some(&payer.pubkey()), + ); + + transaction.sign( + &[&payer, &user_accounts_owner, &user_transfer_authority], + recent_blockhash, + ); + assert_eq!( + banks_client + .process_transaction(transaction) + .await + .unwrap_err() + .unwrap(), + TransactionError::InstructionError( + 2, + InstructionError::Custom(LendingError::NonWhitelistLiquidator as u32) + ) + ); + +}