Skip to content

Commit 6d6a06f

Browse files
feat(pyth-lazer-sdk): add funding rate (#2423)
* add funding rate and refactor publisher count * handle old price feed data * feat: add funding timestamp * feat: add from_f64 method for Price conversion * feat: update PriceFeedData structure and version to 0.6.0 * feat: refactor PriceFeedData serialization tests and update funding rate handling * feat: update funding rate and timestamp types to use Rate and TimestampUs * feat: remove precision check for price value in Rate conversion * feat: update pyth-lazer-protocol version to 0.6.1
1 parent 27938e7 commit 6d6a06f

File tree

5 files changed

+230
-30
lines changed

5 files changed

+230
-30
lines changed

lazer/Cargo.lock

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lazer/sdk/rust/protocol/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "pyth-lazer-protocol"
3-
version = "0.6.0"
3+
version = "0.6.1"
44
edition = "2021"
55
description = "Pyth Lazer SDK - protocol types."
66
license = "Apache-2.0"

lazer/sdk/rust/protocol/src/payload.rs

+75-11
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use {
44
super::router::{PriceFeedId, PriceFeedProperty, TimestampUs},
5-
crate::router::{ChannelId, Price},
5+
crate::router::{ChannelId, Price, Rate},
66
anyhow::bail,
77
byteorder::{ByteOrder, ReadBytesExt, WriteBytesExt, BE, LE},
88
serde::{Deserialize, Serialize},
@@ -33,18 +33,22 @@ pub enum PayloadPropertyValue {
3333
Price(Option<Price>),
3434
BestBidPrice(Option<Price>),
3535
BestAskPrice(Option<Price>),
36-
PublisherCount(Option<u16>),
36+
PublisherCount(u16),
3737
Exponent(i16),
3838
Confidence(Option<Price>),
39+
FundingRate(Option<Rate>),
40+
FundingTimestamp(Option<TimestampUs>),
3941
}
4042

4143
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
4244
pub struct AggregatedPriceFeedData {
4345
pub price: Option<Price>,
4446
pub best_bid_price: Option<Price>,
4547
pub best_ask_price: Option<Price>,
46-
pub publisher_count: Option<u16>,
48+
pub publisher_count: u16,
4749
pub confidence: Option<Price>,
50+
pub funding_rate: Option<Rate>,
51+
pub funding_timestamp: Option<TimestampUs>,
4852
}
4953

5054
/// First bytes of a payload's encoding
@@ -84,6 +88,12 @@ impl PayloadData {
8488
PriceFeedProperty::Confidence => {
8589
PayloadPropertyValue::Confidence(feed.confidence)
8690
}
91+
PriceFeedProperty::FundingRate => {
92+
PayloadPropertyValue::FundingRate(feed.funding_rate)
93+
}
94+
PriceFeedProperty::FundingTimestamp => {
95+
PayloadPropertyValue::FundingTimestamp(feed.funding_timestamp)
96+
}
8797
})
8898
.collect(),
8999
})
@@ -115,7 +125,7 @@ impl PayloadData {
115125
}
116126
PayloadPropertyValue::PublisherCount(count) => {
117127
writer.write_u8(PriceFeedProperty::PublisherCount as u8)?;
118-
write_option_u16::<BO>(&mut writer, *count)?;
128+
writer.write_u16::<BO>(*count)?;
119129
}
120130
PayloadPropertyValue::Exponent(exponent) => {
121131
writer.write_u8(PriceFeedProperty::Exponent as u8)?;
@@ -125,6 +135,14 @@ impl PayloadData {
125135
writer.write_u8(PriceFeedProperty::Confidence as u8)?;
126136
write_option_price::<BO>(&mut writer, *confidence)?;
127137
}
138+
PayloadPropertyValue::FundingRate(rate) => {
139+
writer.write_u8(PriceFeedProperty::FundingRate as u8)?;
140+
write_option_rate::<BO>(&mut writer, *rate)?;
141+
}
142+
PayloadPropertyValue::FundingTimestamp(timestamp) => {
143+
writer.write_u8(PriceFeedProperty::FundingTimestamp as u8)?;
144+
write_option_timestamp::<BO>(&mut writer, *timestamp)?;
145+
}
128146
}
129147
}
130148
}
@@ -164,11 +182,17 @@ impl PayloadData {
164182
} else if property == PriceFeedProperty::BestAskPrice as u8 {
165183
PayloadPropertyValue::BestAskPrice(read_option_price::<BO>(&mut reader)?)
166184
} else if property == PriceFeedProperty::PublisherCount as u8 {
167-
PayloadPropertyValue::PublisherCount(read_option_u16::<BO>(&mut reader)?)
185+
PayloadPropertyValue::PublisherCount(reader.read_u16::<BO>()?)
168186
} else if property == PriceFeedProperty::Exponent as u8 {
169187
PayloadPropertyValue::Exponent(reader.read_i16::<BO>()?)
170188
} else if property == PriceFeedProperty::Confidence as u8 {
171189
PayloadPropertyValue::Confidence(read_option_price::<BO>(&mut reader)?)
190+
} else if property == PriceFeedProperty::FundingRate as u8 {
191+
PayloadPropertyValue::FundingRate(read_option_rate::<BO>(&mut reader)?)
192+
} else if property == PriceFeedProperty::FundingTimestamp as u8 {
193+
PayloadPropertyValue::FundingTimestamp(read_option_timestamp::<BO>(
194+
&mut reader,
195+
)?)
172196
} else {
173197
bail!("unknown property");
174198
};
@@ -196,14 +220,54 @@ fn read_option_price<BO: ByteOrder>(mut reader: impl Read) -> std::io::Result<Op
196220
Ok(value.map(Price))
197221
}
198222

