pyth-crosschain/pythnet/pythnet_sdk/src/messages.rs

193 lines
6.4 KiB
Rust

#[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::<byteorder::LE, Message>(&buffer).unwrap();
assert_eq!(deserialized, msg);
}
}