Skip to content

Commit

Permalink
Docs
Browse files Browse the repository at this point in the history
  • Loading branch information
guibescos committed Feb 22, 2024
1 parent d5a3df6 commit 9dc5f2c
Showing 1 changed file with 82 additions and 15 deletions.
97 changes: 82 additions & 15 deletions target_chains/solana/pyth_solana_receiver_state/src/price_update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,26 @@ use {
};


/**
* This enum represents how many guardian signatures were checked for a Pythnet price update
* If full, guardian quorum has been attained
* If partial, at least config.minimum signatures have been verified, but in the case config.minimum_signatures changes in the future we also include the number of signatures that were checked
*/
/// Pyth price updates are bridged to all blockchains via Wormhole.
/// Using the price updates on another chain requires verifying the signatures of the Wormhole guardians.
/// The usual process is to check the signatures for two thirds of the total number of guardians, but this can be cumbersome on Solana because of the transaction size limits,
/// so we also allow for partial verification.
///
/// This enum represents how much a price update has been verified:
/// - If `Full`, we have verified the signatures for two thirds of the current guardians.
/// - If `Partial`, only `num_signatures` guardian signatures have been checked.
///
/// # Warning
/// Using partially verified price updates is dangerous, as it lowers the threshold of guardians that need to collude to produce a malicious price update.
#[derive(AnchorSerialize, AnchorDeserialize, Copy, Clone, PartialEq, BorshSchema, Debug)]
pub enum VerificationLevel {
Partial { num_signatures: u8 },
Full,
}

impl VerificationLevel {
/// Compare two `VerificationLevel`.
/// `Full` is always greater than `Partial`, and `Partial` with more signatures is greater than `Partial` with fewer signatures.
pub fn gte(&self, other: VerificationLevel) -> bool {
match self {
VerificationLevel::Full => true,
Expand All @@ -40,18 +48,25 @@ impl VerificationLevel {
}
}

/// A price update account. This account is used by the Pyth Receiver program to store a verified price update from a Pyth price feed.
/// It contains:
/// - `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.
#[account]
#[derive(BorshSchema)]
pub struct PriceUpdateV1 {
pub write_authority: Pubkey, // This write authority can close this account
pub verification_level: VerificationLevel, // Whether all the guardian signatures have been checked, and if not, how many have been checked
pub write_authority: Pubkey,
pub verification_level: VerificationLevel,
pub price_message: PriceFeedMessage,
}

impl PriceUpdateV1 {
pub const LEN: usize = 8 + 32 + 2 + 32 + 8 + 8 + 4 + 8 + 8 + 8 + 8;
}

/// A Pyth price.
/// The actual price is `(price ± conf)* 10^exponent`. `publish_time` may be used to check the recency of the price.
pub struct Price {
pub price: i64,
pub conf: u64,
Expand All @@ -60,6 +75,14 @@ pub struct Price {
}

impl PriceUpdateV1 {
/// Get a `Price` from a `PriceUpdateV1` 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_price_unchecked(
&self,
feed_id: &FeedId,
Expand All @@ -76,6 +99,26 @@ impl PriceUpdateV1 {
})
}

/// Get a `Price` from a `PriceUpdateV1` 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.
/// Please read the documentation for [`VerificationLevel`] for more information.
///
/// # Example
/// ```
/// use pyth_solana_receiver_state::price_update::{get_feed_id_from_hex, VerificationLevel};
/// use anchor_lang::prelude::*;
///
/// const MAXIMUM_AGE = 30;
/// const FEED_ID: &str = "0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d"; // SOL/USD
///
/// pub fn read_price_account(ctx : Context<ReadPriceAccount>) -> Result<()> {
/// let price_update = &mut ctx.accounts.price_update;
/// let price = price_update.get_price_no_older_than_with_custom_verification_level(&Clock::get()?, MAXIMUM_AGE, &get_feed_id_from_hex(FEED_ID)?, VerificationLevel::Partial{num_signatures: 5})?;
/// Ok(())
/// }
///```
pub fn get_price_no_older_than_with_custom_verification_level(
&self,
clock: &Clock,
Expand All @@ -98,6 +141,22 @@ impl PriceUpdateV1 {
Ok(price)
}

/// Get a `Price` from a `PriceUpdateV1` account for a given `FeedId` no older than `maximum_age` with `Full` verification.
///
/// # Example
/// ```
/// use pyth_solana_receiver_state::price_update::{get_feed_id_from_hex};
/// use anchor_lang::prelude::*;
///
/// const MAXIMUM_AGE = 30;
/// const FEED_ID: &str = "0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d"; // SOL/USD
///
/// pub fn read_price_account(ctx : Context<ReadPriceAccount>) -> Result<()> {
/// let price_update = &mut ctx.accounts.price_update;
/// let price = price_update.get_price_no_older_than(&Clock::get()?, MAXIMUM_AGE, &get_feed_id_from_hex(FEED_ID)?)?;
/// Ok(())
/// }
///```
pub fn get_price_no_older_than(
&self,
clock: &Clock,
Expand All @@ -113,16 +172,24 @@ impl PriceUpdateV1 {
}
}

/**
* This function takes a hex string and returns a FeedId
* The hex string should be 66 characters long, and should start with "0x"
*/
/// Get a `FeedId` from a hex string.
///
/// Price feed ids are a 32 byte unique identifier for each price feed in the Pyth network.
/// They are sometimes represented as a 64 character hex string (with or without a 0x prefix).
///
/// # Example
///
/// ```
/// use pyth_solana_receiver_state::price_update::get_feed_id_from_hex;
/// let feed_id = get_feed_id_from_hex("0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d").unwrap();
/// ```
pub fn get_feed_id_from_hex(input: &str) -> std::result::Result<FeedId, GetPriceError> {
if input.len() != 66 {
return Err(GetPriceError::FeedIdMustBe32Bytes);
}
let mut feed_id: FeedId = [0; 32];
feed_id.copy_from_slice(&hex::decode(&input[2..]).unwrap());
match input.len() {
66 => feed_id.copy_from_slice(&hex::decode(&input[2..]).unwrap()),
64 => feed_id.copy_from_slice(&hex::decode(input).unwrap()),
_ => return Err(GetPriceError::FeedIdMustBe32Bytes),
}
Ok(feed_id)
}

Expand Down

0 comments on commit 9dc5f2c

Please sign in to comment.