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 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
#[cfg(feature = "quickcheck")]
use quickcheck::Arbitrary;
use {
borsh::{
BorshDeserialize,
BorshSchema,
BorshSerialize,
},
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,
BorshDeserialize,
BorshSerialize,
BorshSchema,
)]
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);
}
}