@web3dotorg/evm-slc-core-contracts 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/governance/ExecutorsRegistry.sol +710 -0
- package/governance/Governance.sol +969 -0
- package/governance/NeutralsRegistry.sol +1130 -0
- package/governance/ParameterRegistry.sol +381 -0
- package/interfaces/IExecutorsRegistry.sol +62 -0
- package/interfaces/IGovernance.sol +57 -0
- package/interfaces/INeutralsRegistry.sol +72 -0
- package/interfaces/IQParameters.sol +37 -0
- package/interfaces/ISLCCore.sol +59 -0
- package/interfaces/ISLCCoreFactory.sol +29 -0
- package/interfaces/IWrappedToken.sol +8 -0
- package/libs/Errors.sol +22 -0
- package/libs/NeutralsSelection.sol +78 -0
- package/mocks/Helper.sol +4 -0
- package/mocks/MockGovernance.sol +75 -0
- package/mocks/MockNeutralsRegistry.sol +126 -0
- package/mocks/SimpleContract.sol +14 -0
- package/mocks/WrappedToken.sol +24 -0
- package/package.json +27 -0
- package/slc-core/SLCCore.sol +119 -0
- package/slc-core/SLCCoreFactory.sol +215 -0
- package/token/Token.sol +20 -0
- package/utils/Globals.sol +7 -0
@@ -0,0 +1,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¶ms=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
|
+
}
|