pyth_lazer_protocol/
message.rs

1use {
2    self::format_magics_le::{EVM_FORMAT_MAGIC, SOLANA_FORMAT_MAGIC},
3    crate::router::ParsedPayload,
4    anyhow::{bail, Context},
5    byteorder::{ByteOrder, ReadBytesExt, WriteBytesExt, BE, LE},
6    derive_more::From,
7    format_magics_le::{JSON_FORMAT_MAGIC, LE_ECDSA_FORMAT_MAGIC, LE_UNSIGNED_FORMAT_MAGIC},
8    std::io::{Cursor, Read, Write},
9};
10
11/// Constants containing first bytes (LE) of a price update.
12pub mod format_magics_le {
13    /// First bytes (LE) of a JSON-encoded price update (JSON structure is represented by
14    /// `router::ParsedPayload` type).
15    ///
16    /// Note: this header will only be present if the binary delivery method is requested
17    /// in a Websocket subscription. If the default (JSON) delivery method is used,
18    /// the price update JSON will simply be embedded in the main JSON of the notification.
19    pub const JSON_FORMAT_MAGIC: u32 = 3302625434;
20    /// First bytes (LE) of an EVM-targeted price update (BE-encoded payload with an ECDSA signature).
21    pub const EVM_FORMAT_MAGIC: u32 = 2593727018;
22    /// First bytes (LE) of a Solana-targeted price update with a native Solana signature
23    /// (LE-encoded payload with a Ed25519 signature).
24    pub const SOLANA_FORMAT_MAGIC: u32 = 2182742457;
25    /// First bytes (LE) of a price update with LE-encoded payload and an ECDSA signature
26    /// (suitable for Solana).
27    pub const LE_ECDSA_FORMAT_MAGIC: u32 = 1296547300;
28    /// First bytes (LE) of a price update with LE-encoded payload without a signature
29    /// (suitable for off-chain usage).
30    pub const LE_UNSIGNED_FORMAT_MAGIC: u32 = 1499680012;
31}
32
33#[derive(Debug, Clone, PartialEq, Eq, Hash, From)]
34pub enum Message {
35    Evm(EvmMessage),
36    Solana(SolanaMessage),
37    LeEcdsa(LeEcdsaMessage),
38    LeUnsigned(LeUnsignedMessage),
39    Json(ParsedPayload),
40}
41
42impl Message {
43    pub fn serialize(&self, mut writer: impl Write) -> anyhow::Result<()> {
44        match self {
45            Message::Evm(message) => message.serialize(writer),
46            Message::Solana(message) => message.serialize(writer),
47            Message::LeEcdsa(message) => message.serialize(writer),
48            Message::LeUnsigned(message) => message.serialize(writer),
49            Message::Json(message) => {
50                writer.write_u32::<LE>(JSON_FORMAT_MAGIC)?;
51                serde_json::to_writer(writer, message)?;
52                Ok(())
53            }
54        }
55    }
56
57    pub fn deserialize_slice(data: &[u8]) -> anyhow::Result<Self> {
58        let magic = LE::read_u32(data.get(0..4).context("data too short")?);
59        match magic {
60            JSON_FORMAT_MAGIC => Ok(serde_json::from_slice::<ParsedPayload>(&data[4..])?.into()),
61            EVM_FORMAT_MAGIC => Ok(EvmMessage::deserialize_slice(data)?.into()),
62            SOLANA_FORMAT_MAGIC => Ok(SolanaMessage::deserialize_slice(data)?.into()),
63            LE_ECDSA_FORMAT_MAGIC => Ok(LeEcdsaMessage::deserialize_slice(data)?.into()),
64            LE_UNSIGNED_FORMAT_MAGIC => Ok(LeUnsignedMessage::deserialize_slice(data)?.into()),
65            _ => bail!("unrecognized format magic"),
66        }
67    }
68}
69
70/// EVM signature enveope.
71#[derive(Debug, Clone, PartialEq, Eq, Hash)]
72pub struct EvmMessage {
73    pub payload: Vec<u8>,
74    pub signature: [u8; 64],
75    pub recovery_id: u8,
76}
77
78impl EvmMessage {
79    pub fn serialize(&self, mut writer: impl Write) -> anyhow::Result<()> {
80        writer.write_u32::<LE>(EVM_FORMAT_MAGIC)?;
81        writer.write_all(&self.signature)?;
82        writer.write_u8(self.recovery_id)?;
83        writer.write_u16::<BE>(self.payload.len().try_into()?)?;
84        writer.write_all(&self.payload)?;
85        Ok(())
86    }
87
88    pub fn deserialize_slice(data: &[u8]) -> anyhow::Result<Self> {
89        Self::deserialize(Cursor::new(data))
90    }
91
92    pub fn deserialize(mut reader: impl Read) -> anyhow::Result<Self> {
93        let magic = reader.read_u32::<LE>()?;
94        if magic != EVM_FORMAT_MAGIC {
95            bail!("magic mismatch");
96        }
97        let mut signature = [0u8; 64];
98        reader.read_exact(&mut signature)?;
99        let recovery_id = reader.read_u8()?;
100        let payload_len: usize = reader.read_u16::<BE>()?.into();
101        let mut payload = vec![0u8; payload_len];
102        reader.read_exact(&mut payload)?;
103        Ok(Self {
104            payload,
105            signature,
106            recovery_id,
107        })
108    }
109}
110
111/// Solana signature envelope.
112#[derive(Debug, Clone, PartialEq, Eq, Hash)]
113pub struct SolanaMessage {
114    pub payload: Vec<u8>,
115    pub signature: [u8; 64],
116    pub public_key: [u8; 32],
117}
118
119impl SolanaMessage {
120    pub fn serialize(&self, mut writer: impl Write) -> anyhow::Result<()> {
121        writer.write_u32::<LE>(SOLANA_FORMAT_MAGIC)?;
122        writer.write_all(&self.signature)?;
123        writer.write_all(&self.public_key)?;
124        writer.write_u16::<LE>(self.payload.len().try_into()?)?;
125        writer.write_all(&self.payload)?;
126        Ok(())
127    }
128
129    pub fn deserialize_slice(data: &[u8]) -> anyhow::Result<Self> {
130        Self::deserialize(Cursor::new(data))
131    }
132
133    pub fn deserialize(mut reader: impl Read) -> anyhow::Result<Self> {
134        let magic = reader.read_u32::<LE>()?;
135        if magic != SOLANA_FORMAT_MAGIC {
136            bail!("magic mismatch");
137        }
138        let mut signature = [0u8; 64];
139        reader.read_exact(&mut signature)?;
140        let mut public_key = [0u8; 32];
141        reader.read_exact(&mut public_key)?;
142        let payload_len: usize = reader.read_u16::<LE>()?.into();
143        let mut payload = vec![0u8; payload_len];
144        reader.read_exact(&mut payload)?;
145        Ok(Self {
146            payload,
147            signature,
148            public_key,
149        })
150    }
151}
152
153/// LE-ECDSA format enveope.
154#[derive(Debug, Clone, PartialEq, Eq, Hash)]
155pub struct LeEcdsaMessage {
156    pub payload: Vec<u8>,
157    pub signature: [u8; 64],
158    pub recovery_id: u8,
159}
160
161impl LeEcdsaMessage {
162    pub fn serialize(&self, mut writer: impl Write) -> anyhow::Result<()> {
163        writer.write_u32::<LE>(LE_ECDSA_FORMAT_MAGIC)?;
164        writer.write_all(&self.signature)?;
165        writer.write_u8(self.recovery_id)?;
166        writer.write_u16::<LE>(self.payload.len().try_into()?)?;
167        writer.write_all(&self.payload)?;
168        Ok(())
169    }
170
171    pub fn deserialize_slice(data: &[u8]) -> anyhow::Result<Self> {
172        Self::deserialize(Cursor::new(data))
173    }
174
175    pub fn deserialize(mut reader: impl Read) -> anyhow::Result<Self> {
176        let magic = reader.read_u32::<LE>()?;
177        if magic != LE_ECDSA_FORMAT_MAGIC {
178            bail!("magic mismatch");
179        }
180        let mut signature = [0u8; 64];
181        reader.read_exact(&mut signature)?;
182        let recovery_id = reader.read_u8()?;
183        let payload_len: usize = reader.read_u16::<LE>()?.into();
184        let mut payload = vec![0u8; payload_len];
185        reader.read_exact(&mut payload)?;
186        Ok(Self {
187            payload,
188            signature,
189            recovery_id,
190        })
191    }
192}
193
194/// LE-Unsigned format enveope.
195#[derive(Debug, Clone, PartialEq, Eq, Hash)]
196pub struct LeUnsignedMessage {
197    pub payload: Vec<u8>,
198}
199
200impl LeUnsignedMessage {
201    pub fn serialize(&self, mut writer: impl Write) -> anyhow::Result<()> {
202        writer.write_u32::<LE>(LE_UNSIGNED_FORMAT_MAGIC)?;
203        writer.write_u16::<LE>(self.payload.len().try_into()?)?;
204        writer.write_all(&self.payload)?;
205        Ok(())
206    }
207
208    pub fn deserialize_slice(data: &[u8]) -> anyhow::Result<Self> {
209        Self::deserialize(Cursor::new(data))
210    }
211
212    pub fn deserialize(mut reader: impl Read) -> anyhow::Result<Self> {
213        let magic = reader.read_u32::<LE>()?;
214        if magic != LE_UNSIGNED_FORMAT_MAGIC {
215            bail!("magic mismatch");
216        }
217        let payload_len: usize = reader.read_u16::<LE>()?.into();
218        let mut payload = vec![0u8; payload_len];
219        reader.read_exact(&mut payload)?;
220        Ok(Self { payload })
221    }
222}
223
224#[test]
225fn test_evm_serde() {
226    let m1 = EvmMessage {
227        payload: vec![1, 2, 4, 3],
228        signature: [5; 64],
229        recovery_id: 1,
230    };
231    let mut buf = Vec::new();
232    m1.serialize(&mut buf).unwrap();
233    assert_eq!(m1, EvmMessage::deserialize_slice(&buf).unwrap());
234}
235
236#[test]
237fn test_solana_serde() {
238    let m1 = SolanaMessage {
239        payload: vec![1, 2, 4, 3],
240        signature: [5; 64],
241        public_key: [6; 32],
242    };
243    let mut buf = Vec::new();
244    m1.serialize(&mut buf).unwrap();
245    assert_eq!(m1, SolanaMessage::deserialize_slice(&buf).unwrap());
246}
247
248#[test]
249fn test_le_ecdsa_serde() {
250    let m1 = LeEcdsaMessage {
251        payload: vec![1, 2, 4, 3],
252        signature: [5; 64],
253        recovery_id: 1,
254    };
255    let mut buf = Vec::new();
256    m1.serialize(&mut buf).unwrap();
257    assert_eq!(m1, LeEcdsaMessage::deserialize_slice(&buf).unwrap());
258}
259
260#[test]
261fn test_le_unsigned_serde() {
262    let m1 = LeUnsignedMessage {
263        payload: vec![1, 2, 4, 3],
264    };
265    let mut buf = Vec::new();
266    m1.serialize(&mut buf).unwrap();
267    assert_eq!(m1, LeUnsignedMessage::deserialize_slice(&buf).unwrap());
268}