Skip to content

Commit c88c2f5

Browse files
committed
feat: update parseTwapPriceFeedUpdates function to accept a single array of price update data
1 parent 3ab3f0d commit c88c2f5

File tree

5 files changed

+51
-62
lines changed

5 files changed

+51
-62
lines changed

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

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ abstract contract Pyth is
362362
}
363363

364364
function parseTwapPriceFeedUpdates(
365-
bytes[][] calldata updateData,
365+
bytes[] calldata updateData,
366366
bytes32[] calldata priceIds
367367
)
368368
external
@@ -375,24 +375,19 @@ abstract contract Pyth is
375375
if (updateData.length != 2) {
376376
revert PythErrors.InvalidUpdateData();
377377
}
378-
// We only charge fee for 1 update even though we need 2 updates to derive TWAP.
379-
// This is for better UX since user's intention is to get a single TWAP price.
380-
uint requiredFee = getUpdateFee(updateData[0]);
381378

382-
if (requiredFee != getUpdateFee(updateData[1])) {
383-
revert PythErrors.InvalidUpdateData();
384-
}
379+
uint requiredFee = getUpdateFee(updateData);
385380
if (msg.value < requiredFee) revert PythErrors.InsufficientFee();
386381

387382
unchecked {
388383
twapPriceFeeds = new PythStructs.TwapPriceFeed[](priceIds.length);
389-
for (uint i = 0; i < updateData[0].length; i++) {
384+
for (uint i = 0; i < updateData.length - 1; i++) {
390385
if (
391-
(updateData[0][i].length > 4 &&
392-
UnsafeCalldataBytesLib.toUint32(updateData[0][i], 0) ==
386+
(updateData[i].length > 4 &&
387+
UnsafeCalldataBytesLib.toUint32(updateData[i], 0) ==
393388
ACCUMULATOR_MAGIC) &&
394-
(updateData[1][i].length > 4 &&
395-
UnsafeCalldataBytesLib.toUint32(updateData[1][i], 0) ==
389+
(updateData[i + 1].length > 4 &&
390+
UnsafeCalldataBytesLib.toUint32(updateData[i + 1], 0) ==
396391
ACCUMULATOR_MAGIC)
397392
) {
398393
uint offsetStart;
@@ -405,19 +400,16 @@ abstract contract Pyth is
405400
offsetStart,
406401
twapPriceInfoStart,
407402
priceIdStart
408-
) = processSingleTwapUpdate(updateData[0][i]);
403+
) = processSingleTwapUpdate(updateData[i]);
409404
(
410405
offsetEnd,
411406
twapPriceInfoEnd,
412407
priceIdEnd
413-
) = processSingleTwapUpdate(updateData[1][i]);
408+
) = processSingleTwapUpdate(updateData[i + 1]);
414409

415410
if (priceIdStart != priceIdEnd)
416411
revert PythErrors.InvalidTwapUpdateDataSet();
417412

418-
// Perform additional validation checks on the TWAP price data
419-
// to ensure proper time ordering, consistent exponents, and timestamp integrity
420-
// before using the data for calculations
421413
validateTwapPriceInfo(twapPriceInfoStart, twapPriceInfoEnd);
422414

423415
uint k = findIndexOfPriceId(priceIds, priceIdStart);

target_chains/ethereum/contracts/forge-test/Pyth.t.sol

Lines changed: 16 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
1111
import "./utils/WormholeTestUtils.t.sol";
1212
import "./utils/PythTestUtils.t.sol";
1313
import "./utils/RandTestUtils.t.sol";
14+
import "forge-std/console.sol";
1415

1516
contract PythTest is Test, WormholeTestUtils, PythTestUtils {
1617
IPyth public pyth;
@@ -105,7 +106,7 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils {
105106
function createBatchedTwapUpdateDataFromMessagesWithConfig(
106107
PriceFeedMessage[] memory messages,
107108
MerkleUpdateConfig memory config
108-
) public returns (bytes[][] memory updateData, uint updateFee) {
109+
) public returns (bytes[] memory updateData, uint updateFee) {
109110
require(messages.length >= 2, "At least 2 messages required for TWAP");
110111

111112
// Create TWAP messages from regular price feed messages
@@ -140,33 +141,24 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils {
140141
endTwapMessages[0].prevPublishTime = messages[1].prevPublishTime;
141142
endTwapMessages[0].publishSlot = 1100; // End slot (100 slots after start)
142143

143-
// Generate the update data for start and end using the TWAP-specific function
144-
bytes[] memory startUpdateData = new bytes[](1);
145-
startUpdateData[0] = generateWhMerkleTwapUpdateWithSource(
144+
// Create the updateData array with exactly 2 elements as required by parseTwapPriceFeedUpdates
145+
updateData = new bytes[](2);
146+
updateData[0] = generateWhMerkleTwapUpdateWithSource(
146147
startTwapMessages,
147148
config
148149
);
149-
150-
bytes[] memory endUpdateData = new bytes[](1);
151-
endUpdateData[0] = generateWhMerkleTwapUpdateWithSource(
150+
updateData[1] = generateWhMerkleTwapUpdateWithSource(
152151
endTwapMessages,
153152
config
154153
);
155154

156-
// Create the updateData array with exactly 2 elements as required by parseTwapPriceFeedUpdates
157-
updateData = new bytes[][](2);
158-
updateData[0] = startUpdateData;
159-
updateData[1] = endUpdateData;
160-
161155
// Calculate the update fee
162-
// We only charge fee for 1 update even though we need 2 updates to derive TWAP.
163-
// This is for better UX since user's intention is to get a single TWAP price.
164-
updateFee = pyth.getUpdateFee(updateData[0]);
156+
updateFee = pyth.getUpdateFee(updateData);
165157
}
166158

167159
function createBatchedTwapUpdateDataFromMessages(
168160
PriceFeedMessage[] memory messages
169-
) internal returns (bytes[][] memory updateData, uint updateFee) {
161+
) internal returns (bytes[] memory updateData, uint updateFee) {
170162
(
171163
updateData,
172164
updateFee
@@ -423,7 +415,7 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils {
423415

424416
// Create update data for TWAP calculation
425417
(
426-
bytes[][] memory updateData,
418+
bytes[] memory updateData,
427419
uint updateFee
428420
) = createBatchedTwapUpdateDataFromMessages(messages);
429421

@@ -461,8 +453,8 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils {
461453
priceIds[0] = bytes32(uint256(1));
462454

463455
// Create invalid update data with wrong length
464-
bytes[][] memory updateData = new bytes[][](1); // Should be 2
465-
updateData[0] = new bytes[](1);
456+
bytes[] memory updateData = new bytes[](1); // Should be 2
457+
updateData[0] = new bytes(1);
466458

467459
vm.expectRevert(PythErrors.InvalidUpdateData.selector);
468460
pyth.parseTwapPriceFeedUpdates{value: 0}(updateData, priceIds);
@@ -489,7 +481,7 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils {
489481
messages[1].prevPublishTime = 1000;
490482

491483
(
492-
bytes[][] memory updateData,
484+
bytes[] memory updateData,
493485
uint updateFee
494486
) = createBatchedTwapUpdateDataFromMessages(messages);
495487

@@ -518,7 +510,7 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils {
518510
messages[1].prevPublishTime = 900;
519511

520512
(
521-
bytes[][] memory updateData,
513+
bytes[] memory updateData,
522514
uint updateFee
523515
) = createBatchedTwapUpdateDataFromMessages(messages);
524516

@@ -549,7 +541,7 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils {
549541
messages[1].prevPublishTime = 1000;
550542

551543
(
552-
bytes[][] memory updateData,
544+
bytes[] memory updateData,
553545
uint updateFee
554546
) = createBatchedTwapUpdateDataFromMessages(messages);
555547

@@ -578,7 +570,7 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils {
578570
messages[1].prevPublishTime = 1000;
579571

580572
(
581-
bytes[][] memory updateData,
573+
bytes[] memory updateData,
582574
uint updateFee
583575
) = createBatchedTwapUpdateDataFromMessages(messages);
584576

@@ -603,7 +595,7 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils {
603595
messages[1].prevPublishTime = 1000;
604596

605597
(
606-
bytes[][] memory updateData,
598+
bytes[] memory updateData,
607599
uint updateFee
608600
) = createBatchedTwapUpdateDataFromMessages(messages);
609601

target_chains/ethereum/sdk/solidity/AbstractPyth.sol

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,4 +135,14 @@ abstract contract AbstractPyth is IPyth {
135135
virtual
136136
override
137137
returns (PythStructs.PriceFeed[] memory priceFeeds);
138+
139+
function parseTwapPriceFeedUpdates(
140+
bytes[] calldata updateData,
141+
bytes32[] calldata priceIds
142+
)
143+
external
144+
payable
145+
virtual
146+
override
147+
returns (PythStructs.TwapPriceFeed[] memory twapPriceFeeds);
138148
}

target_chains/ethereum/sdk/solidity/IPyth.sol

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -119,26 +119,26 @@ interface IPyth is IPythEvents {
119119
uint64 maxPublishTime
120120
) external payable returns (PythStructs.PriceFeed[] memory priceFeeds);
121121

122-
/// @notice Parse time-weighted average price (TWAP) from two sets of price update data for the given `priceIds`.
122+
/// @notice Parse time-weighted average price (TWAP) from two consecutive price updates for the given `priceIds`.
123123
///
124124
/// This method calculates TWAP between two data points by processing the difference in cumulative price values
125-
/// divided by the time period. It requires two sets of update data that contain valid price information
125+
/// divided by the time period. It requires exactly two updates that contain valid price information
126126
/// for all the requested price IDs.
127127
///
128128
/// This method requires the caller to pay a fee in wei; the required fee can be computed by calling
129-
/// `getUpdateFee` with the length of either updateData array (both arrays must have the same length).
129+
/// `getUpdateFee` with the updateData array.
130130
///
131131
/// @dev Reverts if:
132132
/// - The transferred fee is not sufficient
133133
/// - The updateData is invalid or malformed
134+
/// - The updateData array does not contain exactly 2 updates
134135
/// - There is no update for any of the given `priceIds`
135-
/// - The two update datasets are not comparable (different number of updates or mismatched price IDs)
136136
/// - The time ordering between data points is invalid (start time must be before end time)
137-
/// @param updateData Array containing two arrays of price update data (start and end points for TWAP calculation)
137+
/// @param updateData Array containing exactly two price updates (start and end points for TWAP calculation)
138138
/// @param priceIds Array of price ids to calculate TWAP for
139139
/// @return twapPriceFeeds Array of TWAP price feeds corresponding to the given `priceIds` (with the same order)
140140
function parseTwapPriceFeedUpdates(
141-
bytes[][] calldata updateData,
141+
bytes[] calldata updateData,
142142
bytes32[] calldata priceIds
143143
)
144144
external

target_chains/ethereum/sdk/solidity/MockPyth.sol

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ contract MockPyth is AbstractPyth {
162162
}
163163

164164
function parseTwapPriceFeedUpdates(
165-
bytes[][] calldata updateData,
165+
bytes[] calldata updateData,
166166
bytes32[] calldata priceIds
167167
)
168168
external
@@ -172,9 +172,7 @@ contract MockPyth is AbstractPyth {
172172
// Validate inputs and fee
173173
if (updateData.length != 2) revert PythErrors.InvalidUpdateData();
174174

175-
uint requiredFee = getUpdateFee(updateData[0]);
176-
if (requiredFee != getUpdateFee(updateData[1]))
177-
revert PythErrors.InvalidUpdateData();
175+
uint requiredFee = getUpdateFee(updateData);
178176
if (msg.value < requiredFee) revert PythErrors.InsufficientFee();
179177

180178
twapPriceFeeds = new PythStructs.TwapPriceFeed[](priceIds.length);
@@ -188,7 +186,7 @@ contract MockPyth is AbstractPyth {
188186
}
189187

190188
function findPriceFeed(
191-
bytes[][] calldata updateData,
189+
bytes[] calldata updateData,
192190
bytes32 priceId,
193191
uint index
194192
)
@@ -200,21 +198,18 @@ contract MockPyth is AbstractPyth {
200198
bool found
201199
)
202200
{
203-
for (uint j = 0; j < updateData[index].length; j++) {
204-
(feed, prevPublishTime) = abi.decode(
205-
updateData[index][j],
206-
(PythStructs.PriceFeed, uint64)
207-
);
201+
(feed, prevPublishTime) = abi.decode(
202+
updateData[index],
203+
(PythStructs.PriceFeed, uint64)
204+
);
208205

209-
if (feed.id == priceId) {
210-
found = true;
211-
break;
212-
}
206+
if (feed.id == priceId) {
207+
found = true;
213208
}
214209
}
215210

216211
function processTwapPriceFeed(
217-
bytes[][] calldata updateData,
212+
bytes[] calldata updateData,
218213
bytes32 priceId,
219214
uint index,
220215
PythStructs.TwapPriceFeed[] memory twapPriceFeeds

0 commit comments

Comments
 (0)