Skip to content

Commit 404cf9e

Browse files
committed
Update price feed structure + cascade changes
1 parent 37604d2 commit 404cf9e

File tree

12 files changed

+356
-377
lines changed

12 files changed

+356
-377
lines changed

examples/cw-contract/src/contract.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,13 @@ pub fn execute(
6666

6767
/// Query the Pyth contract the current price of the configured price feed.
6868
#[cfg_attr(not(feature = "library"), entry_point)]
69-
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
69+
pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
7070
match msg {
71-
QueryMsg::FetchPrice {} => to_binary(&query_fetch_price(deps)?),
71+
QueryMsg::FetchPrice {} => to_binary(&query_fetch_price(deps, env)?),
7272
}
7373
}
7474

75-
fn query_fetch_price(deps: Deps) -> StdResult<FetchPriceResponse> {
75+
fn query_fetch_price(deps: Deps, env: Env) -> StdResult<FetchPriceResponse> {
7676
let state = STATE.load(deps.storage)?;
7777

7878
// query_price_feed is the standard way to read the current price from a Pyth price feed.
@@ -92,13 +92,13 @@ fn query_fetch_price(deps: Deps) -> StdResult<FetchPriceResponse> {
9292
// you handle this scenario more carefully. Consult the [consumer best practices](https://docs.pyth.network/consumers/best-practices)
9393
// for recommendations.
9494
let current_price = price_feed
95-
.get_current_price()
95+
.get_price_no_older_than(env.block.time.seconds() as i64, 60)
9696
.ok_or_else(|| StdError::not_found("Current price is not available"))?;
9797

9898
// Get an exponentially-weighted moving average price and confidence interval.
9999
// The same notes about availability apply to this price.
100100
let ema_price = price_feed
101-
.get_ema_price()
101+
.get_ema_price_no_older_than(env.block.time.seconds() as i64, 60)
102102
.ok_or_else(|| StdError::not_found("EMA price is not available"))?;
103103

104104
Ok(FetchPriceResponse {

pyth-sdk-cw/src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ pub use pyth_sdk::{
1616
Price,
1717
PriceFeed,
1818
PriceIdentifier,
19-
PriceStatus,
2019
ProductIdentifier,
2120
};
2221

pyth-sdk-solana/examples/eth_price.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ use pyth_sdk_solana::load_price_feed_from_account;
44
use solana_client::rpc_client::RpcClient;
55
use solana_program::pubkey::Pubkey;
66
use std::str::FromStr;
7+
use std::time::{
8+
SystemTime,
9+
UNIX_EPOCH,
10+
};
711
use std::{
812
thread,
913
time,
@@ -24,10 +28,13 @@ fn main() {
2428
load_price_feed_from_account(&eth_price_key, &mut eth_price_account).unwrap();
2529

2630
println!(".....ETH/USD.....");
27-
println!("status .......... {:?}", eth_price_feed.status);
28-
println!("num_publishers .. {}", eth_price_feed.num_publishers);
2931

30-
let maybe_price = eth_price_feed.get_current_price();
32+
let current_time = SystemTime::now()
33+
.duration_since(UNIX_EPOCH)
34+
.unwrap()
35+
.as_secs() as i64;
36+
37+
let maybe_price = eth_price_feed.get_price_no_older_than(current_time, 60);
3138
match maybe_price {
3239
Some(p) => {
3340
println!("price ........... {} x 10^{}", p.price, p.expo);
@@ -40,7 +47,7 @@ fn main() {
4047
}
4148

4249

43-
let maybe_ema_price = eth_price_feed.get_ema_price();
50+
let maybe_ema_price = eth_price_feed.get_ema_price_no_older_than(current_time, 60);
4451
match maybe_ema_price {
4552
Some(ema_price) => {
4653
println!(

pyth-sdk-solana/examples/get_accounts.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ use pyth_sdk_solana::state::{
1414
use solana_client::rpc_client::RpcClient;
1515
use solana_program::pubkey::Pubkey;
1616
use std::str::FromStr;
17+
use std::time::{
18+
SystemTime,
19+
UNIX_EPOCH,
20+
};
1721

1822
fn get_price_type(ptype: &PriceType) -> &'static str {
1923
match ptype {
@@ -73,7 +77,12 @@ fn main() {
7377

7478
println!(" price_account .. {:?}", px_pkey);
7579

76-
let maybe_price = price_feed.get_current_price();
80+
let current_time = SystemTime::now()
81+
.duration_since(UNIX_EPOCH)
82+
.unwrap()
83+
.as_secs() as i64;
84+
85+
let maybe_price = price_feed.get_price_no_older_than(current_time, 60);
7786
match maybe_price {
7887
Some(p) => {
7988
println!(" price ........ {} x 10^{}", p.price, p.expo);
@@ -89,8 +98,6 @@ fn main() {
8998
" price_type ... {}",
9099
get_price_type(&price_account.ptype)
91100
);
92-
println!(" exponent ..... {}", price_feed.expo);
93-
println!(" status ....... {}", get_status(&price_feed.status));
94101
println!(
95102
" corp_act ..... {}",
96103
get_corp_act(&price_account.agg.corp_act)
@@ -100,7 +107,7 @@ fn main() {
100107
println!(" valid_slot ... {}", price_account.valid_slot);
101108
println!(" publish_slot . {}", price_account.agg.pub_slot);
102109

103-
let maybe_ema_price = price_feed.get_ema_price();
110+
let maybe_ema_price = price_feed.get_ema_price_no_older_than(current_time, 60);
104111
match maybe_ema_price {
105112
Some(ema_price) => {
106113
println!(

pyth-sdk-solana/src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ pub use pyth_sdk::{
2020
Price,
2121
PriceFeed,
2222
PriceIdentifier,
23-
PriceStatus,
2423
ProductIdentifier,
2524
};
2625

pyth-sdk-solana/src/state.rs

Lines changed: 175 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,15 @@ use bytemuck::{
1212
PodCastError,
1313
Zeroable,
1414
};
15-
use pyth_sdk::{
16-
PriceIdentifier,
17-
ProductIdentifier,
18-
};
15+
use pyth_sdk::PriceIdentifier;
1916
use solana_program::pubkey::Pubkey;
2017
use std::mem::size_of;
2118

2219
pub use pyth_sdk::{
2320
Price,
2421
PriceFeed,
25-
PriceStatus,
2622
};
2723

28-
use solana_program::clock::Clock;
29-
use solana_program::sysvar::Sysvar;
30-
3124
use crate::VALID_SLOT_PERIOD;
3225

3326
use crate::PythError;
@@ -115,6 +108,37 @@ impl Default for PriceType {
115108
}
116109
}
117110

111+
112+
/// Represents availability status of a price feed.
113+
#[derive(
114+
Copy,
115+
Clone,
116+
Debug,
117+
PartialEq,
118+
Eq,
119+
BorshSerialize,
120+
BorshDeserialize,
121+
serde::Serialize,
122+
serde::Deserialize,
123+
)]
124+
#[repr(C)]
125+
pub enum PriceStatus {
126+
/// The price feed is not currently updating for an unknown reason.
127+
Unknown,
128+
/// The price feed is updating as expected.
129+
Trading,
130+
/// The price feed is not currently updating because trading in the product has been halted.
131+
Halted,
132+
/// The price feed is not currently updating because an auction is setting the price.
133+
Auction,
134+
}
135+
136+
impl Default for PriceStatus {
137+
fn default() -> Self {
138+
PriceStatus::Unknown
139+
}
140+
}
141+
118142
/// Mapping accounts form a linked-list containing the listing of all products on Pyth.
119143
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
120144
#[repr(C)]
@@ -323,38 +347,38 @@ unsafe impl Pod for PriceAccount {
323347

324348
impl PriceAccount {
325349
pub fn to_price_feed(&self, price_key: &Pubkey) -> PriceFeed {
326-
let mut status = self.agg.status;
327-
let mut prev_price = self.prev_price;
328-
let mut prev_conf = self.prev_conf;
329-
let mut prev_publish_time = self.prev_timestamp;
330-
331-
if let Ok(clock) = Clock::get() {
332-
if matches!(status, PriceStatus::Trading)
333-
&& clock.slot.saturating_sub(self.agg.pub_slot) > VALID_SLOT_PERIOD
334-
{
335-
status = PriceStatus::Unknown;
336-
prev_price = self.agg.price;
337-
prev_conf = self.agg.conf;
338-
prev_publish_time = self.timestamp;
350+
let status = self.agg.status;
351+
352+
let price = if matches!(status, PriceStatus::Trading) {
353+
Price {
354+
conf: self.agg.conf,
355+
expo: self.expo,
356+
price: self.agg.price,
357+
publish_time: self.timestamp,
358+
}
359+
} else {
360+
Price {
361+
conf: self.prev_conf,
362+
expo: self.expo,
363+
price: self.prev_price,
364+
publish_time: self.prev_timestamp,
339365
}
366+
};
367+
368+
let mut ema_price = Price {
369+
conf: self.ema_conf.val as u64,
370+
expo: self.expo,
371+
price: self.ema_price.val,
372+
publish_time: self.timestamp,
373+
};
374+
375+
// Ema price only gets updated if status is Trading. So it's update timestamp will be
376+
// prev_timestamp when the status is not Trading.
377+
if !matches!(status, PriceStatus::Trading) {
378+
ema_price.publish_time = self.prev_timestamp;
340379
}
341380

342-
PriceFeed::new(
343-
PriceIdentifier::new(price_key.to_bytes()),
344-
status,
345-
self.timestamp,
346-
self.expo,
347-
self.num,
348-
self.num_qt,
349-
ProductIdentifier::new(self.prod.to_bytes()),
350-
self.agg.price,
351-
self.agg.conf,
352-
self.ema_price.val,
353-
self.ema_conf.val as u64,
354-
prev_price,
355-
prev_conf,
356-
prev_publish_time,
357-
)
381+
PriceFeed::new(PriceIdentifier::new(price_key.to_bytes()), price, ema_price)
358382
}
359383
}
360384

@@ -447,3 +471,117 @@ fn get_attr_str(buf: &[u8]) -> (&str, &[u8]) {
447471
let remaining_buf = &buf[len + 1..];
448472
(str, remaining_buf)
449473
}
474+
475+
#[cfg(test)]
476+
mod test {
477+
use pyth_sdk::{
478+
Identifier,
479+
Price,
480+
PriceFeed,
481+
};
482+
use solana_program::pubkey::Pubkey;
483+
484+
use super::{
485+
PriceAccount,
486+
PriceInfo,
487+
PriceStatus,
488+
Rational,
489+
};
490+
491+
492+
#[test]
493+
fn test_trading_price_to_price_feed() {
494+
let price_account = PriceAccount {
495+
expo: 5,
496+
agg: PriceInfo {
497+
price: 10,
498+
conf: 20,
499+
status: PriceStatus::Trading,
500+
..Default::default()
501+
},
502+
timestamp: 200,
503+
prev_timestamp: 100,
504+
ema_price: Rational {
505+
val: 40,
506+
..Default::default()
507+
},
508+
ema_conf: Rational {
509+
val: 50,
510+
..Default::default()
511+
},
512+
prev_price: 60,
513+
prev_conf: 70,
514+
..Default::default()
515+
};
516+
517+
let pubkey = Pubkey::new_from_array([3; 32]);
518+
let price_feed = price_account.to_price_feed(&pubkey);
519+
520+
assert_eq!(
521+
price_feed,
522+
PriceFeed::new(
523+
Identifier::new(pubkey.to_bytes()),
524+
Price {
525+
conf: 20,
526+
price: 10,
527+
expo: 5,
528+
publish_time: 200,
529+
},
530+
Price {
531+
conf: 50,
532+
price: 40,
533+
expo: 5,
534+
publish_time: 200,
535+
}
536+
)
537+
);
538+
}
539+
540+
#[test]
541+
fn test_non_trading_price_to_price_feed() {
542+
let price_account = PriceAccount {
543+
expo: 5,
544+
agg: PriceInfo {
545+
price: 10,
546+
conf: 20,
547+
status: PriceStatus::Unknown,
548+
..Default::default()
549+
},
550+
timestamp: 200,
551+
prev_timestamp: 100,
552+
ema_price: Rational {
553+
val: 40,
554+
..Default::default()
555+
},
556+
ema_conf: Rational {
557+
val: 50,
558+
..Default::default()
559+
},
560+
prev_price: 60,
561+
prev_conf: 70,
562+
..Default::default()
563+
};
564+
565+
let pubkey = Pubkey::new_from_array([3; 32]);
566+
let price_feed = price_account.to_price_feed(&pubkey);
567+
568+
assert_eq!(
569+
price_feed,
570+
PriceFeed::new(
571+
Identifier::new(pubkey.to_bytes()),
572+
Price {
573+
conf: 70,
574+
price: 60,
575+
expo: 5,
576+
publish_time: 100,
577+
},
578+
Price {
579+
conf: 50,
580+
price: 40,
581+
expo: 5,
582+
publish_time: 100,
583+
}
584+
)
585+
);
586+
}
587+
}

0 commit comments

Comments
 (0)