Skip to content

Commit

Permalink
writing tests
Browse files Browse the repository at this point in the history
  • Loading branch information
0xripleys committed Dec 15, 2023
1 parent 045139b commit 2b4ad67
Show file tree
Hide file tree
Showing 11 changed files with 528 additions and 123 deletions.
71 changes: 37 additions & 34 deletions token-lending/program/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use solana_program::{
},
};
use solend_sdk::{
oracles::validate_pyth_price_account_info,
oracles::{get_oracle_type, validate_pyth_price_account_info, OracleType},
state::{LendingMarketMetadata, RateLimiter, RateLimiterConfig, ReserveType},
};
use solend_sdk::{switchboard_v2_devnet, switchboard_v2_mainnet};
Expand Down Expand Up @@ -368,22 +368,7 @@ fn process_init_reserve(

if let Some(extra_oracle_pubkey) = config.extra_oracle_pubkey {
let extra_oracle_info = next_account_info(account_info_iter)?;

if extra_oracle_info.key != &extra_oracle_pubkey {
msg!("Extra oracle provided does not match the extra oracle pubkey in the config");
return Err(LendingError::InvalidOracleConfig.into());
}

if *extra_oracle_info.owner == lending_market.oracle_program_id {
validate_pyth_price_account_info(&lending_market, extra_oracle_info)?;
} else if *extra_oracle_info.owner == lending_market.switchboard_oracle_program_id
|| *extra_oracle_info.owner == switchboard_v2_mainnet::id()
{
validate_switchboard_keys(&lending_market, extra_oracle_info)?;
} else {
msg!("Extra oracle provided is not owned by pyth or switchboard");
return Err(LendingError::InvalidOracleConfig.into());
}
validate_extra_oracle(&lending_market, extra_oracle_pubkey, extra_oracle_info)?;
}

let (market_price, smoothed_market_price) =
Expand Down Expand Up @@ -492,6 +477,33 @@ fn process_init_reserve(
Ok(())
}

fn validate_extra_oracle(
lending_market: &LendingMarket,
extra_oracle_pubkey: Pubkey,
extra_oracle_info: &AccountInfo<'_>,
) -> Result<(), ProgramError> {
if extra_oracle_pubkey == solend_program::NULL_PUBKEY {
msg!("Extra oracle cannot equal the null pubkey");
return Err(LendingError::InvalidOracleConfig.into());
}

if extra_oracle_info.key != &extra_oracle_pubkey {
msg!("Extra oracle provided does not match the extra oracle pubkey in the config");
return Err(LendingError::InvalidOracleConfig.into());
}

match get_oracle_type(extra_oracle_info)? {
OracleType::Pyth => {
validate_pyth_price_account_info(lending_market, extra_oracle_info)?;
}
OracleType::Switchboard => {
validate_switchboard_keys(lending_market, extra_oracle_info)?;
}
}

Ok(())
}

fn process_refresh_reserve(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
let account_info_iter = &mut accounts.iter().peekable();
let reserve_info = next_account_info(account_info_iter)?;
Expand Down Expand Up @@ -565,7 +577,13 @@ fn _refresh_reserve<'a>(
return Err(LendingError::InvalidAccountInput.into());
}

let (market_price, _) = get_price(None, extra_oracle_account_info, clock)?;
let market_price = match get_oracle_type(extra_oracle_account_info)? {
OracleType::Pyth => get_pyth_price(extra_oracle_account_info, clock)?.0,
OracleType::Switchboard => {
get_switchboard_price_v2(extra_oracle_account_info, clock)?
}
};

Some(market_price.try_mul(reserve.price_scale())?)
}
None => {
Expand Down Expand Up @@ -2342,22 +2360,7 @@ fn process_update_reserve_config(

if let Some(extra_oracle_pubkey) = config.extra_oracle_pubkey {
let extra_oracle_info = next_account_info(account_info_iter)?;

if extra_oracle_info.key != &extra_oracle_pubkey {
msg!("Extra oracle provided does not match the extra oracle pubkey in the config");
return Err(LendingError::InvalidOracleConfig.into());
}

if *extra_oracle_info.owner == lending_market.oracle_program_id {
validate_pyth_price_account_info(&lending_market, extra_oracle_info)?;
} else if *extra_oracle_info.owner == lending_market.switchboard_oracle_program_id
|| *extra_oracle_info.owner == switchboard_v2_mainnet::id()
{
validate_switchboard_keys(&lending_market, extra_oracle_info)?;
} else {
msg!("Extra oracle provided is not owned by pyth or switchboard");
return Err(LendingError::InvalidOracleConfig.into());
}
validate_extra_oracle(&lending_market, extra_oracle_pubkey, extra_oracle_info)?;
}

reserve.config = config;
Expand Down
72 changes: 1 addition & 71 deletions token-lending/program/tests/helpers/mock_pyth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,13 @@ use solana_program::{
pubkey::Pubkey,
sysvar::Sysvar,
};
use std::cell::RefMut;
use switchboard_v2::{AggregatorAccountData, SwitchboardDecimal};

use borsh::{BorshDeserialize, BorshSerialize};
use spl_token::solana_program::{account_info::next_account_info, program_error::ProgramError};
use thiserror::Error;

use super::{load_mut, QUOTE_CURRENCY};

pub mod mock_pyth_program {
solana_program::declare_id!("SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f");
}

#[derive(BorshSerialize, BorshDeserialize)]
pub enum MockPythInstruction {
/// Accounts:
Expand All @@ -40,15 +34,7 @@ pub enum MockPythInstruction {
expo: i32,
ema_price: i64,
ema_conf: u64,
},

/// Accounts:
/// 0: AggregatorAccount
InitSwitchboard,

/// Accounts:
/// 0: AggregatorAccount
SetSwitchboardPrice { price: i64, expo: i32 },
}
}

pub fn process_instruction(
Expand Down Expand Up @@ -150,37 +136,6 @@ impl Processor {
price_account.agg.pub_slot = Clock::get()?.slot;
price_account.agg.status = PriceStatus::Trading;

Ok(())
}
MockPythInstruction::InitSwitchboard => {
msg!("Mock Pyth: Init Switchboard");
let switchboard_feed = next_account_info(account_info_iter)?;
let mut data = switchboard_feed.try_borrow_mut_data()?;

let discriminator = [217, 230, 65, 101, 201, 162, 27, 125];
data[0..8].copy_from_slice(&discriminator);
Ok(())
}
MockPythInstruction::SetSwitchboardPrice { price, expo } => {
msg!("Mock Pyth: Set Switchboard price");
let switchboard_feed = next_account_info(account_info_iter)?;
let data = switchboard_feed.try_borrow_mut_data()?;

let mut aggregator_account: RefMut<AggregatorAccountData> =
RefMut::map(data, |data| {
bytemuck::from_bytes_mut(
&mut data[8..std::mem::size_of::<AggregatorAccountData>() + 8],
)
});

aggregator_account.min_oracle_results = 1;
aggregator_account.latest_confirmed_round.num_success = 1;
aggregator_account.latest_confirmed_round.result = SwitchboardDecimal {
mantissa: price as i128,
scale: expo as u32,
};
aggregator_account.latest_confirmed_round.round_open_slot = Clock::get()?.slot;

Ok(())
}
}
Expand Down Expand Up @@ -244,28 +199,3 @@ pub fn set_price(
data,
}
}

