pyth_lazer_protocol/
publisher.rs

1use {
2    crate::{price::Price, rate::Rate, time::TimestampUs, PriceFeedId},
3    derive_more::derive::From,
4    serde::{Deserialize, Serialize},
5};
6
7/// Represents a binary (bincode-serialized) stream update sent
8/// from the publisher to the router.
9#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
10#[serde(rename_all = "camelCase")]
11pub struct PriceFeedDataV2 {
12    pub price_feed_id: PriceFeedId,
13    /// Timestamp of the last update provided by the source of the prices
14    /// (like an exchange). If unavailable, this value is set to `publisher_timestamp_us`.
15    pub source_timestamp_us: TimestampUs,
16    /// Timestamp of the last update provided by the publisher.
17    pub publisher_timestamp_us: TimestampUs,
18    /// Last known value of the best executable price of this price feed.
19    /// `None` if no value is currently available.
20    pub price: Option<Price>,
21    /// Last known value of the best bid price of this price feed.
22    /// `None` if no value is currently available.
23    pub best_bid_price: Option<Price>,
24    /// Last known value of the best ask price of this price feed.
25    /// `None` if no value is currently available.
26    pub best_ask_price: Option<Price>,
27    /// Last known value of the funding rate of this feed.
28    /// `None` if no value is currently available.
29    pub funding_rate: Option<Rate>,
30}
31
32/// Old Represents a binary (bincode-serialized) stream update sent
33/// from the publisher to the router.
34/// Superseded by `PriceFeedData`.
35#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
36#[serde(rename_all = "camelCase")]
37pub struct PriceFeedDataV1 {
38    pub price_feed_id: PriceFeedId,
39    /// Timestamp of the last update provided by the source of the prices
40    /// (like an exchange). If unavailable, this value is set to `publisher_timestamp_us`.
41    pub source_timestamp_us: TimestampUs,
42    /// Timestamp of the last update provided by the publisher.
43    pub publisher_timestamp_us: TimestampUs,
44    /// Last known value of the best executable price of this price feed.
45    /// `None` if no value is currently available.
46    #[serde(with = "crate::serde_price_as_i64")]
47    pub price: Option<Price>,
48    /// Last known value of the best bid price of this price feed.
49    /// `None` if no value is currently available.
50    #[serde(with = "crate::serde_price_as_i64")]
51    pub best_bid_price: Option<Price>,
52    /// Last known value of the best ask price of this price feed.
53    /// `None` if no value is currently available.
54    #[serde(with = "crate::serde_price_as_i64")]
55    pub best_ask_price: Option<Price>,
56}
57
58impl From<PriceFeedDataV1> for PriceFeedDataV2 {
59    fn from(v0: PriceFeedDataV1) -> Self {
60        Self {
61            price_feed_id: v0.price_feed_id,
62            source_timestamp_us: v0.source_timestamp_us,
63            publisher_timestamp_us: v0.publisher_timestamp_us,
64            price: v0.price,
65            best_bid_price: v0.best_bid_price,
66            best_ask_price: v0.best_ask_price,
67            funding_rate: None,
68        }
69    }
70}
71
72/// A response sent from the server to the publisher client.
73/// Currently only serde errors are reported back to the client.
74#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, From)]
75#[serde(tag = "type")]
76#[serde(rename_all = "camelCase")]
77pub enum ServerResponse {
78    UpdateDeserializationError(UpdateDeserializationErrorResponse),
79}
80/// Sent to the publisher if the binary data could not be parsed
81#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
82#[serde(rename_all = "camelCase")]
83pub struct UpdateDeserializationErrorResponse {
84    pub error: String,
85}
86
87#[test]
88fn price_feed_data_v1_serde() {
89    let data = [
90        1, 0, 0, 0, // price_feed_id
91        2, 0, 0, 0, 0, 0, 0, 0, // source_timestamp_us
92        3, 0, 0, 0, 0, 0, 0, 0, // publisher_timestamp_us
93        4, 0, 0, 0, 0, 0, 0, 0, // price
94        5, 0, 0, 0, 0, 0, 0, 0, // best_bid_price
95        6, 2, 0, 0, 0, 0, 0, 0, // best_ask_price
96    ];
97
98    let expected = PriceFeedDataV1 {
99        price_feed_id: PriceFeedId(1),
100        source_timestamp_us: TimestampUs::from_micros(2),
101        publisher_timestamp_us: TimestampUs::from_micros(3),
102        price: Some(Price::from_nonzero_mantissa(4.try_into().unwrap())),
103        best_bid_price: Some(Price::from_nonzero_mantissa(5.try_into().unwrap())),
104        best_ask_price: Some(Price::from_nonzero_mantissa(
105            (2 * 256 + 6).try_into().unwrap(),
106        )),
107    };
108    assert_eq!(
109        bincode::deserialize::<PriceFeedDataV1>(&data).unwrap(),
110        expected
111    );
112    assert_eq!(bincode::serialize(&expected).unwrap(), data);
113
114    let data2 = [
115        1, 0, 0, 0, // price_feed_id
116        2, 0, 0, 0, 0, 0, 0, 0, // source_timestamp_us
117        3, 0, 0, 0, 0, 0, 0, 0, // publisher_timestamp_us
118        4, 0, 0, 0, 0, 0, 0, 0, // price
119        0, 0, 0, 0, 0, 0, 0, 0, // best_bid_price
120        0, 0, 0, 0, 0, 0, 0, 0, // best_ask_price
121    ];
122    let expected2 = PriceFeedDataV1 {
123        price_feed_id: PriceFeedId(1),
124        source_timestamp_us: TimestampUs::from_micros(2),
125        publisher_timestamp_us: TimestampUs::from_micros(3),
126        price: Some(Price::from_nonzero_mantissa(4.try_into().unwrap())),
127        best_bid_price: None,
128        best_ask_price: None,
129    };
130    assert_eq!(
131        bincode::deserialize::<PriceFeedDataV1>(&data2).unwrap(),
132        expected2
133    );
134    assert_eq!(bincode::serialize(&expected2).unwrap(), data2);
135}
136
137#[test]
138fn price_feed_data_v2_serde() {
139    let data = [
140        1, 0, 0, 0, // price_feed_id
141        2, 0, 0, 0, 0, 0, 0, 0, // source_timestamp_us
142        3, 0, 0, 0, 0, 0, 0, 0, // publisher_timestamp_us
143        1, 4, 0, 0, 0, 0, 0, 0, 0, // price
144        1, 5, 0, 0, 0, 0, 0, 0, 0, // best_bid_price
145        1, 6, 2, 0, 0, 0, 0, 0, 0, // best_ask_price
146        0, // funding_rate
147    ];
148
149    let expected = PriceFeedDataV2 {
150        price_feed_id: PriceFeedId(1),
151        source_timestamp_us: TimestampUs::from_micros(2),
152        publisher_timestamp_us: TimestampUs::from_micros(3),
153        price: Some(Price::from_nonzero_mantissa(4.try_into().unwrap())),
154        best_bid_price: Some(Price::from_nonzero_mantissa(5.try_into().unwrap())),
155        best_ask_price: Some(Price::from_nonzero_mantissa(
156            (2 * 256 + 6).try_into().unwrap(),
157        )),
158        funding_rate: None,
159    };
160    assert_eq!(
161        bincode::deserialize::<PriceFeedDataV2>(&data).unwrap(),
162        expected
163    );
164    assert_eq!(bincode::serialize(&expected).unwrap(), data);
165
166    let data2 = [
167        1, 0, 0, 0, // price_feed_id
168        2, 0, 0, 0, 0, 0, 0, 0, // source_timestamp_us
169        3, 0, 0, 0, 0, 0, 0, 0, // publisher_timestamp_us
170        1, 4, 0, 0, 0, 0, 0, 0, 0, // price
171        0, // best_bid_price
172        0, // best_ask_price
173        1, 7, 3, 0, 0, 0, 0, 0, 0, // funding_rate
174    ];
175    let expected2 = PriceFeedDataV2 {
176        price_feed_id: PriceFeedId(1),
177        source_timestamp_us: TimestampUs::from_micros(2),
178        publisher_timestamp_us: TimestampUs::from_micros(3),
179        price: Some(Price::from_nonzero_mantissa(4.try_into().unwrap())),
180        best_bid_price: None,
181        best_ask_price: None,
182        funding_rate: Some(Rate::from_mantissa(3 * 256 + 7)),
183    };
184    assert_eq!(
185        bincode::deserialize::<PriceFeedDataV2>(&data2).unwrap(),
186        expected2
187    );
188    assert_eq!(bincode::serialize(&expected2).unwrap(), data2);
189}