@@ -23,35 +23,8 @@ abstract contract Scheduler is IScheduler, SchedulerState {
2323 function createSubscription (
2424 SubscriptionParams memory subscriptionParams
2525 ) 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);
5528
5629 // Calculate minimum balance required for this subscription
5730 uint256 minimumBalance = this .getMinimumBalance (
@@ -111,34 +84,8 @@ abstract contract Scheduler is IScheduler, SchedulerState {
11184 return ;
11285 }
11386
114- // Validate parameters for active or to-be-activated subscriptions
115- if (newParams.priceIds.length > MAX_PRICE_IDS_PER_SUBSCRIPTION) {
116- revert TooManyPriceIds (
117- newParams.priceIds.length ,
118- MAX_PRICE_IDS_PER_SUBSCRIPTION
119- );
120- }
121-
122- // Validate update criteria
123- if (
124- ! newParams.updateCriteria.updateOnHeartbeat &&
125- ! newParams.updateCriteria.updateOnDeviation
126- ) {
127- revert InvalidUpdateCriteria ();
128- }
129-
130- // If gas config is unset, set it to the default (100x multipliers)
131- if (
132- newParams.gasConfig.maxBaseFeeMultiplierCapPct == 0 ||
133- newParams.gasConfig.maxPriorityFeeMultiplierCapPct == 0
134- ) {
135- newParams
136- .gasConfig
137- .maxPriorityFeeMultiplierCapPct = DEFAULT_MAX_PRIORITY_FEE_MULTIPLIER_CAP_PCT;
138- newParams
139- .gasConfig
140- .maxBaseFeeMultiplierCapPct = DEFAULT_MAX_BASE_FEE_MULTIPLIER_CAP_PCT;
141- }
87+ // Validate the new parameters, including setting default gas config
88+ _validateAndPrepareSubscriptionParams (newParams);
14289
14390 // Handle activation/deactivation
14491 if (! wasActive && willBeActive) {
@@ -173,6 +120,83 @@ abstract contract Scheduler is IScheduler, SchedulerState {
173120 emit SubscriptionUpdated (subscriptionId);
174121 }
175122
123+ /**
124+ * @notice Validates subscription parameters and sets default gas config if needed.
125+ * @dev This function modifies the passed-in params struct in place for gas config defaults.
126+ * @param params The subscription parameters to validate and prepare.
127+ */
128+ function _validateAndPrepareSubscriptionParams (
129+ SubscriptionParams memory params
130+ ) internal pure {
131+ // No zero‐feed subscriptions
132+ if (params.priceIds.length == 0 ) {
133+ revert EmptyPriceIds ();
134+ }
135+
136+ // Price ID limits and uniqueness
137+ if (params.priceIds.length > MAX_PRICE_IDS_PER_SUBSCRIPTION) {
138+ revert TooManyPriceIds (
139+ params.priceIds.length ,
140+ MAX_PRICE_IDS_PER_SUBSCRIPTION
141+ );
142+ }
143+ for (uint i = 0 ; i < params.priceIds.length ; i++ ) {
144+ for (uint j = i + 1 ; j < params.priceIds.length ; j++ ) {
145+ if (params.priceIds[i] == params.priceIds[j]) {
146+ revert DuplicatePriceId (params.priceIds[i]);
147+ }
148+ }
149+ }
150+
151+ // Whitelist size limit and uniqueness
152+ if (params.readerWhitelist.length > MAX_READER_WHITELIST_SIZE) {
153+ revert TooManyWhitelistedReaders (
154+ params.readerWhitelist.length ,
155+ MAX_READER_WHITELIST_SIZE
156+ );
157+ }
158+ for (uint i = 0 ; i < params.readerWhitelist.length ; i++ ) {
159+ for (uint j = i + 1 ; j < params.readerWhitelist.length ; j++ ) {
160+ if (params.readerWhitelist[i] == params.readerWhitelist[j]) {
161+ revert DuplicateWhitelistAddress (params.readerWhitelist[i]);
162+ }
163+ }
164+ }
165+
166+ // Validate update criteria
167+ if (
168+ ! params.updateCriteria.updateOnHeartbeat &&
169+ ! params.updateCriteria.updateOnDeviation
170+ ) {
171+ revert InvalidUpdateCriteria ();
172+ }
173+ if (
174+ params.updateCriteria.updateOnHeartbeat &&
175+ params.updateCriteria.heartbeatSeconds == 0
176+ ) {
177+ revert InvalidUpdateCriteria ();
178+ }
179+ if (
180+ params.updateCriteria.updateOnDeviation &&
181+ params.updateCriteria.deviationThresholdBps == 0
182+ ) {
183+ revert InvalidUpdateCriteria ();
184+ }
185+
186+ // If gas config is unset, set it to the default (100x multipliers)
187+ if (
188+ params.gasConfig.maxBaseFeeMultiplierCapPct == 0 ||
189+ params.gasConfig.maxPriorityFeeMultiplierCapPct == 0
190+ ) {
191+ params
192+ .gasConfig
193+ .maxPriorityFeeMultiplierCapPct = DEFAULT_MAX_PRIORITY_FEE_MULTIPLIER_CAP_PCT;
194+ params
195+ .gasConfig
196+ .maxBaseFeeMultiplierCapPct = DEFAULT_MAX_BASE_FEE_MULTIPLIER_CAP_PCT;
197+ }
198+ }
199+
176200 /**
177201 * @notice Internal helper to clear stored PriceFeed data for price IDs removed from a subscription.
178202 * @param subscriptionId The ID of the subscription being updated.
@@ -243,10 +267,11 @@ abstract contract Scheduler is IScheduler, SchedulerState {
243267
244268 // Parse price feed updates with an expected timestamp range of [-10s, now]
245269 // We will validate the trigger conditions and timestamps ourselves
246- // using the returned PriceFeeds.
247270 uint64 curTime = SafeCast.toUint64 (block .timestamp );
248271 uint64 maxPublishTime = curTime + FUTURE_TIMESTAMP_MAX_VALIDITY_PERIOD;
249- uint64 minPublishTime = curTime - PAST_TIMESTAMP_MAX_VALIDITY_PERIOD;
272+ uint64 minPublishTime = curTime > PAST_TIMESTAMP_MAX_VALIDITY_PERIOD
273+ ? curTime - PAST_TIMESTAMP_MAX_VALIDITY_PERIOD
274+ : 0 ;
250275 PythStructs.PriceFeed[] memory priceFeeds;
251276 uint64 [] memory slots;
252277 (priceFeeds, slots) = pyth.parsePriceFeedUpdatesWithSlots {
@@ -721,8 +746,9 @@ abstract contract Scheduler is IScheduler, SchedulerState {
721746 function getMinimumBalance (
722747 uint8 numPriceFeeds
723748 ) external pure override returns (uint256 minimumBalanceInWei ) {
724- // Simple implementation - minimum balance is 0.01 ETH per price feed
725- return numPriceFeeds * 0.01 ether ;
749+ // Placeholder implementation
750+ // TODO: make this governable
751+ return uint256 (numPriceFeeds) * 0.01 ether ;
726752 }
727753
728754 // ACCESS CONTROL MODIFIERS
0 commit comments