@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.
- package/governance/ExecutorsRegistry.sol +710 -0
- package/governance/Governance.sol +969 -0
- package/governance/NeutralsRegistry.sol +1130 -0
- package/governance/ParameterRegistry.sol +381 -0
- package/interfaces/IExecutorsRegistry.sol +62 -0
- package/interfaces/IGovernance.sol +57 -0
- package/interfaces/INeutralsRegistry.sol +72 -0
- package/interfaces/IQParameters.sol +37 -0
- package/interfaces/ISLCCore.sol +59 -0
- package/interfaces/ISLCCoreFactory.sol +29 -0
- package/interfaces/IWrappedToken.sol +8 -0
- package/libs/Errors.sol +22 -0
- package/libs/NeutralsSelection.sol +78 -0
- package/mocks/Helper.sol +4 -0
- package/mocks/MockGovernance.sol +75 -0
- package/mocks/MockNeutralsRegistry.sol +126 -0
- package/mocks/SimpleContract.sol +14 -0
- package/mocks/WrappedToken.sol +24 -0
- package/package.json +27 -0
- package/slc-core/SLCCore.sol +119 -0
- package/slc-core/SLCCoreFactory.sol +215 -0
- package/token/Token.sol +20 -0
- package/utils/Globals.sol +7 -0
@@ -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
|
+
}
|