@@ -13,17 +13,30 @@ abstract contract Pulse is IPulse, PulseState {
13
13
address admin ,
14
14
uint128 pythFeeInWei ,
15
15
address pythAddress ,
16
- bool prefillRequestStorage
16
+ address defaultProvider ,
17
+ bool prefillRequestStorage ,
18
+ uint256 exclusivityPeriodSeconds
17
19
) internal {
18
20
require (admin != address (0 ), "admin is zero address " );
19
21
require (pythAddress != address (0 ), "pyth is zero address " );
22
+ require (
23
+ defaultProvider != address (0 ),
24
+ "defaultProvider is zero address "
25
+ );
20
26
21
27
_state.admin = admin;
22
28
_state.accruedFeesInWei = 0 ;
23
29
_state.pythFeeInWei = pythFeeInWei;
24
30
_state.pyth = pythAddress;
25
31
_state.currentSequenceNumber = 1 ;
26
32
33
+ // Two-step initialization process:
34
+ // 1. Set the default provider address here
35
+ // 2. Provider must call registerProvider() in a separate transaction to set their fee
36
+ // This ensures the provider maintains control over their own fee settings
37
+ _state.defaultProvider = defaultProvider;
38
+ _state.exclusivityPeriodSeconds = exclusivityPeriodSeconds;
39
+
27
40
if (prefillRequestStorage) {
28
41
for (uint8 i = 0 ; i < NUM_REQUESTS; i++ ) {
29
42
Request storage req = _state.requests[i];
@@ -45,6 +58,12 @@ abstract contract Pulse is IPulse, PulseState {
45
58
bytes32 [] calldata priceIds ,
46
59
uint256 callbackGasLimit
47
60
) external payable override returns (uint64 requestSequenceNumber ) {
61
+ address provider = _state.defaultProvider;
62
+ require (
63
+ _state.providers[provider].isRegistered,
64
+ "Provider not registered "
65
+ );
66
+
48
67
// NOTE: The 60-second future limit on publishTime prevents a DoS vector where
49
68
// attackers could submit many low-fee requests for far-future updates when gas prices
50
69
// are low, forcing executors to fulfill them later when gas prices might be much higher.
@@ -65,13 +84,17 @@ abstract contract Pulse is IPulse, PulseState {
65
84
req.callbackGasLimit = callbackGasLimit;
66
85
req.requester = msg .sender ;
67
86
req.numPriceIds = uint8 (priceIds.length );
87
+ req.provider = provider;
68
88
69
89
// Copy price IDs to storage
70
90
for (uint8 i = 0 ; i < priceIds.length ; i++ ) {
71
91
req.priceIds[i] = priceIds[i];
72
92
}
73
93
74
- _state.accruedFeesInWei += SafeCast.toUint128 (msg .value );
94
+ _state.providers[provider].accruedFeesInWei += SafeCast.toUint128 (
95
+ msg .value - _state.pythFeeInWei
96
+ );
97
+ _state.accruedFeesInWei += _state.pythFeeInWei;
75
98
76
99
emit PriceUpdateRequested (req, priceIds);
77
100
}
@@ -83,6 +106,16 @@ abstract contract Pulse is IPulse, PulseState {
83
106
) external payable override {
84
107
Request storage req = findActiveRequest (sequenceNumber);
85
108
109
+ // Check provider exclusivity using configurable period
110
+ if (
111
+ block .timestamp < req.publishTime + _state.exclusivityPeriodSeconds
112
+ ) {
113
+ require (
114
+ msg .sender == req.provider,
115
+ "Only assigned provider during exclusivity period "
116
+ );
117
+ }
118
+
86
119
// Verify priceIds match
87
120
require (
88
121
priceIds.length == req.numPriceIds,
@@ -105,19 +138,10 @@ abstract contract Pulse is IPulse, PulseState {
105
138
106
139
clearRequest (sequenceNumber);
107
140
108
- // Check if enough gas remains for callback + events/cleanup
109
- // We need extra gas beyond callbackGasLimit for:
110
- // 1. Emitting success/failure events
111
- // 2. Error handling in catch blocks
112
- // 3. State cleanup operations
113
- if (gasleft () < (req.callbackGasLimit * 3 ) / 2 ) {
114
- revert InsufficientGas ();
115
- }
116
-
117
141
try
118
142
IPulseConsumer (req.requester).pulseCallback {
119
143
gas: req.callbackGasLimit
120
- }(sequenceNumber, msg . sender , priceFeeds)
144
+ }(sequenceNumber, priceFeeds)
121
145
{
122
146
// Callback succeeded
123
147
emitPriceUpdate (sequenceNumber, priceIds, priceFeeds);
@@ -173,9 +197,12 @@ abstract contract Pulse is IPulse, PulseState {
173
197
function getFee (
174
198
uint256 callbackGasLimit
175
199
) public view override returns (uint128 feeAmount ) {
176
- uint128 baseFee = _state.pythFeeInWei;
177
- uint256 gasFee = callbackGasLimit * tx .gasprice ;
178
- feeAmount = baseFee + SafeCast.toUint128 (gasFee);
200
+ uint128 baseFee = _state.pythFeeInWei; // Fixed fee to Pyth
201
+ uint128 providerFeeInWei = _state
202
+ .providers[_state.defaultProvider]
203
+ .feeInWei; // Provider's per-gas rate
204
+ uint256 gasFee = callbackGasLimit * providerFeeInWei; // Total provider fee based on gas
205
+ feeAmount = baseFee + SafeCast.toUint128 (gasFee); // Total fee user needs to pay
179
206
}
180
207
181
208
function getPythFeeInWei ()
@@ -271,21 +298,89 @@ abstract contract Pulse is IPulse, PulseState {
271
298
}
272
299
273
300
function setFeeManager (address manager ) external override {
274
- require (msg .sender == _state.admin, "Only admin can set fee manager " );
275
- address oldFeeManager = _state.feeManager;
276
- _state.feeManager = manager;
277
- emit FeeManagerUpdated (_state.admin, oldFeeManager, manager);
301
+ require (
302
+ _state.providers[msg .sender ].isRegistered,
303
+ "Provider not registered "
304
+ );
305
+ address oldFeeManager = _state.providers[msg .sender ].feeManager;
306
+ _state.providers[msg .sender ].feeManager = manager;
307
+ emit FeeManagerUpdated (msg .sender , oldFeeManager, manager);
278
308
}
279
309
280
- function withdrawAsFeeManager (uint128 amount ) external override {
281
- require (msg .sender == _state.feeManager, "Only fee manager " );
282
- require (_state.accruedFeesInWei >= amount, "Insufficient balance " );
310
+ function withdrawAsFeeManager (
311
+ address provider ,
312
+ uint128 amount
313
+ ) external override {
314
+ require (
315
+ msg .sender == _state.providers[provider].feeManager,
316
+ "Only fee manager "
317
+ );
318
+ require (
319
+ _state.providers[provider].accruedFeesInWei >= amount,
320
+ "Insufficient balance "
321
+ );
283
322
284
- _state.accruedFeesInWei -= amount;
323
+ _state.providers[provider]. accruedFeesInWei -= amount;
285
324
286
325
(bool sent , ) = msg .sender .call {value: amount}("" );
287
326
require (sent, "Failed to send fees " );
288
327
289
328
emit FeesWithdrawn (msg .sender , amount);
290
329
}
330
+
331
+ function registerProvider (uint128 feeInWei ) external override {
332
+ ProviderInfo storage provider = _state.providers[msg .sender ];
333
+ require (! provider.isRegistered, "Provider already registered " );
334
+ provider.feeInWei = feeInWei;
335
+ provider.isRegistered = true ;
336
+ emit ProviderRegistered (msg .sender , feeInWei);
337
+ }
338
+
339
+ function setProviderFee (uint128 newFeeInWei ) external override {
340
+ require (
341
+ _state.providers[msg .sender ].isRegistered,
342
+ "Provider not registered "
343
+ );
344
+ uint128 oldFee = _state.providers[msg .sender ].feeInWei;
345
+ _state.providers[msg .sender ].feeInWei = newFeeInWei;
346
+ emit ProviderFeeUpdated (msg .sender , oldFee, newFeeInWei);
347
+ }
348
+
349
+ function getProviderInfo (
350
+ address provider
351
+ ) external view override returns (ProviderInfo memory ) {
352
+ return _state.providers[provider];
353
+ }
354
+
355
+ function getDefaultProvider () external view override returns (address ) {
356
+ return _state.defaultProvider;
357
+ }
358
+
359
+ function setDefaultProvider (address provider ) external override {
360
+ require (
361
+ msg .sender == _state.admin,
362
+ "Only admin can set default provider "
363
+ );
364
+ require (
365
+ _state.providers[provider].isRegistered,
366
+ "Provider not registered "
367
+ );
368
+ address oldProvider = _state.defaultProvider;
369
+ _state.defaultProvider = provider;
370
+ emit DefaultProviderUpdated (oldProvider, provider);
371
+ }
372
+
373
+ function setExclusivityPeriod (uint256 periodSeconds ) external override {
374
+ require (
375
+ msg .sender == _state.admin,
376
+ "Only admin can set exclusivity period "
377
+ );
378
+ uint256 oldPeriod = _state.exclusivityPeriodSeconds;
379
+ _state.exclusivityPeriodSeconds = periodSeconds;
380
+ emit ExclusivityPeriodUpdated (oldPeriod, periodSeconds);
381
+ }
382
+
383
+ function getExclusivityPeriod () external view override returns (uint256 ) {
384
+ return _state.exclusivityPeriodSeconds;
385
+ }
291
386
}
0 commit comments