pub fn set_switchboard_price(
program_id: Pubkey,
switchboard_feed: Pubkey,
price: i64,
expo: i32,
) -> Instruction {
let data = MockPythInstruction::SetSwitchboardPrice { price, expo }
.try_to_vec()
.unwrap();
Instruction {
program_id,
accounts: vec![AccountMeta::new(switchboard_feed, false)],
data,
}
}

pub fn init_switchboard(program_id: Pubkey, switchboard_feed: Pubkey) -> Instruction {
let data = MockPythInstruction::InitSwitchboard.try_to_vec().unwrap();
Instruction {
program_id,
accounts: vec![AccountMeta::new(switchboard_feed, false)],
data,
}
}
123 changes: 123 additions & 0 deletions token-lending/program/tests/helpers/mock_switchboard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/// mock oracle prices in tests with this program.
use solana_program::{
account_info::AccountInfo,
clock::Clock,
entrypoint::ProgramResult,
instruction::{AccountMeta, Instruction},
msg,
pubkey::Pubkey,
sysvar::Sysvar,
};
use std::cell::RefMut;
use switchboard_v2::{AggregatorAccountData, SwitchboardDecimal};

use borsh::{BorshDeserialize, BorshSerialize};
use spl_token::solana_program::{account_info::next_account_info, program_error::ProgramError};
use thiserror::Error;

