@@ -23,35 +23,8 @@ abstract contract Scheduler is IScheduler, SchedulerState {
23
23
function createSubscription (
24
24
SubscriptionParams memory subscriptionParams
25
25
) external payable override returns (uint256 subscriptionId ) {
26
- if (
27
- subscriptionParams.priceIds.length > MAX_PRICE_IDS_PER_SUBSCRIPTION
28
- ) {
29
- revert TooManyPriceIds (
30
- subscriptionParams.priceIds.length ,
31
- MAX_PRICE_IDS_PER_SUBSCRIPTION
32
- );
33
- }
34
-
35
- // Validate update criteria
36
- if (
37
- ! subscriptionParams.updateCriteria.updateOnHeartbeat &&
38
- ! subscriptionParams.updateCriteria.updateOnDeviation
39
- ) {
40
- revert InvalidUpdateCriteria ();
41
- }
42
-
43
- // If gas config is unset, set it to the default (100x multipliers)
44
- if (
45
- subscriptionParams.gasConfig.maxBaseFeeMultiplierCapPct == 0 ||
46
- subscriptionParams.gasConfig.maxPriorityFeeMultiplierCapPct == 0
47
- ) {
48
- subscriptionParams
49
- .gasConfig
50
- .maxPriorityFeeMultiplierCapPct = DEFAULT_MAX_PRIORITY_FEE_MULTIPLIER_CAP_PCT;
51
- subscriptionParams
52
- .gasConfig
53
- .maxBaseFeeMultiplierCapPct = DEFAULT_MAX_BASE_FEE_MULTIPLIER_CAP_PCT;
54
- }
26
+ // Validate params and set default gas config
27
+ _validateAndPrepareSubscriptionParams (subscriptionParams);
55
28
56
29
// Calculate minimum balance required for this subscription
57
30
uint256 minimumBalance = this .getMinimumBalance (
@@ -133,34 +106,8 @@ abstract contract Scheduler is IScheduler, SchedulerState {
133
106
return ;
134
107
}
135
108
136
- // Validate parameters for active or to-be-activated subscriptions
137
- if (newParams.priceIds.length > MAX_PRICE_IDS_PER_SUBSCRIPTION) {
138
- revert TooManyPriceIds (
139
- newParams.priceIds.length ,
140
- MAX_PRICE_IDS_PER_SUBSCRIPTION
141
- );
142
- }
143
-
144
- // Validate update criteria
145
- if (
146
- ! newParams.updateCriteria.updateOnHeartbeat &&
147
- ! newParams.updateCriteria.updateOnDeviation
148
- ) {
149
- revert InvalidUpdateCriteria ();
150
- }
151
-
152
- // If gas config is unset, set it to the default (100x multipliers)
153
- if (
154
- newParams.gasConfig.maxBaseFeeMultiplierCapPct == 0 ||
155
- newParams.gasConfig.maxPriorityFeeMultiplierCapPct == 0
156
- ) {
157
- newParams
158
- .gasConfig
159
- .maxPriorityFeeMultiplierCapPct = DEFAULT_MAX_PRIORITY_FEE_MULTIPLIER_CAP_PCT;
160
- newParams
161
- .gasConfig
162
- .maxBaseFeeMultiplierCapPct = DEFAULT_MAX_BASE_FEE_MULTIPLIER_CAP_PCT;
163
- }
109
+ // Validate the new parameters, including setting default gas config
110
+ _validateAndPrepareSubscriptionParams (newParams);
164
111
165
112
// Handle activation/deactivation
166
113
if (! wasActive && willBeActive) {
@@ -195,6 +142,83 @@ abstract contract Scheduler is IScheduler, SchedulerState {
195
142
emit SubscriptionUpdated (subscriptionId);
196
143
}
197
144
145
+ /**
146
+ * @notice Validates subscription parameters and sets default gas config if needed.
147
+ * @dev This function modifies the passed-in params struct in place for gas config defaults.
148
+ * @param params The subscription parameters to validate and prepare.
149
+ */
150
+ function _validateAndPrepareSubscriptionParams (
151
+ SubscriptionParams memory params
152
+ ) internal pure {
153
+ // No zero‐feed subscriptions
154
+ if (params.priceIds.length == 0 ) {
155
+ revert EmptyPriceIds ();
156
+ }
157
+
158
+ // Price ID limits and uniqueness
159
+ if (params.priceIds.length > MAX_PRICE_IDS_PER_SUBSCRIPTION) {
160
+ revert TooManyPriceIds (
161
+ params.priceIds.length ,
162
+ MAX_PRICE_IDS_PER_SUBSCRIPTION
163
+ );
164
+ }
165
+ for (uint i = 0 ; i < params.priceIds.length ; i++ ) {
166
+ for (uint j = i + 1 ; j < params.priceIds.length ; j++ ) {
167
+ if (params.priceIds[i] == params.priceIds[j]) {
168
+ revert DuplicatePriceId (params.priceIds[i]);
169
+ }
170
+ }
171
+ }
172
+
173
+ // Whitelist size limit and uniqueness
174
+ if (params.readerWhitelist.length > MAX_READER_WHITELIST_SIZE) {
175
+ revert TooManyWhitelistedReaders (
176
+ params.readerWhitelist.length ,
177
+ MAX_READER_WHITELIST_SIZE
178
+ );
179
+ }
180
+ for (uint i = 0 ; i < params.readerWhitelist.length ; i++ ) {
181
+ for (uint j = i + 1 ; j < params.readerWhitelist.length ; j++ ) {
182
+ if (params.readerWhitelist[i] == params.readerWhitelist[j]) {
183
+ revert DuplicateWhitelistAddress (params.readerWhitelist[i]);
184
+ }
185
+ }
186
+ }
187
+
188
+ // Validate update criteria
189
+ if (
190
+ ! params.updateCriteria.updateOnHeartbeat &&
191
+ ! params.updateCriteria.updateOnDeviation
192
+ ) {
193
+ revert InvalidUpdateCriteria ();
194
+ }
195
+ if (
196
+ params.updateCriteria.updateOnHeartbeat &&
197
+ params.updateCriteria.heartbeatSeconds == 0
198
+ ) {
199
+ revert InvalidUpdateCriteria ();
200
+ }
201
+ if (
202
+ params.updateCriteria.updateOnDeviation &&
203
+ params.updateCriteria.deviationThresholdBps == 0
204
+ ) {
205
+ revert InvalidUpdateCriteria ();
206
+ }
207
+
208
+ // If gas config is unset, set it to the default (100x multipliers)
209
+ if (
210
+ params.gasConfig.maxBaseFeeMultiplierCapPct == 0 ||
211
+ params.gasConfig.maxPriorityFeeMultiplierCapPct == 0
212
+ ) {
213
+ params
214
+ .gasConfig
215
+ .maxPriorityFeeMultiplierCapPct = DEFAULT_MAX_PRIORITY_FEE_MULTIPLIER_CAP_PCT;
216
+ params
217
+ .gasConfig
218
+ .maxBaseFeeMultiplierCapPct = DEFAULT_MAX_BASE_FEE_MULTIPLIER_CAP_PCT;
219
+ }
220
+ }
221
+
198
222
/**
199
223
* @notice Internal helper to clear stored PriceFeed data for price IDs removed from a subscription.
200
224
* @param subscriptionId The ID of the subscription being updated.
@@ -265,10 +289,11 @@ abstract contract Scheduler is IScheduler, SchedulerState {
265
289
266
290
// Parse price feed updates with an expected timestamp range of [-10s, now]
267
291
// We will validate the trigger conditions and timestamps ourselves
268
- // using the returned PriceFeeds.
269
292
uint64 curTime = SafeCast.toUint64 (block .timestamp );
270
293
uint64 maxPublishTime = curTime + FUTURE_TIMESTAMP_MAX_VALIDITY_PERIOD;
271
- uint64 minPublishTime = curTime - PAST_TIMESTAMP_MAX_VALIDITY_PERIOD;
294
+ uint64 minPublishTime = curTime > PAST_TIMESTAMP_MAX_VALIDITY_PERIOD
295
+ ? curTime - PAST_TIMESTAMP_MAX_VALIDITY_PERIOD
296
+ : 0 ;
272
297
PythStructs.PriceFeed[] memory priceFeeds;
273
298
uint64 [] memory slots;
274
299
(priceFeeds, slots) = pyth.parsePriceFeedUpdatesWithSlots {
0 commit comments