Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

whitelist liquidations #61

Open
wants to merge 3 commits into
base: upcoming
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions token-lending/program/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<LendingError> for ProgramError {
Expand Down
26 changes: 26 additions & 0 deletions token-lending/program/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand All @@ -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([
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This account has on-chain transactions from a year ago. Is that account's private key leaked?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's literally in the repo so yeah

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its the account they use for tests in repo

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
])
];
12 changes: 10 additions & 2 deletions token-lending/program/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1526,6 +1526,7 @@ fn process_repay_obligation_liquidity(
Ok(())
}


#[inline(never)] // avoid stack frame limit
fn process_liquidate_obligation(
program_id: &Pubkey,
Expand All @@ -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");
Expand Down Expand Up @@ -2115,7 +2123,7 @@ fn get_price(
}

fn get_pyth_price(pyth_price_info: &AccountInfo, clock: &Clock) -> Result<Decimal, ProgramError> {
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());
Expand Down Expand Up @@ -2196,7 +2204,7 @@ fn get_switchboard_price(
switchboard_feed_info: &AccountInfo,
clock: &Clock,
) -> Result<Decimal, ProgramError> {
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());
Expand Down
153 changes: 149 additions & 4 deletions token-lending/program/tests/liquidate_obligation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@ use helpers::*;
use solana_program_test::*;
use solana_sdk::{
pubkey::Pubkey,
signature::{Keypair, Signer},
transaction::Transaction,
signature::{read_keypair_file, Keypair, Signer},
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]
Expand All @@ -40,7 +44,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 =

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have you tested with a non whitelisted liquidator?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point yeh let me add

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();
Expand Down Expand Up @@ -182,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)
)
);

}