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

Use pyth sdk + tests #113

Merged
merged 20 commits into from
Dec 13, 2022
Merged
Show file tree
Hide file tree
Changes from 2 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
76 changes: 76 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions token-lending/program/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ arrayref = "0.3.6"
bytemuck = "1.5.1"
num-derive = "0.3"
num-traits = "0.2"
pyth-sdk-solana = "0.6.1"
solana-program = "=1.9.18"
spl-token = { version = "3.2.0", features=["no-entrypoint"] }
static_assertions = "1.1.0"
switchboard-program = "0.2.0"
switchboard-v2 = "0.1.3"
thiserror = "1.0"
Expand Down
46 changes: 15 additions & 31 deletions token-lending/program/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::{
error::LendingError,
instruction::LendingInstruction,
math::{Decimal, Rate, TryAdd, TryDiv, TryMul, TrySub, WAD},
// TODO use pyth_sdk_solana everywhere
pyth,
state::{
CalculateBorrowResult, CalculateLiquidationResult, CalculateRepayResult,
Expand All @@ -14,6 +15,7 @@ use crate::{
},
};
use num_traits::FromPrimitive;
use pyth_sdk_solana;
use solana_program::{
account_info::{next_account_info, AccountInfo},
decode_error::DecodeError,
Expand Down Expand Up @@ -2591,54 +2593,36 @@ fn get_price(
}

fn get_pyth_price(pyth_price_info: &AccountInfo, clock: &Clock) -> Result<Decimal, ProgramError> {
const MAX_PYTH_CONFIDENCE_RATIO: u64 = 10;
const STALE_AFTER_SLOTS_ELAPSED: u64 = 240;

if *pyth_price_info.key == solend_program::NULL_PUBKEY {
return Err(LendingError::NullOracleConfig.into());
}

let pyth_price_data = pyth_price_info.try_borrow_data()?;
let pyth_price = pyth::load::<pyth::Price>(&pyth_price_data)
.map_err(|_| ProgramError::InvalidAccountData)?;

if pyth_price.ptype != pyth::PriceType::Price {
msg!("Oracle price type is invalid {}", pyth_price.ptype as u8);
return Err(LendingError::InvalidOracleConfig.into());
}

if pyth_price.agg.status != pyth::PriceStatus::Trading {
msg!(
"Oracle price status is invalid: {}",
pyth_price.agg.status as u8
);
return Err(LendingError::InvalidOracleConfig.into());
}
let price_feed = pyth_sdk_solana::load_price_feed_from_account_info(pyth_price_info)?;
let pyth_price = price_feed
.get_latest_available_price_within_duration(clock.unix_timestamp, STALE_AFTER_SLOTS_ELAPSED)
.ok_or(LendingError::InvalidOracleConfig)?;

let slots_elapsed = clock
.slot
.checked_sub(pyth_price.valid_slot)
.ok_or(LendingError::MathOverflow)?;
if slots_elapsed >= STALE_AFTER_SLOTS_ELAPSED {
msg!("Pyth oracle price is stale");
return Err(LendingError::InvalidOracleConfig.into());
}

let price: u64 = pyth_price.agg.price.try_into().map_err(|_| {
let price: u64 = pyth_price.price.try_into().map_err(|_| {
msg!("Oracle price cannot be negative");
LendingError::InvalidOracleConfig
})?;

let conf = pyth_price.agg.conf;

let confidence_ratio: u64 = 10;
// Perhaps confidence_ratio should exist as a per reserve config
// 100/confidence_ratio = maximum size of confidence range as a percent of price
// confidence_ratio of 10 filters out pyth prices with conf > 10% of price
if conf.checked_mul(confidence_ratio).unwrap() > price {
if pyth_price
.conf
.checked_mul(MAX_PYTH_CONFIDENCE_RATIO)
.unwrap()
> price
{
msg!(
"Oracle price confidence is too wide. price: {}, conf: {}",
price,
conf,
pyth_price.conf,
);
return Err(LendingError::InvalidOracleConfig.into());
}
Expand Down
5 changes: 5 additions & 0 deletions token-lending/program/src/pyth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use bytemuck::{
cast_slice, cast_slice_mut, from_bytes, from_bytes_mut, try_cast_slice, try_cast_slice_mut,
0xripleys marked this conversation as resolved.
Show resolved Hide resolved
Pod, PodCastError, Zeroable,
};
use static_assertions::assert_eq_size;
use std::mem::size_of;

pub const MAGIC: u32 = 0xa1b2c3d4;
Expand Down Expand Up @@ -53,6 +54,7 @@ pub struct PriceInfo {
pub corp_act: CorpAction,
pub pub_slot: u64,
}
assert_eq_size!(PriceInfo, [u8; 32]);

#[derive(Copy, Clone)]
#[repr(C)]
Expand All @@ -61,6 +63,7 @@ pub struct PriceComp {
agg: PriceInfo,
latest: PriceInfo,
}
assert_eq_size!(PriceComp, [u8; 96]);

#[derive(PartialEq, Copy, Clone)]
#[repr(C)]
Expand Down Expand Up @@ -97,6 +100,8 @@ pub struct Price {
pub comp: [PriceComp; 32], // price components one per quoter
}

assert_eq_size!(Price, [u8; 3312]);

#[cfg(target_endian = "little")]
unsafe impl Zeroable for Price {}

Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
26 changes: 24 additions & 2 deletions token-lending/program/tests/helpers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ pub mod flash_loan_receiver;
pub mod genesis;

use assert_matches::*;
use bytemuck::{cast_slice_mut, from_bytes_mut, try_cast_slice_mut, Pod, PodCastError};
use pyth_sdk_solana::state::PriceAccount;
use solana_program::{program_option::COption, program_pack::Pack, pubkey::Pubkey};
use solana_program_test::*;
use solana_sdk::{
Expand All @@ -22,7 +24,7 @@ use solend_program::{
},
math::{Decimal, Rate, TryAdd, TryMul},
processor::switchboard_v2_mainnet,
pyth,
// pyth,
state::{
InitLendingMarketParams, InitObligationParams, InitReserveParams, LendingMarket,
NewReserveCollateralParams, NewReserveLiquidityParams, Obligation, ObligationCollateral,
Expand All @@ -35,6 +37,10 @@ use spl_token::{
state::{Account as Token, AccountState, Mint},
};
use std::{convert::TryInto, str::FromStr};
use std::{
mem::size_of,
time::{SystemTime, UNIX_EPOCH},
};
use switchboard_v2::AggregatorAccountData;

pub const QUOTE_CURRENCY: [u8; 32] =
Expand Down Expand Up @@ -1261,6 +1267,13 @@ pub fn add_usdc_oracle_switchboardv2(test: &mut ProgramTest) -> TestOracle {
)
}

pub fn load_mut<T: Pod>(data: &mut [u8]) -> Result<&mut T, PodCastError> {
let size = size_of::<T>();
Ok(from_bytes_mut(cast_slice_mut::<u8, u8>(
try_cast_slice_mut(&mut data[0..size])?,
)))
}

pub fn add_oracle(
test: &mut ProgramTest,
pyth_product_pubkey: Pubkey,
Expand All @@ -1287,7 +1300,7 @@ pub fn add_oracle(
panic!("Unable to locate {}", filename);
}));

let mut pyth_price = pyth::load_mut::<pyth::Price>(pyth_price_data.as_mut_slice()).unwrap();
let mut pyth_price = load_mut::<PriceAccount>(pyth_price_data.as_mut_slice()).unwrap();
0xripleys marked this conversation as resolved.
Show resolved Hide resolved

let decimals = 10u64
.checked_pow(pyth_price.expo.checked_abs().unwrap().try_into().unwrap())
Expand All @@ -1302,6 +1315,15 @@ pub fn add_oracle(
.try_into()
.unwrap();

pyth_price.prev_price = pyth_price.agg.price;
pyth_price.prev_slot = pyth_price.valid_slot;
pyth_price.prev_conf = pyth_price.agg.conf;
pyth_price.timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
pyth_price.prev_timestamp = pyth_price.timestamp;

test.add_account(
pyth_price_pubkey,
Account {
Expand Down