Skip to content

Commit 9ddc7fd

Browse files
authored
Ethereum/parse price feed updates accumulators (#855)
* feat(target-chains/ethereum): add accumulator support for parsePriceFeedUpdates * feat(target-chains/ethereum): working impl & test of parsePriceFeedUpdates w/ accumulator data * refactor(target-contracts/ethereum): refactor pyth accumulator * refactor: remove console logs & imports * refactor(target-chain/eth): refactor and more tests * feat(target-chains/ethereum): address PR feedback refactor, add parse revert tests * chore: fix comment * test(target-chains/ethereum): add/clean up tests * test: add another test * test: address more feedback
1 parent bdc3fed commit 9ddc7fd

File tree

4 files changed

+856
-161
lines changed

4 files changed

+856
-161
lines changed

target_chains/ethereum/contracts/contracts/pyth/Pyth.sol

Lines changed: 146 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ abstract contract Pyth is
8080
updateData[i].length > 4 &&
8181
UnsafeBytesLib.toUint32(updateData[i], 0) == ACCUMULATOR_MAGIC
8282
) {
83-
updatePricesUsingAccumulator(updateData[i]);
83+
updatePriceInfosFromAccumulatorUpdate(updateData[i]);
8484
} else {
8585
updatePriceBatchFromVm(updateData[i]);
8686
}
@@ -432,84 +432,131 @@ abstract contract Pyth is
432432
}
433433

