Skip to content

Commit 6f0eb47

Browse files
authored
Merge pull request #2543 from pyth-network/evm-twap
feat(target_chains/ethereum): add twap
2 parents 528e7d9 + fd94f73 commit 6f0eb47

File tree

20 files changed

+1686
-39
lines changed

20 files changed

+1686
-39
lines changed

target_chains/cosmwasm/examples/cw-contract/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 174 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ import "@pythnetwork/pyth-sdk-solidity/AbstractPyth.sol";
77
import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
88

99
import "@pythnetwork/pyth-sdk-solidity/PythErrors.sol";
10+
import "@pythnetwork/pyth-sdk-solidity/PythUtils.sol";
1011
import "./PythAccumulator.sol";
1112
import "./PythGetters.sol";
1213
import "./PythSetters.sol";
1314
import "./PythInternalStructs.sol";
14-
1515
abstract 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

Comments
 (0)