@web3dotorg/evm-slc-core-contracts 0.3.14 → 0.3.16
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 +23 -0
- package/governance/Governance.sol +23 -242
- package/governance/NeutralsRegistry.sol +1 -1
- package/libs/GovernanceUtils.sol +304 -0
- package/package.json +1 -1
- package/utils/Globals.sol +24 -0
|
@@ -41,6 +41,7 @@ contract ExecutorsRegistry is IExecutorsRegistry, ERC165, UUPSUpgradeable, AValu
|
|
|
41
41
|
bool isApproved;
|
|
42
42
|
bool isActive;
|
|
43
43
|
bool isStandby;
|
|
44
|
+
string externalLink;
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
struct WithdrawalAnnouncement {
|
|
@@ -320,6 +321,19 @@ contract ExecutorsRegistry is IExecutorsRegistry, ERC165, UUPSUpgradeable, AValu
|
|
|
320
321
|
emit RewardsClaimed(msg.sender, rewards_);
|
|
321
322
|
}
|
|
322
323
|
|
|
324
|
+
/**
|
|
325
|
+
* @notice Set an external link for the executor (e.g., profile or info page)
|
|
326
|
+
* @param externalLink_ The external link string
|
|
327
|
+
*/
|
|
328
|
+
function setExternalLink(string calldata externalLink_) external {
|
|
329
|
+
ExecutorsRegistryStorage storage $ = _getExecutorsRegistryStorage();
|
|
330
|
+
ExecutorInfo storage executorInfo = $.executors[msg.sender];
|
|
331
|
+
|
|
332
|
+
if (executorInfo.stake == 0) revert ExecutorHasNoStake(msg.sender);
|
|
333
|
+
|
|
334
|
+
executorInfo.externalLink = externalLink_;
|
|
335
|
+
}
|
|
336
|
+
|
|
323
337
|
/**
|
|
324
338
|
* @notice Synchronize local maxActiveExecutors parameter with the one in the registry
|
|
325
339
|
*/
|
|
@@ -362,6 +376,15 @@ contract ExecutorsRegistry is IExecutorsRegistry, ERC165, UUPSUpgradeable, AValu
|
|
|
362
376
|
return _getExecutorsRegistryStorage().executors[executor_];
|
|
363
377
|
}
|
|
364
378
|
|
|
379
|
+
/**
|
|
380
|
+
* @notice Get the external link set by an executor
|
|
381
|
+
* @param executor_ Address of the executor
|
|
382
|
+
* @return The external link string
|
|
383
|
+
*/
|
|
384
|
+
function getExternalLink(address executor_) external view returns (string memory) {
|
|
385
|
+
return _getExecutorsRegistryStorage().executors[executor_].externalLink;
|
|
386
|
+
}
|
|
387
|
+
|
|
365
388
|
/**
|
|
366
389
|
* @notice Get active executors list with pagination
|
|
367
390
|
* @param offset_ Starting index
|
|
@@ -22,11 +22,13 @@ import {IGovernance} from "../interfaces/IGovernance.sol";
|
|
|
22
22
|
import {IQParameters} from "../interfaces/IQParameters.sol";
|
|
23
23
|
import {INeutralsRegistry} from "../interfaces/INeutralsRegistry.sol";
|
|
24
24
|
import {IExecutorsRegistry} from "../interfaces/IExecutorsRegistry.sol";
|
|
25
|
+
import {GovernanceUtils} from "../libs/GovernanceUtils.sol";
|
|
25
26
|
import {DAOSLC_PARAM_NAME, TREASURY_ADDRESS_PARAM_NAME} from "../utils/Globals.sol";
|
|
26
27
|
|
|
28
|
+
import * as Globals from "../utils/Globals.sol";
|
|
29
|
+
|
|
27
30
|
contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
|
|
28
31
|
using EnumerableSet for EnumerableSet.AddressSet;
|
|
29
|
-
|
|
30
32
|
using SafeERC20 for IWrappedToken;
|
|
31
33
|
|
|
32
34
|
// keccak256(abi.encode(uint256(keccak256("evm-slc-core.storage.Governance")) - 1)) & ~bytes32(uint256(0xff))
|
|
@@ -36,30 +38,6 @@ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
|
|
|
36
38
|
bytes32 private constant GOVERNOR_STORAGE_LOCATION =
|
|
37
39
|
0x7c712897014dbe49c045ef1299aa2d5f9e67e48eea4403efa21f1e0f3ac0cb00;
|
|
38
40
|
|
|
39
|
-
string private constant VOTING_DELAY_PARAM_NAME = "slc.governance.votingDelay";
|
|
40
|
-
|
|
41
|
-
// solhint-disable-next-line gas-small-strings
|
|
42
|
-
string private constant NEUTRALS_VOTING_PERIOD_PARAM_NAME =
|
|
43
|
-
"slc.governance.neutralsVotingPeriod";
|
|
44
|
-
|
|
45
|
-
// solhint-disable-next-line gas-small-strings
|
|
46
|
-
string private constant EXECUTORS_VOTING_PERIOD_PARAM_NAME =
|
|
47
|
-
"slc.governance.executorsVotingPeriod";
|
|
48
|
-
|
|
49
|
-
// solhint-disable-next-line gas-small-strings
|
|
50
|
-
string private constant NEUTRALS_THRESHOLD_PARAM_NAME = "slc.governance.neutralsThreshold";
|
|
51
|
-
|
|
52
|
-
// solhint-disable-next-line gas-small-strings
|
|
53
|
-
string private constant EXECUTORS_THRESHOLD_PARAM_NAME = "slc.governance.executorsThreshold";
|
|
54
|
-
|
|
55
|
-
string private constant EXECUTORS_SHARE_PARAM_NAME = "slc.governance.executorsShare";
|
|
56
|
-
|
|
57
|
-
string private constant NEUTRALS_SHARE_PARAM_NAME = "slc.governance.neutralsShare";
|
|
58
|
-
|
|
59
|
-
string private constant TREASURY_SHARE_PARAM_NAME = "slc.governance.treasuryShare";
|
|
60
|
-
|
|
61
|
-
string private constant MIN_SERVICE_FEE_PARAM_NAME = "slc.governance.minServiceFee";
|
|
62
|
-
|
|
63
41
|
enum VotingPhase {
|
|
64
42
|
None,
|
|
65
43
|
Neutrals,
|
|
@@ -89,6 +67,7 @@ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
|
|
|
89
67
|
string additionalLink;
|
|
90
68
|
EnumerableSet.AddressSet selectedNeutrals;
|
|
91
69
|
bytes data;
|
|
70
|
+
uint256 createdAt;
|
|
92
71
|
VotingPhase votingPhase;
|
|
93
72
|
bytes32 leadingOperationHash;
|
|
94
73
|
// Proposals
|
|
@@ -104,6 +83,7 @@ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
|
|
|
104
83
|
string additionalLink;
|
|
105
84
|
address[] selectedNeutrals;
|
|
106
85
|
bytes data;
|
|
86
|
+
uint256 createdAt;
|
|
107
87
|
VotingPhase votingPhase;
|
|
108
88
|
bytes32 leadingOperationHash;
|
|
109
89
|
// Proposals
|
|
@@ -214,6 +194,7 @@ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
|
|
|
214
194
|
requestState.neutralsNumber = neutralsNumber_;
|
|
215
195
|
requestState.additionalLink = additionalLink_;
|
|
216
196
|
requestState.data = data_;
|
|
197
|
+
requestState.createdAt = block.timestamp;
|
|
217
198
|
for (uint256 i = 0; i < selectedNeutrals_.length; ++i) {
|
|
218
199
|
requestState.selectedNeutrals.add(selectedNeutrals_[i]);
|
|
219
200
|
}
|
|
@@ -229,41 +210,7 @@ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
|
|
|
229
210
|
uint256 proposalId_,
|
|
230
211
|
OperationBundle calldata operationBundle_
|
|
231
212
|
) external {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
ServiceRequestState storage requestState = $
|
|
235
|
-
.slcGovernanceState[$.proposalState[proposalId_].slcCore]
|
|
236
|
-
.requestState[$.proposalState[proposalId_].requestId];
|
|
237
|
-
|
|
238
|
-
require(
|
|
239
|
-
requestState.votingPhase == VotingPhase.Neutrals,
|
|
240
|
-
InvalidVotingPhaseForProposeOperations(requestState.votingPhase)
|
|
241
|
-
);
|
|
242
|
-
|
|
243
|
-
require(
|
|
244
|
-
requestState.selectedNeutrals.contains(msg.sender),
|
|
245
|
-
AccountNotSelected(msg.sender)
|
|
246
|
-
);
|
|
247
|
-
|
|
248
|
-
SLCProposalState storage proposalState = $.proposalState[proposalId_];
|
|
249
|
-
|
|
250
|
-
bytes32 operationHash_ = hashOperation(operationBundle_);
|
|
251
|
-
|
|
252
|
-
bytes32 previousOperationHash_ = proposalState.neutralToOperation[msg.sender];
|
|
253
|
-
if (previousOperationHash_ != bytes32(0)) {
|
|
254
|
-
proposalState.operationByNeutral[previousOperationHash_].remove(msg.sender);
|
|
255
|
-
|
|
256
|
-
if (proposalState.operationByNeutral[previousOperationHash_].length() == 0) {
|
|
257
|
-
_removeOperation(proposalState, previousOperationHash_);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
proposalState.operationByNeutral[operationHash_].add(msg.sender);
|
|
262
|
-
proposalState.neutralToOperation[msg.sender] = operationHash_;
|
|
263
|
-
|
|
264
|
-
_addOperation(proposalState, operationBundle_, operationHash_);
|
|
265
|
-
|
|
266
|
-
proposalState.operationBundleByHash[operationHash_] = operationBundle_;
|
|
213
|
+
GovernanceUtils.handleProposeOperations(proposalId_, operationBundle_);
|
|
267
214
|
}
|
|
268
215
|
|
|
269
216
|
function implementation() external view returns (address) {
|
|
@@ -391,6 +338,7 @@ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
|
|
|
391
338
|
additionalLink: requestState.additionalLink,
|
|
392
339
|
selectedNeutrals: requestState.selectedNeutrals.values(),
|
|
393
340
|
data: requestState.data,
|
|
341
|
+
createdAt: requestState.createdAt,
|
|
394
342
|
votingPhase: requestState.votingPhase,
|
|
395
343
|
leadingOperationHash: requestState.leadingOperationHash,
|
|
396
344
|
requestNonce: requestState.proposalNonce,
|
|
@@ -446,7 +394,7 @@ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
|
|
|
446
394
|
* @inheritdoc IGovernor
|
|
447
395
|
*/
|
|
448
396
|
function votingDelay() public view override returns (uint256) {
|
|
449
|
-
return _getGovernanceStorage().parametersRegistry.getUint(VOTING_DELAY_PARAM_NAME);
|
|
397
|
+
return _getGovernanceStorage().parametersRegistry.getUint(Globals.VOTING_DELAY_PARAM_NAME);
|
|
450
398
|
}
|
|
451
399
|
|
|
452
400
|
/**
|
|
@@ -473,12 +421,16 @@ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
|
|
|
473
421
|
|
|
474
422
|
function getNeutralsVotingPeriod() public view returns (uint256) {
|
|
475
423
|
return
|
|
476
|
-
_getGovernanceStorage().parametersRegistry.getUint(
|
|
424
|
+
_getGovernanceStorage().parametersRegistry.getUint(
|
|
425
|
+
Globals.NEUTRALS_VOTING_PERIOD_PARAM_NAME
|
|
426
|
+
);
|
|
477
427
|
}
|
|
478
428
|
|
|
479
429
|
function getExecutorsVotingPeriod() public view returns (uint256) {
|
|
480
430
|
return
|
|
481
|
-
_getGovernanceStorage().parametersRegistry.getUint(
|
|
431
|
+
_getGovernanceStorage().parametersRegistry.getUint(
|
|
432
|
+
Globals.EXECUTORS_VOTING_PERIOD_PARAM_NAME
|
|
433
|
+
);
|
|
482
434
|
}
|
|
483
435
|
|
|
484
436
|
/**
|
|
@@ -486,7 +438,7 @@ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
|
|
|
486
438
|
*/
|
|
487
439
|
function getNeutralsThreshold() public view returns (uint256) {
|
|
488
440
|
uint256 neutralsThreshold_ = _getGovernanceStorage().parametersRegistry.getUint(
|
|
489
|
-
NEUTRALS_THRESHOLD_PARAM_NAME
|
|
441
|
+
Globals.NEUTRALS_THRESHOLD_PARAM_NAME
|
|
490
442
|
);
|
|
491
443
|
|
|
492
444
|
if (neutralsThreshold_ > 100) {
|
|
@@ -501,7 +453,7 @@ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
|
|
|
501
453
|
*/
|
|
502
454
|
function getExecutorsThreshold() public view returns (uint256) {
|
|
503
455
|
uint256 executorsThreshold_ = _getGovernanceStorage().parametersRegistry.getUint(
|
|
504
|
-
EXECUTORS_THRESHOLD_PARAM_NAME
|
|
456
|
+
Globals.EXECUTORS_THRESHOLD_PARAM_NAME
|
|
505
457
|
);
|
|
506
458
|
|
|
507
459
|
if (executorsThreshold_ > 100) {
|
|
@@ -525,50 +477,7 @@ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
|
|
|
525
477
|
uint256 proposalId_,
|
|
526
478
|
address[] memory selectedNeutrals_
|
|
527
479
|
) internal {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
ServiceRequestState storage requestState = $
|
|
531
|
-
.slcGovernanceState[$.proposalState[proposalId_].slcCore]
|
|
532
|
-
.requestState[$.proposalState[proposalId_].requestId];
|
|
533
|
-
|
|
534
|
-
uint256 serviceFee_ = requestState.serviceFee;
|
|
535
|
-
if (serviceFee_ == 0 && msg.value == 0) return;
|
|
536
|
-
|
|
537
|
-
if (msg.value > 0) {
|
|
538
|
-
require(serviceFee_ == msg.value, InvalidServiceFee(serviceFee_, msg.value));
|
|
539
|
-
$.token.deposit{value: msg.value}();
|
|
540
|
-
} else {
|
|
541
|
-
$.token.safeTransferFrom(requestState.requester, address(this), serviceFee_);
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
uint256 executorsShare_ = _getExecutorsShare();
|
|
545
|
-
uint256 neutralsShare_ = _getNeutralsShare();
|
|
546
|
-
uint256 treasuryShare_ = _getTreasuryShare();
|
|
547
|
-
|
|
548
|
-
// Validate shares total 100%
|
|
549
|
-
uint256 totalShares_ = executorsShare_ + neutralsShare_ + treasuryShare_;
|
|
550
|
-
require(totalShares_ == 100, InvalidSharesTotal(totalShares_));
|
|
551
|
-
|
|
552
|
-
// Calculate amounts
|
|
553
|
-
uint256 executorsAmount_ = (serviceFee_ * executorsShare_) / 100;
|
|
554
|
-
uint256 neutralsAmount_ = (serviceFee_ * neutralsShare_) / 100;
|
|
555
|
-
uint256 treasuryAmount_ = (serviceFee_ * treasuryShare_) / 100;
|
|
556
|
-
|
|
557
|
-
// Distribute to executors
|
|
558
|
-
if (executorsAmount_ > 0) {
|
|
559
|
-
$.executorsRegistry.distributeRewards(executorsAmount_);
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
// Distribute to neutrals
|
|
563
|
-
if (neutralsAmount_ > 0) {
|
|
564
|
-
$.neutralsRegistry.distributeRewards(selectedNeutrals_, neutralsAmount_);
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
// Send to treasury
|
|
568
|
-
if (treasuryAmount_ > 0) {
|
|
569
|
-
address treasury_ = _getTreasuryAddress();
|
|
570
|
-
$.token.safeTransfer(treasury_, treasuryAmount_);
|
|
571
|
-
}
|
|
480
|
+
GovernanceUtils.distributeServiceFeeRewards(proposalId_, selectedNeutrals_);
|
|
572
481
|
}
|
|
573
482
|
|
|
574
483
|
function _executeOperations(
|
|
@@ -578,34 +487,7 @@ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
|
|
|
578
487
|
bytes[] memory /*calldatas*/,
|
|
579
488
|
bytes32 /*descriptionHash*/
|
|
580
489
|
) internal override {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
ServiceRequestState storage requestState = $
|
|
584
|
-
.slcGovernanceState[$.proposalState[proposalId].slcCore]
|
|
585
|
-
.requestState[$.proposalState[proposalId].requestId];
|
|
586
|
-
|
|
587
|
-
bytes32 leadingOperationHash_ = requestState.leadingOperationHash;
|
|
588
|
-
if (leadingOperationHash_ == bytes32(0)) {
|
|
589
|
-
revert Unreachable();
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
OperationBundle storage leadingOperationBundle_ = $
|
|
593
|
-
.proposalState[proposalId]
|
|
594
|
-
.operationBundleByHash[leadingOperationHash_];
|
|
595
|
-
|
|
596
|
-
ISLCCore slcCore_ = ISLCCore(targets[0]);
|
|
597
|
-
|
|
598
|
-
uint256 operationsLength_ = leadingOperationBundle_.operations.length;
|
|
599
|
-
for (uint256 i = 0; i < operationsLength_; ++i) {
|
|
600
|
-
slcCore_.arbitraryExecute(
|
|
601
|
-
leadingOperationBundle_.operations[i].operation_,
|
|
602
|
-
leadingOperationBundle_.operations[i].to_,
|
|
603
|
-
leadingOperationBundle_.operations[i].data_,
|
|
604
|
-
leadingOperationBundle_.operations[i].value_
|
|
605
|
-
);
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
requestState.votingPhase = VotingPhase.Executed;
|
|
490
|
+
GovernanceUtils.executeOperations(proposalId, targets);
|
|
609
491
|
}
|
|
610
492
|
|
|
611
493
|
/**
|
|
@@ -710,27 +592,12 @@ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
|
|
|
710
592
|
}
|
|
711
593
|
|
|
712
594
|
function _getDAOSLC() internal view returns (address) {
|
|
713
|
-
return _getGovernanceStorage().parametersRegistry.getAddr(DAOSLC_PARAM_NAME);
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
function _getExecutorsShare() internal view returns (uint256) {
|
|
717
|
-
return _getGovernanceStorage().parametersRegistry.getUint(EXECUTORS_SHARE_PARAM_NAME);
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
function _getNeutralsShare() internal view returns (uint256) {
|
|
721
|
-
return _getGovernanceStorage().parametersRegistry.getUint(NEUTRALS_SHARE_PARAM_NAME);
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
function _getTreasuryShare() internal view returns (uint256) {
|
|
725
|
-
return _getGovernanceStorage().parametersRegistry.getUint(TREASURY_SHARE_PARAM_NAME);
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
function _getTreasuryAddress() internal view returns (address) {
|
|
729
|
-
return _getGovernanceStorage().parametersRegistry.getAddr(TREASURY_ADDRESS_PARAM_NAME);
|
|
595
|
+
return _getGovernanceStorage().parametersRegistry.getAddr(Globals.DAOSLC_PARAM_NAME);
|
|
730
596
|
}
|
|
731
597
|
|
|
732
598
|
function _getMinServiceFee() internal view returns (uint256) {
|
|
733
|
-
return
|
|
599
|
+
return
|
|
600
|
+
_getGovernanceStorage().parametersRegistry.getUint(Globals.MIN_SERVICE_FEE_PARAM_NAME);
|
|
734
601
|
}
|
|
735
602
|
|
|
736
603
|
function _makeProposal(
|
|
@@ -786,93 +653,7 @@ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
|
|
|
786
653
|
}
|
|
787
654
|
|
|
788
655
|
function _fillProposalState(uint256 proposalId_) private {
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
ServiceRequestState storage requestState = $
|
|
792
|
-
.slcGovernanceState[$.proposalState[proposalId_].slcCore]
|
|
793
|
-
.requestState[$.proposalState[proposalId_].requestId];
|
|
794
|
-
|
|
795
|
-
uint256 neutralsLength_ = requestState.selectedNeutrals.length();
|
|
796
|
-
|
|
797
|
-
$.proposalState[proposalId_].neutralsThreshold = Math.mulDiv(
|
|
798
|
-
neutralsLength_,
|
|
799
|
-
getNeutralsThreshold(),
|
|
800
|
-
100,
|
|
801
|
-
Math.Rounding.Ceil
|
|
802
|
-
);
|
|
803
|
-
|
|
804
|
-
uint256 executorsCount_ = $.executorsRegistry.getExecutorsCount();
|
|
805
|
-
$.proposalState[proposalId_].executorsThreshold = Math.mulDiv(
|
|
806
|
-
executorsCount_,
|
|
807
|
-
getExecutorsThreshold(),
|
|
808
|
-
100,
|
|
809
|
-
Math.Rounding.Ceil
|
|
810
|
-
);
|
|
811
|
-
|
|
812
|
-
requestState.proposalIds.push(proposalId_);
|
|
813
|
-
|
|
814
|
-
requestState.votingPhase = VotingPhase.Neutrals;
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
function _addOperation(
|
|
818
|
-
SLCProposalState storage proposalState,
|
|
819
|
-
OperationBundle memory operationBundle_,
|
|
820
|
-
bytes32 operationHash_
|
|
821
|
-
) private returns (bool) {
|
|
822
|
-
if (!_containsOperation(proposalState, operationHash_)) {
|
|
823
|
-
proposalState.operationBundles.push(operationBundle_);
|
|
824
|
-
// The value is stored at length-1, but we add 1 to all indexes
|
|
825
|
-
// and use 0 as a sentinel value
|
|
826
|
-
proposalState.operationBundlePosition[operationHash_] = proposalState
|
|
827
|
-
.operationBundles
|
|
828
|
-
.length;
|
|
829
|
-
return true;
|
|
830
|
-
} else {
|
|
831
|
-
return false;
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
function _removeOperation(
|
|
836
|
-
SLCProposalState storage proposalState,
|
|
837
|
-
bytes32 operationHash_
|
|
838
|
-
) private returns (bool) {
|
|
839
|
-
// We cache the value's position to prevent multiple reads from the same storage slot
|
|
840
|
-
uint256 position_ = proposalState.operationBundlePosition[operationHash_];
|
|
841
|
-
|
|
842
|
-
if (position_ != 0) {
|
|
843
|
-
// Equivalent to contains(set, value)
|
|
844
|
-
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
|
|
845
|
-
// the array, and then remove the last element (sometimes called as 'swap and pop').
|
|
846
|
-
|
|
847
|
-
uint256 valueIndex_ = position_ - 1;
|
|
848
|
-
uint256 lastIndex_ = proposalState.operationBundles.length - 1;
|
|
849
|
-
|
|
850
|
-
if (valueIndex_ != lastIndex_) {
|
|
851
|
-
OperationBundle storage lastValue = proposalState.operationBundles[lastIndex_];
|
|
852
|
-
|
|
853
|
-
// Move the lastValue to the index where the value to delete is
|
|
854
|
-
proposalState.operationBundles[valueIndex_] = lastValue;
|
|
855
|
-
// Update the tracked position of the lastValue (that was just moved)
|
|
856
|
-
proposalState.operationBundlePosition[hashOperation(lastValue)] = position_;
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
// Delete the slot where the moved value was stored
|
|
860
|
-
proposalState.operationBundles.pop();
|
|
861
|
-
|
|
862
|
-
// Delete the tracked position for the deleted slot
|
|
863
|
-
delete proposalState.operationBundlePosition[operationHash_];
|
|
864
|
-
|
|
865
|
-
return true;
|
|
866
|
-
} else {
|
|
867
|
-
return false;
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
function _containsOperation(
|
|
872
|
-
SLCProposalState storage proposalState,
|
|
873
|
-
bytes32 operationHash_
|
|
874
|
-
) private view returns (bool) {
|
|
875
|
-
return proposalState.operationBundlePosition[operationHash_] != 0;
|
|
656
|
+
GovernanceUtils.fillProposalState(proposalId_);
|
|
876
657
|
}
|
|
877
658
|
|
|
878
659
|
function _findLeadingOperation(uint256 proposalId_) private view returns (bytes32) {
|
|
@@ -287,7 +287,7 @@ contract NeutralsRegistry is INeutralsRegistry, ERC165, UUPSUpgradeable {
|
|
|
287
287
|
|
|
288
288
|
uint256 amount_ = announcement.amount;
|
|
289
289
|
|
|
290
|
-
delete $.delegations[
|
|
290
|
+
delete $.delegations[msg.sender][neutral_].withdrawal;
|
|
291
291
|
|
|
292
292
|
if ($.delegations[msg.sender][neutral_].delegatedAmount == 0) {
|
|
293
293
|
$.neutralsPerDelegator[msg.sender].remove(neutral_);
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.28;
|
|
3
|
+
|
|
4
|
+
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
|
|
5
|
+
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
6
|
+
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
|
|
7
|
+
import {GovernorUpgradeable} from "@openzeppelin/contracts-upgradeable/governance/GovernorUpgradeable.sol";
|
|
8
|
+
|
|
9
|
+
import {ISLCCore} from "../interfaces/ISLCCore.sol";
|
|
10
|
+
import {Governance} from "../governance/Governance.sol";
|
|
11
|
+
import {IGovernance} from "../interfaces/IGovernance.sol";
|
|
12
|
+
import {IWrappedToken} from "../interfaces/IWrappedToken.sol";
|
|
13
|
+
import * as Globals from "../utils/Globals.sol";
|
|
14
|
+
|
|
15
|
+
library GovernanceUtils {
|
|
16
|
+
using EnumerableSet for EnumerableSet.AddressSet;
|
|
17
|
+
using SafeERC20 for IWrappedToken;
|
|
18
|
+
|
|
19
|
+
bytes32 private constant GOVERNANCE_STORAGE_LOCATION =
|
|
20
|
+
0xb93e2841340255f69de4f9300770e3cc1894dc081e8bf6d34a78e9ac897c7900;
|
|
21
|
+
|
|
22
|
+
bytes32 private constant GOVERNOR_STORAGE_LOCATION =
|
|
23
|
+
0x7c712897014dbe49c045ef1299aa2d5f9e67e48eea4403efa21f1e0f3ac0cb00;
|
|
24
|
+
|
|
25
|
+
function handleProposeOperations(
|
|
26
|
+
uint256 proposalId_,
|
|
27
|
+
IGovernance.OperationBundle calldata operationBundle_
|
|
28
|
+
) external {
|
|
29
|
+
Governance.GovernanceStorage storage $ = _getGovernanceStorage();
|
|
30
|
+
|
|
31
|
+
Governance.ServiceRequestState storage requestState = $
|
|
32
|
+
.slcGovernanceState[$.proposalState[proposalId_].slcCore]
|
|
33
|
+
.requestState[$.proposalState[proposalId_].requestId];
|
|
34
|
+
|
|
35
|
+
require(
|
|
36
|
+
requestState.votingPhase == Governance.VotingPhase.Neutrals,
|
|
37
|
+
Governance.InvalidVotingPhaseForProposeOperations(requestState.votingPhase)
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
require(
|
|
41
|
+
requestState.selectedNeutrals.contains(msg.sender),
|
|
42
|
+
Governance.AccountNotSelected(msg.sender)
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
Governance.SLCProposalState storage proposalState = $.proposalState[proposalId_];
|
|
46
|
+
|
|
47
|
+
bytes32 operationHash_ = hashOperation(operationBundle_);
|
|
48
|
+
|
|
49
|
+
bytes32 previousOperationHash_ = proposalState.neutralToOperation[msg.sender];
|
|
50
|
+
if (previousOperationHash_ != bytes32(0)) {
|
|
51
|
+
proposalState.operationByNeutral[previousOperationHash_].remove(msg.sender);
|
|
52
|
+
|
|
53
|
+
if (proposalState.operationByNeutral[previousOperationHash_].length() == 0) {
|
|
54
|
+
_removeOperation(proposalState, previousOperationHash_);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
proposalState.operationByNeutral[operationHash_].add(msg.sender);
|
|
59
|
+
proposalState.neutralToOperation[msg.sender] = operationHash_;
|
|
60
|
+
|
|
61
|
+
_addOperation(proposalState, operationBundle_, operationHash_);
|
|
62
|
+
|
|
63
|
+
proposalState.operationBundleByHash[operationHash_] = operationBundle_;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function fillProposalState(uint256 proposalId_) external {
|
|
67
|
+
Governance.GovernanceStorage storage $ = _getGovernanceStorage();
|
|
68
|
+
|
|
69
|
+
Governance.ServiceRequestState storage requestState = $
|
|
70
|
+
.slcGovernanceState[$.proposalState[proposalId_].slcCore]
|
|
71
|
+
.requestState[$.proposalState[proposalId_].requestId];
|
|
72
|
+
|
|
73
|
+
uint256 neutralsLength_ = requestState.selectedNeutrals.length();
|
|
74
|
+
|
|
75
|
+
$.proposalState[proposalId_].neutralsThreshold = Math.mulDiv(
|
|
76
|
+
neutralsLength_,
|
|
77
|
+
getNeutralsThreshold(),
|
|
78
|
+
100,
|
|
79
|
+
Math.Rounding.Ceil
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
uint256 executorsCount_ = $.executorsRegistry.getExecutorsCount();
|
|
83
|
+
$.proposalState[proposalId_].executorsThreshold = Math.mulDiv(
|
|
84
|
+
executorsCount_,
|
|
85
|
+
getExecutorsThreshold(),
|
|
86
|
+
100,
|
|
87
|
+
Math.Rounding.Ceil
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
requestState.proposalIds.push(proposalId_);
|
|
91
|
+
|
|
92
|
+
requestState.votingPhase = Governance.VotingPhase.Neutrals;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function distributeServiceFeeRewards(
|
|
96
|
+
uint256 proposalId_,
|
|
97
|
+
address[] memory selectedNeutrals_
|
|
98
|
+
) external {
|
|
99
|
+
Governance.GovernanceStorage storage $ = _getGovernanceStorage();
|
|
100
|
+
|
|
101
|
+
Governance.ServiceRequestState storage requestState = $
|
|
102
|
+
.slcGovernanceState[$.proposalState[proposalId_].slcCore]
|
|
103
|
+
.requestState[$.proposalState[proposalId_].requestId];
|
|
104
|
+
|
|
105
|
+
uint256 serviceFee_ = requestState.serviceFee;
|
|
106
|
+
if (serviceFee_ == 0 && msg.value == 0) return;
|
|
107
|
+
|
|
108
|
+
if (msg.value > 0) {
|
|
109
|
+
require(
|
|
110
|
+
serviceFee_ == msg.value,
|
|
111
|
+
Governance.InvalidServiceFee(serviceFee_, msg.value)
|
|
112
|
+
);
|
|
113
|
+
$.token.deposit{value: msg.value}();
|
|
114
|
+
} else {
|
|
115
|
+
$.token.safeTransferFrom(requestState.requester, address(this), serviceFee_);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
uint256 executorsShare_ = getExecutorsShare();
|
|
119
|
+
uint256 neutralsShare_ = getNeutralsShare();
|
|
120
|
+
uint256 treasuryShare_ = getTreasuryShare();
|
|
121
|
+
|
|
122
|
+
// Validate shares total 100%
|
|
123
|
+
uint256 totalShares_ = executorsShare_ + neutralsShare_ + treasuryShare_;
|
|
124
|
+
require(totalShares_ == 100, Governance.InvalidSharesTotal(totalShares_));
|
|
125
|
+
|
|
126
|
+
// Calculate amounts
|
|
127
|
+
uint256 executorsAmount_ = (serviceFee_ * executorsShare_) / 100;
|
|
128
|
+
uint256 neutralsAmount_ = (serviceFee_ * neutralsShare_) / 100;
|
|
129
|
+
uint256 treasuryAmount_ = (serviceFee_ * treasuryShare_) / 100;
|
|
130
|
+
|
|
131
|
+
// Distribute to executors
|
|
132
|
+
if (executorsAmount_ > 0) {
|
|
133
|
+
$.executorsRegistry.distributeRewards(executorsAmount_);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Distribute to neutrals
|
|
137
|
+
if (neutralsAmount_ > 0) {
|
|
138
|
+
$.neutralsRegistry.distributeRewards(selectedNeutrals_, neutralsAmount_);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Send to treasury
|
|
142
|
+
if (treasuryAmount_ > 0) {
|
|
143
|
+
address treasury_ = getTreasuryAddress();
|
|
144
|
+
$.token.safeTransfer(treasury_, treasuryAmount_);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function executeOperations(uint256 proposalId, address[] memory targets) external {
|
|
149
|
+
Governance.GovernanceStorage storage $ = _getGovernanceStorage();
|
|
150
|
+
|
|
151
|
+
Governance.ServiceRequestState storage requestState = $
|
|
152
|
+
.slcGovernanceState[$.proposalState[proposalId].slcCore]
|
|
153
|
+
.requestState[$.proposalState[proposalId].requestId];
|
|
154
|
+
|
|
155
|
+
bytes32 leadingOperationHash_ = requestState.leadingOperationHash;
|
|
156
|
+
if (leadingOperationHash_ == bytes32(0)) {
|
|
157
|
+
revert Governance.Unreachable();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
IGovernance.OperationBundle storage leadingOperationBundle_ = $
|
|
161
|
+
.proposalState[proposalId]
|
|
162
|
+
.operationBundleByHash[leadingOperationHash_];
|
|
163
|
+
|
|
164
|
+
ISLCCore slcCore_ = ISLCCore(targets[0]);
|
|
165
|
+
|
|
166
|
+
uint256 operationsLength_ = leadingOperationBundle_.operations.length;
|
|
167
|
+
for (uint256 i = 0; i < operationsLength_; ++i) {
|
|
168
|
+
slcCore_.arbitraryExecute(
|
|
169
|
+
leadingOperationBundle_.operations[i].operation_,
|
|
170
|
+
leadingOperationBundle_.operations[i].to_,
|
|
171
|
+
leadingOperationBundle_.operations[i].data_,
|
|
172
|
+
leadingOperationBundle_.operations[i].value_
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
requestState.votingPhase = Governance.VotingPhase.Executed;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function hashOperation(
|
|
180
|
+
IGovernance.OperationBundle memory operation_
|
|
181
|
+
) public pure returns (bytes32) {
|
|
182
|
+
return keccak256(abi.encode(operation_));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function getExecutorsShare() public view returns (uint256) {
|
|
186
|
+
return
|
|
187
|
+
_getGovernanceStorage().parametersRegistry.getUint(Globals.EXECUTORS_SHARE_PARAM_NAME);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function getNeutralsShare() public view returns (uint256) {
|
|
191
|
+
return
|
|
192
|
+
_getGovernanceStorage().parametersRegistry.getUint(Globals.NEUTRALS_SHARE_PARAM_NAME);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function getTreasuryShare() public view returns (uint256) {
|
|
196
|
+
return
|
|
197
|
+
_getGovernanceStorage().parametersRegistry.getUint(Globals.TREASURY_SHARE_PARAM_NAME);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function getTreasuryAddress() public view returns (address) {
|
|
201
|
+
return
|
|
202
|
+
_getGovernanceStorage().parametersRegistry.getAddr(
|
|
203
|
+
Globals.TREASURY_ADDRESS_PARAM_NAME
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function getNeutralsThreshold() public view returns (uint256) {
|
|
208
|
+
uint256 neutralsThreshold_ = _getGovernanceStorage().parametersRegistry.getUint(
|
|
209
|
+
Globals.NEUTRALS_THRESHOLD_PARAM_NAME
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
if (neutralsThreshold_ > 100) {
|
|
213
|
+
revert Governance.NeutralsThresholdTooHigh(neutralsThreshold_);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return neutralsThreshold_;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function getExecutorsThreshold() public view returns (uint256) {
|
|
220
|
+
uint256 executorsThreshold_ = _getGovernanceStorage().parametersRegistry.getUint(
|
|
221
|
+
Globals.EXECUTORS_THRESHOLD_PARAM_NAME
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
if (executorsThreshold_ > 100) {
|
|
225
|
+
revert Governance.ExecutorsThresholdTooHigh(executorsThreshold_);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return executorsThreshold_;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function _addOperation(
|
|
232
|
+
Governance.SLCProposalState storage proposalState,
|
|
233
|
+
IGovernance.OperationBundle memory operationBundle_,
|
|
234
|
+
bytes32 operationHash_
|
|
235
|
+
) private returns (bool) {
|
|
236
|
+
if (!_containsOperation(proposalState, operationHash_)) {
|
|
237
|
+
proposalState.operationBundles.push(operationBundle_);
|
|
238
|
+
// The value is stored at length-1, but we add 1 to all indexes
|
|
239
|
+
// and use 0 as a sentinel value
|
|
240
|
+
proposalState.operationBundlePosition[operationHash_] = proposalState
|
|
241
|
+
.operationBundles
|
|
242
|
+
.length;
|
|
243
|
+
return true;
|
|
244
|
+
} else {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function _containsOperation(
|
|
250
|
+
Governance.SLCProposalState storage proposalState,
|
|
251
|
+
bytes32 operationHash_
|
|
252
|
+
) private view returns (bool) {
|
|
253
|
+
return proposalState.operationBundlePosition[operationHash_] != 0;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function _removeOperation(
|
|
257
|
+
Governance.SLCProposalState storage proposalState,
|
|
258
|
+
bytes32 operationHash_
|
|
259
|
+
) private returns (bool) {
|
|
260
|
+
// We cache the value's position to prevent multiple reads from the same storage slot
|
|
261
|
+
uint256 position_ = proposalState.operationBundlePosition[operationHash_];
|
|
262
|
+
|
|
263
|
+
if (position_ != 0) {
|
|
264
|
+
// Equivalent to contains(set, value)
|
|
265
|
+
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
|
|
266
|
+
// the array, and then remove the last element (sometimes called as 'swap and pop').
|
|
267
|
+
|
|
268
|
+
uint256 valueIndex_ = position_ - 1;
|
|
269
|
+
uint256 lastIndex_ = proposalState.operationBundles.length - 1;
|
|
270
|
+
|
|
271
|
+
if (valueIndex_ != lastIndex_) {
|
|
272
|
+
IGovernance.OperationBundle storage lastValue = proposalState.operationBundles[
|
|
273
|
+
lastIndex_
|
|
274
|
+
];
|
|
275
|
+
|
|
276
|
+
// Move the lastValue to the index where the value to delete is
|
|
277
|
+
proposalState.operationBundles[valueIndex_] = lastValue;
|
|
278
|
+
// Update the tracked position of the lastValue (that was just moved)
|
|
279
|
+
proposalState.operationBundlePosition[hashOperation(lastValue)] = position_;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Delete the slot where the moved value was stored
|
|
283
|
+
proposalState.operationBundles.pop();
|
|
284
|
+
|
|
285
|
+
// Delete the tracked position for the deleted slot
|
|
286
|
+
delete proposalState.operationBundlePosition[operationHash_];
|
|
287
|
+
|
|
288
|
+
return true;
|
|
289
|
+
} else {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function _getGovernanceStorage()
|
|
295
|
+
private
|
|
296
|
+
pure
|
|
297
|
+
returns (Governance.GovernanceStorage storage $)
|
|
298
|
+
{
|
|
299
|
+
/* solhint-disable-next-line no-inline-assembly */
|
|
300
|
+
assembly ("memory-safe") {
|
|
301
|
+
$.slot := GOVERNANCE_STORAGE_LOCATION
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
package/package.json
CHANGED
package/utils/Globals.sol
CHANGED
|
@@ -4,4 +4,28 @@ pragma solidity ^0.8.28;
|
|
|
4
4
|
string constant TREASURY_ADDRESS_PARAM_NAME = "slc.general.treasuryAddress";
|
|
5
5
|
string constant DAOSLC_PARAM_NAME = "slc.general.daoSLC";
|
|
6
6
|
|
|
7
|
+
// Governance
|
|
8
|
+
|
|
9
|
+
string constant VOTING_DELAY_PARAM_NAME = "slc.governance.votingDelay";
|
|
10
|
+
|
|
11
|
+
// solhint-disable-next-line gas-small-strings
|
|
12
|
+
string constant NEUTRALS_VOTING_PERIOD_PARAM_NAME = "slc.governance.neutralsVotingPeriod";
|
|
13
|
+
|
|
14
|
+
// solhint-disable-next-line gas-small-strings
|
|
15
|
+
string constant EXECUTORS_VOTING_PERIOD_PARAM_NAME = "slc.governance.executorsVotingPeriod";
|
|
16
|
+
|
|
17
|
+
// solhint-disable-next-line gas-small-strings
|
|
18
|
+
string constant NEUTRALS_THRESHOLD_PARAM_NAME = "slc.governance.neutralsThreshold";
|
|
19
|
+
|
|
20
|
+
// solhint-disable-next-line gas-small-strings
|
|
21
|
+
string constant EXECUTORS_THRESHOLD_PARAM_NAME = "slc.governance.executorsThreshold";
|
|
22
|
+
|
|
23
|
+
string constant EXECUTORS_SHARE_PARAM_NAME = "slc.governance.executorsShare";
|
|
24
|
+
|
|
25
|
+
string constant NEUTRALS_SHARE_PARAM_NAME = "slc.governance.neutralsShare";
|
|
26
|
+
|
|
27
|
+
string constant TREASURY_SHARE_PARAM_NAME = "slc.governance.treasuryShare";
|
|
28
|
+
|
|
29
|
+
string constant MIN_SERVICE_FEE_PARAM_NAME = "slc.governance.minServiceFee";
|
|
30
|
+
|
|
7
31
|
uint256 constant MAX_BASIS_POINTS = 100_00;
|