From fa08cf03e78571f4209185d5c5007395ad2bc43e Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 11 Dec 2024 20:50:45 +0000 Subject: [PATCH] feat(ethereum): implement TWAP verification method - Add TwapInfo struct for storing TWAP data - Add latestTwapInfo mapping to state - Implement updateTwapFeed for TWAP updates - Add validateTwapMessages for validation - Add calculateAndStoreTwap for computation - Add getTwapFeed for retrieval - Follow gas-efficient patterns Co-Authored-By: Jayant Krishnamurthy --- .../contracts/contracts/pyth/Pyth.sol | 84 +++++++++++++++++++ .../contracts/pyth/PythInternalStructs.sol | 9 ++ .../contracts/contracts/pyth/PythState.sol | 3 + 3 files changed, 96 insertions(+) diff --git a/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol b/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol index 3c17bb0ab7..ae0f2804c4 100644 --- a/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol +++ b/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol @@ -394,4 +394,88 @@ abstract contract Pyth is function version() public pure returns (string memory) { return "1.4.3"; } + + // TWAP verification and update methods + function updateTwapFeed( + bytes[] calldata updateData, + bytes32 feedId, + uint64 startTime, + uint64 endTime + ) external payable { + // Parse and validate both start and end updates + PythStructs.PriceFeed[] memory startFeed = parsePriceFeedUpdatesInternal( + updateData, + new bytes32[](1), + PythInternalStructs.ParseConfig(startTime, startTime, true) + ); + + PythStructs.PriceFeed[] memory endFeed = parsePriceFeedUpdatesInternal( + updateData, + new bytes32[](1), + PythInternalStructs.ParseConfig(endTime, endTime, true) + ); + + // Validate TWAP requirements + validateTwapMessages(startFeed[0], endFeed[0], feedId); + + // Calculate and store TWAP + calculateAndStoreTwap(startFeed[0], endFeed[0]); + } + + function validateTwapMessages( + PythStructs.PriceFeed memory startFeed, + PythStructs.PriceFeed memory endFeed, + bytes32 expectedFeedId + ) internal pure { + // Validate feed IDs match expected ID + if (startFeed.id != expectedFeedId || endFeed.id != expectedFeedId) + revert PythErrors.InvalidArgument(); + + // Validate feed IDs match each other + if (startFeed.id != endFeed.id) + revert PythErrors.InvalidArgument(); + + // Validate exponents match + if (startFeed.price.expo != endFeed.price.expo) + revert PythErrors.InvalidArgument(); + + // Validate time order + if (startFeed.price.publishTime >= endFeed.price.publishTime) + revert PythErrors.InvalidArgument(); + } + + function calculateAndStoreTwap( + PythStructs.PriceFeed memory startFeed, + PythStructs.PriceFeed memory endFeed + ) internal returns (PythInternalStructs.TwapInfo memory) { + // Calculate time-weighted average price + uint64 timeDiff = uint64(endFeed.price.publishTime - startFeed.price.publishTime); + int64 priceDiff = endFeed.price.price - startFeed.price.price; + + // Calculate TWAP using linear interpolation + int64 twapPrice = startFeed.price.price + (priceDiff * int64(timeDiff) / int64(timeDiff)); + + // Create TWAP info + PythInternalStructs.TwapInfo memory twapInfo = PythInternalStructs.TwapInfo({ + feedId: startFeed.id, + startTime: uint64(startFeed.price.publishTime), + endTime: uint64(endFeed.price.publishTime), + price: twapPrice, + conf: endFeed.price.conf, // Use the end confidence + exponent: startFeed.price.expo + }); + + // Store TWAP info in state + _state.latestTwapInfo[startFeed.id] = twapInfo; + + return twapInfo; + } + + function getTwapFeed( + bytes32 id + ) public view returns (PythInternalStructs.TwapInfo memory twapInfo) { + twapInfo = _state.latestTwapInfo[id]; + if (twapInfo.startTime == 0) revert PythErrors.PriceFeedNotFound(); + return twapInfo; + } } diff --git a/target_chains/ethereum/contracts/contracts/pyth/PythInternalStructs.sol b/target_chains/ethereum/contracts/contracts/pyth/PythInternalStructs.sol index 5da6e1bb56..31c4759435 100644 --- a/target_chains/ethereum/contracts/contracts/pyth/PythInternalStructs.sol +++ b/target_chains/ethereum/contracts/contracts/pyth/PythInternalStructs.sol @@ -30,4 +30,13 @@ contract PythInternalStructs { uint16 chainId; bytes32 emitterAddress; } + + struct TwapInfo { + bytes32 feedId; + uint64 startTime; + uint64 endTime; + int64 price; + uint64 conf; + int32 exponent; + } } diff --git a/target_chains/ethereum/contracts/contracts/pyth/PythState.sol b/target_chains/ethereum/contracts/contracts/pyth/PythState.sol index 6829cf9ba1..dddfe39750 100644 --- a/target_chains/ethereum/contracts/contracts/pyth/PythState.sol +++ b/target_chains/ethereum/contracts/contracts/pyth/PythState.sol @@ -38,6 +38,9 @@ contract PythStorage { // Mapping of cached price information // priceId => PriceInfo mapping(bytes32 => PythInternalStructs.PriceInfo) latestPriceInfo; + // Mapping of TWAP information + // priceId => TwapInfo + mapping(bytes32 => PythInternalStructs.TwapInfo) latestTwapInfo; } }