use anyhow::Result;
use borsh::{BorshDeserialize, BorshSerialize};
use solana_account::Account;
use solana_keypair::Keypair;
use solana_pubkey::Pubkey;
use solana_signer::Signer;
use std::time::{SystemTime, UNIX_EPOCH};
use crate::account_builders::AccountBuilderBase;
use crate::TestContext;
pub const PYTH_DISCRIMINATOR: [u8; 8] = [34, 241, 35, 99, 157, 126, 244, 205];
pub const DEFAULT_PYTH_RECEIVER_ID: Pubkey = Pubkey::new_from_array([
0x02, 0xe1, 0xae, 0xce, 0x70, 0xcc, 0x1b, 0xac, 0x7a, 0x72, 0xa9, 0x36, 0x74, 0xe4, 0x5a, 0x7b,
0xe1, 0xa8, 0xbd, 0x5a, 0x03, 0xbd, 0x7c, 0x50, 0xfd, 0x3f, 0xa2, 0xc5, 0xa4, 0x92, 0x88, 0x28,
]);
#[derive(Clone, Copy, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
#[repr(u8)]
pub enum VerificationLevel {
Partial { num_signatures: u8 },
Full,
}
#[derive(Clone, Copy, BorshSerialize, BorshDeserialize)]
#[repr(C)]
pub 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(Clone, Copy, BorshSerialize, BorshDeserialize)]
#[repr(C)]
pub struct PriceUpdateV2 {
pub write_authority: Pubkey,
pub verification_level: VerificationLevel,
pub price_message: PriceFeedMessage,
pub posted_slot: u64,
}
pub struct MockPythOracleBuilder<'a> {
ctx: &'a mut TestContext,
price: i64,
exponent: i32,
confidence: u64,
publish_time: Option<i64>,
feed_id: Option<[u8; 32]>,
program_id: Pubkey,
}
impl<'a> MockPythOracleBuilder<'a> {
pub fn new(ctx: &'a mut TestContext) -> Self {
Self {
ctx,
price: 0,
exponent: -8,
confidence: 100_000,
publish_time: None,
feed_id: None,
program_id: DEFAULT_PYTH_RECEIVER_ID,
}
}
pub fn price(mut self, price: i64) -> Self {
self.price = price;
self
}
pub fn exponent(mut self, exp: i32) -> Self {
self.exponent = exp;
self
}
pub fn confidence(mut self, conf: u64) -> Self {
self.confidence = conf;
self
}
pub fn publish_time(mut self, time: i64) -> Self {
self.publish_time = Some(time);
self
}
pub fn feed_id(mut self, id: [u8; 32]) -> Self {
self.feed_id = Some(id);
self
}
pub fn program_id(mut self, id: Pubkey) -> Self {
self.program_id = id;
self
}
pub fn build(self) -> Result<Pubkey> {
let oracle_keypair = Keypair::new();
let oracle_pubkey = oracle_keypair.pubkey();
let current_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
let publish_time = self.publish_time.unwrap_or(current_time);
let feed_id = self.feed_id.unwrap_or(oracle_pubkey.to_bytes());
let price_update = PriceUpdateV2 {
write_authority: Pubkey::default(),
verification_level: VerificationLevel::Full,
price_message: PriceFeedMessage {
feed_id,
price: self.price,
conf: self.confidence,
exponent: self.exponent,
publish_time,
prev_publish_time: publish_time - 1,
ema_price: self.price,
ema_conf: self.confidence,
},
posted_slot: self.ctx.slot(),
};
let mut data = PYTH_DISCRIMINATOR.to_vec();
price_update.serialize(&mut data)?;
self.ctx
.create_account()
.pubkey(oracle_pubkey)
.owner(self.program_id)
.lamports(1_000_000_000)
.data(&data)
.create()?;
Ok(oracle_pubkey)
}
}
impl TestContext {
pub fn create_mock_pyth_oracle(&mut self) -> MockPythOracleBuilder<'_> {
MockPythOracleBuilder::new(self)
}
pub fn update_pyth_price(&mut self, oracle: &Pubkey, price: i64, exponent: i32) -> Result<()> {
let account = self.read_account(oracle)?;
let mut price_update: PriceUpdateV2 =
BorshDeserialize::deserialize(&mut &account.data[8..])?;
let current_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
price_update.price_message.price = price;
price_update.price_message.exponent = exponent;
price_update.price_message.ema_price = price;
price_update.price_message.prev_publish_time = price_update.price_message.publish_time;
price_update.price_message.publish_time = current_time;
price_update.posted_slot = self.slot();
let mut new_data = PYTH_DISCRIMINATOR.to_vec();
price_update.serialize(&mut new_data)?;
self.write_account(
oracle,
Account {
data: new_data,
..account
},
)
}
pub fn refresh_pyth_oracle(&mut self, oracle: &Pubkey) -> Result<()> {
let account = self.read_account(oracle)?;
let mut price_update: PriceUpdateV2 =
BorshDeserialize::deserialize(&mut &account.data[8..])?;
let current_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
price_update.price_message.prev_publish_time = price_update.price_message.publish_time;
price_update.price_message.publish_time = current_time;
price_update.posted_slot = self.slot();
let mut new_data = PYTH_DISCRIMINATOR.to_vec();
price_update.serialize(&mut new_data)?;
self.write_account(
oracle,
Account {
data: new_data,
..account
},
)
}
}