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:
parent
f79f205895
commit
1e5df8537a
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue