|
|
@@ -83,6 +83,70 @@ impl TwapUpdate {
|
|
|
+ 8
|
|
|
// posted_slot
|
|
|
);
|
|
|
+
|
|
|
+ /// Get a `TwapPrice` from a `TwapUpdate` account for a given `FeedId`.
|
|
|
+ ///
|
|
|
+ /// # Warning
|
|
|
+ /// This function does not check :
|
|
|
+ /// - How recent the price is
|
|
|
+ /// - Whether the price update has been verified
|
|
|
+ ///
|
|
|
+ /// It is therefore unsafe to use this function without any extra checks,
|
|
|
+ /// as it allows for the possibility of using unverified or outdated price updates.
|
|
|
+ pub fn get_twap_unchecked(
|
|
|
+ &self,
|
|
|
+ feed_id: &FeedId,
|
|
|
+ ) -> std::result::Result<TwapPrice, GetPriceError> {
|
|
|
+ check!(
|
|
|
+ self.twap.feed_id == *feed_id,
|
|
|
+ GetPriceError::MismatchedFeedId
|
|
|
+ );
|
|
|
+ Ok(self.twap)
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Get a `TwapPrice` from a `TwapUpdate` account for a given `FeedId` no older than `maximum_age` with `Full` verification.
|
|
|
+ ///
|
|
|
+ /// # Example
|
|
|
+ /// ```
|
|
|
+ /// use pyth_solana_receiver_sdk::price_update::{get_feed_id_from_hex, TwapUpdate};
|
|
|
+ /// use anchor_lang::prelude::*;
|
|
|
+ ///
|
|
|
+ /// const MAXIMUM_AGE : u64 = 30;
|
|
|
+ /// const FEED_ID: &str = "0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d"; // SOL/USD
|
|
|
+ ///
|
|
|
+ /// #[derive(Accounts)]
|
|
|
+ /// pub struct ReadTwapAccount<'info> {
|
|
|
+ /// pub twap_update: Account<'info, TwapUpdate>,
|
|
|
+ /// }
|
|
|
+ ///
|
|
|
+ /// pub fn read_twap_account(ctx : Context<ReadTwapAccount>) -> Result<()> {
|
|
|
+ /// let twap_update = &ctx.accounts.twap_update;
|
|
|
+ /// let twap = twap_update.get_twap_no_older_than(&Clock::get()?, MAXIMUM_AGE, &get_feed_id_from_hex(FEED_ID)?)?;
|
|
|
+ /// Ok(())
|
|
|
+ /// }
|
|
|
+ /// ```
|
|
|
+ pub fn get_twap_no_older_than(
|
|
|
+ &self,
|
|
|
+ clock: &Clock,
|
|
|
+ maximum_age: u64,
|
|
|
+ feed_id: &FeedId,
|
|
|
+ ) -> std::result::Result<TwapPrice, GetPriceError> {
|
|
|
+ // Ensure the update is fully verified
|
|
|
+ check!(
|
|
|
+ self.verification_level.eq(&VerificationLevel::Full),
|
|
|
+ GetPriceError::InsufficientVerificationLevel
|
|
|
+ );
|
|
|
+ // Ensure the update isn't outdated
|
|
|
+ let twap_price = self.get_twap_unchecked(feed_id)?;
|
|
|
+ check!(
|
|
|
+ twap_price
|
|
|
+ .end_time
|
|
|
+ .saturating_add(maximum_age.try_into().unwrap())
|
|
|
+ >= clock.unix_timestamp,
|
|
|
+ GetPriceError::PriceTooOld
|
|
|
+ );
|
|
|
+ Ok(twap_price)
|
|
|
+ }
|
|
|
}
|
|
|
/// The time weighted average price & conf for a feed over the window [start_time, end_time].
|
|
|
/// This type is used to persist the calculated TWAP in TwapUpdate accounts on Solana.
|
|
|
@@ -249,7 +313,7 @@ pub mod tests {
|
|
|
use {
|
|
|
crate::{
|
|
|
error::GetPriceError,
|
|
|
- price_update::{Price, PriceUpdateV2, VerificationLevel},
|
|
|
+ price_update::{Price, PriceUpdateV2, TwapPrice, TwapUpdate, VerificationLevel},
|
|
|
},
|
|
|
anchor_lang::Discriminator,
|
|
|
pythnet_sdk::messages::PriceFeedMessage,
|
|
|
@@ -486,4 +550,78 @@ pub mod tests {
|
|
|
Err(GetPriceError::MismatchedFeedId)
|
|
|
);
|
|
|
}
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn test_get_twap_no_older_than() {
|
|
|
+ let expected_twap = TwapPrice {
|
|
|
+ feed_id: [0; 32],
|
|
|
+ start_time: 800,
|
|
|
+ end_time: 900,
|
|
|
+ price: 1,
|
|
|
+ conf: 2,
|
|
|
+ exponent: -3,
|
|
|
+ down_slots_ratio: 0,
|
|
|
+ };
|
|
|
+
|
|
|
+ let feed_id = [0; 32];
|
|
|
+ let mismatched_feed_id = [1; 32];
|
|
|
+ let mock_clock = Clock {
|
|
|
+ unix_timestamp: 1000,
|
|
|
+ ..Default::default()
|
|
|
+ };
|
|
|
+
|
|
|
+ let twap_update_unverified = TwapUpdate {
|
|
|
+ write_authority: Pubkey::new_unique(),
|
|
|
+ verification_level: VerificationLevel::Partial { num_signatures: 0 },
|
|
|
+ twap: expected_twap,
|
|
|
+ posted_slot: 0,
|
|
|
+ };
|
|
|
+
|
|
|
+ let twap_update_fully_verified = TwapUpdate {
|
|
|
+ write_authority: Pubkey::new_unique(),
|
|
|
+ verification_level: VerificationLevel::Full,
|
|
|
+ twap: expected_twap,
|
|
|
+ posted_slot: 0,
|
|
|
+ };
|
|
|
+
|
|
|
+ // Test unchecked access
|
|
|
+ assert_eq!(
|
|
|
+ twap_update_unverified.get_twap_unchecked(&feed_id),
|
|
|
+ Ok(expected_twap)
|
|
|
+ );
|
|
|
+ assert_eq!(
|
|
|
+ twap_update_fully_verified.get_twap_unchecked(&feed_id),
|
|
|
+ Ok(expected_twap)
|
|
|
+ );
|
|
|
+
|
|
|
+ // Test with age and verification checks
|
|
|
+ assert_eq!(
|
|
|
+ twap_update_unverified.get_twap_no_older_than(&mock_clock, 100, &feed_id),
|
|
|
+ Err(GetPriceError::InsufficientVerificationLevel)
|
|
|
+ );
|
|
|
+ assert_eq!(
|
|
|
+ twap_update_fully_verified.get_twap_no_older_than(&mock_clock, 100, &feed_id),
|
|
|
+ Ok(expected_twap)
|
|
|
+ );
|
|
|
+
|
|
|
+ // Test with reduced maximum age
|
|
|
+ assert_eq!(
|
|
|
+ twap_update_fully_verified.get_twap_no_older_than(&mock_clock, 10, &feed_id),
|
|
|
+ Err(GetPriceError::PriceTooOld)
|
|
|
+ );
|
|
|
+
|
|
|
+ // Test with mismatched feed id
|
|
|
+ assert_eq!(
|
|
|
+ twap_update_fully_verified.get_twap_unchecked(&mismatched_feed_id),
|
|
|
+ Err(GetPriceError::MismatchedFeedId)
|
|
|
+ );
|
|
|
+ assert_eq!(
|
|
|
+ twap_update_fully_verified.get_twap_no_older_than(
|
|
|
+ &mock_clock,
|
|
|
+ 100,
|
|
|
+ &mismatched_feed_id
|
|
|
+ ),
|
|
|
+ Err(GetPriceError::MismatchedFeedId)
|
|
|
+ );
|
|
|
+ }
|
|
|
}
|