feat: add posted slot (#1372)

* feat(solana): add posted slot

* chore: run pre-commit

* Go

* Fix rebase

* Fix test

* Go
This commit is contained in:
guibescos 2024-03-20 19:38:33 +00:00 committed by GitHub
parent f79f205895
commit 1e5df8537a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 86 additions and 38 deletions

View File

@ -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 {

View File

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

View File

@ -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

View File

@ -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();

View File

@ -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]

View File

@ -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,
};