diff --git a/pyth-sdk-solana/src/state.rs b/pyth-sdk-solana/src/state.rs index c926acb..cee0ce1 100644 --- a/pyth-sdk-solana/src/state.rs +++ b/pyth-sdk-solana/src/state.rs @@ -16,6 +16,7 @@ use pyth_sdk::{ PriceIdentifier, UnixTimestamp, }; +use solana_program::clock::Clock; use solana_program::pubkey::Pubkey; use std::mem::size_of; @@ -354,6 +355,32 @@ impl PriceAccount { } } + /// Get the last valid price as long as it was updated within `slot_threshold` slots of the + /// current slot. + pub fn get_price_no_older_than(&self, clock: &Clock, slot_threshold: u64) -> Option { + if self.agg.status == PriceStatus::Trading + && self.agg.pub_slot >= clock.slot - slot_threshold + { + return Some(Price { + conf: self.agg.conf, + expo: self.expo, + price: self.agg.price, + publish_time: self.timestamp, + }); + } + + if self.prev_slot >= clock.slot - slot_threshold { + return Some(Price { + conf: self.prev_conf, + expo: self.expo, + price: self.prev_price, + publish_time: self.prev_timestamp, + }); + } + + None + } + pub fn to_price_feed(&self, price_key: &Pubkey) -> PriceFeed { let status = self.agg.status; @@ -480,6 +507,7 @@ mod test { Price, PriceFeed, }; + use solana_program::clock::Clock; use solana_program::pubkey::Pubkey; use super::{ @@ -585,4 +613,128 @@ mod test { ) ); } + + #[test] + fn test_happy_use_latest_price_in_price_no_older_than() { + let price_account = PriceAccount { + expo: 5, + agg: PriceInfo { + price: 10, + conf: 20, + status: PriceStatus::Trading, + pub_slot: 1, + ..Default::default() + }, + timestamp: 200, + prev_timestamp: 100, + prev_price: 60, + prev_conf: 70, + ..Default::default() + }; + + let clock = Clock { + slot: 5, + ..Default::default() + }; + + assert_eq!( + price_account.get_price_no_older_than(&clock, 4), + Some(Price { + conf: 20, + expo: 5, + price: 10, + publish_time: 200, + }) + ); + } + + #[test] + fn test_happy_use_prev_price_in_price_no_older_than() { + let price_account = PriceAccount { + expo: 5, + agg: PriceInfo { + price: 10, + conf: 20, + status: PriceStatus::Unknown, + pub_slot: 3, + ..Default::default() + }, + timestamp: 200, + prev_timestamp: 100, + prev_price: 60, + prev_conf: 70, + prev_slot: 1, + ..Default::default() + }; + + let clock = Clock { + slot: 5, + ..Default::default() + }; + + assert_eq!( + price_account.get_price_no_older_than(&clock, 4), + Some(Price { + conf: 70, + expo: 5, + price: 60, + publish_time: 100, + }) + ); + } + + #[test] + fn test_sad_cur_price_unknown_in_price_no_older_than() { + let price_account = PriceAccount { + expo: 5, + agg: PriceInfo { + price: 10, + conf: 20, + status: PriceStatus::Unknown, + pub_slot: 3, + ..Default::default() + }, + timestamp: 200, + prev_timestamp: 100, + prev_price: 60, + prev_conf: 70, + prev_slot: 1, + ..Default::default() + }; + + let clock = Clock { + slot: 5, + ..Default::default() + }; + + // current price is unknown, prev price is too stale + assert_eq!(price_account.get_price_no_older_than(&clock, 3), None); + } + + #[test] + fn test_sad_cur_price_stale_in_price_no_older_than() { + let price_account = PriceAccount { + expo: 5, + agg: PriceInfo { + price: 10, + conf: 20, + status: PriceStatus::Trading, + pub_slot: 3, + ..Default::default() + }, + timestamp: 200, + prev_timestamp: 100, + prev_price: 60, + prev_conf: 70, + prev_slot: 1, + ..Default::default() + }; + + let clock = Clock { + slot: 5, + ..Default::default() + }; + + assert_eq!(price_account.get_price_no_older_than(&clock, 1), None); + } }