@@ -379,11 +379,11 @@ abstract contract Pyth is
379
379
view
380
380
returns (
381
381
/// @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
384
384
uint newOffset ,
385
- PythStructs.TwapPriceInfo memory twapPriceInfo ,
386
- bytes32 priceId
385
+ PythStructs.TwapPriceInfo[] memory twapPriceInfos ,
386
+ bytes32 [] memory priceIds
387
387
)
388
388
{
389
389
UpdateType updateType;
@@ -417,12 +417,22 @@ abstract contract Pyth is
417
417
revert PythErrors.InvalidUpdateData ();
418
418
}
419
419
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
+ }
426
436
427
437
if (offset != encoded.length ) {
428
438
revert PythErrors.InvalidTwapUpdateData ();
@@ -439,72 +449,109 @@ abstract contract Pyth is
439
449
override
440
450
returns (PythStructs.TwapPriceFeed[] memory twapPriceFeeds )
441
451
{
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 ) {
445
454
revert PythErrors.InvalidUpdateData ();
446
455
}
447
456
448
457
uint requiredFee = getUpdateFee (updateData);
449
458
if (msg .value < requiredFee) revert PythErrors.InsufficientFee ();
450
459
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++ ) {
455
495
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]
462
497
) {
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 ;
500
501
}
501
502
}
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 );
502
511
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 ;
506
523
}
507
524
}
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
+ }
508
555
}
509
556
}
510
557
0 commit comments