Skip to content

Commit

Permalink
feat(solana-receiver-sdk): add get_twap_no_older_than to consume Tw…
Browse files Browse the repository at this point in the history
…apUpdates (#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
  • Loading branch information
tejasbadadare authored Dec 9, 2024
1 parent a08be17 commit b451050
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 4 deletions.
2 changes: 1 addition & 1 deletion target_chains/solana/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions target_chains/solana/pyth_solana_receiver_sdk/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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"
140 changes: 139 additions & 1 deletion target_chains/solana/pyth_solana_receiver_sdk/src/price_update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
);
}
}

0 comments on commit b451050

Please sign in to comment.