Просмотр исходного кода

pyth2wormhole: implement wire format v3rev0 (#189)

* pyth2wormhole: implement wire format v3rev0

commit-id:c106d3b3

* pyth.js: Run prettier formatting

commit-id:fc9fb160

* ethereum: Fix tab indentation in Pyth contracts

commit-id:3d2ab7d8

* p2w-terra contract: fix formatting

commit-id:685dd14f

* p2w-sdk: Use wasm in the JS package, detect bundler/node via `window`

This commit makes sure that our wasm usage is more robust. We ensure
that the JSON representation renders all important numbers in their
string decimal form. b

commit-id:75f9c224

* p2w-sdk: review suggestions: field ranems, limit serde attributes

commit-id:9f596b80

* p2w-sdk/rust: Remove utils.rs and the helper type

commit-id:e72c6345
Stanisław Drozd 3 лет назад
Родитель
Сommit
a4c749b99c

+ 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

+ 65 - 68
ethereum/contracts/pyth/Pyth.sol

@@ -18,7 +18,7 @@ contract Pyth is PythGetters, PythSetters, AbstractPyth {
         address wormhole,
         uint16 pyth2WormholeChainId,
         bytes32 pyth2WormholeEmitter
-    ) virtual public {        
+    ) virtual public {
         setWormhole(wormhole);
         setPyth2WormholeChainId(pyth2WormholeChainId);
         setPyth2WormholeEmitter(pyth2WormholeEmitter);
@@ -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;
     }
 
@@ -75,7 +74,7 @@ contract Pyth is PythGetters, PythSetters, AbstractPyth {
         return true;
     }
 
-    
+
     function parseBatchPriceAttestation(bytes memory encoded) public pure returns (PythInternalStructs.BatchPriceAttestation memory bpa) {
         uint index = 0;
 
@@ -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.versionMinor >= 0, "invalid version minor, expected 0 or more");
+
+        bpa.header.hdrSize = encoded.toUint16(index);
         index += 2;
-        require(bpa.header.version == 2, "invalid version");
+
+        // 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);
@@ -106,24 +129,10 @@ contract Pyth is PythGetters, PythSetters, AbstractPyth {
 
         // Deserialize each attestation
         for (uint j=0; j < bpa.nAttestations; j++) {
-	    // NOTE: We don't advance the global index immediately.
-	    // attestationIndex is an attestation-local offset used
-	    // 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");
+            // NOTE: We don't advance the global index immediately.
+            // attestationIndex is an attestation-local offset used
+            // for readability and easier debugging.
+            uint attestationIndex = 0;
 
             // Attestation
             bpa.attestations[j].productId = encoded.toBytes32(index + attestationIndex);
@@ -131,67 +140,55 @@ contract Pyth is PythGetters, PythSetters, AbstractPyth {
 
             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].prev_publish_time = encoded.toUint64(index + attestationIndex);
+            bpa.attestations[j].publishTime = encoded.toUint64(index + attestationIndex);
             attestationIndex += 8;
 
-            bpa.attestations[j].prev_price = int64(encoded.toUint64(index + attestationIndex));
+            bpa.attestations[j].prevPublishTime = encoded.toUint64(index + attestationIndex);
             attestationIndex += 8;
 
-            bpa.attestations[j].prev_conf = encoded.toUint64(index + attestationIndex);
+            bpa.attestations[j].prevPrice = int64(encoded.toUint64(index + attestationIndex));
             attestationIndex += 8;
 
-	    require(attestationIndex <= bpa.attestationSize, "INTERNAL: Consumed more than `attestationSize` bytes");
+            bpa.attestations[j].prevConf = encoded.toUint64(index + attestationIndex);
+            attestationIndex += 8;
+
+            require(attestationIndex <= bpa.attestationSize, "INTERNAL: Consumed more than `attestationSize` bytes");
 
-	    // Respect specified attestation size for forward-compat
-	    index += bpa.attestationSize;
+            // Respect specified attestation size for forward-compat
+            index += bpa.attestationSize;
         }
     }
 
     /// Maximum acceptable time period before price is considered to be stale.
-    /// 
+    ///
     /// This includes attestation delay which currently might up to a minute.
     uint private constant VALID_TIME_PERIOD_SECS = 180;
 
@@ -207,7 +204,7 @@ contract Pyth is PythGetters, PythSetters, AbstractPyth {
         if (diff(block.timestamp, info.attestationTime) > VALID_TIME_PERIOD_SECS) {
             info.priceFeed.status = PythStructs.PriceStatus.UNKNOWN;
         }
-        
+
         return info.priceFeed;
     }
 

+ 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;
     }

Разница между файлами не показана из-за своего большого размера
+ 57 - 34
ethereum/test/pyth.js


+ 27 - 34
terra/contracts/pyth-bridge/src/contract.rs

@@ -9,8 +9,8 @@ use cosmwasm_std::{
     QueryRequest,
     Response,
     StdResult,
-    WasmQuery,
     Timestamp,
+    WasmQuery,
 };
 
 use pyth_sdk::{
@@ -23,8 +23,8 @@ use crate::{
         ExecuteMsg,
         InstantiateMsg,
         MigrateMsg,
-        QueryMsg,
         PriceFeedResponse,
+        QueryMsg,
     },
     state::{
         config,
@@ -37,16 +37,12 @@ use crate::{
     },
 };
 
-use p2w_sdk::{
-    BatchPriceAttestation,
-};
+use p2w_sdk::BatchPriceAttestation;
 
 use wormhole::{
     error::ContractError,
     msg::QueryMsg as WormholeQueryMsg,
-    state::{
-        ParsedVAA,
-    },
+    state::ParsedVAA,
 };
 
 #[cfg_attr(not(feature = "library"), entry_point)]
@@ -130,21 +126,21 @@ fn process_batch_attestation(
         let price_feed = PriceFeed::new(
             price_attestation.price_id.to_bytes(),
             price_attestation.status,
-	    price_attestation.publish_time,
+            price_attestation.publish_time,
             price_attestation.expo,
-            price_attestation.max_num_publishers, // max_num_publishers data is currently unavailable
-            price_attestation.num_publishers, // num_publishers data is currently unavailable
+            price_attestation.max_num_publishers,
+            price_attestation.num_publishers,
             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.prev_price,
-	    price_attestation.prev_conf,
-	    price_attestation.prev_publish_time,
+            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;
@@ -161,8 +157,8 @@ fn process_batch_attestation(
 }
 
 /// Returns true if the price_feed is newer than the stored one.
-/// 
-/// This function returns error only if there be issues in ser/de when it reads from the bucket. Such an example would be 
+///
+/// This function returns error only if there be issues in ser/de when it reads from the bucket. Such an example would be
 /// upgrades which migration is not handled carefully so the binary stored in the bucket won't be parsed.
 fn update_price_feed_if_new(
     deps: &mut DepsMut,
@@ -205,9 +201,7 @@ fn update_price_feed_if_new(
 #[cfg_attr(not(feature = "library"), entry_point)]
 pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
     match msg {
-        QueryMsg::PriceFeed { id } => {
-            to_binary(&query_price_info(deps, env, id.as_ref())?)
-        }
+        QueryMsg::PriceFeed { id } => to_binary(&query_price_info(deps, env, id.as_ref())?),
     }
 }
 
@@ -222,21 +216,20 @@ pub fn query_price_info(deps: Deps, env: Env, address: &[u8]) -> StdResult<Price
             //   but more than that is set to unknown, the reason is huge clock difference means there exists a
             //   problem in a either Terra or Solana blockchain and if it is Solana we don't want to propagate
             //   Solana internal problems to Terra
-            let time_abs_diff = if env.block.time.seconds() > terra_price_info.attestation_time.seconds() {
-                env.block.time.seconds() - terra_price_info.attestation_time.seconds()
-            } else {
-                terra_price_info.attestation_time.seconds() - env.block.time.seconds()
-            };
+            let time_abs_diff =
+                if env.block.time.seconds() > terra_price_info.attestation_time.seconds() {
+                    env.block.time.seconds() - terra_price_info.attestation_time.seconds()
+                } else {
+                    terra_price_info.attestation_time.seconds() - env.block.time.seconds()
+                };
 
             if time_abs_diff > VALID_TIME_PERIOD.as_secs() {
                 terra_price_info.price_feed.status = PriceStatus::Unknown;
             }
 
-            Ok(
-                PriceFeedResponse {
-                    price_feed: terra_price_info.price_feed,
-                }
-            )
+            Ok(PriceFeedResponse {
+                price_feed: terra_price_info.price_feed,
+            })
         }
         Err(_) => ContractError::AssetNotFound.std_err(),
     }
@@ -283,7 +276,7 @@ mod test {
         price_feed
     }
 
-    /// Updates the price feed with the given attestation time stamp and 
+    /// Updates the price feed with the given attestation time stamp and
     /// returns the update status (true means updated, false means ignored)
     fn do_update_price_feed(
         deps: &mut DepsMut,

+ 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} prices in batch:\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);
       }
 

