Explorar el Código

pyth2wormhole: implement wire format v3rev0

commit-id:c106d3b3
Stan Drozd hace 3 años
padre
commit
11f13cecab

+ 2 - 1
Dockerfile.wasm

@@ -43,4 +43,5 @@ RUN --mount=type=cache,target=/root/.cache \
 
 FROM scratch AS export
 
-COPY --from=build /usr/src/bridge/third_party/pyth/p2w-sdk/rust/bundler third_party/pyth/p2w-sdk/js/src/solana/p2w-core
+COPY --from=build /usr/src/bridge/third_party/pyth/p2w-sdk/rust/bundler third_party/pyth/p2w-sdk/js/src/solana/p2w-core/bundler
+COPY --from=build /usr/src/bridge/third_party/pyth/p2w-sdk/rust/nodejs third_party/pyth/p2w-sdk/js/src/solana/p2w-core/nodejs

+ 53 - 56
ethereum/contracts/pyth/Pyth.sol

@@ -37,7 +37,7 @@ contract Pyth is PythGetters, PythSetters, AbstractPyth {
 
             PythInternalStructs.PriceInfo memory latestPrice = latestPriceInfo(attestation.priceId);
 
-            if(attestation.timestamp > latestPrice.attestationTime) {
+            if(attestation.attestationTime > latestPrice.attestationTime) {
                 setLatestPriceInfo(attestation.priceId, newPriceInfo(attestation));
             }
         }
@@ -46,22 +46,21 @@ contract Pyth is PythGetters, PythSetters, AbstractPyth {
     }
 
     function newPriceInfo(PythInternalStructs.PriceAttestation memory pa) private view returns (PythInternalStructs.PriceInfo memory info) {
-        info.attestationTime = pa.timestamp;
+        info.attestationTime = pa.attestationTime;
+	info.publishTime = pa.publishTime;
         info.arrivalTime = block.timestamp;
         info.arrivalBlock = block.number;
         
         info.priceFeed.id = pa.priceId;
         info.priceFeed.price = pa.price;
-        info.priceFeed.conf = pa.confidenceInterval;
+        info.priceFeed.conf = pa.conf;
+        info.priceFeed.expo = pa.expo;
         info.priceFeed.status = PythStructs.PriceStatus(pa.status);
-        info.priceFeed.expo = pa.exponent;
-        info.priceFeed.emaPrice = pa.emaPrice.value;
-        info.priceFeed.emaConf = uint64(pa.emaConf.value);
+        info.priceFeed.emaPrice = pa.emaPrice;
+        info.priceFeed.emaConf = pa.emaConf;
         info.priceFeed.productId = pa.productId;
-
-        // These aren't sent in the wire format yet
-        info.priceFeed.numPublishers = 0;
-        info.priceFeed.maxNumPublishers = 0;
+        info.priceFeed.numPublishers = pa.numPublishers;
+        info.priceFeed.maxNumPublishers = pa.maxNumPublishers;
         return info;
     }
 
@@ -84,14 +83,38 @@ contract Pyth is PythGetters, PythSetters, AbstractPyth {
         index += 4;
         require(bpa.header.magic == 0x50325748, "invalid magic value");
 
-        bpa.header.version = encoded.toUint16(index);
+        bpa.header.versionMajor = encoded.toUint16(index);
+        index += 2;
+        require(bpa.header.versionMajor == 3, "invalid version major, expected 3");
+
+        bpa.header.versionMinor = encoded.toUint16(index);
         index += 2;
-        require(bpa.header.version == 2, "invalid version");
+        require(bpa.header.versionMinor >= 0, "invalid version minor, expected 0 or more");
+
+	bpa.header.hdrSize = encoded.toUint16(index);
+	index += 2;
+
+	// NOTE(2022-04-19): Currently, only payloadId comes after
+	// hdrSize. Future extra header fields must be read using a
+	// separate offset to respect hdrSize, i.e.:
+        // 
+	// uint hdrIndex = 0;
+	// bpa.header.payloadId = encoded.toUint8(index + hdrIndex);
+	// hdrIndex += 1;
+        //
+	// bpa.header.someNewField = encoded.toUint32(index + hdrIndex);
+	// hdrIndex += 4;
+	//
+	// // Skip remaining unknown header bytes
+	// index += bpa.header.hdrSize;
 
         bpa.header.payloadId = encoded.toUint8(index);
-        index += 1;
-        // Payload ID of 2 required for batch header
-        require(bpa.header.payloadId == 2, "invalid payload ID");
+
+	// Skip remaining unknown header bytes
+	index += bpa.header.hdrSize;
+
+        // Payload ID of 2 required for batch headerBa
+        require(bpa.header.payloadId == 2, "invalid payload ID, expected 2 for BatchPriceAttestation");
 
         // Parse the number of attestations
         bpa.nAttestations = encoded.toUint16(index);
@@ -111,76 +134,50 @@ contract Pyth is PythGetters, PythSetters, AbstractPyth {
 	    // for readability and easier debugging.
 	    uint attestationIndex = 0;
 
-            // Header
-            bpa.attestations[j].header.magic = encoded.toUint32(index + attestationIndex);
-            attestationIndex += 4;
-            require(bpa.attestations[j].header.magic == 0x50325748, "invalid magic value");
-
-            bpa.attestations[j].header.version = encoded.toUint16(index + attestationIndex);
-            attestationIndex += 2;
-            require(bpa.attestations[j].header.version == 2, "invalid version");
-
-            bpa.attestations[j].header.payloadId = encoded.toUint8(index + attestationIndex);
-            attestationIndex += 1;
-            // Payload ID of 1 required for individual attestation
-            require(bpa.attestations[j].header.payloadId == 1, "invalid payload ID");
-
             // Attestation
             bpa.attestations[j].productId = encoded.toBytes32(index + attestationIndex);
             attestationIndex += 32;
 
             bpa.attestations[j].priceId = encoded.toBytes32(index + attestationIndex);
             attestationIndex += 32;
-            bpa.attestations[j].priceType = encoded.toUint8(index + attestationIndex);
-            attestationIndex += 1;
 
             bpa.attestations[j].price = int64(encoded.toUint64(index + attestationIndex));
             attestationIndex += 8;
 
-            bpa.attestations[j].exponent = int32(encoded.toUint32(index + attestationIndex));
-            attestationIndex += 4;
-
-            bpa.attestations[j].emaPrice.value = int64(encoded.toUint64(index + attestationIndex));
-            attestationIndex += 8;
-            bpa.attestations[j].emaPrice.numerator = int64(encoded.toUint64(index + attestationIndex));
-            attestationIndex += 8;
-            bpa.attestations[j].emaPrice.denominator = int64(encoded.toUint64(index + attestationIndex));
+            bpa.attestations[j].conf = encoded.toUint64(index + attestationIndex);
             attestationIndex += 8;
 
-            bpa.attestations[j].emaConf.value = int64(encoded.toUint64(index + attestationIndex));
-            attestationIndex += 8;
-            bpa.attestations[j].emaConf.numerator = int64(encoded.toUint64(index + attestationIndex));
-            attestationIndex += 8;
-            bpa.attestations[j].emaConf.denominator = int64(encoded.toUint64(index + attestationIndex));
+            bpa.attestations[j].expo = int32(encoded.toUint32(index + attestationIndex));
+            attestationIndex += 4;
+
+            bpa.attestations[j].emaPrice = int64(encoded.toUint64(index + attestationIndex));
             attestationIndex += 8;
 
-            bpa.attestations[j].confidenceInterval = encoded.toUint64(index + attestationIndex);
+            bpa.attestations[j].emaConf = encoded.toUint64(index + attestationIndex);
             attestationIndex += 8;
 
             bpa.attestations[j].status = encoded.toUint8(index + attestationIndex);
             attestationIndex += 1;
-            bpa.attestations[j].corpAct = encoded.toUint8(index + attestationIndex);
-            attestationIndex += 1;
-
-            bpa.attestations[j].timestamp = encoded.toUint64(index + attestationIndex);
-            attestationIndex += 8;
 
-            bpa.attestations[j].num_publishers = encoded.toUint32(index + attestationIndex);
+            bpa.attestations[j].numPublishers = encoded.toUint32(index + attestationIndex);
             attestationIndex += 4;
 
-            bpa.attestations[j].max_num_publishers = encoded.toUint32(index + attestationIndex);
+            bpa.attestations[j].maxNumPublishers = encoded.toUint32(index + attestationIndex);
             attestationIndex += 4;
 
-            bpa.attestations[j].publish_time = encoded.toUint64(index + attestationIndex);
+            bpa.attestations[j].attestationTime = encoded.toUint64(index + attestationIndex);
+            attestationIndex += 8;
+
+            bpa.attestations[j].publishTime = encoded.toUint64(index + attestationIndex);
             attestationIndex += 8;
 
-            bpa.attestations[j].prev_publish_time = encoded.toUint64(index + attestationIndex);
+            bpa.attestations[j].prevPublishTime = encoded.toUint64(index + attestationIndex);
             attestationIndex += 8;
 
-            bpa.attestations[j].prev_price = int64(encoded.toUint64(index + attestationIndex));
+            bpa.attestations[j].prevPrice = int64(encoded.toUint64(index + attestationIndex));
             attestationIndex += 8;
 
-            bpa.attestations[j].prev_conf = encoded.toUint64(index + attestationIndex);
+            bpa.attestations[j].prevConf = encoded.toUint64(index + attestationIndex);
             attestationIndex += 8;
 
 	    require(attestationIndex <= bpa.attestationSize, "INTERNAL: Consumed more than `attestationSize` bytes");

+ 15 - 21
ethereum/contracts/pyth/PythInternalStructs.sol

@@ -19,35 +19,28 @@ contract PythInternalStructs {
 
     struct Header {
         uint32 magic;
-        uint16 version;
+        uint16 versionMajor;
+        uint16 versionMinor;
+	uint16 hdrSize;
         uint8 payloadId;
     }
 
     struct PriceAttestation {
-        Header header;
-
         bytes32 productId;
         bytes32 priceId;
-        uint8 priceType;
-
         int64 price;
-        int32 exponent;
-
-        Rational emaPrice;
-        Rational emaConf;
-
-        uint64 confidenceInterval;
-
+        uint64 conf;
+        int32 expo;
+        int64 emaPrice;
+        uint64 emaConf;
         uint8 status;
-        uint8 corpAct;
-
-        uint64 timestamp;
-	uint32 num_publishers;
-	uint32 max_num_publishers;
-	uint64 publish_time;
-	uint64 prev_publish_time;
-	int64 prev_price;
-	uint64 prev_conf;
+	uint32 numPublishers;
+	uint32 maxNumPublishers;
+        uint64 attestationTime;
+	uint64 publishTime;
+	uint64 prevPublishTime;
+	int64 prevPrice;
+	uint64 prevConf;
     }
 
     struct Rational {
@@ -67,6 +60,7 @@ contract PythInternalStructs {
     struct PriceInfo {
         PythStructs.PriceFeed priceFeed;
         uint256 attestationTime;
+	uint256 publishTime;
         uint256 arrivalTime;
         uint256 arrivalBlock;
     }

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 1
ethereum/test/pyth.js


+ 4 - 4
terra/contracts/pyth-bridge/src/contract.rs

@@ -136,15 +136,15 @@ fn process_batch_attestation(
             price_attestation.num_publishers, // num_publishers data is currently unavailable
             price_attestation.product_id.to_bytes(),
             price_attestation.price,
-            price_attestation.confidence_interval,
-            price_attestation.ema_price.val,
-            price_attestation.ema_conf.val as u64,
+            price_attestation.conf,
+            price_attestation.ema_price,
+            price_attestation.ema_conf,
 	    price_attestation.prev_price,
 	    price_attestation.prev_conf,
 	    price_attestation.prev_publish_time,
         );
 
-        let attestation_time = Timestamp::from_seconds(price_attestation.timestamp as u64);
+        let attestation_time = Timestamp::from_seconds(price_attestation.attestation_time as u64);
 
         if update_price_feed_if_new(&mut deps, &env, price_feed, attestation_time)? {
             new_attestations_cnt += 1;

+ 2 - 2
third_party/pyth/p2w-integration-observer/src/index.ts

@@ -141,9 +141,9 @@ async function readinessProbeRoutine(port: number) {
 
 	    console.log(`[seqno ${poolEntry}] Parsed VAA:\n`, parsedVaa);
 
-	    let parsedAttestations = parseBatchPriceAttestation(Buffer.from(parsedVaa.payload));
+	    let parsedAttestations = (await parseBatchPriceAttestation(Buffer.from(parsedVaa.payload))).priceAttestations;
 
-	    console.log(`[seqno ${poolEntry}] Parsed ${parsedAttestations.nAttestations} price attestations:\n`, parsedAttestations);
+	    console.log(`[seqno ${poolEntry}] Parsed ${parsedAttestations.length} price attestations:\n`, parsedAttestations);
 
 	    // try {
 	    // 	let tx = await p2w_eth.attestPrice(vaaResponse.vaaBytes, {gasLimit: 1000000});

+ 1 - 1
third_party/pyth/p2w-relay/src/listen.ts

@@ -153,7 +153,7 @@ async function processVaa(vaaBytes: string) {
   let batchAttestation;
 
   try {
-    batchAttestation = parseBatchPriceAttestation(
+      batchAttestation = await parseBatchPriceAttestation(
       Buffer.from(parsedVAA.payload)
     );
   } catch (e: any) {

+ 2 - 2
third_party/pyth/p2w-relay/src/relay/evm.ts

@@ -32,12 +32,12 @@ export class EvmRelay implements Relay {
     for (let i = 0; i < signedVAAs.length; ++i) {
       let batchNo = i + 1;
       let parsedVAA = parse_vaa(hexToUint8Array(signedVAAs[i]));
-      let parsedBatch = parseBatchPriceAttestation(
+      let parsedBatch = await parseBatchPriceAttestation(
         Buffer.from(parsedVAA.payload)
       );
 
       let priceIds: PriceId[] = [];
-      for (let j = 0; j < parsedBatch.nAttestations; ++j) {
+      for (let j = 0; j < parsedBatch.priceAttestations.length; ++j) {
         priceIds.push(parsedBatch.priceAttestations[j].priceId);
       }
 

+ 55 - 105
third_party/pyth/p2w-sdk/js/src/index.ts

@@ -2,6 +2,20 @@ import { getSignedVAA, CHAIN_ID_SOLANA } from "@certusone/wormhole-sdk";
 import { zeroPad } from "ethers/lib/utils";
 import { PublicKey } from "@solana/web3.js";
 
+let _P2W_WASM: any = undefined;
+
+
+async function importWasm() {
+    if (!_P2W_WASM) {
+	if (typeof window === 'undefined') {
+	  _P2W_WASM = await import("./solana/p2w-core/nodejs/p2w_sdk");
+	} else {
+	  _P2W_WASM = await import("./solana/p2w-core/bundler/p2w_sdk");
+	}
+    }
+    return _P2W_WASM;
+}
+
 
 /*
   // Definitions exist in p2w-sdk/rust/
@@ -88,10 +102,6 @@ In version 2 prices are sent in batch with the following structure:
 
 */
 
-export const PYTH_PRICE_ATTESTATION_MIN_LENGTH: number = 150;
-export const PYTH_BATCH_PRICE_ATTESTATION_MIN_LENGTH: number =
-    11 + PYTH_PRICE_ATTESTATION_MIN_LENGTH;
-
 export type Rational = {
     value: BigInt;
     numerator: BigInt;
@@ -99,121 +109,61 @@ export type Rational = {
 };
 
 export type PriceAttestation = {
-    magic: number;
-    version: number;
-    payloadId: number;
     productId: string;
     priceId: string;
-    priceType: number;
     price: BigInt;
-    exponent: number;
-    emaPrice: Rational;
-    emaConfidence: Rational;
-    confidenceInterval: BigInt;
+    conf: BigInt;
+    expo: number;
+    emaPrice: BigInt;
+    emaConf: BigInt;
     status: number;
-    corpAct: number;
-    timestamp: BigInt;
+    numPublishers: BigInt;
+    maxNumPublishers: BigInt;
+    attestationTime: BigInt;
+    publishTime: BigInt;
+    prevPublishTime: BigInt;
+    prevPrice: BigInt;
+    prevConf: BigInt;
 };
 
 export type BatchPriceAttestation = {
-    magic: number;
-    version: number;
-    payloadId: number;
-    nAttestations: number;
-    attestationSize: number;
     priceAttestations: PriceAttestation[];
 };
 
-export const PYTH_MAGIC: number = 0x50325748;
-
-function isPyth(payload: Buffer): boolean {
-    if (payload.length < 4) return false;
-    if (
-        payload[0] === 80 &&
-        payload[1] === 50 &&
-        payload[2] === 87 &&
-        payload[3] === 72
-    ) {
-        // The numbers correspond to "P2WH"
-        return true;
-    }
-
-    return false;
-}
-
-export function parsePriceAttestation(arr: Buffer): PriceAttestation {
+export function rawToPriceAttestation(rawVal: any): PriceAttestation {
     return {
-        magic: arr.readUInt32BE(0),
-        version: arr.readUInt16BE(4),
-        payloadId: arr[6],
-        productId: arr.slice(7, 7 + 32).toString("hex"),
-        priceId: arr.slice(39, 39 + 32).toString("hex"),
-        priceType: arr[71],
-        price: arr.readBigInt64BE(72),
-        exponent: arr.readInt32BE(80),
-        emaPrice: {
-            value: arr.readBigInt64BE(84),
-            numerator: arr.readBigInt64BE(92),
-            denominator: arr.readBigInt64BE(100),
-        },
-        emaConfidence: {
-            value: arr.readBigInt64BE(108),
-            numerator: arr.readBigInt64BE(116),
-            denominator: arr.readBigInt64BE(124),
-        },
-        confidenceInterval: arr.readBigUInt64BE(132),
-        status: arr.readUInt32BE(140),
-        corpAct: arr.readUInt32BE(141),
-        timestamp: arr.readBigUInt64BE(142),
+	productId: rawVal.product_id,
+	priceId: rawVal.price_id,
+	price: rawVal.price,
+	conf: rawVal.conf,
+	expo: rawVal.expo,
+	emaPrice: rawVal.ema_price,
+	emaConf: rawVal.ema_conf,
+	status: rawVal.status,
+	numPublishers: rawVal.num_publishers,
+	maxNumPublishers: rawVal.max_num_publishers,
+	attestationTime: rawVal.attestation_time,
+	publishTime: rawVal.publish_time,
+	prevPublishTime: rawVal.prev_publish_time,
+	prevPrice: rawVal.prev_price,
+	prevConf: rawVal.prev_conf,
     };
 }
 
-export function parseBatchPriceAttestation(
+export async function parseBatchPriceAttestation(
     arr: Buffer
-): BatchPriceAttestation {
-    if (!isPyth(arr)) {
-        throw new Error(
-            "Cannot parse payload. Header mismatch: This is not a Pyth 2 Wormhole message"
-        );
-    }
-
-    if (arr.length < PYTH_BATCH_PRICE_ATTESTATION_MIN_LENGTH) {
-        throw new Error(
-            "Cannot parse payload. Payload length is wrong: length: " +
-            arr.length +
-            ", expected length to be at least:" +
-            PYTH_BATCH_PRICE_ATTESTATION_MIN_LENGTH
-        );
-    }
+): Promise<BatchPriceAttestation> {
+    
+    let wasm = await importWasm();
+    let rawVal = await wasm.parse_batch_attestation(arr);
 
-    const magic = arr.readUInt32BE(0);
-    const version = arr.readUInt16BE(4);
-    const payloadId = arr[6];
-    const nAttestations = arr.readUInt16BE(7);
-    const attestationSize = arr.readUInt16BE(9);
+    let priceAttestations = [];
 
-    if (attestationSize < PYTH_PRICE_ATTESTATION_MIN_LENGTH) {
-        throw new Error(
-            `Cannot parse payload. Size of attestation ${attestationSize} is less than V2 length ${PYTH_PRICE_ATTESTATION_MIN_LENGTH}`
-        );
-    }
-
-    let priceAttestations: PriceAttestation[] = [];
-
-    let offset = 11;
-    for (let i = 0; i < nAttestations; i += 1) {
-        priceAttestations.push(
-            parsePriceAttestation(arr.subarray(offset, offset + attestationSize))
-        );
-        offset += attestationSize;
+    for (let rawAttestation of rawVal.price_attestations) {
+	priceAttestations.push(rawToPriceAttestation(rawAttestation));
     }
 
     return {
-        magic,
-        version,
-        payloadId,
-        nAttestations,
-        attestationSize,
         priceAttestations,
     };
 }
@@ -232,17 +182,17 @@ export function getBatchAttestationHashKey(
 }
 
 export function getBatchSummary(
-    batchAttestation: BatchPriceAttestation
+    batch: BatchPriceAttestation
 ): string {
     let abstractRepresentation = {
-        num_attestations: batchAttestation.nAttestations,
-        prices: batchAttestation.priceAttestations.map((priceAttestation) => {
+        num_attestations: batch.priceAttestations.length,
+        prices: batch.priceAttestations.map((priceAttestation) => {
             return {
                 price_id: priceAttestation.priceId,
-                price: computePrice(priceAttestation.price, priceAttestation.exponent),
+                price: computePrice(priceAttestation.price, priceAttestation.expo),
                 conf: computePrice(
-                    priceAttestation.confidenceInterval,
-                    priceAttestation.exponent
+                    priceAttestation.conf,
+                    priceAttestation.expo
                 ),
             };
         }),

+ 139 - 218
third_party/pyth/p2w-sdk/rust/src/lib.rs

@@ -12,12 +12,7 @@ use std::io::Read;
 use std::iter::Iterator;
 use std::mem;
 
-use pyth_sdk_solana::state::{
-    CorpAction,
-    PriceStatus,
-    PriceType,
-    Rational,
-};
+use pyth_sdk_solana::state::PriceStatus;
 
 #[cfg(feature = "solana")]
 use solitaire::{
@@ -38,7 +33,15 @@ pub type ErrBox = Box<dyn std::error::Error>;
 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 P2W_FORMAT_VER_MAJOR: u16 = 3;
+
+/// Starting with v3, format introduces a minor version to mark forward-compatible iterations
+pub const P2W_FORMAT_VER_MINOR: u16 = 0;
+
+/// Starting with v3, format introduces append-only
+/// forward-compatibility to the header. This is the current number of
+/// bytes after the hdr_size field.
+pub const P2W_FORMAT_HDR_SIZE: u16 = 1;
 
 pub const PUBKEY_LEN: usize = 32;
 
@@ -62,27 +65,21 @@ pub enum PayloadId {
 /// chain logic.
 #[derive(Clone, Default, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
 pub struct PriceAttestation {
-    pub product_id:          Pubkey,
-    pub price_id:            Pubkey,
-    pub price_type:          PriceType,
-    pub price:               i64,
-    pub expo:                i32,
-    pub ema_price:           Rational,
-    pub ema_conf:            Rational,
-    pub confidence_interval: u64,
-    pub status:              PriceStatus,
-    pub corp_act:            CorpAction,
-    // TODO(2022-04-07) format v3: Rename this aptly named timestamp
-    // field to attestation_time (it's a grey area in terms of
-    // compatibility and v3 is due very soon either way)
-    /// NOTE: SOL on-chain time of attestation
-    pub timestamp:           UnixTimestamp,
-    pub num_publishers:      u32,
-    pub max_num_publishers:  u32,
-    pub publish_time:        UnixTimestamp,
-    pub prev_publish_time:   UnixTimestamp,
-    pub prev_price:          i64,
-    pub prev_conf:           u64, 
+    pub product_id:         Pubkey,
+    pub price_id:           Pubkey,
+    pub price:              i64,
+    pub conf:               u64,
+    pub expo:               i32,
+    pub ema_price:          i64,
+    pub ema_conf:           u64,
+    pub status:             PriceStatus,
+    pub num_publishers:     u32,
+    pub max_num_publishers: u32,
+    pub attestation_time:   UnixTimestamp,
+    pub publish_time:       UnixTimestamp,
+    pub prev_publish_time:  UnixTimestamp,
+    pub prev_price:         i64,
+    pub prev_conf:          u64,
 }
 
 #[derive(Clone, Default, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
@@ -98,8 +95,14 @@ impl BatchPriceAttestation {
         // magic
         let mut buf = P2W_MAGIC.to_vec();
 
-        // version
-        buf.extend_from_slice(&P2W_FORMAT_VERSION.to_be_bytes()[..]);
+        // version_major
+        buf.extend_from_slice(&P2W_FORMAT_VER_MAJOR.to_be_bytes()[..]);
+
+        // version_minor
+        buf.extend_from_slice(&P2W_FORMAT_VER_MINOR.to_be_bytes()[..]);
+
+        // hdr_size
+        buf.extend_from_slice(&P2W_FORMAT_HDR_SIZE.to_be_bytes()[..]);
 
         // payload_id
         buf.push(PayloadId::PriceBatchAttestation as u8);
@@ -154,20 +157,46 @@ impl BatchPriceAttestation {
             .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()?);
+        let mut version_major_vec = vec![0u8; mem::size_of_val(&P2W_FORMAT_VER_MAJOR)];
+        bytes.read_exact(version_major_vec.as_mut_slice())?;
+        let version_major = u16::from_be_bytes(version_major_vec.as_slice().try_into()?);
+
+        // Major must match exactly
+        if version_major != P2W_FORMAT_VER_MAJOR {
+            return Err(format!(
+                "Unsupported format version_major {}, expected {}",
+                version_major, P2W_FORMAT_VER_MAJOR
+            )
+            .into());
+        }
+
+        let mut version_minor_vec = vec![0u8; mem::size_of_val(&P2W_FORMAT_VER_MINOR)];
+        bytes.read_exact(version_minor_vec.as_mut_slice())?;
+        let version_minor = u16::from_be_bytes(version_minor_vec.as_slice().try_into()?);
 
-        if version != P2W_FORMAT_VERSION {
+        // Only older minors are not okay for this codebase
+        if version_minor < P2W_FORMAT_VER_MINOR {
             return Err(format!(
-                "Unsupported format version {}, expected {}",
-                version, P2W_FORMAT_VERSION
+                "Unsupported format version_minor {}, expected {} or more",
+                version_minor, P2W_FORMAT_VER_MINOR
             )
             .into());
         }
 
+        // Read header size value
+        let mut hdr_size_vec = vec![0u8; mem::size_of_val(&P2W_FORMAT_HDR_SIZE)];
+        bytes.read_exact(hdr_size_vec.as_mut_slice())?;
+        let hdr_size = u16::from_be_bytes(hdr_size_vec.as_slice().try_into()?);
+
+        // Consume the declared number of remaining header
+        // bytes. Remaining header fields must be read from hdr_buf
+        let mut hdr_buf = vec![0u8; hdr_size as usize];
+        bytes.read_exact(hdr_buf.as_mut_slice())?;
+
         let mut payload_id_vec = vec![0u8; mem::size_of::<PayloadId>()];
-        bytes.read_exact(payload_id_vec.as_mut_slice())?;
+        hdr_buf
+            .as_slice()
+            .read_exact(payload_id_vec.as_mut_slice())?;
 
         if payload_id_vec[0] != PayloadId::PriceBatchAttestation as u8 {
             return Err(format!(
@@ -178,6 +207,7 @@ impl BatchPriceAttestation {
             .into());
         }
 
+        // Header consumed, continue with remaining fields
         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()?);
@@ -192,8 +222,6 @@ impl BatchPriceAttestation {
             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) => {
@@ -208,35 +236,6 @@ impl BatchPriceAttestation {
     }
 }
 
-pub fn serialize_rational(rational: &Rational) -> Vec<u8> {
-    let mut v = vec![];
-    // val
-    v.extend(&rational.val.to_be_bytes()[..]);
-
-    // numer
-    v.extend(&rational.numer.to_be_bytes()[..]);
-
-    // denom
-    v.extend(&rational.denom.to_be_bytes()[..]);
-
-    v
-}
-
-pub fn deserialize_rational(mut bytes: impl Read) -> Result<Rational, 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(Rational { val, numer, denom })
-}
 
 // On-chain data types
 
@@ -251,21 +250,19 @@ impl PriceAttestation {
         Ok(PriceAttestation {
             product_id: Pubkey::new(&price.prod.val[..]),
             price_id,
-            price_type: price.ptype,
             price: price.agg.price,
-            ema_price: price.ema_price,
-            ema_conf: price.ema_conf,
+            conf: price.agg.conf,
             expo: price.expo,
-            confidence_interval: price.agg.conf,
+            ema_price: price.ema_price.val,
+            ema_conf: price.ema_conf.val as u64,
             status: price.agg.status,
-            corp_act: price.agg.corp_act,
-            timestamp: attestation_time,
             num_publishers: price.num_qt,
             max_num_publishers: price.num,
-	    publish_time: price.timestamp,
-	    prev_publish_time: price.prev_timestamp,
-	    prev_price: price.prev_price,
-	    prev_conf: price.prev_conf,
+            attestation_time,
+            publish_time: price.timestamp,
+            prev_publish_time: price.prev_timestamp,
+            prev_price: price.prev_price,
+            prev_conf: price.prev_conf,
         })
     }
 
@@ -276,31 +273,22 @@ impl PriceAttestation {
         let PriceAttestation {
             product_id,
             price_id,
-            price_type,
             price,
+            conf,
             expo,
             ema_price,
             ema_conf,
-            confidence_interval,
             status,
-            corp_act,
-            timestamp,
             num_publishers,
             max_num_publishers,
-	    publish_time,
-	    prev_publish_time,
-	    prev_price,
-	    prev_conf
+            attestation_time,
+            publish_time,
+            prev_publish_time,
+            prev_price,
+            prev_conf,
         } = 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);
+        let mut buf = Vec::new();
 
         // product_id
         buf.extend_from_slice(&product_id.to_bytes()[..]);
@@ -308,90 +296,48 @@ impl PriceAttestation {
         // 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
+        // conf
+        buf.extend_from_slice(&conf.to_be_bytes()[..]);
+
+        // expo
         buf.extend_from_slice(&expo.to_be_bytes()[..]);
 
         // ema_price
-        buf.append(&mut serialize_rational(&ema_price));
+        buf.extend_from_slice(&ema_price.to_be_bytes()[..]);
 
         // ema_conf
-        buf.append(&mut serialize_rational(&ema_conf));
-
-        // confidence_interval
-        buf.extend_from_slice(&confidence_interval.to_be_bytes()[..]);
+        buf.extend_from_slice(&ema_conf.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()[..]);
-
         // num_publishers
         buf.extend_from_slice(&num_publishers.to_be_bytes()[..]);
 
         // max_num_publishers
         buf.extend_from_slice(&max_num_publishers.to_be_bytes()[..]);
 
-	// publish_time
+        // attestation_time
+        buf.extend_from_slice(&attestation_time.to_be_bytes()[..]);
+
+        // publish_time
         buf.extend_from_slice(&publish_time.to_be_bytes()[..]);
 
-	// prev_publish_time
+        // prev_publish_time
         buf.extend_from_slice(&prev_publish_time.to_be_bytes()[..]);
 
         // prev_price
         buf.extend_from_slice(&prev_price.to_be_bytes()[..]);
 
-	// prev_conf
+        // prev_conf
         buf.extend_from_slice(&prev_conf.to_be_bytes()[..]);
 
         buf
     }
     pub fn deserialize(mut bytes: impl Read) -> Result<Self, 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 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());
@@ -400,32 +346,25 @@ impl PriceAttestation {
         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];
-        bytes.read_exact(price_type_vec.as_mut_slice())?;
-        let price_type = match price_type_vec[0] {
-            a if a == PriceType::Price as u8 => PriceType::Price,
-            a if a == PriceType::Unknown as u8 => PriceType::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 conf_vec = vec![0u8; mem::size_of::<u64>()];
+        bytes.read_exact(conf_vec.as_mut_slice())?;
+        let conf = u64::from_be_bytes(conf_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 ema_price = deserialize_rational(&mut bytes)?;
-        let ema_conf = deserialize_rational(&mut bytes)?;
+        let mut ema_price_vec = vec![0u8; mem::size_of::<i64>()];
+        bytes.read_exact(ema_price_vec.as_mut_slice())?;
+        let ema_price = i64::from_be_bytes(ema_price_vec.as_slice().try_into()?);
 
-        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 ema_conf_vec = vec![0u8; mem::size_of::<u64>()];
+        bytes.read_exact(ema_conf_vec.as_mut_slice())?;
+        let ema_conf = u64::from_be_bytes(ema_conf_vec.as_slice().try_into()?);
 
         let mut status_vec = vec![0u8];
         bytes.read_exact(status_vec.as_mut_slice())?;
@@ -439,19 +378,6 @@ impl PriceAttestation {
             }
         };
 
-        let mut corp_act_vec = vec![0u8];
-        bytes.read_exact(corp_act_vec.as_mut_slice())?;
-        let corp_act = match corp_act_vec[0] {
-            a if a == CorpAction::NoCorpAct as u8 => CorpAction::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()?);
-
         let mut num_publishers_vec = vec![0u8; mem::size_of::<u32>()];
         bytes.read_exact(num_publishers_vec.as_mut_slice())?;
         let num_publishers = u32::from_be_bytes(num_publishers_vec.as_slice().try_into()?);
@@ -460,13 +386,19 @@ impl PriceAttestation {
         bytes.read_exact(max_num_publishers_vec.as_mut_slice())?;
         let max_num_publishers = u32::from_be_bytes(max_num_publishers_vec.as_slice().try_into()?);
 
+        let mut attestation_time_vec = vec![0u8; mem::size_of::<UnixTimestamp>()];
+        bytes.read_exact(attestation_time_vec.as_mut_slice())?;
+        let attestation_time =
+            UnixTimestamp::from_be_bytes(attestation_time_vec.as_slice().try_into()?);
+
         let mut publish_time_vec = vec![0u8; mem::size_of::<UnixTimestamp>()];
         bytes.read_exact(publish_time_vec.as_mut_slice())?;
         let publish_time = UnixTimestamp::from_be_bytes(publish_time_vec.as_slice().try_into()?);
 
         let mut prev_publish_time_vec = vec![0u8; mem::size_of::<UnixTimestamp>()];
         bytes.read_exact(prev_publish_time_vec.as_mut_slice())?;
-        let prev_publish_time = UnixTimestamp::from_be_bytes(prev_publish_time_vec.as_slice().try_into()?);
+        let prev_publish_time =
+            UnixTimestamp::from_be_bytes(prev_publish_time_vec.as_slice().try_into()?);
 
         let mut prev_price_vec = vec![0u8; mem::size_of::<i64>()];
         bytes.read_exact(prev_price_vec.as_mut_slice())?;
@@ -479,21 +411,19 @@ impl PriceAttestation {
         Ok(Self {
             product_id,
             price_id,
-            price_type,
             price,
+            conf,
             expo,
             ema_price,
             ema_conf,
-            confidence_interval,
             status,
-            corp_act,
-            timestamp,
             num_publishers,
             max_num_publishers,
-	    publish_time,
-	    prev_publish_time,
-	    prev_price,
-	    prev_conf,
+            attestation_time,
+            publish_time,
+            prev_publish_time,
+            prev_price,
+            prev_conf,
         })
     }
 }
@@ -504,41 +434,27 @@ impl PriceAttestation {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use pyth_sdk_solana::state::{
-        PriceStatus,
-        PriceType,
-        Rational,
-    };
+    use pyth_sdk_solana::state::PriceStatus;
 
     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 = price.unwrap_or([222u8; 32]);
         PriceAttestation {
-            product_id:          Pubkey::new_from_array(product_id_bytes),
-            price_id:            Pubkey::new_from_array(price_id_bytes),
-            price:               0x2bad2feed7,
-            price_type:          PriceType::Price,
-            ema_price:           Rational {
-                val:   -42,
-                numer: 15,
-                denom: 37,
-            },
-            ema_conf:            Rational {
-                val:   42,
-                numer: 1111,
-                denom: 2222,
-            },
-            expo:                -3,
-            status:              PriceStatus::Trading,
-            confidence_interval: 101,
-            corp_act:            CorpAction::NoCorpAct,
-            timestamp:           (0xdeadbeeffadedeedu64) as i64,
-	    num_publishers: 123212u32,
-	    max_num_publishers: 321232u32,
-	    publish_time: 0xdeadbeefi64,
-	    prev_publish_time: 0xdeadbabei64,
-	    prev_price: 0xdeadfacebeefi64,
-	    prev_conf: 0xbadbadbeefu64, // I could do this all day -SD
+            product_id:         Pubkey::new_from_array(product_id_bytes),
+            price_id:           Pubkey::new_from_array(price_id_bytes),
+            price:              0x2bad2feed7,
+            conf:               101,
+            ema_price:          -42,
+            ema_conf:           42,
+            expo:               -3,
+            status:             PriceStatus::Trading,
+            num_publishers:     123212u32,
+            max_num_publishers: 321232u32,
+            attestation_time:   (0xdeadbeeffadedeedu64) as i64,
+            publish_time:       0xdeadbeefi64,
+            prev_publish_time:  0xdeadbabei64,
+            prev_price:         0xdeadfacebeefi64,
+            prev_conf:          0xbadbadbeefu64, // I could do this all day -SD
         }
     }
 
@@ -574,7 +490,12 @@ mod tests {
     #[test]
     fn test_batch_serde() -> Result<(), ErrBox> {
         let attestations: Vec<_> = (1..=10)
-            .map(|i| mock_attestation(Some([(i % 256) as u8; 32]), Some([(255 - (i % 256)) as u8; 32])))
+            .map(|i| {
+                mock_attestation(
+                    Some([(i % 256) as u8; 32]),
+                    Some([(255 - (i % 256)) as u8; 32]),
+                )
+            })
             .collect();
 
         let batch_attestation = BatchPriceAttestation {

+ 1 - 1
third_party/pyth/price-service/src/listen.ts

@@ -143,7 +143,7 @@ export class Listener implements PriceFeedVaaInfo {
     let batchAttestation;
 
     try {
-      batchAttestation = parseBatchPriceAttestation(
+      batchAttestation = await parseBatchPriceAttestation(
         Buffer.from(parsedVAA.payload)
       );
     } catch (e: any) {

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio