@@ -7,11 +7,11 @@ import "@pythnetwork/pyth-sdk-solidity/AbstractPyth.sol";
7
7
import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol " ;
8
8
9
9
import "@pythnetwork/pyth-sdk-solidity/PythErrors.sol " ;
10
+ import "@pythnetwork/pyth-sdk-solidity/PythUtils.sol " ;
10
11
import "./PythAccumulator.sol " ;
11
12
import "./PythGetters.sol " ;
12
13
import "./PythSetters.sol " ;
13
14
import "./PythInternalStructs.sol " ;
14
-
15
15
abstract contract Pyth is
16
16
PythGetters ,
17
17
PythSetters ,
@@ -308,6 +308,165 @@ abstract contract Pyth is
308
308
);
309
309
}
310
310
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
+
311
470
function parsePriceFeedUpdatesUnique (
312
471
bytes [] calldata updateData ,
313
472
bytes32 [] calldata priceIds ,
@@ -397,6 +556,19 @@ abstract contract Pyth is
397
556
}
398
557
399
558
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
+ );
401
573
}
402
574
}
0 commit comments