pyth_min/messages.rs
1use crate::byte_utils::{interpret_bytes_as_i32, interpret_bytes_as_i64, interpret_bytes_as_u64};
2
3/// Id of a feed producing the message. One feed produces one or more messages.
4pub type FeedId = [u8; 32];
5
6#[repr(C)]
7#[derive(Debug, Copy, Clone, PartialEq)]
8pub struct PriceFeedMessage {
9 pub feed_id: FeedId,
10 pub price: i64,
11 pub conf: u64,
12 pub exponent: i32,
13 /// The timestamp of this price update in seconds
14 pub publish_time: i64,
15 /// The timestamp of the previous price update. This field is intended to allow users to
16 /// identify the single unique price update for any moment in time:
17 /// for any time t, the unique update is the one such that prev_publish_time < t <= publish_time.
18 ///
19 /// Note that there may not be such an update while we are migrating to the new message-sending logic,
20 /// as some price updates on pythnet may not be sent to other chains (because the message-sending
21 /// logic may not have triggered). We can solve this problem by making the message-sending mandatory
22 /// (which we can do once publishers have migrated over).
23 ///
24 /// Additionally, this field may be equal to publish_time if the message is sent on a slot where
25 /// where the aggregation was unsuccesful. This problem will go away once all publishers have
26 /// migrated over to a recent version of pyth-agent.
27 pub prev_publish_time: i64,
28 pub ema_price: i64,
29 pub ema_conf: u64,
30}
31
32impl PriceFeedMessage {
33 /// Interpret a PriceFeedMessage from a byte slice (which must be exactly 84 bytes long with no
34 /// padding, but is really 88 bytes after Rust struct padding). This is useful if you want to
35 /// read price/confidence with no checks for verification or how recent the update was.
36 ///
37 /// If you have fetched a "Price Feed Account" on chain, you probably want to get the data with
38 ///
39 /// `let data = &ctx.accounts.price.try_borrow_data()?[..];`
40 ///
41 /// and you can extract this message by reading bytes 41-129. Skip the first 8 bytes (Anchor
42 /// discriminator), the authority (32 bytes), and the verification type (1-2 bytes). The end of
43 /// the message is also padding.
44 ///
45 /// `let message_bytes = &data[41..125];` or `&data[42..126];`
46 pub fn get_feed_from_bytes(v: &[u8]) -> PriceFeedMessage {
47 assert!(v.len() == 84);
48
49 let feed_id: FeedId = {
50 let mut arr = [0u8; 32];
51 arr.copy_from_slice(&v[0..32]);
52 arr
53 };
54 let price = interpret_bytes_as_i64(&v[32..40]);
55 let conf = interpret_bytes_as_u64(&v[40..48]);
56 let exponent = interpret_bytes_as_i32(&v[48..52]);
57 let publish_time = interpret_bytes_as_i64(&v[52..60]);
58 let prev_publish_time = interpret_bytes_as_i64(&v[60..68]);
59 let ema_price = interpret_bytes_as_i64(&v[68..76]);
60 let ema_conf = interpret_bytes_as_u64(&v[76..84]);
61
62 PriceFeedMessage {
63 feed_id,
64 price,
65 conf,
66 exponent,
67 publish_time,
68 prev_publish_time,
69 ema_price,
70 ema_conf,
71 }
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use crate::byte_utils::hex_to_bytes;
78
79 use super::*;
80
81 #[test]
82 fn price_feed_message_from_bytes() {
83 // From mainnet: https://solana.fm/address/7UVimffxr9ow1uXYxsr4LHAcV58mLzhmwaeKvJ1pjLiE
84 let hex_data = "22f123639d7ef4cd60314704340deddf371fd42472148f248e9d1a6d1a5eb2ac3acd8b7fd5d6b24301ef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d107fc8e30300000049a7550100000000f8ffffff314963660000000030496366000000008cc427ed030000009b14030100000000dded1e100000000000";
85 let bytes = hex_to_bytes(hex_data);
86
87 // Skip the first 8 bytes (Anchor discriminator), the authority (32 bytes), and the
88 // verification type (1 bytes). The end of the message might be padding.
89 let message_bytes = &bytes[41..125];
90 println!("{:?}", message_bytes);
91
92 let message = PriceFeedMessage::get_feed_from_bytes(message_bytes);
93 println!("{:?}", message);
94
95 // Note that Solana (and most explorers for it) use little-endian...
96
97 // 32-byte feed ID:
98 // 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30
99 // ef0d 8b6f da2c eba4 1da1 5d40 95d1 da39 2a0d 2f8e d0c6 c7bc 0f4c fac8 c280 b56d
100
101 assert_eq!(message.price, 16706469648); // 107f c8e3 0300 0000 or bytes [6, 127, 200, 227, 3, 0, 0, 0]
102 assert_eq!(message.conf, 22390601); // 49a7 5501 0000 0000 or bytes [73, 167, 85, 1, 0, 0, 0, 0]
103
104 // NOTE if you tried to interpret the byte slice from a pointer
105 // (e.g. unsafe { &*(v.as_ptr() as *const PriceFeedMessage)
106 // then this part and beyond would fail due to padding issues
107
108 assert_eq!(message.exponent, -8); // f8ff ffff or bytes [248, 255, 255, 255]
109 assert_eq!(message.publish_time, 1717782833); // 3149 6366 0000 0000 or bytes [49, 73, 99, 102, 0, 0, 0, 0]
110 assert_eq!(message.prev_publish_time, 1717782832); // 3049 6366 0000 0000 or bytes [140, 196, 39, 237, 3, 0, 0, 0]
111 assert_eq!(message.ema_price, 16863708300); // 8cc4 27ed 0300 0000 or bytes [155, 20, 3, 1, 0, 0, 0, 0]
112 assert_eq!(message.ema_conf, 16979099); // 9b14 0301 0000 0000 or bytes [221, 237, 30, 16, 0, 0, 0, 0, 0]
113
114 // dded 1e10 0000 0000 remains for the posted slot
115 }
116}