diff --git a/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol b/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol index 7e02cd8aea..18d51ca5f8 100644 --- a/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol +++ b/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol @@ -120,6 +120,37 @@ abstract contract Pyth is return getTotalFee(totalNumUpdates); } + function getTwapUpdateFee( + bytes[] calldata updateData + ) public view override returns (uint feeAmount) { + uint totalNumUpdates = 0; + // For TWAP updates, updateData is always length 2 (start and end points), + // but each VAA can contain multiple price feeds. We only need to count + // the number of updates in the first VAA since both VAAs will have the + // same number of price feeds. + if ( + updateData[0].length > 4 && + UnsafeCalldataBytesLib.toUint32(updateData[0], 0) == + ACCUMULATOR_MAGIC + ) { + ( + uint offset, + UpdateType updateType + ) = extractUpdateTypeFromAccumulatorHeader(updateData[0]); + if (updateType != UpdateType.WormholeMerkle) { + revert PythErrors.InvalidUpdateData(); + } + totalNumUpdates += parseWormholeMerkleHeaderNumUpdates( + updateData[0], + offset + ); + } else { + revert PythErrors.InvalidUpdateData(); + } + + return getTotalFee(totalNumUpdates); + } + // This is an overwrite of the same method in AbstractPyth.sol // to be more gas efficient. function updatePriceFeedsIfNecessary( @@ -372,18 +403,18 @@ abstract contract Pyth is ); } - function processSingleTwapUpdate( + function extractTwapPriceInfos( bytes calldata updateData ) private view returns ( /// @return newOffset The next position in the update data after processing this TWAP update - /// @return twapPriceInfo The extracted time-weighted average price information - /// @return priceId The unique identifier for this price feed + /// @return priceInfos Array of extracted TWAP price information + /// @return priceIds Array of corresponding price feed IDs uint newOffset, - PythStructs.TwapPriceInfo memory twapPriceInfo, - bytes32 priceId + PythStructs.TwapPriceInfo[] memory twapPriceInfos, + bytes32[] memory priceIds ) { UpdateType updateType; @@ -417,12 +448,22 @@ abstract contract Pyth is revert PythErrors.InvalidUpdateData(); } - // Extract start TWAP data with robust error checking - (offset, twapPriceInfo, priceId) = extractTwapPriceInfoFromMerkleProof( - digest, - encoded, - offset - ); + // Initialize arrays to store all price infos and ids from this update + twapPriceInfos = new PythStructs.TwapPriceInfo[](numUpdates); + priceIds = new bytes32[](numUpdates); + + // Extract each TWAP price info from the merkle proof + for (uint i = 0; i < numUpdates; i++) { + PythStructs.TwapPriceInfo memory twapPriceInfo; + bytes32 priceId; + ( + offset, + twapPriceInfo, + priceId + ) = extractTwapPriceInfoFromMerkleProof(digest, encoded, offset); + twapPriceInfos[i] = twapPriceInfo; + priceIds[i] = priceId; + } if (offset != encoded.length) { revert PythErrors.InvalidTwapUpdateData(); @@ -439,71 +480,89 @@ abstract contract Pyth is override returns (PythStructs.TwapPriceFeed[] memory twapPriceFeeds) { - // TWAP requires exactly 2 updates - one for the start point and one for the end point - // to calculate the time-weighted average price between those two points + // TWAP requires exactly 2 updates: one for the start point and one for the end point if (updateData.length != 2) { revert PythErrors.InvalidUpdateData(); } - uint requiredFee = getUpdateFee(updateData); + uint requiredFee = getTwapUpdateFee(updateData); if (msg.value < requiredFee) revert PythErrors.InsufficientFee(); - unchecked { - twapPriceFeeds = new PythStructs.TwapPriceFeed[](priceIds.length); - for (uint i = 0; i < updateData.length - 1; i++) { - if ( - (updateData[i].length > 4 && - UnsafeCalldataBytesLib.toUint32(updateData[i], 0) == - ACCUMULATOR_MAGIC) && - (updateData[i + 1].length > 4 && - UnsafeCalldataBytesLib.toUint32(updateData[i + 1], 0) == - ACCUMULATOR_MAGIC) - ) { - uint offsetStart; - uint offsetEnd; - bytes32 priceIdStart; - bytes32 priceIdEnd; - PythStructs.TwapPriceInfo memory twapPriceInfoStart; - PythStructs.TwapPriceInfo memory twapPriceInfoEnd; - ( - offsetStart, - twapPriceInfoStart, - priceIdStart - ) = processSingleTwapUpdate(updateData[i]); - ( - offsetEnd, - twapPriceInfoEnd, - priceIdEnd - ) = processSingleTwapUpdate(updateData[i + 1]); - - if (priceIdStart != priceIdEnd) - revert PythErrors.InvalidTwapUpdateDataSet(); - - validateTwapPriceInfo(twapPriceInfoStart, twapPriceInfoEnd); - - uint k = findIndexOfPriceId(priceIds, priceIdStart); - - // If priceFeed[k].id != 0 then it means that there was a valid - // update for priceIds[k] and we don't need to process this one. - if (k == priceIds.length || twapPriceFeeds[k].id != 0) { - continue; - } - - twapPriceFeeds[k] = calculateTwap( - priceIdStart, - twapPriceInfoStart, - twapPriceInfoEnd - ); - } else { - revert PythErrors.InvalidUpdateData(); - } + // Process start update data + PythStructs.TwapPriceInfo[] memory startTwapPriceInfos; + bytes32[] memory startPriceIds; + { + uint offsetStart; + ( + offsetStart, + startTwapPriceInfos, + startPriceIds + ) = extractTwapPriceInfos(updateData[0]); + } + + // Process end update data + PythStructs.TwapPriceInfo[] memory endTwapPriceInfos; + bytes32[] memory endPriceIds; + { + uint offsetEnd; + (offsetEnd, endTwapPriceInfos, endPriceIds) = extractTwapPriceInfos( + updateData[1] + ); + } + + // Verify that we have the same number of price feeds in start and end updates + if (startPriceIds.length != endPriceIds.length) { + revert PythErrors.InvalidTwapUpdateDataSet(); + } + + // Hermes always returns price feeds in the same order for start and end updates + // This allows us to assume startPriceIds[i] == endPriceIds[i] for efficiency + for (uint i = 0; i < startPriceIds.length; i++) { + if (startPriceIds[i] != endPriceIds[i]) { + revert PythErrors.InvalidTwapUpdateDataSet(); } + } - for (uint k = 0; k < priceIds.length; k++) { - if (twapPriceFeeds[k].id == 0) { - revert PythErrors.PriceFeedNotFoundWithinRange(); + // Initialize the output array + twapPriceFeeds = new PythStructs.TwapPriceFeed[](priceIds.length); + + // For each requested price ID, find matching start and end data points + for (uint i = 0; i < priceIds.length; i++) { + bytes32 requestedPriceId = priceIds[i]; + int startIdx = -1; + + // Find the index of this price ID in the startPriceIds array + // (which is the same as the endPriceIds array based on our validation above) + for (uint j = 0; j < startPriceIds.length; j++) { + if (startPriceIds[j] == requestedPriceId) { + startIdx = int(j); + break; } } + + // If we found the price ID + if (startIdx >= 0) { + uint idx = uint(startIdx); + // Validate the pair of price infos + validateTwapPriceInfo( + startTwapPriceInfos[idx], + endTwapPriceInfos[idx] + ); + + // Calculate TWAP from these data points + twapPriceFeeds[i] = calculateTwap( + requestedPriceId, + startTwapPriceInfos[idx], + endTwapPriceInfos[idx] + ); + } + } + + // Ensure all requested price IDs were found + for (uint k = 0; k < priceIds.length; k++) { + if (twapPriceFeeds[k].id == 0) { + revert PythErrors.PriceFeedNotFoundWithinRange(); + } } } diff --git a/target_chains/ethereum/contracts/forge-test/Pyth.t.sol b/target_chains/ethereum/contracts/forge-test/Pyth.t.sol index ea86542a7e..a9bab2548c 100644 --- a/target_chains/ethereum/contracts/forge-test/Pyth.t.sol +++ b/target_chains/ethereum/contracts/forge-test/Pyth.t.sol @@ -27,16 +27,18 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils { uint8 constant MERKLE_TREE_DEPTH = 9; // Base TWAP messages that will be used as templates for tests - TwapPriceFeedMessage[1] baseTwapStartMessages; - TwapPriceFeedMessage[1] baseTwapEndMessages; - bytes32[1] basePriceIds; + TwapPriceFeedMessage[2] baseTwapStartMessages; + TwapPriceFeedMessage[2] baseTwapEndMessages; + bytes32[2] basePriceIds; function setUp() public { pyth = IPyth(setUpPyth(setUpWormholeReceiver(NUM_GUARDIAN_SIGNERS))); - // Initialize base TWAP messages + // Initialize base TWAP messages for two price feeds basePriceIds[0] = bytes32(uint256(1)); + basePriceIds[1] = bytes32(uint256(2)); + // First price feed TWAP messages baseTwapStartMessages[0] = TwapPriceFeedMessage({ priceId: basePriceIds[0], cumulativePrice: 100_000, // Base cumulative value @@ -58,6 +60,29 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils { prevPublishTime: 1000, expo: -8 }); + + // Second price feed TWAP messages + baseTwapStartMessages[1] = TwapPriceFeedMessage({ + priceId: basePriceIds[1], + cumulativePrice: 500_000, // Different base cumulative value + cumulativeConf: 20_000, // Different base cumulative conf + numDownSlots: 0, + publishSlot: 1000, + publishTime: 1000, + prevPublishTime: 900, + expo: -8 + }); + + baseTwapEndMessages[1] = TwapPriceFeedMessage({ + priceId: basePriceIds[1], + cumulativePrice: 800_000, // Increased by 300_000 + cumulativeConf: 40_000, // Increased by 20_000 + numDownSlots: 0, + publishSlot: 1100, + publishTime: 1100, + prevPublishTime: 1000, + expo: -8 + }); } function generateRandomPriceMessages( @@ -139,44 +164,61 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils { ) public returns (bytes[] memory updateData, uint updateFee) { require(messages.length >= 2, "At least 2 messages required for TWAP"); - // Create TWAP messages from regular price feed messages - // For TWAP calculation, we need cumulative values that increase over time + // Create arrays to hold the start and end TWAP messages for all price feeds TwapPriceFeedMessage[] - memory startTwapMessages = new TwapPriceFeedMessage[](1); - startTwapMessages[0].priceId = messages[0].priceId; - // For test purposes, we'll set cumulative values for start message - startTwapMessages[0].cumulativePrice = int128(messages[0].price) * 1000; - startTwapMessages[0].cumulativeConf = uint128(messages[0].conf) * 1000; - startTwapMessages[0].numDownSlots = 0; // No down slots for testing - startTwapMessages[0].expo = messages[0].expo; - startTwapMessages[0].publishTime = messages[0].publishTime; - startTwapMessages[0].prevPublishTime = messages[0].prevPublishTime; - startTwapMessages[0].publishSlot = 1000; // Start slot - + memory startTwapMessages = new TwapPriceFeedMessage[]( + messages.length / 2 + ); TwapPriceFeedMessage[] - memory endTwapMessages = new TwapPriceFeedMessage[](1); - endTwapMessages[0].priceId = messages[1].priceId; - // For end message, make sure cumulative values are higher than start - endTwapMessages[0].cumulativePrice = - int128(messages[1].price) * - 1000 + - startTwapMessages[0].cumulativePrice; - endTwapMessages[0].cumulativeConf = - uint128(messages[1].conf) * - 1000 + - startTwapMessages[0].cumulativeConf; - endTwapMessages[0].numDownSlots = 0; // No down slots for testing - endTwapMessages[0].expo = messages[1].expo; - endTwapMessages[0].publishTime = messages[1].publishTime; - endTwapMessages[0].prevPublishTime = messages[1].prevPublishTime; - endTwapMessages[0].publishSlot = 1100; // End slot (100 slots after start) - - // Create the updateData array with exactly 2 elements as required by parseTwapPriceFeedUpdates + memory endTwapMessages = new TwapPriceFeedMessage[]( + messages.length / 2 + ); + + // Fill the arrays with all price feeds' start and end points + for (uint i = 0; i < messages.length / 2; i++) { + // Create start message for this price feed + startTwapMessages[i].priceId = messages[i * 2].priceId; + startTwapMessages[i].cumulativePrice = + int128(messages[i * 2].price) * + 1000; + startTwapMessages[i].cumulativeConf = + uint128(messages[i * 2].conf) * + 1000; + startTwapMessages[i].numDownSlots = 0; // No down slots for testing + startTwapMessages[i].expo = messages[i * 2].expo; + startTwapMessages[i].publishTime = messages[i * 2].publishTime; + startTwapMessages[i].prevPublishTime = messages[i * 2] + .prevPublishTime; + startTwapMessages[i].publishSlot = 1000; // Start slot + + // Create end message for this price feed + endTwapMessages[i].priceId = messages[i * 2 + 1].priceId; + endTwapMessages[i].cumulativePrice = + int128(messages[i * 2 + 1].price) * + 1000 + + startTwapMessages[i].cumulativePrice; + endTwapMessages[i].cumulativeConf = + uint128(messages[i * 2 + 1].conf) * + 1000 + + startTwapMessages[i].cumulativeConf; + endTwapMessages[i].numDownSlots = 0; // No down slots for testing + endTwapMessages[i].expo = messages[i * 2 + 1].expo; + endTwapMessages[i].publishTime = messages[i * 2 + 1].publishTime; + endTwapMessages[i].prevPublishTime = messages[i * 2 + 1] + .prevPublishTime; + endTwapMessages[i].publishSlot = 1100; // End slot (100 slots after start) + } + + // Create exactly 2 updateData entries as required by parseTwapPriceFeedUpdates updateData = new bytes[](2); + + // First update contains all start points updateData[0] = generateWhMerkleTwapUpdateWithSource( startTwapMessages, config ); + + // Second update contains all end points updateData[1] = generateWhMerkleTwapUpdateWithSource( endTwapMessages, config @@ -491,7 +533,7 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils { ) ); - uint updateFee = pyth.getUpdateFee(updateData); + uint updateFee = pyth.getTwapUpdateFee(updateData); // Parse the TWAP updates PythStructs.TwapPriceFeed[] memory twapPriceFeeds = pyth @@ -571,7 +613,7 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils { ) ); - uint updateFee = pyth.getUpdateFee(updateData); + uint updateFee = pyth.getTwapUpdateFee(updateData); vm.expectRevert(PythErrors.InvalidTwapUpdateDataSet.selector); pyth.parseTwapPriceFeedUpdates{value: updateFee}(updateData, priceIds); @@ -622,7 +664,7 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils { ) ); - uint updateFee = pyth.getUpdateFee(updateData); + uint updateFee = pyth.getTwapUpdateFee(updateData); vm.expectRevert(PythErrors.InvalidTwapUpdateDataSet.selector); pyth.parseTwapPriceFeedUpdates{value: updateFee}(updateData, priceIds); @@ -669,7 +711,7 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils { ) ); - uint updateFee = pyth.getUpdateFee(updateData); + uint updateFee = pyth.getTwapUpdateFee(updateData); vm.expectRevert(PythErrors.InvalidTwapUpdateDataSet.selector); pyth.parseTwapPriceFeedUpdates{value: updateFee}(updateData, priceIds); @@ -716,7 +758,7 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils { ) ); - uint updateFee = pyth.getUpdateFee(updateData); + uint updateFee = pyth.getTwapUpdateFee(updateData); vm.expectRevert(PythErrors.InvalidTwapUpdateData.selector); pyth.parseTwapPriceFeedUpdates{value: updateFee}(updateData, priceIds); @@ -758,7 +800,7 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils { ) ); - uint updateFee = pyth.getUpdateFee(updateData); + uint updateFee = pyth.getTwapUpdateFee(updateData); vm.expectRevert(PythErrors.InsufficientFee.selector); pyth.parseTwapPriceFeedUpdates{value: updateFee - 1}( @@ -766,4 +808,178 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils { priceIds ); } + + function testParseTwapPriceFeedUpdatesMultipleFeeds() public { + bytes32[] memory priceIds = new bytes32[](2); + priceIds[0] = basePriceIds[0]; + priceIds[1] = basePriceIds[1]; + + // Create update data with both price feeds in the same updates + bytes[] memory updateData = new bytes[](2); // Just 2 updates (start/end) for both price feeds + + // Combine both price feeds in the same messages + TwapPriceFeedMessage[] + memory startMessages = new TwapPriceFeedMessage[](2); + TwapPriceFeedMessage[] memory endMessages = new TwapPriceFeedMessage[]( + 2 + ); + + // Add both price feeds to the start and end messages + startMessages[0] = baseTwapStartMessages[0]; + startMessages[1] = baseTwapStartMessages[1]; + endMessages[0] = baseTwapEndMessages[0]; + endMessages[1] = baseTwapEndMessages[1]; + + // Generate Merkle updates with both price feeds included + MerkleUpdateConfig memory config = MerkleUpdateConfig( + MERKLE_TREE_DEPTH, + NUM_GUARDIAN_SIGNERS, + SOURCE_EMITTER_CHAIN_ID, + SOURCE_EMITTER_ADDRESS, + false + ); + + // Create just 2 updates that contain both price feeds + updateData[0] = generateWhMerkleTwapUpdateWithSource( + startMessages, + config + ); + updateData[1] = generateWhMerkleTwapUpdateWithSource( + endMessages, + config + ); + + uint updateFee = pyth.getTwapUpdateFee(updateData); + + // Parse the TWAP updates + PythStructs.TwapPriceFeed[] memory twapPriceFeeds = pyth + .parseTwapPriceFeedUpdates{value: updateFee}(updateData, priceIds); + + // Validate results for first price feed + assertEq(twapPriceFeeds[0].id, basePriceIds[0]); + assertEq( + twapPriceFeeds[0].startTime, + baseTwapStartMessages[0].publishTime + ); + assertEq(twapPriceFeeds[0].endTime, baseTwapEndMessages[0].publishTime); + assertEq(twapPriceFeeds[0].twap.expo, baseTwapStartMessages[0].expo); + // Expected TWAP price: (210_000 - 100_000) / (1100 - 1000) = 1100 + assertEq(twapPriceFeeds[0].twap.price, int64(1100)); + // Expected TWAP conf: (18_000 - 10_000) / (1100 - 1000) = 80 + assertEq(twapPriceFeeds[0].twap.conf, uint64(80)); + assertEq(twapPriceFeeds[0].downSlotsRatio, uint32(0)); + + // Validate results for second price feed + assertEq(twapPriceFeeds[1].id, basePriceIds[1]); + assertEq( + twapPriceFeeds[1].startTime, + baseTwapStartMessages[1].publishTime + ); + assertEq(twapPriceFeeds[1].endTime, baseTwapEndMessages[1].publishTime); + assertEq(twapPriceFeeds[1].twap.expo, baseTwapStartMessages[1].expo); + // Expected TWAP price: (800_000 - 500_000) / (1100 - 1000) = 3000 + assertEq(twapPriceFeeds[1].twap.price, int64(3000)); + // Expected TWAP conf: (40_000 - 20_000) / (1100 - 1000) = 200 + assertEq(twapPriceFeeds[1].twap.conf, uint64(200)); + assertEq(twapPriceFeeds[1].downSlotsRatio, uint32(0)); + } + + function testParseTwapPriceFeedUpdatesRevertsWithMismatchedArrayLengths() + public + { + // Case 1: Too many updates (more than 2) + bytes32[] memory priceIds = new bytes32[](1); + priceIds[0] = basePriceIds[0]; + + // Create 3 updates (should only be 2) + bytes[] memory updateData = new bytes[](3); + + TwapPriceFeedMessage[] + memory startMessages = new TwapPriceFeedMessage[](1); + TwapPriceFeedMessage[] memory endMessages = new TwapPriceFeedMessage[]( + 1 + ); + startMessages[0] = baseTwapStartMessages[0]; + endMessages[0] = baseTwapEndMessages[0]; + + MerkleUpdateConfig memory config = MerkleUpdateConfig( + MERKLE_TREE_DEPTH, + NUM_GUARDIAN_SIGNERS, + SOURCE_EMITTER_CHAIN_ID, + SOURCE_EMITTER_ADDRESS, + false + ); + + // Fill with valid updates, but too many of them + updateData[0] = generateWhMerkleTwapUpdateWithSource( + startMessages, + config + ); + updateData[1] = generateWhMerkleTwapUpdateWithSource( + endMessages, + config + ); + updateData[2] = generateWhMerkleTwapUpdateWithSource( + startMessages, + config + ); + + uint updateFee = pyth.getTwapUpdateFee(updateData); + + vm.expectRevert(PythErrors.InvalidUpdateData.selector); + pyth.parseTwapPriceFeedUpdates{value: updateFee}(updateData, priceIds); + + // Case 2: Too few updates (less than 2) + updateData = new bytes[](1); // Only 1 update (should be 2) + updateData[0] = generateWhMerkleTwapUpdateWithSource( + startMessages, + config + ); + + updateFee = pyth.getUpdateFee(updateData); + + vm.expectRevert(PythErrors.InvalidUpdateData.selector); + pyth.parseTwapPriceFeedUpdates{value: updateFee}(updateData, priceIds); + } + + function testParseTwapPriceFeedUpdatesWithRequestedButNotFoundPriceId() + public + { + // Create price IDs, including one that's not in the updates + bytes32[] memory priceIds = new bytes32[](2); + priceIds[0] = basePriceIds[0]; // This one exists in our updates + priceIds[1] = bytes32(uint256(999)); // This one doesn't exist in our updates + + TwapPriceFeedMessage[] + memory startMessages = new TwapPriceFeedMessage[](1); + TwapPriceFeedMessage[] memory endMessages = new TwapPriceFeedMessage[]( + 1 + ); + startMessages[0] = baseTwapStartMessages[0]; // Only includes priceIds[0] + endMessages[0] = baseTwapEndMessages[0]; // Only includes priceIds[0] + + MerkleUpdateConfig memory config = MerkleUpdateConfig( + MERKLE_TREE_DEPTH, + NUM_GUARDIAN_SIGNERS, + SOURCE_EMITTER_CHAIN_ID, + SOURCE_EMITTER_ADDRESS, + false + ); + + bytes[] memory updateData = new bytes[](2); + updateData[0] = generateWhMerkleTwapUpdateWithSource( + startMessages, + config + ); + updateData[1] = generateWhMerkleTwapUpdateWithSource( + endMessages, + config + ); + + uint updateFee = pyth.getTwapUpdateFee(updateData); + + // Should revert because one of the requested price IDs is not found in the updates + vm.expectRevert(PythErrors.PriceFeedNotFoundWithinRange.selector); + pyth.parseTwapPriceFeedUpdates{value: updateFee}(updateData, priceIds); + } } diff --git a/target_chains/ethereum/sdk/solidity/IPyth.sol b/target_chains/ethereum/sdk/solidity/IPyth.sol index bf629f7a68..65fda5b6d9 100644 --- a/target_chains/ethereum/sdk/solidity/IPyth.sol +++ b/target_chains/ethereum/sdk/solidity/IPyth.sol @@ -94,6 +94,13 @@ interface IPyth is IPythEvents { bytes[] calldata updateData ) external view returns (uint feeAmount); + /// @notice Returns the required fee to update a TWAP price. + /// @param updateData Array of price update data. + /// @return feeAmount The required fee in Wei. + function getTwapUpdateFee( + bytes[] calldata updateData + ) external view returns (uint feeAmount); + /// @notice Parse `updateData` and return price feeds of the given `priceIds` if they are all published /// within `minPublishTime` and `maxPublishTime`. /// diff --git a/target_chains/ethereum/sdk/solidity/MockPyth.sol b/target_chains/ethereum/sdk/solidity/MockPyth.sol index f2feeb5ddb..200fe97159 100644 --- a/target_chains/ethereum/sdk/solidity/MockPyth.sol +++ b/target_chains/ethereum/sdk/solidity/MockPyth.sol @@ -80,6 +80,12 @@ contract MockPyth is AbstractPyth { return singleUpdateFeeInWei * updateData.length; } + function getTwapUpdateFee( + bytes[] calldata updateData + ) public view override returns (uint feeAmount) { + return singleUpdateFeeInWei * updateData.length; + } + function parsePriceFeedUpdatesInternal( bytes[] calldata updateData, bytes32[] calldata priceIds, diff --git a/target_chains/ethereum/sdk/solidity/abis/AbstractPyth.json b/target_chains/ethereum/sdk/solidity/abis/AbstractPyth.json index 8e160f02c1..6d1a2a8f08 100644 --- a/target_chains/ethereum/sdk/solidity/abis/AbstractPyth.json +++ b/target_chains/ethereum/sdk/solidity/abis/AbstractPyth.json @@ -344,6 +344,25 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "updateData", + "type": "bytes[]" + } + ], + "name": "getTwapUpdateFee", + "outputs": [ + { + "internalType": "uint256", + "name": "feeAmount", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { diff --git a/target_chains/ethereum/sdk/solidity/abis/IPyth.json b/target_chains/ethereum/sdk/solidity/abis/IPyth.json index c52b25ed92..0516d39d97 100644 --- a/target_chains/ethereum/sdk/solidity/abis/IPyth.json +++ b/target_chains/ethereum/sdk/solidity/abis/IPyth.json @@ -247,6 +247,25 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "updateData", + "type": "bytes[]" + } + ], + "name": "getTwapUpdateFee", + "outputs": [ + { + "internalType": "uint256", + "name": "feeAmount", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { diff --git a/target_chains/ethereum/sdk/solidity/abis/MockPyth.json b/target_chains/ethereum/sdk/solidity/abis/MockPyth.json index 4a727569ec..92cba70ce1 100644 --- a/target_chains/ethereum/sdk/solidity/abis/MockPyth.json +++ b/target_chains/ethereum/sdk/solidity/abis/MockPyth.json @@ -483,6 +483,25 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "updateData", + "type": "bytes[]" + } + ], + "name": "getTwapUpdateFee", + "outputs": [ + { + "internalType": "uint256", + "name": "feeAmount", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ {