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