pyth_lazer_protocol/
publisher.rs

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