@web3dotorg/evm-slc-core-contracts 0.3.13 → 0.3.15

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.
@@ -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,
@@ -92,7 +70,7 @@ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
92
70
  VotingPhase votingPhase;
93
71
  bytes32 leadingOperationHash;
94
72
  // Proposals
95
- uint256 requestNonce;
73
+ uint256 proposalNonce;
96
74
  uint256[] proposalIds;
97
75
  }
98
76
 
@@ -114,7 +92,7 @@ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
114
92
  struct SLCProposalState {
115
93
  // Parent data
116
94
  address slcCore;
117
- uint256 requestNumber;
95
+ uint256 requestId;
118
96
  // Voting parameters
119
97
  uint256 neutralsThreshold;
120
98
  uint256 executorsThreshold;
@@ -193,9 +171,14 @@ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
193
171
  uint256 minServiceFee_ = _getMinServiceFee();
194
172
  require(serviceFee_ >= minServiceFee_, ServiceFeeTooLow(serviceFee_, minServiceFee_));
195
173
 
196
- (uint256 proposalId_, uint256 requestCounter_) = _makeProposal(
174
+ // solhint-disable-next-line gas-increment-by-one
175
+ uint256 requestCounter_ = $.slcGovernanceState[slcCore_].totalRequestsCount++;
176
+
177
+ uint256 proposalId_ = _makeProposal(
197
178
  additionalLink_,
198
- requester_
179
+ requester_,
180
+ slcCore_,
181
+ requestCounter_
199
182
  );
200
183
 
201
184
  address[] memory selectedNeutrals_ = $.neutralsRegistry.getNeutralsSlice(neutralsNumber_);
@@ -224,41 +207,7 @@ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
224
207
  uint256 proposalId_,
225
208
  OperationBundle calldata operationBundle_
226
209
  ) external {
227
- GovernanceStorage storage $ = _getGovernanceStorage();
228
-
229
- ServiceRequestState storage requestState = $
230
- .slcGovernanceState[$.proposalState[proposalId_].slcCore]
231
- .requestState[$.proposalState[proposalId_].requestNumber];
232
-
233
- require(
234
- requestState.votingPhase == VotingPhase.Neutrals,
235
- InvalidVotingPhaseForProposeOperations(requestState.votingPhase)
236
- );
237
-
238
- require(
239
- requestState.selectedNeutrals.contains(msg.sender),
240
- AccountNotSelected(msg.sender)
241
- );
242
-
243
- SLCProposalState storage proposalState = $.proposalState[proposalId_];
244
-
245
- bytes32 operationHash_ = hashOperation(operationBundle_);
246
-
247
- bytes32 previousOperationHash_ = proposalState.neutralToOperation[msg.sender];
248
- if (previousOperationHash_ != bytes32(0)) {
249
- proposalState.operationByNeutral[previousOperationHash_].remove(msg.sender);
250
-
251
- if (proposalState.operationByNeutral[previousOperationHash_].length() == 0) {
252
- _removeOperation(proposalState, previousOperationHash_);
253
- }
254
- }
255
-
256
- proposalState.operationByNeutral[operationHash_].add(msg.sender);
257
- proposalState.neutralToOperation[msg.sender] = operationHash_;
258
-
259
- _addOperation(proposalState, operationBundle_, operationHash_);
260
-
261
- proposalState.operationBundleByHash[operationHash_] = operationBundle_;
210
+ GovernanceUtils.handleProposeOperations(proposalId_, operationBundle_);
262
211
  }
263
212
 
264
213
  function implementation() external view returns (address) {
@@ -343,7 +292,7 @@ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
343
292
 
344
293
  ServiceRequestState storage requestState = $
345
294
  .slcGovernanceState[proposalState.slcCore]
346
- .requestState[proposalState.requestNumber];
295
+ .requestState[proposalState.requestId];
347
296
 
348
297
  uint256 selectedNeutralsLength_ = requestState.selectedNeutrals.length();
349
298
  bytes32[] memory neutralToOperationHashes_ = new bytes32[](selectedNeutralsLength_);
@@ -388,7 +337,7 @@ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
388
337
  data: requestState.data,
389
338
  votingPhase: requestState.votingPhase,
390
339
  leadingOperationHash: requestState.leadingOperationHash,
391
- requestNonce: requestState.requestNonce,
340
+ requestNonce: requestState.proposalNonce,
392
341
  proposalIds: requestState.proposalIds
393
342
  });
