Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lazer/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion lazer/sdk/rust/protocol/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pyth-lazer-protocol"
version = "0.6.0"
version = "0.6.1"
edition = "2021"
description = "Pyth Lazer SDK - protocol types."
license = "Apache-2.0"
Expand Down
86 changes: 75 additions & 11 deletions lazer/sdk/rust/protocol/src/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use {
super::router::{PriceFeedId, PriceFeedProperty, TimestampUs},
crate::router::{ChannelId, Price},
crate::router::{ChannelId, Price, Rate},
anyhow::bail,
byteorder::{ByteOrder, ReadBytesExt, WriteBytesExt, BE, LE},
serde::{Deserialize, Serialize},
Expand Down Expand Up @@ -33,18 +33,22 @@ pub enum PayloadPropertyValue {
Price(Option<Price>),
BestBidPrice(Option<Price>),
BestAskPrice(Option<Price>),
PublisherCount(Option<u16>),
PublisherCount(u16),
Exponent(i16),
Confidence(Option<Price>),
FundingRate(Option<Rate>),
FundingTimestamp(Option<TimestampUs>),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this different from the timestamp of the update itself?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never mind this is answered above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unding timestamp is the timestamp of the funding rate which is different from the timestamp we have in the aggregated update (which is the time we created the aggregate). Also the timestamp needs to be different per feed as each funding rate has different intervals.

}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AggregatedPriceFeedData {
pub price: Option<Price>,
pub best_bid_price: Option<Price>,
pub best_ask_price: Option<Price>,
pub publisher_count: Option<u16>,
pub publisher_count: u16,
pub confidence: Option<Price>,
pub funding_rate: Option<Rate>,
pub funding_timestamp: Option<TimestampUs>,
}

/// First bytes of a payload's encoding
Expand Down Expand Up @@ -84,6 +88,12 @@ impl PayloadData {
PriceFeedProperty::Confidence => {
PayloadPropertyValue::Confidence(feed.confidence)
}
PriceFeedProperty::FundingRate => {
PayloadPropertyValue::FundingRate(feed.funding_rate)
}
PriceFeedProperty::FundingTimestamp => {
PayloadPropertyValue::FundingTimestamp(feed.funding_timestamp)
}
})
.collect(),
})
Expand Down Expand Up @@ -115,7 +125,7 @@ impl PayloadData {
}
PayloadPropertyValue::PublisherCount(count) => {
writer.write_u8(PriceFeedProperty::PublisherCount as u8)?;
write_option_u16::<BO>(&mut writer, *count)?;
writer.write_u16::<BO>(*count)?;
}
PayloadPropertyValue::Exponent(exponent) => {
writer.write_u8(PriceFeedProperty::Exponent as u8)?;
Expand All @@ -125,6 +135,14 @@ impl PayloadData {
writer.write_u8(PriceFeedProperty::Confidence as u8)?;
write_option_price::<BO>(&mut writer, *confidence)?;
}
PayloadPropertyValue::FundingRate(rate) => {
writer.write_u8(PriceFeedProperty::FundingRate as u8)?;
write_option_rate::<BO>(&mut writer, *rate)?;
}
PayloadPropertyValue::FundingTimestamp(timestamp) => {
writer.write_u8(PriceFeedProperty::FundingTimestamp as u8)?;
write_option_timestamp::<BO>(&mut writer, *timestamp)?;
}
}
}
}
Expand Down Expand Up @@ -164,11 +182,17 @@ impl PayloadData {
} else if property == PriceFeedProperty::BestAskPrice as u8 {
PayloadPropertyValue::BestAskPrice(read_option_price::<BO>(&mut reader)?)
} else if property == PriceFeedProperty::PublisherCount as u8 {
PayloadPropertyValue::PublisherCount(read_option_u16::<BO>(&mut reader)?)
PayloadPropertyValue::PublisherCount(reader.read_u16::<BO>()?)
} else if property == PriceFeedProperty::Exponent as u8 {
PayloadPropertyValue::Exponent(reader.read_i16::<BO>()?)
} else if property == PriceFeedProperty::Confidence as u8 {
PayloadPropertyValue::Confidence(read_option_price::<BO>(&mut reader)?)
} else if property == PriceFeedProperty::FundingRate as u8 {
PayloadPropertyValue::FundingRate(read_option_rate::<BO>(&mut reader)?)
} else if property == PriceFeedProperty::FundingTimestamp as u8 {
PayloadPropertyValue::FundingTimestamp(read_option_timestamp::<BO>(
&mut reader,
)?)
} else {
bail!("unknown property");
};
Expand Down Expand Up @@ -196,14 +220,54 @@ fn read_option_price<BO: ByteOrder>(mut reader: impl Read) -> std::io::Result<Op
Ok(value.map(Price))
}

