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}