Browse Source

feat: add posted slot (#1372)

* feat(solana): add posted slot

* chore: run pre-commit

* Go

* Fix rebase

* Fix test

* Go
guibescos 1 year ago
parent
commit
1e5df8537a

+ 5 - 0
target_chains/solana/program_simulator/src/lib.rs

@@ -18,6 +18,7 @@ use {
         ProgramTestBanksClientExt,
     },
     solana_sdk::{
+        clock::Clock,
         compute_budget,
         signature::{
             Keypair,
@@ -111,6 +112,10 @@ impl ProgramSimulator {
         let lamports = self.banks_client.get_balance(pubkey).await.unwrap();
         Ok(lamports)
     }
+
+    pub async fn get_clock(&mut self) -> Result<Clock, BanksClientError> {
+        self.banks_client.get_sysvar::<Clock>().await
+    }
 }
 
 pub fn into_transaction_error<T: Into<anchor_lang::prelude::Error>>(error: T) -> TransactionError {

+ 10 - 9
target_chains/solana/programs/pyth-solana-receiver/src/lib.rs

@@ -7,7 +7,7 @@ use {
             DataSource,
         },
         price_update::{
-            PriceUpdateV1,
+            PriceUpdateV2,
             VerificationLevel,
         },
     },
@@ -122,7 +122,7 @@ pub mod pyth_solana_receiver {
     /// Post a price update using a VAA and a MerklePriceUpdate.
     /// This function allows you to post a price update in a single transaction.
     /// Compared to `post_update`, it only checks whatever signatures are present in the provided VAA and doesn't fail if the number of signatures is lower than the Wormhole quorum of two thirds of the guardians.
-    /// The number of signatures that were in the VAA is stored in the `VerificationLevel` of the `PriceUpdateV1` account.
+    /// The number of signatures that were in the VAA is stored in the `VerificationLevel` of the `PriceUpdateV2` account.
     ///
     /// We recommend using `post_update_atomic` with 5 signatures. This is close to the maximum signatures you can verify in one transaction without exceeding the transaction size limit.
     ///
@@ -225,7 +225,7 @@ pub mod pyth_solana_receiver {
         let write_authority: &Signer<'_> = &ctx.accounts.write_authority;
         let encoded_vaa = VaaAccount::load(&ctx.accounts.encoded_vaa)?; // IMPORTANT: This line checks that the encoded_vaa has ProcessingStatus::Verified. This check is critical otherwise the program could be tricked into accepting unverified VAAs.
         let treasury: &AccountInfo<'_> = &ctx.accounts.treasury;
-        let price_update_account: &mut Account<'_, PriceUpdateV1> =
+        let price_update_account: &mut Account<'_, PriceUpdateV2> =
             &mut ctx.accounts.price_update_account;
 
         let vaa_components = VaaComponents {
@@ -303,8 +303,8 @@ pub struct PostUpdate<'info> {
     pub treasury:             AccountInfo<'info>,
     /// The constraint is such that either the price_update_account is uninitialized or the write_authority is the write_authority.
     /// Pubkey::default() is the SystemProgram on Solana and it can't sign so it's impossible that price_update_account.write_authority == Pubkey::default() once the account is initialized
-    #[account(init_if_needed, constraint = price_update_account.write_authority == Pubkey::default() || price_update_account.write_authority == write_authority.key() @ ReceiverError::WrongWriteAuthority , payer =payer, space = PriceUpdateV1::LEN)]
-    pub price_update_account: Account<'info, PriceUpdateV1>,
+    #[account(init_if_needed, constraint = price_update_account.write_authority == Pubkey::default() || price_update_account.write_authority == write_authority.key() @ ReceiverError::WrongWriteAuthority , payer =payer, space = PriceUpdateV2::LEN)]
+    pub price_update_account: Account<'info, PriceUpdateV2>,
     pub system_program:       Program<'info, System>,
     pub write_authority:      Signer<'info>,
 }
@@ -326,8 +326,8 @@ pub struct PostUpdateAtomic<'info> {
     pub treasury:             AccountInfo<'info>,
     /// The constraint is such that either the price_update_account is uninitialized or the write_authority is the write_authority.
     /// Pubkey::default() is the SystemProgram on Solana and it can't sign so it's impossible that price_update_account.write_authority == Pubkey::default() once the account is initialized
-    #[account(init_if_needed, constraint = price_update_account.write_authority == Pubkey::default() || price_update_account.write_authority == write_authority.key() @ ReceiverError::WrongWriteAuthority, payer = payer, space = PriceUpdateV1::LEN)]
-    pub price_update_account: Account<'info, PriceUpdateV1>,
+    #[account(init_if_needed, constraint = price_update_account.write_authority == Pubkey::default() || price_update_account.write_authority == write_authority.key() @ ReceiverError::WrongWriteAuthority, payer = payer, space = PriceUpdateV2::LEN)]
+    pub price_update_account: Account<'info, PriceUpdateV2>,
     pub system_program:       Program<'info, System>,
     pub write_authority:      Signer<'info>,
 }
@@ -337,7 +337,7 @@ pub struct ReclaimRent<'info> {
     #[account(mut)]
     pub payer:                Signer<'info>,
     #[account(mut, close = payer, constraint = price_update_account.write_authority == payer.key() @ ReceiverError::WrongWriteAuthority)]
-    pub price_update_account: Account<'info, PriceUpdateV1>,
+    pub price_update_account: Account<'info, PriceUpdateV2>,
 }
 
 #[derive(Debug, AnchorSerialize, AnchorDeserialize, Clone)]
@@ -396,7 +396,7 @@ fn post_price_update_from_vaa<'info>(
     payer: &Signer<'info>,
     write_authority: &Signer<'info>,
     treasury: &AccountInfo<'info>,
-    price_update_account: &mut Account<'_, PriceUpdateV1>,
+    price_update_account: &mut Account<'_, PriceUpdateV2>,
     vaa_components: &VaaComponents,
     vaa_payload: &[u8],
     price_update: &MerklePriceUpdate,
@@ -450,6 +450,7 @@ fn post_price_update_from_vaa<'info>(
             price_update_account.write_authority = write_authority.key();
             price_update_account.verification_level = vaa_components.verification_level;
             price_update_account.price_message = price_feed_message;
+            price_update_account.posted_slot = Clock::get()?.slot;
         }
         Message::TwapMessage(_) => {
             return err!(ReceiverError::UnsupportedMessageType);

+ 21 - 5
target_chains/solana/programs/pyth-solana-receiver/tests/test_post_price_update_from_vaa.rs

@@ -24,7 +24,7 @@ use {
     pyth_solana_receiver_sdk::{
         config::DataSource,
         price_update::{
-            PriceUpdateV1,
+            PriceUpdateV2,
             VerificationLevel,
         },
     },
@@ -352,7 +352,7 @@ async fn test_post_price_update_from_vaa() {
     .await;
 
     let mut price_update_account = program_simulator
-        .get_anchor_account_data::<PriceUpdateV1>(price_update_keypair.pubkey())
+        .get_anchor_account_data::<PriceUpdateV2>(price_update_keypair.pubkey())
         .await
         .unwrap();
 
@@ -365,6 +365,10 @@ async fn test_post_price_update_from_vaa() {
         Message::PriceFeedMessage(price_update_account.price_message),
         feed_1
     );
+    assert_eq!(
+        price_update_account.posted_slot,
+        program_simulator.get_clock().await.unwrap().slot
+    );
 
     // Now poster_2 will pay
     program_simulator
@@ -393,7 +397,7 @@ async fn test_post_price_update_from_vaa() {
     .await;
 
     price_update_account = program_simulator
-        .get_anchor_account_data::<PriceUpdateV1>(price_update_keypair.pubkey())
+        .get_anchor_account_data::<PriceUpdateV2>(price_update_keypair.pubkey())
         .await
         .unwrap();
 
@@ -406,6 +410,10 @@ async fn test_post_price_update_from_vaa() {
         Message::PriceFeedMessage(price_update_account.price_message),
         feed_1
     );
+    assert_eq!(
+        price_update_account.posted_slot,
+        program_simulator.get_clock().await.unwrap().slot
+    );
 
 
     // Now change the fee!
@@ -456,7 +464,7 @@ async fn test_post_price_update_from_vaa() {
 
     // Transaction failed, so the account should not have been updated
     price_update_account = program_simulator
-        .get_anchor_account_data::<PriceUpdateV1>(price_update_keypair.pubkey())
+        .get_anchor_account_data::<PriceUpdateV2>(price_update_keypair.pubkey())
         .await
         .unwrap();
     assert_eq!(price_update_account.write_authority, poster.pubkey());
@@ -468,6 +476,10 @@ async fn test_post_price_update_from_vaa() {
         Message::PriceFeedMessage(price_update_account.price_message),
         feed_1
     );
+    assert_eq!(
+        price_update_account.posted_slot,
+        program_simulator.get_clock().await.unwrap().slot
+    );
 
 
     // Airdrop more
@@ -510,7 +522,7 @@ async fn test_post_price_update_from_vaa() {
     .await;
 
     price_update_account = program_simulator
-        .get_anchor_account_data::<PriceUpdateV1>(price_update_keypair.pubkey())
+        .get_anchor_account_data::<PriceUpdateV2>(price_update_keypair.pubkey())
         .await
         .unwrap();
     assert_eq!(price_update_account.write_authority, poster.pubkey());
@@ -522,6 +534,10 @@ async fn test_post_price_update_from_vaa() {
         Message::PriceFeedMessage(price_update_account.price_message),
         feed_2
     );
+    assert_eq!(
+        price_update_account.posted_slot,
+        program_simulator.get_clock().await.unwrap().slot
+    );
 
     assert_eq!(
         program_simulator

+ 12 - 3
target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates.rs

@@ -20,7 +20,7 @@ use {
         },
     },
     pyth_solana_receiver_sdk::price_update::{
-        PriceUpdateV1,
+        PriceUpdateV2,
         VerificationLevel,
     },
     pythnet_sdk::{
@@ -88,7 +88,7 @@ async fn test_post_update() {
     .await;
 
     let mut price_update_account = program_simulator
-        .get_anchor_account_data::<PriceUpdateV1>(price_update_keypair.pubkey())
+        .get_anchor_account_data::<PriceUpdateV2>(price_update_keypair.pubkey())
         .await
         .unwrap();
 
@@ -101,6 +101,10 @@ async fn test_post_update() {
         Message::PriceFeedMessage(price_update_account.price_message),
         feed_1
     );
+    assert_eq!(
+        price_update_account.posted_slot,
+        program_simulator.get_clock().await.unwrap().slot
+    );
 
     // post another update to the same account
     program_simulator
@@ -126,7 +130,7 @@ async fn test_post_update() {
     .await;
 
     price_update_account = program_simulator
-        .get_anchor_account_data::<PriceUpdateV1>(price_update_keypair.pubkey())
+        .get_anchor_account_data::<PriceUpdateV2>(price_update_keypair.pubkey())
         .await
         .unwrap();
 
@@ -139,6 +143,11 @@ async fn test_post_update() {
         Message::PriceFeedMessage(price_update_account.price_message),
         feed_2
     );
+    assert_eq!(
+        price_update_account.posted_slot,
+        program_simulator.get_clock().await.unwrap().slot
+    );
+
 
     // This poster doesn't have the write authority
     let poster_2 = program_simulator.get_funded_keypair().await.unwrap();

+ 16 - 4
target_chains/solana/programs/pyth-solana-receiver/tests/test_post_updates_atomic.rs

@@ -20,7 +20,7 @@ use {
         },
     },
     pyth_solana_receiver_sdk::price_update::{
-        PriceUpdateV1,
+        PriceUpdateV2,
         VerificationLevel,
     },
     pythnet_sdk::{
@@ -94,7 +94,7 @@ async fn test_post_update_atomic() {
     .await;
 
     let mut price_update_account = program_simulator
-        .get_anchor_account_data::<PriceUpdateV1>(price_update_keypair.pubkey())
+        .get_anchor_account_data::<PriceUpdateV2>(price_update_keypair.pubkey())
         .await
         .unwrap();
 
@@ -107,6 +107,10 @@ async fn test_post_update_atomic() {
         Message::PriceFeedMessage(price_update_account.price_message),
         feed_1
     );
+    assert_eq!(
+        price_update_account.posted_slot,
+        program_simulator.get_clock().await.unwrap().slot
+    );
 
     // post another update to the same account
     program_simulator
@@ -136,7 +140,7 @@ async fn test_post_update_atomic() {
     assert_treasury_balance(&mut program_simulator, 0, SECONDARY_TREASURY_ID).await;
 
     price_update_account = program_simulator
-        .get_anchor_account_data::<PriceUpdateV1>(price_update_keypair.pubkey())
+        .get_anchor_account_data::<PriceUpdateV2>(price_update_keypair.pubkey())
         .await
         .unwrap();
 
@@ -149,6 +153,10 @@ async fn test_post_update_atomic() {
         Message::PriceFeedMessage(price_update_account.price_message),
         feed_2
     );
+    assert_eq!(
+        price_update_account.posted_slot,
+        program_simulator.get_clock().await.unwrap().slot
+    );
 
     // use another treasury account
     program_simulator
@@ -183,7 +191,7 @@ async fn test_post_update_atomic() {
     .await;
 
     price_update_account = program_simulator
-        .get_anchor_account_data::<PriceUpdateV1>(price_update_keypair.pubkey())
+        .get_anchor_account_data::<PriceUpdateV2>(price_update_keypair.pubkey())
         .await
         .unwrap();
     assert_eq!(price_update_account.write_authority, poster.pubkey());
@@ -195,6 +203,10 @@ async fn test_post_update_atomic() {
         Message::PriceFeedMessage(price_update_account.price_message),
         feed_1
     );
+    assert_eq!(
+        price_update_account.posted_slot,
+        program_simulator.get_clock().await.unwrap().slot
+    );
 }
 
 #[tokio::test]

+ 22 - 17
target_chains/solana/pyth_solana_receiver_sdk/src/price_update.rs

@@ -53,16 +53,18 @@ impl VerificationLevel {
 /// - `write_authority`: The write authority for this account. This authority can close this account to reclaim rent or update the account to contain a different price update.
 /// - `verification_level`: The [`VerificationLevel`] of this price update. This represents how many Wormhole guardian signatures have been verified for this price update.
 /// - `price_message`: The actual price update.
+/// - `posted_slot`: The slot at which this price update was posted.
 #[account]
 #[derive(BorshSchema)]
-pub struct PriceUpdateV1 {
+pub struct PriceUpdateV2 {
     pub write_authority:    Pubkey,
     pub verification_level: VerificationLevel,
     pub price_message:      PriceFeedMessage,
+    pub posted_slot:        u64,
 }
 
-impl PriceUpdateV1 {
-    pub const LEN: usize = 8 + 32 + 2 + 32 + 8 + 8 + 4 + 8 + 8 + 8 + 8;
+impl PriceUpdateV2 {
+    pub const LEN: usize = 8 + 32 + 2 + 32 + 8 + 8 + 4 + 8 + 8 + 8 + 8 + 8;
 }
 
 /// A Pyth price.
@@ -75,8 +77,8 @@ pub struct Price {
     pub publish_time: i64,
 }
 
-impl PriceUpdateV1 {
-    /// Get a `Price` from a `PriceUpdateV1` account for a given `FeedId`.
+impl PriceUpdateV2 {
+    /// Get a `Price` from a `PriceUpdateV2` account for a given `FeedId`.
     ///
     /// # Warning
     /// This function does not check :
@@ -100,7 +102,7 @@ impl PriceUpdateV1 {
         })
     }
 
-    /// Get a `Price` from a `PriceUpdateV1` account for a given `FeedId` no older than `maximum_age` with customizable verification level.
+    /// Get a `Price` from a `PriceUpdateV2` account for a given `FeedId` no older than `maximum_age` with customizable verification level.
     ///
     /// # Warning
     /// Lowering the verification level from `Full` to `Partial` increases the risk of using a malicious price update.
@@ -108,7 +110,7 @@ impl PriceUpdateV1 {
     ///
     /// # Example
     /// ```
-    /// use pyth_solana_receiver_sdk::price_update::{get_feed_id_from_hex, VerificationLevel, PriceUpdateV1};
+    /// use pyth_solana_receiver_sdk::price_update::{get_feed_id_from_hex, VerificationLevel, PriceUpdateV2};
     /// use anchor_lang::prelude::*;
     ///
     /// const MAXIMUM_AGE : u64 = 30;
@@ -117,7 +119,7 @@ impl PriceUpdateV1 {
     /// #[derive(Accounts)]
     /// #[instruction(amount_in_usd : u64)]
     /// pub struct ReadPriceAccount<'info> {
-    ///     pub price_update: Account<'info, PriceUpdateV1>,
+    ///     pub price_update: Account<'info, PriceUpdateV2>,
     /// }
     ///
     /// pub fn read_price_account(ctx : Context<ReadPriceAccount>) -> Result<()> {
@@ -148,11 +150,11 @@ impl PriceUpdateV1 {
         Ok(price)
     }
 
-    /// Get a `Price` from a `PriceUpdateV1` account for a given `FeedId` no older than `maximum_age` with `Full` verification.
+    /// Get a `Price` from a `PriceUpdateV2` 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, PriceUpdateV1};
+    /// use pyth_solana_receiver_sdk::price_update::{get_feed_id_from_hex, PriceUpdateV2};
     /// use anchor_lang::prelude::*;
     ///
     /// const MAXIMUM_AGE : u64 = 30;
@@ -161,7 +163,7 @@ impl PriceUpdateV1 {
     /// #[derive(Accounts)]
     /// #[instruction(amount_in_usd : u64)]
     /// pub struct ReadPriceAccount<'info> {
-    ///     pub price_update: Account<'info, PriceUpdateV1>,
+    ///     pub price_update: Account<'info, PriceUpdateV2>,
     /// }
     ///
     /// pub fn read_price_account(ctx : Context<ReadPriceAccount>) -> Result<()> {
@@ -217,7 +219,7 @@ pub mod tests {
             error::GetPriceError,
             price_update::{
                 Price,
-                PriceUpdateV1,
+                PriceUpdateV2,
                 VerificationLevel,
             },
         },
@@ -233,8 +235,8 @@ pub mod tests {
     #[test]
     fn check_size() {
         assert!(
-            PriceUpdateV1::discriminator().len() + borsh0_10::get_packed_len::<PriceUpdateV1>()
-                == PriceUpdateV1::LEN
+            PriceUpdateV2::discriminator().len() + borsh0_10::get_packed_len::<PriceUpdateV2>()
+                == PriceUpdateV2::LEN
         );
     }
 
@@ -293,7 +295,7 @@ pub mod tests {
             ..Default::default()
         };
 
-        let price_update_unverified = PriceUpdateV1 {
+        let price_update_unverified = PriceUpdateV2 {
             write_authority:    Pubkey::new_unique(),
             verification_level: VerificationLevel::Partial { num_signatures: 0 },
             price_message:      PriceFeedMessage {
@@ -306,9 +308,10 @@ pub mod tests {
                 prev_publish_time: 899,
                 publish_time: 900,
             },
+            posted_slot:        0,
         };
 
-        let price_update_partially_verified = PriceUpdateV1 {
+        let price_update_partially_verified = PriceUpdateV2 {
             write_authority:    Pubkey::new_unique(),
             verification_level: VerificationLevel::Partial { num_signatures: 5 },
             price_message:      PriceFeedMessage {
@@ -321,9 +324,10 @@ pub mod tests {
                 prev_publish_time: 899,
                 publish_time: 900,
             },
+            posted_slot:        0,
         };
 
-        let price_update_fully_verified = PriceUpdateV1 {
+        let price_update_fully_verified = PriceUpdateV2 {
             write_authority:    Pubkey::new_unique(),
             verification_level: VerificationLevel::Full,
             price_message:      PriceFeedMessage {
@@ -336,6 +340,7 @@ pub mod tests {
                 prev_publish_time: 899,
                 publish_time: 900,
             },
+            posted_slot:        0,
         };