@web3dotorg/evm-slc-core-contracts 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,969 @@
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
+ /* solhint-disable-next-line no-unused-import */
7
+ import {IERC6372} from "@openzeppelin/contracts/interfaces/IERC6372.sol";
8
+ /* solhint-disable-next-line no-unused-import */
9
+ import {IGovernor} from "@openzeppelin/contracts/governance/IGovernor.sol";
10
+ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
11
+ import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
12
+ import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
13
+ import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
14
+ import {GovernorUpgradeable} from "@openzeppelin/contracts-upgradeable/governance/GovernorUpgradeable.sol";
15
+
16
+ import {Paginator} from "@solarity/solidity-lib/libs/arrays/Paginator.sol";
17
+
18
+ import {Errors} from "../libs/Errors.sol";
19
+ import {IWrappedToken} from "../interfaces/IWrappedToken.sol";
20
+ import {ISLCCore} from "../interfaces/ISLCCore.sol";
21
+ import {IGovernance} from "../interfaces/IGovernance.sol";
22
+ import {IQParameters} from "../interfaces/IQParameters.sol";
23
+ import {INeutralsRegistry} from "../interfaces/INeutralsRegistry.sol";
24
+ import {IExecutorsRegistry} from "../interfaces/IExecutorsRegistry.sol";
25
+ import {DAOSLC_PARAM_NAME, TREASURY_ADDRESS_PARAM_NAME} from "../utils/Globals.sol";
26
+
27
+ contract Governance is IGovernance, GovernorUpgradeable, UUPSUpgradeable {
28
+ using EnumerableSet for EnumerableSet.AddressSet;
29
+
30
+ using SafeERC20 for IWrappedToken;
31
+
32
+ // keccak256(abi.encode(uint256(keccak256("evm-slc-core.storage.Governance")) - 1)) & ~bytes32(uint256(0xff))
33
+ bytes32 private constant GOVERNANCE_STORAGE_LOCATION =
34
+ 0xb93e2841340255f69de4f9300770e3cc1894dc081e8bf6d34a78e9ac897c7900;
35
+
36
+ bytes32 private constant GOVERNOR_STORAGE_LOCATION =
37
+ 0x7c712897014dbe49c045ef1299aa2d5f9e67e48eea4403efa21f1e0f3ac0cb00;
38
+
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
+ enum VotingPhase {
64
+ None,
65
+ Neutrals,
66
+ Executors,
67
+ Executed
68
+ }
69
+
70
+ struct GovernanceStorage {
71
+ IWrappedToken token;
72
+ IExecutorsRegistry executorsRegistry;
73
+ INeutralsRegistry neutralsRegistry;
74
+ IQParameters parametersRegistry;
75
+ mapping(address slcCore => SLCGovernanceState) slcGovernanceState;
76
+ mapping(uint256 proposalId => SLCProposalState) proposalState;
77
+ }
78
+
79
+ struct SLCGovernanceState {
80
+ uint256 totalRequestsCount;
81
+ mapping(uint256 requestNumber => ServiceRequestState) requestState;
82
+ }
83
+
84
+ struct ServiceRequestState {
85
+ address requester;
86
+ uint256 serviceFee;
87
+ uint256 neutralsNumber;
88
+ string additionalLink;
89
+ bytes data;
90
+ VotingPhase votingPhase;
91
+ bytes32 leadingOperationHash;
92
+ // Proposals
93
+ uint256 requestNonce;
94
+ uint256[] proposalIds;
95
+ }
96
+
97
+ struct SLCProposalState {
98
+ // Parent data
99
+ address slcCore;
100
+ uint256 requestNumber;
101
+ // Voting parameters
102
+ uint256 neutralsThreshold;
103
+ uint256 executorsThreshold;
104
+ // Service request data
105
+ EnumerableSet.AddressSet selectedNeutrals;
106
+ // Voting data
107
+ OperationBundle[] operationBundles;
108
+ mapping(bytes32 operationHash => uint256 position) operationBundlePosition;
109
+ mapping(bytes32 operationHash => OperationBundle) operationBundleByHash;
110
+ mapping(bytes32 operationHash => EnumerableSet.AddressSet) operationByNeutral;
111
+ mapping(address neutral => bytes32 operationHash) neutralToOperation;
112
+ EnumerableSet.AddressSet positiveExecutorVotes;
113
+ EnumerableSet.AddressSet negativeExecutorVotes;
114
+ }
115
+
116
+ struct SLCProposalStateView {
117
+ // Voting parameters
118
+ uint256 neutralsThreshold;
119
+ uint256 executorsThreshold;
120
+ // Service request data
121
+ address[] selectedNeutrals;
122
+ bytes32[] neutralToOperationHashes;
123
+ // Voting data
124
+ OperationBundle[] operationBundles;
125
+ address[] positiveExecutorVotes;
126
+ address[] negativeExecutorVotes;
127
+ }
128
+
129
+ event RejectedByExecutors(uint256 proposalId, uint256 newProposalId);
130
+
131
+ error Unreachable();
132
+ error AccountAlreadyVoted(address account);
133
+ error NeutralsThresholdTooHigh(uint256 threshold);
134
+ error ExecutorsThresholdTooHigh(uint256 threshold);
135
+ error InvalidTimepoint(uint256 timepoint);
136
+ error AccountNotSelected(address account);
137
+ error AccountNotExecutor(address account);
138
+ error InvalidServiceFee(uint256 serviceFee, uint256 msgValue);
139
+ error InvalidVotingPhaseForExecutorVote(VotingPhase votingPhase);
140
+ error InvalidVotingPhaseForProposeOperations(VotingPhase votingPhase);
141
+ error InvalidSharesTotal(uint256 totalShares);
142
+ error ServiceFeeTooLow(uint256 serviceFee, uint256 minServiceFee);
143
+ error MinServiceFeeNotSet(address parametersRegistry);
144
+
145
+ modifier onlyDAOSLC() {
146
+ _requireDAOSLC();
147
+ _;
148
+ }
149
+
150
+ function __Governance_init(
151
+ address token_,
152
+ address executorsRegistry_,
153
+ address neutralsRegistry_,
154
+ address parametersRegistry_
155
+ ) external initializer {
156
+ GovernanceStorage storage $ = _getGovernanceStorage();
157
+
158
+ $.token = IWrappedToken(token_);
159
+ $.executorsRegistry = IExecutorsRegistry(executorsRegistry_);
160
+ $.neutralsRegistry = INeutralsRegistry(neutralsRegistry_);
161
+ $.parametersRegistry = IQParameters(parametersRegistry_);
162
+
163
+ // Approve token usage for reward distribution
164
+ $.token.approve(executorsRegistry_, type(uint256).max);
165
+ $.token.approve(neutralsRegistry_, type(uint256).max);
166
+ }
167
+
168
+ function processServiceRequest(
169
+ address requester_,
170
+ uint256 serviceFee_,
171
+ uint256 neutralsNumber_,
172
+ string calldata additionalLink_,
173
+ bytes calldata data_
174
+ ) external payable override returns (bool) {
175
+ GovernanceStorage storage $ = _getGovernanceStorage();
176
+
177
+ address slcCore_ = msg.sender;
178
+
179
+ uint256 minServiceFee_ = _getMinServiceFee();
180
+ require(serviceFee_ >= minServiceFee_, ServiceFeeTooLow(serviceFee_, minServiceFee_));
181
+
182
+ (uint256 proposalId_, uint256 requestCounter_) = _makeProposal(
183
+ additionalLink_,
184
+ requester_
185
+ );
186
+
187
+ ServiceRequestState storage requestState = $.slcGovernanceState[slcCore_].requestState[
188
+ requestCounter_
189
+ ];
190
+ requestState.requester = requester_;
191
+ requestState.serviceFee = serviceFee_;
192
+ requestState.neutralsNumber = neutralsNumber_;
193
+ requestState.additionalLink = additionalLink_;
194
+ requestState.data = data_;
195
+
196
+ address[] memory selectedNeutrals_ = $.neutralsRegistry.getNeutralsSlice(neutralsNumber_);
197
+ _fillProposalState(selectedNeutrals_, proposalId_);
198
+
199
+ _distributeServiceFeeRewards(proposalId_, selectedNeutrals_);
200
+
201
+ return true;
202
+ }
203
+
204
+ function proposeOperations(
205
+ uint256 proposalId_,
206
+ OperationBundle calldata operationBundle_
207
+ ) external {
208
+ GovernanceStorage storage $ = _getGovernanceStorage();
209
+
210
+ ServiceRequestState storage requestState = $
211
+ .slcGovernanceState[$.proposalState[proposalId_].slcCore]
212
+ .requestState[$.proposalState[proposalId_].requestNumber];
213
+
214
+ require(
215
+ requestState.votingPhase == VotingPhase.Neutrals,
216
+ InvalidVotingPhaseForProposeOperations(requestState.votingPhase)
217
+ );
218
+
219
+ require(
220
+ $.proposalState[proposalId_].selectedNeutrals.contains(msg.sender),
221
+ AccountNotSelected(msg.sender)
222
+ );
223
+
224
+ SLCProposalState storage proposalState = $.proposalState[proposalId_];
225
+
226
+ bytes32 operationHash_ = hashOperation(operationBundle_);
227
+
228
+ bytes32 previousOperationHash_ = proposalState.neutralToOperation[msg.sender];
229
+ if (previousOperationHash_ != bytes32(0)) {
230
+ proposalState.operationByNeutral[previousOperationHash_].remove(msg.sender);
231
+
232
+ if (proposalState.operationByNeutral[previousOperationHash_].length() == 0) {
233
+ _removeOperation(proposalState, previousOperationHash_);
234
+ }
235
+ }
236
+
237
+ proposalState.operationByNeutral[operationHash_].add(msg.sender);
238
+ proposalState.neutralToOperation[msg.sender] = operationHash_;
239
+
240
+ _addOperation(proposalState, operationBundle_, operationHash_);
241
+
242
+ proposalState.operationBundleByHash[operationHash_] = operationBundle_;
243
+ }
244
+
245
+ function implementation() external view returns (address) {
246
+ return ERC1967Utils.getImplementation();
247
+ }
248
+
249
+ /**
250
+ * @inheritdoc IERC6372
251
+ */
252
+ function clock() public view override returns (uint48) {
253
+ return uint48(block.timestamp);
254
+ }
255
+
256
+ /**
257
+ * @inheritdoc IERC6372
258
+ */
259
+ // solhint-disable-next-line func-name-mixedcase
260
+ function CLOCK_MODE() public pure override returns (string memory) {
261
+ return "mode=timestamp";
262
+ }
263
+
264
+ // TODO: fix
265
+ function COUNTING_MODE() public pure virtual override returns (string memory) {
266
+ return "support=bravo,fractional&quorum=for,abstain&params=fractional";
267
+ }
268
+
269
+ function getServiceRequestsBySLCCore(
270
+ address slcCore_,
271
+ uint256 offset_,
272
+ uint256 limit_
273
+ ) external view returns (ServiceRequestState[] memory) {
274
+ GovernanceStorage storage $ = _getGovernanceStorage();
275
+
276
+ uint256 to_ = Paginator.getTo(
277
+ $.slcGovernanceState[slcCore_].totalRequestsCount,
278
+ offset_,
279
+ limit_
280
+ );
281
+
282
+ ServiceRequestState[] memory requests_ = new ServiceRequestState[](to_ - offset_);
283
+
284
+ for (uint256 i = offset_; i < to_; ++i) {
285
+ requests_[i - offset_] = $.slcGovernanceState[slcCore_].requestState[i];
286
+ }
287
+
288
+ return requests_;
289
+ }
290
+
291
+ function getProposalsByServiceRequest(
292
+ address slcCore_,
293
+ uint256 requestNumber_,
294
+ uint256 offset_,
295
+ uint256 limit_
296
+ ) external view returns (SLCProposalStateView[] memory, ProposalCore[] memory) {
297
+ GovernanceStorage storage $ = _getGovernanceStorage();
298
+
299
+ uint256[] storage proposalIds_ = $
300
+ .slcGovernanceState[slcCore_]
301
+ .requestState[requestNumber_]
302
+ .proposalIds;
303
+
304
+ uint256 to_ = Paginator.getTo(proposalIds_.length, offset_, limit_);
305
+
306
+ SLCProposalStateView[] memory proposals_ = new SLCProposalStateView[](to_ - offset_);
307
+ ProposalCore[] memory proposalCores_ = new ProposalCore[](to_ - offset_);
308
+
309
+ for (uint256 i = offset_; i < to_; ++i) {
310
+ (proposals_[i - offset_], proposalCores_[i - offset_]) = getProposalData(
311
+ proposalIds_[i]
312
+ );
313
+ }
314
+
315
+ return (proposals_, proposalCores_);
316
+ }
317
+
318
+ function getProposalData(
319
+ uint256 proposalId
320
+ ) public view returns (SLCProposalStateView memory, ProposalCore memory) {
321
+ GovernanceStorage storage $ = _getGovernanceStorage();
322
+ GovernorStorage storage $governor = _getGovernorStorageData();
323
+
324
+ SLCProposalState storage proposalState = $.proposalState[proposalId];
325
+
326
+ bytes32[] memory neutralToOperationHashes_ = new bytes32[](
327
+ proposalState.selectedNeutrals.length()
328
+ );
329
+
330
+ uint256 selectedNeutralsLength_ = proposalState.selectedNeutrals.length();
331
+ for (uint256 i = 0; i < selectedNeutralsLength_; ++i) {
332
+ neutralToOperationHashes_[i] = proposalState.neutralToOperation[
333
+ proposalState.selectedNeutrals.at(i)
334
+ ];
335
+ }
336
+
337
+ SLCProposalStateView memory proposalData = SLCProposalStateView({
338
+ neutralsThreshold: proposalState.neutralsThreshold,
339
+ executorsThreshold: proposalState.executorsThreshold,
340
+ selectedNeutrals: proposalState.selectedNeutrals.values(),
341
+ neutralToOperationHashes: neutralToOperationHashes_,
342
+ operationBundles: proposalState.operationBundles,
343
+ positiveExecutorVotes: proposalState.positiveExecutorVotes.values(),
344
+ negativeExecutorVotes: proposalState.negativeExecutorVotes.values()
345
+ });
346
+
347
+ ProposalCore memory proposalCore = $governor._proposals[proposalId];
348
+
349
+ return (proposalData, proposalCore);
350
+ }
351
+
352
+ function hasVoted(uint256 proposalId, address account) external view returns (bool) {
353
+ GovernanceStorage storage $ = _getGovernanceStorage();
354
+
355
+ return
356
+ $.proposalState[proposalId].positiveExecutorVotes.contains(account) ||
357
+ $.proposalState[proposalId].negativeExecutorVotes.contains(account);
358
+ }
359
+
360
+ function state(uint256 proposalId) public view override returns (ProposalState) {
361
+ GovernanceStorage storage $ = _getGovernanceStorage();
362
+
363
+ if (
364
+ $.proposalState[proposalId].negativeExecutorVotes.length() >=
365
+ $.proposalState[proposalId].executorsThreshold
366
+ ) {
367
+ return ProposalState.Defeated;
368
+ }
369
+
370
+ if (_voteSucceeded(proposalId)) {
371
+ return ProposalState.Succeeded;
372
+ }
373
+
374
+ return super.state(proposalId);
375
+ }
376
+
377
+ function hashOperation(OperationBundle memory operation_) public pure returns (bytes32) {
378
+ return keccak256(abi.encode(operation_));
379
+ }
380
+
381
+ function getSystemToken() external view returns (address) {
382
+ GovernanceStorage storage $ = _getGovernanceStorage();
383
+ return address($.token);
384
+ }
385
+
386
+ function getParametersRegistry() external view returns (address) {
387
+ GovernanceStorage storage $ = _getGovernanceStorage();
388
+ return address($.parametersRegistry);
389
+ }
390
+
391
+ /**
392
+ * @inheritdoc IGovernor
393
+ */
394
+ function votingDelay() public view override returns (uint256) {
395
+ try _getGovernanceStorage().parametersRegistry.getUint(VOTING_DELAY_PARAM_NAME) returns (
396
+ uint256 votingDelay_
397
+ ) {
398
+ return votingDelay_;
399
+ } catch {
400
+ return 0;
401
+ }
402
+ }
403
+
404
+ /**
405
+ * @inheritdoc IGovernor
406
+ */
407
+ function votingPeriod() public view override returns (uint256) {
408
+ return getNeutralsVotingPeriod() + getExecutorsVotingPeriod();
409
+ }
410
+
411
+ /**
412
+ * @inheritdoc IGovernor
413
+ */
414
+ function quorum(uint256 timepoint) public view override returns (uint256) {
415
+ require(timepoint > 0 && timepoint <= 2, InvalidTimepoint(timepoint));
416
+
417
+ VotingPhase votingPhase = VotingPhase(timepoint);
418
+
419
+ if (votingPhase == VotingPhase.Neutrals) {
420
+ return getNeutralsThreshold();
421
+ }
422
+
423
+ return getExecutorsThreshold();
424
+ }
425
+
426
+ function getNeutralsVotingPeriod() public view returns (uint256) {
427
+ GovernanceStorage storage $ = _getGovernanceStorage();
428
+
429
+ try $.parametersRegistry.getUint(NEUTRALS_VOTING_PERIOD_PARAM_NAME) returns (
430
+ uint256 neutralsVotingPeriod_
431
+ ) {
432
+ return neutralsVotingPeriod_;
433
+ } catch {
434
+ revert Errors.ParameterIsNotSet(
435
+ address($.parametersRegistry),
436
+ NEUTRALS_VOTING_PERIOD_PARAM_NAME
437
+ );
438
+ }
439
+ }
440
+
441
+ function getExecutorsVotingPeriod() public view returns (uint256) {
442
+ GovernanceStorage storage $ = _getGovernanceStorage();
443
+
444
+ try $.parametersRegistry.getUint(EXECUTORS_VOTING_PERIOD_PARAM_NAME) returns (
445
+ uint256 executorsVotingPeriod_
446
+ ) {
447
+ return executorsVotingPeriod_;
448
+ } catch {
449
+ revert Errors.ParameterIsNotSet(
450
+ address($.parametersRegistry),
451
+ EXECUTORS_VOTING_PERIOD_PARAM_NAME
452
+ );
453
+ }
454
+ }
455
+
456
+ /**
457
+ * @notice Returns the threshold for the neutrals voting phase. Expected value is in range [0, 100].
458
+ */
459
+ function getNeutralsThreshold() public view returns (uint256) {
460
+ GovernanceStorage storage $ = _getGovernanceStorage();
461
+
462
+ try $.parametersRegistry.getUint(NEUTRALS_THRESHOLD_PARAM_NAME) returns (
463
+ uint256 neutralsThreshold_
464
+ ) {
465
+ if (neutralsThreshold_ > 100) {
466
+ revert NeutralsThresholdTooHigh(neutralsThreshold_);
467
+ }
468
+
469
+ return neutralsThreshold_;
470
+ } catch {
471
+ revert Errors.ParameterIsNotSet(
472
+ address($.parametersRegistry),
473
+ NEUTRALS_THRESHOLD_PARAM_NAME
474
+ );
475
+ }
476
+ }
477
+
478
+ /**
479
+ * @notice Returns the threshold for the executors voting phase. Expected value is in range [0, 100].
480
+ */
481
+ function getExecutorsThreshold() public view returns (uint256) {
482
+ GovernanceStorage storage $ = _getGovernanceStorage();
483
+
484
+ try $.parametersRegistry.getUint(EXECUTORS_THRESHOLD_PARAM_NAME) returns (
485
+ uint256 executorsThreshold_
486
+ ) {
487
+ if (executorsThreshold_ > 100) {
488
+ revert ExecutorsThresholdTooHigh(executorsThreshold_);
489
+ }
490
+
491
+ return executorsThreshold_;
492
+ } catch {
493
+ revert Errors.ParameterIsNotSet(
494
+ address($.parametersRegistry),
495
+ EXECUTORS_THRESHOLD_PARAM_NAME
496
+ );
497
+ }
498
+ }
499
+
500
+ /**
501
+ * @inheritdoc IERC165
502
+ */
503
+ function supportsInterface(
504
+ bytes4 interfaceId_
505
+ ) public view override(IERC165, GovernorUpgradeable) returns (bool) {
506
+ return
507
+ interfaceId_ == type(IGovernance).interfaceId || super.supportsInterface(interfaceId_);
508
+ }
509
+
510
+ function _distributeServiceFeeRewards(
511
+ uint256 proposalId_,
512
+ address[] memory selectedNeutrals_
513
+ ) internal {
514
+ GovernanceStorage storage $ = _getGovernanceStorage();
515
+
516
+ ServiceRequestState storage requestState = $
517
+ .slcGovernanceState[$.proposalState[proposalId_].slcCore]
518
+ .requestState[$.proposalState[proposalId_].requestNumber];
519
+
520
+ uint256 serviceFee_ = requestState.serviceFee;
521
+ if (serviceFee_ == 0 && msg.value == 0) return;
522
+
523
+ if (msg.value > 0) {
524
+ require(serviceFee_ == msg.value, InvalidServiceFee(serviceFee_, msg.value));
525
+ $.token.deposit{value: msg.value}();
526
+ } else {
527
+ $.token.safeTransferFrom(requestState.requester, address(this), serviceFee_);
528
+ }
529
+
530
+ uint256 executorsShare_ = _getExecutorsShare();
531
+ uint256 neutralsShare_ = _getNeutralsShare();
532
+ uint256 treasuryShare_ = _getTreasuryShare();
533
+
534
+ // Validate shares total 100%
535
+ uint256 totalShares_ = executorsShare_ + neutralsShare_ + treasuryShare_;
536
+ require(totalShares_ == 100, InvalidSharesTotal(totalShares_));
537
+
538
+ // Calculate amounts
539
+ uint256 executorsAmount_ = (serviceFee_ * executorsShare_) / 100;
540
+ uint256 neutralsAmount_ = (serviceFee_ * neutralsShare_) / 100;
541
+ uint256 treasuryAmount_ = (serviceFee_ * treasuryShare_) / 100;
542
+
543
+ // Distribute to executors
544
+ if (executorsAmount_ > 0) {
545
+ $.executorsRegistry.distributeRewards(executorsAmount_);
546
+ }
547
+
548
+ // Distribute to neutrals
549
+ if (neutralsAmount_ > 0) {
550
+ $.neutralsRegistry.distributeRewards(selectedNeutrals_, neutralsAmount_);
551
+ }
552
+
553
+ // Send to treasury
554
+ if (treasuryAmount_ > 0) {
555
+ address treasury_ = _getTreasuryAddress();
556
+ $.token.safeTransfer(treasury_, treasuryAmount_);
557
+ }
558
+ }
559
+
560
+ function _executeOperations(
561
+ uint256 proposalId,
562
+ address[] memory targets,
563
+ uint256[] memory /*values*/,
564
+ bytes[] memory /*calldatas*/,
565
+ bytes32 /*descriptionHash*/
566
+ ) internal override {
567
+ GovernanceStorage storage $ = _getGovernanceStorage();
568
+
569
+ ServiceRequestState storage requestState = $
570
+ .slcGovernanceState[$.proposalState[proposalId].slcCore]
571
+ .requestState[$.proposalState[proposalId].requestNumber];
572
+
573
+ bytes32 leadingOperationHash_ = requestState.leadingOperationHash;
574
+ if (leadingOperationHash_ == bytes32(0)) {
575
+ revert Unreachable();
576
+ }
577
+
578
+ OperationBundle storage leadingOperationBundle_ = $
579
+ .proposalState[proposalId]
580
+ .operationBundleByHash[leadingOperationHash_];
581
+
582
+ ISLCCore slcCore_ = ISLCCore(targets[0]);
583
+
584
+ uint256 operationsLength_ = leadingOperationBundle_.operations.length;
585
+ for (uint256 i = 0; i < operationsLength_; ++i) {
586
+ slcCore_.arbitraryExecute(
587
+ leadingOperationBundle_.operations[i].operation_,
588
+ leadingOperationBundle_.operations[i].to_,
589
+ leadingOperationBundle_.operations[i].data_,
590
+ leadingOperationBundle_.operations[i].value_
591
+ );
592
+ }
593
+
594
+ requestState.votingPhase = VotingPhase.Executed;
595
+ }
596
+
597
+ /**
598
+ * @dev Register a vote for `proposalId` by `account` with a given `support`, voting `weight` and voting `params`.
599
+ *
600
+ * Note: Support is generic and can represent various things depending on the voting system used.
601
+ */
602
+ // solhint-disable-next-line function-max-lines
603
+ function _countVote(
604
+ uint256 proposalId,
605
+ address account,
606
+ uint8 support,
607
+ uint256 totalWeight,
608
+ bytes memory
609
+ ) internal virtual override returns (uint256) {
610
+ GovernanceStorage storage $ = _getGovernanceStorage();
611
+
612
+ ServiceRequestState storage requestState = $
613
+ .slcGovernanceState[$.proposalState[proposalId].slcCore]
614
+ .requestState[$.proposalState[proposalId].requestNumber];
615
+
616
+ require($.executorsRegistry.isExecutor(account), AccountNotExecutor(account));
617
+
618
+ if (requestState.votingPhase == VotingPhase.Neutrals && _quorumReached(proposalId)) {
619
+ requestState.votingPhase = VotingPhase.Executors;
620
+ requestState.leadingOperationHash = _findLeadingOperation(proposalId);
621
+ }
622
+
623
+ require(
624
+ requestState.votingPhase == VotingPhase.Executors,
625
+ InvalidVotingPhaseForExecutorVote(requestState.votingPhase)
626
+ );
627
+
628
+ if (
629
+ $.proposalState[proposalId].positiveExecutorVotes.contains(account) ||
630
+ $.proposalState[proposalId].negativeExecutorVotes.contains(account)
631
+ ) {
632
+ revert AccountAlreadyVoted(account);
633
+ }
634
+
635
+ if (support == 1) {
636
+ $.proposalState[proposalId].positiveExecutorVotes.add(account);
637
+ } else {
638
+ $.proposalState[proposalId].negativeExecutorVotes.add(account);
639
+ }
640
+
641
+ if (
642
+ $.proposalState[proposalId].negativeExecutorVotes.length() >=
643
+ $.proposalState[proposalId].executorsThreshold
644
+ ) {
645
+ (uint256 newProposalId_, ) = _makeProposal(
646
+ requestState.additionalLink,
647
+ requestState.requester
648
+ );
649
+ _fillProposalState(
650
+ $.proposalState[proposalId].selectedNeutrals.values(),
651
+ newProposalId_
652
+ );
653
+
654
+ emit RejectedByExecutors(proposalId, newProposalId_);
655
+ }
656
+
657
+ if (
658
+ $.proposalState[proposalId].positiveExecutorVotes.length() >=
659
+ $.proposalState[proposalId].executorsThreshold
660
+ ) {
661
+ _executeProposal(proposalId);
662
+ }
663
+
664
+ return totalWeight;
665
+ }
666
+
667
+ // solhint-disable-next-line no-empty-blocks
668
+ function _authorizeUpgrade(address) internal override onlyDAOSLC {}
669
+
670
+ /**
671
+ * @dev Amount of votes already cast passes the threshold limit.
672
+ */
673
+ function _quorumReached(uint256 proposalId_) internal view override returns (bool) {
674
+ return _findLeadingOperation(proposalId_) != bytes32(0);
675
+ }
676
+
677
+ /**
678
+ * @dev Is the proposal successful or not.
679
+ */
680
+ function _voteSucceeded(uint256 proposalId_) internal view override returns (bool) {
681
+ GovernanceStorage storage $ = _getGovernanceStorage();
682
+
683
+ return
684
+ $.proposalState[proposalId_].positiveExecutorVotes.length() >=
685
+ $.proposalState[proposalId_].executorsThreshold;
686
+ }
687
+
688
+ /**
689
+ * @notice For executors and neutrals voting weight is always 1 for the time being
690
+ */
691
+ function _getVotes(address, uint256, bytes memory) internal pure override returns (uint256) {
692
+ return 1;
693
+ }
694
+
695
+ function _requireDAOSLC() internal view {
696
+ require(msg.sender == _getDAOSLC(), Errors.SenderExpectedToBeDAOSLC(msg.sender));
697
+ }
698
+
699
+ function _getDAOSLC() internal view returns (address) {
700
+ GovernanceStorage storage $ = _getGovernanceStorage();
701
+
702
+ try $.parametersRegistry.getAddr(DAOSLC_PARAM_NAME) returns (address daoslc_) {
703
+ return daoslc_;
704
+ } catch {
705
+ revert Errors.ParameterIsNotSet(address($.parametersRegistry), DAOSLC_PARAM_NAME);
706
+ }
707
+ }
708
+
709
+ function _getExecutorsShare() internal view returns (uint256) {
710
+ GovernanceStorage storage $ = _getGovernanceStorage();
711
+
712
+ try $.parametersRegistry.getUint(EXECUTORS_SHARE_PARAM_NAME) returns (
713
+ uint256 executorsShare_
714
+ ) {
715
+ return executorsShare_;
716
+ } catch {
717
+ revert Errors.ParameterIsNotSet(
718
+ address($.parametersRegistry),
719
+ EXECUTORS_SHARE_PARAM_NAME
720
+ );
721
+ }
722
+ }
723
+
724
+ function _getNeutralsShare() internal view returns (uint256) {
725
+ GovernanceStorage storage $ = _getGovernanceStorage();
726
+
727
+ try $.parametersRegistry.getUint(NEUTRALS_SHARE_PARAM_NAME) returns (
728
+ uint256 neutralsShare_
729
+ ) {
730
+ return neutralsShare_;
731
+ } catch {
732
+ revert Errors.ParameterIsNotSet(
733
+ address($.parametersRegistry),
734
+ NEUTRALS_SHARE_PARAM_NAME
735
+ );
736
+ }
737
+ }
738
+
739
+ function _getTreasuryShare() internal view returns (uint256) {
740
+ GovernanceStorage storage $ = _getGovernanceStorage();
741
+
742
+ try $.parametersRegistry.getUint(TREASURY_SHARE_PARAM_NAME) returns (
743
+ uint256 treasuryShare_
744
+ ) {
745
+ return treasuryShare_;
746
+ } catch {
747
+ revert Errors.ParameterIsNotSet(
748
+ address($.parametersRegistry),
749
+ TREASURY_SHARE_PARAM_NAME
750
+ );
751
+ }
752
+ }
753
+
754
+ function _getTreasuryAddress() internal view returns (address) {
755
+ GovernanceStorage storage $ = _getGovernanceStorage();
756
+
757
+ try $.parametersRegistry.getAddr(TREASURY_ADDRESS_PARAM_NAME) returns (address treasury_) {
758
+ return treasury_;
759
+ } catch {
760
+ revert Errors.ParameterIsNotSet(
761
+ address($.parametersRegistry),
762
+ TREASURY_ADDRESS_PARAM_NAME
763
+ );
764
+ }
765
+ }
766
+
767
+ function _getMinServiceFee() internal view returns (uint256) {
768
+ GovernanceStorage storage $ = _getGovernanceStorage();
769
+
770
+ try $.parametersRegistry.getUint(MIN_SERVICE_FEE_PARAM_NAME) returns (
771
+ uint256 minServiceFee_
772
+ ) {
773
+ return minServiceFee_;
774
+ } catch {
775
+ revert Errors.ParameterIsNotSet(
776
+ address($.parametersRegistry),
777
+ MIN_SERVICE_FEE_PARAM_NAME
778
+ );
779
+ }
780
+ }
781
+
782
+ function _makeProposal(
783
+ string memory additionalLink_,
784
+ address requester_
785
+ ) private returns (uint256 proposalId_, uint256 requestCounter_) {
786
+ GovernanceStorage storage $ = _getGovernanceStorage();
787
+
788
+ address slcCore_ = msg.sender;
789
+
790
+ // solhint-disable-next-line gas-increment-by-one
791
+ requestCounter_ = $.slcGovernanceState[slcCore_].totalRequestsCount++;
792
+ // solhint-disable-next-line gas-increment-by-one
793
+ uint256 requestNonce_ = $
794
+ .slcGovernanceState[slcCore_]
795
+ .requestState[requestCounter_]
796
+ .requestNonce++;
797
+
798
+ address[] memory targets = new address[](1);
799
+ targets[0] = slcCore_;
800
+
801
+ uint256[] memory values = new uint256[](1);
802
+ values[0] = 0;
803
+
804
+ bytes[] memory calldatas = new bytes[](1);
805
+ calldatas[0] = abi.encodePacked(requestCounter_, requestNonce_);
806
+
807
+ proposalId_ = _propose(targets, values, calldatas, additionalLink_, requester_);
808
+
809
+ $.proposalState[proposalId_].slcCore = slcCore_;
810
+ $.proposalState[proposalId_].requestNumber = requestCounter_;
811
+ }
812
+
813
+ function _executeProposal(uint256 proposalId_) private {
814
+ GovernanceStorage storage $ = _getGovernanceStorage();
815
+
816
+ uint256 requestCounter_ = $.proposalState[proposalId_].requestNumber;
817
+ ServiceRequestState storage requestState = $
818
+ .slcGovernanceState[$.proposalState[proposalId_].slcCore]
819
+ .requestState[requestCounter_];
820
+
821
+ address[] memory targets = new address[](1);
822
+ targets[0] = $.proposalState[proposalId_].slcCore;
823
+
824
+ // Here we need to execute the previous proposal related to this request
825
+ bytes[] memory calldatas = new bytes[](1);
826
+ calldatas[0] = abi.encodePacked(requestCounter_, requestState.requestNonce - 1);
827
+
828
+ execute(
829
+ targets,
830
+ new uint256[](1),
831
+ calldatas,
832
+ keccak256(bytes(requestState.additionalLink))
833
+ );
834
+ }
835
+
836
+ function _fillProposalState(address[] memory selectedNeutrals_, uint256 proposalId_) private {
837
+ GovernanceStorage storage $ = _getGovernanceStorage();
838
+
839
+ ServiceRequestState storage requestState = $
840
+ .slcGovernanceState[$.proposalState[proposalId_].slcCore]
841
+ .requestState[$.proposalState[proposalId_].requestNumber];
842
+
843
+ uint256 neutralsLength_ = selectedNeutrals_.length;
844
+ for (uint256 i = 0; i < neutralsLength_; ++i) {
845
+ $.proposalState[proposalId_].selectedNeutrals.add(selectedNeutrals_[i]);
846
+ }
847
+
848
+ $.proposalState[proposalId_].neutralsThreshold = Math.mulDiv(
849
+ neutralsLength_,
850
+ getNeutralsThreshold(),
851
+ 100,
852
+ Math.Rounding.Ceil
853
+ );
854
+
855
+ uint256 executorsCount_ = $.executorsRegistry.getExecutorsCount();
856
+ $.proposalState[proposalId_].executorsThreshold = Math.mulDiv(
857
+ executorsCount_,
858
+ getExecutorsThreshold(),
859
+ 100,
860
+ Math.Rounding.Ceil
861
+ );
862
+
863
+ requestState.proposalIds.push(proposalId_);
864
+
865
+ requestState.votingPhase = VotingPhase.Neutrals;
866
+ }
867
+
868
+ function _addOperation(
869
+ SLCProposalState storage proposalState,
870
+ OperationBundle memory operationBundle_,
871
+ bytes32 operationHash_
872
+ ) private returns (bool) {
873
+ if (!_containsOperation(proposalState, operationHash_)) {
874
+ proposalState.operationBundles.push(operationBundle_);
875
+ // The value is stored at length-1, but we add 1 to all indexes
876
+ // and use 0 as a sentinel value
877
+ proposalState.operationBundlePosition[operationHash_] = proposalState
878
+ .operationBundles
879
+ .length;
880
+ return true;
881
+ } else {
882
+ return false;
883
+ }
884
+ }
885
+
886
+ function _removeOperation(
887
+ SLCProposalState storage proposalState,
888
+ bytes32 operationHash_
889
+ ) private returns (bool) {
890
+ // We cache the value's position to prevent multiple reads from the same storage slot
891
+ uint256 position_ = proposalState.operationBundlePosition[operationHash_];
892
+
893
+ if (position_ != 0) {
894
+ // Equivalent to contains(set, value)
895
+ // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
896
+ // the array, and then remove the last element (sometimes called as 'swap and pop').
897
+
898
+ uint256 valueIndex_ = position_ - 1;
899
+ uint256 lastIndex_ = proposalState.operationBundles.length - 1;
900
+
901
+ if (valueIndex_ != lastIndex_) {
902
+ OperationBundle storage lastValue = proposalState.operationBundles[lastIndex_];
903
+
904
+ // Move the lastValue to the index where the value to delete is
905
+ proposalState.operationBundles[valueIndex_] = lastValue;
906
+ // Update the tracked position of the lastValue (that was just moved)
907
+ proposalState.operationBundlePosition[hashOperation(lastValue)] = position_;
908
+ }
909
+
910
+ // Delete the slot where the moved value was stored
911
+ proposalState.operationBundles.pop();
912
+
913
+ // Delete the tracked position for the deleted slot
914
+ delete proposalState.operationBundlePosition[operationHash_];
915
+
916
+ return true;
917
+ } else {
918
+ return false;
919
+ }
920
+ }
921
+
922
+ function _containsOperation(
923
+ SLCProposalState storage proposalState,
924
+ bytes32 operationHash_
925
+ ) private view returns (bool) {
926
+ return proposalState.operationBundlePosition[operationHash_] != 0;
927
+ }
928
+
929
+ function _findLeadingOperation(uint256 proposalId_) private view returns (bytes32) {
930
+ GovernanceStorage storage $ = _getGovernanceStorage();
931
+
932
+ SLCProposalState storage proposalState = $.proposalState[proposalId_];
933
+
934
+ uint256 operationBundlesLength_ = proposalState.operationBundles.length;
935
+ uint256 leadingOperationApprovals_ = 0;
936
+ bytes32 leadingOperation_ = bytes32(0);
937
+
938
+ for (uint256 i = 0; i < operationBundlesLength_; ++i) {
939
+ bytes32 operationHash_ = hashOperation(proposalState.operationBundles[i]);
940
+ uint256 operationApprovals_ = proposalState
941
+ .operationByNeutral[operationHash_]
942
+ .length();
943
+
944
+ if (
945
+ operationApprovals_ >= proposalState.neutralsThreshold &&
946
+ operationApprovals_ > leadingOperationApprovals_
947
+ ) {
948
+ leadingOperation_ = operationHash_;
949
+ leadingOperationApprovals_ = operationApprovals_;
950
+ }
951
+ }
952
+
953
+ return leadingOperation_;
954
+ }
955
+
956
+ function _getGovernanceStorage() private pure returns (GovernanceStorage storage $) {
957
+ /* solhint-disable-next-line no-inline-assembly */
958
+ assembly ("memory-safe") {
959
+ $.slot := GOVERNANCE_STORAGE_LOCATION
960
+ }
961
+ }
962
+
963
+ function _getGovernorStorageData() private pure returns (GovernorStorage storage $) {
964
+ /* solhint-disable-next-line no-inline-assembly */
965
+ assembly ("memory-safe") {
966
+ $.slot := GOVERNOR_STORAGE_LOCATION
967
+ }
968
+ }
969
+ }