-
Notifications
You must be signed in to change notification settings - Fork 49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: state.rs: Support Pythnet flavor prices (incl. 64 pubs) #112
Open
drozdziak1
wants to merge
2
commits into
main
Choose a base branch
from
drozdziak1/64-publishers
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -283,10 +283,87 @@ pub struct Rational { | |
pub denom: i64, | ||
} | ||
|
||
/// Macro for PriceAccountSolana/PriceAccountPythnet methods, | ||
/// eliminates repetition for the shared methods. Self type name is the | ||
/// only parameter. | ||
macro_rules! price_account_impl { | ||
($name:ty) => { | ||
impl $name { | ||
pub fn get_publish_time(&self) -> UnixTimestamp { | ||
match self.agg.status { | ||
PriceStatus::Trading => self.timestamp, | ||
_ => self.prev_timestamp, | ||
} | ||
} | ||
|
||
/// Get the last valid price as long as it was updated within `slot_threshold` slots of | ||
/// the current slot. | ||
pub fn get_price_no_older_than( | ||
&self, | ||
clock: &Clock, | ||
slot_threshold: u64, | ||
) -> Option<Price> { | ||
if self.agg.status == PriceStatus::Trading | ||
&& self.agg.pub_slot >= clock.slot - slot_threshold | ||
{ | ||
return Some(Price { | ||
conf: self.agg.conf, | ||
expo: self.expo, | ||
price: self.agg.price, | ||
publish_time: self.timestamp, | ||
}); | ||
} | ||
|
||
if self.prev_slot >= clock.slot - slot_threshold { | ||
return Some(Price { | ||
conf: self.prev_conf, | ||
expo: self.expo, | ||
price: self.prev_price, | ||
publish_time: self.prev_timestamp, | ||
}); | ||
} | ||
|
||
None | ||
} | ||
|
||
pub fn to_price_feed(&self, price_key: &Pubkey) -> PriceFeed { | ||
let status = self.agg.status; | ||
|
||
let price = match status { | ||
PriceStatus::Trading => Price { | ||
conf: self.agg.conf, | ||
expo: self.expo, | ||
price: self.agg.price, | ||
publish_time: self.get_publish_time(), | ||
}, | ||
_ => Price { | ||
conf: self.prev_conf, | ||
expo: self.expo, | ||
price: self.prev_price, | ||
publish_time: self.get_publish_time(), | ||
}, | ||
}; | ||
|
||
let ema_price = Price { | ||
conf: self.ema_conf.val as u64, | ||
expo: self.expo, | ||
price: self.ema_price.val, | ||
publish_time: self.get_publish_time(), | ||
}; | ||
|
||
PriceFeed::new(PriceIdentifier::new(price_key.to_bytes()), price, ema_price) | ||
} | ||
} | ||
}; | ||
} | ||
|
||
/// Backwards-compatible typedef | ||
pub type PriceAccount = PriceAccountSolana; | ||
|
||
/// Price accounts represent a continuously-updating price feed for a product. | ||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] | ||
#[repr(C)] | ||
pub struct PriceAccount { | ||
pub struct PriceAccountSolana { | ||
/// pyth magic number | ||
pub magic: u32, | ||
/// program version | ||
|
@@ -339,77 +416,96 @@ pub struct PriceAccount { | |
pub comp: [PriceComp; 32], | ||
} | ||
|
||
price_account_impl!(PriceAccountSolana); | ||
|
||
#[cfg(target_endian = "little")] | ||
unsafe impl Zeroable for PriceAccount { | ||
unsafe impl Zeroable for PriceAccountSolana { | ||
} | ||
|
||
#[cfg(target_endian = "little")] | ||
unsafe impl Pod for PriceAccount { | ||
unsafe impl Pod for PriceAccountSolana { | ||
} | ||
|
||
impl PriceAccount { | ||
pub fn get_publish_time(&self) -> UnixTimestamp { | ||
match self.agg.status { | ||
PriceStatus::Trading => self.timestamp, | ||
_ => self.prev_timestamp, | ||
} | ||
} | ||
|
||
/// Get the last valid price as long as it was updated within `slot_threshold` slots of the | ||
/// current slot. | ||
pub fn get_price_no_older_than(&self, clock: &Clock, slot_threshold: u64) -> Option<Price> { | ||
if self.agg.status == PriceStatus::Trading | ||
&& self.agg.pub_slot >= clock.slot - slot_threshold | ||
{ | ||
return Some(Price { | ||
conf: self.agg.conf, | ||
expo: self.expo, | ||
price: self.agg.price, | ||
publish_time: self.timestamp, | ||
}); | ||
} | ||
|
||
if self.prev_slot >= clock.slot - slot_threshold { | ||
return Some(Price { | ||
conf: self.prev_conf, | ||
expo: self.expo, | ||
price: self.prev_price, | ||
publish_time: self.prev_timestamp, | ||
}); | ||
} | ||
|
||
None | ||
} | ||
|
||
pub fn to_price_feed(&self, price_key: &Pubkey) -> PriceFeed { | ||
let status = self.agg.status; | ||
|
||
let price = match status { | ||
PriceStatus::Trading => Price { | ||
conf: self.agg.conf, | ||
expo: self.expo, | ||
price: self.agg.price, | ||
publish_time: self.get_publish_time(), | ||
}, | ||
_ => Price { | ||
conf: self.prev_conf, | ||
expo: self.expo, | ||
price: self.prev_price, | ||
publish_time: self.get_publish_time(), | ||
}, | ||
}; | ||
|
||
let ema_price = Price { | ||
conf: self.ema_conf.val as u64, | ||
expo: self.expo, | ||
price: self.ema_price.val, | ||
publish_time: self.get_publish_time(), | ||
}; | ||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] | ||
#[repr(C)] | ||
pub struct PriceAccountPythnet { | ||
/// pyth magic number | ||
pub magic: u32, | ||
/// program version | ||
pub ver: u32, | ||
/// account type | ||
pub atype: u32, | ||
/// price account size | ||
pub size: u32, | ||
/// price or calculation type | ||
pub ptype: PriceType, | ||
/// price exponent | ||
pub expo: i32, | ||
/// number of component prices | ||
pub num: u32, | ||
/// number of quoters that make up aggregate | ||
pub num_qt: u32, | ||
/// slot of last valid (not unknown) aggregate price | ||
pub last_slot: u64, | ||
/// valid slot-time of agg. price | ||
pub valid_slot: u64, | ||
/// exponentially moving average price | ||
pub ema_price: Rational, | ||
/// exponentially moving average confidence interval | ||
pub ema_conf: Rational, | ||
/// unix timestamp of aggregate price | ||
pub timestamp: i64, | ||
/// min publishers for valid price | ||
pub min_pub: u8, | ||
/// space for future derived values | ||
pub drv2: u8, | ||
/// space for future derived values | ||
pub drv3: u16, | ||
/// space for future derived values | ||
pub drv4: u32, | ||
/// product account key | ||
pub prod: Pubkey, | ||
/// next Price account in linked list | ||
pub next: Pubkey, | ||
/// valid slot of previous update | ||
pub prev_slot: u64, | ||
/// aggregate price of previous update with TRADING status | ||
pub prev_price: i64, | ||
/// confidence interval of previous update with TRADING status | ||
pub prev_conf: u64, | ||
/// unix timestamp of previous aggregate with TRADING status | ||
pub prev_timestamp: i64, | ||
/// aggregate price info | ||
pub agg: PriceInfo, | ||
/// price components one per quoter. | ||
pub comp: [PriceComp; 32], | ||
/// Rationale (2023-12-12): Rust is currently unable to derive Default for [PriceComp; 64] | ||
pub comp2: [PriceComp; 32], | ||
/// Cumulative sums of aggregative price and confidence used to compute arithmetic moving | ||
/// averages | ||
pub price_cumulative: PriceCumulative, | ||
} | ||
|
||
PriceFeed::new(PriceIdentifier::new(price_key.to_bytes()), price, ema_price) | ||
} | ||
/// NOTE(2023-12-12): Copied from pyth-client | ||
#[repr(C)] | ||
#[derive(Copy, Clone, Debug, Default, Pod, Zeroable, PartialEq, Eq)] | ||
pub struct PriceCumulative { | ||
/// Cumulative sum of price * slot_gap | ||
pub price: i128, | ||
/// Cumulative sum of conf * slot_gap | ||
pub conf: u128, | ||
/// Cumulative number of slots where the price wasn't recently updated (within | ||
/// PC_MAX_SEND_LATENCY slots). This field should be used to calculate the downtime | ||
/// as a percent of slots between two times `T` and `t` as follows: | ||
/// `(T.num_down_slots - t.num_down_slots) / (T.agg_.pub_slot_ - t.agg_.pub_slot_)` | ||
pub num_down_slots: u64, | ||
/// Padding for alignment | ||
pub unused: u64, | ||
} | ||
|
||
price_account_impl!(PriceAccountPythnet); | ||
|
||
fn load<T: Pod>(data: &[u8]) -> Result<&T, PodCastError> { | ||
let size = size_of::<T>(); | ||
if data.len() >= size { | ||
|
@@ -456,8 +552,8 @@ pub fn load_product_account(data: &[u8]) -> Result<&ProductAccount, PythError> { | |
} | ||
|
||
/// Get a `Price` account from the raw byte value of a Solana account. | ||
pub fn load_price_account(data: &[u8]) -> Result<&PriceAccount, PythError> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it ok that this functions loads the account is PriceAccountSolana ? I think it's used in pyth-agent |
||
let pyth_price = load::<PriceAccount>(data).map_err(|_| PythError::InvalidAccountData)?; | ||
pub fn load_price_account(data: &[u8]) -> Result<&PriceAccountSolana, PythError> { | ||
let pyth_price = load::<PriceAccountSolana>(data).map_err(|_| PythError::InvalidAccountData)?; | ||
|
||
if pyth_price.magic != MAGIC { | ||
return Err(PythError::InvalidAccountData); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is wrong, there is room for 128 publishers afaik