fn write_option_u16<BO: ByteOrder>(
fn write_option_rate<BO: ByteOrder>(
mut writer: impl Write,
value: Option<Rate>,
) -> std::io::Result<()> {
match value {
Some(value) => {
writer.write_u8(1)?;
writer.write_i64::<BO>(value.0)
}
None => {
writer.write_u8(0)?;
Ok(())
}
}
}

fn read_option_rate<BO: ByteOrder>(mut reader: impl Read) -> std::io::Result<Option<Rate>> {
let present = reader.read_u8()? != 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what you were discussing about the onchain payload right? We need to implement this function on contract?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the contract needs to update to support this.

if present {
Ok(Some(Rate(reader.read_i64::<BO>()?)))
} else {
Ok(None)
}
}

fn write_option_timestamp<BO: ByteOrder>(
mut writer: impl Write,
value: Option<u16>,
value: Option<TimestampUs>,
) -> std::io::Result<()> {
writer.write_u16::<BO>(value.unwrap_or(0))
match value {
Some(value) => {
writer.write_u8(1)?;
writer.write_u64::<BO>(value.0)
}
None => {
writer.write_u8(0)?;
Ok(())
}
}
}

fn read_option_u16<BO: ByteOrder>(mut reader: impl Read) -> std::io::Result<Option<u16>> {
let value = reader.read_u16::<BO>()?;
Ok(Some(value))
fn read_option_timestamp<BO: ByteOrder>(
mut reader: impl Read,
) -> std::io::Result<Option<TimestampUs>> {
let present = reader.read_u8()? != 0;
if present {
Ok(Some(TimestampUs(reader.read_u64::<BO>()?)))
} else {
Ok(None)
}
}
106 changes: 99 additions & 7 deletions lazer/sdk/rust/protocol/src/publisher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! eliminating WebSocket overhead.

use {
super::router::{Price, PriceFeedId, TimestampUs},
super::router::{Price, PriceFeedId, Rate, TimestampUs},
derive_more::derive::From,
serde::{Deserialize, Serialize},
};
Expand All @@ -12,7 +12,33 @@ use {
/// from the publisher to the router.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PriceFeedData {
pub struct PriceFeedDataV2 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, how will V1/V2 be communicated in the binary? Do we need to add a magic?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are different urls in the relayers for these 2.

pub price_feed_id: PriceFeedId,
/// Timestamp of the last update provided by the source of the prices
/// (like an exchange). If unavailable, this value is set to `publisher_timestamp_us`.
pub source_timestamp_us: TimestampUs,
/// Timestamp of the last update provided by the publisher.
pub publisher_timestamp_us: TimestampUs,
/// Last known value of the best executable price of this price feed.
/// `None` if no value is currently available.
pub price: Option<Price>,
/// Last known value of the best bid price of this price feed.
/// `None` if no value is currently available.
pub best_bid_price: Option<Price>,
/// Last known value of the best ask price of this price feed.
/// `None` if no value is currently available.
pub best_ask_price: Option<Price>,
/// Last known value of the funding rate of this feed.
/// `None` if no value is currently available.
pub funding_rate: Option<Rate>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about funding timestamp?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reused the source_timestamp as funding timestamp.

}

/// Old Represents a binary (bincode-serialized) stream update sent
/// from the publisher to the router.
/// Superseded by `PriceFeedData`.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PriceFeedDataV1 {
pub price_feed_id: PriceFeedId,
/// Timestamp of the last update provided by the source of the prices
/// (like an exchange). If unavailable, this value is set to `publisher_timestamp_us`.
Expand All @@ -33,6 +59,20 @@ pub struct PriceFeedData {
pub best_ask_price: Option<Price>,
}

impl From<PriceFeedDataV1> for PriceFeedDataV2 {
fn from(v0: PriceFeedDataV1) -> Self {
Self {
price_feed_id: v0.price_feed_id,
source_timestamp_us: v0.source_timestamp_us,
publisher_timestamp_us: v0.publisher_timestamp_us,
price: v0.price,
best_bid_price: v0.best_bid_price,
best_ask_price: v0.best_ask_price,
funding_rate: None,
}
}
}

/// A response sent from the server to the publisher client.
/// Currently only serde errors are reported back to the client.
#[derive(Debug, Clone, Serialize, Deserialize, From)]
Expand All @@ -49,7 +89,7 @@ pub struct UpdateDeserializationErrorResponse {
}

#[test]
fn price_feed_data_serde() {
fn price_feed_data_v1_serde() {
let data = [
1, 0, 0, 0, // price_feed_id
2, 0, 0, 0, 0, 0, 0, 0, // source_timestamp_us
Expand All @@ -59,7 +99,7 @@ fn price_feed_data_serde() {
6, 2, 0, 0, 0, 0, 0, 0, // best_ask_price
];

let expected = PriceFeedData {
let expected = PriceFeedDataV1 {
price_feed_id: PriceFeedId(1),
source_timestamp_us: TimestampUs(2),
publisher_timestamp_us: TimestampUs(3),
Expand All @@ -68,7 +108,7 @@ fn price_feed_data_serde() {
best_ask_price: Some(Price((2 * 256 + 6).try_into().unwrap())),
};
assert_eq!(
bincode::deserialize::<PriceFeedData>(&data).unwrap(),
bincode::deserialize::<PriceFeedDataV1>(&data).unwrap(),
expected
);
assert_eq!(bincode::serialize(&expected).unwrap(), data);
Expand All @@ -81,16 +121,68 @@ fn price_feed_data_serde() {
0, 0, 0, 0, 0, 0, 0, 0, // best_bid_price
0, 0, 0, 0, 0, 0, 0, 0, // best_ask_price
];
let expected2 = PriceFeedData {
let expected2 = PriceFeedDataV1 {
price_feed_id: PriceFeedId(1),
source_timestamp_us: TimestampUs(2),
publisher_timestamp_us: TimestampUs(3),
price: Some(Price(4.try_into().unwrap())),
best_bid_price: None,
best_ask_price: None,
};
assert_eq!(
bincode::deserialize::<PriceFeedDataV1>(&data2).unwrap(),
expected2
);
assert_eq!(bincode::serialize(&expected2).unwrap(), data2);
}

#[test]
fn price_feed_data_v2_serde() {
let data = [
1, 0, 0, 0, // price_feed_id
2, 0, 0, 0, 0, 0, 0, 0, // source_timestamp_us
3, 0, 0, 0, 0, 0, 0, 0, // publisher_timestamp_us
1, 4, 0, 0, 0, 0, 0, 0, 0, // price
1, 5, 0, 0, 0, 0, 0, 0, 0, // best_bid_price
1, 6, 2, 0, 0, 0, 0, 0, 0, // best_ask_price
0, // funding_rate
];

let expected = PriceFeedDataV2 {
price_feed_id: PriceFeedId(1),
source_timestamp_us: TimestampUs(2),
publisher_timestamp_us: TimestampUs(3),
price: Some(Price(4.try_into().unwrap())),
best_bid_price: Some(Price(5.try_into().unwrap())),
best_ask_price: Some(Price((2 * 256 + 6).try_into().unwrap())),
funding_rate: None,
};
assert_eq!(
bincode::deserialize::<PriceFeedDataV2>(&data).unwrap(),
expected
);
assert_eq!(bincode::serialize(&expected).unwrap(), data);

let data2 = [
1, 0, 0, 0, // price_feed_id
2, 0, 0, 0, 0, 0, 0, 0, // source_timestamp_us
3, 0, 0, 0, 0, 0, 0, 0, // publisher_timestamp_us
1, 4, 0, 0, 0, 0, 0, 0, 0, // price
0, // best_bid_price
0, // best_ask_price
1, 7, 3, 0, 0, 0, 0, 0, 0, // funding_rate
];
let expected2 = PriceFeedDataV2 {
price_feed_id: PriceFeedId(1),
source_timestamp_us: TimestampUs(2),
publisher_timestamp_us: TimestampUs(3),
price: Some(Price(4.try_into().unwrap())),
best_bid_price: None,
best_ask_price: None,
funding_rate: Some(Rate(3 * 256 + 7)),
};
assert_eq!(
bincode::deserialize::<PriceFeedData>(&data2).unwrap(),
bincode::deserialize::<PriceFeedDataV2>(&data2).unwrap(),
expected2
);
assert_eq!(bincode::serialize(&expected2).unwrap(), data2);
Expand Down
Loading
Loading