@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,1130 @@
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
2
|
+
pragma solidity ^0.8.28;
|
3
|
+
|
4
|
+
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
|
5
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
6
|
+
import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
|
7
|
+
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
|
8
|
+
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
9
|
+
import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
|
10
|
+
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
|
11
|
+
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
|
12
|
+
|
13
|
+
import {PRECISION} from "@solarity/solidity-lib/utils/Globals.sol";
|
14
|
+
import {Paginator} from "@solarity/solidity-lib/libs/arrays/Paginator.sol";
|
15
|
+
import {PriorityQueue} from "@solarity/solidity-lib/libs/data-structures/PriorityQueue.sol";
|
16
|
+
|
17
|
+
import {Errors} from "../libs/Errors.sol";
|
18
|
+
|
19
|
+
import {DAOSLC_PARAM_NAME, TREASURY_ADDRESS_PARAM_NAME, MAX_BASIS_POINTS} from "../utils/Globals.sol";
|
20
|
+
|
21
|
+
import {IGovernance} from "../interfaces/IGovernance.sol";
|
22
|
+
import {IQParameters} from "../interfaces/IQParameters.sol";
|
23
|
+
import {NeutralsSelection} from "../libs/NeutralsSelection.sol";
|
24
|
+
import {INeutralsRegistry} from "../interfaces/INeutralsRegistry.sol";
|
25
|
+
|
26
|
+
/**
|
27
|
+
* @title NeutralsRegistry
|
28
|
+
* @notice This contract manages the registration, staking, delegation, reward distribution, and slashing of neutrals in the SLC system.
|
29
|
+
* Neutrals can stake tokens to participate, receive delegations, and earn rewards. The contract supports withdrawal announcements
|
30
|
+
* and integrates with governance and parameter registries. Only authorized roles (DAOSLC, Governance)
|
31
|
+
* can perform certain sensitive operations such as slashing and reward distribution.
|
32
|
+
*/
|
33
|
+
contract NeutralsRegistry is INeutralsRegistry, ERC165, UUPSUpgradeable {
|
34
|
+
using SafeERC20 for IERC20;
|
35
|
+
using EnumerableSet for EnumerableSet.AddressSet;
|
36
|
+
using PriorityQueue for PriorityQueue.AddressQueue;
|
37
|
+
|
38
|
+
// keccak256(abi.encode(uint256(keccak256("evm-slc-core.storage.NeutralsRegistry")) - 1)) & ~bytes32(uint256(0xff))
|
39
|
+
bytes32 private constant NEUTRALS_REGISTRY_STORAGE_LOCATION =
|
40
|
+
0x02eec0af57616a1cb2ef3538f733bfc9b20f30a56aced6911a05ed5520d14900;
|
41
|
+
|
42
|
+
string private constant MAX_ACTIVE_NEUTRALS_PARAM_NAME = "slc.neutrals.maxActiveNeutrals";
|
43
|
+
|
44
|
+
// solhint-disable-next-line gas-small-strings
|
45
|
+
string private constant MAX_DELEGATION_AMPLIFICATION_PARAM_NAME =
|
46
|
+
"slc.neutrals.maxDelegationAmplification";
|
47
|
+
|
48
|
+
string private constant MINIMUM_STAKE_PARAM_NAME = "slc.neutrals.minimumStake";
|
49
|
+
|
50
|
+
// solhint-disable-next-line gas-small-strings
|
51
|
+
string private constant NEUTRALS_WITHDRAWAL_PERIOD_PARAM_NAME =
|
52
|
+
"slc.neutrals.neutralsWithdrawalPeriod";
|
53
|
+
|
54
|
+
// solhint-disable-next-line gas-small-strings
|
55
|
+
string private constant DELEGATORS_WITHDRAWAL_PERIOD_PARAM_NAME =
|
56
|
+
"slc.neutrals.delegatorsWithdrawalPeriod";
|
57
|
+
|
58
|
+
string private constant NEUTRALS_SLASHING_RECIPIENT_PARAM_NAME =
|
59
|
+
"slc.neutrals.slashingRecipient";
|
60
|
+
|
61
|
+
struct DelegatorRewardPool {
|
62
|
+
uint256 cumulativeSum;
|
63
|
+
}
|
64
|
+
|
65
|
+
struct Delegation {
|
66
|
+
uint256 delegationAmount;
|
67
|
+
WithdrawalAnnouncement withdrawal;
|
68
|
+
uint256 owedValue;
|
69
|
+
uint256 cumulativeSum;
|
70
|
+
}
|
71
|
+
|
72
|
+
struct NeutralsInfo {
|
73
|
+
uint256 selfStake;
|
74
|
+
uint256 weight;
|
75
|
+
uint256 delegatedStake;
|
76
|
+
uint256 rewards;
|
77
|
+
uint256 delegationShare;
|
78
|
+
string externalLink;
|
79
|
+
bool isActive;
|
80
|
+
bool isStandby;
|
81
|
+
}
|
82
|
+
|
83
|
+
struct WithdrawalAnnouncement {
|
84
|
+
uint256 amount;
|
85
|
+
uint256 announcedAt;
|
86
|
+
uint256 availableAt;
|
87
|
+
}
|
88
|
+
|
89
|
+
struct NeutralsRegistryStorage {
|
90
|
+
IGovernance governance;
|
91
|
+
IERC20 stakeToken;
|
92
|
+
IQParameters parametersRegistry;
|
93
|
+
uint256 rewardsToDistribute;
|
94
|
+
PriorityQueue.AddressQueue activeNeutrals;
|
95
|
+
PriorityQueue.AddressQueue standbyNeutrals;
|
96
|
+
uint256 maxActiveNeutrals;
|
97
|
+
mapping(address neutral => NeutralsInfo info) neutrals;
|
98
|
+
mapping(address neutral => WithdrawalAnnouncement) neutralsWithdrawalAnnouncements;
|
99
|
+
mapping(address neutral => DelegatorRewardPool) delegatorsRewardPools;
|
100
|
+
mapping(address delegator => mapping(address neutral => Delegation)) delegations;
|
101
|
+
mapping(address neutral => EnumerableSet.AddressSet delegators) delegatorsPerNeutral;
|
102
|
+
mapping(address delegator => EnumerableSet.AddressSet delegatedNeutrals) neutralsPerDelegator;
|
103
|
+
}
|
104
|
+
|
105
|
+
modifier onlyDAOSLC() {
|
106
|
+
_requireDAOSLC();
|
107
|
+
_;
|
108
|
+
}
|
109
|
+
|
110
|
+
modifier onlyGovernance() {
|
111
|
+
_requireGovernance();
|
112
|
+
_;
|
113
|
+
}
|
114
|
+
|
115
|
+
modifier syncedMaxActiveNeutrals() {
|
116
|
+
_requiredSyncedMaxActiveNeutrals();
|
117
|
+
_;
|
118
|
+
}
|
119
|
+
|
120
|
+
function __NeutralsRegistry_init(address governance_) external initializer {
|
121
|
+
__UUPSUpgradeable_init();
|
122
|
+
|
123
|
+
if (governance_ == address(0)) revert Errors.InvalidGovernance();
|
124
|
+
|
125
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
126
|
+
$.governance = IGovernance(governance_);
|
127
|
+
|
128
|
+
address stakeToken_ = $.governance.getSystemToken();
|
129
|
+
address parametersRegistry_ = $.governance.getParametersRegistry();
|
130
|
+
|
131
|
+
if (stakeToken_ == address(0)) revert Errors.InvalidERC20Token();
|
132
|
+
if (parametersRegistry_ == address(0)) revert Errors.InvalidParametersRegistry();
|
133
|
+
|
134
|
+
$.stakeToken = IERC20(stakeToken_);
|
135
|
+
$.parametersRegistry = IQParameters(parametersRegistry_);
|
136
|
+
$.maxActiveNeutrals = _getMaxActiveNeutrals();
|
137
|
+
}
|
138
|
+
|
139
|
+
/**
|
140
|
+
* @notice Stake tokens to become a neutral
|
141
|
+
* @param amount_ Amount of tokens to stake
|
142
|
+
*/
|
143
|
+
function addNeutralsStake(uint256 amount_) external syncedMaxActiveNeutrals {
|
144
|
+
if (amount_ == 0) revert Errors.InvalidStakeAmount(amount_);
|
145
|
+
|
146
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
147
|
+
NeutralsInfo storage neutralInfo_ = $.neutrals[msg.sender];
|
148
|
+
|
149
|
+
neutralInfo_.selfStake += amount_;
|
150
|
+
|
151
|
+
_manipulateNeutralState(msg.sender);
|
152
|
+
|
153
|
+
$.stakeToken.safeTransferFrom(msg.sender, address(this), amount_);
|
154
|
+
|
155
|
+
emit NeutralStaked(msg.sender, amount_, neutralInfo_.selfStake);
|
156
|
+
}
|
157
|
+
|
158
|
+
/**
|
159
|
+
* @notice Announce intention to withdraw a portion of self-stake
|
160
|
+
* @param amount_ Amount of tokens to withdraw
|
161
|
+
*/
|
162
|
+
function announceNeutralsWithdrawal(uint256 amount_) public syncedMaxActiveNeutrals {
|
163
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
164
|
+
|
165
|
+
NeutralsInfo storage neutralInfo_ = $.neutrals[msg.sender];
|
166
|
+
WithdrawalAnnouncement storage announcement_ = $.neutralsWithdrawalAnnouncements[
|
167
|
+
msg.sender
|
168
|
+
];
|
169
|
+
|
170
|
+
if (neutralInfo_.selfStake == 0) revert NeutralHasNoStake(msg.sender);
|
171
|
+
if (amount_ > neutralInfo_.selfStake)
|
172
|
+
revert Errors.InsufficientStakeAmount(msg.sender, amount_, neutralInfo_.selfStake);
|
173
|
+
if (announcement_.announcedAt != 0) revert WithdrawalAlreadyAnnounced(msg.sender);
|
174
|
+
|
175
|
+
uint256 withdrawalPeriod_ = _getNeutralsWithdrawalPeriod();
|
176
|
+
uint256 availableAt_ = block.timestamp + withdrawalPeriod_;
|
177
|
+
|
178
|
+
announcement_.amount = amount_;
|
179
|
+
announcement_.announcedAt = block.timestamp;
|
180
|
+
announcement_.availableAt = availableAt_;
|
181
|
+
|
182
|
+
neutralInfo_.selfStake -= amount_;
|
183
|
+
|
184
|
+
_manipulateNeutralState(msg.sender);
|
185
|
+
|
186
|
+
emit WithdrawalAnnounced(msg.sender, amount_, availableAt_);
|
187
|
+
}
|
188
|
+
|
189
|
+
/**
|
190
|
+
* @notice Cancel a previously announced withdrawal
|
191
|
+
*/
|
192
|
+
function cancelNeutralsWithdrawal() public syncedMaxActiveNeutrals {
|
193
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
194
|
+
NeutralsInfo storage neutralInfo = $.neutrals[msg.sender];
|
195
|
+
WithdrawalAnnouncement storage announcement = $.neutralsWithdrawalAnnouncements[
|
196
|
+
msg.sender
|
197
|
+
];
|
198
|
+
|
199
|
+
if (announcement.announcedAt == 0) revert NoWithdrawalAnnouncement(msg.sender);
|
200
|
+
|
201
|
+
uint256 amount_ = announcement.amount;
|
202
|
+
|
203
|
+
neutralInfo.selfStake += amount_;
|
204
|
+
|
205
|
+
delete $.neutralsWithdrawalAnnouncements[msg.sender];
|
206
|
+
|
207
|
+
_manipulateNeutralState(msg.sender);
|
208
|
+
|
209
|
+
emit WithdrawalCancelled(msg.sender, amount_);
|
210
|
+
}
|
211
|
+
|
212
|
+
/**
|
213
|
+
* @notice Update the amount for a previously announced withdrawal
|
214
|
+
* @param newAmount_ New withdrawal amount
|
215
|
+
*/
|
216
|
+
function updateNeutralsWithdrawal(uint256 newAmount_) public {
|
217
|
+
cancelNeutralsWithdrawal();
|
218
|
+
announceNeutralsWithdrawal(newAmount_);
|
219
|
+
}
|
220
|
+
|
221
|
+
/**
|
222
|
+
* @notice Complete withdrawal after the announcement period has passed
|
223
|
+
*/
|
224
|
+
function completeNeutralsWithdrawal() external {
|
225
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
226
|
+
NeutralsInfo storage neutralInfo = $.neutrals[msg.sender];
|
227
|
+
WithdrawalAnnouncement storage announcement = $.neutralsWithdrawalAnnouncements[
|
228
|
+
msg.sender
|
229
|
+
];
|
230
|
+
|
231
|
+
if (announcement.announcedAt == 0) revert NoWithdrawalAnnouncement(msg.sender);
|
232
|
+
|
233
|
+
if (block.timestamp < announcement.availableAt) {
|
234
|
+
revert WithdrawalNotReady(msg.sender, announcement.availableAt);
|
235
|
+
}
|
236
|
+
|
237
|
+
uint256 amount_ = announcement.amount;
|
238
|
+
|
239
|
+
delete $.neutralsWithdrawalAnnouncements[msg.sender];
|
240
|
+
|
241
|
+
$.stakeToken.safeTransfer(msg.sender, amount_);
|
242
|
+
|
243
|
+
emit NeutralUnstaked(msg.sender, amount_, neutralInfo.selfStake);
|
244
|
+
}
|
245
|
+
|
246
|
+
/**
|
247
|
+
* @notice Delegate tokens to a neutral
|
248
|
+
* @param neutral_ Address of the neutral to delegate to
|
249
|
+
* @param amount_ Amount of tokens to delegate
|
250
|
+
*/
|
251
|
+
function delegate(address neutral_, uint256 amount_) external {
|
252
|
+
if (amount_ == 0) revert InvalidDelegationAmount(amount_);
|
253
|
+
if (neutral_ == msg.sender) revert NeutralCannotDelegateToHimself(msg.sender);
|
254
|
+
|
255
|
+
_checkDelegation(neutral_, amount_);
|
256
|
+
|
257
|
+
_addDelegationStake(msg.sender, neutral_, amount_);
|
258
|
+
|
259
|
+
_getNeutralsRegistryStorage().stakeToken.safeTransferFrom(
|
260
|
+
msg.sender,
|
261
|
+
address(this),
|
262
|
+
amount_
|
263
|
+
);
|
264
|
+
}
|
265
|
+
|
266
|
+
/**
|
267
|
+
* @notice Complete withdrawal of delegated tokens after the announcement period
|
268
|
+
* @param neutral_ Address of the neutral
|
269
|
+
*/
|
270
|
+
function completeDelegationWithdrawal(address neutral_) external {
|
271
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
272
|
+
WithdrawalAnnouncement storage announcement = $
|
273
|
+
.delegations[msg.sender][neutral_].withdrawal;
|
274
|
+
|
275
|
+
if (announcement.announcedAt == 0) revert NoWithdrawalAnnouncement(msg.sender);
|
276
|
+
|
277
|
+
if (block.timestamp < announcement.availableAt) {
|
278
|
+
revert WithdrawalNotReady(msg.sender, announcement.availableAt);
|
279
|
+
}
|
280
|
+
|
281
|
+
uint256 amount_ = announcement.amount;
|
282
|
+
|
283
|
+
delete $.delegations[neutral_][msg.sender].withdrawal;
|
284
|
+
|
285
|
+
$.stakeToken.safeTransfer(msg.sender, amount_);
|
286
|
+
|
287
|
+
emit DelegationWithdrawn(msg.sender, neutral_, amount_);
|
288
|
+
}
|
289
|
+
|
290
|
+
/**
|
291
|
+
* @notice Claim rewards accrued as a neutral
|
292
|
+
*/
|
293
|
+
function claimNeutralRewards() external {
|
294
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
295
|
+
uint256 rewards_ = $.neutrals[msg.sender].rewards;
|
296
|
+
|
297
|
+
if (rewards_ == 0) revert Errors.NoRewardsAvailible(msg.sender);
|
298
|
+
|
299
|
+
delete $.neutrals[msg.sender].rewards;
|
300
|
+
|
301
|
+
_getNeutralsRegistryStorage().stakeToken.safeTransfer(msg.sender, rewards_);
|
302
|
+
|
303
|
+
emit RewardsClaimed(msg.sender, rewards_);
|
304
|
+
}
|
305
|
+
|
306
|
+
/**
|
307
|
+
* @notice Claim rewards accrued as a delegator for a specific neutral
|
308
|
+
* @param neutral_ Address of the neutral
|
309
|
+
*/
|
310
|
+
function claimDelegatorRewards(address neutral_) external {
|
311
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
312
|
+
uint256 rewards_ = _distributeAllDelegatorRewards(msg.sender, neutral_);
|
313
|
+
|
314
|
+
$.stakeToken.safeTransfer(msg.sender, rewards_);
|
315
|
+
|
316
|
+
emit RewardsClaimed(msg.sender, rewards_);
|
317
|
+
}
|
318
|
+
|
319
|
+
/**
|
320
|
+
* @notice Set the share of rewards to be distributed to delegators
|
321
|
+
* @param delegationShare_ Percentage of rewards for delegators in basis points (for example, 250 basis points = 2.5 %)
|
322
|
+
*/
|
323
|
+
function setDelegationShare(uint256 delegationShare_) external {
|
324
|
+
require(
|
325
|
+
delegationShare_ <= MAX_BASIS_POINTS,
|
326
|
+
Errors.ValueShouldBeInBasisPoints(delegationShare_)
|
327
|
+
);
|
328
|
+
|
329
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
330
|
+
NeutralsInfo storage neutralInfo = $.neutrals[msg.sender];
|
331
|
+
|
332
|
+
if (neutralInfo.selfStake == 0) revert NeutralHasNoStake(msg.sender);
|
333
|
+
neutralInfo.delegationShare = delegationShare_;
|
334
|
+
}
|
335
|
+
|
336
|
+
/**
|
337
|
+
* @notice Set an external link for the neutral (e.g., profile or info page)
|
338
|
+
* @param externalLink_ The external link string
|
339
|
+
*/
|
340
|
+
function setExternalLink(string calldata externalLink_) external {
|
341
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
342
|
+
NeutralsInfo storage neutralInfo = $.neutrals[msg.sender];
|
343
|
+
|
344
|
+
if (neutralInfo.selfStake == 0) revert NeutralHasNoStake(msg.sender);
|
345
|
+
|
346
|
+
neutralInfo.externalLink = externalLink_;
|
347
|
+
}
|
348
|
+
|
349
|
+
/**
|
350
|
+
* @notice Slash a neutral's self-stake (only callable by DAOSLC)
|
351
|
+
* @param neutral_ Address of the neutral to slash
|
352
|
+
* @param amount_ Amount to slash
|
353
|
+
* @dev This function accounts for withdrawal announcements to prevent front-running.
|
354
|
+
* If an executor has announced withdrawal, the total slashable amount includes
|
355
|
+
* both their current stake and the withdrawal announcement amount.
|
356
|
+
*/
|
357
|
+
function slashNeutral(
|
358
|
+
address neutral_,
|
359
|
+
uint256 amount_
|
360
|
+
) external onlyDAOSLC syncedMaxActiveNeutrals {
|
361
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
362
|
+
NeutralsInfo storage neutralInfo = $.neutrals[neutral_];
|
363
|
+
WithdrawalAnnouncement storage announcement = $.neutralsWithdrawalAnnouncements[neutral_];
|
364
|
+
|
365
|
+
uint256 totalSlashableAmount_ = neutralInfo.selfStake;
|
366
|
+
if (announcement.announcedAt != 0) {
|
367
|
+
totalSlashableAmount_ += announcement.amount;
|
368
|
+
}
|
369
|
+
|
370
|
+
if (totalSlashableAmount_ == 0) revert NeutralHasNoStake(neutral_);
|
371
|
+
if (amount_ > totalSlashableAmount_)
|
372
|
+
revert Errors.InsufficientStakeAmount(neutral_, amount_, neutralInfo.selfStake);
|
373
|
+
|
374
|
+
address slashingRecipient_ = _getSlashingRecipient();
|
375
|
+
|
376
|
+
if (amount_ > neutralInfo.selfStake && announcement.announcedAt != 0) {
|
377
|
+
neutralInfo.selfStake += announcement.amount;
|
378
|
+
delete $.neutralsWithdrawalAnnouncements[neutral_];
|
379
|
+
}
|
380
|
+
|
381
|
+
neutralInfo.selfStake -= amount_;
|
382
|
+
|
383
|
+
_manipulateNeutralState(neutral_);
|
384
|
+
|
385
|
+
$.stakeToken.safeTransfer(slashingRecipient_, amount_);
|
386
|
+
|
387
|
+
emit NeutralSlashed(neutral_, amount_, slashingRecipient_);
|
388
|
+
}
|
389
|
+
|
390
|
+
/**
|
391
|
+
* @notice Synchronize local maxActiveNeutrals parameter with the one in the registry
|
392
|
+
*/
|
393
|
+
function boundActiveNeutralsList() external {
|
394
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
395
|
+
uint256 remoteMaxActiveNeutrals_ = _getMaxActiveNeutrals();
|
396
|
+
if ($.maxActiveNeutrals == remoteMaxActiveNeutrals_) {
|
397
|
+
revert Errors.ParameterIsAlreadySynced(MAX_ACTIVE_NEUTRALS_PARAM_NAME);
|
398
|
+
}
|
399
|
+
|
400
|
+
$.maxActiveNeutrals = remoteMaxActiveNeutrals_;
|
401
|
+
|
402
|
+
_handleMaxActiveNeutralsResize();
|
403
|
+
}
|
404
|
+
|
405
|
+
function tryBecomeActive() external syncedMaxActiveNeutrals {
|
406
|
+
_tryActivateNeutral(msg.sender);
|
407
|
+
}
|
408
|
+
|
409
|
+
/**
|
410
|
+
* @notice Distribute rewards to selected neutrals (only callable by Governance)
|
411
|
+
* @param selectedNeutrals_ Array of selected neutral addresses
|
412
|
+
* @param amount_ Total amount to distribute
|
413
|
+
*/
|
414
|
+
function distributeRewards(
|
415
|
+
address[] calldata selectedNeutrals_,
|
416
|
+
uint256 amount_
|
417
|
+
) external onlyGovernance {
|
418
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
419
|
+
|
420
|
+
uint256 selectedNeutralsLen_ = selectedNeutrals_.length;
|
421
|
+
uint256 neutralPart_ = amount_ / selectedNeutralsLen_;
|
422
|
+
uint256 remainder_ = amount_ % selectedNeutralsLen_;
|
423
|
+
|
424
|
+
for (uint256 i = 0; i < selectedNeutralsLen_; ++i) {
|
425
|
+
_updateNeutralRewards(selectedNeutrals_[i], neutralPart_);
|
426
|
+
}
|
427
|
+
|
428
|
+
address treasuryAddress_ = _getTreasuryAddress();
|
429
|
+
|
430
|
+
$.stakeToken.safeTransferFrom(msg.sender, address(this), amount_);
|
431
|
+
$.stakeToken.safeTransfer(treasuryAddress_, remainder_);
|
432
|
+
emit RewardsDistributed(selectedNeutrals_, amount_);
|
433
|
+
}
|
434
|
+
|
435
|
+
/**
|
436
|
+
* @notice Get the current governance contract address
|
437
|
+
* @return Address of the governance contract
|
438
|
+
*/
|
439
|
+
function getCurrentGovernance() external view returns (address) {
|
440
|
+
return address(_getNeutralsRegistryStorage().governance);
|
441
|
+
}
|
442
|
+
|
443
|
+
/**
|
444
|
+
* @notice Get the address of the staking token
|
445
|
+
* @return Address of the staking token
|
446
|
+
*/
|
447
|
+
function getToken() external view returns (address) {
|
448
|
+
return address(_getNeutralsRegistryStorage().stakeToken);
|
449
|
+
}
|
450
|
+
|
451
|
+
function getCurrentMaxActiveNeutralsParameter() external view returns (uint256) {
|
452
|
+
return _getNeutralsRegistryStorage().maxActiveNeutrals;
|
453
|
+
}
|
454
|
+
|
455
|
+
/**
|
456
|
+
* @notice Get the address of the parameters registry
|
457
|
+
* @return Address of the parameters registry
|
458
|
+
*/
|
459
|
+
function getParameterRegistry() external view returns (address) {
|
460
|
+
return address(_getNeutralsRegistryStorage().parametersRegistry);
|
461
|
+
}
|
462
|
+
|
463
|
+
/**
|
464
|
+
* @notice Get a paginated list of active neutrals
|
465
|
+
* @param offset_ Starting index
|
466
|
+
* @param limit_ Maximum number of neutrals to return
|
467
|
+
* @return Array of active neutral addresses
|
468
|
+
*/
|
469
|
+
function getActiveNeutrals(
|
470
|
+
uint256 offset_,
|
471
|
+
uint256 limit_
|
472
|
+
) external view returns (address[] memory) {
|
473
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
474
|
+
|
475
|
+
uint256 to_ = Paginator.getTo($.activeNeutrals.length(), offset_, limit_);
|
476
|
+
|
477
|
+
address[] memory result = new address[](to_ - offset_);
|
478
|
+
|
479
|
+
for (uint256 i = offset_; i < to_; ++i) {
|
480
|
+
result[i - offset_] = address(uint160(uint256(($.activeNeutrals._queue._values[i]))));
|
481
|
+
}
|
482
|
+
|
483
|
+
return result;
|
484
|
+
}
|
485
|
+
|
486
|
+
/**
|
487
|
+
* @notice Get a paginated list of standby neutrals
|
488
|
+
* @param offset_ Starting index
|
489
|
+
* @param limit_ Maximum number of neutrals to return
|
490
|
+
* @return Array of standby neutral addresses
|
491
|
+
*/
|
492
|
+
function getStandbyNeutrals(
|
493
|
+
uint256 offset_,
|
494
|
+
uint256 limit_
|
495
|
+
) external view returns (address[] memory) {
|
496
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
497
|
+
|
498
|
+
uint256 to_ = Paginator.getTo($.standbyNeutrals.length(), offset_, limit_);
|
499
|
+
|
500
|
+
address[] memory result = new address[](to_ - offset_);
|
501
|
+
|
502
|
+
for (uint256 i = offset_; i < to_; ++i) {
|
503
|
+
result[i - offset_] = address(uint160(uint256(($.standbyNeutrals._queue._values[i]))));
|
504
|
+
}
|
505
|
+
|
506
|
+
return result;
|
507
|
+
}
|
508
|
+
|
509
|
+
/**
|
510
|
+
* @notice Get withdrawal announcement information for a neutral
|
511
|
+
* @param neutral_ Address of the neutral
|
512
|
+
* @return Withdrawal announcement struct
|
513
|
+
*/
|
514
|
+
function getWithdrawalAnnouncement(
|
515
|
+
address neutral_
|
516
|
+
) external view returns (WithdrawalAnnouncement memory) {
|
517
|
+
return _getNeutralsRegistryStorage().neutralsWithdrawalAnnouncements[neutral_];
|
518
|
+
}
|
519
|
+
|
520
|
+
/**
|
521
|
+
* @notice Get delegation of delegator for neutral
|
522
|
+
* @param delegator_ Address of the delegator_
|
523
|
+
* @param neutral_ Address of the neutral
|
524
|
+
* @return Info about delegation
|
525
|
+
*/
|
526
|
+
function getDelegation(
|
527
|
+
address delegator_,
|
528
|
+
address neutral_
|
529
|
+
) external view returns (Delegation memory) {
|
530
|
+
return _getNeutralsRegistryStorage().delegations[delegator_][neutral_];
|
531
|
+
}
|
532
|
+
|
533
|
+
/**
|
534
|
+
* @notice Get all delegators for the neutral
|
535
|
+
*/
|
536
|
+
function getDelegatorsPerNeutral(
|
537
|
+
address neutral_,
|
538
|
+
uint256 offset_,
|
539
|
+
uint256 limit_
|
540
|
+
) external view returns (address[] memory) {
|
541
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
542
|
+
|
543
|
+
return Paginator.part($.delegatorsPerNeutral[neutral_], offset_, limit_);
|
544
|
+
}
|
545
|
+
|
546
|
+
/**
|
547
|
+
* @notice Get all neutrals that were delegated by the delegator.
|
548
|
+
*/
|
549
|
+
function getNeutralsPerDelegator(
|
550
|
+
address delegator_,
|
551
|
+
uint256 offset_,
|
552
|
+
uint256 limit_
|
553
|
+
) external view returns (address[] memory) {
|
554
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
555
|
+
|
556
|
+
return Paginator.part($.neutralsPerDelegator[delegator_], offset_, limit_);
|
557
|
+
}
|
558
|
+
|
559
|
+
/**
|
560
|
+
* @notice Check if a neutral's withdrawal is ready to be completed
|
561
|
+
* @param neutral_ Address of the neutral
|
562
|
+
* @return True if withdrawal can be completed
|
563
|
+
*/
|
564
|
+
function isWithdrawalReady(address neutral_) external view returns (bool) {
|
565
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
566
|
+
WithdrawalAnnouncement storage announcement_ = $.neutralsWithdrawalAnnouncements[neutral_];
|
567
|
+
|
568
|
+
if (announcement_.announcedAt == 0) return false;
|
569
|
+
|
570
|
+
return block.timestamp >= announcement_.availableAt;
|
571
|
+
}
|
572
|
+
|
573
|
+
/**
|
574
|
+
* @notice Get a random slice of active neutrals for selection
|
575
|
+
* @param number_ Number of neutrals to select
|
576
|
+
* @return Array of selected neutral addresses
|
577
|
+
*/
|
578
|
+
function getNeutralsSlice(uint256 number_) external view returns (address[] memory) {
|
579
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
580
|
+
uint256 activeNeutralsLength_ = $.activeNeutrals.length();
|
581
|
+
|
582
|
+
if (number_ > activeNeutralsLength_ || number_ == 0) {
|
583
|
+
revert SelectedNeutralsNumberOutOfRange(number_, activeNeutralsLength_);
|
584
|
+
}
|
585
|
+
|
586
|
+
address[] memory activeNeutrals_ = $.activeNeutrals.values();
|
587
|
+
uint256[] memory randomizedWeights_ = new uint256[](activeNeutralsLength_);
|
588
|
+
bytes32 seed_ = keccak256(abi.encode(blockhash(block.number - 1)));
|
589
|
+
|
590
|
+
for (uint256 i = 0; i < activeNeutralsLength_; ++i) {
|
591
|
+
address neutral_ = activeNeutrals_[i];
|
592
|
+
uint256 neutralWeight_ = $.neutrals[neutral_].weight;
|
593
|
+
|
594
|
+
uint256 randomNumber_ = NeutralsSelection.getRandomNumber(abi.encodePacked(seed_, i));
|
595
|
+
randomizedWeights_[i] = Math.mulDiv(randomNumber_, neutralWeight_, PRECISION);
|
596
|
+
}
|
597
|
+
|
598
|
+
NeutralsSelection.selectTopAddressesByWeight(activeNeutrals_, randomizedWeights_, number_);
|
599
|
+
|
600
|
+
// solhint-disable-next-line no-inline-assembly
|
601
|
+
assembly ("memory-safe") {
|
602
|
+
mstore(activeNeutrals_, number_)
|
603
|
+
}
|
604
|
+
|
605
|
+
return activeNeutrals_;
|
606
|
+
}
|
607
|
+
|
608
|
+
/**
|
609
|
+
* @notice Get the count of currently active neutrals
|
610
|
+
* @return Number of active neutrals
|
611
|
+
*/
|
612
|
+
function getNeutralsCount() external view override returns (uint256) {
|
613
|
+
return _getNeutralsRegistryStorage().activeNeutrals.length();
|
614
|
+
}
|
615
|
+
|
616
|
+
/**
|
617
|
+
* @notice Get detailed information about a neutral
|
618
|
+
* @param neutral_ Address of the neutral
|
619
|
+
* @return NeutralsInfo struct with all neutral data
|
620
|
+
*/
|
621
|
+
function getNeutralInfo(address neutral_) external view returns (NeutralsInfo memory) {
|
622
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
623
|
+
return $.neutrals[neutral_];
|
624
|
+
}
|
625
|
+
|
626
|
+
/**
|
627
|
+
* @notice Get the delegation share set by a neutral
|
628
|
+
* @param neutral_ Address of the neutral
|
629
|
+
* @return Delegation share (percentage)
|
630
|
+
*/
|
631
|
+
function getNeutralDelegationShare(address neutral_) external view returns (uint256) {
|
632
|
+
return _getNeutralsRegistryStorage().neutrals[neutral_].delegationShare;
|
633
|
+
}
|
634
|
+
|
635
|
+
/**
|
636
|
+
* @notice Get the external link set by a neutral
|
637
|
+
* @param neutral_ Address of the neutral
|
638
|
+
* @return The external link string
|
639
|
+
*/
|
640
|
+
function getExternalLink(address neutral_) external view returns (string memory) {
|
641
|
+
return _getNeutralsRegistryStorage().neutrals[neutral_].externalLink;
|
642
|
+
}
|
643
|
+
|
644
|
+
function implementation() external view returns (address) {
|
645
|
+
return ERC1967Utils.getImplementation();
|
646
|
+
}
|
647
|
+
|
648
|
+
/**
|
649
|
+
* @notice Announce intention to withdraw delegated tokens from a neutral
|
650
|
+
* @param neutral_ Address of the neutral
|
651
|
+
* @param amount_ Amount of tokens to withdraw
|
652
|
+
*/
|
653
|
+
function announceDelegationWithdrawal(address neutral_, uint256 amount_) public {
|
654
|
+
if (amount_ == 0) revert InvalidDelegationAmount(amount_);
|
655
|
+
|
656
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
657
|
+
WithdrawalAnnouncement storage announcement = $
|
658
|
+
.delegations[msg.sender][neutral_].withdrawal;
|
659
|
+
|
660
|
+
uint256 delegationAmount_ = $.delegations[msg.sender][neutral_].delegationAmount;
|
661
|
+
|
662
|
+
if (amount_ > delegationAmount_)
|
663
|
+
revert InsufficientDelegationAmount(msg.sender, neutral_, amount_, delegationAmount_);
|
664
|
+
if (announcement.announcedAt != 0) revert WithdrawalAlreadyAnnounced(msg.sender);
|
665
|
+
|
666
|
+
uint256 withdrawalPeriod_ = _getDelegatorsWithdrawalPeriod();
|
667
|
+
uint256 availableAt_ = block.timestamp + withdrawalPeriod_;
|
668
|
+
|
669
|
+
announcement.amount = amount_;
|
670
|
+
announcement.announcedAt = block.timestamp;
|
671
|
+
announcement.availableAt = availableAt_;
|
672
|
+
|
673
|
+
_removeDelegationStake(msg.sender, neutral_, amount_);
|
674
|
+
|
675
|
+
emit WithdrawalAnnounced(msg.sender, amount_, availableAt_);
|
676
|
+
}
|
677
|
+
|
678
|
+
/**
|
679
|
+
* @notice Cancel a previously announced delegation withdrawal
|
680
|
+
* @param neutral_ Address of the neutral
|
681
|
+
*/
|
682
|
+
function cancelDelegationWithdrawal(address neutral_) public {
|
683
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
684
|
+
WithdrawalAnnouncement storage announcement = $
|
685
|
+
.delegations[msg.sender][neutral_].withdrawal;
|
686
|
+
|
687
|
+
if (announcement.announcedAt == 0) revert NoWithdrawalAnnouncement(msg.sender);
|
688
|
+
uint256 amount_ = announcement.amount;
|
689
|
+
|
690
|
+
_addDelegationStake(msg.sender, neutral_, amount_);
|
691
|
+
|
692
|
+
delete $.delegations[msg.sender][neutral_].withdrawal;
|
693
|
+
|
694
|
+
emit WithdrawalCancelled(msg.sender, amount_);
|
695
|
+
}
|
696
|
+
|
697
|
+
/**
|
698
|
+
* @notice Update the amount for a previously announced delegation withdrawal
|
699
|
+
* @param neutral_ Address of the neutral
|
700
|
+
* @param newAmount_ New withdrawal amount
|
701
|
+
*/
|
702
|
+
function updateDelegationWithdrawal(address neutral_, uint256 newAmount_) public {
|
703
|
+
cancelDelegationWithdrawal(neutral_);
|
704
|
+
announceDelegationWithdrawal(neutral_, newAmount_);
|
705
|
+
}
|
706
|
+
|
707
|
+
/**
|
708
|
+
* @notice Check if an address is an active neutral
|
709
|
+
* @param candidate_ Address to check
|
710
|
+
* @return True if the address is an active neutral
|
711
|
+
*/
|
712
|
+
function isNeutral(address candidate_) public view returns (bool) {
|
713
|
+
return _getNeutralsRegistryStorage().neutrals[candidate_].isActive;
|
714
|
+
}
|
715
|
+
|
716
|
+
/**
|
717
|
+
* @notice Get the available rewards for a neutral
|
718
|
+
* @param neutral_ Address of the neutral
|
719
|
+
* @return Amount of available rewards
|
720
|
+
*/
|
721
|
+
function getAvailibleRewards(address neutral_) public view returns (uint256) {
|
722
|
+
return _getNeutralsRegistryStorage().neutrals[neutral_].rewards;
|
723
|
+
}
|
724
|
+
|
725
|
+
/// @inheritdoc ERC165
|
726
|
+
function supportsInterface(
|
727
|
+
bytes4 interfaceId_
|
728
|
+
) public view override(ERC165, IERC165) returns (bool) {
|
729
|
+
return
|
730
|
+
interfaceId_ == type(INeutralsRegistry).interfaceId ||
|
731
|
+
super.supportsInterface(interfaceId_);
|
732
|
+
}
|
733
|
+
|
734
|
+
function _tryActivateNeutral(address neutral_) internal {
|
735
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
736
|
+
NeutralsInfo storage neutralInfo = $.neutrals[neutral_];
|
737
|
+
|
738
|
+
if (neutralInfo.selfStake < _getMinimumStake()) return;
|
739
|
+
|
740
|
+
if ($.activeNeutrals.length() < $.maxActiveNeutrals) {
|
741
|
+
_activateNeutral(neutral_);
|
742
|
+
} else {
|
743
|
+
address lowestNeutral_ = $.activeNeutrals.topValue();
|
744
|
+
uint256 lowestStake_ = $.neutrals[lowestNeutral_].selfStake;
|
745
|
+
|
746
|
+
if (neutralInfo.selfStake > lowestStake_) {
|
747
|
+
_removeFromActiveQueue(lowestNeutral_);
|
748
|
+
_addToStandby(lowestNeutral_);
|
749
|
+
|
750
|
+
_activateNeutral(neutral_);
|
751
|
+
} else {
|
752
|
+
_addToStandby(neutral_);
|
753
|
+
}
|
754
|
+
}
|
755
|
+
}
|
756
|
+
|
757
|
+
/**
|
758
|
+
* @notice Handle changing of maxActiveNeutrals parameter in Registry.
|
759
|
+
* In case of maxActiveNeutrals increasing, do nothing, but if it is decreased,
|
760
|
+
* and size of the list of active neutrals is greater than
|
761
|
+
* the maxActiveNeutrals parameter, then the list will be reduced.
|
762
|
+
* Neutrals, which was removed from active queue, are moved to standby list.
|
763
|
+
*/
|
764
|
+
function _handleMaxActiveNeutralsResize() internal {
|
765
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
766
|
+
uint256 maxActiveNeutrals_ = $.maxActiveNeutrals;
|
767
|
+
uint256 activeNeutralsLength_ = $.activeNeutrals.length();
|
768
|
+
|
769
|
+
if (maxActiveNeutrals_ >= activeNeutralsLength_) return;
|
770
|
+
|
771
|
+
// SAFE: Previously checked, that activeNeutralsLength_ > maxActiveNeutrals
|
772
|
+
uint256 delta_ = activeNeutralsLength_ - maxActiveNeutrals_;
|
773
|
+
|
774
|
+
while (delta_ > 0) {
|
775
|
+
address lowestNeutral_ = $.activeNeutrals.topValue();
|
776
|
+
|
777
|
+
_removeFromActiveQueue(lowestNeutral_);
|
778
|
+
_addToStandby(lowestNeutral_);
|
779
|
+
|
780
|
+
--delta_;
|
781
|
+
}
|
782
|
+
}
|
783
|
+
|
784
|
+
function _addToActiveQueue(address neutral_) internal {
|
785
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
786
|
+
NeutralsInfo storage neutralInfo = $.neutrals[neutral_];
|
787
|
+
|
788
|
+
$.activeNeutrals.add(neutral_, type(uint256).max - neutralInfo.selfStake);
|
789
|
+
|
790
|
+
neutralInfo.isActive = true;
|
791
|
+
}
|
792
|
+
|
793
|
+
function _addToStandby(address neutral_) internal {
|
794
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
795
|
+
NeutralsInfo storage neutralInfo = $.neutrals[neutral_];
|
796
|
+
|
797
|
+
if (neutralInfo.selfStake < _getMinimumStake()) return;
|
798
|
+
|
799
|
+
if (neutralInfo.isStandby) {
|
800
|
+
_removeFromStandby(neutral_);
|
801
|
+
}
|
802
|
+
|
803
|
+
$.standbyNeutrals.add(neutral_, neutralInfo.selfStake);
|
804
|
+
|
805
|
+
neutralInfo.isStandby = true;
|
806
|
+
|
807
|
+
emit NeutralMovedToStandby(neutral_);
|
808
|
+
}
|
809
|
+
|
810
|
+
function _removeFromStandby(address neutral_) internal {
|
811
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
812
|
+
|
813
|
+
$.standbyNeutrals.remove(neutral_);
|
814
|
+
|
815
|
+
$.neutrals[neutral_].isStandby = false;
|
816
|
+
}
|
817
|
+
|
818
|
+
function _removeFromActiveQueue(address neutral_) internal {
|
819
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
820
|
+
|
821
|
+
$.activeNeutrals.remove(neutral_);
|
822
|
+
|
823
|
+
$.neutrals[neutral_].isActive = false;
|
824
|
+
}
|
825
|
+
|
826
|
+
/**
|
827
|
+
* @inheritdoc UUPSUpgradeable
|
828
|
+
*/
|
829
|
+
// solhint-disable-next-line no-empty-blocks
|
830
|
+
function _authorizeUpgrade(address newImplementation_) internal override onlyDAOSLC {}
|
831
|
+
|
832
|
+
function _activateNeutral(address neutral_) internal {
|
833
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
834
|
+
|
835
|
+
_removeFromStandby(neutral_);
|
836
|
+
_addToActiveQueue(neutral_);
|
837
|
+
|
838
|
+
emit NeutralActivated(neutral_, $.neutrals[neutral_].selfStake);
|
839
|
+
}
|
840
|
+
|
841
|
+
function _getMinimumStake() internal view returns (uint256) {
|
842
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
843
|
+
try $.parametersRegistry.getUint(MINIMUM_STAKE_PARAM_NAME) returns (
|
844
|
+
uint256 minimumStake_
|
845
|
+
) {
|
846
|
+
return minimumStake_;
|
847
|
+
} catch {
|
848
|
+
revert Errors.ParameterIsNotSet(
|
849
|
+
address($.parametersRegistry),
|
850
|
+
MINIMUM_STAKE_PARAM_NAME
|
851
|
+
);
|
852
|
+
}
|
853
|
+
}
|
854
|
+
|
855
|
+
function _getMaxDelegationAmplification() private view returns (uint256) {
|
856
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
857
|
+
|
858
|
+
try $.parametersRegistry.getUint(MAX_DELEGATION_AMPLIFICATION_PARAM_NAME) returns (
|
859
|
+
uint256 maxDelegationAmplification_
|
860
|
+
) {
|
861
|
+
return maxDelegationAmplification_;
|
862
|
+
} catch {
|
863
|
+
revert Errors.ParameterIsNotSet(
|
864
|
+
address($.parametersRegistry),
|
865
|
+
MAX_DELEGATION_AMPLIFICATION_PARAM_NAME
|
866
|
+
);
|
867
|
+
}
|
868
|
+
}
|
869
|
+
|
870
|
+
function _getMaxActiveNeutrals() internal view returns (uint256) {
|
871
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
872
|
+
try $.parametersRegistry.getUint(MAX_ACTIVE_NEUTRALS_PARAM_NAME) returns (
|
873
|
+
uint256 maxActiveNeutrals_
|
874
|
+
) {
|
875
|
+
return maxActiveNeutrals_;
|
876
|
+
} catch {
|
877
|
+
revert Errors.ParameterIsNotSet(
|
878
|
+
address($.parametersRegistry),
|
879
|
+
MAX_ACTIVE_NEUTRALS_PARAM_NAME
|
880
|
+
);
|
881
|
+
}
|
882
|
+
}
|
883
|
+
|
884
|
+
function _getDAOSLC() internal view returns (address) {
|
885
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
886
|
+
try $.parametersRegistry.getAddr(DAOSLC_PARAM_NAME) returns (address daoslc_) {
|
887
|
+
return daoslc_;
|
888
|
+
} catch {
|
889
|
+
revert Errors.ParameterIsNotSet(address($.parametersRegistry), DAOSLC_PARAM_NAME);
|
890
|
+
}
|
891
|
+
}
|
892
|
+
|
893
|
+
function _getNeutralsWithdrawalPeriod() internal view returns (uint256) {
|
894
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
895
|
+
try $.parametersRegistry.getUint(NEUTRALS_WITHDRAWAL_PERIOD_PARAM_NAME) returns (
|
896
|
+
uint256 neutralsWithdrawalPeriod_
|
897
|
+
) {
|
898
|
+
return neutralsWithdrawalPeriod_;
|
899
|
+
} catch {
|
900
|
+
revert Errors.ParameterIsNotSet(
|
901
|
+
address($.parametersRegistry),
|
902
|
+
NEUTRALS_WITHDRAWAL_PERIOD_PARAM_NAME
|
903
|
+
);
|
904
|
+
}
|
905
|
+
}
|
906
|
+
|
907
|
+
function _getDelegatorsWithdrawalPeriod() internal view returns (uint256) {
|
908
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
909
|
+
try $.parametersRegistry.getUint(NEUTRALS_WITHDRAWAL_PERIOD_PARAM_NAME) returns (
|
910
|
+
uint256 delegatorsWithdrawalPeriod_
|
911
|
+
) {
|
912
|
+
return delegatorsWithdrawalPeriod_;
|
913
|
+
} catch {
|
914
|
+
revert Errors.ParameterIsNotSet(
|
915
|
+
address($.parametersRegistry),
|
916
|
+
DELEGATORS_WITHDRAWAL_PERIOD_PARAM_NAME
|
917
|
+
);
|
918
|
+
}
|
919
|
+
}
|
920
|
+
|
921
|
+
function _getTreasuryAddress() internal view returns (address) {
|
922
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
923
|
+
try $.parametersRegistry.getAddr(TREASURY_ADDRESS_PARAM_NAME) returns (
|
924
|
+
address treasuryAddress_
|
925
|
+
) {
|
926
|
+
return treasuryAddress_;
|
927
|
+
} catch {
|
928
|
+
revert Errors.ParameterIsNotSet(
|
929
|
+
address($.parametersRegistry),
|
930
|
+
TREASURY_ADDRESS_PARAM_NAME
|
931
|
+
);
|
932
|
+
}
|
933
|
+
}
|
934
|
+
|
935
|
+
function _getSlashingRecipient() internal view returns (address) {
|
936
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
937
|
+
try $.parametersRegistry.getAddr(NEUTRALS_SLASHING_RECIPIENT_PARAM_NAME) returns (
|
938
|
+
address slashingRecipient_
|
939
|
+
) {
|
940
|
+
return slashingRecipient_;
|
941
|
+
} catch {
|
942
|
+
revert Errors.ParameterIsNotSet(
|
943
|
+
address($.parametersRegistry),
|
944
|
+
NEUTRALS_SLASHING_RECIPIENT_PARAM_NAME
|
945
|
+
);
|
946
|
+
}
|
947
|
+
}
|
948
|
+
|
949
|
+
function _recalculateNeutralWeight(address neutral_) private {
|
950
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
951
|
+
uint256 selfStake = $.neutrals[neutral_].selfStake;
|
952
|
+
uint256 delegatedStake = $.neutrals[neutral_].delegatedStake;
|
953
|
+
|
954
|
+
$.neutrals[neutral_].weight = Math.sqrt(selfStake + delegatedStake);
|
955
|
+
}
|
956
|
+
|
957
|
+
function _addDelegationStake(address delegator_, address neutral_, uint256 amount_) private {
|
958
|
+
_updateDelegatorOwedValue(delegator_, neutral_);
|
959
|
+
|
960
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
961
|
+
NeutralsInfo storage neutralsInfo = $.neutrals[neutral_];
|
962
|
+
Delegation storage delegation = $.delegations[delegator_][neutral_];
|
963
|
+
|
964
|
+
if (delegation.delegationAmount == 0) {
|
965
|
+
$.neutralsPerDelegator[delegator_].add(neutral_);
|
966
|
+
$.delegatorsPerNeutral[neutral_].add(delegator_);
|
967
|
+
}
|
968
|
+
|
969
|
+
delegation.delegationAmount += amount_;
|
970
|
+
neutralsInfo.delegatedStake += amount_;
|
971
|
+
|
972
|
+
_recalculateNeutralWeight(neutral_);
|
973
|
+
}
|
974
|
+
|
975
|
+
function _removeDelegationStake(
|
976
|
+
address delegator_,
|
977
|
+
address neutral_,
|
978
|
+
uint256 amount_
|
979
|
+
) private {
|
980
|
+
_updateDelegatorOwedValue(delegator_, neutral_);
|
981
|
+
|
982
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
983
|
+
NeutralsInfo storage neutralsInfo = $.neutrals[neutral_];
|
984
|
+
Delegation storage delegation = $.delegations[delegator_][neutral_];
|
985
|
+
|
986
|
+
delegation.delegationAmount -= amount_;
|
987
|
+
neutralsInfo.delegatedStake -= amount_;
|
988
|
+
|
989
|
+
if (delegation.delegationAmount == 0) {
|
990
|
+
$.neutralsPerDelegator[delegator_].remove(neutral_);
|
991
|
+
$.delegatorsPerNeutral[neutral_].remove(delegator_);
|
992
|
+
}
|
993
|
+
|
994
|
+
_recalculateNeutralWeight(neutral_);
|
995
|
+
}
|
996
|
+
|
997
|
+
function _updateNeutralRewards(address neutral_, uint256 amount_) private {
|
998
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
999
|
+
uint256 delegatedStake_ = $.neutrals[neutral_].delegatedStake;
|
1000
|
+
|
1001
|
+
if (delegatedStake_ == 0) {
|
1002
|
+
$.neutrals[neutral_].rewards += amount_;
|
1003
|
+
return;
|
1004
|
+
}
|
1005
|
+
|
1006
|
+
uint256 delegationShare_ = $.neutrals[neutral_].delegationShare;
|
1007
|
+
uint256 delegatorsPart_ = Math.mulDiv(delegationShare_, amount_, MAX_BASIS_POINTS);
|
1008
|
+
uint256 neutralPart_ = amount_ - delegatorsPart_;
|
1009
|
+
|
1010
|
+
$.neutrals[neutral_].rewards += neutralPart_;
|
1011
|
+
_updateDelegatorsRewardPool(neutral_, delegatorsPart_);
|
1012
|
+
}
|
1013
|
+
|
1014
|
+
function _updateDelegatorsRewardPool(address neutral_, uint256 amount_) private {
|
1015
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
1016
|
+
uint256 delegatedStake_ = $.neutrals[neutral_].delegatedStake;
|
1017
|
+
|
1018
|
+
$.delegatorsRewardPools[neutral_].cumulativeSum += Math.mulDiv(
|
1019
|
+
amount_,
|
1020
|
+
PRECISION,
|
1021
|
+
delegatedStake_
|
1022
|
+
);
|
1023
|
+
}
|
1024
|
+
|
1025
|
+
function _updateDelegatorOwedValue(address delegator_, address neutral_) private {
|
1026
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
1027
|
+
DelegatorRewardPool storage rewardPool = $.delegatorsRewardPools[neutral_];
|
1028
|
+
Delegation storage delegation = $.delegations[delegator_][neutral_];
|
1029
|
+
|
1030
|
+
uint256 delegatedAmount_ = delegation.delegationAmount;
|
1031
|
+
uint256 poolCummulativeSum_ = rewardPool.cumulativeSum;
|
1032
|
+
uint256 delegatorCummulativeSum_ = delegation.cumulativeSum;
|
1033
|
+
|
1034
|
+
delegation.owedValue += Math.mulDiv(
|
1035
|
+
delegatedAmount_,
|
1036
|
+
poolCummulativeSum_ - delegatorCummulativeSum_,
|
1037
|
+
PRECISION
|
1038
|
+
);
|
1039
|
+
|
1040
|
+
delegation.cumulativeSum = rewardPool.cumulativeSum;
|
1041
|
+
}
|
1042
|
+
|
1043
|
+
function _distributeAllDelegatorRewards(
|
1044
|
+
address delegator_,
|
1045
|
+
address neutral_
|
1046
|
+
) private returns (uint256) {
|
1047
|
+
_updateDelegatorOwedValue(delegator_, neutral_);
|
1048
|
+
|
1049
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
1050
|
+
Delegation storage delegation = $.delegations[delegator_][neutral_];
|
1051
|
+
|
1052
|
+
uint256 amount_ = delegation.owedValue;
|
1053
|
+
|
1054
|
+
if (amount_ == 0) revert Errors.NoRewardsAvailible(delegator_);
|
1055
|
+
|
1056
|
+
delete delegation.owedValue;
|
1057
|
+
|
1058
|
+
return amount_;
|
1059
|
+
}
|
1060
|
+
|
1061
|
+
function _manipulateNeutralState(address neutral_) private {
|
1062
|
+
if (isNeutral(neutral_)) {
|
1063
|
+
_removeFromActiveQueue(neutral_);
|
1064
|
+
} else {
|
1065
|
+
_removeFromStandby(neutral_);
|
1066
|
+
}
|
1067
|
+
|
1068
|
+
_addToStandby(neutral_);
|
1069
|
+
_tryActivateFromStandby();
|
1070
|
+
|
1071
|
+
_recalculateNeutralWeight(neutral_);
|
1072
|
+
}
|
1073
|
+
|
1074
|
+
function _tryActivateFromStandby() private {
|
1075
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
1076
|
+
|
1077
|
+
if ($.standbyNeutrals.length() == 0) return;
|
1078
|
+
_tryActivateNeutral($.standbyNeutrals.topValue());
|
1079
|
+
}
|
1080
|
+
|
1081
|
+
function _requireDAOSLC() private view {
|
1082
|
+
if (msg.sender != _getDAOSLC()) {
|
1083
|
+
revert Errors.SenderExpectedToBeDAOSLC(msg.sender);
|
1084
|
+
}
|
1085
|
+
}
|
1086
|
+
|
1087
|
+
function _checkDelegation(address neutral_, uint256 amount_) private view {
|
1088
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
1089
|
+
NeutralsInfo storage neutralInfo_ = $.neutrals[neutral_];
|
1090
|
+
|
1091
|
+
uint256 neutralsStake_ = neutralInfo_.selfStake;
|
1092
|
+
uint256 maxDelegationAmplification_ = _getMaxDelegationAmplification();
|
1093
|
+
uint256 expectedDelegationAmount_ = neutralInfo_.delegatedStake + amount_;
|
1094
|
+
uint256 allowedDelegationAmount_ = maxDelegationAmplification_ * neutralsStake_;
|
1095
|
+
|
1096
|
+
if (neutralInfo_.selfStake == 0) revert NeutralHasNoStake(neutral_);
|
1097
|
+
|
1098
|
+
if (expectedDelegationAmount_ >= allowedDelegationAmount_)
|
1099
|
+
revert MaxDelegationAmplificationConstraintViolated(
|
1100
|
+
amount_,
|
1101
|
+
expectedDelegationAmount_,
|
1102
|
+
allowedDelegationAmount_
|
1103
|
+
);
|
1104
|
+
}
|
1105
|
+
|
1106
|
+
function _requireGovernance() private view {
|
1107
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
1108
|
+
if (msg.sender != address($.governance)) {
|
1109
|
+
revert Errors.SenderExpectedToBeGovernance(msg.sender);
|
1110
|
+
}
|
1111
|
+
}
|
1112
|
+
|
1113
|
+
function _requiredSyncedMaxActiveNeutrals() private view {
|
1114
|
+
NeutralsRegistryStorage storage $ = _getNeutralsRegistryStorage();
|
1115
|
+
if ($.maxActiveNeutrals != _getMaxActiveNeutrals()) {
|
1116
|
+
revert Errors.ParameterIsNotSynced(MAX_ACTIVE_NEUTRALS_PARAM_NAME);
|
1117
|
+
}
|
1118
|
+
}
|
1119
|
+
|
1120
|
+
function _getNeutralsRegistryStorage()
|
1121
|
+
private
|
1122
|
+
pure
|
1123
|
+
returns (NeutralsRegistryStorage storage $)
|
1124
|
+
{
|
1125
|
+
/* solhint-disable-next-line no-inline-assembly */
|
1126
|
+
assembly ("memory-safe") {
|
1127
|
+
$.slot := NEUTRALS_REGISTRY_STORAGE_LOCATION
|
1128
|
+
}
|
1129
|
+
}
|
1130
|
+
}
|