فهرست منبع

feat(target_chains/ethereum/pyth): strict minimal updateData parsing (#2637)

* feat: strict updatedata parsing

* fix: abis, interfaces

* fix: merge, optimizer_runs

* fix: reset optimizer_runs to 200
Tejas Badadare 6 ماه پیش
والد
کامیت
f01d4bd4bc

+ 1 - 1
target_chains/ethereum/contracts/contracts/pulse/Scheduler.sol

@@ -273,7 +273,7 @@ abstract contract Scheduler is IScheduler, SchedulerState {
         (
         (
             PythStructs.PriceFeed[] memory priceFeeds,
             PythStructs.PriceFeed[] memory priceFeeds,
             uint64[] memory slots
             uint64[] memory slots
-        ) = pyth.parsePriceFeedUpdatesWithSlots{value: pythFee}(
+        ) = pyth.parsePriceFeedUpdatesWithSlotsStrict{value: pythFee}(
                 updateData,
                 updateData,
                 params.priceIds,
                 params.priceIds,
                 0, // We enforce the past max validity ourselves in _validateShouldUpdatePrices
                 0, // We enforce the past max validity ourselves in _validateShouldUpdatePrices

+ 45 - 25
target_chains/ethereum/contracts/contracts/pyth/Pyth.sol

@@ -239,10 +239,10 @@ abstract contract Pyth is
         if (k < context.priceIds.length && context.priceFeeds[k].id == 0) {
         if (k < context.priceIds.length && context.priceFeeds[k].id == 0) {
             uint publishTime = uint(priceInfo.publishTime);
             uint publishTime = uint(priceInfo.publishTime);
             if (
             if (
-                publishTime >= context.config.minPublishTime &&
-                publishTime <= context.config.maxPublishTime &&
-                (!context.config.checkUniqueness ||
-                    context.config.minPublishTime > prevPublishTime)
+                publishTime >= context.minAllowedPublishTime &&
+                publishTime <= context.maxAllowedPublishTime &&
+                (!context.checkUniqueness ||
+                    context.minAllowedPublishTime > prevPublishTime)
             ) {
             ) {
                 context.priceFeeds[k].id = priceId;
                 context.priceFeeds[k].id = priceId;
                 context.priceFeeds[k].price.price = priceInfo.price;
                 context.priceFeeds[k].price.price = priceInfo.price;
@@ -262,7 +262,7 @@ abstract contract Pyth is
     function _processSingleUpdateDataBlob(
     function _processSingleUpdateDataBlob(
         bytes calldata singleUpdateData,
         bytes calldata singleUpdateData,
         PythInternalStructs.UpdateParseContext memory context
         PythInternalStructs.UpdateParseContext memory context
-    ) internal view {
+    ) internal view returns (uint64 numUpdates) {
         // Check magic number and length first
         // Check magic number and length first
         if (
         if (
             singleUpdateData.length <= 4 ||
             singleUpdateData.length <= 4 ||
@@ -312,12 +312,18 @@ abstract contract Pyth is
         if (offset != encoded.length) {
         if (offset != encoded.length) {
             revert PythErrors.InvalidUpdateData();
             revert PythErrors.InvalidUpdateData();
         }
         }
+
+        // Return the number of updates in this blob for tracking
+        return merkleData.numUpdates;
     }
     }
 
 
     function parsePriceFeedUpdatesInternal(
     function parsePriceFeedUpdatesInternal(
         bytes[] calldata updateData,
         bytes[] calldata updateData,
         bytes32[] calldata priceIds,
         bytes32[] calldata priceIds,
-        PythInternalStructs.ParseConfig memory config
+        uint64 minAllowedPublishTime,
+        uint64 maxAllowedPublishTime,
+        bool checkUniqueness,
+        bool checkUpdateDataIsMinimal
     )
     )
         internal
         internal
         returns (
         returns (
@@ -333,18 +339,35 @@ abstract contract Pyth is
         // Create the context struct that holds all shared parameters
         // Create the context struct that holds all shared parameters
         PythInternalStructs.UpdateParseContext memory context;
         PythInternalStructs.UpdateParseContext memory context;
         context.priceIds = priceIds;
         context.priceIds = priceIds;
-        context.config = config;
+        context.minAllowedPublishTime = minAllowedPublishTime;
+        context.maxAllowedPublishTime = maxAllowedPublishTime;
+        context.checkUniqueness = checkUniqueness;
+        context.checkUpdateDataIsMinimal = checkUpdateDataIsMinimal;
         context.priceFeeds = new PythStructs.PriceFeed[](priceIds.length);
         context.priceFeeds = new PythStructs.PriceFeed[](priceIds.length);
         context.slots = new uint64[](priceIds.length);
         context.slots = new uint64[](priceIds.length);
 
 
+        // Track total updates for minimal update data check
+        uint64 totalUpdatesAcrossBlobs = 0;
+
         unchecked {
         unchecked {
             // Process each update, passing the context struct
             // Process each update, passing the context struct
             // Parsed results will be filled in context.priceFeeds and context.slots
             // 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++) {
-                _processSingleUpdateDataBlob(updateData[i], context);
+                totalUpdatesAcrossBlobs += _processSingleUpdateDataBlob(
+                    updateData[i],
+                    context
+                );
             }
             }
         }
         }
 
 
+        // In minimal update data mode, revert if we have more or less updates than price IDs
+        if (
+            checkUpdateDataIsMinimal &&
+            totalUpdatesAcrossBlobs != priceIds.length
+        ) {
+            revert PythErrors.InvalidArgument();
+        }
+
         // Check all price feeds were found
         // 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) {
             if (context.priceFeeds[k].id == 0) {
@@ -369,15 +392,14 @@ abstract contract Pyth is
         (priceFeeds, ) = parsePriceFeedUpdatesInternal(
         (priceFeeds, ) = parsePriceFeedUpdatesInternal(
             updateData,
             updateData,
             priceIds,
             priceIds,
-            PythInternalStructs.ParseConfig(
-                minPublishTime,
-                maxPublishTime,
-                false
-            )
+            minPublishTime,
+            maxPublishTime,
+            false,
+            false
         );
         );
     }
     }
 
 
-    function parsePriceFeedUpdatesWithSlots(
+    function parsePriceFeedUpdatesWithSlotsStrict(
         bytes[] calldata updateData,
         bytes[] calldata updateData,
         bytes32[] calldata priceIds,
         bytes32[] calldata priceIds,
         uint64 minPublishTime,
         uint64 minPublishTime,
@@ -395,11 +417,10 @@ abstract contract Pyth is
             parsePriceFeedUpdatesInternal(
             parsePriceFeedUpdatesInternal(
                 updateData,
                 updateData,
                 priceIds,
                 priceIds,
-                PythInternalStructs.ParseConfig(
-                    minPublishTime,
-                    maxPublishTime,
-                    false
-                )
+                minPublishTime,
+                maxPublishTime,
+                false,
+                true
             );
             );
     }
     }
 
 
@@ -606,11 +627,10 @@ abstract contract Pyth is
         (priceFeeds, ) = parsePriceFeedUpdatesInternal(
         (priceFeeds, ) = parsePriceFeedUpdatesInternal(
             updateData,
             updateData,
             priceIds,
             priceIds,
-            PythInternalStructs.ParseConfig(
-                minPublishTime,
-                maxPublishTime,
-                true
-            )
+            minPublishTime,
+            maxPublishTime,
+            true,
+            false
         );
         );
     }
     }
 
 
@@ -683,7 +703,7 @@ abstract contract Pyth is
     }
     }
 
 
     function version() public pure returns (string memory) {
     function version() public pure returns (string memory) {
-        return "1.4.4";
+        return "1.4.5-alpha.1";
     }
     }
 
 
     /// @notice Calculates TWAP from two price points
     /// @notice Calculates TWAP from two price points

+ 7 - 7
target_chains/ethereum/contracts/contracts/pyth/PythInternalStructs.sol

@@ -9,18 +9,18 @@ import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
 contract PythInternalStructs {
 contract PythInternalStructs {
     using BytesLib for bytes;
     using BytesLib for bytes;
 
 
-    struct ParseConfig {
-        uint64 minPublishTime;
-        uint64 maxPublishTime;
-        bool checkUniqueness;
-    }
-
     /// Internal struct to hold parameters for update processing
     /// Internal struct to hold parameters for update processing
     /// @dev Storing these variable in a struct rather than local variables
     /// @dev Storing these variable in a struct rather than local variables
     /// helps reduce stack depth when passing arguments to functions.
     /// helps reduce stack depth when passing arguments to functions.
     struct UpdateParseContext {
     struct UpdateParseContext {
         bytes32[] priceIds;
         bytes32[] priceIds;
-        ParseConfig config;
+        uint64 minAllowedPublishTime;
+        uint64 maxAllowedPublishTime;
+        bool checkUniqueness;
+        /// When checkUpdateDataIsMinimal is true, parsing will revert
+        /// if the number of passed in updates exceeds or is less than
+        /// the length of priceIds.
+        bool checkUpdateDataIsMinimal;
         PythStructs.PriceFeed[] priceFeeds;
         PythStructs.PriceFeed[] priceFeeds;
         uint64[] slots;
         uint64[] slots;
     }
     }

+ 24 - 20
target_chains/ethereum/contracts/forge-test/PulseScheduler.t.sol

@@ -256,7 +256,11 @@ contract SchedulerTest is Test, SchedulerEvents, PulseSchedulerTestUtils {
             numInitialFeeds
             numInitialFeeds
         );
         );
 
 
-        mockParsePriceFeedUpdatesWithSlots(pyth, initialPriceFeeds, slots);
+        mockParsePriceFeedUpdatesWithSlotsStrict(
+            pyth,
+            initialPriceFeeds,
+            slots
+        );
         bytes[] memory updateData = createMockUpdateData(initialPriceFeeds);
         bytes[] memory updateData = createMockUpdateData(initialPriceFeeds);
 
 
         vm.prank(pusher);
         vm.prank(pusher);
@@ -830,7 +834,7 @@ contract SchedulerTest is Test, SchedulerEvents, PulseSchedulerTestUtils {
             priceIds.length
             priceIds.length
         );
         );
 
 
-        mockParsePriceFeedUpdatesWithSlots(pyth, priceFeeds1, slots);
+        mockParsePriceFeedUpdatesWithSlotsStrict(pyth, priceFeeds1, slots);
         bytes[] memory updateData1 = createMockUpdateData(priceFeeds1);
         bytes[] memory updateData1 = createMockUpdateData(priceFeeds1);
 
 
         // Perform first update
         // Perform first update
@@ -881,7 +885,7 @@ contract SchedulerTest is Test, SchedulerEvents, PulseSchedulerTestUtils {
             priceFeeds2[i].emaPrice.publishTime = publishTime2;
             priceFeeds2[i].emaPrice.publishTime = publishTime2;
         }
         }
 
 
-        mockParsePriceFeedUpdatesWithSlots(pyth, priceFeeds2, slots); // Mock for the second call
+        mockParsePriceFeedUpdatesWithSlotsStrict(pyth, priceFeeds2, slots); // Mock for the second call
         bytes[] memory updateData2 = createMockUpdateData(priceFeeds2);
         bytes[] memory updateData2 = createMockUpdateData(priceFeeds2);
 
 
         // Perform second update
         // Perform second update
@@ -942,7 +946,7 @@ contract SchedulerTest is Test, SchedulerEvents, PulseSchedulerTestUtils {
             );
             );
 
 
         uint256 mockPythFee = MOCK_PYTH_FEE_PER_FEED * params.priceIds.length;
         uint256 mockPythFee = MOCK_PYTH_FEE_PER_FEED * params.priceIds.length;
-        mockParsePriceFeedUpdatesWithSlots(pyth, priceFeeds, slots);
+        mockParsePriceFeedUpdatesWithSlotsStrict(pyth, priceFeeds, slots);
         bytes[] memory updateData = createMockUpdateData(priceFeeds);
         bytes[] memory updateData = createMockUpdateData(priceFeeds);
 
 
         // Get state before
         // Get state before
@@ -1027,7 +1031,7 @@ contract SchedulerTest is Test, SchedulerEvents, PulseSchedulerTestUtils {
             priceIds.length
             priceIds.length
         );
         );
         uint256 mockPythFee = MOCK_PYTH_FEE_PER_FEED * priceIds.length;
         uint256 mockPythFee = MOCK_PYTH_FEE_PER_FEED * priceIds.length;
-        mockParsePriceFeedUpdatesWithSlots(pyth, priceFeeds, slots);
+        mockParsePriceFeedUpdatesWithSlotsStrict(pyth, priceFeeds, slots);
         bytes[] memory updateData = createMockUpdateData(priceFeeds);
         bytes[] memory updateData = createMockUpdateData(priceFeeds);
 
 
         // Calculate minimum keeper fee (overhead + feed-specific fee)
         // Calculate minimum keeper fee (overhead + feed-specific fee)
@@ -1085,7 +1089,7 @@ contract SchedulerTest is Test, SchedulerEvents, PulseSchedulerTestUtils {
         PythStructs.PriceFeed[] memory priceFeeds1;
         PythStructs.PriceFeed[] memory priceFeeds1;
         uint64[] memory slots1;
         uint64[] memory slots1;
         (priceFeeds1, slots1) = createMockPriceFeedsWithSlots(publishTime1, 2);
         (priceFeeds1, slots1) = createMockPriceFeedsWithSlots(publishTime1, 2);
-        mockParsePriceFeedUpdatesWithSlots(pyth, priceFeeds1, slots1);
+        mockParsePriceFeedUpdatesWithSlotsStrict(pyth, priceFeeds1, slots1);
         bytes[] memory updateData1 = createMockUpdateData(priceFeeds1);
         bytes[] memory updateData1 = createMockUpdateData(priceFeeds1);
         vm.prank(pusher);
         vm.prank(pusher);
         scheduler.updatePriceFeeds(subscriptionId, updateData1);
         scheduler.updatePriceFeeds(subscriptionId, updateData1);
@@ -1096,7 +1100,7 @@ contract SchedulerTest is Test, SchedulerEvents, PulseSchedulerTestUtils {
         PythStructs.PriceFeed[] memory priceFeeds2;
         PythStructs.PriceFeed[] memory priceFeeds2;
         uint64[] memory slots2;
         uint64[] memory slots2;
         (priceFeeds2, slots2) = createMockPriceFeedsWithSlots(publishTime2, 2);
         (priceFeeds2, slots2) = createMockPriceFeedsWithSlots(publishTime2, 2);
-        mockParsePriceFeedUpdatesWithSlots(pyth, priceFeeds2, slots2);
+        mockParsePriceFeedUpdatesWithSlotsStrict(pyth, priceFeeds2, slots2);
         bytes[] memory updateData2 = createMockUpdateData(priceFeeds2);
         bytes[] memory updateData2 = createMockUpdateData(priceFeeds2);
 
 
         // Expect revert because heartbeat condition is not met
         // Expect revert because heartbeat condition is not met
@@ -1132,7 +1136,7 @@ contract SchedulerTest is Test, SchedulerEvents, PulseSchedulerTestUtils {
         PythStructs.PriceFeed[] memory priceFeeds1;
         PythStructs.PriceFeed[] memory priceFeeds1;
         uint64[] memory slots;
         uint64[] memory slots;
         (priceFeeds1, slots) = createMockPriceFeedsWithSlots(publishTime1, 2);
         (priceFeeds1, slots) = createMockPriceFeedsWithSlots(publishTime1, 2);
-        mockParsePriceFeedUpdatesWithSlots(pyth, priceFeeds1, slots);
+        mockParsePriceFeedUpdatesWithSlotsStrict(pyth, priceFeeds1, slots);
         bytes[] memory updateData1 = createMockUpdateData(priceFeeds1);
         bytes[] memory updateData1 = createMockUpdateData(priceFeeds1);
         vm.prank(pusher);
         vm.prank(pusher);
         scheduler.updatePriceFeeds(subscriptionId, updateData1);
         scheduler.updatePriceFeeds(subscriptionId, updateData1);
@@ -1158,7 +1162,7 @@ contract SchedulerTest is Test, SchedulerEvents, PulseSchedulerTestUtils {
             priceFeeds2[i].price.publishTime = publishTime2;
             priceFeeds2[i].price.publishTime = publishTime2;
         }
         }
 
 
-        mockParsePriceFeedUpdatesWithSlots(pyth, priceFeeds2, slots);
+        mockParsePriceFeedUpdatesWithSlotsStrict(pyth, priceFeeds2, slots);
         bytes[] memory updateData2 = createMockUpdateData(priceFeeds2);
         bytes[] memory updateData2 = createMockUpdateData(priceFeeds2);
 
 
         // Expect revert because deviation condition is not met
         // Expect revert because deviation condition is not met
@@ -1183,7 +1187,7 @@ contract SchedulerTest is Test, SchedulerEvents, PulseSchedulerTestUtils {
         PythStructs.PriceFeed[] memory priceFeeds1;
         PythStructs.PriceFeed[] memory priceFeeds1;
         uint64[] memory slots1;
         uint64[] memory slots1;
         (priceFeeds1, slots1) = createMockPriceFeedsWithSlots(publishTime1, 2);
         (priceFeeds1, slots1) = createMockPriceFeedsWithSlots(publishTime1, 2);
-        mockParsePriceFeedUpdatesWithSlots(pyth, priceFeeds1, slots1);
+        mockParsePriceFeedUpdatesWithSlotsStrict(pyth, priceFeeds1, slots1);
         bytes[] memory updateData1 = createMockUpdateData(priceFeeds1);
         bytes[] memory updateData1 = createMockUpdateData(priceFeeds1);
 
 
         vm.prank(pusher);
         vm.prank(pusher);
@@ -1195,7 +1199,7 @@ contract SchedulerTest is Test, SchedulerEvents, PulseSchedulerTestUtils {
         uint64[] memory slots2;
         uint64[] memory slots2;
         (priceFeeds2, slots2) = createMockPriceFeedsWithSlots(publishTime2, 2);
         (priceFeeds2, slots2) = createMockPriceFeedsWithSlots(publishTime2, 2);
         // Mock Pyth response to return feeds with the older timestamp
         // Mock Pyth response to return feeds with the older timestamp
-        mockParsePriceFeedUpdatesWithSlots(pyth, priceFeeds2, slots2);
+        mockParsePriceFeedUpdatesWithSlotsStrict(pyth, priceFeeds2, slots2);
         bytes[] memory updateData2 = createMockUpdateData(priceFeeds2);
         bytes[] memory updateData2 = createMockUpdateData(priceFeeds2);
 
 
         // Expect revert with TimestampOlderThanLastUpdate (checked in _validateShouldUpdatePrices)
         // Expect revert with TimestampOlderThanLastUpdate (checked in _validateShouldUpdatePrices)
@@ -1235,7 +1239,7 @@ contract SchedulerTest is Test, SchedulerEvents, PulseSchedulerTestUtils {
         slots[1] = 200; // Different slot
         slots[1] = 200; // Different slot
 
 
         // Mock Pyth response to return these feeds with mismatched slots
         // Mock Pyth response to return these feeds with mismatched slots
-        mockParsePriceFeedUpdatesWithSlots(pyth, priceFeeds, slots);
+        mockParsePriceFeedUpdatesWithSlotsStrict(pyth, priceFeeds, slots);
         bytes[] memory updateData = createMockUpdateData(priceFeeds);
         bytes[] memory updateData = createMockUpdateData(priceFeeds);
 
 
         // Expect revert with PriceSlotMismatch error
         // Expect revert with PriceSlotMismatch error
@@ -1350,7 +1354,7 @@ contract SchedulerTest is Test, SchedulerEvents, PulseSchedulerTestUtils {
                 PythStructs.PriceFeed[] memory priceFeeds_reduce,
                 PythStructs.PriceFeed[] memory priceFeeds_reduce,
                 uint64[] memory slots_reduce
                 uint64[] memory slots_reduce
             ) = createMockPriceFeedsWithSlots(publishTime + (i * 60), 2);
             ) = createMockPriceFeedsWithSlots(publishTime + (i * 60), 2);
-            mockParsePriceFeedUpdatesWithSlots(
+            mockParsePriceFeedUpdatesWithSlotsStrict(
                 pyth,
                 pyth,
                 priceFeeds_reduce,
                 priceFeeds_reduce,
                 slots_reduce
                 slots_reduce
@@ -1422,7 +1426,7 @@ contract SchedulerTest is Test, SchedulerEvents, PulseSchedulerTestUtils {
         PythStructs.PriceFeed[] memory priceFeeds;
         PythStructs.PriceFeed[] memory priceFeeds;
         uint64[] memory slots;
         uint64[] memory slots;
         (priceFeeds, slots) = createMockPriceFeedsWithSlots(publishTime, 2);
         (priceFeeds, slots) = createMockPriceFeedsWithSlots(publishTime, 2);
-        mockParsePriceFeedUpdatesWithSlots(pyth, priceFeeds, slots);
+        mockParsePriceFeedUpdatesWithSlotsStrict(pyth, priceFeeds, slots);
         bytes[] memory updateData = createMockUpdateData(priceFeeds);
         bytes[] memory updateData = createMockUpdateData(priceFeeds);
 
 
         vm.prank(pusher);
         vm.prank(pusher);
@@ -1464,7 +1468,7 @@ contract SchedulerTest is Test, SchedulerEvents, PulseSchedulerTestUtils {
         PythStructs.PriceFeed[] memory priceFeeds;
         PythStructs.PriceFeed[] memory priceFeeds;
         uint64[] memory slots;
         uint64[] memory slots;
         (priceFeeds, slots) = createMockPriceFeedsWithSlots(publishTime, 3);
         (priceFeeds, slots) = createMockPriceFeedsWithSlots(publishTime, 3);
-        mockParsePriceFeedUpdatesWithSlots(pyth, priceFeeds, slots);
+        mockParsePriceFeedUpdatesWithSlotsStrict(pyth, priceFeeds, slots);
         bytes[] memory updateData = createMockUpdateData(priceFeeds);
         bytes[] memory updateData = createMockUpdateData(priceFeeds);
 
 
         vm.prank(pusher);
         vm.prank(pusher);
@@ -1519,7 +1523,7 @@ contract SchedulerTest is Test, SchedulerEvents, PulseSchedulerTestUtils {
         PythStructs.PriceFeed[] memory priceFeeds;
         PythStructs.PriceFeed[] memory priceFeeds;
         uint64[] memory slots;
         uint64[] memory slots;
         (priceFeeds, slots) = createMockPriceFeedsWithSlots(publishTime, 2);
         (priceFeeds, slots) = createMockPriceFeedsWithSlots(publishTime, 2);
-        mockParsePriceFeedUpdatesWithSlots(pyth, priceFeeds, slots);
+        mockParsePriceFeedUpdatesWithSlotsStrict(pyth, priceFeeds, slots);
         bytes[] memory updateData = createMockUpdateData(priceFeeds);
         bytes[] memory updateData = createMockUpdateData(priceFeeds);
 
 
         vm.prank(pusher);
         vm.prank(pusher);
@@ -1563,7 +1567,7 @@ contract SchedulerTest is Test, SchedulerEvents, PulseSchedulerTestUtils {
             publishTime,
             publishTime,
             priceIds.length
             priceIds.length
         );
         );
-        mockParsePriceFeedUpdatesWithSlots(pyth, priceFeeds, slots);
+        mockParsePriceFeedUpdatesWithSlotsStrict(pyth, priceFeeds, slots);
         bytes[] memory updateData = createMockUpdateData(priceFeeds);
         bytes[] memory updateData = createMockUpdateData(priceFeeds);
 
 
         vm.prank(pusher);
         vm.prank(pusher);
@@ -1630,7 +1634,7 @@ contract SchedulerTest is Test, SchedulerEvents, PulseSchedulerTestUtils {
             priceFeeds[i].emaPrice.expo = priceFeeds[i].price.expo;
             priceFeeds[i].emaPrice.expo = priceFeeds[i].price.expo;
         }
         }
 
 
-        mockParsePriceFeedUpdatesWithSlots(pyth, priceFeeds, slots);
+        mockParsePriceFeedUpdatesWithSlotsStrict(pyth, priceFeeds, slots);
         bytes[] memory updateData = createMockUpdateData(priceFeeds);
         bytes[] memory updateData = createMockUpdateData(priceFeeds);
 
 
         vm.prank(pusher);
         vm.prank(pusher);
@@ -1935,7 +1939,7 @@ contract SchedulerTest is Test, SchedulerEvents, PulseSchedulerTestUtils {
         slots[1] = 100; // Same slot
         slots[1] = 100; // Same slot
 
 
         // Mock Pyth response (should succeed in the real world as minValidTime is 0)
         // Mock Pyth response (should succeed in the real world as minValidTime is 0)
-        mockParsePriceFeedUpdatesWithSlots(pyth, priceFeeds, slots);
+        mockParsePriceFeedUpdatesWithSlotsStrict(pyth, priceFeeds, slots);
         bytes[] memory updateData = createMockUpdateData(priceFeeds);
         bytes[] memory updateData = createMockUpdateData(priceFeeds);
 
 
         // Expect PricesUpdated event with the latest valid timestamp
         // Expect PricesUpdated event with the latest valid timestamp
@@ -1988,7 +1992,7 @@ contract SchedulerTest is Test, SchedulerEvents, PulseSchedulerTestUtils {
         slots[1] = 100; // Same slot
         slots[1] = 100; // Same slot
 
 
         // Mock Pyth response (should succeed in the real world as minValidTime is 0)
         // Mock Pyth response (should succeed in the real world as minValidTime is 0)
-        mockParsePriceFeedUpdatesWithSlots(pyth, priceFeeds, slots);
+        mockParsePriceFeedUpdatesWithSlotsStrict(pyth, priceFeeds, slots);
         bytes[] memory updateData = createMockUpdateData(priceFeeds);
         bytes[] memory updateData = createMockUpdateData(priceFeeds);
 
 
         // Expect revert with TimestampTooOld (checked in _validateShouldUpdatePrices)
         // Expect revert with TimestampTooOld (checked in _validateShouldUpdatePrices)

+ 2 - 2
target_chains/ethereum/contracts/forge-test/PulseSchedulerGasBenchmark.t.sol

@@ -71,7 +71,7 @@ contract PulseSchedulerGasBenchmark is Test, PulseSchedulerTestUtils {
         );
         );
 
 
         // Mock Pyth response for the benchmark
         // Mock Pyth response for the benchmark
-        mockParsePriceFeedUpdatesWithSlots(pyth, newPriceFeeds, newSlots);
+        mockParsePriceFeedUpdatesWithSlotsStrict(pyth, newPriceFeeds, newSlots);
 
 
         // Actual benchmark: Measure gas for updating price feeds
         // Actual benchmark: Measure gas for updating price feeds
         uint256 startGas = gasleft();
         uint256 startGas = gasleft();
@@ -124,7 +124,7 @@ contract PulseSchedulerGasBenchmark is Test, PulseSchedulerTestUtils {
             numFeeds
             numFeeds
         );
         );
 
 
-        mockParsePriceFeedUpdatesWithSlots(pyth, priceFeeds, slots);
+        mockParsePriceFeedUpdatesWithSlotsStrict(pyth, priceFeeds, slots);
         bytes[] memory updateData = createMockUpdateData(priceFeeds);
         bytes[] memory updateData = createMockUpdateData(priceFeeds);
 
 
         // Update the price feeds. We should have enough balance to cover the update
         // Update the price feeds. We should have enough balance to cover the update

+ 72 - 2
target_chains/ethereum/contracts/forge-test/Pyth.t.sol

@@ -278,7 +278,7 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils {
         }
         }
     }
     }
 
 
-    function testParsePriceFeedUpdatesWithSlotsWorks(uint seed) public {
+    function testParsePriceFeedUpdatesWithSlotsStrictWorks(uint seed) public {
         setRandSeed(seed);
         setRandSeed(seed);
         uint numMessages = 1 + (getRandUint() % 10);
         uint numMessages = 1 + (getRandUint() % 10);
         (
         (
@@ -293,7 +293,7 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils {
         (
         (
             PythStructs.PriceFeed[] memory priceFeeds,
             PythStructs.PriceFeed[] memory priceFeeds,
             uint64[] memory slots
             uint64[] memory slots
-        ) = pyth.parsePriceFeedUpdatesWithSlots{value: updateFee}(
+        ) = pyth.parsePriceFeedUpdatesWithSlotsStrict{value: updateFee}(
                 updateData,
                 updateData,
                 priceIds,
                 priceIds,
                 0,
                 0,
@@ -434,6 +434,76 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils {
         );
         );
     }
     }
 
 
+    function testParsePriceFeedUpdatesWithSlotsStrictRevertsWithExcessUpdateData()
+        public
+    {
+        // Create a price update with more price updates than requested price IDs
+        uint numPriceIds = 2;
+        uint numMessages = numPriceIds + 1; // One more than the number of price IDs
+
+        (
+            bytes32[] memory priceIds,
+            PriceFeedMessage[] memory messages
+        ) = generateRandomPriceMessages(numMessages);
+
+        // Only use a subset of the price IDs to trigger the strict check
+        bytes32[] memory requestedPriceIds = new bytes32[](numPriceIds);
+        for (uint i = 0; i < numPriceIds; i++) {
+            requestedPriceIds[i] = priceIds[i];
+        }
+
+        (
+            bytes[] memory updateData,
+            uint updateFee
+        ) = createBatchedUpdateDataFromMessages(messages);
+
+        // Should revert in strict mode
+        vm.expectRevert(PythErrors.InvalidArgument.selector);
+        pyth.parsePriceFeedUpdatesWithSlotsStrict{value: updateFee}(
+            updateData,
+            requestedPriceIds,
+            0,
+            MAX_UINT64
+        );
+    }
+
+    function testParsePriceFeedUpdatesWithSlotsStrictRevertsWithFewerUpdateData()
+        public
+    {
+        // Create a price update with fewer price updates than requested price IDs
+        uint numPriceIds = 3;
+        uint numMessages = numPriceIds - 1; // One less than the number of price IDs
+
+        (
+            bytes32[] memory priceIds,
+            PriceFeedMessage[] memory messages
+        ) = generateRandomPriceMessages(numMessages);
+
+        // Create a larger array of requested price IDs to trigger the strict check
+        bytes32[] memory requestedPriceIds = new bytes32[](numPriceIds);
+        for (uint i = 0; i < numMessages; i++) {
+            requestedPriceIds[i] = priceIds[i];
+        }
+        // Add an extra price ID that won't be in the updates
+        requestedPriceIds[numMessages] = bytes32(
+            uint256(keccak256(abi.encodePacked("extra_id")))
+        );
+
+        (
+            bytes[] memory updateData,
+            uint updateFee
+        ) = createBatchedUpdateDataFromMessages(messages);
+
+        // Should revert in strict mode because we have fewer updates than price IDs
+        vm.expectRevert(PythErrors.InvalidArgument.selector);
+        pyth.parsePriceFeedUpdatesWithSlotsStrict{value: updateFee}(
+            updateData,
+            requestedPriceIds,
+            0,
+            MAX_UINT64
+        );
+    }
+
     function testParsePriceFeedUpdatesRevertsIfUpdateSourceChainIsInvalid()
     function testParsePriceFeedUpdatesRevertsIfUpdateSourceChainIsInvalid()
         public
         public
     {
     {

+ 2 - 2
target_chains/ethereum/contracts/forge-test/utils/MockPriceFeedTestUtils.sol

@@ -170,7 +170,7 @@ abstract contract MockPriceFeedTestUtils is Test {
     }
     }
 
 
     // Helper function to mock Pyth response with slots
     // Helper function to mock Pyth response with slots
-    function mockParsePriceFeedUpdatesWithSlots(
+    function mockParsePriceFeedUpdatesWithSlotsStrict(
         address pyth,
         address pyth,
         PythStructs.PriceFeed[] memory priceFeeds,
         PythStructs.PriceFeed[] memory priceFeeds,
         uint64[] memory slots
         uint64[] memory slots
@@ -187,7 +187,7 @@ abstract contract MockPriceFeedTestUtils is Test {
             pyth,
             pyth,
             expectedFee,
             expectedFee,
             abi.encodeWithSelector(
             abi.encodeWithSelector(
-                IPyth.parsePriceFeedUpdatesWithSlots.selector
+                IPyth.parsePriceFeedUpdatesWithSlotsStrict.selector
             ),
             ),
             abi.encode(priceFeeds, slots)
             abi.encode(priceFeeds, slots)
         );
         );

+ 2 - 5
target_chains/ethereum/contracts/foundry.toml

@@ -7,10 +7,7 @@ src = 'contracts'
 # truffle doesn't try to build them
 # truffle doesn't try to build them
 test = 'forge-test'
 test = 'forge-test'
 
 
-libs = [
-    'lib',
-    'node_modules',
-]
+libs = ['lib', 'node_modules']
 
 
 # This doesn't work on ../../../node_modules alone because the sources from
 # This doesn't work on ../../../node_modules alone because the sources from
 # parent directories are also represented as absolute paths and won't doesn't
 # parent directories are also represented as absolute paths and won't doesn't
@@ -20,5 +17,5 @@ libs = [
 ignored_warnings_from = [
 ignored_warnings_from = [
     "lib",
     "lib",
     "node_modules/",
     "node_modules/",
-    "/home/runner/work/pyth-crosschain/pyth-crosschain/node_modules"
+    "/home/runner/work/pyth-crosschain/pyth-crosschain/node_modules",
 ]
 ]

+ 1 - 1
target_chains/ethereum/sdk/solidity/AbstractPyth.sol

@@ -136,7 +136,7 @@ abstract contract AbstractPyth is IPyth {
         override
         override
         returns (PythStructs.PriceFeed[] memory priceFeeds);
         returns (PythStructs.PriceFeed[] memory priceFeeds);
 
 
-    function parsePriceFeedUpdatesWithSlots(
+    function parsePriceFeedUpdatesWithSlotsStrict(
         bytes[] calldata updateData,
         bytes[] calldata updateData,
         bytes32[] calldata priceIds,
         bytes32[] calldata priceIds,
         uint64 minPublishTime,
         uint64 minPublishTime,

+ 2 - 3
target_chains/ethereum/sdk/solidity/IPyth.sol

@@ -172,15 +172,14 @@ interface IPyth is IPythEvents {
         uint64 maxPublishTime
         uint64 maxPublishTime
     ) external payable returns (PythStructs.PriceFeed[] memory priceFeeds);
     ) external payable returns (PythStructs.PriceFeed[] memory priceFeeds);
 
 
-    /// @dev Same as `parsePriceFeedUpdates`, but also returns the Pythnet slot
-    /// associated with each price update.
+    /// @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 updateData Array of price update data.
     /// @param priceIds Array of price ids.
     /// @param priceIds Array of price ids.
     /// @param minPublishTime minimum acceptable publishTime for the given `priceIds`.
     /// @param minPublishTime minimum acceptable publishTime for the given `priceIds`.
     /// @param maxPublishTime maximum 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 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).
     /// @return slots Array of the Pythnet slot corresponding to the given `priceIds` (with the same order).
-    function parsePriceFeedUpdatesWithSlots(
+    function parsePriceFeedUpdatesWithSlotsStrict(
         bytes[] calldata updateData,
         bytes[] calldata updateData,
         bytes32[] calldata priceIds,
         bytes32[] calldata priceIds,
         uint64 minPublishTime,
         uint64 minPublishTime,

+ 1 - 1
target_chains/ethereum/sdk/solidity/MockPyth.sol

@@ -170,7 +170,7 @@ contract MockPyth is AbstractPyth {
         );
         );
     }
     }
 
 
-    function parsePriceFeedUpdatesWithSlots(
+    function parsePriceFeedUpdatesWithSlotsStrict(
         bytes[] calldata updateData,
         bytes[] calldata updateData,
         bytes32[] calldata priceIds,
         bytes32[] calldata priceIds,
         uint64 minPublishTime,
         uint64 minPublishTime,

+ 4 - 1
target_chains/ethereum/sdk/solidity/PythAggregatorV3.sol

@@ -31,7 +31,10 @@ contract PythAggregatorV3 {
         pyth.updatePriceFeeds{value: fee}(priceUpdateData);
         pyth.updatePriceFeeds{value: fee}(priceUpdateData);
 
 
         // refund remaining eth
         // refund remaining eth
-        payable(msg.sender).call{value: address(this).balance}("");
+        // solhint-disable-next-line no-unused-vars
+        (bool success, ) = payable(msg.sender).call{
+            value: address(this).balance
+        }("");
     }
     }
 
 
     function decimals() public view virtual returns (uint8) {
     function decimals() public view virtual returns (uint8) {

+ 1 - 1
target_chains/ethereum/sdk/solidity/abis/AbstractPyth.json

@@ -608,7 +608,7 @@
         "type": "uint64"
         "type": "uint64"
       }
       }
     ],
     ],
-    "name": "parsePriceFeedUpdatesWithSlots",
+    "name": "parsePriceFeedUpdatesWithSlotsStrict",
     "outputs": [
     "outputs": [
       {
       {
         "components": [
         "components": [

+ 1 - 1
target_chains/ethereum/sdk/solidity/abis/IPyth.json

@@ -498,7 +498,7 @@
         "type": "uint64"
         "type": "uint64"
       }
       }
     ],
     ],
-    "name": "parsePriceFeedUpdatesWithSlots",
+    "name": "parsePriceFeedUpdatesWithSlotsStrict",
     "outputs": [
     "outputs": [
       {
       {
         "components": [
         "components": [

+ 1 - 1
target_chains/ethereum/sdk/solidity/abis/MockPyth.json

@@ -747,7 +747,7 @@
         "type": "uint64"
         "type": "uint64"
       }
       }
     ],
     ],
-    "name": "parsePriceFeedUpdatesWithSlots",
+    "name": "parsePriceFeedUpdatesWithSlotsStrict",
     "outputs": [
     "outputs": [
       {
       {
         "components": [
         "components": [