Skip to content

Commit 248b2f7

Browse files
authored
Replace get_prev_price with better methods (#68)
* Replace get_prev_price with better methods * Fix handling stale price with new methods * Expose price identifier * Bump version * Actually clock exists now in non-bpf
1 parent fb9312e commit 248b2f7

File tree

9 files changed

+88
-43
lines changed

9 files changed

+88
-43
lines changed

examples/cw-contract/Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "example-cw-contract"
33
version = "0.1.0"
4-
authors = ["Ali Behjati <[email protected]>"]
4+
authors = ["Pyth Data Foundation"]
55
edition = "2018"
66

77
exclude = [
@@ -34,7 +34,7 @@ cosmwasm-storage = { version = "1.0.0" }
3434
cw-storage-plus = "0.13.4"
3535
schemars = "0.8"
3636
serde = { version = "1.0", default-features = false, features = ["derive"] }
37-
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.
37+
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.
3838

3939
[dev-dependencies]
4040
cosmwasm-schema = { version = "1.0.0" }

pyth-sdk-cw/Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "pyth-sdk-cw"
3-
version = "0.1.0"
3+
version = "0.2.0"
44
authors = ["Pyth Data Foundation"]
55
edition = "2018"
66
license = "Apache-2.0"
@@ -14,7 +14,7 @@ readme = "README.md"
1414
cosmwasm-std = { version = "1.0.0" }
1515
cosmwasm-storage = { version = "1.0.0" }
1616
serde = { version = "1.0.136", features = ["derive"] }
17-
pyth-sdk = { path = "../pyth-sdk", version = "0.4.1" }
17+
pyth-sdk = { path = "../pyth-sdk", version = "0.5.0" }
1818
schemars = "0.8.1"
1919

2020
[dev-dependencies]

pyth-sdk-solana/Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "pyth-sdk-solana"
3-
version = "0.4.2"
3+
version = "0.5.0"
44
authors = ["Pyth Data Foundation"]
55
edition = "2018"
66
license = "Apache-2.0"
@@ -19,7 +19,7 @@ num-derive = "0.3"
1919
num-traits = "0.2"
2020
thiserror = "1.0"
2121
serde = { version = "1.0.136", features = ["derive"] }
22-
pyth-sdk = { path = "../pyth-sdk", version = "0.4.0" }
22+
pyth-sdk = { path = "../pyth-sdk", version = "0.5.0" }
2323

2424
[dev-dependencies]
2525
solana-client = "1.8.1, < 1.11"

pyth-sdk-solana/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use state::load_price_account;
1919
pub use pyth_sdk::{
2020
Price,
2121
PriceFeed,
22+
PriceIdentifier,
2223
PriceStatus,
2324
ProductIdentifier,
2425
};

pyth-sdk-solana/src/state.rs

+18-16
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,9 @@ pub use pyth_sdk::{
2525
PriceStatus,
2626
};
2727

28-
#[cfg(target_arch = "bpf")]
29-
use solana_program::{
30-
clock::Clock,
31-
sysvar::Sysvar,
32-
};
28+
use solana_program::clock::Clock;
29+
use solana_program::sysvar::Sysvar;
3330

34-
#[cfg(target_arch = "bpf")]
3531
use crate::VALID_SLOT_PERIOD;
3632

3733
use crate::PythError;
@@ -348,14 +344,20 @@ unsafe impl Pod for PriceAccount {
348344

349345
impl PriceAccount {
350346
pub fn to_price_feed(&self, price_key: &Pubkey) -> PriceFeed {
351-
#[allow(unused_mut)]
352347
let mut status = self.agg.status;
353-
354-
#[cfg(target_arch = "bpf")]
355-
if matches!(status, PriceStatus::Trading)
356-
&& Clock::get().unwrap().slot.saturating_sub(self.agg.pub_slot) > VALID_SLOT_PERIOD
357-
{
358-
status = PriceStatus::Unknown;
348+
let mut prev_price = self.prev_price;
349+
let mut prev_conf = self.prev_conf;
350+
let mut prev_publish_time = self.prev_timestamp;
351+
352+
if let Ok(clock) = Clock::get() {
353+
if matches!(status, PriceStatus::Trading)
354+
&& clock.slot.saturating_sub(self.agg.pub_slot) > VALID_SLOT_PERIOD
355+
{
356+
status = PriceStatus::Unknown;
357+
prev_price = self.agg.price;
358+
prev_conf = self.agg.conf;
359+
prev_publish_time = self.timestamp;
360+
}
359361
}
360362

361363
PriceFeed::new(
@@ -370,9 +372,9 @@ impl PriceAccount {
370372
self.agg.conf,
371373
self.ema_price.val,
372374
self.ema_conf.val as u64,
373-
self.prev_price,
374-
self.prev_conf,
375-
self.prev_timestamp,
375+
prev_price,
376+
prev_conf,
377+
prev_publish_time,
376378
)
377379
}
378380
}

pyth-sdk-solana/test-contract/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ test-bpf = []
88
no-entrypoint = []
99

1010
[dependencies]
11-
pyth-sdk-solana = { path = "../", version = "0.4.0" }
11+
pyth-sdk-solana = { path = "../", version = "0.5.0" }
1212
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
1313
bytemuck = "1.7.2"
1414
borsh = "0.9"

pyth-sdk-solana/test-contract/tests/stale_price.rs

-4
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,7 @@ async fn test_price_stale() {
5555
price.agg.status = PriceStatus::Trading;
5656
price.agg.pub_slot = 1000 - VALID_SLOT_PERIOD - 1;
5757

58-
#[cfg(feature = "test-bpf")] // Only in BPF the clock check is performed
5958
let expected_status = PriceStatus::Unknown;
6059

61-
#[cfg(not(feature = "test-bpf"))]
62-
let expected_status = PriceStatus::Trading;
63-
6460
test_instr_exec_ok(instruction::price_status_check(&price, expected_status)).await;
6561
}

pyth-sdk/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "pyth-sdk"
3-
version = "0.4.2"
3+
version = "0.5.0"
44
authors = ["Pyth Data Foundation"]
55
edition = "2018"
66
license = "Apache-2.0"

pyth-sdk/src/lib.rs

+61-15
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ pub type ProductIdentifier = Identifier;
8484
/// Jan 1970). It is a signed integer because it's the standard in Unix systems and allows easier
8585
/// time difference.
8686
pub type UnixTimestamp = i64;
87+
pub type DurationInSeconds = u64;
8788

8889
/// Represents availability status of a price feed.
8990
#[derive(
@@ -277,22 +278,67 @@ impl PriceFeed {
277278
}
278279
}
279280

280-
/// Get the "unchecked" previous price with Trading status,
281-
/// along with the timestamp at which it was generated.
281+
/// Get the latest available price, along with the timestamp when it was generated.
282282
///
283-
/// WARNING:
284-
/// We make no guarantees about the unchecked price and confidence returned by
285-
/// this function: it could differ significantly from the current price.
286-
/// We strongly encourage you to use `get_current_price` instead.
287-
pub fn get_prev_price_unchecked(&self) -> (Price, UnixTimestamp) {
288-
(
289-
Price {
290-
price: self.prev_price,
291-
conf: self.prev_conf,
292-
expo: self.expo,
293-
},
294-
self.prev_publish_time,
295-
)
283+
/// This function returns the same price as `get_current_price` in the case where a price was
284+
/// available at the time this `PriceFeed` was published (`publish_time`). However, if a
285+
/// price was not available at that time, this function returns the price from the latest
286+
/// time at which the price was available. The returned price can be from arbitrarily far in
287+
/// the past; this function makes no guarantees that the returned price is recent or useful
288+
/// for any particular application.
289+
///
290+
/// Users of this function should check the returned timestamp to ensure that the returned price
291+
/// is sufficiently recent for their application. If you are considering using this
292+
/// function, it may be safer / easier to use either `get_current_price` or
293+
/// `get_latest_available_price_within_duration`.
294+
///
295+
/// Returns a struct containing the latest available price, confidence interval, and the
296+
/// exponent for both numbers along with the timestamp when that price was generated.
297+
pub fn get_latest_available_price_unchecked(&self) -> (Price, UnixTimestamp) {
298+
match self.status {
299+
PriceStatus::Trading => (
300+
Price {
301+
price: self.price,
302+
conf: self.conf,
303+
expo: self.expo,
304+
},
305+
self.publish_time,
306+
),
307+
_ => (
308+
Price {
309+
price: self.prev_price,
310+
conf: self.prev_conf,
311+
expo: self.expo,
312+
},
313+
self.prev_publish_time,
314+
),
315+
}
316+
}
317+
318+
/// Get the latest price as long as it was updated within `duration` seconds of the
319+
/// `current_time`.
320+
///
321+
/// This function is a sanity-checked version of `get_latest_available_price_unchecked` which is
322+
/// useful in applications that require a sufficiently-recent price. Returns `None` if the
323+
/// price wasn't updated sufficiently recently.
324+
///
325+
/// Returns a struct containing the latest available price, confidence interval and the exponent
326+
/// for both numbers, or `None` if no price update occurred within `duration` seconds of the
327+
/// `current_time`.
328+
pub fn get_latest_available_price_within_duration(
329+
&self,
330+
current_time: UnixTimestamp,
331+
duration: DurationInSeconds,
332+
) -> Option<Price> {
333+
let (price, publish_time) = self.get_latest_available_price_unchecked();
334+
335+
let time_diff_abs = (publish_time - current_time).abs() as u64;
336+
337+
if time_diff_abs > duration {
338+
return None;
339+
}
340+
341+
Some(price)
296342
}
297343
}
298344
#[cfg(test)]

0 commit comments

Comments
 (0)