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);
    }
}