@@ -250,6 +250,8 @@ abstract contract Scheduler is IScheduler, SchedulerState {
250
250
bytes [] calldata updateData ,
251
251
bytes32 [] calldata priceIds
252
252
) external override {
253
+ uint256 startGas = gasleft ();
254
+
253
255
SubscriptionStatus storage status = _state.subscriptionStatuses[
254
256
subscriptionId
255
257
];
@@ -261,9 +263,12 @@ abstract contract Scheduler is IScheduler, SchedulerState {
261
263
revert InactiveSubscription ();
262
264
}
263
265
264
- // Verify price IDs match subscription
266
+ // Verify price IDs match subscription length
265
267
if (priceIds.length != params.priceIds.length ) {
266
- revert InvalidPriceIdsLength (priceIds[0 ], params.priceIds[0 ]);
268
+ revert InvalidPriceIdsLength (
269
+ priceIds.length ,
270
+ params.priceIds.length
271
+ );
267
272
}
268
273
269
274
// Keepers must provide priceIds in the exact same order as defined in the subscription
@@ -277,27 +282,27 @@ abstract contract Scheduler is IScheduler, SchedulerState {
277
282
IPyth pyth = IPyth (_state.pyth);
278
283
uint256 pythFee = pyth.getUpdateFee (updateData);
279
284
280
- // Check if subscription has enough balance
285
+ // If we don't have enough balance, revert
281
286
if (status.balanceInWei < pythFee) {
282
287
revert InsufficientBalance ();
283
288
}
284
289
285
290
// Parse the price feed updates with an acceptable timestamp range of [-1h, +10s] from now.
286
291
// We will validate the trigger conditions ourselves.
287
292
uint64 curTime = SafeCast.toUint64 (block .timestamp );
288
- uint64 maxPublishTime = curTime + FUTURE_TIMESTAMP_MAX_VALIDITY_PERIOD;
289
- uint64 minPublishTime = curTime > PAST_TIMESTAMP_MAX_VALIDITY_PERIOD
290
- ? curTime - PAST_TIMESTAMP_MAX_VALIDITY_PERIOD
291
- : 0 ;
292
293
(
293
294
PythStructs.PriceFeed[] memory priceFeeds ,
294
295
uint64 [] memory slots
295
296
) = pyth.parsePriceFeedUpdatesWithSlots {value: pythFee}(
296
297
updateData,
297
298
priceIds,
298
- minPublishTime,
299
- maxPublishTime
299
+ curTime > PAST_TIMESTAMP_MAX_VALIDITY_PERIOD
300
+ ? curTime - PAST_TIMESTAMP_MAX_VALIDITY_PERIOD
301
+ : 0 ,
302
+ curTime + FUTURE_TIMESTAMP_MAX_VALIDITY_PERIOD
300
303
);
304
+ status.balanceInWei -= pythFee;
305
+ status.totalSpent += pythFee;
301
306
302
307
// Verify all price feeds have the same Pythnet slot.
303
308
// All feeds in a subscription must be updated at the same time.
@@ -312,36 +317,21 @@ abstract contract Scheduler is IScheduler, SchedulerState {
312
317
// is more recent than latest stored update's. Reverts if not.
313
318
_validateShouldUpdatePrices (subscriptionId, params, status, priceFeeds);
314
319
315
- // Store the price updates, update status, and emit event
316
- _storePriceUpdatesAndStatus (
317
- subscriptionId,
318
- status,
319
- priceFeeds,
320
- pythFee
321
- );
322
- }
323
-
324
- /**
325
- * @notice Stores the price updates, updates subscription status, and emits event.
326
- */
327
- function _storePriceUpdatesAndStatus (
328
- uint256 subscriptionId ,
329
- SubscriptionStatus storage status ,
330
- PythStructs.PriceFeed[] memory priceFeeds ,
331
- uint256 pythFee
332
- ) internal {
333
- // Store the price updates
320
+ // Update status and store the updates
321
+ uint256 latestPublishTime = 0 ; // Use the most recent publish time from the validated feeds
334
322
for (uint8 i = 0 ; i < priceFeeds.length ; i++ ) {
335
- _state.priceUpdates[subscriptionId][ priceFeeds[i].id] = priceFeeds[
336
- i
337
- ];
323
+ if ( priceFeeds[i].price.publishTime > latestPublishTime) {
324
+ latestPublishTime = priceFeeds[i].price.publishTime;
325
+ }
338
326
}
339
- status.priceLastUpdatedAt = priceFeeds[ 0 ].price.publishTime ;
340
- status.balanceInWei -= pythFee ;
341
- status.totalUpdates += 1 ;
342
- status.totalSpent += pythFee ;
327
+ status.priceLastUpdatedAt = latestPublishTime ;
328
+ status.totalUpdates += priceFeeds. length ;
329
+
330
+ _storePriceUpdates (subscriptionId, priceFeeds) ;
343
331
344
- emit PricesUpdated (subscriptionId, priceFeeds[0 ].price.publishTime);
332
+ _processFeesAndPayKeeper (status, startGas, priceIds.length );
333
+
334
+ emit PricesUpdated (subscriptionId, latestPublishTime);
345
335
}
346
336
347
337
/**
@@ -737,4 +727,53 @@ abstract contract Scheduler is IScheduler, SchedulerState {
737
727
_state.activeSubscriptionIndex[subscriptionId] = 0 ;
738
728
}
739
729
}
730
+
731
+ /**
732
+ * @notice Internal function to store the parsed price feeds.
733
+ * @param subscriptionId The ID of the subscription.
734
+ * @param priceFeeds The array of price feeds to store.
735
+ */
736
+ function _storePriceUpdates (
737
+ uint256 subscriptionId ,
738
+ PythStructs.PriceFeed[] memory priceFeeds
739
+ ) internal {
740
+ for (uint8 i = 0 ; i < priceFeeds.length ; i++ ) {
741
+ _state.priceUpdates[subscriptionId][priceFeeds[i].id] = priceFeeds[
742
+ i
743
+ ];
744
+ }
745
+ }
746
+
747
+ /**
748
+ * @notice Internal function to calculate total fees, deduct from balance, and pay the keeper.
749
+ * @dev This function sends funds to `msg.sender`, so be sure that this is being called by a keeper.
750
+ * @dev Note that the Pyth fee is already paid in the parsePriceFeedUpdatesWithSlots call.
751
+ * @param status Storage reference to the subscription's status.
752
+ * @param startGas Gas remaining at the start of the parent function call.
753
+ * @param numPriceIds Number of price IDs being updated.
754
+ */
755
+ function _processFeesAndPayKeeper (
756
+ SubscriptionStatus storage status ,
757
+ uint256 startGas ,
758
+ uint256 numPriceIds
759
+ ) internal {
760
+ // Calculate fee components
761
+ uint256 gasCost = (startGas - gasleft () + GAS_OVERHEAD) * tx .gasprice ;
762
+ uint256 keeperSpecificFee = uint256 (_state.singleUpdateKeeperFeeInWei) *
763
+ numPriceIds;
764
+ uint256 totalKeeperFee = gasCost + keeperSpecificFee;
765
+
766
+ // Check balance
767
+ if (status.balanceInWei < totalKeeperFee) {
768
+ revert InsufficientBalance ();
769
+ }
770
+
771
+ // Pay keeper and update status if successful
772
+ (bool sent , ) = msg .sender .call {value: totalKeeperFee}("" );
773
+ if (! sent) {
774
+ revert KeeperPaymentFailed ();
775
+ }
776
+ status.balanceInWei -= totalKeeperFee;
777
+ status.totalSpent += totalKeeperFee;
778
+ }
740
779
}
0 commit comments