|
|
@@ -24,36 +24,13 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
|
|
|
setPyth2WormholeEmitter(pyth2WormholeEmitter);
|
|
|
}
|
|
|
|
|
|
- function updatePriceBatchFromVm(bytes calldata encodedVm) private returns (PythInternalStructs.BatchPriceAttestation memory bpa) {
|
|
|
+ function updatePriceBatchFromVm(bytes calldata encodedVm) private {
|
|
|
(IWormhole.VM memory vm, bool valid, string memory reason) = wormhole().parseAndVerifyVM(encodedVm);
|
|
|
|
|
|
require(valid, reason);
|
|
|
require(verifyPythVM(vm), "invalid data source chain/emitter ID");
|
|
|
|
|
|
- PythInternalStructs.BatchPriceAttestation memory batch = parseBatchPriceAttestation(vm.payload);
|
|
|
-
|
|
|
- uint freshPrices = 0;
|
|
|
-
|
|
|
- for (uint i = 0; i < batch.attestations.length; i++) {
|
|
|
- PythInternalStructs.PriceAttestation memory attestation = batch.attestations[i];
|
|
|
-
|
|
|
- PythInternalStructs.PriceInfo memory newPriceInfo = createNewPriceInfo(attestation);
|
|
|
- PythInternalStructs.PriceInfo memory latestPrice = latestPriceInfo(attestation.priceId);
|
|
|
-
|
|
|
- bool fresh = false;
|
|
|
- if(newPriceInfo.price.publishTime > latestPrice.price.publishTime) {
|
|
|
- freshPrices += 1;
|
|
|
- fresh = true;
|
|
|
- setLatestPriceInfo(attestation.priceId, newPriceInfo);
|
|
|
- }
|
|
|
-
|
|
|
- emit PriceFeedUpdate(attestation.priceId, fresh, vm.emitterChainId, vm.sequence, latestPrice.price.publishTime,
|
|
|
- newPriceInfo.price.publishTime, newPriceInfo.price.price, newPriceInfo.price.conf);
|
|
|
- }
|
|
|
-
|
|
|
- emit BatchPriceFeedUpdate(vm.emitterChainId, vm.sequence, batch.attestations.length, freshPrices);
|
|
|
-
|
|
|
- return batch;
|
|
|
+ parseAndProcessBatchPriceAttestation(vm);
|
|
|
}
|
|
|
|
|
|
function updatePriceFeeds(bytes[] calldata updateData) public override payable {
|
|
|
@@ -76,146 +53,163 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
|
|
|
return singleUpdateFeeInWei() * updateData.length;
|
|
|
}
|
|
|
|
|
|
- function createNewPriceInfo(PythInternalStructs.PriceAttestation memory pa) private pure returns (PythInternalStructs.PriceInfo memory info) {
|
|
|
- PythInternalStructs.PriceAttestationStatus status = PythInternalStructs.PriceAttestationStatus(pa.status);
|
|
|
- if (status == PythInternalStructs.PriceAttestationStatus.TRADING) {
|
|
|
- info.price.price = pa.price;
|
|
|
- info.price.conf = pa.conf;
|
|
|
- info.price.publishTime = pa.publishTime;
|
|
|
- info.emaPrice.publishTime = pa.publishTime;
|
|
|
- } else {
|
|
|
- info.price.price = pa.prevPrice;
|
|
|
- info.price.conf = pa.prevConf;
|
|
|
- info.price.publishTime = pa.prevPublishTime;
|
|
|
-
|
|
|
- // The EMA is last updated when the aggregate had trading status,
|
|
|
- // so, we use prev_publish_time (the time when the aggregate last had trading status).
|
|
|
- info.emaPrice.publishTime = pa.prevPublishTime;
|
|
|
- }
|
|
|
-
|
|
|
- info.price.expo = pa.expo;
|
|
|
- info.emaPrice.price = pa.emaPrice;
|
|
|
- info.emaPrice.conf = pa.emaConf;
|
|
|
- info.emaPrice.expo = pa.expo;
|
|
|
-
|
|
|
- return info;
|
|
|
- }
|
|
|
-
|
|
|
function verifyPythVM(IWormhole.VM memory vm) private view returns (bool valid) {
|
|
|
return isValidDataSource(vm.emitterChainId, vm.emitterAddress);
|
|
|
}
|
|
|
|
|
|
-
|
|
|
- function parseBatchPriceAttestation(bytes memory encoded) public pure returns (PythInternalStructs.BatchPriceAttestation memory bpa) {
|
|
|
+ function parseAndProcessBatchPriceAttestation(IWormhole.VM memory vm) internal {
|
|
|
+ bytes memory encoded = vm.payload;
|
|
|
uint index = 0;
|
|
|
|
|
|
// Check header
|
|
|
- bpa.header.magic = encoded.toUint32(index);
|
|
|
- index += 4;
|
|
|
- require(bpa.header.magic == 0x50325748, "invalid magic value");
|
|
|
-
|
|
|
- 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;
|
|
|
-
|
|
|
- // 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);
|
|
|
-
|
|
|
- // 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");
|
|
|
+ {
|
|
|
+ uint32 magic = encoded.toUint32(index);
|
|
|
+ index += 4;
|
|
|
+ require(magic == 0x50325748, "invalid magic value");
|
|
|
+
|
|
|
+ uint16 versionMajor = encoded.toUint16(index);
|
|
|
+ index += 2;
|
|
|
+ require(versionMajor == 3, "invalid version major, expected 3");
|
|
|
+
|
|
|
+ uint16 versionMinor = encoded.toUint16(index);
|
|
|
+ index += 2;
|
|
|
+ require(versionMinor >= 0, "invalid version minor, expected 0 or more");
|
|
|
+
|
|
|
+ uint16 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;
|
|
|
+
|
|
|
+ uint8 payloadId = encoded.toUint8(index);
|
|
|
+
|
|
|
+ // Skip remaining unknown header bytes
|
|
|
+ index += hdrSize;
|
|
|
+
|
|
|
+ // Payload ID of 2 required for batch headerBa
|
|
|
+ require(payloadId == 2, "invalid payload ID, expected 2 for BatchPriceAttestation");
|
|
|
+ }
|
|
|
|
|
|
// Parse the number of attestations
|
|
|
- bpa.nAttestations = encoded.toUint16(index);
|
|
|
+ uint16 nAttestations = encoded.toUint16(index);
|
|
|
index += 2;
|
|
|
|
|
|
// Parse the attestation size
|
|
|
- bpa.attestationSize = encoded.toUint16(index);
|
|
|
+ uint16 attestationSize = encoded.toUint16(index);
|
|
|
index += 2;
|
|
|
- require(encoded.length == (index + (bpa.attestationSize * bpa.nAttestations)), "invalid BatchPriceAttestation size");
|
|
|
-
|
|
|
- bpa.attestations = new PythInternalStructs.PriceAttestation[](bpa.nAttestations);
|
|
|
+ require(encoded.length == (index + (attestationSize * nAttestations)), "invalid BatchPriceAttestation size");
|
|
|
|
|
|
+ PythInternalStructs.PriceInfo memory info;
|
|
|
+ bytes32 priceId;
|
|
|
+ uint freshPrices = 0;
|
|
|
+
|
|
|
// Deserialize each attestation
|
|
|
- for (uint j=0; j < bpa.nAttestations; j++) {
|
|
|
+ for (uint j=0; j < 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;
|
|
|
|
|
|
- // Attestation
|
|
|
- bpa.attestations[j].productId = encoded.toBytes32(index + attestationIndex);
|
|
|
+ // Unused bytes32 product id
|
|
|
attestationIndex += 32;
|
|
|
|
|
|
- bpa.attestations[j].priceId = encoded.toBytes32(index + attestationIndex);
|
|
|
+ priceId = encoded.toBytes32(index + attestationIndex);
|
|
|
attestationIndex += 32;
|
|
|
|
|
|
- bpa.attestations[j].price = int64(encoded.toUint64(index + attestationIndex));
|
|
|
+ info.price.price = int64(encoded.toUint64(index + attestationIndex));
|
|
|
attestationIndex += 8;
|
|
|
|
|
|
- bpa.attestations[j].conf = encoded.toUint64(index + attestationIndex);
|
|
|
+ info.price.conf = encoded.toUint64(index + attestationIndex);
|
|
|
attestationIndex += 8;
|
|
|
|
|
|
- bpa.attestations[j].expo = int32(encoded.toUint32(index + attestationIndex));
|
|
|
+ info.price.expo = int32(encoded.toUint32(index + attestationIndex));
|
|
|
+ info.emaPrice.expo = info.price.expo;
|
|
|
attestationIndex += 4;
|
|
|
|
|
|
- bpa.attestations[j].emaPrice = int64(encoded.toUint64(index + attestationIndex));
|
|
|
+ info.emaPrice.price = int64(encoded.toUint64(index + attestationIndex));
|
|
|
attestationIndex += 8;
|
|
|
|
|
|
- bpa.attestations[j].emaConf = encoded.toUint64(index + attestationIndex);
|
|
|
+ info.emaPrice.conf = encoded.toUint64(index + attestationIndex);
|
|
|
attestationIndex += 8;
|
|
|
|
|
|
- bpa.attestations[j].status = encoded.toUint8(index + attestationIndex);
|
|
|
- attestationIndex += 1;
|
|
|
-
|
|
|
- bpa.attestations[j].numPublishers = encoded.toUint32(index + attestationIndex);
|
|
|
- attestationIndex += 4;
|
|
|
-
|
|
|
- bpa.attestations[j].maxNumPublishers = encoded.toUint32(index + attestationIndex);
|
|
|
- attestationIndex += 4;
|
|
|
+ {
|
|
|
+ // Status is an enum (encoded as uint8) with the following values:
|
|
|
+ // 0 = UNKNOWN: The price feed is not currently updating for an unknown reason.
|
|
|
+ // 1 = TRADING: The price feed is updating as expected.
|
|
|
+ // 2 = HALTED: The price feed is not currently updating because trading in the product has been halted.
|
|
|
+ // 3 = AUCTION: The price feed is not currently updating because an auction is setting the price.
|
|
|
+ uint8 status = encoded.toUint8(index + attestationIndex);
|
|
|
+ attestationIndex += 1;
|
|
|
+
|
|
|
+ // Unused uint32 numPublishers
|
|
|
+ attestationIndex += 4;
|
|
|
+
|
|
|
+ // Unused uint32 numPublishers
|
|
|
+ attestationIndex += 4;
|
|
|
+
|
|
|
+ // Unused uint64 attestationTime
|
|
|
+ attestationIndex += 8;
|
|
|
+
|
|
|
+ info.price.publishTime = encoded.toUint64(index + attestationIndex);
|
|
|
+ info.emaPrice.publishTime = info.price.publishTime;
|
|
|
+ attestationIndex += 8;
|
|
|
+
|
|
|
+ if (status == 1) { // status == TRADING
|
|
|
+ attestationIndex += 24;
|
|
|
+ } else {
|
|
|
+ // If status is not trading then the latest available price is
|
|
|
+ // the previous price info that are passed here.
|
|
|
+
|
|
|
+ // Previous publish time
|
|
|
+ info.price.publishTime = encoded.toUint64(index + attestationIndex);
|
|
|
+ attestationIndex += 8;
|
|
|
+
|
|
|
+ // Previous price
|
|
|
+ info.price.price = int64(encoded.toUint64(index + attestationIndex));
|
|
|
+ attestationIndex += 8;
|
|
|
+
|
|
|
+ // Previous confidence
|
|
|
+ info.price.conf = encoded.toUint64(index + attestationIndex);
|
|
|
+ attestationIndex += 8;
|
|
|
+
|
|
|
+ // The EMA is last updated when the aggregate had trading status,
|
|
|
+ // so, we use previous publish time here too.
|
|
|
+ info.emaPrice.publishTime = info.price.publishTime;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- bpa.attestations[j].attestationTime = encoded.toUint64(index + attestationIndex);
|
|
|
- attestationIndex += 8;
|
|
|
+ require(attestationIndex <= attestationSize, "INTERNAL: Consumed more than `attestationSize` bytes");
|
|
|
|
|
|
- bpa.attestations[j].publishTime = encoded.toUint64(index + attestationIndex);
|
|
|
- attestationIndex += 8;
|
|
|
+ // Respect specified attestation size for forward-compat
|
|
|
+ index += attestationSize;
|
|
|
|
|
|
- bpa.attestations[j].prevPublishTime = encoded.toUint64(index + attestationIndex);
|
|
|
- attestationIndex += 8;
|
|
|
+ // Store the attestation
|
|
|
+ PythInternalStructs.PriceInfo memory latestPrice = latestPriceInfo(priceId);
|
|
|
|
|
|
- bpa.attestations[j].prevPrice = int64(encoded.toUint64(index + attestationIndex));
|
|
|
- attestationIndex += 8;
|
|
|
+ bool fresh = false;
|
|
|
+ if(info.price.publishTime > latestPrice.price.publishTime) {
|
|
|
+ freshPrices += 1;
|
|
|
+ fresh = true;
|
|
|
+ setLatestPriceInfo(priceId, info);
|
|
|
+ }
|
|
|
|
|
|
- bpa.attestations[j].prevConf = encoded.toUint64(index + attestationIndex);
|
|
|
- attestationIndex += 8;
|
|
|
+ emit PriceFeedUpdate(priceId, fresh, vm.emitterChainId, vm.sequence, latestPrice.price.publishTime,
|
|
|
+ info.price.publishTime, info.price.price, info.price.conf);
|
|
|
+ }
|
|
|
|
|
|
- require(attestationIndex <= bpa.attestationSize, "INTERNAL: Consumed more than `attestationSize` bytes");
|
|
|
|
|
|
- // Respect specified attestation size for forward-compat
|
|
|
- index += bpa.attestationSize;
|
|
|
- }
|
|
|
+ emit BatchPriceFeedUpdate(vm.emitterChainId, vm.sequence, nAttestations, freshPrices);
|
|
|
}
|
|
|
|
|
|
function queryPriceFeed(bytes32 id) public view override returns (PythStructs.PriceFeed memory priceFeed){
|