#[cfg(feature = "quickcheck")] use quickcheck::Arbitrary; use serde::{ Deserialize, Serialize, }; /// Message format for sending data to other chains via the accumulator program /// When serialized with PythNet serialization format, each message starts with a unique /// 1-byte discriminator, followed by the serialized struct data in the definition(s) below. /// /// Messages are forward-compatible. You may add new fields to messages after all previously /// defined fields. All code for parsing messages must ignore any extraneous bytes at the end of /// the message (which could be fields that the code does not yet understand). /// /// The oracle is not using the Message enum due to the contract size limit and /// some of the methods for PriceFeedMessage and TwapMessage are not used by the oracle /// for the same reason. Rust compiler doesn't include the unused methods in the contract. /// Once we start using the unused structs and methods, the contract size will increase. #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] #[cfg_attr( feature = "strum", derive(strum::EnumDiscriminants), strum_discriminants(name(MessageType)), strum_discriminants(vis(pub)), strum_discriminants(derive( Hash, strum::EnumIter, strum::EnumString, strum::IntoStaticStr, strum::Display, Serialize, Deserialize )) )] pub enum Message { PriceFeedMessage(PriceFeedMessage), TwapMessage(TwapMessage), } impl Message { pub fn publish_time(&self) -> i64 { match self { Self::PriceFeedMessage(msg) => msg.publish_time, Self::TwapMessage(msg) => msg.publish_time, } } pub fn feed_id(&self) -> FeedId { match self { Self::PriceFeedMessage(msg) => msg.feed_id, Self::TwapMessage(msg) => msg.feed_id, } } } #[cfg(feature = "quickcheck")] impl Arbitrary for Message { fn arbitrary(g: &mut quickcheck::Gen) -> Self { match u8::arbitrary(g) % 2 { 0 => Message::PriceFeedMessage(Arbitrary::arbitrary(g)), _ => Message::TwapMessage(Arbitrary::arbitrary(g)), } } } /// 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, Serialize, Deserialize)] 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, } #[cfg(feature = "quickcheck")] impl Arbitrary for PriceFeedMessage { fn arbitrary(g: &mut quickcheck::Gen) -> Self { let mut id = [0u8; 32]; for item in &mut id { *item = u8::arbitrary(g); } let publish_time = i64::arbitrary(g); PriceFeedMessage { id, price: i64::arbitrary(g), conf: u64::arbitrary(g), exponent: i32::arbitrary(g), publish_time, prev_publish_time: publish_time.saturating_sub(i64::arbitrary(g)), ema_price: i64::arbitrary(g), ema_conf: u64::arbitrary(g), } } } /// Message format for sending Twap data via the accumulator program #[repr(C)] #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] pub struct TwapMessage { pub feed_id: FeedId, pub cumulative_price: i128, pub cumulative_conf: u128, pub num_down_slots: u64, pub exponent: i32, pub publish_time: i64, pub prev_publish_time: i64, pub publish_slot: u64, } #[cfg(feature = "quickcheck")] impl Arbitrary for TwapMessage { fn arbitrary(g: &mut quickcheck::Gen) -> Self { let mut id = [0u8; 32]; for item in &mut id { *item = u8::arbitrary(g); } let publish_time = i64::arbitrary(g); TwapMessage { id, cumulative_price: i128::arbitrary(g), cumulative_conf: u128::arbitrary(g), num_down_slots: u64::arbitrary(g), exponent: i32::arbitrary(g), publish_time, prev_publish_time: publish_time.saturating_sub(i64::arbitrary(g)), publish_slot: u64::arbitrary(g), } } } #[cfg(test)] mod tests { use crate::{ messages::{ Message, PriceFeedMessage, }, wire::Serializer, }; // Test if additional payload to the end of a message is forward compatible #[test] fn test_forward_compatibility() { use { serde::Serialize, std::iter, }; let msg = Message::PriceFeedMessage(PriceFeedMessage { feed_id: [1u8; 32], price: 1, conf: 1, exponent: 1, publish_time: 1, prev_publish_time: 1, ema_price: 1, ema_conf: 1, }); let mut buffer = Vec::new(); let mut cursor = std::io::Cursor::new(&mut buffer); let mut serializer: Serializer<_, byteorder::LE> = Serializer::new(&mut cursor); msg.serialize(&mut serializer).unwrap(); buffer.extend(iter::repeat(0).take(10)); let deserialized = crate::wire::from_slice::(&buffer).unwrap(); assert_eq!(deserialized, msg); } }