@web3dotorg/evm-slc-core-contracts 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,710 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.28;
3
+
4
+ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5
+ import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
6
+ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
7
+ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
8
+ import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
9
+ import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
10
+
11
+ import {Paginator} from "@solarity/solidity-lib/libs/arrays/Paginator.sol";
12
+ import {PriorityQueue} from "@solarity/solidity-lib/libs/data-structures/PriorityQueue.sol";
13
+ import {AValueDistributor} from "@solarity/solidity-lib/finance/staking/AValueDistributor.sol";
14
+
15
+ import {Errors} from "../libs/Errors.sol";
16
+
17
+ import {DAOSLC_PARAM_NAME} from "../utils/Globals.sol";
18
+
19
+ import {IGovernance} from "../interfaces/IGovernance.sol";
20
+ import {IQParameters} from "../interfaces/IQParameters.sol";
21
+ import {IExecutorsRegistry} from "../interfaces/IExecutorsRegistry.sol";
22
+
23
+ /**
24
+ * @title ExecutorsRegistry
25
+ */
26
+ contract ExecutorsRegistry is IExecutorsRegistry, ERC165, UUPSUpgradeable, AValueDistributor {
27
+ using SafeERC20 for IERC20;
28
+ using PriorityQueue for PriorityQueue.AddressQueue;
29
+
30
+ // keccak256(abi.encode(uint256(keccak256("evm-slc-core.storage.ExecutorsRegistry")) - 1)) & ~bytes32(uint256(0xff))
31
+ bytes32 private constant EXECUTORS_REGISTRY_STORAGE_LOCATION =
32
+ 0x84b429261479e61afa83fbf020317b3fd4401958ce5dd91d22b08cd00ea3ae00;
33
+
34
+ string private constant MAX_ACTIVE_EXECUTORS_PARAM_NAME = "slc.executors.maxActiveExecutors";
35
+ string private constant MINIMUM_STAKE_PARAM_NAME = "slc.executors.minimumStake";
36
+ string private constant WITHDRAWAL_PERIOD_PARAM_NAME = "slc.executors.withdrawalPeriod";
37
+ string private constant SLASHING_RECIPIENT_PARAM_NAME = "slc.executors.slashingRecipient";
38
+
39
+ struct ExecutorInfo {
40
+ uint256 stake;
41
+ bool isApproved;
42
+ bool isActive;
43
+ bool isStandby;
44
+ }
45
+
46
+ struct WithdrawalAnnouncement {
47
+ uint256 amount;
48
+ uint256 announcedAt;
49
+ uint256 availableAt;
50
+ }
51
+
52
+ struct ExecutorsRegistryStorage {
53
+ IGovernance governance;
54
+ IERC20 stakeToken;
55
+ IQParameters parametersRegistry;
56
+ uint256 rewardsToDistribute;
57
+ uint256 maxActiveExecutors;
58
+ PriorityQueue.AddressQueue activeExecutors;
59
+ PriorityQueue.AddressQueue standbyExecutors;
60
+ mapping(address executor => ExecutorInfo info) executors;
61
+ mapping(address executor => WithdrawalAnnouncement) withdrawalAnnouncements;
62
+ }
63
+
64
+ modifier onlyDAOSLC() {
65
+ _requireDAOSLC();
66
+ _;
67
+ }
68
+
69
+ modifier onlyGovernance() {
70
+ _requireGovernance();
71
+ _;
72
+ }
73
+
74
+ modifier syncedMaxActiveExecutors() {
75
+ _requiredSyncedMaxActiveExecutors();
76
+ _;
77
+ }
78
+
79
+ function __ExecutorsRegistry_init(
80
+ address governance_,
81
+ address[] calldata initialExecutors_
82
+ ) external initializer {
83
+ __UUPSUpgradeable_init();
84
+
85
+ if (governance_ == address(0)) revert Errors.InvalidGovernance();
86
+
87
+ ExecutorsRegistryStorage storage $ = _getExecutorsRegistryStorage();
88
+ $.governance = IGovernance(governance_);
89
+
90
+ address stakeToken_ = $.governance.getSystemToken();
91
+ address parametersRegistry_ = $.governance.getParametersRegistry();
92
+
93
+ if (stakeToken_ == address(0)) revert Errors.InvalidERC20Token();
94
+ if (parametersRegistry_ == address(0)) revert Errors.InvalidParametersRegistry();
95
+
96
+ $.stakeToken = IERC20(stakeToken_);
97
+ $.parametersRegistry = IQParameters(parametersRegistry_);
98
+ $.maxActiveExecutors = _getMaxActiveExecutors();
99
+
100
+ for (uint256 i = 0; i < initialExecutors_.length; ++i) {
101
+ address executor_ = initialExecutors_[i];
102
+ if (executor_ == address(0)) revert InvalidInitialExecutor(executor_);
103
+
104
+ $.executors[executor_].isApproved = true;
105
+
106
+ _addToActiveQueue(executor_);
107
+ }
108
+ }
109
+
110
+ /**
111
+ * @notice Stake tokens to become an executor
112
+ * @param amount_ Amount of tokens to stake
113
+ */
114
+ function stake(uint256 amount_) external syncedMaxActiveExecutors {
115
+ if (amount_ == 0) revert Errors.InvalidStakeAmount(amount_);
116
+
117
+ ExecutorsRegistryStorage storage $ = _getExecutorsRegistryStorage();
118
+ ExecutorInfo storage executorInfo = $.executors[msg.sender];
119
+
120
+ executorInfo.stake += amount_;
121
+
122
+ _manipulateExecutorState(msg.sender);
123
+
124
+ $.stakeToken.safeTransferFrom(msg.sender, address(this), amount_);
125
+
126
+ emit ExecutorStaked(msg.sender, amount_, executorInfo.stake);
127
+ }
128
+
129
+ /**
130
+ * @notice Announce withdrawal intention
131
+ * @param amount_ Amount of tokens to withdraw
132
+ * @dev The withdrawal period should be longer than the typical DAO slashing proposal
133
+ * execution time to ensure that executors cannot front-run slashing by announcing
134
+ * withdrawal. Note that announced withdrawals can still be slashed during the
135
+ * withdrawal period, as the slashing function accounts for withdrawal announcements.
136
+ */
137
+ function announceWithdrawal(uint256 amount_) public syncedMaxActiveExecutors {
138
+ if (amount_ == 0) revert Errors.InvalidStakeAmount(amount_);
139
+
140
+ address executor_ = msg.sender;
141
+ ExecutorsRegistryStorage storage $ = _getExecutorsRegistryStorage();
142
+ ExecutorInfo storage executorInfo = $.executors[executor_];
143
+ WithdrawalAnnouncement storage announcement = $.withdrawalAnnouncements[executor_];
144
+
145
+ if (executorInfo.stake == 0) revert ExecutorHasNoStake(executor_);
146
+ if (amount_ > executorInfo.stake)
147
+ revert Errors.InsufficientStakeAmount(executor_, amount_, executorInfo.stake);
148
+ if (announcement.announcedAt != 0) revert WithdrawalAlreadyAnnounced(executor_);
149
+
150
+ uint256 withdrawalPeriod_ = _getWithdrawalPeriod();
151
+ uint256 availableAt_ = block.timestamp + withdrawalPeriod_;
152
+
153
+ announcement.amount = amount_;
154
+ announcement.announcedAt = block.timestamp;
155
+ announcement.availableAt = availableAt_;
156
+
157
+ executorInfo.stake -= amount_;
158
+
159
+ _manipulateExecutorState(executor_);
160
+
161
+ emit WithdrawalAnnounced(executor_, amount_, availableAt_);
162
+ }
163
+
164
+ /**
165
+ * @notice Complete withdrawal after announcement period
166
+ */
167
+ function completeWithdrawal() external {
168
+ ExecutorsRegistryStorage storage $ = _getExecutorsRegistryStorage();
169
+ ExecutorInfo storage executorInfo = $.executors[msg.sender];
170
+ WithdrawalAnnouncement storage announcement = $.withdrawalAnnouncements[msg.sender];
171
+
172
+ if (announcement.announcedAt == 0) revert NoWithdrawalAnnouncement(msg.sender);
173
+
174
+ if (block.timestamp < announcement.availableAt) {
175
+ revert WithdrawalNotReady(msg.sender, announcement.availableAt);
176
+ }
177
+
178
+ uint256 amount_ = announcement.amount;
179
+
180
+ delete $.withdrawalAnnouncements[msg.sender];
181
+
182
+ $.stakeToken.safeTransfer(msg.sender, amount_);
183
+
184
+ emit ExecutorUnstaked(msg.sender, amount_, executorInfo.stake);
185
+ }
186
+
187
+ /**
188
+ * @notice Cancel withdrawal announcement
189
+ */
190
+ function cancelWithdrawal() public syncedMaxActiveExecutors {
191
+ ExecutorsRegistryStorage storage $ = _getExecutorsRegistryStorage();
192
+ ExecutorInfo storage executorInfo = $.executors[msg.sender];
193
+ WithdrawalAnnouncement storage announcement = $.withdrawalAnnouncements[msg.sender];
194
+
195
+ if (announcement.announcedAt == 0) revert NoWithdrawalAnnouncement(msg.sender);
196
+
197
+ uint256 amount_ = announcement.amount;
198
+
199
+ executorInfo.stake += amount_;
200
+
201
+ delete $.withdrawalAnnouncements[msg.sender];
202
+
203
+ _manipulateExecutorState(msg.sender);
204
+
205
+ emit WithdrawalCancelled(msg.sender, amount_);
206
+ }
207
+
208
+ /**
209
+ * @notice Update withdrawal announcement amount
210
+ * @param newAmount_ New withdrawal amount
211
+ */
212
+ function updateWithdrawalAnnouncement(uint256 newAmount_) external {
213
+ cancelWithdrawal();
214
+ announceWithdrawal(newAmount_);
215
+ }
216
+
217
+ /**
218
+ * @notice Approve an executor (only DAOSLC)
219
+ * @param executor_ Address of the executor to approve
220
+ */
221
+ function approveExecutor(address executor_) external onlyDAOSLC syncedMaxActiveExecutors {
222
+ ExecutorsRegistryStorage storage $ = _getExecutorsRegistryStorage();
223
+ ExecutorInfo storage executorInfo = $.executors[executor_];
224
+
225
+ if (executorInfo.isApproved) revert ExecutorAlreadyApproved(executor_);
226
+
227
+ executorInfo.isApproved = true;
228
+
229
+ _manipulateExecutorState(executor_);
230
+
231
+ emit ExecutorApproved(executor_);
232
+ }
233
+
234
+ /**
235
+ * @notice Disapprove an executor (only DAOSLC)
236
+ * @param executor_ Address of the executor to disapprove
237
+ */
238
+ function disapproveExecutor(address executor_) external onlyDAOSLC syncedMaxActiveExecutors {
239
+ ExecutorsRegistryStorage storage $ = _getExecutorsRegistryStorage();
240
+ ExecutorInfo storage executorInfo = $.executors[executor_];
241
+
242
+ if (!executorInfo.isApproved) revert ExecutorNotApproved(executor_);
243
+
244
+ executorInfo.isApproved = false;
245
+
246
+ _removeFromActiveQueue(executor_);
247
+ _removeFromStandby(executor_);
248
+ _tryActivateFromStandby();
249
+
250
+ emit ExecutorDisapproved(executor_);
251
+ }
252
+
253
+ /**
254
+ * @notice Slash an executor's stake (only DAOSLC)
255
+ * @param executor_ Address of the executor to slash
256
+ * @param amount_ Amount to slash from the executor's stake
257
+ * @dev This function accounts for withdrawal announcements to prevent front-running.
258
+ * If an executor has announced withdrawal, the total slashable amount includes
259
+ * both their current stake and the withdrawal announcement amount.
260
+ */
261
+ function slashExecutor(
262
+ address executor_,
263
+ uint256 amount_
264
+ ) external onlyDAOSLC syncedMaxActiveExecutors {
265
+ ExecutorsRegistryStorage storage $ = _getExecutorsRegistryStorage();
266
+ ExecutorInfo storage executorInfo = $.executors[executor_];
267
+ WithdrawalAnnouncement storage announcement = $.withdrawalAnnouncements[executor_];
268
+
269
+ uint256 totalSlashableAmount_ = executorInfo.stake;
270
+ if (announcement.announcedAt != 0) {
271
+ totalSlashableAmount_ += announcement.amount;
272
+ }
273
+
274
+ if (totalSlashableAmount_ == 0) revert ExecutorHasNoStake(executor_);
275
+ if (amount_ > totalSlashableAmount_)
276
+ revert Errors.InsufficientStakeAmount(executor_, amount_, executorInfo.stake);
277
+
278
+ address slashingRecipient_ = _getSlashingRecipient();
279
+
280
+ if (amount_ > executorInfo.stake && announcement.announcedAt != 0) {
281
+ executorInfo.stake += announcement.amount;
282
+ delete $.withdrawalAnnouncements[executor_];
283
+ }
284
+
285
+ executorInfo.stake -= amount_;
286
+
287
+ _manipulateExecutorState(executor_);
288
+
289
+ $.stakeToken.safeTransfer(slashingRecipient_, amount_);
290
+
291
+ emit ExecutorSlashed(executor_, amount_, slashingRecipient_);
292
+ }
293
+
294
+ /**
295
+ * @notice Distribute rewards to active executors (only Governance)
296
+ * @param amount_ Total amount to distribute
297
+ */
298
+ function distributeRewards(uint256 amount_) external onlyGovernance {
299
+ ExecutorsRegistryStorage storage $ = _getExecutorsRegistryStorage();
300
+
301
+ $.stakeToken.safeTransferFrom(msg.sender, address(this), amount_);
302
+
303
+ $.rewardsToDistribute = amount_;
304
+
305
+ _update(address(0));
306
+
307
+ $.rewardsToDistribute = 0;
308
+
309
+ emit RewardsDistributed(amount_);
310
+ }
311
+
312
+ /**
313
+ * @notice Claim rewards for the caller
314
+ */
315
+ function claimRewards() external {
316
+ uint256 rewards_ = _distributeAllValue(msg.sender);
317
+
318
+ _getExecutorsRegistryStorage().stakeToken.safeTransfer(msg.sender, rewards_);
319
+
320
+ emit RewardsClaimed(msg.sender, rewards_);
321
+ }
322
+
323
+ /**
324
+ * @notice Synchronize local maxActiveExecutors parameter with the one in the registry
325
+ */
326
+ function boundActiveExecutors() external {
327
+ ExecutorsRegistryStorage storage $ = _getExecutorsRegistryStorage();
328
+ uint256 remoteMaxActiveExecutors_ = _getMaxActiveExecutors();
329
+ if ($.maxActiveExecutors == remoteMaxActiveExecutors_) {
330
+ revert Errors.ParameterIsAlreadySynced(MAX_ACTIVE_EXECUTORS_PARAM_NAME);
331
+ }
332
+
333
+ $.maxActiveExecutors = remoteMaxActiveExecutors_;
334
+
335
+ _handleMaxActiveExecutorsResize();
336
+ }
337
+
338
+ function tryBecomeActive() external syncedMaxActiveExecutors {
339
+ _tryActivateExecutor(msg.sender);
340
+ }
341
+
342
+ /**
343
+ * @inheritdoc IExecutorsRegistry
344
+ */
345
+ function getExecutorsCount() external view override returns (uint256) {
346
+ return _getExecutorsRegistryStorage().activeExecutors.length();
347
+ }
348
+
349
+ /**
350
+ * @inheritdoc IExecutorsRegistry
351
+ */
352
+ function isExecutor(address candidate_) public view override returns (bool) {
353
+ return _getExecutorsRegistryStorage().executors[candidate_].isActive;
354
+ }
355
+
356
+ /**
357
+ * @notice Get executor information
358
+ * @param executor_ Address of the executor
359
+ * @return ExecutorInfo struct with all executor data
360
+ */
361
+ function getExecutorInfo(address executor_) external view returns (ExecutorInfo memory) {
362
+ return _getExecutorsRegistryStorage().executors[executor_];
363
+ }
364
+
365
+ /**
366
+ * @notice Get active executors list with pagination
367
+ * @param offset_ Starting index
368
+ * @param limit_ Maximum number of executors to return
369
+ * @return Array of active executor addresses
370
+ */
371
+ function getActiveExecutors(
372
+ uint256 offset_,
373
+ uint256 limit_
374
+ ) external view returns (address[] memory) {
375
+ ExecutorsRegistryStorage storage $ = _getExecutorsRegistryStorage();
376
+
377
+ uint256 to_ = Paginator.getTo($.activeExecutors.length(), offset_, limit_);
378
+
379
+ address[] memory result = new address[](to_ - offset_);
380
+
381
+ for (uint256 i = offset_; i < to_; ++i) {
382
+ result[i - offset_] = address(uint160(uint256(($.activeExecutors._queue._values[i]))));
383
+ }
384
+
385
+ return result;
386
+ }
387
+
388
+ /**
389
+ * @notice Get standby executors list with pagination
390
+ * @param offset_ Starting index
391
+ * @param limit_ Maximum number of executors to return
392
+ * @return Array of standby executor addresses
393
+ */
394
+ function getStandbyExecutors(
395
+ uint256 offset_,
396
+ uint256 limit_
397
+ ) external view returns (address[] memory) {
398
+ ExecutorsRegistryStorage storage $ = _getExecutorsRegistryStorage();
399
+
400
+ uint256 to_ = Paginator.getTo($.standbyExecutors.length(), offset_, limit_);
401
+
402
+ address[] memory result = new address[](to_ - offset_);
403
+
404
+ for (uint256 i = offset_; i < to_; ++i) {
405
+ result[i - offset_] = address(
406
+ uint160(uint256(($.standbyExecutors._queue._values[i])))
407
+ );
408
+ }
409
+
410
+ return result;
411
+ }
412
+
413
+ /**
414
+ * @notice Get withdrawal announcement information for an executor
415
+ * @param executor_ Address of the executor
416
+ * @return Withdrawal announcement struct
417
+ */
418
+ function getWithdrawalAnnouncement(
419
+ address executor_
420
+ ) external view returns (WithdrawalAnnouncement memory) {
421
+ return _getExecutorsRegistryStorage().withdrawalAnnouncements[executor_];
422
+ }
423
+
424
+ /**
425
+ * @notice Check if withdrawal is ready to be completed
426
+ * @param executor_ Address of the executor
427
+ * @return True if withdrawal can be completed
428
+ */
429
+ function isWithdrawalReady(address executor_) external view returns (bool) {
430
+ ExecutorsRegistryStorage storage $ = _getExecutorsRegistryStorage();
431
+ WithdrawalAnnouncement storage announcement = $.withdrawalAnnouncements[executor_];
432
+
433
+ if (announcement.announcedAt == 0) return false;
434
+
435
+ return block.timestamp >= announcement.availableAt;
436
+ }
437
+
438
+ function hasShares(address executor_) public view returns (bool) {
439
+ return userDistribution(executor_).shares == 1;
440
+ }
441
+
442
+ function supportsInterface(
443
+ bytes4 interfaceId_
444
+ ) public view override(ERC165, IERC165) returns (bool) {
445
+ return
446
+ interfaceId_ == type(IExecutorsRegistry).interfaceId ||
447
+ super.supportsInterface(interfaceId_);
448
+ }
449
+
450
+ function implementation() external view returns (address) {
451
+ return ERC1967Utils.getImplementation();
452
+ }
453
+
454
+ function _getValueToDistribute(uint256, uint256) internal view override returns (uint256) {
455
+ return _getExecutorsRegistryStorage().rewardsToDistribute;
456
+ }
457
+
458
+ function _tryActivateExecutor(address executor_) internal {
459
+ ExecutorsRegistryStorage storage $ = _getExecutorsRegistryStorage();
460
+ ExecutorInfo storage executorInfo = $.executors[executor_];
461
+
462
+ if (!executorInfo.isApproved) return;
463
+ if (executorInfo.stake < _getMinimumStake()) return;
464
+
465
+ if ($.activeExecutors.length() < $.maxActiveExecutors) {
466
+ _activateExecutor(executor_);
467
+ } else {
468
+ address lowestExecutor_ = $.activeExecutors.topValue();
469
+ uint256 lowestStake_ = $.executors[lowestExecutor_].stake;
470
+
471
+ if (executorInfo.stake > lowestStake_) {
472
+ _removeFromActiveQueue(lowestExecutor_);
473
+ _addToStandby(lowestExecutor_);
474
+
475
+ _activateExecutor(executor_);
476
+ } else {
477
+ _addToStandby(executor_);
478
+ }
479
+ }
480
+ }
481
+
482
+ // We need our topValue to be the one with the lowest stake
483
+ function _addToActiveQueue(address executor_) internal {
484
+ ExecutorsRegistryStorage storage $ = _getExecutorsRegistryStorage();
485
+ ExecutorInfo storage executorInfo = $.executors[executor_];
486
+
487
+ $.activeExecutors.add(executor_, type(uint256).max - executorInfo.stake);
488
+
489
+ executorInfo.isActive = true;
490
+
491
+ _updateExecutorShares(executor_);
492
+ }
493
+
494
+ function _addToStandby(address executor_) internal {
495
+ ExecutorsRegistryStorage storage $ = _getExecutorsRegistryStorage();
496
+ ExecutorInfo storage executorInfo = $.executors[executor_];
497
+
498
+ if (executorInfo.stake < _getMinimumStake()) return;
499
+
500
+ if (executorInfo.isStandby) {
501
+ _removeFromStandby(executor_);
502
+ }
503
+
504
+ $.standbyExecutors.add(executor_, executorInfo.stake);
505
+
506
+ executorInfo.isStandby = true;
507
+
508
+ emit ExecutorMovedToStandby(executor_);
509
+ }
510
+
511
+ function _removeFromStandby(address executor_) internal {
512
+ ExecutorsRegistryStorage storage $ = _getExecutorsRegistryStorage();
513
+
514
+ $.standbyExecutors.remove(executor_);
515
+
516
+ $.executors[executor_].isStandby = false;
517
+ }
518
+
519
+ function _removeFromActiveQueue(address executor_) internal {
520
+ ExecutorsRegistryStorage storage $ = _getExecutorsRegistryStorage();
521
+
522
+ $.activeExecutors.remove(executor_);
523
+
524
+ $.executors[executor_].isActive = false;
525
+
526
+ _updateExecutorShares(executor_);
527
+ }
528
+
529
+ function _tryActivateFromStandby() internal {
530
+ ExecutorsRegistryStorage storage $ = _getExecutorsRegistryStorage();
531
+
532
+ if ($.standbyExecutors.length() == 0) return;
533
+
534
+ // Get highest staked standby executor (top of max priority queue)
535
+ // Guaranteed to be non-zero address since we check for length above
536
+ _tryActivateExecutor($.standbyExecutors.topValue());
537
+ }
538
+
539
+ function _activateExecutor(address executor_) internal {
540
+ ExecutorsRegistryStorage storage $ = _getExecutorsRegistryStorage();
541
+
542
+ _removeFromStandby(executor_);
543
+ _addToActiveQueue(executor_);
544
+
545
+ emit ExecutorActivated(executor_, $.executors[executor_].stake);
546
+ }
547
+
548
+ /**
549
+ * @notice Handle changing of maxActiveExecutors parameter in Registry.
550
+ * In case of maxActiveExecutor increasing, do nothing, but if it is decreased,
551
+ * and size of the list of active executors is greater than
552
+ * the maxActiveExecutors parameter, then the list will be reduced.
553
+ * Executors, which was removed from active queue, are moved to standby list.
554
+ */
555
+ function _handleMaxActiveExecutorsResize() internal {
556
+ ExecutorsRegistryStorage storage $ = _getExecutorsRegistryStorage();
557
+ uint256 maxActiveExecutors_ = $.maxActiveExecutors;
558
+ uint256 activeExecutorsLength_ = $.activeExecutors.length();
559
+
560
+ if (maxActiveExecutors_ >= activeExecutorsLength_) return;
561
+
562
+ // SAFE: Previously checked, that activeExecutorsLength_ > maxActiveExecutors_
563
+ uint256 delta_ = activeExecutorsLength_ - maxActiveExecutors_;
564
+
565
+ while (delta_ > 0) {
566
+ address lowestExecutor_ = $.activeExecutors.topValue();
567
+
568
+ _removeFromActiveQueue(lowestExecutor_);
569
+ _addToStandby(lowestExecutor_);
570
+
571
+ --delta_;
572
+ }
573
+ }
574
+
575
+ // solhint-disable-next-line no-empty-blocks
576
+ function _authorizeUpgrade(address newImplementation_) internal override onlyDAOSLC {}
577
+
578
+ function _getMinimumStake() internal view returns (uint256) {
579
+ ExecutorsRegistryStorage storage $ = _getExecutorsRegistryStorage();
580
+
581
+ try $.parametersRegistry.getUint(MINIMUM_STAKE_PARAM_NAME) returns (
582
+ uint256 minimumStake_
583
+ ) {
584
+ return minimumStake_;
585
+ } catch {
586
+ revert Errors.ParameterIsNotSet(
587
+ address($.parametersRegistry),
588
+ MINIMUM_STAKE_PARAM_NAME
589
+ );
590
+ }
591
+ }
592
+
593
+ function _getMaxActiveExecutors() internal view returns (uint256) {
594
+ ExecutorsRegistryStorage storage $ = _getExecutorsRegistryStorage();
595
+
596
+ try $.parametersRegistry.getUint(MAX_ACTIVE_EXECUTORS_PARAM_NAME) returns (
597
+ uint256 maxActiveExecutors_
598
+ ) {
599
+ return maxActiveExecutors_;
600
+ } catch {
601
+ revert Errors.ParameterIsNotSet(
602
+ address($.parametersRegistry),
603
+ MAX_ACTIVE_EXECUTORS_PARAM_NAME
604
+ );
605
+ }
606
+ }
607
+
608
+ function _getDAOSLC() internal view returns (address) {
609
+ ExecutorsRegistryStorage storage $ = _getExecutorsRegistryStorage();
610
+
611
+ try $.parametersRegistry.getAddr(DAOSLC_PARAM_NAME) returns (address daoslc_) {
612
+ return daoslc_;
613
+ } catch {
614
+ revert Errors.ParameterIsNotSet(address($.parametersRegistry), DAOSLC_PARAM_NAME);
615
+ }
616
+ }
617
+
618
+ function _getWithdrawalPeriod() internal view returns (uint256) {
619
+ ExecutorsRegistryStorage storage $ = _getExecutorsRegistryStorage();
620
+
621
+ try $.parametersRegistry.getUint(WITHDRAWAL_PERIOD_PARAM_NAME) returns (
622
+ uint256 withdrawalPeriod_
623
+ ) {
624
+ return withdrawalPeriod_;
625
+ } catch {
626
+ revert Errors.ParameterIsNotSet(
627
+ address($.parametersRegistry),
628
+ WITHDRAWAL_PERIOD_PARAM_NAME
629
+ );
630
+ }
631
+ }
632
+
633
+ function _getSlashingRecipient() internal view returns (address) {
634
+ ExecutorsRegistryStorage storage $ = _getExecutorsRegistryStorage();
635
+
636
+ try $.parametersRegistry.getAddr(SLASHING_RECIPIENT_PARAM_NAME) returns (
637
+ address slashingRecipient_
638
+ ) {
639
+ return slashingRecipient_;
640
+ } catch {
641
+ revert Errors.ParameterIsNotSet(
642
+ address($.parametersRegistry),
643
+ SLASHING_RECIPIENT_PARAM_NAME
644
+ );
645
+ }
646
+ }
647
+
648
+ /**
649
+ * @notice Update executor shares based on eligibility (approved + above minimum stake)
650
+ * @dev Executors get equal shares (1 share each) if eligible, regardless of stake amount
651
+ * @param executor_ Address of the executor
652
+ */
653
+ function _updateExecutorShares(address executor_) private {
654
+ ExecutorsRegistryStorage storage $ = _getExecutorsRegistryStorage();
655
+ ExecutorInfo storage executorInfo = $.executors[executor_];
656
+
657
+ bool currentlyHasShares = hasShares(executor_);
658
+ bool isEligible = executorInfo.isApproved && isExecutor(executor_);
659
+
660
+ if (isEligible && !currentlyHasShares) {
661
+ _addShares(executor_, 1);
662
+ } else if (!isEligible && currentlyHasShares) {
663
+ _removeShares(executor_, 1);
664
+ }
665
+ }
666
+
667
+ function _requireGovernance() private view {
668
+ ExecutorsRegistryStorage storage $ = _getExecutorsRegistryStorage();
669
+ if (msg.sender != address($.governance)) {
670
+ revert Errors.SenderExpectedToBeGovernance(msg.sender);
671
+ }
672
+ }
673
+
674
+ function _manipulateExecutorState(address executor_) private {
675
+ if (isExecutor(executor_)) {
676
+ _removeFromActiveQueue(executor_);
677
+ } else {
678
+ _removeFromStandby(executor_);
679
+ }
680
+
681
+ _addToStandby(executor_);
682
+ _tryActivateFromStandby();
683
+
684
+ _updateExecutorShares(executor_);
685
+ }
686
+
687
+ function _requireDAOSLC() private view {
688
+ if (msg.sender != _getDAOSLC()) {
689
+ revert Errors.SenderExpectedToBeDAOSLC(msg.sender);
690
+ }
691
+ }
692
+
693
+ function _requiredSyncedMaxActiveExecutors() private view {
694
+ ExecutorsRegistryStorage storage $ = _getExecutorsRegistryStorage();
695
+ if ($.maxActiveExecutors != _getMaxActiveExecutors()) {
696
+ revert Errors.ParameterIsNotSynced(MAX_ACTIVE_EXECUTORS_PARAM_NAME);
697
+ }
698
+ }
699
+
700
+ function _getExecutorsRegistryStorage()
701
+ private
702
+ pure
703
+ returns (ExecutorsRegistryStorage storage $)
704
+ {
705
+ // solhint-disable-next-line no-inline-assembly
706
+ assembly ("memory-safe") {
707
+ $.slot := EXECUTORS_REGISTRY_STORAGE_LOCATION
708
+ }
709
+ }
710
+ }