From 4fe1222c31de3c65487144953fcee9c26185c0f7 Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Thu, 22 May 2025 08:31:52 -0500 Subject: [PATCH 01/13] updated parsePriceFeedUpdatesInternal name, added flag for storeUpdatesIfFresh --- .../contracts/contracts/pyth/Pyth.sol | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol b/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol index 0df97396ed..7d60cd86a4 100644 --- a/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol +++ b/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol @@ -317,15 +317,16 @@ abstract contract Pyth is return merkleData.numUpdates; } - function parsePriceFeedUpdatesInternal( + function parsePriceFeedUpdatesWithConfig( bytes[] calldata updateData, bytes32[] calldata priceIds, uint64 minAllowedPublishTime, uint64 maxAllowedPublishTime, bool checkUniqueness, - bool checkUpdateDataIsMinimal + bool checkUpdateDataIsMinimal, + bool storeUpdatesIfFresh ) - internal + public returns ( PythStructs.PriceFeed[] memory priceFeeds, uint64[] memory slots @@ -389,13 +390,14 @@ abstract contract Pyth is override returns (PythStructs.PriceFeed[] memory priceFeeds) { - (priceFeeds, ) = parsePriceFeedUpdatesInternal( + (priceFeeds, ) = parsePriceFeedUpdatesWithConfig( updateData, priceIds, minPublishTime, maxPublishTime, false, - false + false, + true // TODO: Figure out when to use the flag ); } @@ -414,13 +416,14 @@ abstract contract Pyth is ) { return - parsePriceFeedUpdatesInternal( + parsePriceFeedUpdatesWithConfig( updateData, priceIds, minPublishTime, maxPublishTime, false, - true + true, + true // TODO: Figure out when to use the flag ); } @@ -624,13 +627,14 @@ abstract contract Pyth is override returns (PythStructs.PriceFeed[] memory priceFeeds) { - (priceFeeds, ) = parsePriceFeedUpdatesInternal( + (priceFeeds, ) = parsePriceFeedUpdatesWithConfig( updateData, priceIds, minPublishTime, maxPublishTime, true, - false + false, + true // TODO: Figure out when to use flag ); } From 552a34fd3605807d4f44b354f079a2ad2e1ffa76 Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Thu, 22 May 2025 13:47:14 -0500 Subject: [PATCH 02/13] removed TODO, updated interface --- .../contracts/contracts/pyth/Pyth.sol | 6 ++-- target_chains/ethereum/sdk/solidity/IPyth.sol | 31 +++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol b/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol index 7d60cd86a4..f84633a74d 100644 --- a/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol +++ b/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol @@ -397,7 +397,7 @@ abstract contract Pyth is maxPublishTime, false, false, - true // TODO: Figure out when to use the flag + true ); } @@ -423,7 +423,7 @@ abstract contract Pyth is maxPublishTime, false, true, - true // TODO: Figure out when to use the flag + true ); } @@ -634,7 +634,7 @@ abstract contract Pyth is maxPublishTime, true, false, - true // TODO: Figure out when to use flag + true ); } diff --git a/target_chains/ethereum/sdk/solidity/IPyth.sol b/target_chains/ethereum/sdk/solidity/IPyth.sol index 5418504429..e97fd5212c 100644 --- a/target_chains/ethereum/sdk/solidity/IPyth.sol +++ b/target_chains/ethereum/sdk/solidity/IPyth.sol @@ -126,6 +126,37 @@ interface IPyth is IPythEvents { uint64 maxPublishTime ) external payable returns (PythStructs.PriceFeed[] memory priceFeeds); + /// @notice Parse `updateData` and return price feeds of the given `priceIds` if they are all published + /// within `minPublishTime` and `maxPublishTime,` but choose to store price updates if `storeUpdatesIfFresh`. + /// + /// You can use this method if you want to use a Pyth price at a fixed time and not the most recent price; + /// otherwise, please consider using `updatePriceFeeds`. This method may store the price updates on-chain, if they + /// are more recent than the current stored prices. + /// + /// This method requires the caller to pay a fee in wei; the required fee can be computed by calling + /// `getUpdateFee` with the length of the `updateData` array. + /// + /// This method will eventually allow the caller to determine whether parsed price feeds should update + /// the stored values as well. + /// + /// @dev Reverts if the transferred fee is not sufficient or the updateData is invalid or there is + /// no update for any of the given `priceIds` within the given time range. + /// @param updateData Array of price update data. + /// @param priceIds Array of price ids. + /// @param minPublishTime minimum acceptable publishTime for the given `priceIds`. + /// @param maxPublishTime maximum acceptable publishTime for the given `priceIds`. + /// @param storeUpdatesIfFresh flag for the parse function to + /// @return priceFeeds Array of the price feeds corresponding to the given `priceIds` (with the same order). + function parsePriceFeedUpdatesWithConfig( + bytes[] calldata updateData, + bytes32[] calldata priceIds, + uint64 minAllowedPublishTime, + uint64 maxAllowedPublishTime, + bool checkUniqueness, + bool checkUpdateDataIsMinimal, + bool storeUpdatesIfFresh + ) public returns ( PythStructs.PriceFeed[] memory priceFeeds, uint64[] memory slots); + /// @notice Parse time-weighted average price (TWAP) from two consecutive price updates for the given `priceIds`. /// /// This method calculates TWAP between two data points by processing the difference in cumulative price values From b3cf5d32c39db3de297229bd650b8eed7a6dd3d5 Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Fri, 23 May 2025 15:51:44 -0500 Subject: [PATCH 03/13] stash while checking build errors --- .../ethereum/abi_generator/src/index.js | 20 +++ target_chains/ethereum/sdk/solidity/IPyth.sol | 6 +- .../ethereum/sdk/solidity/abis/IPyth.json | 115 ++++++++++++++++++ 3 files changed, 138 insertions(+), 3 deletions(-) diff --git a/target_chains/ethereum/abi_generator/src/index.js b/target_chains/ethereum/abi_generator/src/index.js index 524e715ebc..93471ea446 100644 --- a/target_chains/ethereum/abi_generator/src/index.js +++ b/target_chains/ethereum/abi_generator/src/index.js @@ -42,9 +42,29 @@ function generateAbi(contracts) { fs.mkdirSync("abis"); } + if (output.errors) { + // We can still generate ABIs with warnings, only throw for errors + const errors = output.errors.filter((e) => e.severity === "error"); + if (errors.length > 0) { + console.error("Compilation errors:"); + for (const error of errors) { + console.error(error.formattedMessage || error.message); + } + throw new Error("Compilation failed due to errors"); + } + } + + for (let contract of contracts) { const contractFile = `${contract}.sol`; + if (!output.contracts[contractFile]) { + throw new Error(`Unable to produce ABI for ${contractFile}.`); + } + if (!output.contracts[contractFile][contract]) { + throw new Error(`Unable to produce ABI for ${contractFile}:${contract}.`); + } + const abi = output.contracts[contractFile][contract].abi; fs.writeFileSync( `abis/${contract}.json`, diff --git a/target_chains/ethereum/sdk/solidity/IPyth.sol b/target_chains/ethereum/sdk/solidity/IPyth.sol index e97fd5212c..47ea25d752 100644 --- a/target_chains/ethereum/sdk/solidity/IPyth.sol +++ b/target_chains/ethereum/sdk/solidity/IPyth.sol @@ -143,8 +143,8 @@ interface IPyth is IPythEvents { /// no update for any of the given `priceIds` within the given time range. /// @param updateData Array of price update data. /// @param priceIds Array of price ids. - /// @param minPublishTime minimum acceptable publishTime for the given `priceIds`. - /// @param maxPublishTime maximum acceptable publishTime for the given `priceIds`. + /// @param minAllowedPublishTime minimum acceptable publishTime for the given `priceIds`. + /// @param maxAllowedPublishTime maximum acceptable publishTime for the given `priceIds`. /// @param storeUpdatesIfFresh flag for the parse function to /// @return priceFeeds Array of the price feeds corresponding to the given `priceIds` (with the same order). function parsePriceFeedUpdatesWithConfig( @@ -155,7 +155,7 @@ interface IPyth is IPythEvents { bool checkUniqueness, bool checkUpdateDataIsMinimal, bool storeUpdatesIfFresh - ) public returns ( PythStructs.PriceFeed[] memory priceFeeds, uint64[] memory slots); + ) external returns ( PythStructs.PriceFeed[] memory priceFeeds, uint64[] memory slots); /// @notice Parse time-weighted average price (TWAP) from two consecutive price updates for the given `priceIds`. /// diff --git a/target_chains/ethereum/sdk/solidity/abis/IPyth.json b/target_chains/ethereum/sdk/solidity/abis/IPyth.json index cbb0717797..3f3f197b4c 100644 --- a/target_chains/ethereum/sdk/solidity/abis/IPyth.json +++ b/target_chains/ethereum/sdk/solidity/abis/IPyth.json @@ -475,6 +475,121 @@ "stateMutability": "payable", "type": "function" }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "updateData", + "type": "bytes[]" + }, + { + "internalType": "bytes32[]", + "name": "priceIds", + "type": "bytes32[]" + }, + { + "internalType": "uint64", + "name": "minAllowedPublishTime", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "maxAllowedPublishTime", + "type": "uint64" + }, + { + "internalType": "bool", + "name": "checkUniqueness", + "type": "bool" + }, + { + "internalType": "bool", + "name": "checkUpdateDataIsMinimal", + "type": "bool" + }, + { + "internalType": "bool", + "name": "storeUpdatesIfFresh", + "type": "bool" + } + ], + "name": "parsePriceFeedUpdatesWithConfig", + "outputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "price", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "conf", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "internalType": "struct PythStructs.Price", + "name": "emaPrice", + "type": "tuple" + } + ], + "internalType": "struct PythStructs.PriceFeed[]", + "name": "priceFeeds", + "type": "tuple[]" + }, + { + "internalType": "uint64[]", + "name": "slots", + "type": "uint64[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { From 2db25fcf6d07b006edb8fb3846d8a25655f29e96 Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Fri, 23 May 2025 16:41:33 -0500 Subject: [PATCH 04/13] fixed interface header --- target_chains/ethereum/contracts/contracts/pyth/Pyth.sol | 1 + target_chains/ethereum/sdk/solidity/IPyth.sol | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol b/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol index f84633a74d..e484ad85f8 100644 --- a/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol +++ b/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol @@ -327,6 +327,7 @@ abstract contract Pyth is bool storeUpdatesIfFresh ) public + payable returns ( PythStructs.PriceFeed[] memory priceFeeds, uint64[] memory slots diff --git a/target_chains/ethereum/sdk/solidity/IPyth.sol b/target_chains/ethereum/sdk/solidity/IPyth.sol index 47ea25d752..9abb587aeb 100644 --- a/target_chains/ethereum/sdk/solidity/IPyth.sol +++ b/target_chains/ethereum/sdk/solidity/IPyth.sol @@ -155,7 +155,7 @@ interface IPyth is IPythEvents { bool checkUniqueness, bool checkUpdateDataIsMinimal, bool storeUpdatesIfFresh - ) external returns ( PythStructs.PriceFeed[] memory priceFeeds, uint64[] memory slots); + ) external payable returns ( PythStructs.PriceFeed[] memory priceFeeds, uint64[] memory slots); /// @notice Parse time-weighted average price (TWAP) from two consecutive price updates for the given `priceIds`. /// From a1822f82249c183e5f82b1d3d88f708591560e00 Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Fri, 23 May 2025 16:44:41 -0500 Subject: [PATCH 05/13] reset index.js --- .../ethereum/abi_generator/src/index.js | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/target_chains/ethereum/abi_generator/src/index.js b/target_chains/ethereum/abi_generator/src/index.js index 93471ea446..524e715ebc 100644 --- a/target_chains/ethereum/abi_generator/src/index.js +++ b/target_chains/ethereum/abi_generator/src/index.js @@ -42,29 +42,9 @@ function generateAbi(contracts) { fs.mkdirSync("abis"); } - if (output.errors) { - // We can still generate ABIs with warnings, only throw for errors - const errors = output.errors.filter((e) => e.severity === "error"); - if (errors.length > 0) { - console.error("Compilation errors:"); - for (const error of errors) { - console.error(error.formattedMessage || error.message); - } - throw new Error("Compilation failed due to errors"); - } - } - - for (let contract of contracts) { const contractFile = `${contract}.sol`; - if (!output.contracts[contractFile]) { - throw new Error(`Unable to produce ABI for ${contractFile}.`); - } - if (!output.contracts[contractFile][contract]) { - throw new Error(`Unable to produce ABI for ${contractFile}:${contract}.`); - } - const abi = output.contracts[contractFile][contract].abi; fs.writeFileSync( `abis/${contract}.json`, From 2a5b2ec98082419ab1923e38a7a67b7fab0942ce Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Mon, 26 May 2025 17:03:29 -0500 Subject: [PATCH 06/13] fixed mockpyth function header --- .../ethereum/sdk/solidity/MockPyth.sol | 50 ++++++++++++------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/target_chains/ethereum/sdk/solidity/MockPyth.sol b/target_chains/ethereum/sdk/solidity/MockPyth.sol index e0a59f9cdd..106df94711 100644 --- a/target_chains/ethereum/sdk/solidity/MockPyth.sol +++ b/target_chains/ethereum/sdk/solidity/MockPyth.sol @@ -86,15 +86,21 @@ contract MockPyth is AbstractPyth { return singleUpdateFeeInWei * updateData.length; } - function parsePriceFeedUpdatesInternal( + function parsePriceFeedUpdatesWithConfig( bytes[] calldata updateData, bytes32[] calldata priceIds, - uint64 minPublishTime, - uint64 maxPublishTime, - bool unique + uint64 minAllowedPublishTime, + uint64 maxAllowedPublishTime, + bool checkUniqueness, + bool checkUpdateDataIsMinimal, + bool storeUpdatesIfFresh ) - internal - returns (PythStructs.PriceFeed[] memory feeds, uint64[] memory slots) + public + payable + returns ( + PythStructs.PriceFeed[] memory feeds, + uint64[] memory slots + ) { uint requiredFee = getUpdateFee(updateData); if (msg.value < requiredFee) revert PythErrors.InsufficientFee(); @@ -124,9 +130,9 @@ contract MockPyth is AbstractPyth { if (feeds[i].id == priceIds[i]) { if ( - minPublishTime <= publishTime && - publishTime <= maxPublishTime && - (!unique || prevPublishTime < minPublishTime) + minAllowedPublishTime <= publishTime && + publishTime <= maxAllowedPublishTime && + (!checkUniqueness || prevPublishTime < minAllowedPublishTime) ) { break; } else { @@ -146,12 +152,14 @@ contract MockPyth is AbstractPyth { uint64 minPublishTime, uint64 maxPublishTime ) external payable override returns (PythStructs.PriceFeed[] memory feeds) { - (feeds, ) = parsePriceFeedUpdatesInternal( + (feeds, ) = parsePriceFeedUpdatesWithConfig( updateData, priceIds, minPublishTime, maxPublishTime, - false + false, + true, + true ); } @@ -161,11 +169,13 @@ contract MockPyth is AbstractPyth { uint64 minPublishTime, uint64 maxPublishTime ) external payable override returns (PythStructs.PriceFeed[] memory feeds) { - (feeds, ) = parsePriceFeedUpdatesInternal( + (feeds, ) = parsePriceFeedUpdatesWithConfig( updateData, priceIds, minPublishTime, maxPublishTime, + false, + true, true ); } @@ -182,13 +192,15 @@ contract MockPyth is AbstractPyth { returns (PythStructs.PriceFeed[] memory feeds, uint64[] memory slots) { return - parsePriceFeedUpdatesInternal( - updateData, - priceIds, - minPublishTime, - maxPublishTime, - false - ); + parsePriceFeedUpdatesWithConfig( + updateData, + priceIds, + minPublishTime, + maxPublishTime, + false, + true, + true + ); } function parseTwapPriceFeedUpdates( From 6b4b0c8ba7131fe31b738f79d11243d02596fa89 Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Mon, 26 May 2025 17:52:59 -0500 Subject: [PATCH 07/13] restored update functionality upon flag --- target_chains/ethereum/contracts/contracts/pyth/Pyth.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol b/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol index e484ad85f8..74c6f0c9bb 100644 --- a/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol +++ b/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol @@ -359,6 +359,10 @@ abstract contract Pyth is updateData[i], context ); + if (storeUpdatesIfFresh) { + bytes32 curPriceId = context.priceIds[i]; + updateLatestPriceIfNecessary(curPriceId, _state.latestPriceInfo[curPriceId]); + } } } From 2e79358265cc40c5e2be2438af379d6e71befdee Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Tue, 27 May 2025 12:59:58 -0500 Subject: [PATCH 08/13] working on function test case --- .../ethereum/contracts/forge-test/Pyth.t.sol | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/target_chains/ethereum/contracts/forge-test/Pyth.t.sol b/target_chains/ethereum/contracts/forge-test/Pyth.t.sol index b883ba9e58..209e3b37d3 100644 --- a/target_chains/ethereum/contracts/forge-test/Pyth.t.sol +++ b/target_chains/ethereum/contracts/forge-test/Pyth.t.sol @@ -278,6 +278,86 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils { } } + function testParsePriceFeedUpdatesWithConfigIfStorageTrue(uint seed) public { + setRandSeed(seed); + uint numMessages = 1 + (getRandUint() % 10); + ( + bytes32[] memory priceIds, + PriceFeedMessage[] memory messages + ) = generateRandomPriceMessages(numMessages); + + ( + bytes[] memory updateData, + uint updateFee + ) = createBatchedUpdateDataFromMessages(messages); + + (PythStructs.PriceFeed[] memory priceFeeds,) = pyth.parsePriceFeedUpdatesWithConfig{ + value: updateFee + }(updateData, priceIds, 0, MAX_UINT64, false, true, true); + + for (uint i = 0; i < numMessages; i++) { + // Validating that returned priceIds are equal + assertEq(priceFeeds[i].id, priceIds[i]); + assertEq(priceFeeds[i].price.price, messages[i].price); + assertEq(priceFeeds[i].price.conf, messages[i].conf); + assertEq(priceFeeds[i].price.expo, messages[i].expo); + assertEq(priceFeeds[i].price.publishTime, messages[i].publishTime); + assertEq(priceFeeds[i].emaPrice.price, messages[i].emaPrice); + assertEq(priceFeeds[i].emaPrice.conf, messages[i].emaConf); + assertEq(priceFeeds[i].emaPrice.expo, messages[i].expo); + assertEq( + priceFeeds[i].emaPrice.publishTime, + messages[i].publishTime + ); + + // Validating that prices are stored on chain + PythStructs.Price memory curPrice = pyth.getPriceUnsafe( + messages[i].priceId + ); + + assertEq(priceFeeds[i].price.price, curPrice.price); + assertEq(priceFeeds[i].price.conf, curPrice.conf); + assertEq(priceFeeds[i].price.expo, curPrice.expo); + assertEq(priceFeeds[i].price.publishTime, curPrice.publishTime); + } + } + + function testParsePriceFeedUpdatesWithConfigIfStorageFalse(uint seed) public { + setRandSeed(seed); + uint numMessages = 1 + (getRandUint() % 10); + ( + bytes32[] memory priceIds, + PriceFeedMessage[] memory messages + ) = generateRandomPriceMessages(numMessages); + + ( + bytes[] memory updateData, + uint updateFee + ) = createBatchedUpdateDataFromMessages(messages); + + // Store prices before update + PythStructs.Price[] memory originalPrices = new PythStructs.Price[](priceIds.length); + for (uint i = 0; i < priceIds.length; i++) { + originalPrices[i] = pyth.getPriceUnsafe(priceIds[i]); + } + + pyth.parsePriceFeedUpdatesWithConfig{ + value: updateFee + }(updateData, priceIds, 0, MAX_UINT64, false, true, true); + + // validate that stored prices of each priceId are same as before update + for (uint i = 0; i < priceIds.length; i++) { + PythStructs.Price memory curPrice = pyth.getPriceUnsafe( + priceIds[i] + ); + + assertEq(curPrice.price, originalPrices[i].price); + assertEq(curPrice.conf, originalPrices[i].conf); + assertEq(curPrice.expo, originalPrices[i].expo); + assertEq(curPrice.publishTime, originalPrices[i].publishTime); + } + } + function testParsePriceFeedUpdatesWithSlotsStrictWorks(uint seed) public { setRandSeed(seed); uint numMessages = 1 + (getRandUint() % 10); From f1183f1927e34b1d7590f48306b6a56038f2f4e5 Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Tue, 27 May 2025 16:40:04 -0500 Subject: [PATCH 09/13] fixed store = false test --- .../contracts/contracts/pyth/Pyth.sol | 21 +++++++++++++++--- .../ethereum/contracts/forge-test/Pyth.t.sol | 22 +++++-------------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol b/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol index 74c6f0c9bb..cb7fac7247 100644 --- a/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol +++ b/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol @@ -359,9 +359,24 @@ abstract contract Pyth is updateData[i], context ); - if (storeUpdatesIfFresh) { - bytes32 curPriceId = context.priceIds[i]; - updateLatestPriceIfNecessary(curPriceId, _state.latestPriceInfo[curPriceId]); + } + + if (storeUpdatesIfFresh) { + for (uint j = 0; j < priceIds.length; j++) { + bytes32 curPriceId = priceIds[j]; + if (context.priceFeeds[j].id != 0) { + // Create a new PriceInfo memory object from the context.priceFeeds data + PythInternalStructs.PriceInfo memory newInfo; + newInfo.publishTime = uint64(context.priceFeeds[j].price.publishTime); + newInfo.expo = context.priceFeeds[j].price.expo; + newInfo.price = context.priceFeeds[j].price.price; + newInfo.conf = context.priceFeeds[j].price.conf; + newInfo.emaPrice = context.priceFeeds[j].emaPrice.price; + newInfo.emaConf = context.priceFeeds[j].emaPrice.conf; + + // Pass the new memory object to update storage + updateLatestPriceIfNecessary(curPriceId, newInfo); + } } } } diff --git a/target_chains/ethereum/contracts/forge-test/Pyth.t.sol b/target_chains/ethereum/contracts/forge-test/Pyth.t.sol index 209e3b37d3..a66c3adeff 100644 --- a/target_chains/ethereum/contracts/forge-test/Pyth.t.sol +++ b/target_chains/ethereum/contracts/forge-test/Pyth.t.sol @@ -335,26 +335,14 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils { uint updateFee ) = createBatchedUpdateDataFromMessages(messages); - // Store prices before update - PythStructs.Price[] memory originalPrices = new PythStructs.Price[](priceIds.length); - for (uint i = 0; i < priceIds.length; i++) { - originalPrices[i] = pyth.getPriceUnsafe(priceIds[i]); - } - pyth.parsePriceFeedUpdatesWithConfig{ value: updateFee - }(updateData, priceIds, 0, MAX_UINT64, false, true, true); + }(updateData, priceIds, 0, MAX_UINT64, false, true, false); - // validate that stored prices of each priceId are same as before update - for (uint i = 0; i < priceIds.length; i++) { - PythStructs.Price memory curPrice = pyth.getPriceUnsafe( - priceIds[i] - ); - - assertEq(curPrice.price, originalPrices[i].price); - assertEq(curPrice.conf, originalPrices[i].conf); - assertEq(curPrice.expo, originalPrices[i].expo); - assertEq(curPrice.publishTime, originalPrices[i].publishTime); + // validate that stored prices of each priceId are still unpopulated + for (uint i = 0; i < numMessages; i++) { + vm.expectRevert(PythErrors.PriceFeedNotFound.selector); + pyth.getPriceUnsafe(priceIds[i]); } } From 53690d2eafdeca15a5be180704287f7495ba15e0 Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Tue, 27 May 2025 17:19:04 -0500 Subject: [PATCH 10/13] added gas benchmark tests --- .../contracts/forge-test/GasBenchmark.t.sol | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/target_chains/ethereum/contracts/forge-test/GasBenchmark.t.sol b/target_chains/ethereum/contracts/forge-test/GasBenchmark.t.sol index ee82c3280f..71e04a0d67 100644 --- a/target_chains/ethereum/contracts/forge-test/GasBenchmark.t.sol +++ b/target_chains/ethereum/contracts/forge-test/GasBenchmark.t.sol @@ -240,6 +240,36 @@ contract GasBenchmark is Test, WormholeTestUtils, PythTestUtils { ); } + function testBenchmarkParsePriceFeedUpdatesWithConfigTrue() public { + bytes32[] memory ids = new bytes32[](1); + ids[0] = priceIds[0]; + + pyth.parsePriceFeedUpdatesWithConfig{value: freshPricesUpdateFee[0]}( + freshPricesUpdateData[0], + ids, + 0, + 50, + false, + true, // check minimal + true + ); + } + + function testBenchmarkParsePriceFeedUpdatesWithConfigFalse() public { + bytes32[] memory ids = new bytes32[](1); + ids[0] = priceIds[0]; + + pyth.parsePriceFeedUpdatesWithConfig{value: freshPricesUpdateFee[0]}( + freshPricesUpdateData[0], // contains only priceIds[0] + ids, + 0, + 50, + false, + true, // check minimal + true + ); + } + function testBenchmarkParsePriceFeedUpdatesUniqueFor() public { bytes32[] memory ids = new bytes32[](1); ids[0] = priceIds[0]; From f604a1361d1a14ed92b3ef08f36a58834bbc8166 Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Wed, 28 May 2025 11:38:48 -0500 Subject: [PATCH 11/13] cut down to -41 bytes --- .../contracts/contracts/pyth/Pyth.sol | 54 +++++++++---------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol b/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol index cb7fac7247..be33788b0e 100644 --- a/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol +++ b/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol @@ -333,20 +333,18 @@ abstract contract Pyth is uint64[] memory slots ) { - { - uint requiredFee = getUpdateFee(updateData); - if (msg.value < requiredFee) revert PythErrors.InsufficientFee(); - } + if (msg.value < getUpdateFee(updateData)) revert PythErrors.InsufficientFee(); // Create the context struct that holds all shared parameters - PythInternalStructs.UpdateParseContext memory context; - context.priceIds = priceIds; - context.minAllowedPublishTime = minAllowedPublishTime; - context.maxAllowedPublishTime = maxAllowedPublishTime; - context.checkUniqueness = checkUniqueness; - context.checkUpdateDataIsMinimal = checkUpdateDataIsMinimal; - context.priceFeeds = new PythStructs.PriceFeed[](priceIds.length); - context.slots = new uint64[](priceIds.length); + PythInternalStructs.UpdateParseContext memory context = PythInternalStructs.UpdateParseContext({ + priceIds: priceIds, + minAllowedPublishTime: minAllowedPublishTime, + maxAllowedPublishTime: maxAllowedPublishTime, + checkUniqueness: checkUniqueness, + checkUpdateDataIsMinimal: checkUpdateDataIsMinimal, + priceFeeds: new PythStructs.PriceFeed[](priceIds.length), + slots: new uint64[](priceIds.length) + }); // Track total updates for minimal update data check uint64 totalUpdatesAcrossBlobs = 0; @@ -354,29 +352,24 @@ abstract contract Pyth is unchecked { // Process each update, passing the context struct // Parsed results will be filled in context.priceFeeds and context.slots - for (uint i = 0; i < updateData.length; i++) { + for (uint i = 0; i < updateData.length; ++i) { totalUpdatesAcrossBlobs += _processSingleUpdateDataBlob( updateData[i], context ); } - if (storeUpdatesIfFresh) { - for (uint j = 0; j < priceIds.length; j++) { - bytes32 curPriceId = priceIds[j]; - if (context.priceFeeds[j].id != 0) { - // Create a new PriceInfo memory object from the context.priceFeeds data - PythInternalStructs.PriceInfo memory newInfo; - newInfo.publishTime = uint64(context.priceFeeds[j].price.publishTime); - newInfo.expo = context.priceFeeds[j].price.expo; - newInfo.price = context.priceFeeds[j].price.price; - newInfo.conf = context.priceFeeds[j].price.conf; - newInfo.emaPrice = context.priceFeeds[j].emaPrice.price; - newInfo.emaConf = context.priceFeeds[j].emaPrice.conf; - - // Pass the new memory object to update storage - updateLatestPriceIfNecessary(curPriceId, newInfo); - } + for (uint j = 0; j < priceIds.length; ++j) { + PythStructs.PriceFeed memory pf = context.priceFeeds[j]; + if (storeUpdatesIfFresh && pf.id != 0) { + updateLatestPriceIfNecessary(priceIds[j], PythInternalStructs.PriceInfo({ + publishTime: uint64(pf.price.publishTime), + expo: pf.price.expo, + price: pf.price.price, + conf: pf.price.conf, + emaPrice: pf.emaPrice.price, + emaConf: pf.emaPrice.conf + })); } } } @@ -390,7 +383,7 @@ abstract contract Pyth is } // Check all price feeds were found - for (uint k = 0; k < priceIds.length; k++) { + for (uint k = 0; k < priceIds.length; ++k) { if (context.priceFeeds[k].id == 0) { revert PythErrors.PriceFeedNotFoundWithinRange(); } @@ -399,6 +392,7 @@ abstract contract Pyth is // Return results return (context.priceFeeds, context.slots); } + function parsePriceFeedUpdates( bytes[] calldata updateData, bytes32[] calldata priceIds, From 660717e2aeacbd22d2f5603a35c011e78678a4a4 Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Wed, 28 May 2025 13:32:38 -0500 Subject: [PATCH 12/13] removed slots strict function --- .../contracts/contracts/pulse/Scheduler.sol | 7 +++-- .../contracts/contracts/pyth/Pyth.sol | 30 ++----------------- .../contracts/forge-test/GasBenchmark.t.sol | 4 +-- .../ethereum/contracts/forge-test/Pyth.t.sol | 21 +++++++++---- .../utils/MockPriceFeedTestUtils.sol | 2 +- .../ethereum/sdk/solidity/AbstractPyth.sol | 15 ---------- target_chains/ethereum/sdk/solidity/IPyth.sol | 20 ------------- .../ethereum/sdk/solidity/MockPyth.sol | 29 ++---------------- 8 files changed, 28 insertions(+), 100 deletions(-) diff --git a/target_chains/ethereum/contracts/contracts/pulse/Scheduler.sol b/target_chains/ethereum/contracts/contracts/pulse/Scheduler.sol index ce6a1d3dc1..ca7d425213 100644 --- a/target_chains/ethereum/contracts/contracts/pulse/Scheduler.sol +++ b/target_chains/ethereum/contracts/contracts/pulse/Scheduler.sol @@ -308,11 +308,14 @@ abstract contract Scheduler is IScheduler, SchedulerState { ( PythStructs.PriceFeed[] memory priceFeeds, uint64[] memory slots - ) = pyth.parsePriceFeedUpdatesWithSlotsStrict{value: pythFee}( + ) = pyth.parsePriceFeedUpdatesWithConfig{value: pythFee}( updateData, params.priceIds, 0, // We enforce the past max validity ourselves in _validateShouldUpdatePrices - curTime + FUTURE_TIMESTAMP_MAX_VALIDITY_PERIOD + curTime + FUTURE_TIMESTAMP_MAX_VALIDITY_PERIOD, + false, + true, + false ); // Verify all price feeds have the same Pythnet slot. diff --git a/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol b/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol index be33788b0e..559331e5f5 100644 --- a/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol +++ b/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol @@ -411,36 +411,10 @@ abstract contract Pyth is maxPublishTime, false, false, - true + false ); } - function parsePriceFeedUpdatesWithSlotsStrict( - bytes[] calldata updateData, - bytes32[] calldata priceIds, - uint64 minPublishTime, - uint64 maxPublishTime - ) - external - payable - override - returns ( - PythStructs.PriceFeed[] memory priceFeeds, - uint64[] memory slots - ) - { - return - parsePriceFeedUpdatesWithConfig( - updateData, - priceIds, - minPublishTime, - maxPublishTime, - false, - true, - true - ); - } - function extractTwapPriceInfos( bytes calldata updateData ) @@ -648,7 +622,7 @@ abstract contract Pyth is maxPublishTime, true, false, - true + false ); } diff --git a/target_chains/ethereum/contracts/forge-test/GasBenchmark.t.sol b/target_chains/ethereum/contracts/forge-test/GasBenchmark.t.sol index 71e04a0d67..d3e05b399f 100644 --- a/target_chains/ethereum/contracts/forge-test/GasBenchmark.t.sol +++ b/target_chains/ethereum/contracts/forge-test/GasBenchmark.t.sol @@ -251,7 +251,7 @@ contract GasBenchmark is Test, WormholeTestUtils, PythTestUtils { 50, false, true, // check minimal - true + false ); } @@ -266,7 +266,7 @@ contract GasBenchmark is Test, WormholeTestUtils, PythTestUtils { 50, false, true, // check minimal - true + false ); } diff --git a/target_chains/ethereum/contracts/forge-test/Pyth.t.sol b/target_chains/ethereum/contracts/forge-test/Pyth.t.sol index a66c3adeff..9d97a44f39 100644 --- a/target_chains/ethereum/contracts/forge-test/Pyth.t.sol +++ b/target_chains/ethereum/contracts/forge-test/Pyth.t.sol @@ -361,11 +361,14 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils { ( PythStructs.PriceFeed[] memory priceFeeds, uint64[] memory slots - ) = pyth.parsePriceFeedUpdatesWithSlotsStrict{value: updateFee}( + ) = pyth.parsePriceFeedUpdatesWithConfig{value: updateFee}( updateData, priceIds, 0, - MAX_UINT64 + MAX_UINT64, + false, + true, + false ); assertEq(priceFeeds.length, numMessages); @@ -527,11 +530,14 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils { // Should revert in strict mode vm.expectRevert(PythErrors.InvalidArgument.selector); - pyth.parsePriceFeedUpdatesWithSlotsStrict{value: updateFee}( + pyth.parsePriceFeedUpdatesWithConfig{value: updateFee}( updateData, requestedPriceIds, 0, - MAX_UINT64 + MAX_UINT64, + false, + true, + false ); } @@ -564,11 +570,14 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils { // Should revert in strict mode because we have fewer updates than price IDs vm.expectRevert(PythErrors.InvalidArgument.selector); - pyth.parsePriceFeedUpdatesWithSlotsStrict{value: updateFee}( + pyth.parsePriceFeedUpdatesWithConfig{value: updateFee}( updateData, requestedPriceIds, 0, - MAX_UINT64 + MAX_UINT64, + false, + true, + false ); } diff --git a/target_chains/ethereum/contracts/forge-test/utils/MockPriceFeedTestUtils.sol b/target_chains/ethereum/contracts/forge-test/utils/MockPriceFeedTestUtils.sol index 7488047587..4f650ae0b9 100644 --- a/target_chains/ethereum/contracts/forge-test/utils/MockPriceFeedTestUtils.sol +++ b/target_chains/ethereum/contracts/forge-test/utils/MockPriceFeedTestUtils.sol @@ -187,7 +187,7 @@ abstract contract MockPriceFeedTestUtils is Test { pyth, expectedFee, abi.encodeWithSelector( - IPyth.parsePriceFeedUpdatesWithSlotsStrict.selector + IPyth.parsePriceFeedUpdatesWithConfig.selector ), abi.encode(priceFeeds, slots) ); diff --git a/target_chains/ethereum/sdk/solidity/AbstractPyth.sol b/target_chains/ethereum/sdk/solidity/AbstractPyth.sol index dfa60ceb0b..295bc78264 100644 --- a/target_chains/ethereum/sdk/solidity/AbstractPyth.sol +++ b/target_chains/ethereum/sdk/solidity/AbstractPyth.sol @@ -136,21 +136,6 @@ abstract contract AbstractPyth is IPyth { override returns (PythStructs.PriceFeed[] memory priceFeeds); - function parsePriceFeedUpdatesWithSlotsStrict( - bytes[] calldata updateData, - bytes32[] calldata priceIds, - uint64 minPublishTime, - uint64 maxPublishTime - ) - external - payable - virtual - override - returns ( - PythStructs.PriceFeed[] memory priceFeeds, - uint64[] memory slots - ); - function parseTwapPriceFeedUpdates( bytes[] calldata updateData, bytes32[] calldata priceIds diff --git a/target_chains/ethereum/sdk/solidity/IPyth.sol b/target_chains/ethereum/sdk/solidity/IPyth.sol index 9abb587aeb..85f705da0a 100644 --- a/target_chains/ethereum/sdk/solidity/IPyth.sol +++ b/target_chains/ethereum/sdk/solidity/IPyth.sol @@ -202,24 +202,4 @@ interface IPyth is IPythEvents { uint64 minPublishTime, uint64 maxPublishTime ) external payable returns (PythStructs.PriceFeed[] memory priceFeeds); - - /// @dev Same as `parsePriceFeedUpdates`, but checks that the updateData is minimal and also returns the Pythnet slots. - /// @param updateData Array of price update data. - /// @param priceIds Array of price ids. - /// @param minPublishTime minimum acceptable publishTime for the given `priceIds`. - /// @param maxPublishTime maximum acceptable publishTime for the given `priceIds`. - /// @return priceFeeds Array of the price feeds corresponding to the given `priceIds` (with the same order). - /// @return slots Array of the Pythnet slot corresponding to the given `priceIds` (with the same order). - function parsePriceFeedUpdatesWithSlotsStrict( - bytes[] calldata updateData, - bytes32[] calldata priceIds, - uint64 minPublishTime, - uint64 maxPublishTime - ) - external - payable - returns ( - PythStructs.PriceFeed[] memory priceFeeds, - uint64[] memory slots - ); } diff --git a/target_chains/ethereum/sdk/solidity/MockPyth.sol b/target_chains/ethereum/sdk/solidity/MockPyth.sol index 106df94711..75f5505554 100644 --- a/target_chains/ethereum/sdk/solidity/MockPyth.sol +++ b/target_chains/ethereum/sdk/solidity/MockPyth.sol @@ -86,7 +86,7 @@ contract MockPyth is AbstractPyth { return singleUpdateFeeInWei * updateData.length; } - function parsePriceFeedUpdatesWithConfig( + function parsePriceFeedUpdatesWithConfig( bytes[] calldata updateData, bytes32[] calldata priceIds, uint64 minAllowedPublishTime, @@ -159,7 +159,7 @@ contract MockPyth is AbstractPyth { maxPublishTime, false, true, - true + false ); } @@ -176,30 +176,7 @@ contract MockPyth is AbstractPyth { maxPublishTime, false, true, - true - ); - } - - function parsePriceFeedUpdatesWithSlotsStrict( - bytes[] calldata updateData, - bytes32[] calldata priceIds, - uint64 minPublishTime, - uint64 maxPublishTime - ) - external - payable - override - returns (PythStructs.PriceFeed[] memory feeds, uint64[] memory slots) - { - return - parsePriceFeedUpdatesWithConfig( - updateData, - priceIds, - minPublishTime, - maxPublishTime, - false, - true, - true + false ); } From 02fe8404f7f3cb72ceba57ebe79edff5c460c929 Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Thu, 29 May 2025 13:11:57 -0500 Subject: [PATCH 13/13] updating version, reverting preincrement, renaming slots strict to with config in test functions --- .../contracts/contracts/pyth/Pyth.sol | 51 +++++---- .../contracts/forge-test/GasBenchmark.t.sol | 4 +- .../ethereum/contracts/forge-test/Pyth.t.sol | 39 +++++-- target_chains/ethereum/sdk/solidity/IPyth.sol | 12 ++- .../ethereum/sdk/solidity/MockPyth.sol | 8 +- .../sdk/solidity/abis/AbstractPyth.json | 21 +++- .../ethereum/sdk/solidity/abis/IPyth.json | 100 ------------------ .../ethereum/sdk/solidity/abis/MockPyth.json | 21 +++- 8 files changed, 106 insertions(+), 150 deletions(-) diff --git a/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol b/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol index 559331e5f5..b4a1df3f14 100644 --- a/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol +++ b/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol @@ -333,18 +333,20 @@ abstract contract Pyth is uint64[] memory slots ) { - if (msg.value < getUpdateFee(updateData)) revert PythErrors.InsufficientFee(); + if (msg.value < getUpdateFee(updateData)) + revert PythErrors.InsufficientFee(); // Create the context struct that holds all shared parameters - PythInternalStructs.UpdateParseContext memory context = PythInternalStructs.UpdateParseContext({ - priceIds: priceIds, - minAllowedPublishTime: minAllowedPublishTime, - maxAllowedPublishTime: maxAllowedPublishTime, - checkUniqueness: checkUniqueness, - checkUpdateDataIsMinimal: checkUpdateDataIsMinimal, - priceFeeds: new PythStructs.PriceFeed[](priceIds.length), - slots: new uint64[](priceIds.length) - }); + PythInternalStructs.UpdateParseContext + memory context = PythInternalStructs.UpdateParseContext({ + priceIds: priceIds, + minAllowedPublishTime: minAllowedPublishTime, + maxAllowedPublishTime: maxAllowedPublishTime, + checkUniqueness: checkUniqueness, + checkUpdateDataIsMinimal: checkUpdateDataIsMinimal, + priceFeeds: new PythStructs.PriceFeed[](priceIds.length), + slots: new uint64[](priceIds.length) + }); // Track total updates for minimal update data check uint64 totalUpdatesAcrossBlobs = 0; @@ -352,24 +354,27 @@ abstract contract Pyth is unchecked { // Process each update, passing the context struct // Parsed results will be filled in context.priceFeeds and context.slots - for (uint i = 0; i < updateData.length; ++i) { + for (uint i = 0; i < updateData.length; i++) { totalUpdatesAcrossBlobs += _processSingleUpdateDataBlob( updateData[i], context ); } - - for (uint j = 0; j < priceIds.length; ++j) { + + for (uint j = 0; j < priceIds.length; j++) { PythStructs.PriceFeed memory pf = context.priceFeeds[j]; if (storeUpdatesIfFresh && pf.id != 0) { - updateLatestPriceIfNecessary(priceIds[j], PythInternalStructs.PriceInfo({ - publishTime: uint64(pf.price.publishTime), - expo: pf.price.expo, - price: pf.price.price, - conf: pf.price.conf, - emaPrice: pf.emaPrice.price, - emaConf: pf.emaPrice.conf - })); + updateLatestPriceIfNecessary( + priceIds[j], + PythInternalStructs.PriceInfo({ + publishTime: uint64(pf.price.publishTime), + expo: pf.price.expo, + price: pf.price.price, + conf: pf.price.conf, + emaPrice: pf.emaPrice.price, + emaConf: pf.emaPrice.conf + }) + ); } } } @@ -383,7 +388,7 @@ abstract contract Pyth is } // Check all price feeds were found - for (uint k = 0; k < priceIds.length; ++k) { + for (uint k = 0; k < priceIds.length; k++) { if (context.priceFeeds[k].id == 0) { revert PythErrors.PriceFeedNotFoundWithinRange(); } @@ -695,7 +700,7 @@ abstract contract Pyth is } function version() public pure returns (string memory) { - return "1.4.5"; + return "1.4.5-alpha.1"; } /// @notice Calculates TWAP from two price points diff --git a/target_chains/ethereum/contracts/forge-test/GasBenchmark.t.sol b/target_chains/ethereum/contracts/forge-test/GasBenchmark.t.sol index d3e05b399f..063a5c2e6a 100644 --- a/target_chains/ethereum/contracts/forge-test/GasBenchmark.t.sol +++ b/target_chains/ethereum/contracts/forge-test/GasBenchmark.t.sol @@ -250,7 +250,7 @@ contract GasBenchmark is Test, WormholeTestUtils, PythTestUtils { 0, 50, false, - true, // check minimal + true, // check minimal false ); } @@ -265,7 +265,7 @@ contract GasBenchmark is Test, WormholeTestUtils, PythTestUtils { 0, 50, false, - true, // check minimal + true, // check minimal false ); } diff --git a/target_chains/ethereum/contracts/forge-test/Pyth.t.sol b/target_chains/ethereum/contracts/forge-test/Pyth.t.sol index 9d97a44f39..e7b41accff 100644 --- a/target_chains/ethereum/contracts/forge-test/Pyth.t.sol +++ b/target_chains/ethereum/contracts/forge-test/Pyth.t.sol @@ -278,7 +278,9 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils { } } - function testParsePriceFeedUpdatesWithConfigIfStorageTrue(uint seed) public { + function testParsePriceFeedUpdatesWithConfigIfStorageTrue( + uint seed + ) public { setRandSeed(seed); uint numMessages = 1 + (getRandUint() % 10); ( @@ -291,9 +293,16 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils { uint updateFee ) = createBatchedUpdateDataFromMessages(messages); - (PythStructs.PriceFeed[] memory priceFeeds,) = pyth.parsePriceFeedUpdatesWithConfig{ - value: updateFee - }(updateData, priceIds, 0, MAX_UINT64, false, true, true); + (PythStructs.PriceFeed[] memory priceFeeds, ) = pyth + .parsePriceFeedUpdatesWithConfig{value: updateFee}( + updateData, + priceIds, + 0, + MAX_UINT64, + false, + true, + true + ); for (uint i = 0; i < numMessages; i++) { // Validating that returned priceIds are equal @@ -322,7 +331,9 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils { } } - function testParsePriceFeedUpdatesWithConfigIfStorageFalse(uint seed) public { + function testParsePriceFeedUpdatesWithConfigIfStorageFalse( + uint seed + ) public { setRandSeed(seed); uint numMessages = 1 + (getRandUint() % 10); ( @@ -335,9 +346,15 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils { uint updateFee ) = createBatchedUpdateDataFromMessages(messages); - pyth.parsePriceFeedUpdatesWithConfig{ - value: updateFee - }(updateData, priceIds, 0, MAX_UINT64, false, true, false); + pyth.parsePriceFeedUpdatesWithConfig{value: updateFee}( + updateData, + priceIds, + 0, + MAX_UINT64, + false, + true, + false + ); // validate that stored prices of each priceId are still unpopulated for (uint i = 0; i < numMessages; i++) { @@ -346,7 +363,7 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils { } } - function testParsePriceFeedUpdatesWithSlotsStrictWorks(uint seed) public { + function testParsePriceFeedUpdatesWithConfigWorks(uint seed) public { setRandSeed(seed); uint numMessages = 1 + (getRandUint() % 10); ( @@ -505,7 +522,7 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils { ); } - function testParsePriceFeedUpdatesWithSlotsStrictRevertsWithExcessUpdateData() + function testParsePriceFeedUpdatesWithConfigRevertsWithExcessUpdateData() public { // Create a price update with more price updates than requested price IDs @@ -541,7 +558,7 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils { ); } - function testParsePriceFeedUpdatesWithSlotsStrictRevertsWithFewerUpdateData() + function testParsePriceFeedUpdatesWithConfigRevertsWithFewerUpdateData() public { // Create a price update with fewer price updates than requested price IDs diff --git a/target_chains/ethereum/sdk/solidity/IPyth.sol b/target_chains/ethereum/sdk/solidity/IPyth.sol index 85f705da0a..2f0eef94c2 100644 --- a/target_chains/ethereum/sdk/solidity/IPyth.sol +++ b/target_chains/ethereum/sdk/solidity/IPyth.sol @@ -136,7 +136,7 @@ interface IPyth is IPythEvents { /// This method requires the caller to pay a fee in wei; the required fee can be computed by calling /// `getUpdateFee` with the length of the `updateData` array. /// - /// This method will eventually allow the caller to determine whether parsed price feeds should update + /// This method will eventually allow the caller to determine whether parsed price feeds should update /// the stored values as well. /// /// @dev Reverts if the transferred fee is not sufficient or the updateData is invalid or there is @@ -145,7 +145,7 @@ interface IPyth is IPythEvents { /// @param priceIds Array of price ids. /// @param minAllowedPublishTime minimum acceptable publishTime for the given `priceIds`. /// @param maxAllowedPublishTime maximum acceptable publishTime for the given `priceIds`. - /// @param storeUpdatesIfFresh flag for the parse function to + /// @param storeUpdatesIfFresh flag for the parse function to /// @return priceFeeds Array of the price feeds corresponding to the given `priceIds` (with the same order). function parsePriceFeedUpdatesWithConfig( bytes[] calldata updateData, @@ -155,7 +155,13 @@ interface IPyth is IPythEvents { bool checkUniqueness, bool checkUpdateDataIsMinimal, bool storeUpdatesIfFresh - ) external payable returns ( PythStructs.PriceFeed[] memory priceFeeds, uint64[] memory slots); + ) + external + payable + returns ( + PythStructs.PriceFeed[] memory priceFeeds, + uint64[] memory slots + ); /// @notice Parse time-weighted average price (TWAP) from two consecutive price updates for the given `priceIds`. /// diff --git a/target_chains/ethereum/sdk/solidity/MockPyth.sol b/target_chains/ethereum/sdk/solidity/MockPyth.sol index 75f5505554..31fd72d3d3 100644 --- a/target_chains/ethereum/sdk/solidity/MockPyth.sol +++ b/target_chains/ethereum/sdk/solidity/MockPyth.sol @@ -97,10 +97,7 @@ contract MockPyth is AbstractPyth { ) public payable - returns ( - PythStructs.PriceFeed[] memory feeds, - uint64[] memory slots - ) + returns (PythStructs.PriceFeed[] memory feeds, uint64[] memory slots) { uint requiredFee = getUpdateFee(updateData); if (msg.value < requiredFee) revert PythErrors.InsufficientFee(); @@ -132,7 +129,8 @@ contract MockPyth is AbstractPyth { if ( minAllowedPublishTime <= publishTime && publishTime <= maxAllowedPublishTime && - (!checkUniqueness || prevPublishTime < minAllowedPublishTime) + (!checkUniqueness || + prevPublishTime < minAllowedPublishTime) ) { break; } else { diff --git a/target_chains/ethereum/sdk/solidity/abis/AbstractPyth.json b/target_chains/ethereum/sdk/solidity/abis/AbstractPyth.json index b0c8f504d0..8cd0d39878 100644 --- a/target_chains/ethereum/sdk/solidity/abis/AbstractPyth.json +++ b/target_chains/ethereum/sdk/solidity/abis/AbstractPyth.json @@ -599,16 +599,31 @@ }, { "internalType": "uint64", - "name": "minPublishTime", + "name": "minAllowedPublishTime", "type": "uint64" }, { "internalType": "uint64", - "name": "maxPublishTime", + "name": "maxAllowedPublishTime", "type": "uint64" + }, + { + "internalType": "bool", + "name": "checkUniqueness", + "type": "bool" + }, + { + "internalType": "bool", + "name": "checkUpdateDataIsMinimal", + "type": "bool" + }, + { + "internalType": "bool", + "name": "storeUpdatesIfFresh", + "type": "bool" } ], - "name": "parsePriceFeedUpdatesWithSlotsStrict", + "name": "parsePriceFeedUpdatesWithConfig", "outputs": [ { "components": [ diff --git a/target_chains/ethereum/sdk/solidity/abis/IPyth.json b/target_chains/ethereum/sdk/solidity/abis/IPyth.json index 3f3f197b4c..260ad3ccaf 100644 --- a/target_chains/ethereum/sdk/solidity/abis/IPyth.json +++ b/target_chains/ethereum/sdk/solidity/abis/IPyth.json @@ -587,106 +587,6 @@ "type": "uint64[]" } ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes[]", - "name": "updateData", - "type": "bytes[]" - }, - { - "internalType": "bytes32[]", - "name": "priceIds", - "type": "bytes32[]" - }, - { - "internalType": "uint64", - "name": "minPublishTime", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "maxPublishTime", - "type": "uint64" - } - ], - "name": "parsePriceFeedUpdatesWithSlotsStrict", - "outputs": [ - { - "components": [ - { - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "price", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "int64", - "name": "price", - "type": "int64" - }, - { - "internalType": "uint64", - "name": "conf", - "type": "uint64" - }, - { - "internalType": "int32", - "name": "expo", - "type": "int32" - }, - { - "internalType": "uint256", - "name": "publishTime", - "type": "uint256" - } - ], - "internalType": "struct PythStructs.Price", - "name": "emaPrice", - "type": "tuple" - } - ], - "internalType": "struct PythStructs.PriceFeed[]", - "name": "priceFeeds", - "type": "tuple[]" - }, - { - "internalType": "uint64[]", - "name": "slots", - "type": "uint64[]" - } - ], "stateMutability": "payable", "type": "function" }, diff --git a/target_chains/ethereum/sdk/solidity/abis/MockPyth.json b/target_chains/ethereum/sdk/solidity/abis/MockPyth.json index ed4a5b0854..a4d1a7b991 100644 --- a/target_chains/ethereum/sdk/solidity/abis/MockPyth.json +++ b/target_chains/ethereum/sdk/solidity/abis/MockPyth.json @@ -738,16 +738,31 @@ }, { "internalType": "uint64", - "name": "minPublishTime", + "name": "minAllowedPublishTime", "type": "uint64" }, { "internalType": "uint64", - "name": "maxPublishTime", + "name": "maxAllowedPublishTime", "type": "uint64" + }, + { + "internalType": "bool", + "name": "checkUniqueness", + "type": "bool" + }, + { + "internalType": "bool", + "name": "checkUpdateDataIsMinimal", + "type": "bool" + }, + { + "internalType": "bool", + "name": "storeUpdatesIfFresh", + "type": "bool" } ], - "name": "parsePriceFeedUpdatesWithSlotsStrict", + "name": "parsePriceFeedUpdatesWithConfig", "outputs": [ { "components": [