@@ -332,6 +332,118 @@ contract SchedulerTest is Test, SchedulerEvents, PulseSchedulerTestUtils {
332332 );
333333 }
334334
335+ // Helper function to reduce stack depth in testUpdateSubscriptionResetsPriceLastUpdatedAt
336+ function _setupSubscriptionAndFirstUpdate ()
337+ private
338+ returns (uint256 subscriptionId , uint64 publishTime )
339+ {
340+ // Setup subscription with heartbeat criteria
341+ uint32 heartbeatSeconds = 60 ; // 60 second heartbeat
342+ SchedulerState.UpdateCriteria memory criteria = SchedulerState
343+ .UpdateCriteria ({
344+ updateOnHeartbeat: true ,
345+ heartbeatSeconds: heartbeatSeconds,
346+ updateOnDeviation: false ,
347+ deviationThresholdBps: 0
348+ });
349+
350+ subscriptionId = addTestSubscriptionWithUpdateCriteria (
351+ scheduler,
352+ criteria,
353+ address (reader)
354+ );
355+ scheduler.addFunds {value: 1 ether }(subscriptionId);
356+
357+ // Update prices to set priceLastUpdatedAt to a non-zero value
358+ publishTime = SafeCast.toUint64 (block .timestamp );
359+ PythStructs.PriceFeed[] memory priceFeeds;
360+ uint64 [] memory slots;
361+ (priceFeeds, slots) = createMockPriceFeedsWithSlots (publishTime, 2 );
362+ mockParsePriceFeedUpdatesWithSlotsStrict (pyth, priceFeeds, slots);
363+ bytes [] memory updateData = createMockUpdateData (priceFeeds);
364+
365+ vm.prank (pusher);
366+ scheduler.updatePriceFeeds (subscriptionId, updateData);
367+
368+ return (subscriptionId, publishTime);
369+ }
370+
371+ function testUpdateSubscriptionResetsPriceLastUpdatedAt () public {
372+ // 1. Setup subscription and perform first update
373+ (
374+ uint256 subscriptionId ,
375+ uint64 publishTime1
376+ ) = _setupSubscriptionAndFirstUpdate ();
377+
378+ // Verify priceLastUpdatedAt is set
379+ (, SchedulerState.SubscriptionStatus memory status ) = scheduler
380+ .getSubscription (subscriptionId);
381+ assertEq (
382+ status.priceLastUpdatedAt,
383+ publishTime1,
384+ "priceLastUpdatedAt should be set to the first update timestamp "
385+ );
386+
387+ // 2. Update subscription to add price IDs
388+ (SchedulerState.SubscriptionParams memory currentParams , ) = scheduler
389+ .getSubscription (subscriptionId);
390+ bytes32 [] memory newPriceIds = createPriceIds (3 );
391+
392+ SchedulerState.SubscriptionParams memory newParams = currentParams;
393+ newParams.priceIds = newPriceIds;
394+
395+ // Update the subscription
396+ scheduler.updateSubscription (subscriptionId, newParams);
397+
398+ // 3. Verify priceLastUpdatedAt is reset to 0
399+ (, status) = scheduler.getSubscription (subscriptionId);
400+ assertEq (
401+ status.priceLastUpdatedAt,
402+ 0 ,
403+ "priceLastUpdatedAt should be reset to 0 after adding new price IDs "
404+ );
405+
406+ // 4. Verify immediate update is possible
407+ _verifyImmediateUpdatePossible (subscriptionId);
408+ }
409+
410+ function _verifyImmediateUpdatePossible (uint256 subscriptionId ) private {
411+ // Create new price feeds for the new price IDs
412+ uint64 publishTime2 = SafeCast.toUint64 (block .timestamp + 1 ); // Just 1 second later
413+ PythStructs.PriceFeed[] memory priceFeeds;
414+ uint64 [] memory slots;
415+ (priceFeeds, slots) = createMockPriceFeedsWithSlots (publishTime2, 3 ); // 3 feeds for new price IDs
416+ mockParsePriceFeedUpdatesWithSlotsStrict (pyth, priceFeeds, slots);
417+ bytes [] memory updateData = createMockUpdateData (priceFeeds);
418+
419+ // This should succeed even though we haven't waited for heartbeatSeconds
420+ // because priceLastUpdatedAt was reset to 0
421+ vm.prank (pusher);
422+ scheduler.updatePriceFeeds (subscriptionId, updateData);
423+
424+ // Verify the update was processed
425+ (, SchedulerState.SubscriptionStatus memory status ) = scheduler
426+ .getSubscription (subscriptionId);
427+ assertEq (
428+ status.priceLastUpdatedAt,
429+ publishTime2,
430+ "Second update should be processed with new timestamp "
431+ );
432+
433+ // Verify that normal heartbeat criteria apply again for subsequent updates
434+ uint64 publishTime3 = SafeCast.toUint64 (block .timestamp + 10 ); // Only 10 seconds later
435+ (priceFeeds, slots) = createMockPriceFeedsWithSlots (publishTime3, 3 );
436+ mockParsePriceFeedUpdatesWithSlotsStrict (pyth, priceFeeds, slots);
437+ updateData = createMockUpdateData (priceFeeds);
438+
439+ // This should fail because we haven't waited for heartbeatSeconds since the last update
440+ vm.expectRevert (
441+ abi.encodeWithSelector (UpdateConditionsNotMet.selector )
442+ );
443+ vm.prank (pusher);
444+ scheduler.updatePriceFeeds (subscriptionId, updateData);
445+ }
446+
335447 function testcreateSubscriptionWithInsufficientFundsReverts () public {
336448 uint8 numFeeds = 2 ;
337449 SchedulerState.SubscriptionParams
0 commit comments