394
343
  }
@@ -441,7 +390,7 @@ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
441
390
  * @inheritdoc IGovernor
442
391
  */
443
392
  function votingDelay() public view override returns (uint256) {
444
- return _getGovernanceStorage().parametersRegistry.getUint(VOTING_DELAY_PARAM_NAME);
393
+ return _getGovernanceStorage().parametersRegistry.getUint(Globals.VOTING_DELAY_PARAM_NAME);
445
394
  }
446
395
 
447
396
  /**
@@ -468,12 +417,16 @@ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
468
417
 
469
418
  function getNeutralsVotingPeriod() public view returns (uint256) {
470
419
  return
471
- _getGovernanceStorage().parametersRegistry.getUint(NEUTRALS_VOTING_PERIOD_PARAM_NAME);
420
+ _getGovernanceStorage().parametersRegistry.getUint(
421
+ Globals.NEUTRALS_VOTING_PERIOD_PARAM_NAME
422
+ );
472
423
  }
473
424
 
474
425
  function getExecutorsVotingPeriod() public view returns (uint256) {
475
426
  return
476
- _getGovernanceStorage().parametersRegistry.getUint(EXECUTORS_VOTING_PERIOD_PARAM_NAME);
427
+ _getGovernanceStorage().parametersRegistry.getUint(
428
+ Globals.EXECUTORS_VOTING_PERIOD_PARAM_NAME
429
+ );
477
430
  }
478
431
 
479
432
  /**
@@ -481,7 +434,7 @@ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
481
434
  */
482
435
  function getNeutralsThreshold() public view returns (uint256) {
483
436
  uint256 neutralsThreshold_ = _getGovernanceStorage().parametersRegistry.getUint(
484
- NEUTRALS_THRESHOLD_PARAM_NAME
437
+ Globals.NEUTRALS_THRESHOLD_PARAM_NAME
485
438
  );
486
439
 
487
440
  if (neutralsThreshold_ > 100) {
@@ -496,7 +449,7 @@ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
496
449
  */
497
450
  function getExecutorsThreshold() public view returns (uint256) {
498
451
  uint256 executorsThreshold_ = _getGovernanceStorage().parametersRegistry.getUint(
499
- EXECUTORS_THRESHOLD_PARAM_NAME
452
+ Globals.EXECUTORS_THRESHOLD_PARAM_NAME
500
453
  );
501
454
 
502
455
  if (executorsThreshold_ > 100) {
@@ -520,50 +473,7 @@ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
520
473
  uint256 proposalId_,
521
474
  address[] memory selectedNeutrals_
522
475
  ) internal {
523
- GovernanceStorage storage $ = _getGovernanceStorage();
524
-
525
- ServiceRequestState storage requestState = $
526
- .slcGovernanceState[$.proposalState[proposalId_].slcCore]
527
- .requestState[$.proposalState[proposalId_].requestNumber];
528
-
529
- uint256 serviceFee_ = requestState.serviceFee;
530
- if (serviceFee_ == 0 && msg.value == 0) return;
531
-
532
- if (msg.value > 0) {
533
- require(serviceFee_ == msg.value, InvalidServiceFee(serviceFee_, msg.value));
534
- $.token.deposit{value: msg.value}();
535
- } else {
536
- $.token.safeTransferFrom(requestState.requester, address(this), serviceFee_);
537
- }
538
-
539
- uint256 executorsShare_ = _getExecutorsShare();
540
- uint256 neutralsShare_ = _getNeutralsShare();
541
- uint256 treasuryShare_ = _getTreasuryShare();
542
-
543
- // Validate shares total 100%
544
- uint256 totalShares_ = executorsShare_ + neutralsShare_ + treasuryShare_;
545
- require(totalShares_ == 100, InvalidSharesTotal(totalShares_));
546
-
547
- // Calculate amounts
548
- uint256 executorsAmount_ = (serviceFee_ * executorsShare_) / 100;
549
- uint256 neutralsAmount_ = (serviceFee_ * neutralsShare_) / 100;
550
- uint256 treasuryAmount_ = (serviceFee_ * treasuryShare_) / 100;
551
-
552
- // Distribute to executors
553
- if (executorsAmount_ > 0) {
554
- $.executorsRegistry.distributeRewards(executorsAmount_);
555
- }
556
-
557
- // Distribute to neutrals
558
- if (neutralsAmount_ > 0) {
559
- $.neutralsRegistry.distributeRewards(selectedNeutrals_, neutralsAmount_);
560
- }
561
-
562
- // Send to treasury
563
- if (treasuryAmount_ > 0) {
564
- address treasury_ = _getTreasuryAddress();
565
- $.token.safeTransfer(treasury_, treasuryAmount_);
566
- }
476
+ GovernanceUtils.distributeServiceFeeRewards(proposalId_, selectedNeutrals_);
567
477
  }
568
478
 
569
479
  function _executeOperations(
@@ -573,34 +483,7 @@ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
573
483
  bytes[] memory /*calldatas*/,
574
484
  bytes32 /*descriptionHash*/
575
485
  ) internal override {
576
- GovernanceStorage storage $ = _getGovernanceStorage();
577
-
578
- ServiceRequestState storage requestState = $
579
- .slcGovernanceState[$.proposalState[proposalId].slcCore]
580
- .requestState[$.proposalState[proposalId].requestNumber];
581
-
582
- bytes32 leadingOperationHash_ = requestState.leadingOperationHash;
583
- if (leadingOperationHash_ == bytes32(0)) {
584
- revert Unreachable();
585
- }
586
-
587
- OperationBundle storage leadingOperationBundle_ = $
588
- .proposalState[proposalId]
589
- .operationBundleByHash[leadingOperationHash_];
590
-
591
- ISLCCore slcCore_ = ISLCCore(targets[0]);
592
-
593
- uint256 operationsLength_ = leadingOperationBundle_.operations.length;
594
- for (uint256 i = 0; i < operationsLength_; ++i) {
595
- slcCore_.arbitraryExecute(
596
- leadingOperationBundle_.operations[i].operation_,
597
- leadingOperationBundle_.operations[i].to_,
598
- leadingOperationBundle_.operations[i].data_,
599
- leadingOperationBundle_.operations[i].value_
600
- );
601
- }
602
-
603
- requestState.votingPhase = VotingPhase.Executed;
486
+ GovernanceUtils.executeOperations(proposalId, targets);
604
487
  }
605
488
 
606
489
  /**
@@ -620,7 +503,7 @@ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
620
503
 
621
504
  ServiceRequestState storage requestState = $
622
505
  .slcGovernanceState[$.proposalState[proposalId].slcCore]
623
- .requestState[$.proposalState[proposalId].requestNumber];
506
+ .requestState[$.proposalState[proposalId].requestId];
624
507
 
625
508
  require($.executorsRegistry.isExecutor(account), AccountNotExecutor(account));
626
509
 
@@ -651,9 +534,11 @@ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
651
534
  $.proposalState[proposalId].negativeExecutorVotes.length() >=
652
535
  $.proposalState[proposalId].executorsThreshold
653
536
  ) {
654
- (uint256 newProposalId_, ) = _makeProposal(
537
+ uint256 newProposalId_ = _makeProposal(
655
538
  requestState.additionalLink,
656
- requestState.requester
539
+ requestState.requester,
540
+ $.proposalState[proposalId].slcCore,
541
+ requestState.requestId
657
542
  );
658
543
  _fillProposalState(newProposalId_);
659
544
 
@@ -703,44 +588,27 @@ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
703
588
  }
704
589
 
705
590
  function _getDAOSLC() internal view returns (address) {
706
- return _getGovernanceStorage().parametersRegistry.getAddr(DAOSLC_PARAM_NAME);
707
- }
708
-
709
- function _getExecutorsShare() internal view returns (uint256) {
710
- return _getGovernanceStorage().parametersRegistry.getUint(EXECUTORS_SHARE_PARAM_NAME);
711
- }
712
-
713
- function _getNeutralsShare() internal view returns (uint256) {
714
- return _getGovernanceStorage().parametersRegistry.getUint(NEUTRALS_SHARE_PARAM_NAME);
715
- }
716
-
717
- function _getTreasuryShare() internal view returns (uint256) {
718
- return _getGovernanceStorage().parametersRegistry.getUint(TREASURY_SHARE_PARAM_NAME);
719
- }
720
-
721
- function _getTreasuryAddress() internal view returns (address) {
722
- return _getGovernanceStorage().parametersRegistry.getAddr(TREASURY_ADDRESS_PARAM_NAME);
591
+ return _getGovernanceStorage().parametersRegistry.getAddr(Globals.DAOSLC_PARAM_NAME);
723
592
  }
724
593
 
725
594
  function _getMinServiceFee() internal view returns (uint256) {
726
- return _getGovernanceStorage().parametersRegistry.getUint(MIN_SERVICE_FEE_PARAM_NAME);
595
+ return
596
+ _getGovernanceStorage().parametersRegistry.getUint(Globals.MIN_SERVICE_FEE_PARAM_NAME);
727
597
  }
728
598
 
729
599
  function _makeProposal(
730
600
  string memory additionalLink_,
731
- address requester_
732
- ) private returns (uint256 proposalId_, uint256 requestCounter_) {
601
+ address requester_,
602
+ address slcCore_,
603
+ uint256 requestId_
604
+ ) private returns (uint256 proposalId_) {
733
605
  GovernanceStorage storage $ = _getGovernanceStorage();
734
606
 
735
- address slcCore_ = msg.sender;
736
-
737
607
  // solhint-disable-next-line gas-increment-by-one
738
- requestCounter_ = $.slcGovernanceState[slcCore_].totalRequestsCount++;
739
- // solhint-disable-next-line gas-increment-by-one
740
- uint256 requestNonce_ = $
608
+ uint256 proposalNonce_ = $
741
609
  .slcGovernanceState[slcCore_]
742
- .requestState[requestCounter_]
743
- .requestNonce++;
610
+ .requestState[requestId_]
611
+ .proposalNonce++;
744
612
 
745
613
  address[] memory targets = new address[](1);
746
614
  targets[0] = slcCore_;
@@ -749,18 +617,18 @@ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
749
617
  values[0] = 0;
750
618
 
751
619
  bytes[] memory calldatas = new bytes[](1);
752
- calldatas[0] = abi.encodePacked(requestCounter_, requestNonce_);
620
+ calldatas[0] = abi.encodePacked(requestId_, proposalNonce_);
753
621
 
754
622
  proposalId_ = _propose(targets, values, calldatas, additionalLink_, requester_);
755
623
 
756
624
  $.proposalState[proposalId_].slcCore = slcCore_;
757
- $.proposalState[proposalId_].requestNumber = requestCounter_;
625
+ $.proposalState[proposalId_].requestId = requestId_;
758
626
  }
759
627
 
760
628
  function _executeProposal(uint256 proposalId_) private {
761
629
  GovernanceStorage storage $ = _getGovernanceStorage();
762
630
 
763
- uint256 requestCounter_ = $.proposalState[proposalId_].requestNumber;
631
+ uint256 requestCounter_ = $.proposalState[proposalId_].requestId;
764
632
  ServiceRequestState storage requestState = $
765
633
  .slcGovernanceState[$.proposalState[proposalId_].slcCore]
766
634
  .requestState[requestCounter_];
@@ -770,7 +638,7 @@ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
770
638
 
771
639
  // Here we need to execute the previous proposal related to this request
772
640
  bytes[] memory calldatas = new bytes[](1);
773
- calldatas[0] = abi.encodePacked(requestCounter_, requestState.requestNonce - 1);
641
+ calldatas[0] = abi.encodePacked(requestCounter_, requestState.proposalNonce - 1);
774
642
 
775
643
  execute(
776
644
  targets,
@@ -781,93 +649,7 @@ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
781
649
  }
782
650
 
783
651
  function _fillProposalState(uint256 proposalId_) private {
784
- GovernanceStorage storage $ = _getGovernanceStorage();
785
-
786
- ServiceRequestState storage requestState = $
787
- .slcGovernanceState[$.proposalState[proposalId_].slcCore]
788
- .requestState[$.proposalState[proposalId_].requestNumber];
789
-
790
- uint256 neutralsLength_ = requestState.selectedNeutrals.length();
791
-
792
- $.proposalState[proposalId_].neutralsThreshold = Math.mulDiv(
793
- neutralsLength_,
794
- getNeutralsThreshold(),
795
- 100,
796
- Math.Rounding.Ceil
797
- );
798
-
799
- uint256 executorsCount_ = $.executorsRegistry.getExecutorsCount();
800
- $.proposalState[proposalId_].executorsThreshold = Math.mulDiv(
801
- executorsCount_,
802
- getExecutorsThreshold(),
803
- 100,
804
- Math.Rounding.Ceil
805
- );
806
-
807
- requestState.proposalIds.push(proposalId_);
808
-
809
- requestState.votingPhase = VotingPhase.Neutrals;
810
- }
811
-
812
- function _addOperation(
813
- SLCProposalState storage proposalState,
814
- OperationBundle memory operationBundle_,
815
- bytes32 operationHash_
816
- ) private returns (bool) {
817
- if (!_containsOperation(proposalState, operationHash_)) {
818
- proposalState.operationBundles.push(operationBundle_);
819
- // The value is stored at length-1, but we add 1 to all indexes
820
- // and use 0 as a sentinel value
821
- proposalState.operationBundlePosition[operationHash_] = proposalState
822
- .operationBundles
823
- .length;
824
- return true;
825
- } else {
826
- return false;
827
- }
828
- }
829
-
830
- function _removeOperation(
831
- SLCProposalState storage proposalState,
832
- bytes32 operationHash_
833
- ) private returns (bool) {
834
- // We cache the value's position to prevent multiple reads from the same storage slot
835
- uint256 position_ = proposalState.operationBundlePosition[operationHash_];
836
-
837
- if (position_ != 0) {
838
- // Equivalent to contains(set, value)
839
- // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
840
- // the array, and then remove the last element (sometimes called as 'swap and pop').
841
-
842
- uint256 valueIndex_ = position_ - 1;
843
- uint256 lastIndex_ = proposalState.operationBundles.length - 1;
844
-
845
- if (valueIndex_ != lastIndex_) {
846
- OperationBundle storage lastValue = proposalState.operationBundles[lastIndex_];
847
-
848
- // Move the lastValue to the index where the value to delete is
849
- proposalState.operationBundles[valueIndex_] = lastValue;
850
- // Update the tracked position of the lastValue (that was just moved)
851
- proposalState.operationBundlePosition[hashOperation(lastValue)] = position_;
852
- }
853
-
854
- // Delete the slot where the moved value was stored
855
- proposalState.operationBundles.pop();
856
-
857
- // Delete the tracked position for the deleted slot
858
- delete proposalState.operationBundlePosition[operationHash_];
859
-
860
- return true;
861
- } else {
862
- return false;
863
- }
864
- }
865
-
866
- function _containsOperation(
867
- SLCProposalState storage proposalState,
868
- bytes32 operationHash_
869
- ) private view returns (bool) {
870
- return proposalState.operationBundlePosition[operationHash_] != 0;
652
+ GovernanceUtils.fillProposalState(proposalId_);
871
653
  }
872
654
 
873
655
  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[neutral_][msg.sender].withdrawal;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@web3dotorg/evm-slc-core-contracts",
3
- "version": "0.3.13",
3
+ "version": "0.3.15",
4
4
  "author": "<TBD>",
5
5
  "repository": {
6
6
  "type": "git",
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;