Skip to content

Commit 3e5de9b

Browse files
committed
feat(pyth): enhance TWAP processing to support multiple price feeds and improve validation
1 parent 21b35d4 commit 3e5de9b

File tree

2 files changed

+216
-136
lines changed

2 files changed

+216
-136
lines changed

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

Lines changed: 110 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -379,11 +379,11 @@ abstract contract Pyth is
379379
view
380380
returns (
381381
/// @return newOffset The next position in the update data after processing this TWAP update
382-
/// @return twapPriceInfo The extracted time-weighted average price information
383-
/// @return priceId The unique identifier for this price feed
382+
/// @return priceInfos Array of extracted TWAP price information
383+
/// @return priceIds Array of corresponding price feed IDs
384384
uint newOffset,
385-
PythStructs.TwapPriceInfo memory twapPriceInfo,
386-
bytes32 priceId
385+
PythStructs.TwapPriceInfo[] memory twapPriceInfos,
386+
bytes32[] memory priceIds
387387
)
388388
{
389389
UpdateType updateType;
@@ -417,12 +417,22 @@ abstract contract Pyth is
417417
revert PythErrors.InvalidUpdateData();
418418
}
419419

420-
// Extract start TWAP data with robust error checking
421-
(offset, twapPriceInfo, priceId) = extractTwapPriceInfoFromMerkleProof(
422-
digest,
423-
encoded,
424-
offset
425-
);
420+
// Initialize arrays to store all price infos and ids from this update
421+
twapPriceInfos = new PythStructs.TwapPriceInfo[](numUpdates);
422+
priceIds = new bytes32[](numUpdates);
423+
424+
// Extract each TWAP price info from the merkle proof
425+
for (uint i = 0; i < numUpdates; i++) {
426+
PythStructs.TwapPriceInfo memory twapPriceInfo;
427+
bytes32 priceId;
428+
(
429+
offset,
430+
twapPriceInfo,
431+
priceId
432+
) = extractTwapPriceInfoFromMerkleProof(digest, encoded, offset);
433+
twapPriceInfos[i] = twapPriceInfo;
434+
priceIds[i] = priceId;
435+
}
426436