+ 46 - 222
third_party/pyth/p2w-sdk/js/src/index.ts

@@ -3,95 +3,19 @@ import { zeroPad } from "ethers/lib/utils";
 import { PublicKey } from "@solana/web3.js";
 import { PriceFeed, PriceStatus } from "@pythnetwork/pyth-sdk-js";
 
+let _P2W_WASM: any = undefined;
 
-/*
-  // Definitions exist in p2w-sdk/rust/
-  
-  struct Rational {
-      int64 value;
-      int64 numerator;
-      int64 denominator;
-  }
 
-  struct PriceAttestation {
-      uint32 magic; // constant "P2WH"
-      uint16 version;
-
-      // PayloadID uint8 = 1
-      uint8 payloadId;
-
-      bytes32 productId;
-      bytes32 priceId;
-
-      uint8 priceType;
-
-      int64 price;
-      int32 exponent;
-
-      Rational emaPrice;
-      Rational emaConfidence;
-
-      uint64 confidenceInterval;
-
-      uint8 status;
-      uint8 corpAct;
-
-      uint64 timestamp;
-  }
-
-0   uint32        magic // constant "P2WH"
-4   u16           version
-6   u8            payloadId // 1
-7   [u8; 32]      productId
-39  [u8; 32]      priceId
-71  u8            priceType
-72  i64           price
-80  i32           exponent
-84  PythRational  emaPrice
-108 PythRational  emaConfidence
-132 u64           confidenceInterval
-140 u8            status
-141 u8            corpAct
-142 u64           timestamp
-
-In version 2 prices are sent in batch with the following structure:
-
-  struct BatchPriceAttestation {
-      uint32 magic; // constant "P2WH"
-      uint16 version;
-
-      // PayloadID uint8 = 2
-      uint8 payloadId;
-
-      // number of attestations 
-      uint16 nAttestations;
-
-      // Length of each price attestation in bytes
-      //
-      // This field is provided for forwards compatibility. Fields in future may be added in
-      // an append-only way allowing for parsers to continue to work by parsing only up to
-      // the fields they know, leaving unread input in the buffer. Code may still need to work
-      // with the full size of the value however, such as when iterating over lists of attestations,
-      // for these use-cases the structure size is included as a field.
-      //
-      // attestation_size >= 150
-      uint16 attestationSize;
-      
-      priceAttestations: PriceAttestation[]
-  }
-
-0   uint32    magic // constant "P2WH"
-4   u16       version
-6   u8        payloadId // 2
-7   u16       n_attestations
-9   u16       attestation_size // >= 150
-11  ..        price_attestation (Size: attestation_size x [n_attestations])
-
-*/
-
-export const PYTH_PRICE_ATTESTATION_MIN_LENGTH: number = 150;
-export const PYTH_BATCH_PRICE_ATTESTATION_MIN_LENGTH: number =
-    11 + PYTH_PRICE_ATTESTATION_MIN_LENGTH;
+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;
+}
 
 export type Rational = {
     value: BigInt;
@@ -100,123 +24,35 @@ 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;
-    status: number;
-    corpAct: number;
-    timestamp: BigInt;
+    conf: BigInt;
+    expo: number;
+    emaPrice: BigInt;
+    emaConf: BigInt;
+    status: PriceStatus;
+    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 {
-    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[140],
-        corpAct: arr[141],
-        timestamp: arr.readBigUInt64BE(142),
-    };
-}
-
-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);
-
-    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;
-    }
-
-    return {
-        magic,
-        version,
-        payloadId,
-        nAttestations,
-        attestationSize,
-        priceAttestations,
-    };
+    return rawVal;
 }
 
 // Returns a hash of all priceIds within the batch, it can be used to identify whether there is a
