From 3cfe08c0b91a04da314e623c008593ba393baf86 Mon Sep 17 00:00:00 2001 From: swimricky Date: Thu, 8 Jun 2023 10:23:38 -0700 Subject: [PATCH 1/4] perf: optimize parse/updatePriceFeeds for gas & bytesize --- .../contracts/contracts/pyth/Pyth.sol | 264 +++++++++++++++--- .../contracts/pyth/PythAccumulator.sol | 120 ++------ 2 files changed, 250 insertions(+), 134 deletions(-) diff --git a/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol b/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol index 8d5086dec9..e8df92d54c 100644 --- a/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol +++ b/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol @@ -413,6 +413,161 @@ abstract contract Pyth is if (!verifyPythVM(vm)) revert PythErrors.InvalidUpdateDataSource(); } + // function parsePriceFeedUpdates( + // bytes[] calldata updateData, + // bytes32[] calldata priceIds, + // uint64 minPublishTime, + // uint64 maxPublishTime + // ) + // external + // payable + // override + // returns (PythStructs.PriceFeed[] memory priceFeeds) + // { + // unchecked { + // { + // uint requiredFee = getUpdateFee(updateData); + // if (msg.value < requiredFee) + // revert PythErrors.InsufficientFee(); + // } + + // priceFeeds = new PythStructs.PriceFeed[](priceIds.length); + // for (uint i = 0; i < updateData.length; i++) { + // if ( + // updateData[i].length > 4 && + // UnsafeBytesLib.toUint32(updateData[i], 0) == + // ACCUMULATOR_MAGIC + // ) { + // ( + // PythInternalStructs.PriceInfo[] + // memory accumulatorPriceInfos, + // bytes32[] memory accumulatorPriceIds + // ) = extractPriceInfosFromAccumulatorUpdate(updateData[i]); + + // for ( + // uint accDataIdx = 0; + // accDataIdx < accumulatorPriceIds.length; + // accDataIdx++ + // ) { + // bytes32 accumulatorPriceId = accumulatorPriceIds[ + // accDataIdx + // ]; + // // check whether caller requested for this data + // uint k = findIndexOfPriceId( + // priceIds, + // accumulatorPriceId + // ); + + // // 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 || priceFeeds[k].id != 0) { + // continue; + // } + + // PythInternalStructs.PriceInfo + // memory info = accumulatorPriceInfos[accDataIdx]; + + // uint publishTime = uint(info.publishTime); + // // Check the publish time of the price is within the given range + // // and only fill the priceFeedsInfo if it is. + // // If is not, default id value of 0 will still be set and + // // this will allow other updates for this price id to be processed. + // if ( + // publishTime >= minPublishTime && + // publishTime <= maxPublishTime + // ) { + // fillPriceFeedFromPriceInfo( + // priceFeeds, + // k, + // accumulatorPriceId, + // info, + // publishTime + // ); + // } + // } + // } else { + // bytes memory encoded; + // { + // IWormhole.VM + // memory vm = parseAndVerifyBatchAttestationVM( + // updateData[i] + // ); + // encoded = vm.payload; + // } + + // /** Batch price logic */ + // // TODO: gas optimization + // ( + // uint index, + // uint nAttestations, + // uint attestationSize + // ) = parseBatchAttestationHeader(encoded); + + // // Deserialize each attestation + // for (uint j = 0; j < nAttestations; j++) { + // // NOTE: We don't advance the global index immediately. + // // attestationIndex is an attestation-local offset used + // // for readability and easier debugging. + // uint attestationIndex = 0; + + // // Unused bytes32 product id + // attestationIndex += 32; + + // bytes32 priceId = UnsafeBytesLib.toBytes32( + // encoded, + // index + attestationIndex + // ); + + // // check whether caller requested for this data + // uint k = findIndexOfPriceId(priceIds, priceId); + + // // 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 || priceFeeds[k].id != 0) { + // index += attestationSize; + // continue; + // } + + // ( + // PythInternalStructs.PriceInfo memory info, + + // ) = parseSingleAttestationFromBatch( + // encoded, + // index, + // attestationSize + // ); + + // uint publishTime = uint(info.publishTime); + // // Check the publish time of the price is within the given range + // // and only fill the priceFeedsInfo if it is. + // // If is not, default id value of 0 will still be set and + // // this will allow other updates for this price id to be processed. + // if ( + // publishTime >= minPublishTime && + // publishTime <= maxPublishTime + // ) { + // fillPriceFeedFromPriceInfo( + // priceFeeds, + // k, + // priceId, + // info, + // publishTime + // ); + // } + + // index += attestationSize; + // } + // } + // } + + // for (uint k = 0; k < priceIds.length; k++) { + // if (priceFeeds[k].id == 0) { + // revert PythErrors.PriceFeedNotFoundWithinRange(); + // } + // } + // } + // } + function parsePriceFeedUpdates( bytes[] calldata updateData, bytes32[] calldata priceIds, @@ -438,53 +593,79 @@ abstract contract Pyth is UnsafeBytesLib.toUint32(updateData[i], 0) == ACCUMULATOR_MAGIC ) { - ( - PythInternalStructs.PriceInfo[] - memory accumulatorPriceInfos, - bytes32[] memory accumulatorPriceIds - ) = extractPriceInfosFromAccumulatorUpdate(updateData[i]); - - for ( - uint accDataIdx = 0; - accDataIdx < accumulatorPriceIds.length; - accDataIdx++ - ) { - bytes32 accumulatorPriceId = accumulatorPriceIds[ - accDataIdx - ]; - // check whether caller requested for this data - uint k = findIndexOfPriceId( - priceIds, - accumulatorPriceId + bytes memory accumulatorUpdate = updateData[i]; + uint offset; + { + UpdateType updateType; + ( + offset, + updateType + ) = extractUpdateTypeFromAccumulatorHeader( + accumulatorUpdate ); - // 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 || priceFeeds[k].id != 0) { - continue; + if (updateType != UpdateType.WormholeMerkle) { + revert PythErrors.InvalidUpdateData(); } + } + bytes20 digest; + uint8 numUpdates; + bytes memory encoded = UnsafeBytesLib.slice( + accumulatorUpdate, + offset, + accumulatorUpdate.length - offset + ); - PythInternalStructs.PriceInfo - memory info = accumulatorPriceInfos[accDataIdx]; + ( + offset, + digest, + numUpdates + ) = extractWormholeMerkleHeaderDigestAndNumUpdates(encoded); - uint publishTime = uint(info.publishTime); - // Check the publish time of the price is within the given range - // and only fill the priceFeedsInfo if it is. - // If is not, default id value of 0 will still be set and - // this will allow other updates for this price id to be processed. - if ( - publishTime >= minPublishTime && - publishTime <= maxPublishTime - ) { - fillPriceFeedFromPriceInfo( - priceFeeds, - k, - accumulatorPriceId, - info, - publishTime - ); + for (uint j = 0; j < numUpdates; j++) { + PythInternalStructs.PriceInfo memory info; + bytes32 priceId; + + ( + offset, + info, + priceId + ) = extractPriceInfoFromWormholeMerkle( + encoded, + offset, + digest + ); + { + // check whether caller requested for this data + uint k = findIndexOfPriceId(priceIds, priceId); + + // 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 || priceFeeds[k].id != 0) { + continue; + } + + uint publishTime = uint(info.publishTime); + // Check the publish time of the price is within the given range + // and only fill the priceFeedsInfo if it is. + // If is not, default id value of 0 will still be set and + // this will allow other updates for this price id to be processed. + if ( + publishTime >= minPublishTime && + publishTime <= maxPublishTime + ) { + fillPriceFeedFromPriceInfo( + priceFeeds, + k, + priceId, + info, + publishTime + ); + } } } + if (offset != encoded.length) + revert PythErrors.InvalidUpdateData(); } else { bytes memory encoded; { @@ -573,8 +754,7 @@ abstract contract Pyth is bytes32 targetPriceId ) private pure returns (uint index) { uint k = 0; - uint len = priceIds.length; - for (; k < len; k++) { + for (; k < priceIds.length; k++) { if (priceIds[k] == targetPriceId) { break; } diff --git a/target_chains/ethereum/contracts/contracts/pyth/PythAccumulator.sol b/target_chains/ethereum/contracts/contracts/pyth/PythAccumulator.sol index 16cf8c7a7f..c66705dabc 100644 --- a/target_chains/ethereum/contracts/contracts/pyth/PythAccumulator.sol +++ b/target_chains/ethereum/contracts/contracts/pyth/PythAccumulator.sol @@ -42,33 +42,6 @@ abstract contract PythAccumulator is PythGetters, PythSetters, AbstractPyth { revert PythErrors.InvalidUpdateDataSource(); } - function extractPriceInfosFromAccumulatorUpdate( - bytes memory accumulatorUpdate - ) - internal - view - returns ( - PythInternalStructs.PriceInfo[] memory priceInfos, - bytes32[] memory priceIds - ) - { - ( - uint offset, - UpdateType updateType - ) = extractUpdateTypeFromAccumulatorHeader(accumulatorUpdate); - - if (updateType != UpdateType.WormholeMerkle) { - revert PythErrors.InvalidUpdateData(); - } - (priceInfos, priceIds) = extractPriceInfosFromWormholeMerkle( - UnsafeBytesLib.slice( - accumulatorUpdate, - offset, - accumulatorUpdate.length - offset - ) - ); - } - function extractUpdateTypeFromAccumulatorHeader( bytes memory accumulatorUpdate ) internal pure returns (uint offset, UpdateType updateType) { @@ -134,35 +107,24 @@ abstract contract PythAccumulator is PythGetters, PythSetters, AbstractPyth { } } - function extractPriceInfosFromWormholeMerkle( - bytes memory encoded + function extractPriceInfoFromWormholeMerkle( + bytes memory encoded, + uint offset, + bytes20 digest ) internal view returns ( - PythInternalStructs.PriceInfo[] memory priceInfos, - bytes32[] memory priceIds + uint endOffset, + PythInternalStructs.PriceInfo memory priceInfo, + bytes32 priceId ) { - unchecked { - ( - uint offset, - bytes20 digest, - uint8 numUpdates - ) = extractWormholeMerkleHeaderDigestAndNumUpdates(encoded); - - priceInfos = new PythInternalStructs.PriceInfo[](numUpdates); - priceIds = new bytes32[](numUpdates); - for (uint i = 0; i < numUpdates; i++) { - ( - offset, - priceInfos[i], - priceIds[i] - ) = extractPriceFeedFromMerkleProof(digest, encoded, offset); - } - - if (offset != encoded.length) revert PythErrors.InvalidUpdateData(); - } + (endOffset, priceInfo, priceId) = extractPriceFeedFromMerkleProof( + digest, + encoded, + offset + ); } function extractWormholeMerkleHeaderDigestAndNumUpdates( @@ -292,7 +254,9 @@ abstract contract PythAccumulator is PythGetters, PythSetters, AbstractPyth { returns (PythInternalStructs.PriceInfo memory info, bytes32 priceId) { unchecked { - MessageType messageType = getMessageType(encodedMessage); + MessageType messageType = MessageType( + UnsafeBytesLib.toUint8(encodedMessage, 0) + ); if (messageType == MessageType.PriceFeed) { (info, priceId) = parsePriceFeedMessage( UnsafeBytesLib.slice( @@ -307,12 +271,6 @@ abstract contract PythAccumulator is PythGetters, PythSetters, AbstractPyth { } } - function getMessageType( - bytes memory encodedMessage - ) private pure returns (MessageType messageType) { - return MessageType(UnsafeBytesLib.toUint8(encodedMessage, 0)); - } - function parsePriceFeedMessage( bytes memory encodedPriceFeed ) @@ -402,47 +360,25 @@ abstract contract PythAccumulator is PythGetters, PythSetters, AbstractPyth { ) = extractWormholeMerkleHeaderDigestAndNumUpdates(encoded); for (uint i = 0; i < numUpdates; i++) { - offset = verifyAndUpdatePriceFeedFromMerkleProof( + PythInternalStructs.PriceInfo memory priceInfo; + bytes32 priceId; + (offset, priceInfo, priceId) = extractPriceFeedFromMerkleProof( digest, encoded, offset ); + uint64 latestPublishTime = latestPriceInfoPublishTime(priceId); + if (priceInfo.publishTime > latestPublishTime) { + setLatestPriceInfo(priceId, priceInfo); + emit PriceFeedUpdate( + priceId, + priceInfo.publishTime, + priceInfo.price, + priceInfo.conf + ); + } } - if (offset != encoded.length) revert PythErrors.InvalidUpdateData(); } } - - function verifyAndUpdatePriceFeedFromMerkleProof( - bytes20 digest, - bytes memory encoded, - uint offset - ) private returns (uint endOffset) { - PythInternalStructs.PriceInfo memory priceInfo; - bytes32 priceId; - (offset, priceInfo, priceId) = extractPriceFeedFromMerkleProof( - digest, - encoded, - offset - ); - processMessage(priceInfo, priceId); - - return offset; - } - - function processMessage( - PythInternalStructs.PriceInfo memory info, - bytes32 priceId - ) private { - uint64 latestPublishTime = latestPriceInfoPublishTime(priceId); - if (info.publishTime > latestPublishTime) { - setLatestPriceInfo(priceId, info); - emit PriceFeedUpdate( - priceId, - info.publishTime, - info.price, - info.conf - ); - } - } } From facbd54e4f2aa43d0ee217d231fd6ec64dc11627 Mon Sep 17 00:00:00 2001 From: swimricky Date: Thu, 8 Jun 2023 10:28:21 -0700 Subject: [PATCH 2/4] chore: cleanup --- .../contracts/contracts/pyth/Pyth.sol | 155 ------------------ 1 file changed, 155 deletions(-) diff --git a/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol b/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol index e8df92d54c..412fe3770c 100644 --- a/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol +++ b/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol @@ -413,161 +413,6 @@ abstract contract Pyth is if (!verifyPythVM(vm)) revert PythErrors.InvalidUpdateDataSource(); } - // function parsePriceFeedUpdates( - // bytes[] calldata updateData, - // bytes32[] calldata priceIds, - // uint64 minPublishTime, - // uint64 maxPublishTime - // ) - // external - // payable - // override - // returns (PythStructs.PriceFeed[] memory priceFeeds) - // { - // unchecked { - // { - // uint requiredFee = getUpdateFee(updateData); - // if (msg.value < requiredFee) - // revert PythErrors.InsufficientFee(); - // } - - // priceFeeds = new PythStructs.PriceFeed[](priceIds.length); - // for (uint i = 0; i < updateData.length; i++) { - // if ( - // updateData[i].length > 4 && - // UnsafeBytesLib.toUint32(updateData[i], 0) == - // ACCUMULATOR_MAGIC - // ) { - // ( - // PythInternalStructs.PriceInfo[] - // memory accumulatorPriceInfos, - // bytes32[] memory accumulatorPriceIds - // ) = extractPriceInfosFromAccumulatorUpdate(updateData[i]); - - // for ( - // uint accDataIdx = 0; - // accDataIdx < accumulatorPriceIds.length; - // accDataIdx++ - // ) { - // bytes32 accumulatorPriceId = accumulatorPriceIds[ - // accDataIdx - // ]; - // // check whether caller requested for this data - // uint k = findIndexOfPriceId( - // priceIds, - // accumulatorPriceId - // ); - - // // 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 || priceFeeds[k].id != 0) { - // continue; - // } - - // PythInternalStructs.PriceInfo - // memory info = accumulatorPriceInfos[accDataIdx]; - - // uint publishTime = uint(info.publishTime); - // // Check the publish time of the price is within the given range - // // and only fill the priceFeedsInfo if it is. - // // If is not, default id value of 0 will still be set and - // // this will allow other updates for this price id to be processed. - // if ( - // publishTime >= minPublishTime && - // publishTime <= maxPublishTime - // ) { - // fillPriceFeedFromPriceInfo( - // priceFeeds, - // k, - // accumulatorPriceId, - // info, - // publishTime - // ); - // } - // } - // } else { - // bytes memory encoded; - // { - // IWormhole.VM - // memory vm = parseAndVerifyBatchAttestationVM( - // updateData[i] - // ); - // encoded = vm.payload; - // } - - // /** Batch price logic */ - // // TODO: gas optimization - // ( - // uint index, - // uint nAttestations, - // uint attestationSize - // ) = parseBatchAttestationHeader(encoded); - - // // Deserialize each attestation - // for (uint j = 0; j < nAttestations; j++) { - // // NOTE: We don't advance the global index immediately. - // // attestationIndex is an attestation-local offset used - // // for readability and easier debugging. - // uint attestationIndex = 0; - - // // Unused bytes32 product id - // attestationIndex += 32; - - // bytes32 priceId = UnsafeBytesLib.toBytes32( - // encoded, - // index + attestationIndex - // ); - - // // check whether caller requested for this data - // uint k = findIndexOfPriceId(priceIds, priceId); - - // // 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 || priceFeeds[k].id != 0) { - // index += attestationSize; - // continue; - // } - - // ( - // PythInternalStructs.PriceInfo memory info, - - // ) = parseSingleAttestationFromBatch( - // encoded, - // index, - // attestationSize - // ); - - // uint publishTime = uint(info.publishTime); - // // Check the publish time of the price is within the given range - // // and only fill the priceFeedsInfo if it is. - // // If is not, default id value of 0 will still be set and - // // this will allow other updates for this price id to be processed. - // if ( - // publishTime >= minPublishTime && - // publishTime <= maxPublishTime - // ) { - // fillPriceFeedFromPriceInfo( - // priceFeeds, - // k, - // priceId, - // info, - // publishTime - // ); - // } - - // index += attestationSize; - // } - // } - // } - - // for (uint k = 0; k < priceIds.length; k++) { - // if (priceFeeds[k].id == 0) { - // revert PythErrors.PriceFeedNotFoundWithinRange(); - // } - // } - // } - // } - function parsePriceFeedUpdates( bytes[] calldata updateData, bytes32[] calldata priceIds, From 813ce347772dea2204be7cc0f3d1c67af6545b45 Mon Sep 17 00:00:00 2001 From: swimricky Date: Thu, 8 Jun 2023 10:36:56 -0700 Subject: [PATCH 3/4] refactor: renaming functions --- .../contracts/contracts/pyth/PythAccumulator.sol | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/target_chains/ethereum/contracts/contracts/pyth/PythAccumulator.sol b/target_chains/ethereum/contracts/contracts/pyth/PythAccumulator.sol index c66705dabc..57fb8ac987 100644 --- a/target_chains/ethereum/contracts/contracts/pyth/PythAccumulator.sol +++ b/target_chains/ethereum/contracts/contracts/pyth/PythAccumulator.sol @@ -120,7 +120,7 @@ abstract contract PythAccumulator is PythGetters, PythSetters, AbstractPyth { bytes32 priceId ) { - (endOffset, priceInfo, priceId) = extractPriceFeedFromMerkleProof( + (endOffset, priceInfo, priceId) = extractPriceInfoFromMerkleProof( digest, encoded, offset @@ -190,7 +190,7 @@ abstract contract PythAccumulator is PythGetters, PythSetters, AbstractPyth { } } - function extractPriceFeedFromMerkleProof( + function extractPriceInfoFromMerkleProof( bytes20 digest, bytes memory encoded, uint offset @@ -211,7 +211,9 @@ abstract contract PythAccumulator is PythGetters, PythSetters, AbstractPyth { digest ); - (priceInfo, priceId) = extractPriceFeedMessage(encodedMessage); + (priceInfo, priceId) = extractPriceInfoAndIdFromPriceFeedMessage( + encodedMessage + ); return (endOffset, priceInfo, priceId); } @@ -246,7 +248,7 @@ abstract contract PythAccumulator is PythGetters, PythSetters, AbstractPyth { } } - function extractPriceFeedMessage( + function extractPriceInfoAndIdFromPriceFeedMessage( bytes memory encodedMessage ) private @@ -362,7 +364,7 @@ abstract contract PythAccumulator is PythGetters, PythSetters, AbstractPyth { for (uint i = 0; i < numUpdates; i++) { PythInternalStructs.PriceInfo memory priceInfo; bytes32 priceId; - (offset, priceInfo, priceId) = extractPriceFeedFromMerkleProof( + (offset, priceInfo, priceId) = extractPriceInfoFromMerkleProof( digest, encoded, offset From cce251b2799d650faa0cf3f6f55af99c5ae2fbcd Mon Sep 17 00:00:00 2001 From: swimricky Date: Thu, 8 Jun 2023 13:35:02 -0700 Subject: [PATCH 4/4] test: add benchmark tests for parse with wh merkle, clean up duplicate code --- .../contracts/contracts/pyth/Pyth.sol | 6 +- .../contracts/pyth/PythAccumulator.sol | 22 +---- .../contracts/forge-test/GasBenchmark.t.sol | 88 +++++++++++++++++++ 3 files changed, 92 insertions(+), 24 deletions(-) diff --git a/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol b/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol index 412fe3770c..503ad36348 100644 --- a/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol +++ b/target_chains/ethereum/contracts/contracts/pyth/Pyth.sol @@ -475,10 +475,10 @@ abstract contract Pyth is offset, info, priceId - ) = extractPriceInfoFromWormholeMerkle( + ) = extractPriceInfoFromMerkleProof( + digest, encoded, - offset, - digest + offset ); { // check whether caller requested for this data diff --git a/target_chains/ethereum/contracts/contracts/pyth/PythAccumulator.sol b/target_chains/ethereum/contracts/contracts/pyth/PythAccumulator.sol index 57fb8ac987..674d20e57e 100644 --- a/target_chains/ethereum/contracts/contracts/pyth/PythAccumulator.sol +++ b/target_chains/ethereum/contracts/contracts/pyth/PythAccumulator.sol @@ -107,26 +107,6 @@ abstract contract PythAccumulator is PythGetters, PythSetters, AbstractPyth { } } - function extractPriceInfoFromWormholeMerkle( - bytes memory encoded, - uint offset, - bytes20 digest - ) - internal - view - returns ( - uint endOffset, - PythInternalStructs.PriceInfo memory priceInfo, - bytes32 priceId - ) - { - (endOffset, priceInfo, priceId) = extractPriceInfoFromMerkleProof( - digest, - encoded, - offset - ); - } - function extractWormholeMerkleHeaderDigestAndNumUpdates( bytes memory encoded ) internal view returns (uint offset, bytes20 digest, uint8 numUpdates) { @@ -195,7 +175,7 @@ abstract contract PythAccumulator is PythGetters, PythSetters, AbstractPyth { bytes memory encoded, uint offset ) - private + internal pure returns ( uint endOffset, diff --git a/target_chains/ethereum/contracts/forge-test/GasBenchmark.t.sol b/target_chains/ethereum/contracts/forge-test/GasBenchmark.t.sol index 232f09a871..10c10dbe5c 100644 --- a/target_chains/ethereum/contracts/forge-test/GasBenchmark.t.sol +++ b/target_chains/ethereum/contracts/forge-test/GasBenchmark.t.sol @@ -272,6 +272,94 @@ contract GasBenchmark is Test, WormholeTestUtils, PythTestUtils { ); } + function testBenchmarkParsePriceFeedUpdatesForWhMerkle1() public { + uint numIds = 1; + + bytes32[] memory ids = new bytes32[](numIds); + for (uint i = 0; i < numIds; i++) { + ids[i] = priceIds[i]; + } + pyth.parsePriceFeedUpdates{ + value: freshPricesWhMerkleUpdateFee[numIds - 1] + }(freshPricesWhMerkleUpdateData[numIds - 1], ids, 0, 50); + } + + function testBenchmarkParsePriceFeedUpdatesForWhMerkle2() public { + uint numIds = 2; + + bytes32[] memory ids = new bytes32[](numIds); + for (uint i = 0; i < numIds; i++) { + ids[i] = priceIds[i]; + } + pyth.parsePriceFeedUpdates{ + value: freshPricesWhMerkleUpdateFee[numIds - 1] + }(freshPricesWhMerkleUpdateData[numIds - 1], ids, 0, 50); + } + + function testBenchmarkParsePriceFeedUpdatesForWhMerkle3() public { + uint numIds = 3; + + bytes32[] memory ids = new bytes32[](numIds); + for (uint i = 0; i < numIds; i++) { + ids[i] = priceIds[i]; + } + pyth.parsePriceFeedUpdates{ + value: freshPricesWhMerkleUpdateFee[numIds - 1] + }(freshPricesWhMerkleUpdateData[numIds - 1], ids, 0, 50); + } + + function testBenchmarkParsePriceFeedUpdatesForWhMerkle4() public { + uint numIds = 4; + + bytes32[] memory ids = new bytes32[](numIds); + for (uint i = 0; i < numIds; i++) { + ids[i] = priceIds[i]; + } + pyth.parsePriceFeedUpdates{ + value: freshPricesWhMerkleUpdateFee[numIds - 1] + }(freshPricesWhMerkleUpdateData[numIds - 1], ids, 0, 50); + } + + function testBenchmarkParsePriceFeedUpdatesForWhMerkle5() public { + uint numIds = 5; + + bytes32[] memory ids = new bytes32[](numIds); + for (uint i = 0; i < numIds; i++) { + ids[i] = priceIds[i]; + } + pyth.parsePriceFeedUpdates{ + value: freshPricesWhMerkleUpdateFee[numIds - 1] + }(freshPricesWhMerkleUpdateData[numIds - 1], ids, 0, 50); + } + + function testBenchmarkParsePriceFeedUpdatesForAllPriceFeedsShuffledSubsetPriceIds() + public + { + uint numIds = 3; + bytes32[] memory ids = new bytes32[](numIds); + ids[0] = priceIds[4]; + ids[1] = priceIds[2]; + ids[2] = priceIds[0]; + pyth.parsePriceFeedUpdates{ + value: freshPricesWhMerkleUpdateFee[numIds - 1] + }(freshPricesWhMerkleUpdateData[4], ids, 0, 50); + } + + function testBenchmarkParsePriceFeedUpdatesWhMerkleForOnePriceFeedNotWithinRange() + public + { + bytes32[] memory ids = new bytes32[](1); + ids[0] = priceIds[0]; + + vm.expectRevert(PythErrors.PriceFeedNotFoundWithinRange.selector); + pyth.parsePriceFeedUpdates{value: freshPricesWhMerkleUpdateFee[0]}( + freshPricesWhMerkleUpdateData[0], + ids, + 50, + 100 + ); + } + function testBenchmarkParsePriceFeedUpdatesForOnePriceFeedNotWithinRange() public {