Quellcode durchsuchen

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
Mohammad Amin Khashkhashi Moghaddam vor 2 Jahren
Ursprung
Commit
6014400205

+ 2 - 0
pythnet/pythnet_sdk/Cargo.toml

@@ -18,6 +18,8 @@ byteorder = "1.4.3"
 fast-math = "0.1"
 hex = { version = "0.4.3", features = ["serde"] }
 serde = { version = "1.0.144", features = ["derive"] }
+strum = { version = "0.24.1", features = ["derive"], optional = true }
+quickcheck = { version = "1", optional = true}
 sha3 = "0.10.4"
 slow_primes = "0.1.14"
 thiserror = "1.0.40"

+ 1 - 0
pythnet/pythnet_sdk/src/lib.rs

@@ -1,6 +1,7 @@
 pub mod accumulators;
 pub mod error;
 pub mod hashers;
+pub mod messages;
 pub mod wire;
 pub mod wormhole;
 

+ 153 - 0
pythnet/pythnet_sdk/src/messages.rs

@@ -0,0 +1,153 @@
+#[cfg(feature = "quickcheck")]
+use quickcheck::Arbitrary;
+use serde::{
+    Deserialize,
+    Serialize,
+};
+
+/// Message format for sending data to other chains via the accumulator program
+/// When serialized with PythNet serialization format, each message starts with a unique
+/// 1-byte discriminator, followed by the serialized struct data in the definition(s) below.
+///
+/// Messages are forward-compatible. You may add new fields to messages after all previously
+/// defined fields. All code for parsing messages must ignore any extraneous bytes at the end of
+/// the message (which could be fields that the code does not yet understand).
+///
+/// The oracle is not using the Message enum due to the contract size limit and
+/// some of the methods for PriceFeedMessage and TwapMessage are not used by the oracle
+/// for the same reason. Rust compiler doesn't include the unused methods in the contract.
+/// Once we start using the unused structs and methods, the contract size will increase.
+
+#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
+#[cfg_attr(
+    feature = "strum",
+    derive(strum::EnumDiscriminants),
+    strum_discriminants(name(MessageType)),
+    strum_discriminants(vis(pub)),
+    strum_discriminants(derive(
+        Hash,
+        strum::EnumIter,
+        strum::EnumString,
+        strum::IntoStaticStr,
+        strum::ToString,
+        Serialize,
+        Deserialize
+    ))
+)]
+pub enum Message {
+    PriceFeedMessage(PriceFeedMessage),
+    TwapMessage(TwapMessage),
+}
+
+impl Message {
+    pub fn publish_time(&self) -> i64 {
+        match self {
+            Self::PriceFeedMessage(msg) => msg.publish_time,
+            Self::TwapMessage(msg) => msg.publish_time,
+        }
+    }
+
+    pub fn id(&self) -> [u8; 32] {
+        match self {
+            Self::PriceFeedMessage(msg) => msg.id,
+            Self::TwapMessage(msg) => msg.id,
+        }
+    }
+}
+
+#[cfg(feature = "quickcheck")]
+impl Arbitrary for Message {
+    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
+        match u8::arbitrary(g) % 2 {
+            0 => Message::PriceFeedMessage(Arbitrary::arbitrary(g)),
+            _ => Message::TwapMessage(Arbitrary::arbitrary(g)),
+        }
+    }
+}
+
+
+#[repr(C)]
+#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
+pub struct PriceFeedMessage {
+    pub id:                [u8; 32],
+    pub price:             i64,
+    pub conf:              u64,
+    pub exponent:          i32,
+    /// The timestamp of this price update in seconds
+    pub publish_time:      i64,
+    /// The timestamp of the previous price update. This field is intended to allow users to
+    /// identify the single unique price update for any moment in time:
+    /// for any time t, the unique update is the one such that prev_publish_time < t <= publish_time.
+    ///
+    /// Note that there may not be such an update while we are migrating to the new message-sending logic,
+    /// as some price updates on pythnet may not be sent to other chains (because the message-sending
+    /// logic may not have triggered). We can solve this problem by making the message-sending mandatory
+    /// (which we can do once publishers have migrated over).
+    ///
+    /// Additionally, this field may be equal to publish_time if the message is sent on a slot where
+    /// where the aggregation was unsuccesful. This problem will go away once all publishers have
+    /// migrated over to a recent version of pyth-agent.
+    pub prev_publish_time: i64,
+    pub ema_price:         i64,
+    pub ema_conf:          u64,
+}
+
+#[cfg(feature = "quickcheck")]
+impl Arbitrary for PriceFeedMessage {
+    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
+        let mut id = [0u8; 32];
+        for item in &mut id {
+            *item = u8::arbitrary(g);
+        }
+
+        let publish_time = i64::arbitrary(g);
+
+        PriceFeedMessage {
+            id,
+            price: i64::arbitrary(g),
+            conf: u64::arbitrary(g),
+            exponent: i32::arbitrary(g),
+            publish_time,
+            prev_publish_time: publish_time.saturating_sub(i64::arbitrary(g)),
+            ema_price: i64::arbitrary(g),
+            ema_conf: u64::arbitrary(g),
+        }
+    }
+}
+
+/// Message format for sending Twap data via the accumulator program
+#[repr(C)]
+#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
+pub struct TwapMessage {
+    pub id:                [u8; 32],
+    pub cumulative_price:  i128,
+    pub cumulative_conf:   u128,
+    pub num_down_slots:    u64,
+    pub exponent:          i32,
+    pub publish_time:      i64,
+    pub prev_publish_time: i64,
+    pub publish_slot:      u64,
+}
+
+#[cfg(feature = "quickcheck")]
+impl Arbitrary for TwapMessage {
+    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
+        let mut id = [0u8; 32];
+        for item in &mut id {
+            *item = u8::arbitrary(g);
+        }
+
+        let publish_time = i64::arbitrary(g);
+
+        TwapMessage {
+            id,
+            cumulative_price: i128::arbitrary(g),
+            cumulative_conf: u128::arbitrary(g),
+            num_down_slots: u64::arbitrary(g),
+            exponent: i32::arbitrary(g),
+            publish_time,
+            prev_publish_time: publish_time.saturating_sub(i64::arbitrary(g)),
+            publish_slot: u64::arbitrary(g),
+        }
+    }
+}

