Skip to content

Commit 6014400

Browse files
authored
Move message structures from pyth-client repo to pythnet-sdk (#867)
* Move message structures from pyth-client repo to pythnet-sdk All rust based smart contracts could leverage these structures for deserializing pyth messages. But having them in pyth-client makes all the smart contracts packages depend on irrelevant packages such as solana libraries which caused dependency conflicts With these structs moved here it would be easier to reuse them in other places The custom (de)serialization logic remains in pyth-client since it was only to reduce binary size of the program deployed on mainnet/pythnet, therefore not necessary to be exposed here. * Put arbitrary implementations behind quickcheck feature flag * Expose magic variables and define them once
1 parent 91ce66d commit 6014400

File tree

4 files changed

+168
-3
lines changed

4 files changed

+168
-3
lines changed

pythnet/pythnet_sdk/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ byteorder = "1.4.3"
1818
fast-math = "0.1"
1919
hex = { version = "0.4.3", features = ["serde"] }
2020
serde = { version = "1.0.144", features = ["derive"] }
21+
strum = { version = "0.24.1", features = ["derive"], optional = true }
22+
quickcheck = { version = "1", optional = true}
2123
sha3 = "0.10.4"
2224
slow_primes = "0.1.14"
2325
thiserror = "1.0.40"

pythnet/pythnet_sdk/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
pub mod accumulators;
22
pub mod error;
33
pub mod hashers;
4+
pub mod messages;
45
pub mod wire;
56
pub mod wormhole;
67

pythnet/pythnet_sdk/src/messages.rs

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
#[cfg(feature = "quickcheck")]
2+
use quickcheck::Arbitrary;
3+
use serde::{
4+
Deserialize,
5+
Serialize,
6+
};
7+
8+
/// Message format for sending data to other chains via the accumulator program
9+
/// When serialized with PythNet serialization format, each message starts with a unique
10+
/// 1-byte discriminator, followed by the serialized struct data in the definition(s) below.
11+
///
12+
/// Messages are forward-compatible. You may add new fields to messages after all previously
13+
/// defined fields. All code for parsing messages must ignore any extraneous bytes at the end of
14+
/// the message (which could be fields that the code does not yet understand).
15+
///
16+
/// The oracle is not using the Message enum due to the contract size limit and
17+
/// some of the methods for PriceFeedMessage and TwapMessage are not used by the oracle
18+
/// for the same reason. Rust compiler doesn't include the unused methods in the contract.
19+
/// Once we start using the unused structs and methods, the contract size will increase.
20+
21+
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
22+
#[cfg_attr(
23+
feature = "strum",
24+
derive(strum::EnumDiscriminants),
25+
strum_discriminants(name(MessageType)),
26+
strum_discriminants(vis(pub)),
27+
strum_discriminants(derive(
28+
Hash,
29+
strum::EnumIter,
30+
strum::EnumString,
31+
strum::IntoStaticStr,
32+
strum::ToString,
33+
Serialize,
34+
Deserialize
35+
))
36+
)]
37+
pub enum Message {
38+
PriceFeedMessage(PriceFeedMessage),
39+
TwapMessage(TwapMessage),
40+
}
41+
42+
impl Message {
43+
pub fn publish_time(&self) -> i64 {
44+
match self {
45+
Self::PriceFeedMessage(msg) => msg.publish_time,
46+
Self::TwapMessage(msg) => msg.publish_time,
47+
}
48+
}
49+
50+
pub fn id(&self) -> [u8; 32] {
51+
match self {
52+
Self::PriceFeedMessage(msg) => msg.id,
53+
Self::TwapMessage(msg) => msg.id,
54+
}
55+
}
56+
}
57+
58+
#[cfg(feature = "quickcheck")]
59+
impl Arbitrary for Message {
60+
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
61+
match u8::arbitrary(g) % 2 {
62+
0 => Message::PriceFeedMessage(Arbitrary::arbitrary(g)),
63+
_ => Message::TwapMessage(Arbitrary::arbitrary(g)),
64+
}
65+
}
66+
}
67+
68+
69+
#[repr(C)]
70+
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
71+
pub struct PriceFeedMessage {
72+
pub id: [u8; 32],
73+
pub price: i64,
74+
pub conf: u64,
75+
pub exponent: i32,
76+
/// The timestamp of this price update in seconds
77+
pub publish_time: i64,
78+
/// The timestamp of the previous price update. This field is intended to allow users to
79+
/// identify the single unique price update for any moment in time:
80+
/// for any time t, the unique update is the one such that prev_publish_time < t <= publish_time.
81+
///
82+
/// Note that there may not be such an update while we are migrating to the new message-sending logic,
83+
/// as some price updates on pythnet may not be sent to other chains (because the message-sending
84+
/// logic may not have triggered). We can solve this problem by making the message-sending mandatory
85+
/// (which we can do once publishers have migrated over).
86+
///
87+
/// Additionally, this field may be equal to publish_time if the message is sent on a slot where
88+
/// where the aggregation was unsuccesful. This problem will go away once all publishers have
89+
/// migrated over to a recent version of pyth-agent.
90+
pub prev_publish_time: i64,
91+
pub ema_price: i64,
92+
pub ema_conf: u64,
93+
}
94+
95+
#[cfg(feature = "quickcheck")]
96+
impl Arbitrary for PriceFeedMessage {
97+
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
98+
let mut id = [0u8; 32];
99+
for item in &mut id {
100+
*item = u8::arbitrary(g);
101+
}
102+
103+
let publish_time = i64::arbitrary(g);
104+
105+
PriceFeedMessage {
106+
id,
107+
price: i64::arbitrary(g),
108+
conf: u64::arbitrary(g),
109+
exponent: i32::arbitrary(g),
110+
publish_time,
111+
prev_publish_time: publish_time.saturating_sub(i64::arbitrary(g)),
112+
ema_price: i64::arbitrary(g),
113+
ema_conf: u64::arbitrary(g),
114+
}
115+
}
116+
}
117+
118+
/// Message format for sending Twap data via the accumulator program
119+
#[repr(C)]
120+
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
121+
pub struct TwapMessage {
122+
pub id: [u8; 32],
123+
pub cumulative_price: i128,
124+
pub cumulative_conf: u128,
125+
pub num_down_slots: u64,
126+
pub exponent: i32,
127+
pub publish_time: i64,
128+
pub prev_publish_time: i64,
129+
pub publish_slot: u64,
130+
}
131+
132+
#[cfg(feature = "quickcheck")]
133+
impl Arbitrary for TwapMessage {
134+
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
135+
let mut id = [0u8; 32];
136+
for item in &mut id {
137+
*item = u8::arbitrary(g);
138+
}
139+
140+
let publish_time = i64::arbitrary(g);
141+
142+
TwapMessage {
143+
id,
144+
cumulative_price: i128::arbitrary(g),
145+
cumulative_conf: u128::arbitrary(g),
146+
num_down_slots: u64::arbitrary(g),
147+
exponent: i32::arbitrary(g),
148+
publish_time,
149+
prev_publish_time: publish_time.saturating_sub(i64::arbitrary(g)),
150+
publish_slot: u64::arbitrary(g),
151+
}
152+
}
153+
}

