pyth_lazer_protocol/
payload.rs

1//! Types representing binary encoding of signable payloads and signature envelopes.
2
3use {
4    super::router::{PriceFeedId, PriceFeedProperty, TimestampUs},
5    crate::router::{ChannelId, Price},
6    anyhow::bail,
7    byteorder::{ByteOrder, ReadBytesExt, WriteBytesExt, BE, LE},
8    serde::{Deserialize, Serialize},
9    std::{
10        io::{Cursor, Read, Write},
11        num::NonZeroI64,
12    },
13};
14
15/// Data contained within a signable payload.
16#[derive(Debug, Clone, PartialEq, Eq, Hash)]
17pub struct PayloadData {
18    pub timestamp_us: TimestampUs,
19    pub channel_id: ChannelId,
20    // TODO: smallvec?
21    pub feeds: Vec<PayloadFeedData>,
22}
23
24#[derive(Debug, Clone, PartialEq, Eq, Hash)]
25pub struct PayloadFeedData {
26    pub feed_id: PriceFeedId,
27    // TODO: smallvec?
28    pub properties: Vec<PayloadPropertyValue>,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq, Hash)]
32pub enum PayloadPropertyValue {
33    Price(Option<Price>),
34    BestBidPrice(Option<Price>),
35    BestAskPrice(Option<Price>),
36    PublisherCount(Option<u16>),
37    Exponent(i16),
38    Confidence(Option<Price>),
39}
40
41#[derive(Debug, Clone, Default, Serialize, Deserialize)]
42pub struct AggregatedPriceFeedData {
43    pub price: Option<Price>,
44    pub best_bid_price: Option<Price>,
45    pub best_ask_price: Option<Price>,
46    pub publisher_count: Option<u16>,
47    pub confidence: Option<Price>,
48}
49
50pub const PAYLOAD_FORMAT_MAGIC: u32 = 2479346549;
51
52impl PayloadData {
53    pub fn new(
54        timestamp_us: TimestampUs,
55        channel_id: ChannelId,
56        feeds: &[(PriceFeedId, i16, AggregatedPriceFeedData)],
57        requested_properties: &[PriceFeedProperty],
58    ) -> Self {
59        Self {
60            timestamp_us,
61            channel_id,
62            feeds: feeds
63                .iter()
64                .map(|(feed_id, exponent, feed)| PayloadFeedData {
65                    feed_id: *feed_id,
66                    properties: requested_properties
67                        .iter()
68                        .map(|property| match property {
69                            PriceFeedProperty::Price => PayloadPropertyValue::Price(feed.price),
70                            PriceFeedProperty::BestBidPrice => {
71                                PayloadPropertyValue::BestBidPrice(feed.best_bid_price)
72                            }
73                            PriceFeedProperty::BestAskPrice => {
74                                PayloadPropertyValue::BestAskPrice(feed.best_ask_price)
75                            }
76                            PriceFeedProperty::PublisherCount => {
77                                PayloadPropertyValue::PublisherCount(feed.publisher_count)
78                            }
79                            PriceFeedProperty::Exponent => {
80                                PayloadPropertyValue::Exponent(*exponent)
81                            }
82                            PriceFeedProperty::Confidence => {
83                                PayloadPropertyValue::Confidence(feed.confidence)
84                            }
85                        })
86                        .collect(),
87                })
88                .collect(),
89        }
90    }
91
92    pub fn serialize<BO: ByteOrder>(&self, mut writer: impl Write) -> anyhow::Result<()> {
93        writer.write_u32::<BO>(PAYLOAD_FORMAT_MAGIC)?;
94        writer.write_u64::<BO>(self.timestamp_us.0)?;
95        writer.write_u8(self.channel_id.0)?;
96        writer.write_u8(self.feeds.len().try_into()?)?;
97        for feed in &self.feeds {
98            writer.write_u32::<BO>(feed.feed_id.0)?;
99            writer.write_u8(feed.properties.len().try_into()?)?;
100            for property in &feed.properties {
101                match property {
102                    PayloadPropertyValue::Price(price) => {
103                        writer.write_u8(PriceFeedProperty::Price as u8)?;
104                        write_option_price::<BO>(&mut writer, *price)?;
105                    }
106                    PayloadPropertyValue::BestBidPrice(price) => {
107                        writer.write_u8(PriceFeedProperty::BestBidPrice as u8)?;
108                        write_option_price::<BO>(&mut writer, *price)?;
109                    }
110                    PayloadPropertyValue::BestAskPrice(price) => {
111                        writer.write_u8(PriceFeedProperty::BestAskPrice as u8)?;
112                        write_option_price::<BO>(&mut writer, *price)?;
113                    }
114                    PayloadPropertyValue::PublisherCount(count) => {
115                        writer.write_u8(PriceFeedProperty::PublisherCount as u8)?;
116                        write_option_u16::<BO>(&mut writer, *count)?;
117                    }
118                    PayloadPropertyValue::Exponent(exponent) => {
119                        writer.write_u8(PriceFeedProperty::Exponent as u8)?;
120                        writer.write_i16::<BO>(*exponent)?;
121                    }
122                    PayloadPropertyValue::Confidence(confidence) => {
123                        writer.write_u8(PriceFeedProperty::Confidence as u8)?;
124                        write_option_price::<BO>(&mut writer, *confidence)?;
125                    }
126                }
127            }
128        }
129        Ok(())
130    }
131
132    pub fn deserialize_slice_le(data: &[u8]) -> anyhow::Result<Self> {
133        Self::deserialize::<LE>(Cursor::new(data))
134    }
135
136    pub fn deserialize_slice_be(data: &[u8]) -> anyhow::Result<Self> {
137        Self::deserialize::<BE>(Cursor::new(data))
138    }
139
140    pub fn deserialize<BO: ByteOrder>(mut reader: impl Read) -> anyhow::Result<Self> {
141        let magic = reader.read_u32::<BO>()?;
142        if magic != PAYLOAD_FORMAT_MAGIC {
143            bail!("magic mismatch");
144        }
145        let timestamp_us = TimestampUs(reader.read_u64::<BO>()?);
146        let channel_id = ChannelId(reader.read_u8()?);
147        let num_feeds = reader.read_u8()?;
148        let mut feeds = Vec::with_capacity(num_feeds.into());
149        for _ in 0..num_feeds {
150            let feed_id = PriceFeedId(reader.read_u32::<BO>()?);
151            let num_properties = reader.read_u8()?;
152            let mut feed = PayloadFeedData {
153                feed_id,
154                properties: Vec::with_capacity(num_properties.into()),
155            };
156            for _ in 0..num_properties {
157                let property = reader.read_u8()?;
158                let value = if property == PriceFeedProperty::Price as u8 {
159                    PayloadPropertyValue::Price(read_option_price::<BO>(&mut reader)?)
160                } else if property == PriceFeedProperty::BestBidPrice as u8 {
161                    PayloadPropertyValue::BestBidPrice(read_option_price::<BO>(&mut reader)?)
162                } else if property == PriceFeedProperty::BestAskPrice as u8 {
163                    PayloadPropertyValue::BestAskPrice(read_option_price::<BO>(&mut reader)?)
164                } else if property == PriceFeedProperty::PublisherCount as u8 {
165                    PayloadPropertyValue::PublisherCount(read_option_u16::<BO>(&mut reader)?)
166                } else if property == PriceFeedProperty::Exponent as u8 {
167                    PayloadPropertyValue::Exponent(reader.read_i16::<BO>()?)
168                } else if property == PriceFeedProperty::Confidence as u8 {
169                    PayloadPropertyValue::Confidence(read_option_price::<BO>(&mut reader)?)
170                } else {
171                    bail!("unknown property");
172                };
173                feed.properties.push(value);
174            }
175            feeds.push(feed);
176        }
177        Ok(Self {
178            timestamp_us,
179            channel_id,
180            feeds,
181        })
182    }
183}
184
185fn write_option_price<BO: ByteOrder>(
186    mut writer: impl Write,
187    value: Option<Price>,
188) -> std::io::Result<()> {
189    writer.write_i64::<BO>(value.map_or(0, |v| v.0.get()))
190}
191
192fn read_option_price<BO: ByteOrder>(mut reader: impl Read) -> std::io::Result<Option<Price>> {
193    let value = NonZeroI64::new(reader.read_i64::<BO>()?);
194    Ok(value.map(Price))
195}
196
197fn write_option_u16<BO: ByteOrder>(
198    mut writer: impl Write,
199    value: Option<u16>,
200) -> std::io::Result<()> {
201    writer.write_u16::<BO>(value.unwrap_or(0))
202}
203
204fn read_option_u16<BO: ByteOrder>(mut reader: impl Read) -> std::io::Result<Option<u16>> {
205    let value = reader.read_u16::<BO>()?;
206    Ok(Some(value))
207}
208
209pub const BINARY_UPDATE_FORMAT_MAGIC: u32 = 1937213467;
210
211pub const PARSED_FORMAT_MAGIC: u32 = 2584795844;
212pub const EVM_FORMAT_MAGIC: u32 = 706910618;
213pub const SOLANA_FORMAT_MAGIC_BE: u32 = 3103857282;
214pub const SOLANA_FORMAT_MAGIC_LE: u32 = u32::swap_bytes(SOLANA_FORMAT_MAGIC_BE);