+ 12 - 3
pythnet/pythnet_sdk/src/wire.rs

@@ -45,6 +45,7 @@ pub mod v1 {
             Serialize,
         },
     };
+    pub const PYTHNET_ACCUMULATOR_UPDATE_MAGIC: &[u8; 4] = b"PNAU";
 
     // Transfer Format.
     // --------------------------------------------------------------------------------
@@ -62,7 +63,7 @@ pub mod v1 {
     impl AccumulatorUpdateData {
         pub fn new(proof: Proof) -> Self {
             Self {
-                magic: *b"PNAU",
+                magic: *PYTHNET_ACCUMULATOR_UPDATE_MAGIC,
                 major_version: 1,
                 minor_version: 0,
                 trailing: vec![],
@@ -72,7 +73,10 @@ pub mod v1 {
 
         pub fn try_from_slice(bytes: &[u8]) -> Result<Self, Error> {
             let message = from_slice::<byteorder::BE, Self>(bytes).unwrap();
-            require!(&message.magic[..] == b"PNAU", Error::InvalidMagic);
+            require!(
+                &message.magic[..] == PYTHNET_ACCUMULATOR_UPDATE_MAGIC,
+                Error::InvalidMagic
+            );
             require!(message.major_version == 1, Error::InvalidVersion);
             require!(message.minor_version == 0, Error::InvalidVersion);
             Ok(message)
@@ -102,10 +106,15 @@ pub mod v1 {
         pub payload: WormholePayload,
     }
 
+    pub const ACCUMULATOR_UPDATE_WORMHOLE_VERIFICATION_MAGIC: &[u8; 4] = b"AUWV";
+
     impl WormholeMessage {
         pub fn try_from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, Error> {
             let message = from_slice::<byteorder::BE, Self>(bytes.as_ref()).unwrap();
-            require!(&message.magic[..] == b"AUWV", Error::InvalidMagic);
+            require!(
+                &message.magic[..] == ACCUMULATOR_UPDATE_WORMHOLE_VERIFICATION_MAGIC,
+                Error::InvalidMagic
+            );
             Ok(message)
         }
     }