@@ -7,11 +7,11 @@ import "@pythnetwork/pyth-sdk-solidity/AbstractPyth.sol";
77import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol " ;
88
99import "@pythnetwork/pyth-sdk-solidity/PythErrors.sol " ;
10+ import "@pythnetwork/pyth-sdk-solidity/PythUtils.sol " ;
1011import "./PythAccumulator.sol " ;
1112import "./PythGetters.sol " ;
1213import "./PythSetters.sol " ;
1314import "./PythInternalStructs.sol " ;
14-
1515abstract contract Pyth is
1616 PythGetters ,
1717 PythSetters ,
@@ -308,6 +308,165 @@ abstract contract Pyth is
308308 );
309309 }
310310
311+ function processSingleTwapUpdate (
312+ bytes calldata updateData
313+ )
314+ private
315+ view
316+ returns (
317+ /// @return newOffset The next position in the update data after processing this TWAP update
318+ /// @return twapPriceInfo The extracted time-weighted average price information
319+ /// @return priceId The unique identifier for this price feed
320+ uint newOffset ,
321+ PythStructs.TwapPriceInfo memory twapPriceInfo ,
322+ bytes32 priceId
323+ )
324+ {
325+ UpdateType updateType;
326+ uint offset;
327+ bytes20 digest;
328+ uint8 numUpdates;
329+ bytes calldata encoded;
330+ // Extract and validate the header for start data
331+ (offset, updateType) = extractUpdateTypeFromAccumulatorHeader (
332+ updateData
333+ );
334+
335+ if (updateType != UpdateType.WormholeMerkle) {
336+ revert PythErrors.InvalidUpdateData ();
337+ }
338+
339+ (
340+ offset,
341+ digest,
342+ numUpdates,
343+ encoded
344+ ) = extractWormholeMerkleHeaderDigestAndNumUpdatesAndEncodedFromAccumulatorUpdate (
345+ updateData,
346+ offset
347+ );
348+
349+ // Add additional validation before extracting TWAP price info
350+ if (offset >= updateData.length ) {
351+ revert PythErrors.InvalidUpdateData ();
352+ }
353+
354+ // Extract start TWAP data with robust error checking
355+ (offset, twapPriceInfo, priceId) = extractTwapPriceInfoFromMerkleProof (
356+ digest,
357+ encoded,
358+ offset
359+ );
360+
361+ if (offset != encoded.length ) {
362+ revert PythErrors.InvalidTwapUpdateData ();
363+ }
364+ newOffset = offset;
365+ }
366+
367+ function parseTwapPriceFeedUpdates (
368+ bytes [] calldata updateData ,
369+ bytes32 [] calldata priceIds
370+ )
371+ external
372+ payable
373+ override
374+ returns (PythStructs.TwapPriceFeed[] memory twapPriceFeeds )
375+ {
376+ // TWAP requires exactly 2 updates - one for the start point and one for the end point
377+ // to calculate the time-weighted average price between those two points
378+ if (updateData.length != 2 ) {
379+ revert PythErrors.InvalidUpdateData ();
380+ }
381+
382+ uint requiredFee = getUpdateFee (updateData);
383+ if (msg .value < requiredFee) revert PythErrors.InsufficientFee ();
384+
385+ unchecked {
386+ twapPriceFeeds = new PythStructs.TwapPriceFeed [](priceIds.length );
387+ for (uint i = 0 ; i < updateData.length - 1 ; i++ ) {
388+ if (
389+ (updateData[i].length > 4 &&
390+ UnsafeCalldataBytesLib.toUint32 (updateData[i], 0 ) ==
391+ ACCUMULATOR_MAGIC) &&
392+ (updateData[i + 1 ].length > 4 &&
393+ UnsafeCalldataBytesLib.toUint32 (updateData[i + 1 ], 0 ) ==
394+ ACCUMULATOR_MAGIC)
395+ ) {
396+ uint offsetStart;
397+ uint offsetEnd;
398+ bytes32 priceIdStart;
399+ bytes32 priceIdEnd;
400+ PythStructs.TwapPriceInfo memory twapPriceInfoStart;
401+ PythStructs.TwapPriceInfo memory twapPriceInfoEnd;
402+ (
403+ offsetStart,
404+ twapPriceInfoStart,
405+ priceIdStart
406+ ) = processSingleTwapUpdate (updateData[i]);
407+ (
408+ offsetEnd,
409+ twapPriceInfoEnd,
410+ priceIdEnd
411+ ) = processSingleTwapUpdate (updateData[i + 1 ]);
412+
413+ if (priceIdStart != priceIdEnd)
414+ revert PythErrors.InvalidTwapUpdateDataSet ();
415+
416+ validateTwapPriceInfo (twapPriceInfoStart, twapPriceInfoEnd);
417+
418+ uint k = findIndexOfPriceId (priceIds, priceIdStart);
419+
420+ // If priceFeed[k].id != 0 then it means that there was a valid
421+ // update for priceIds[k] and we don't need to process this one.
422+ if (k == priceIds.length || twapPriceFeeds[k].id != 0 ) {
423+ continue ;
424+ }
425+
426+ twapPriceFeeds[k] = calculateTwap (
427+ priceIdStart,
428+ twapPriceInfoStart,
429+ twapPriceInfoEnd
430+ );
431+ } else {
432+ revert PythErrors.InvalidUpdateData ();
433+ }
434+ }
435+
436+ for (uint k = 0 ; k < priceIds.length ; k++ ) {
437+ if (twapPriceFeeds[k].id == 0 ) {
438+ revert PythErrors.PriceFeedNotFoundWithinRange ();
439+ }
440+ }
441+ }
442+ }
443+
444+ function validateTwapPriceInfo (
445+ PythStructs.TwapPriceInfo memory twapPriceInfoStart ,
446+ PythStructs.TwapPriceInfo memory twapPriceInfoEnd
447+ ) private pure {
448+ // First validate each individual price's uniqueness
449+ if (
450+ twapPriceInfoStart.prevPublishTime >= twapPriceInfoStart.publishTime
451+ ) {
452+ revert PythErrors.InvalidTwapUpdateData ();
453+ }
454+ if (twapPriceInfoEnd.prevPublishTime >= twapPriceInfoEnd.publishTime) {
455+ revert PythErrors.InvalidTwapUpdateData ();
456+ }
457+
458+ // Then validate the relationship between the two data points
459+ if (twapPriceInfoStart.expo != twapPriceInfoEnd.expo) {
460+ revert PythErrors.InvalidTwapUpdateDataSet ();
461+ }
462+ if (twapPriceInfoStart.publishSlot > twapPriceInfoEnd.publishSlot) {
463+ revert PythErrors.InvalidTwapUpdateDataSet ();
464+ }
465+ if (twapPriceInfoStart.publishTime > twapPriceInfoEnd.publishTime) {
466+ revert PythErrors.InvalidTwapUpdateDataSet ();
467+ }
468+ }
469+
311470 function parsePriceFeedUpdatesUnique (
312471 bytes [] calldata updateData ,
313472 bytes32 [] calldata priceIds ,
@@ -397,6 +556,19 @@ abstract contract Pyth is
397556 }
398557
399558 function version () public pure returns (string memory ) {
400- return "1.4.4-alpha.1 " ;
559+ return "1.4.4-alpha.2 " ;
560+ }
561+
562+ function calculateTwap (
563+ bytes32 priceId ,
564+ PythStructs.TwapPriceInfo memory twapPriceInfoStart ,
565+ PythStructs.TwapPriceInfo memory twapPriceInfoEnd
566+ ) private pure returns (PythStructs.TwapPriceFeed memory ) {
567+ return
568+ PythUtils.calculateTwap (
569+ priceId,
570+ twapPriceInfoStart,
571+ twapPriceInfoEnd
572+ );
401573 }
402574}
0 commit comments