@@ -332,6 +332,118 @@ contract SchedulerTest is Test, SchedulerEvents, PulseSchedulerTestUtils {
332
332
);
333
333
}
334
334
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
+
335
447
function testcreateSubscriptionWithInsufficientFundsReverts () public {
336
448
uint8 numFeeds = 2 ;
337
449
SchedulerState.SubscriptionParams
0 commit comments