434434
priceFeeds = new PythStructs.PriceFeed[](priceIds.length);
435-
436435
for (uint i = 0; i < updateData.length; i++) {
437-
bytes memory encoded;
438-
439-
{
440-
IWormhole.VM memory vm = parseAndVerifyBatchAttestationVM(
441-
updateData[i]
442-
);
443-
encoded = vm.payload;
444-
}
436+
if (
437+
updateData[i].length > 4 &&
438+
UnsafeBytesLib.toUint32(updateData[i], 0) ==
439+
ACCUMULATOR_MAGIC
440+
) {
441+
(
442+
PythInternalStructs.PriceInfo[]
443+
memory accumulatorPriceInfos,
444+
bytes32[] memory accumulatorPriceIds
445+
) = extractPriceInfosFromAccumulatorUpdate(updateData[i]);
446+
447+
for (
448+
uint accDataIdx = 0;
449+
accDataIdx < accumulatorPriceIds.length;
450+
accDataIdx++
451+
) {
452+
bytes32 accumulatorPriceId = accumulatorPriceIds[
453+
accDataIdx
454+
];
455+
// check whether caller requested for this data
456+
uint k = findIndexOfPriceId(
457+
priceIds,
458+
accumulatorPriceId
459+
);
445460

446-
(
447-
uint index,
448-
uint nAttestations,
449-
uint attestationSize
450-
) = parseBatchAttestationHeader(encoded);
451-
452-
// Deserialize each attestation
453-
for (uint j = 0; j < nAttestations; j++) {
454-
// NOTE: We don't advance the global index immediately.
455-
// attestationIndex is an attestation-local offset used
456-
// for readability and easier debugging.
457-
uint attestationIndex = 0;
458-
459-
// Unused bytes32 product id
460-
attestationIndex += 32;
461-
462-
bytes32 priceId = UnsafeBytesLib.toBytes32(
463-
encoded,
464-
index + attestationIndex
465-
);
461+
// If priceFeed[k].id != 0 then it means that there was a valid
462+
// update for priceIds[k] and we don't need to process this one.
463+
if (k == priceIds.length || priceFeeds[k].id != 0) {
464+
continue;
465+
}
466466

467-
// Check whether the caller requested for this data.
468-
uint k = 0;
469-
for (; k < priceIds.length; k++) {
470-
if (priceIds[k] == priceId) {
471-
break;
467+
PythInternalStructs.PriceInfo
468+
memory info = accumulatorPriceInfos[accDataIdx];
469+
470+
uint publishTime = uint(info.publishTime);
471+
// Check the publish time of the price is within the given range
472+
// and only fill the priceFeedsInfo if it is.
473+
// If is not, default id value of 0 will still be set and
474+
// this will allow other updates for this price id to be processed.
475+
if (
476+
publishTime >= minPublishTime &&
477+
publishTime <= maxPublishTime
478+
) {
479+
fillPriceFeedFromPriceInfo(
480+
priceFeeds,
481+
k,
482+
accumulatorPriceId,
483+
info,
484+
publishTime
485+
);
472486
}
473487
}
474-
475-
// If priceFeed[k].id != 0 then it means that there was a valid
476-
// update for priceIds[k] and we don't need to process this one.
477-
if (k == priceIds.length || priceFeeds[k].id != 0) {
478-
index += attestationSize;
479-
continue;
488+
} else {
489+
bytes memory encoded;
490+
{
491+
IWormhole.VM
492+
memory vm = parseAndVerifyBatchAttestationVM(
493+
updateData[i]
494+
);
495+
encoded = vm.payload;
480496
}
481497

498+
/** Batch price logic */
499+
// TODO: gas optimization
482500
(
483-
PythInternalStructs.PriceInfo memory info,
484-
485-
) = parseSingleAttestationFromBatch(
501+
uint index,
502+
uint nAttestations,
503+
uint attestationSize
504+
) = parseBatchAttestationHeader(encoded);
505+
506+
// Deserialize each attestation
507+
for (uint j = 0; j < nAttestations; j++) {
508+
// NOTE: We don't advance the global index immediately.
509+
// attestationIndex is an attestation-local offset used
510+
// for readability and easier debugging.
511+
uint attestationIndex = 0;
512+
513+
// Unused bytes32 product id
514+
attestationIndex += 32;
515+
516+
bytes32 priceId = UnsafeBytesLib.toBytes32(
486517
encoded,
487-
index,
488-
attestationSize
518+
index + attestationIndex
489519
);
490520

491-
priceFeeds[k].id = priceId;
492-
priceFeeds[k].price.price = info.price;
493-
priceFeeds[k].price.conf = info.conf;
494-
priceFeeds[k].price.expo = info.expo;
495-
priceFeeds[k].price.publishTime = uint(info.publishTime);
496-
priceFeeds[k].emaPrice.price = info.emaPrice;
497-
priceFeeds[k].emaPrice.conf = info.emaConf;
498-
priceFeeds[k].emaPrice.expo = info.expo;
499-
priceFeeds[k].emaPrice.publishTime = uint(info.publishTime);
500-
501-
// Check the publish time of the price is within the given range
502-
// if it is not, then set the id to 0 to indicate that this price id
503-
// still does not have a valid price feed. This will allow other updates
504-
// for this price id to be processed.
505-
if (
506-
priceFeeds[k].price.publishTime < minPublishTime ||
507-
priceFeeds[k].price.publishTime > maxPublishTime
508-
) {
509-
priceFeeds[k].id = 0;
510-
}
521+
// check whether caller requested for this data
522+
uint k = findIndexOfPriceId(priceIds, priceId);
523+
524+
// If priceFeed[k].id != 0 then it means that there was a valid
525+
// update for priceIds[k] and we don't need to process this one.
526+
if (k == priceIds.length || priceFeeds[k].id != 0) {
527+
index += attestationSize;
528+
continue;
529+
}
530+
531+
(
532+
PythInternalStructs.PriceInfo memory info,
533+
534+
) = parseSingleAttestationFromBatch(
535+
encoded,
536+
index,
537+
attestationSize
538+
);
539+
540+
uint publishTime = uint(info.publishTime);
541+
// Check the publish time of the price is within the given range
542+
// and only fill the priceFeedsInfo if it is.
543+
// If is not, default id value of 0 will still be set and
544+
// this will allow other updates for this price id to be processed.
545+
if (
546+
publishTime >= minPublishTime &&
547+
publishTime <= maxPublishTime
548+
) {
549+
fillPriceFeedFromPriceInfo(
550+
priceFeeds,
551+
k,
552+
priceId,
553+
info,
554+
publishTime
555+
);
556+
}
511557

512-
index += attestationSize;
558+
index += attestationSize;
559+
}
513560
}
514561
}
515562

@@ -521,6 +568,38 @@ abstract contract Pyth is
521568
}
522569
}
523570

571+
function findIndexOfPriceId(
572+
bytes32[] calldata priceIds,
573+
bytes32 targetPriceId
574+
) private pure returns (uint index) {
575+
uint k = 0;
576+
uint len = priceIds.length;
577+
for (; k < len; k++) {
578+
if (priceIds[k] == targetPriceId) {
579+
break;
580+
}
581+
}
582+
return k;
583+
}
584+
585+
function fillPriceFeedFromPriceInfo(
586+
PythStructs.PriceFeed[] memory priceFeeds,
587+
uint k,
588+
bytes32 priceId,
589+
PythInternalStructs.PriceInfo memory info,
590+
uint publishTime
591+
) private pure {
592+
priceFeeds[k].id = priceId;
593+
priceFeeds[k].price.price = info.price;
594+
priceFeeds[k].price.conf = info.conf;
595+
priceFeeds[k].price.expo = info.expo;
596+
priceFeeds[k].price.publishTime = publishTime;
597+
priceFeeds[k].emaPrice.price = info.emaPrice;
598+
priceFeeds[k].emaPrice.conf = info.emaConf;
599+
priceFeeds[k].emaPrice.expo = info.expo;
600+
priceFeeds[k].emaPrice.publishTime = publishTime;
601+
}
602+
524603
function queryPriceFeed(
525604
bytes32 id
526605
) public view override returns (PythStructs.PriceFeed memory priceFeed) {

0 commit comments

Comments
 (0)