Parcourir la source

p2w attester contract use p2w-sdk (#68)

* Make solana pyth2wormhole contract to use the sdk
Ali Behjati il y a 3 ans
Parent
commit
f72caf0b56

+ 18 - 18
solana/Dockerfile → Dockerfile.solana

@@ -44,24 +44,24 @@ ENV EMITTER_ADDRESS="11111111111111111111111111111115"
 ENV BRIDGE_ADDRESS="Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o"
 
 # Build Wormhole Solana programs
-RUN --mount=type=cache,target=bridge/target \
-    --mount=type=cache,target=modules/token_bridge/target \
-    --mount=type=cache,target=modules/nft_bridge/target \
-    --mount=type=cache,target=pyth2wormhole/target \
-    --mount=type=cache,target=migration/target \
-    cargo build-bpf --manifest-path "bridge/program/Cargo.toml" -- --locked && \
-    cargo build-bpf --manifest-path "bridge/cpi_poster/Cargo.toml" -- --locked && \
-    cargo build-bpf --manifest-path "modules/token_bridge/program/Cargo.toml" -- --locked && \
-    cargo build-bpf --manifest-path "pyth2wormhole/program/Cargo.toml" -- --locked && \
-    cargo build-bpf --manifest-path "modules/nft_bridge/program/Cargo.toml" -- --locked && \
-    cargo build-bpf --manifest-path "migration/Cargo.toml" -- --locked && \
-    cp bridge/target/deploy/bridge.so /opt/solana/deps/bridge.so && \
-    cp bridge/target/deploy/cpi_poster.so /opt/solana/deps/cpi_poster.so && \
-    cp migration/target/deploy/wormhole_migration.so /opt/solana/deps/wormhole_migration.so && \
-    cp modules/token_bridge/target/deploy/token_bridge.so /opt/solana/deps/token_bridge.so && \
-    cp modules/nft_bridge/target/deploy/nft_bridge.so /opt/solana/deps/nft_bridge.so && \
-    cp modules/token_bridge/token-metadata/spl_token_metadata.so /opt/solana/deps/spl_token_metadata.so && \
-    cp pyth2wormhole/target/deploy/pyth2wormhole.so /opt/solana/deps/pyth2wormhole.so
+RUN --mount=type=cache,target=solana/bridge/target \
+    --mount=type=cache,target=solana/modules/token_bridge/target \
+    --mount=type=cache,target=solana/modules/nft_bridge/target \
+    --mount=type=cache,target=solana/pyth2wormhole/target \
+    --mount=type=cache,target=solana/migration/target \
+    cargo build-bpf --manifest-path "solana/bridge/program/Cargo.toml" -- --locked && \
+    cargo build-bpf --manifest-path "solana/bridge/cpi_poster/Cargo.toml" -- --locked && \
+    cargo build-bpf --manifest-path "solana/modules/token_bridge/program/Cargo.toml" -- --locked && \
+    cargo build-bpf --manifest-path "solana/pyth2wormhole/program/Cargo.toml" -- --locked && \
+    cargo build-bpf --manifest-path "solana/modules/nft_bridge/program/Cargo.toml" -- --locked && \
+    cargo build-bpf --manifest-path "solana/migration/Cargo.toml" -- --locked && \
+    cp solana/bridge/target/deploy/bridge.so /opt/solana/deps/bridge.so && \
+    cp solana/bridge/target/deploy/cpi_poster.so /opt/solana/deps/cpi_poster.so && \
+    cp solana/migration/target/deploy/wormhole_migration.so /opt/solana/deps/wormhole_migration.so && \
+    cp solana/modules/token_bridge/target/deploy/token_bridge.so /opt/solana/deps/token_bridge.so && \
+    cp solana/modules/nft_bridge/target/deploy/nft_bridge.so /opt/solana/deps/nft_bridge.so && \
+    cp solana/modules/token_bridge/token-metadata/spl_token_metadata.so /opt/solana/deps/spl_token_metadata.so && \
+    cp solana/pyth2wormhole/target/deploy/pyth2wormhole.so /opt/solana/deps/pyth2wormhole.so
 
 # Build the Pyth Solana program
 WORKDIR $PYTH_DIR/pyth-client/program

+ 2 - 2
Tiltfile

@@ -207,8 +207,8 @@ docker_build(
 
 docker_build(
     ref = "solana-contract",
-    context = "solana",
-    dockerfile = "solana/Dockerfile",
+    context = ".",
+    dockerfile = "Dockerfile.solana",
 )
 
 # solana local devnet

+ 36 - 21
solana/pyth2wormhole/Cargo.lock

@@ -392,19 +392,6 @@ dependencies = [
  "syn 1.0.73",
 ]
 
-[[package]]
-name = "console"
-version = "0.14.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3993e6445baa160675931ec041a5e03ca84b9c6e32a056150d3aa2bdda0a1f45"
-dependencies = [
- "encode_unicode",
- "lazy_static",
- "libc",
- "terminal_size",
- "winapi",
-]
-
 [[package]]
 name = "console"
 version = "0.15.0"
@@ -603,7 +590,7 @@ version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "61579ada4ec0c6031cfac3f86fdba0d195a7ebeb5e36693bd53cb5999a25beeb"
 dependencies = [
- "console 0.15.0",
+ "console",
  "lazy_static",
  "tempfile",
  "zeroize",
@@ -1223,7 +1210,7 @@ version = "0.16.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2d207dc617c7a380ab07ff572a6e52fa202a2a8f355860ac9c38e23f8196be1b"
 dependencies = [
- "console 0.14.1",
+ "console",
  "lazy_static",
  "number_prefix",
  "regex",
@@ -1607,6 +1594,16 @@ dependencies = [
  "syn 1.0.73",
 ]
 
+[[package]]
+name = "p2w-sdk"
+version = "0.1.0"
+dependencies = [
+ "pyth-client 0.5.0",
+ "serde",
+ "solana-program",
+ "solitaire",
+]
+
 [[package]]
 name = "parking_lot"
 version = "0.11.1"
@@ -1759,12 +1756,29 @@ version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "44de48029c54ec1ca570786b5baeb906b0fc2409c8e0145585e287ee7a526c72"
 
+[[package]]
+name = "pyth-client"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f779e98b8c8016d0c1409247a204bd4fcdea8b67ceeef545f04e324d66c49e52"
+dependencies = [
+ "borsh",
+ "borsh-derive",
+ "bytemuck",
+ "num-derive",
+ "num-traits",
+ "serde",
+ "solana-program",
+ "thiserror",
+]
+
 [[package]]
 name = "pyth2wormhole"
 version = "0.1.0"
 dependencies = [
  "borsh",
- "pyth-client",
+ "p2w-sdk",
+ "pyth-client 0.2.2",
  "rocksalt",
  "serde",
  "serde_derive",
@@ -1783,6 +1797,7 @@ dependencies = [
  "clap 3.0.0-beta.2",
  "env_logger 0.8.4",
  "log",
+ "p2w-sdk",
  "pyth2wormhole",
  "serde",
  "serde_yaml",
@@ -2136,9 +2151,9 @@ checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012"
 
 [[package]]
 name = "serde"
-version = "1.0.133"
+version = "1.0.136"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97565067517b60e2d1ea8b268e59ce036de907ac523ad83a0475da04e818989a"
+checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
 dependencies = [
  "serde_derive",
 ]
@@ -2154,9 +2169,9 @@ dependencies = [
 
 [[package]]
 name = "serde_derive"
-version = "1.0.133"
+version = "1.0.136"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed201699328568d8d08208fdd080e3ff594e6c422e438b6705905da01005d537"
+checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
 dependencies = [
  "proc-macro2 1.0.27",
  "quote 1.0.9",
@@ -2650,7 +2665,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a03587d5bf5f7bc9302385f9ada8412662cdb93b5e3d40fee2a02553a932277c"
 dependencies = [
  "base32",
- "console 0.15.0",
+ "console",
  "dialoguer",
  "hidapi",
  "log",

+ 1 - 0
solana/pyth2wormhole/client/Cargo.toml

@@ -15,6 +15,7 @@ env_logger = "0.8.4"
 log = "0.4.14"
 wormhole-bridge-solana = {path = "../../bridge/program"}
 pyth2wormhole = {path = "../program"}
+p2w-sdk = { path = "../../../third_party/pyth/p2w-sdk/rust" }
 serde = "1"
 serde_yaml = "0.8"
 shellexpand = "2.1.0"

+ 3 - 5
solana/pyth2wormhole/client/src/main.rs

@@ -71,18 +71,16 @@ use bridge::{
 };
 
 use pyth2wormhole::{
-    attest::{
-        P2WEmitter,
-        P2W_MAX_BATCH_SIZE,
-    },
+    attest::P2W_MAX_BATCH_SIZE,
     config::P2WConfigAccount,
     initialize::InitializeAccounts,
     set_config::SetConfigAccounts,
-    types::PriceAttestation,
     AttestData,
     Pyth2WormholeConfig,
 };
 
+use p2w_sdk::P2WEmitter;
+
 use crate::attestation_cfg::AttestationConfig;
 
 pub type ErrBox = Box<dyn std::error::Error>;

+ 1 - 0
solana/pyth2wormhole/program/Cargo.toml

@@ -22,6 +22,7 @@ rocksalt = { path = "../../solitaire/rocksalt" }
 solana-program = "=1.9.4"
 borsh = "=0.9.1"
 pyth-client = "0.2.2"
+p2w-sdk = { path = "../../../third_party/pyth/p2w-sdk/rust" }
 serde = { version = "1", optional = true}
 serde_derive = { version = "1", optional = true}
 serde_json = { version = "1", optional = true}

+ 17 - 11
solana/pyth2wormhole/program/src/attest.rs

@@ -1,10 +1,4 @@
-use crate::{
-    config::P2WConfigAccount,
-    types::{
-        batch_serialize,
-        PriceAttestation,
-    },
-};
+use crate::config::P2WConfigAccount;
 use borsh::{
     BorshDeserialize,
     BorshSerialize,
@@ -24,6 +18,12 @@ use solana_program::{
     rent::Rent,
 };
 
+use p2w_sdk::{
+    BatchPriceAttestation,
+    PriceAttestation,
+    P2WEmitter,
+};
+
 use bridge::{
     accounts::BridgeData,
     types::ConsistencyLevel,
@@ -50,8 +50,6 @@ use solitaire::{
     ToInstruction,
 };
 
-pub type P2WEmitter<'b> = Derive<Info<'b>, "p2w-emitter">;
-
 /// Important: must be manually maintained until native Solitaire
 /// variable len vector support.
 ///
@@ -209,7 +207,11 @@ pub fn attest(ctx: &ExecutionContext, accs: &mut Attest, data: AttestData) -> So
             price.key.clone(),
             accs.clock.unix_timestamp,
             &*price.try_borrow_data()?,
-        )?;
+        )
+        .map_err(|e| {
+            trace!(e.to_string());
+            ProgramError::InvalidAccountData
+        })?;
 
         // The following check is crucial against poorly ordered
         // account inputs, e.g. [Some(prod1), Some(price1),
@@ -230,6 +232,10 @@ pub fn attest(ctx: &ExecutionContext, accs: &mut Attest, data: AttestData) -> So
         attestations.push(attestation);
     }
 
+    let batch_attestation = BatchPriceAttestation {
+        price_attestations: attestations,
+    };
+
     trace!("Attestations successfully created");
 
     let bridge_config = BridgeData::try_from_slice(&accs.wh_bridge.try_borrow_mut_data()?)?.config;
@@ -247,7 +253,7 @@ pub fn attest(ctx: &ExecutionContext, accs: &mut Attest, data: AttestData) -> So
         bridge::instruction::Instruction::PostMessage,
         PostMessageData {
             nonce: 0, // Superseded by the sequence number
-            payload: batch_serialize(attestations.as_slice().iter()).map_err(|e| {
+            payload: batch_attestation.serialize().map_err(|e| {
                 trace!(e.to_string());
                 ProgramError::InvalidAccountData
             })?,

+ 0 - 1
solana/pyth2wormhole/program/src/lib.rs

@@ -3,7 +3,6 @@ pub mod attest;
 pub mod config;
 pub mod initialize;
 pub mod set_config;
-pub mod types;
 
 use solitaire::solitaire;
 

+ 0 - 653
solana/pyth2wormhole/program/src/types/mod.rs

@@ -1,653 +0,0 @@
-//! Constants and values common to every p2w custom-serialized message.
-//!
-//! The format makes no attempt to provide human-readable symbol names
-//! in favor of explicit product/price Solana account addresses
-//! (IDs). This choice was made to disambiguate any symbols with
-//! similar human-readable names and provide a failsafe for some of
-//! the probable adversarial scenarios.
-
-pub mod pyth_extensions;
-
-use std::{
-    borrow::Borrow,
-    convert::{
-        TryFrom,
-        TryInto,
-    },
-    io::Read,
-    iter::Iterator,
-    mem,
-};
-
-use borsh::BorshSerialize;
-use pyth_client::{
-    AccountType,
-    CorpAction,
-    Ema,
-    Price,
-    PriceStatus,
-    PriceType,
-};
-use solana_program::{
-    clock::UnixTimestamp,
-    program_error::ProgramError,
-    pubkey::Pubkey,
-};
-use solitaire::{
-    trace,
-    ErrBox,
-    Result as SoliResult,
-    SolitaireError,
-};
-
-use self::pyth_extensions::{
-    P2WCorpAction,
-    P2WEma,
-    P2WPriceStatus,
-    P2WPriceType,
-};
-
-/// Precedes every message implementing the p2w serialization format
-pub const P2W_MAGIC: &'static [u8] = b"P2WH";
-
-/// Format version used and understood by this codebase
-pub const P2W_FORMAT_VERSION: u16 = 2;
-
-pub const PUBKEY_LEN: usize = 32;
-
-/// Decides the format of following bytes
-#[repr(u8)]
-pub enum PayloadId {
-    PriceAttestation = 1, // Not in use, currently batch attestations imply PriceAttestation messages inside
-    PriceBatchAttestation,
-}
-
-// On-chain data types
-
-/// The main attestation data type.
-///
-/// Important: For maximum security, *both* product_id and price_id
-/// should be used as storage keys for known attestations in target
-/// chain logic.
-#[derive(Clone, Default, Debug, Eq, PartialEq)]
-#[cfg_attr(
-    feature = "wasm",
-    derive(serde_derive::Serialize, serde_derive::Deserialize)
-)]
-pub struct PriceAttestation {
-    pub product_id: Pubkey,
-    pub price_id: Pubkey,
-    pub price_type: P2WPriceType,
-    pub price: i64,
-    pub expo: i32,
-    pub twap: P2WEma,
-    pub twac: P2WEma,
-    pub confidence_interval: u64,
-    pub status: P2WPriceStatus,
-    pub corp_act: P2WCorpAction,
-    pub timestamp: UnixTimestamp,
-}
-
-/// Turn a bunch of attestations into a combined payload.
-///
-/// Batches assume constant-size attestations within a single batch.
-pub fn batch_serialize(
-    attestations: impl Iterator<Item = impl Borrow<PriceAttestation>>,
-) -> Result<Vec<u8>, ErrBox> {
-    // magic
-    let mut buf = P2W_MAGIC.to_vec();
-
-    // version
-    buf.extend_from_slice(&P2W_FORMAT_VERSION.to_be_bytes()[..]);
-
-    // payload_id
-    buf.push(PayloadId::PriceBatchAttestation as u8);
-
-    let collected: Vec<_> = attestations.collect();
-
-    // n_attestations
-    buf.extend_from_slice(&(collected.len() as u16).to_be_bytes()[..]);
-
-    let mut attestation_size = 0; // Will be determined as we serialize attestations
-    let mut serialized_attestations = Vec::with_capacity(collected.len());
-    for (idx, a) in collected.iter().enumerate() {
-        // Learn the current attestation's size
-        let serialized = PriceAttestation::serialize(a.borrow());
-        let a_len = serialized.len();
-
-        // Verify it's the same as the first one we saw for the batch, assign if we're first.
-        if attestation_size > 0 {
-            if a_len != attestation_size {
-                return Err(format!(
-                    "attestation {} serializes to {} bytes, {} expected",
-                    idx + 1,
-                    a_len,
-                    attestation_size
-                )
-                .into());
-            }
-        } else {
-            attestation_size = a_len;
-        }
-
-        serialized_attestations.push(serialized);
-    }
-
-    // attestation_size
-    buf.extend_from_slice(&(attestation_size as u16).to_be_bytes()[..]);
-
-    for mut s in serialized_attestations.into_iter() {
-        buf.append(&mut s)
-    }
-
-    Ok(buf)
-}
-
-/// Undo `batch_serialize`
-pub fn batch_deserialize(mut bytes: impl Read) -> Result<Vec<PriceAttestation>, ErrBox> {
-    let mut magic_vec = vec![0u8; P2W_MAGIC.len()];
-    bytes.read_exact(magic_vec.as_mut_slice())?;
-
-    if magic_vec.as_slice() != P2W_MAGIC {
-        return Err(format!(
-            "Invalid magic {:02X?}, expected {:02X?}",
-            magic_vec, P2W_MAGIC,
-        )
-        .into());
-    }
-
-    let mut version_vec = vec![0u8; mem::size_of_val(&P2W_FORMAT_VERSION)];
-    bytes.read_exact(version_vec.as_mut_slice())?;
-    let version = u16::from_be_bytes(version_vec.as_slice().try_into()?);
-
-    if version != P2W_FORMAT_VERSION {
-        return Err(format!(
-            "Unsupported format version {}, expected {}",
-            version, P2W_FORMAT_VERSION
-        )
-        .into());
-    }
-
-    let mut payload_id_vec = vec![0u8; mem::size_of::<PayloadId>()];
-    bytes.read_exact(payload_id_vec.as_mut_slice())?;
-
-    if payload_id_vec[0] != PayloadId::PriceBatchAttestation as u8 {
-        return Err(format!(
-            "Invalid Payload ID {}, expected {}",
-            payload_id_vec[0],
-            PayloadId::PriceBatchAttestation as u8,
-        )
-        .into());
-    }
-
-    let mut batch_len_vec = vec![0u8; 2];
-    bytes.read_exact(batch_len_vec.as_mut_slice())?;
-    let batch_len = u16::from_be_bytes(batch_len_vec.as_slice().try_into()?);
-
-    let mut attestation_size_vec = vec![0u8; 2];
-    bytes.read_exact(attestation_size_vec.as_mut_slice())?;
-    let attestation_size = u16::from_be_bytes(attestation_size_vec.as_slice().try_into()?);
-
-    let mut ret = Vec::with_capacity(batch_len as usize);
-
-    for i in 0..batch_len {
-        let mut attestation_buf = vec![0u8; attestation_size as usize];
-        bytes.read_exact(attestation_buf.as_mut_slice())?;
-
-        dbg!(&attestation_buf.len());
-
-        match PriceAttestation::deserialize(attestation_buf.as_slice()) {
-            Ok(attestation) => ret.push(attestation),
-            Err(e) => return Err(format!("PriceAttestation {}/{}: {}", i + 1, batch_len, e).into()),
-        }
-    }
-
-    Ok(ret)
-}
-
-impl PriceAttestation {
-    pub fn from_pyth_price_bytes(
-        price_id: Pubkey,
-        timestamp: UnixTimestamp,
-        value: &[u8],
-    ) -> Result<Self, SolitaireError> {
-        let price = parse_pyth_price(value)?;
-
-        Ok(PriceAttestation {
-            product_id: Pubkey::new(&price.prod.val[..]),
-            price_id,
-            price_type: (&price.ptype).into(),
-            price: price.agg.price,
-            twap: (&price.twap).into(),
-            twac: (&price.twac).into(),
-            expo: price.expo,
-            confidence_interval: price.agg.conf,
-            status: (&price.agg.status).into(),
-            corp_act: (&price.agg.corp_act).into(),
-            timestamp: timestamp,
-        })
-    }
-
-    /// Serialize this attestation according to the Pyth-over-wormhole serialization format
-    pub fn serialize(&self) -> Vec<u8> {
-        // A nifty trick to get us yelled at if we forget to serialize a field
-        #[deny(warnings)]
-        let PriceAttestation {
-            product_id,
-            price_id,
-            price_type,
-            price,
-            expo,
-            twap,
-            twac,
-            confidence_interval,
-            status,
-            corp_act,
-            timestamp,
-        } = self;
-
-        // magic
-        let mut buf = P2W_MAGIC.to_vec();
-
-        // version
-        buf.extend_from_slice(&P2W_FORMAT_VERSION.to_be_bytes()[..]);
-
-        // payload_id
-        buf.push(PayloadId::PriceAttestation as u8);
-
-        // product_id
-        buf.extend_from_slice(&product_id.to_bytes()[..]);
-
-        // price_id
-        buf.extend_from_slice(&price_id.to_bytes()[..]);
-
-        // price_type
-        buf.push(price_type.clone() as u8);
-
-        // price
-        buf.extend_from_slice(&price.to_be_bytes()[..]);
-
-        // exponent
-        buf.extend_from_slice(&expo.to_be_bytes()[..]);
-
-        // twap
-        buf.append(&mut twap.serialize());
-
-        // twac
-        buf.append(&mut twac.serialize());
-
-        // confidence_interval
-        buf.extend_from_slice(&confidence_interval.to_be_bytes()[..]);
-
-        // status
-        buf.push(status.clone() as u8);
-
-        // corp_act
-        buf.push(corp_act.clone() as u8);
-
-        // timestamp
-        buf.extend_from_slice(&timestamp.to_be_bytes()[..]);
-
-        buf
-    }
-    pub fn deserialize(mut bytes: impl Read) -> Result<Self, ErrBox> {
-        use P2WCorpAction::*;
-        use P2WPriceStatus::*;
-        use P2WPriceType::*;
-
-        let mut magic_vec = vec![0u8; P2W_MAGIC.len()];
-
-        bytes.read_exact(magic_vec.as_mut_slice())?;
-
-        if magic_vec.as_slice() != P2W_MAGIC {
-            return Err(format!(
-                "Invalid magic {:02X?}, expected {:02X?}",
-                magic_vec, P2W_MAGIC,
-            )
-            .into());
-        }
-
-        let mut version_vec = vec![0u8; mem::size_of_val(&P2W_FORMAT_VERSION)];
-        bytes.read_exact(version_vec.as_mut_slice())?;
-        let version = u16::from_be_bytes(version_vec.as_slice().try_into()?);
-
-        if version != P2W_FORMAT_VERSION {
-            return Err(format!(
-                "Unsupported format version {}, expected {}",
-                version, P2W_FORMAT_VERSION
-            )
-            .into());
-        }
-
-        let mut payload_id_vec = vec![0u8; mem::size_of::<PayloadId>()];
-        bytes.read_exact(payload_id_vec.as_mut_slice())?;
-
-        if PayloadId::PriceAttestation as u8 != payload_id_vec[0] {
-            return Err(format!(
-                "Invalid Payload ID {}, expected {}",
-                payload_id_vec[0],
-                PayloadId::PriceAttestation as u8,
-            )
-            .into());
-        }
-
-        let mut product_id_vec = vec![0u8; PUBKEY_LEN];
-        bytes.read_exact(product_id_vec.as_mut_slice())?;
-        let product_id = Pubkey::new(product_id_vec.as_slice());
-
-        let mut price_id_vec = vec![0u8; PUBKEY_LEN];
-        bytes.read_exact(price_id_vec.as_mut_slice())?;
-        let price_id = Pubkey::new(price_id_vec.as_slice());
-
-        let mut price_type_vec = vec![0u8; mem::size_of::<P2WPriceType>()];
-        bytes.read_exact(price_type_vec.as_mut_slice())?;
-        let price_type = match price_type_vec[0] {
-            a if a == Price as u8 => Price,
-            a if a == P2WPriceType::Unknown as u8 => P2WPriceType::Unknown,
-            other => {
-                return Err(format!("Invalid price_type value {}", other).into());
-            }
-        };
-
-        let mut price_vec = vec![0u8; mem::size_of::<i64>()];
-        bytes.read_exact(price_vec.as_mut_slice())?;
-        let price = i64::from_be_bytes(price_vec.as_slice().try_into()?);
-
-        let mut expo_vec = vec![0u8; mem::size_of::<i32>()];
-        bytes.read_exact(expo_vec.as_mut_slice())?;
-        let expo = i32::from_be_bytes(expo_vec.as_slice().try_into()?);
-
-        let twap = P2WEma::deserialize(&mut bytes)?;
-        let twac = P2WEma::deserialize(&mut bytes)?;
-
-        println!("twac OK");
-        let mut confidence_interval_vec = vec![0u8; mem::size_of::<u64>()];
-        bytes.read_exact(confidence_interval_vec.as_mut_slice())?;
-        let confidence_interval =
-            u64::from_be_bytes(confidence_interval_vec.as_slice().try_into()?);
-
-        let mut status_vec = vec![0u8; mem::size_of::<P2WPriceType>()];
-        bytes.read_exact(status_vec.as_mut_slice())?;
-        let status = match status_vec[0] {
-            a if a == P2WPriceStatus::Unknown as u8 => P2WPriceStatus::Unknown,
-            a if a == Trading as u8 => Trading,
-            a if a == Halted as u8 => Halted,
-            a if a == Auction as u8 => Auction,
-            other => {
-                return Err(format!("Invalid status value {}", other).into());
-            }
-        };
-
-        let mut corp_act_vec = vec![0u8; mem::size_of::<P2WPriceType>()];
-        bytes.read_exact(corp_act_vec.as_mut_slice())?;
-        let corp_act = match corp_act_vec[0] {
-            a if a == NoCorpAct as u8 => NoCorpAct,
-            other => {
-                return Err(format!("Invalid corp_act value {}", other).into());
-            }
-        };
-
-        let mut timestamp_vec = vec![0u8; mem::size_of::<UnixTimestamp>()];
-        bytes.read_exact(timestamp_vec.as_mut_slice())?;
-        let timestamp = UnixTimestamp::from_be_bytes(timestamp_vec.as_slice().try_into()?);
-
-        Ok(Self {
-            product_id,
-            price_id,
-            price_type,
-            price,
-            expo,
-            twap,
-            twac,
-            confidence_interval,
-            status,
-            corp_act,
-            timestamp,
-        })
-    }
-}
-
-/// Deserializes Price from raw bytes, sanity-check.
-fn parse_pyth_price(price_data: &[u8]) -> SoliResult<&Price> {
-    if price_data.len() != mem::size_of::<Price>() {
-        trace!(&format!(
-            "parse_pyth_price: buffer length mismatch ({} expected, got {})",
-            mem::size_of::<Price>(),
-            price_data.len()
-        ));
-        return Err(ProgramError::InvalidAccountData.into());
-    }
-    let price_account = pyth_client::cast::<Price>(price_data);
-
-    if price_account.atype != AccountType::Price as u32 {
-        trace!(&format!(
-            "parse_pyth_price: AccountType mismatch ({} expected, got {})",
-            mem::size_of::<Price>(),
-            price_data.len()
-        ));
-        return Err(ProgramError::InvalidAccountData.into());
-    }
-
-    Ok(price_account)
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use pyth_client::{
-        AccKey,
-        AccountType,
-        PriceComp,
-        PriceInfo,
-    };
-
-    macro_rules! empty_acckey {
-        () => {
-            AccKey { val: [0u8; 32] }
-        };
-    }
-
-    macro_rules! empty_priceinfo {
-        () => {
-            PriceInfo {
-                price: 0,
-                conf: 0,
-                status: PriceStatus::Unknown,
-                corp_act: CorpAction::NoCorpAct,
-                pub_slot: 0,
-            }
-        };
-    }
-
-    macro_rules! empty_pricecomp {
-        () => {
-            PriceComp {
-                publisher: empty_acckey!(),
-                agg: empty_priceinfo!(),
-                latest: empty_priceinfo!(),
-            }
-        };
-    }
-
-    macro_rules! empty_ema {
-        () => {
-            (&P2WEma::default()).into()
-        };
-    }
-
-    macro_rules! empty_price {
-        () => {
-            Price {
-                magic: pyth_client::MAGIC,
-                ver: pyth_client::VERSION,
-                atype: AccountType::Price as u32,
-                size: 0,
-                ptype: PriceType::Price,
-                expo: 0,
-                num: 0,
-                num_qt: 0,
-                last_slot: 0,
-                valid_slot: 0,
-                drv1: 0,
-                drv2: 0,
-                drv3: 0,
-                twap: empty_ema!(),
-                twac: empty_ema!(),
-                prod: empty_acckey!(),
-                next: empty_acckey!(),
-                prev_slot: 0,  // valid slot of previous update
-                prev_price: 0, // aggregate price of previous update
-                prev_conf: 0,  // confidence interval of previous update
-                agg: empty_priceinfo!(),
-                // A nice macro might come in handy if this gets annoying
-                comp: [
-                    empty_pricecomp!(),
-                    empty_pricecomp!(),
-                    empty_pricecomp!(),
-                    empty_pricecomp!(),
-                    empty_pricecomp!(),
-                    empty_pricecomp!(),
-                    empty_pricecomp!(),
-                    empty_pricecomp!(),
-                    empty_pricecomp!(),
-                    empty_pricecomp!(),
-                    empty_pricecomp!(),
-                    empty_pricecomp!(),
-                    empty_pricecomp!(),
-                    empty_pricecomp!(),
-                    empty_pricecomp!(),
-                    empty_pricecomp!(),
-                    empty_pricecomp!(),
-                    empty_pricecomp!(),
-                    empty_pricecomp!(),
-                    empty_pricecomp!(),
-                    empty_pricecomp!(),
-                    empty_pricecomp!(),
-                    empty_pricecomp!(),
-                    empty_pricecomp!(),
-                    empty_pricecomp!(),
-                    empty_pricecomp!(),
-                    empty_pricecomp!(),
-                    empty_pricecomp!(),
-                    empty_pricecomp!(),
-                    empty_pricecomp!(),
-                    empty_pricecomp!(),
-                    empty_pricecomp!(),
-                ],
-            }
-        };
-    }
-
-    fn mock_attestation(prod: Option<[u8; 32]>, price: Option<[u8; 32]>) -> PriceAttestation {
-        let product_id_bytes = prod.unwrap_or([21u8; 32]);
-        let price_id_bytes = prod.unwrap_or([222u8; 32]);
-        PriceAttestation {
-            product_id: Pubkey::new_from_array(product_id_bytes),
-            price_id: Pubkey::new_from_array(price_id_bytes),
-            price: (0xdeadbeefdeadbabe as u64) as i64,
-            price_type: P2WPriceType::Price,
-            twap: P2WEma {
-                val: -42,
-                numer: 15,
-                denom: 37,
-            },
-            twac: P2WEma {
-                val: 42,
-                numer: 1111,
-                denom: 2222,
-            },
-            expo: -3,
-            status: P2WPriceStatus::Trading,
-            confidence_interval: 101,
-            corp_act: P2WCorpAction::NoCorpAct,
-            timestamp: 123456789i64,
-        }
-    }
-
-    #[test]
-    fn test_parse_pyth_price_wrong_size_slices() {
-        assert!(parse_pyth_price(&[]).is_err());
-        assert!(parse_pyth_price(vec![0u8; 1].as_slice()).is_err());
-    }
-
-    #[test]
-    fn test_parse_pyth_price() -> SoliResult<()> {
-        let price = Price {
-            expo: 5,
-            agg: PriceInfo {
-                price: 42,
-                ..empty_priceinfo!()
-            },
-            ..empty_price!()
-        };
-        let price_vec = vec![price];
-
-        // use the C repr to mock pyth's format
-        let (_, bytes, _) = unsafe { price_vec.as_slice().align_to::<u8>() };
-
-        parse_pyth_price(bytes)?;
-        Ok(())
-    }
-
-    #[test]
-    fn test_attestation_serde() -> Result<(), ErrBox> {
-        let product_id_bytes = [21u8; 32];
-        let price_id_bytes = [222u8; 32];
-        let attestation: PriceAttestation =
-            mock_attestation(Some(product_id_bytes), Some(price_id_bytes));
-
-        println!("Hex product_id: {:02X?}", &product_id_bytes);
-        println!("Hex price_id: {:02X?}", &price_id_bytes);
-
-        println!("Regular: {:#?}", &attestation);
-        println!("Hex: {:#02X?}", &attestation);
-        let bytes = attestation.serialize();
-        println!("Hex Bytes: {:02X?}", bytes);
-
-        assert_eq!(
-            PriceAttestation::deserialize(bytes.as_slice())?,
-            attestation
-        );
-        Ok(())
-    }
-
-    #[test]
-    fn test_attestation_serde_wrong_size() -> Result<(), ErrBox> {
-        assert!(PriceAttestation::deserialize(&[][..]).is_err());
-        assert!(PriceAttestation::deserialize(vec![0u8; 1].as_slice()).is_err());
-        Ok(())
-    }
-
-    #[test]
-    fn test_batch_serde() -> Result<(), ErrBox> {
-        let attestations: Vec<_> = (0..65535)
-            .map(|i| mock_attestation(Some([(i % 256) as u8; 32]), None))
-            .collect();
-
-        let serialized = batch_serialize(attestations.iter())?;
-
-        let deserialized = batch_deserialize(serialized.as_slice())?;
-
-        assert_eq!(attestations, deserialized);
-
-        Ok(())
-    }
-
-    #[test]
-    fn test_batch_serde_wrong_size() -> Result<(), ErrBox> {
-        assert!(batch_deserialize(&[][..]).is_err());
-        assert!(batch_deserialize(vec![0u8; 1].as_slice()).is_err());
-
-        let attestations: Vec<_> = (0..20)
-            .map(|i| mock_attestation(Some([(i % 256) as u8; 32]), None))
-            .collect();
-
-        let serialized = batch_serialize(attestations.iter())?;
-
-        // Missing last byte in last attestation must be an error
-        let len = serialized.len();
-        assert!(batch_deserialize(&serialized.as_slice()[..len - 1]).is_err());
-
-        Ok(())
-    }
-}

+ 0 - 176
solana/pyth2wormhole/program/src/types/pyth_extensions.rs

@@ -1,176 +0,0 @@
-//! This module contains 1:1 (or close) copies of selected Pyth types
-//! with quick and dirty enhancements.
-
-use std::{
-    convert::TryInto,
-    io::Read,
-    mem,
-};
-
-use pyth_client::{
-    CorpAction,
-    Ema,
-    PriceStatus,
-    PriceType,
-};
-use solitaire::ErrBox;
-
-/// 1:1 Copy of pyth_client::PriceType with derived additional traits.
-#[derive(Clone, Debug, Eq, PartialEq)]
-#[cfg_attr(
-    feature = "wasm",
-    derive(serde_derive::Serialize, serde_derive::Deserialize)
-)]
-#[repr(u8)]
-pub enum P2WPriceType {
-    Unknown,
-    Price,
-}
-
-impl From<&PriceType> for P2WPriceType {
-    fn from(pt: &PriceType) -> Self {
-        match pt {
-            PriceType::Unknown => Self::Unknown,
-            PriceType::Price => Self::Price,
-        }
-    }
-}
-
-impl Default for P2WPriceType {
-    fn default() -> Self {
-        Self::Price
-    }
-}
-
-/// 1:1 Copy of pyth_client::PriceStatus with derived additional traits.
-#[derive(Clone, Debug, Eq, PartialEq)]
-#[cfg_attr(
-    feature = "wasm",
-    derive(serde_derive::Serialize, serde_derive::Deserialize)
-)]
-pub enum P2WPriceStatus {
-    Unknown,
-    Trading,
-    Halted,
-    Auction,
-}
-
-impl From<&PriceStatus> for P2WPriceStatus {
-    fn from(ps: &PriceStatus) -> Self {
-        match ps {
-            PriceStatus::Unknown => Self::Unknown,
-            PriceStatus::Trading => Self::Trading,
-            PriceStatus::Halted => Self::Halted,
-            PriceStatus::Auction => Self::Auction,
-        }
-    }
-}
-
-impl Default for P2WPriceStatus {
-    fn default() -> Self {
-        Self::Trading
-    }
-}
-
-/// 1:1 Copy of pyth_client::CorpAction with derived additional traits.
-#[derive(Clone, Debug, Eq, PartialEq)]
-#[cfg_attr(
-    feature = "wasm",
-    derive(serde_derive::Serialize, serde_derive::Deserialize)
-)]
-pub enum P2WCorpAction {
-    NoCorpAct,
-}
-
-impl Default for P2WCorpAction {
-    fn default() -> Self {
-        Self::NoCorpAct
-    }
-}
-
-impl From<&CorpAction> for P2WCorpAction {
-    fn from(ca: &CorpAction) -> Self {
-        match ca {
-            CorpAction::NoCorpAct => P2WCorpAction::NoCorpAct,
-        }
-    }
-}
-
-/// 1:1 Copy of pyth_client::Ema with all-pub fields.
-#[derive(Clone, Default, Debug, Eq, PartialEq)]
-#[cfg_attr(
-    feature = "wasm",
-    derive(serde_derive::Serialize, serde_derive::Deserialize)
-)]
-#[repr(C)]
-pub struct P2WEma {
-    pub val: i64,
-    pub numer: i64,
-    pub denom: i64,
-}
-
-/// CAUTION: This impl may panic and requires an unsafe cast
-impl From<&Ema> for P2WEma {
-    fn from(ema: &Ema) -> Self {
-        let our_size = mem::size_of::<P2WEma>();
-        let upstream_size = mem::size_of::<Ema>();
-        if our_size == upstream_size {
-            unsafe { std::mem::transmute_copy(ema) }
-        } else {
-            dbg!(our_size);
-            dbg!(upstream_size);
-            // Because of private upstream fields it's impossible to
-            // complain about type-level changes at compile-time
-            panic!("P2WEma sizeof mismatch")
-        }
-    }
-}
-
-/// CAUTION: This impl may panic and requires an unsafe cast
-impl Into<Ema> for &P2WEma {
-    fn into(self) -> Ema {
-        let our_size = mem::size_of::<P2WEma>();
-        let upstream_size = mem::size_of::<Ema>();
-        if our_size == upstream_size {
-            unsafe { std::mem::transmute_copy(self) }
-        } else {
-            dbg!(our_size);
-            dbg!(upstream_size);
-            // Because of private upstream fields it's impossible to
-            // complain about type-level changes at compile-time
-            panic!("P2WEma sizeof mismatch")
-        }
-    }
-}
-
-impl P2WEma {
-    pub fn serialize(&self) -> Vec<u8> {
-        let mut v = vec![];
-        // val
-        v.extend(&self.val.to_be_bytes()[..]);
-
-        // numer
-        v.extend(&self.numer.to_be_bytes()[..]);
-
-        // denom
-        v.extend(&self.denom.to_be_bytes()[..]);
-
-        v
-    }
-
-    pub fn deserialize(mut bytes: impl Read) -> Result<Self, ErrBox> {
-        let mut val_vec = vec![0u8; mem::size_of::<i64>()];
-        bytes.read_exact(val_vec.as_mut_slice())?;
-        let val = i64::from_be_bytes(val_vec.as_slice().try_into()?);
-
-        let mut numer_vec = vec![0u8; mem::size_of::<i64>()];
-        bytes.read_exact(numer_vec.as_mut_slice())?;
-        let numer = i64::from_be_bytes(numer_vec.as_slice().try_into()?);
-
-        let mut denom_vec = vec![0u8; mem::size_of::<i64>()];
-        bytes.read_exact(denom_vec.as_mut_slice())?;
-        let denom = i64::from_be_bytes(denom_vec.as_slice().try_into()?);
-
-        Ok(Self { val, numer, denom })
-    }
-}

+ 1 - 0
third_party/pyth/Dockerfile.p2w-attest

@@ -4,6 +4,7 @@ RUN apt-get install -y python3
 
 ADD third_party/pyth/pyth_utils.py /usr/src/pyth/pyth_utils.py
 ADD third_party/pyth/p2w_autoattest.py /usr/src/pyth/p2w_autoattest.py
+ADD third_party/pyth/p2w-sdk/rust /usr/src/third_party/pyth/p2w-sdk/rust
 
 RUN --mount=type=cache,target=/root/.cache \
     --mount=type=cache,target=target \