@@ -233,17 +69,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
                 ),
             };
         }),
@@ -260,34 +96,22 @@ export async function getSignedAttestation(host: string, p2w_addr: string, seque
 
 export function priceAttestationToPriceFeed(priceAttestation: PriceAttestation): PriceFeed {
     let status;
-    if (priceAttestation.status === 0) {
-        status = PriceStatus.Unknown;
-    } else if (priceAttestation.status === 1) {
-        status = PriceStatus.Trading;
-    } else if (priceAttestation.status === 2) {
-        status = PriceStatus.Halted;
-    } else if (priceAttestation.status === 3) {
-        status = PriceStatus.Auction;
-    } else {
-        throw(new Error(`Invalid attestation status: ${priceAttestation.status}`));
-    }
 
-    // FIXME: populate 0 fields once they are in priceAttestation
     return new PriceFeed({
-        conf: priceAttestation.confidenceInterval.toString(),
-        emaConf: priceAttestation.emaConfidence.value.toString(),
-        emaPrice: priceAttestation.emaPrice.value.toString(),
-        expo: priceAttestation.exponent,
+        conf: priceAttestation.conf.toString(),
+        emaConf: priceAttestation.emaConf.toString(),
+        emaPrice: priceAttestation.emaPrice.toString(),
+        expo: priceAttestation.expo as any,
         id: priceAttestation.priceId,
-        maxNumPublishers: 0,
-        numPublishers: 0,
-        prevConf: "0",
-        prevPrice: "0",
-        prevPublishTime: 0,
+        maxNumPublishers: priceAttestation.maxNumPublishers as any,
+        numPublishers: priceAttestation.numPublishers as any,
+        prevConf: priceAttestation.prevConf.toString(),
+        prevPrice: priceAttestation.prevPrice.toString(),
+        prevPublishTime: priceAttestation.prevPublishTime as any,
         price: priceAttestation.price.toString(),
         productId: priceAttestation.productId,
-        publishTime: 0,
-        status
+        publishTime: priceAttestation.publishTime as any,
+        status: priceAttestation.status,
     })
 }
 

+ 173 - 221
third_party/pyth/p2w-sdk/rust/src/lib.rs

@@ -6,18 +6,19 @@
 //! similar human-readable names and provide a failsafe for some of
 //! the probable adversarial scenarios.
 
+use serde::{
+    Deserialize,
+    Serialize,
+    Serializer,
+};
+
 use std::borrow::Borrow;
 use std::convert::TryInto;
 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::{
@@ -32,13 +33,25 @@ use solana_program::pubkey::Pubkey;
 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
 pub mod wasm;
 
+#[cfg(feature = "wasm")]
+#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
+use wasm_bindgen::prelude::*;
+
 pub type ErrBox = Box<dyn std::error::Error>;
 
 /// 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 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;
 
@@ -60,32 +73,46 @@ pub enum PayloadId {
 /// 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, serde::Serialize, serde::Deserialize)]
+///
+/// NOTE(2022-04-25): the serde attributes help prevent math errors,
+/// and no less annoying low-effort serialization override method is known.
+#[derive(Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
 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,
+    #[serde(serialize_with = "use_to_string")]
+    pub price:              i64,
+    #[serde(serialize_with = "use_to_string")]
+    pub conf:               u64,
+    pub expo:               i32,
+    #[serde(serialize_with = "use_to_string")]
+    pub ema_price:          i64,
+    #[serde(serialize_with = "use_to_string")]
+    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,
+    #[serde(serialize_with = "use_to_string")]
+    pub prev_price:         i64,
+    #[serde(serialize_with = "use_to_string")]
+    pub prev_conf:          u64,
+}
+
+/// Helper allowing ToString implementers to be serialized as strings accordingly
+pub fn use_to_string<T, S>(val: &T, s: S) -> Result<S::Ok, S::Error>
+where
+    T: ToString,
+    S: Serializer,
+{
+    s.serialize_str(&val.to_string())
 }
 
 #[derive(Clone, Default, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
+#[serde(rename_all = "camelCase")]
 pub struct BatchPriceAttestation {
     pub price_attestations: Vec<PriceAttestation>,
 }
@@ -98,8 +125,14 @@ impl BatchPriceAttestation {
         // magic
         let mut buf = P2W_MAGIC.to_vec();
 
-        // version
-        buf.extend_from_slice(&P2W_FORMAT_VERSION.to_be_bytes()[..]);
+        // major_version
+        buf.extend_from_slice(&P2W_FORMAT_VER_MAJOR.to_be_bytes()[..]);
+
+        // minor_version 
+        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 +187,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 major_version_vec = vec![0u8; mem::size_of_val(&P2W_FORMAT_VER_MAJOR)];
+        bytes.read_exact(major_version_vec.as_mut_slice())?;
+        let major_version = u16::from_be_bytes(major_version_vec.as_slice().try_into()?);
+
+        // Major must match exactly
+        if major_version != P2W_FORMAT_VER_MAJOR {
+            return Err(format!(
+                "Unsupported format major_version {}, expected {}",
+                major_version, P2W_FORMAT_VER_MAJOR
+            )
+            .into());
+        }
+
+        let mut minor_version_vec = vec![0u8; mem::size_of_val(&P2W_FORMAT_VER_MINOR)];
+        bytes.read_exact(minor_version_vec.as_mut_slice())?;
+        let minor_version = u16::from_be_bytes(minor_version_vec.as_slice().try_into()?);
 
-        if version != P2W_FORMAT_VERSION {
+        // Only older minors are not okay for this codebase
+        if minor_version < P2W_FORMAT_VER_MINOR {
             return Err(format!(
-                "Unsupported format version {}, expected {}",
-                version, P2W_FORMAT_VERSION
+                "Unsupported format minor_version {}, expected {} or more",
+                minor_version, 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 +237,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 +252,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 +266,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 +280,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,
-            status: price.agg.status,
-            corp_act: price.agg.corp_act,
-            timestamp: attestation_time,
+            ema_price: price.ema_price.val,
+            ema_conf: price.ema_conf.val as u64,
+            status: price.agg.status.into(),
             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 +303,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 +326,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 +376,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 +408,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 +416,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 +441,19 @@ impl PriceAttestation {
         Ok(Self {
             product_id,
             price_id,
-            price_type,
             price,
+            conf,
             expo,
             ema_price,
             ema_conf,
-            confidence_interval,
-            status,
-            corp_act,
-            timestamp,
+            status: status.into(),
             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 +464,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.into(),
+            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,12 +520,18 @@ 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 {
             price_attestations: attestations,
         };
+        println!("Batch hex struct: {:#02X?}", batch_attestation);
 
         let serialized = batch_attestation.serialize()?;
         println!("Batch hex Bytes: {:02X?}", serialized);

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

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

Некоторые файлы не были показаны из-за большого количества измененных файлов