浏览代码

feat(solana-receiver-sdk): add `get_twap_no_older_than` to consume TwapUpdates (#2174)

* feat: impl post_twap_update and necessary types

* refactor: move TwapPrice to receiver sdk

* feat: add functions to consume twapupdate accounts

* fix: merge conflict

* feat: bump crate version
Tejas Badadare 11 月之前
父节点
当前提交
b451050ec8

+ 1 - 1
target_chains/solana/Cargo.lock

@@ -3107,7 +3107,7 @@ dependencies = [
 
 [[package]]
 name = "pyth-solana-receiver-sdk"
-version = "0.3.2"
+version = "0.4.0"
 dependencies = [
  "anchor-lang",
  "hex",

+ 4 - 2
target_chains/solana/pyth_solana_receiver_sdk/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "pyth-solana-receiver-sdk"
-version = "0.3.2"
+version = "0.4.0"
 description = "SDK for the Pyth Solana Receiver program"
 authors = ["Pyth Data Association"]
 repository = "https://github.com/pyth-network/pyth-crosschain"
@@ -15,5 +15,7 @@ name = "pyth_solana_receiver_sdk"
 [dependencies]
 anchor-lang = ">=0.28.0"
 hex = ">=0.4.3"
-pythnet-sdk = { path = "../../../pythnet/pythnet_sdk", version = "2.1.0", features = ["solana-program"]}
+pythnet-sdk = { path = "../../../pythnet/pythnet_sdk", version = "2.1.0", features = [
+    "solana-program",
+] }
 solana-program = ">=1.16.0, <2.0.0"

+ 139 - 1
target_chains/solana/pyth_solana_receiver_sdk/src/price_update.rs

@@ -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)
+        );
+    }
 }