#![allow(dead_code)]
use borsh::BorshDeserialize;
use solana_program::{
account_info::AccountInfo,
entrypoint::ProgramResult,
program_error::ProgramError,
};
use crate::consts::{PYTH_MAX_PRICE_AGE_SECS, PYTH_PUSH_ORACLE_PROGRAM, PYTH_RECEIVER_PROGRAM};
use crate::error::StreakError;
#[derive(Clone, Copy, Debug)]
pub struct Price {
pub price: i64,
pub conf: u64,
pub expo: i32,
pub publish_time: i64,
}
impl Price {
pub fn scale_to_exponent(&self, target_expo: i32) -> Option<Price> {
let diff = self.expo - target_expo;
if diff == 0 {
return Some(*self);
}
if diff > 0 {
let factor = 10i64.checked_pow(diff as u32)?;
Some(Price {
price: self.price.checked_div(factor)?,
conf: self.conf / (factor as u64),
expo: target_expo,
publish_time: self.publish_time,
})
} else {
let factor = 10i64.checked_pow((-diff) as u32)?;
Some(Price {
price: self.price.checked_mul(factor)?,
conf: self.conf.checked_mul(factor as u64)?,
expo: target_expo,
publish_time: self.publish_time,
})
}
}
}
#[derive(BorshDeserialize, Clone, Debug)]
#[allow(dead_code)]
enum VerificationLevel {
Partial { num_signatures: u8 },
Full,
}
#[derive(BorshDeserialize, Clone, Debug)]
struct PriceFeedMessage {
pub feed_id: [u8; 32],
pub price: i64,
pub conf: u64,
pub exponent: i32,
pub publish_time: i64,
pub prev_publish_time: i64,
pub ema_price: i64,
pub ema_conf: u64,
}
#[derive(BorshDeserialize, Clone, Debug)]
struct RawPriceUpdateV2 {
pub write_authority: [u8; 32],
pub verification_level: VerificationLevel,
pub price_message: PriceFeedMessage,
pub posted_slot: u64,
}
pub fn assert_pyth_receiver_owner(info: &AccountInfo) -> ProgramResult {
if *info.key == solana_program::pubkey::Pubkey::default() {
return Err(StreakError::PythInvalidAccount.into());
}
if info.owner != &PYTH_RECEIVER_PROGRAM && info.owner != &PYTH_PUSH_ORACLE_PROGRAM {
return Err(StreakError::PythBadOwner.into());
}
Ok(())
}
pub fn load_fresh_price(
info: &AccountInfo,
expected_feed_id: &[u8; 32],
unix_timestamp: i64,
) -> Result<Price, ProgramError> {
assert_pyth_receiver_owner(info)?;
let data = info.try_borrow_data()?;
if data.len() < 8 {
return Err(StreakError::PythInvalidAccount.into());
}
let raw = RawPriceUpdateV2::deserialize(&mut &data[8..])
.map_err(|_| ProgramError::from(StreakError::PythInvalidAccount))?;
if &raw.price_message.feed_id != expected_feed_id {
return Err(StreakError::PythInvalidAccount.into());
}
let age = unix_timestamp.saturating_sub(raw.price_message.publish_time);
if age < 0 || age as u64 > PYTH_MAX_PRICE_AGE_SECS {
return Err(StreakError::OraclePriceStale.into());
}
Ok(Price {
price: raw.price_message.price,
conf: raw.price_message.conf,
expo: raw.price_message.exponent,
publish_time: raw.price_message.publish_time,
})
}
pub fn outcome_from_open_close(open: Price, close: Price) -> Result<u8, ProgramError> {
const EXP: i32 = -8;
let o = open
.scale_to_exponent(EXP)
.ok_or(StreakError::OracleNormalize)?;
let c = close
.scale_to_exponent(EXP)
.ok_or(StreakError::OracleNormalize)?;
if c.price > o.price {
Ok(0u8) } else {
Ok(1u8) }
}