From bac12636e7d6923bab2beeca99fc453a4a4b22eb Mon Sep 17 00:00:00 2001 From: Ali Behjati Date: Tue, 16 Aug 2022 17:03:58 +0200 Subject: [PATCH 1/5] Replace get_prev_price with better methods --- pyth-sdk/src/lib.rs | 76 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 61 insertions(+), 15 deletions(-) diff --git a/pyth-sdk/src/lib.rs b/pyth-sdk/src/lib.rs index 7025576..4208b0c 100644 --- a/pyth-sdk/src/lib.rs +++ b/pyth-sdk/src/lib.rs @@ -84,6 +84,7 @@ pub type ProductIdentifier = Identifier; /// Jan 1970). It is a signed integer because it's the standard in Unix systems and allows easier /// time difference. pub type UnixTimestamp = i64; +pub type DurationInSeconds = u64; /// Represents availability status of a price feed. #[derive( @@ -277,22 +278,67 @@ impl PriceFeed { } } - /// Get the "unchecked" previous price with Trading status, - /// along with the timestamp at which it was generated. + /// Get the latest available price, along with the timestamp when it was generated. /// - /// WARNING: - /// We make no guarantees about the unchecked price and confidence returned by - /// this function: it could differ significantly from the current price. - /// We strongly encourage you to use `get_current_price` instead. - pub fn get_prev_price_unchecked(&self) -> (Price, UnixTimestamp) { - ( - Price { - price: self.prev_price, - conf: self.prev_conf, - expo: self.expo, - }, - self.prev_publish_time, - ) + /// This function returns the same price as `get_current_price` in the case where a price was + /// available at the time this `PriceFeed` was published (`publish_time`). However, if a + /// price was not available at that time, this function returns the price from the latest + /// time at which the price was available. The returned price can be from arbitrarily far in + /// the past; this function makes no guarantees that the returned price is recent or useful + /// for any particular application. + /// + /// Users of this function should check the returned timestamp to ensure that the returned price + /// is sufficiently recent for their application. If you are considering using this + /// function, it may be safer / easier to use either `get_current_price` or + /// `get_latest_available_price_within_duration`. + /// + /// Returns a struct containing the latest available price, confidence interval, and the + /// exponent for both numbers along with the timestamp when that price was generated. + pub fn get_latest_available_price_unchecked(&self) -> (Price, UnixTimestamp) { + match self.status { + PriceStatus::Trading => ( + Price { + price: self.price, + conf: self.conf, + expo: self.expo, + }, + self.publish_time, + ), + _ => ( + Price { + price: self.prev_price, + conf: self.prev_conf, + expo: self.expo, + }, + self.prev_publish_time, + ), + } + } + + /// Get the latest price as long as it was updated within `duration` seconds of the + /// `current_time`. + /// + /// This function is a sanity-checked version of `get_latest_available_price_unchecked` which is + /// useful in applications that require a sufficiently-recent price. Returns `None` if the + /// price wasn't updated sufficiently recently. + /// + /// Returns a struct containing the latest available price, confidence interval and the exponent + /// for both numbers, or `None` if no price update occurred within `duration` seconds of the + /// `current_time`. + pub fn get_latest_available_price_within_duration( + &self, + current_time: UnixTimestamp, + duration: DurationInSeconds, + ) -> Option { + let (price, publish_time) = self.get_latest_available_price_unchecked(); + + let time_diff_abs = (publish_time - current_time).abs() as u64; + + if time_diff_abs > duration { + return None; + } + + Some(price) } } #[cfg(test)] From 9f4053a8794505e393a8044c3769580c871fc9d3 Mon Sep 17 00:00:00 2001 From: Ali Behjati Date: Tue, 16 Aug 2022 17:27:35 +0200 Subject: [PATCH 2/5] Fix handling stale price with new methods --- pyth-sdk-solana/src/state.rs | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/pyth-sdk-solana/src/state.rs b/pyth-sdk-solana/src/state.rs index ffd32f3..2dba6ef 100644 --- a/pyth-sdk-solana/src/state.rs +++ b/pyth-sdk-solana/src/state.rs @@ -25,13 +25,9 @@ pub use pyth_sdk::{ PriceStatus, }; -#[cfg(target_arch = "bpf")] -use solana_program::{ - clock::Clock, - sysvar::Sysvar, -}; +use solana_program::clock::Clock; +use solana_program::sysvar::Sysvar; -#[cfg(target_arch = "bpf")] use crate::VALID_SLOT_PERIOD; use crate::PythError; @@ -348,14 +344,20 @@ unsafe impl Pod for PriceAccount { impl PriceAccount { pub fn to_price_feed(&self, price_key: &Pubkey) -> PriceFeed { - #[allow(unused_mut)] let mut status = self.agg.status; - - #[cfg(target_arch = "bpf")] - if matches!(status, PriceStatus::Trading) - && Clock::get().unwrap().slot.saturating_sub(self.agg.pub_slot) > VALID_SLOT_PERIOD - { - status = PriceStatus::Unknown; + let mut prev_price = self.prev_price; + let mut prev_conf = self.prev_conf; + let mut prev_publish_time = self.prev_timestamp; + + if let Ok(clock) = Clock::get() { + if matches!(status, PriceStatus::Trading) + && clock.slot.saturating_sub(self.agg.pub_slot) > VALID_SLOT_PERIOD + { + status = PriceStatus::Unknown; + prev_price = self.agg.price; + prev_conf = self.agg.conf; + prev_publish_time = self.timestamp; + } } PriceFeed::new( @@ -370,9 +372,9 @@ impl PriceAccount { self.agg.conf, self.ema_price.val, self.ema_conf.val as u64, - self.prev_price, - self.prev_conf, - self.prev_timestamp, + prev_price, + prev_conf, + prev_publish_time, ) } } From 86b3b9b40378ef80117d3d9ee8c7d8f2368e5162 Mon Sep 17 00:00:00 2001 From: Ali Behjati Date: Tue, 16 Aug 2022 17:28:19 +0200 Subject: [PATCH 3/5] Expose price identifier --- pyth-sdk-solana/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/pyth-sdk-solana/src/lib.rs b/pyth-sdk-solana/src/lib.rs index bf399b2..725add6 100644 --- a/pyth-sdk-solana/src/lib.rs +++ b/pyth-sdk-solana/src/lib.rs @@ -19,6 +19,7 @@ use state::load_price_account; pub use pyth_sdk::{ Price, PriceFeed, + PriceIdentifier, PriceStatus, ProductIdentifier, }; From c8caa40488d10477e30b2c1377cef84fbd2937d9 Mon Sep 17 00:00:00 2001 From: Ali Behjati Date: Tue, 16 Aug 2022 17:06:58 +0200 Subject: [PATCH 4/5] Bump version --- examples/cw-contract/Cargo.toml | 4 ++-- pyth-sdk-cw/Cargo.toml | 4 ++-- pyth-sdk-solana/Cargo.toml | 4 ++-- pyth-sdk-solana/test-contract/Cargo.toml | 2 +- pyth-sdk/Cargo.toml | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/cw-contract/Cargo.toml b/examples/cw-contract/Cargo.toml index 2f400db..833a509 100644 --- a/examples/cw-contract/Cargo.toml +++ b/examples/cw-contract/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "example-cw-contract" version = "0.1.0" -authors = ["Ali Behjati "] +authors = ["Pyth Data Foundation"] edition = "2018" exclude = [ @@ -34,7 +34,7 @@ cosmwasm-storage = { version = "1.0.0" } cw-storage-plus = "0.13.4" schemars = "0.8" serde = { version = "1.0", default-features = false, features = ["derive"] } -pyth-sdk-cw = { version = "0.1.0", path = "../../pyth-sdk-cw" } # Remove path and use version only when you use this example on your own. +pyth-sdk-cw = { version = "0.2.0", path = "../../pyth-sdk-cw" } # Remove path and use version only when you use this example on your own. [dev-dependencies] cosmwasm-schema = { version = "1.0.0" } diff --git a/pyth-sdk-cw/Cargo.toml b/pyth-sdk-cw/Cargo.toml index 852341c..b87dc34 100644 --- a/pyth-sdk-cw/Cargo.toml +++ b/pyth-sdk-cw/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyth-sdk-cw" -version = "0.1.0" +version = "0.2.0" authors = ["Pyth Data Foundation"] edition = "2018" license = "Apache-2.0" @@ -14,7 +14,7 @@ readme = "README.md" cosmwasm-std = { version = "1.0.0" } cosmwasm-storage = { version = "1.0.0" } serde = { version = "1.0.136", features = ["derive"] } -pyth-sdk = { path = "../pyth-sdk", version = "0.4.1" } +pyth-sdk = { path = "../pyth-sdk", version = "0.5.0" } schemars = "0.8.1" [dev-dependencies] diff --git a/pyth-sdk-solana/Cargo.toml b/pyth-sdk-solana/Cargo.toml index 570ab93..fb5070a 100644 --- a/pyth-sdk-solana/Cargo.toml +++ b/pyth-sdk-solana/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyth-sdk-solana" -version = "0.4.2" +version = "0.5.0" authors = ["Pyth Data Foundation"] edition = "2018" license = "Apache-2.0" @@ -19,7 +19,7 @@ num-derive = "0.3" num-traits = "0.2" thiserror = "1.0" serde = { version = "1.0.136", features = ["derive"] } -pyth-sdk = { path = "../pyth-sdk", version = "0.4.0" } +pyth-sdk = { path = "../pyth-sdk", version = "0.5.0" } [dev-dependencies] solana-client = "1.8.1, < 1.11" diff --git a/pyth-sdk-solana/test-contract/Cargo.toml b/pyth-sdk-solana/test-contract/Cargo.toml index 7160a35..bc792bf 100644 --- a/pyth-sdk-solana/test-contract/Cargo.toml +++ b/pyth-sdk-solana/test-contract/Cargo.toml @@ -8,7 +8,7 @@ test-bpf = [] no-entrypoint = [] [dependencies] -pyth-sdk-solana = { path = "../", version = "0.4.0" } +pyth-sdk-solana = { path = "../", version = "0.5.0" } solana-program = "1.8.1, < 1.11" # Currently latest Solana 1.11 crate can't build bpf: https://github.com/solana-labs/solana/issues/26188 bytemuck = "1.7.2" borsh = "0.9" diff --git a/pyth-sdk/Cargo.toml b/pyth-sdk/Cargo.toml index d5ac947..27851ab 100644 --- a/pyth-sdk/Cargo.toml +++ b/pyth-sdk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyth-sdk" -version = "0.4.2" +version = "0.5.0" authors = ["Pyth Data Foundation"] edition = "2018" license = "Apache-2.0" From e418b14a24d7918e9ea33ffcce59fc5069d34561 Mon Sep 17 00:00:00 2001 From: Ali Behjati Date: Tue, 16 Aug 2022 18:36:55 +0200 Subject: [PATCH 5/5] Actually clock exists now in non-bpf --- pyth-sdk-solana/test-contract/tests/stale_price.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pyth-sdk-solana/test-contract/tests/stale_price.rs b/pyth-sdk-solana/test-contract/tests/stale_price.rs index bc24b52..6a15150 100644 --- a/pyth-sdk-solana/test-contract/tests/stale_price.rs +++ b/pyth-sdk-solana/test-contract/tests/stale_price.rs @@ -55,11 +55,7 @@ async fn test_price_stale() { price.agg.status = PriceStatus::Trading; price.agg.pub_slot = 1000 - VALID_SLOT_PERIOD - 1; - #[cfg(feature = "test-bpf")] // Only in BPF the clock check is performed let expected_status = PriceStatus::Unknown; - #[cfg(not(feature = "test-bpf"))] - let expected_status = PriceStatus::Trading; - test_instr_exec_ok(instruction::price_status_check(&price, expected_status)).await; }