pythnet/pythnet_sdk/src/wire.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ pub mod v1 {
4545
Serialize,
4646
},
4747
};
48+
pub const PYTHNET_ACCUMULATOR_UPDATE_MAGIC: &[u8; 4] = b"PNAU";
4849

4950
// Transfer Format.
5051
// --------------------------------------------------------------------------------
@@ -62,7 +63,7 @@ pub mod v1 {
6263
impl AccumulatorUpdateData {
6364
pub fn new(proof: Proof) -> Self {
6465
Self {
65-
magic: *b"PNAU",
66+
magic: *PYTHNET_ACCUMULATOR_UPDATE_MAGIC,
6667
major_version: 1,
6768
minor_version: 0,
6869
trailing: vec![],
@@ -72,7 +73,10 @@ pub mod v1 {
7273

7374
pub fn try_from_slice(bytes: &[u8]) -> Result<Self, Error> {
7475
let message = from_slice::<byteorder::BE, Self>(bytes).unwrap();
75-
require!(&message.magic[..] == b"PNAU", Error::InvalidMagic);
76+
require!(
77+
&message.magic[..] == PYTHNET_ACCUMULATOR_UPDATE_MAGIC,
78+
Error::InvalidMagic
79+
);
7680
require!(message.major_version == 1, Error::InvalidVersion);
7781
require!(message.minor_version == 0, Error::InvalidVersion);
7882
Ok(message)
@@ -102,10 +106,15 @@ pub mod v1 {
102106
pub payload: WormholePayload,
103107
}
104108

109+
pub const ACCUMULATOR_UPDATE_WORMHOLE_VERIFICATION_MAGIC: &[u8; 4] = b"AUWV";
110+
105111
impl WormholeMessage {
106112
pub fn try_from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, Error> {
107113
let message = from_slice::<byteorder::BE, Self>(bytes.as_ref()).unwrap();
108-
require!(&message.magic[..] == b"AUWV", Error::InvalidMagic);
114+
require!(
115+
&message.magic[..] == ACCUMULATOR_UPDATE_WORMHOLE_VERIFICATION_MAGIC,
116+
Error::InvalidMagic
117+
);
109118
Ok(message)
110119
}
111120
}

0 commit comments

Comments
 (0)