#[derive(BorshSerialize, BorshDeserialize)]
pub enum MockSwitchboardInstruction {
/// Accounts:
/// 0: AggregatorAccount
InitSwitchboard,

/// Accounts:
/// 0: AggregatorAccount
SetSwitchboardPrice { price: i64, expo: i32 },
}

pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
Processor::process(program_id, accounts, instruction_data)
}

pub struct Processor;
impl Processor {
pub fn process(
_program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
let instruction = MockSwitchboardInstruction::try_from_slice(instruction_data)?;
let account_info_iter = &mut accounts.iter().peekable();

match instruction {
MockSwitchboardInstruction::InitSwitchboard => {
msg!("Mock Switchboard: Init Switchboard");
let switchboard_feed = next_account_info(account_info_iter)?;
let mut data = switchboard_feed.try_borrow_mut_data()?;

let discriminator = [217, 230, 65, 101, 201, 162, 27, 125];
data[0..8].copy_from_slice(&discriminator);
Ok(())
}
MockSwitchboardInstruction::SetSwitchboardPrice { price, expo } => {
msg!("Mock Switchboard: Set Switchboard price");
let switchboard_feed = next_account_info(account_info_iter)?;
let data = switchboard_feed.try_borrow_mut_data()?;

let mut aggregator_account: RefMut<AggregatorAccountData> =
RefMut::map(data, |data| {
bytemuck::from_bytes_mut(
&mut data[8..std::mem::size_of::<AggregatorAccountData>() + 8],
)
});

aggregator_account.min_oracle_results = 1;
aggregator_account.latest_confirmed_round.num_success = 1;
aggregator_account.latest_confirmed_round.result = SwitchboardDecimal {
mantissa: price as i128,
scale: expo as u32,
};
aggregator_account.latest_confirmed_round.round_open_slot = Clock::get()?.slot;

Ok(())
}
}
}
}

#[derive(Error, Debug, Copy, Clone)]
pub enum MockSwitchboardError {
/// Invalid instruction
#[error("Invalid Instruction")]
InvalidInstruction,
#[error("The account is not currently owned by the program")]
IncorrectProgramId,
#[error("Failed to deserialize")]
FailedToDeserialize,
}

impl From<MockSwitchboardError> for ProgramError {
fn from(e: MockSwitchboardError) -> Self {
ProgramError::Custom(e as u32)
}
}

pub fn set_switchboard_price(
program_id: Pubkey,
switchboard_feed: Pubkey,
price: i64,
expo: i32,
) -> Instruction {
let data = MockSwitchboardInstruction::SetSwitchboardPrice { price, expo }
.try_to_vec()
.unwrap();
Instruction {
program_id,
accounts: vec![AccountMeta::new(switchboard_feed, false)],
data,
}
}

pub fn init_switchboard(program_id: Pubkey, switchboard_feed: Pubkey) -> Instruction {
let data = MockSwitchboardInstruction::InitSwitchboard.try_to_vec().unwrap();
Instruction {
program_id,
accounts: vec![AccountMeta::new(switchboard_feed, false)],
data,
}
}
6 changes: 6 additions & 0 deletions token-lending/program/tests/helpers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod flash_loan_proxy;
pub mod flash_loan_receiver;
pub mod genesis;
pub mod mock_pyth;
pub mod mock_switchboard;
pub mod solend_program_test;

use bytemuck::{cast_slice_mut, from_bytes_mut, try_cast_slice_mut, Pod, PodCastError};
Expand Down Expand Up @@ -70,6 +71,11 @@ pub mod wsol_mint {
solana_program::declare_id!("So1m5eppzgokXLBt9Cg8KCMPWhHfTzVaGh26Y415MRG");
}

pub mod msol_mint {
// fake mint, not the real wsol bc i can't mint wsol programmatically
solana_program::declare_id!("mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So");
}

pub mod bonk_mint {
solana_program::declare_id!("bonk99WdRCGrh56xQaeQuRMpMHgiNZEfVoZ53DJAoHS");
}
Expand Down
Loading

0 comments on commit 2b4ad67

Please sign in to comment.