427437
if (offset != encoded.length) {
428438
revert PythErrors.InvalidTwapUpdateData();
@@ -439,72 +449,109 @@ abstract contract Pyth is
439449
override
440450
returns (PythStructs.TwapPriceFeed[] memory twapPriceFeeds)
441451
{
442-
// TWAP requires pairs of updates (start and end points) for each price feed
443-
// So updateData length must be exactly 2 * number of price feeds
444-
if (updateData.length != priceIds.length * 2) {
452+
// TWAP requires exactly 2 updates: one for the start point and one for the end point
453+
if (updateData.length != 2) {
445454
revert PythErrors.InvalidUpdateData();
446455
}
447456

448457
uint requiredFee = getUpdateFee(updateData);
449458
if (msg.value < requiredFee) revert PythErrors.InsufficientFee();
450459

451-
unchecked {
452-
twapPriceFeeds = new PythStructs.TwapPriceFeed[](priceIds.length);
453-
// Iterate over pairs of updates
454-
for (uint i = 0; i < updateData.length; i += 2) {
460+
// Process start update data
461+
PythStructs.TwapPriceInfo[] memory startTwapPriceInfos;
462+
bytes32[] memory startPriceIds;
463+
{
464+
uint offsetStart;
465+
(
466+
offsetStart,
467+
startTwapPriceInfos,
468+
startPriceIds
469+
) = processSingleTwapUpdate(updateData[0]);
470+
}
471+
472+
// Process end update data
473+
PythStructs.TwapPriceInfo[] memory endTwapPriceInfos;
474+
bytes32[] memory endPriceIds;
475+
{
476+
uint offsetEnd;
477+
(
478+
offsetEnd,
479+
endTwapPriceInfos,
480+
endPriceIds
481+
) = processSingleTwapUpdate(updateData[1]);
482+
}
483+
484+
// Verify that we have the same number of price feeds in start and end updates
485+
if (startPriceIds.length != endPriceIds.length) {
486+
revert PythErrors.InvalidTwapUpdateDataSet();
487+
}
488+
489+
// Create a mapping to check that every startPriceId has a matching endPriceId
490+
// This ensures price feed continuity between start and end points
491+
bool[] memory endPriceIdMatched = new bool[](endPriceIds.length);
492+
for (uint i = 0; i < startPriceIds.length; i++) {
493+
bool foundMatch = false;
494+
for (uint j = 0; j < endPriceIds.length; j++) {
455495
if (
456-
(updateData[i].length > 4 &&
457-
UnsafeCalldataBytesLib.toUint32(updateData[i], 0) ==
458-
ACCUMULATOR_MAGIC) &&
459-
(updateData[i + 1].length > 4 &&
460-
UnsafeCalldataBytesLib.toUint32(updateData[i + 1], 0) ==
461-
ACCUMULATOR_MAGIC)
496+
startPriceIds[i] == endPriceIds[j] && !endPriceIdMatched[j]
462497
) {
463-
uint offsetStart;
464-
uint offsetEnd;
465-
bytes32 priceIdStart;
466-
bytes32 priceIdEnd;
467-
PythStructs.TwapPriceInfo memory twapPriceInfoStart;
468-
PythStructs.TwapPriceInfo memory twapPriceInfoEnd;
469-
(
470-
offsetStart,
471-
twapPriceInfoStart,
472-
priceIdStart
473-
) = processSingleTwapUpdate(updateData[i]);
474-
(
475-
offsetEnd,
476-
twapPriceInfoEnd,
477-
priceIdEnd
478-
) = processSingleTwapUpdate(updateData[i + 1]);
479-
480-
if (priceIdStart != priceIdEnd)
481-
revert PythErrors.InvalidTwapUpdateDataSet();
482-
483-
validateTwapPriceInfo(twapPriceInfoStart, twapPriceInfoEnd);
484-
485-
uint k = findIndexOfPriceId(priceIds, priceIdStart);
486-
487-
// If priceFeed[k].id != 0 then it means that there was a valid
488-
// update for priceIds[k] and we don't need to process this one.
489-
if (k == priceIds.length || twapPriceFeeds[k].id != 0) {
490-
continue;
491-
}
492-
493-
twapPriceFeeds[k] = calculateTwap(
494-
priceIdStart,
495-
twapPriceInfoStart,
496-
twapPriceInfoEnd
497-
);
498-
} else {
499-
revert PythErrors.InvalidUpdateData();
498+
endPriceIdMatched[j] = true;
499+
foundMatch = true;
500+
break;
500501
}
501502
}
503+
// If a price ID in start doesn't have a match in end, it's invalid
504+
if (!foundMatch) {
505+
revert PythErrors.InvalidTwapUpdateDataSet();
506+
}
507+
}
508+
509+
// Initialize the output array
510+
twapPriceFeeds = new PythStructs.TwapPriceFeed[](priceIds.length);
502511

503-
for (uint k = 0; k < priceIds.length; k++) {
504-
if (twapPriceFeeds[k].id == 0) {
505-
revert PythErrors.PriceFeedNotFoundWithinRange();
512+
// For each requested price ID, find matching start and end data points
513+
for (uint i = 0; i < priceIds.length; i++) {
514+
bytes32 requestedPriceId = priceIds[i];
515+
int startIdx = -1;
516+
int endIdx = -1;
517+
518+
// Find the index of this price ID in start and end arrays
519+
for (uint j = 0; j < startPriceIds.length; j++) {
520+
if (startPriceIds[j] == requestedPriceId) {
521+
startIdx = int(j);
522+
break;
506523
}
507524
}
525+
526+
for (uint j = 0; j < endPriceIds.length; j++) {
527+
if (endPriceIds[j] == requestedPriceId) {
528+
endIdx = int(j);
529+
break;
530+
}
531+
}
532+
533+
// If we found both start and end data for this price ID
534+
if (startIdx >= 0 && endIdx >= 0) {
535+
// Validate the pair of price infos
536+
validateTwapPriceInfo(
537+
startTwapPriceInfos[uint(startIdx)],
538+
endTwapPriceInfos[uint(endIdx)]
539+
);
540+
541+
// Calculate TWAP from these data points
542+
twapPriceFeeds[i] = calculateTwap(
543+
requestedPriceId,
544+
startTwapPriceInfos[uint(startIdx)],
545+
endTwapPriceInfos[uint(endIdx)]
546+
);
547+
}
548+
}
549+
550+
// Ensure all requested price IDs were found
551+
for (uint k = 0; k < priceIds.length; k++) {
552+
if (twapPriceFeeds[k].id == 0) {
553+
revert PythErrors.PriceFeedNotFoundWithinRange();
554+
}
508555
}
509556
}
510557

0 commit comments

Comments
 (0)