199-
fn write_option_u16<BO: ByteOrder>(
223+
fn write_option_rate<BO: ByteOrder>(
224+
mut writer: impl Write,
225+
value: Option<Rate>,
226+
) -> std::io::Result<()> {
227+
match value {
228+
Some(value) => {
229+
writer.write_u8(1)?;
230+
writer.write_i64::<BO>(value.0)
231+
}
232+
None => {
233+
writer.write_u8(0)?;
234+
Ok(())
235+
}
236+
}
237+
}
238+
239+
fn read_option_rate<BO: ByteOrder>(mut reader: impl Read) -> std::io::Result<Option<Rate>> {
240+
let present = reader.read_u8()? != 0;
241+
if present {
242+
Ok(Some(Rate(reader.read_i64::<BO>()?)))
243+
} else {
244+
Ok(None)
245+
}
246+
}
247+
248+
fn write_option_timestamp<BO: ByteOrder>(
200249
mut writer: impl Write,
201-
value: Option<u16>,
250+
value: Option<TimestampUs>,
202251
) -> std::io::Result<()> {
203-
writer.write_u16::<BO>(value.unwrap_or(0))
252+
match value {
253+
Some(value) => {
254+
writer.write_u8(1)?;
255+
writer.write_u64::<BO>(value.0)
256+
}
257+
None => {
258+
writer.write_u8(0)?;
259+
Ok(())
260+
}
261+
}
204262
}
205263

206-
fn read_option_u16<BO: ByteOrder>(mut reader: impl Read) -> std::io::Result<Option<u16>> {
207-
let value = reader.read_u16::<BO>()?;
208-
Ok(Some(value))
264+
fn read_option_timestamp<BO: ByteOrder>(
265+
mut reader: impl Read,
266+
) -> std::io::Result<Option<TimestampUs>> {
267+
let present = reader.read_u8()? != 0;
268+
if present {
269+
Ok(Some(TimestampUs(reader.read_u64::<BO>()?)))
270+
} else {
271+
Ok(None)
272+
}
209273
}

lazer/sdk/rust/protocol/src/publisher.rs

+99-7
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//! eliminating WebSocket overhead.
44
55
use {
6-
super::router::{Price, PriceFeedId, TimestampUs},
6+
super::router::{Price, PriceFeedId, Rate, TimestampUs},
77
derive_more::derive::From,
88
serde::{Deserialize, Serialize},
99
};
@@ -12,7 +12,33 @@ use {
1212
/// from the publisher to the router.
1313
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1414
#[serde(rename_all = "camelCase")]
15-
pub struct PriceFeedData {
15+
pub 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")]
41+
pub struct PriceFeedDataV1 {
1642
pub price_feed_id: PriceFeedId,
1743
/// Timestamp of the last update provided by the source of the prices
1844
/// (like an exchange). If unavailable, this value is set to `publisher_timestamp_us`.
@@ -33,6 +59,20 @@ pub struct PriceFeedData {
3359
pub best_ask_price: Option<Price>,
3460
}
3561

62+
impl 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+
3676
/// A response sent from the server to the publisher client.
3777
/// Currently only serde errors are reported back to the client.
3878
#[derive(Debug, Clone, Serialize, Deserialize, From)]
@@ -49,7 +89,7 @@ pub struct UpdateDeserializationErrorResponse {
4989
}
5090

5191
#[test]
52-
fn price_feed_data_serde() {
92+
fn price_feed_data_v1_serde() {
5393
let data = [
5494
1, 0, 0, 0, // price_feed_id
5595
2, 0, 0, 0, 0, 0, 0, 0, // source_timestamp_us
@@ -59,7 +99,7 @@ fn price_feed_data_serde() {
5999
6, 2, 0, 0, 0, 0, 0, 0, // best_ask_price
60100
];
61101

62-
let expected = PriceFeedData {
102+
let expected = PriceFeedDataV1 {
63103
price_feed_id: PriceFeedId(1),
64104
source_timestamp_us: TimestampUs(2),
65105
publisher_timestamp_us: TimestampUs(3),
@@ -68,7 +108,7 @@ fn price_feed_data_serde() {
68108
best_ask_price: Some(Price((2 * 256 + 6).try_into().unwrap())),
69109
};
70110
assert_eq!(
71-
bincode::deserialize::<PriceFeedData>(&data).unwrap(),
111+
bincode::deserialize::<PriceFeedDataV1>(&data).unwrap(),
72112
expected
73113
);
74114
assert_eq!(bincode::serialize(&expected).unwrap(), data);
@@ -81,16 +121,68 @@ fn price_feed_data_serde() {
81121
0, 0, 0, 0, 0, 0, 0, 0, // best_bid_price
82122
0, 0, 0, 0, 0, 0, 0, 0, // best_ask_price
83123
];
84-
let expected2 = PriceFeedData {
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]
140+
fn 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 {
85176
price_feed_id: PriceFeedId(1),
86177
source_timestamp_us: TimestampUs(2),
87178
publisher_timestamp_us: TimestampUs(3),
88179
price: Some(Price(4.try_into().unwrap())),
89180
best_bid_price: None,
90181
best_ask_price: None,
182+
funding_rate: Some(Rate(3 * 256 + 7)),
91183
};
92184
assert_eq!(
93-
bincode::deserialize::<PriceFeedData>(&data2).unwrap(),
185+
bincode::deserialize::<PriceFeedDataV2>(&data2).unwrap(),
94186
expected2
95187
);
96188
assert_eq!(bincode::serialize(&expected2).unwrap(), data2);

0 commit comments

Comments
 (0)