2
2
3
3
pragma solidity 0.8.24 ;
4
4
5
- import {SortitionSumTreeBase } from "./SortitionSumTreeBase .sol " ;
5
+ import {ISortitionSumTree } from "./interfaces/ISortitionSumTree .sol " ;
6
6
import {IStakeController} from "./interfaces/IStakeController.sol " ;
7
+ import {KlerosCoreBase} from "./KlerosCoreBase.sol " ;
8
+ import {Initializable} from "../proxy/Initializable.sol " ;
9
+ import {UUPSProxiable} from "../proxy/UUPSProxiable.sol " ;
10
+ import "../libraries/Constants.sol " ;
7
11
8
12
/// @title SortitionSumTree
9
- /// @notice Basic implementation of the pure sortition module
13
+ /// @notice Responsible for sortition operations
10
14
/// @dev Contains only tree management and drawing logic, no phase management or token operations
11
- contract SortitionSumTree is SortitionSumTreeBase {
15
+ contract SortitionSumTree is ISortitionSumTree , Initializable , UUPSProxiable {
12
16
string public constant override version = "2.0.0 " ;
13
17
18
+ // ************************************* //
19
+ // * Enums / Structs * //
20
+ // ************************************* //
21
+
22
+ struct SumTree {
23
+ uint256 K; // The maximum number of children per node.
24
+ // We use this to keep track of vacant positions in the tree after removing a leaf. This is for keeping the tree as balanced as possible without spending gas on moving nodes around.
25
+ uint256 [] stack;
26
+ uint256 [] nodes;
27
+ // Two-way mapping of IDs to node indexes. Note that node index 0 is reserved for the root node, and means the ID does not have a node.
28
+ mapping (bytes32 => uint256 ) IDsToNodeIndexes;
29
+ mapping (uint256 => bytes32 ) nodeIndexesToIDs;
30
+ }
31
+
32
+ // ************************************* //
33
+ // * Storage * //
34
+ // ************************************* //
35
+
36
+ address public governor; // The governor of the contract.
37
+ IStakeController public stakeController; // The stake controller for coordination.
38
+
39
+ mapping (bytes32 treeHash = > SumTree) internal sortitionSumTrees; // The mapping trees by keys.
40
+
41
+ // ************************************* //
42
+ // * Function Modifiers * //
43
+ // ************************************* //
44
+
45
+ modifier onlyByGovernor () {
46
+ if (governor != msg .sender ) revert GovernorOnly ();
47
+ _;
48
+ }
49
+
50
+ modifier onlyByStakeController () {
51
+ if (address (stakeController) != msg .sender ) revert StakeControllerOnly ();
52
+ _;
53
+ }
54
+
55
+ // ************************************* //
56
+ // * Constructor * //
57
+ // ************************************* //
58
+
14
59
// ************************************* //
15
60
// * Constructor * //
16
61
// ************************************* //
@@ -24,7 +69,8 @@ contract SortitionSumTree is SortitionSumTreeBase {
24
69
/// @param _governor The governor's address.
25
70
/// @param _stakeController The StakeController contract.
26
71
function initialize (address _governor , IStakeController _stakeController ) external initializer {
27
- __SortitionSumTreeBase_initialize (_governor, _stakeController);
72
+ governor = _governor;
73
+ stakeController = _stakeController;
28
74
}
29
75
30
76
// ************************************* //
@@ -36,4 +82,309 @@ contract SortitionSumTree is SortitionSumTreeBase {
36
82
function _authorizeUpgrade (address ) internal view override onlyByGovernor {
37
83
// NOP
38
84
}
85
+
86
+ /// @dev Changes the governor of the contract.
87
+ /// @param _governor The new governor.
88
+ function changeGovernor (address _governor ) external onlyByGovernor {
89
+ governor = _governor;
90
+ }
91
+
92
+ /// @dev Changes the `stakeController` storage variable.
93
+ /// @param _stakeController The new stake controller address.
94
+ function changeStakeController (IStakeController _stakeController ) external onlyByGovernor {
95
+ stakeController = _stakeController;
96
+ }
97
+
98
+ // ************************************* //
99
+ // * Tree Management * //
100
+ // ************************************* //
101
+
102
+ /// @inheritdoc ISortitionSumTree
103
+ function createTree (bytes32 _key , bytes memory _extraData ) external override onlyByStakeController {
104
+ SumTree storage tree = sortitionSumTrees[_key];
105
+ uint256 K = _extraDataToTreeK (_extraData);
106
+ if (tree.K != 0 ) revert TreeAlreadyExists ();
107
+ if (K <= 1 ) revert InvalidTreeK ();
108
+ tree.K = K;
109
+ tree.nodes.push (0 );
110
+ }
111
+
112
+ /// @inheritdoc ISortitionSumTree
113
+ function setStake (
114
+ address _account ,
115
+ uint96 _courtID ,
116
+ uint256 _newStake
117
+ ) external virtual override onlyByStakeController {
118
+ bytes32 stakePathID = _accountAndCourtIDToStakePathID (_account, _courtID);
119
+ bool finished = false ;
120
+ uint96 currentCourtID = _courtID;
121
+ KlerosCoreBase core = stakeController.core ();
122
+
123
+ while (! finished) {
124
+ _set (bytes32 (uint256 (currentCourtID)), _newStake, stakePathID);
125
+ if (currentCourtID == GENERAL_COURT) {
126
+ finished = true ;
127
+ } else {
128
+ // Fetch parent court ID. Ensure core.courts() is accessible and correct.
129
+ (uint96 parentCourtID , , , , , , ) = core.courts (currentCourtID);
130
+ if (parentCourtID == currentCourtID) {
131
+ // Avoid infinite loop if parent is self (e.g. for general court already handled or misconfiguration)
132
+ finished = true ;
133
+ } else {
134
+ currentCourtID = parentCourtID;
135
+ }
136
+ }
137
+ }
138
+ }
139
+
140
+ // ************************************* //
141
+ // * Drawing * //
142
+ // ************************************* //
143
+
144
+ /// @inheritdoc ISortitionSumTree
145
+ function draw (
146
+ bytes32 _court ,
147
+ uint256 _coreDisputeID ,
148
+ uint256 _nonce ,
149
+ uint256 _randomNumber
150
+ ) external view virtual override returns (address drawnAddress ) {
151
+ SumTree storage tree = sortitionSumTrees[_court];
152
+
153
+ if (tree.nodes.length == 0 || tree.nodes[0 ] == 0 ) {
154
+ return address (0 ); // No jurors staked.
155
+ }
156
+
157
+ uint256 currentDrawnNumber = uint256 (keccak256 (abi.encodePacked (_randomNumber, _coreDisputeID, _nonce))) %
158
+ tree.nodes[0 ];
159
+
160
+ // While it still has children
161
+ uint256 treeIndex = 0 ;
162
+ while ((tree.K * treeIndex) + 1 < tree.nodes.length ) {
163
+ for (uint256 i = 1 ; i <= tree.K; i++ ) {
164
+ // Loop over children.
165
+ uint256 nodeIndex = (tree.K * treeIndex) + i;
166
+ uint256 nodeValue = tree.nodes[nodeIndex];
167
+
168
+ if (currentDrawnNumber >= nodeValue) {
169
+ // Go to the next child.
170
+ currentDrawnNumber -= nodeValue;
171
+ } else {
172
+ // Pick this child.
173
+ treeIndex = nodeIndex;
174
+ break ;
175
+ }
176
+ }
177
+ }
178
+ drawnAddress = _stakePathIDToAccount (tree.nodeIndexesToIDs[treeIndex]);
179
+ }
180
+
181
+ // ************************************* //
182
+ // * View Functions * //
183
+ // ************************************* //
184
+
185
+ /// @inheritdoc ISortitionSumTree
186
+ function stakeOf (address _juror , uint96 _courtID ) external view override returns (uint256 value ) {
187
+ bytes32 stakePathID = _accountAndCourtIDToStakePathID (_juror, _courtID);
188
+ return stakeOf (bytes32 (uint256 (_courtID)), stakePathID);
189
+ }
190
+
191
+ /// @inheritdoc ISortitionSumTree
192
+ function stakeOf (bytes32 _key , bytes32 _ID ) public view override returns (uint256 ) {
193
+ SumTree storage tree = sortitionSumTrees[_key];
194
+ uint treeIndex = tree.IDsToNodeIndexes[_ID];
195
+ if (treeIndex == 0 ) {
196
+ return 0 ;
197
+ }
198
+ return tree.nodes[treeIndex];
199
+ }
200
+
201
+ /// @inheritdoc ISortitionSumTree
202
+ function getTotalStakeInCourt (uint96 _courtID ) external view override returns (uint256 ) {
203
+ SumTree storage tree = sortitionSumTrees[bytes32 (uint256 (_courtID))];
204
+ if (tree.nodes.length == 0 ) return 0 ;
205
+ return tree.nodes[0 ]; // Root node contains total stake
206
+ }
207
+
208
+ /// @inheritdoc ISortitionSumTree
209
+ function accountAndCourtIDToStakePathID (
210
+ address _account ,
211
+ uint96 _courtID
212
+ ) external pure override returns (bytes32 stakePathID ) {
213
+ return _accountAndCourtIDToStakePathID (_account, _courtID);
214
+ }
215
+
216
+ /// @inheritdoc ISortitionSumTree
217
+ function stakePathIDToAccount (bytes32 _stakePathID ) external pure override returns (address account ) {
218
+ return _stakePathIDToAccount (_stakePathID);
219
+ }
220
+
221
+ // ************************************* //
222
+ // * Internal * //
223
+ // ************************************* //
224
+
225
+ /// @dev Update all the parents of a node.
226
+ /// @param _key The key of the tree to update.
227
+ /// @param _treeIndex The index of the node to start from.
228
+ /// @param _plusOrMinus Whether to add (true) or substract (false).
229
+ /// @param _value The value to add or substract.
230
+ /// `O(log_k(n))` where
231
+ /// `k` is the maximum number of children per node in the tree,
232
+ /// and `n` is the maximum number of nodes ever appended.
233
+ function _updateParents (bytes32 _key , uint256 _treeIndex , bool _plusOrMinus , uint256 _value ) private {
234
+ SumTree storage tree = sortitionSumTrees[_key];
235
+
236
+ uint256 parentIndex = _treeIndex;
237
+ while (parentIndex != 0 ) {
238
+ parentIndex = (parentIndex - 1 ) / tree.K;
239
+ tree.nodes[parentIndex] = _plusOrMinus
240
+ ? tree.nodes[parentIndex] + _value
241
+ : tree.nodes[parentIndex] - _value;
242
+ }
243
+ }
244
+
245
+ /// @dev Retrieves a juror's address from the stake path ID.
246
+ /// @param _stakePathID The stake path ID to unpack.
247
+ /// @return account The account.
248
+ function _stakePathIDToAccount (bytes32 _stakePathID ) internal pure returns (address account ) {
249
+ assembly {
250
+ // solium-disable-line security/no-inline-assembly
251
+ let ptr := mload (0x40 )
252
+ for {
253
+ let i := 0x00
254
+ } lt (i, 0x14 ) {
255
+ i := add (i, 0x01 )
256
+ } {
257
+ mstore8 (add (add (ptr, 0x0c ), i), byte (i, _stakePathID))
258
+ }
259
+ account := mload (ptr)
260
+ }
261
+ }
262
+
263
+ function _extraDataToTreeK (bytes memory _extraData ) internal pure returns (uint256 K ) {
264
+ if (_extraData.length >= 32 ) {
265
+ assembly {
266
+ // solium-disable-line security/no-inline-assembly
267
+ K := mload (add (_extraData, 0x20 ))
268
+ }
269
+ } else {
270
+ K = DEFAULT_K;
271
+ }
272
+ }
273
+
274
+ /// @dev Set a value in a tree.
275
+ /// @param _key The key of the tree.
276
+ /// @param _value The new value.
277
+ /// @param _ID The ID of the value.
278
+ /// `O(log_k(n))` where
279
+ /// `k` is the maximum number of children per node in the tree,
280
+ /// and `n` is the maximum number of nodes ever appended.
281
+ function _set (bytes32 _key , uint256 _value , bytes32 _ID ) internal {
282
+ SumTree storage tree = sortitionSumTrees[_key];
283
+ uint256 treeIndex = tree.IDsToNodeIndexes[_ID];
284
+
285
+ if (treeIndex == 0 ) {
286
+ // No existing node.
287
+ if (_value != 0 ) {
288
+ // Non zero value.
289
+ // Append.
290
+ // Add node.
291
+ if (tree.stack.length == 0 ) {
292
+ // No vacant spots.
293
+ // Get the index and append the value.
294
+ treeIndex = tree.nodes.length ;
295
+ tree.nodes.push (_value);
296
+
297
+ // Potentially append a new node and make the parent a sum node.
298
+ if (treeIndex != 1 && (treeIndex - 1 ) % tree.K == 0 ) {
299
+ // Is first child.
300
+ uint256 parentIndex = treeIndex / tree.K;
301
+ bytes32 parentID = tree.nodeIndexesToIDs[parentIndex];
302
+ uint256 newIndex = treeIndex + 1 ;
303
+ tree.nodes.push (tree.nodes[parentIndex]);
304
+ delete tree.nodeIndexesToIDs[parentIndex];
305
+ tree.IDsToNodeIndexes[parentID] = newIndex;
306
+ tree.nodeIndexesToIDs[newIndex] = parentID;
307
+ }
308
+ } else {
309
+ // Some vacant spot.
310
+ // Pop the stack and append the value.
311
+ treeIndex = tree.stack[tree.stack.length - 1 ];
312
+ tree.stack.pop ();
313
+ tree.nodes[treeIndex] = _value;
314
+ }
315
+
316
+ // Add label.
317
+ tree.IDsToNodeIndexes[_ID] = treeIndex;
318
+ tree.nodeIndexesToIDs[treeIndex] = _ID;
319
+
320
+ _updateParents (_key, treeIndex, true , _value);
321
+ }
322
+ } else {
323
+ // Existing node.
324
+ if (_value == 0 ) {
325
+ // Zero value.
326
+ // Remove.
327
+ // Remember value and set to 0.
328
+ uint256 value = tree.nodes[treeIndex];
329
+ tree.nodes[treeIndex] = 0 ;
330
+
331
+ // Push to stack.
332
+ tree.stack.push (treeIndex);
333
+
334
+ // Clear label.
335
+ delete tree.IDsToNodeIndexes[_ID];
336
+ delete tree.nodeIndexesToIDs[treeIndex];
337
+
338
+ _updateParents (_key, treeIndex, false , value);
339
+ } else if (_value != tree.nodes[treeIndex]) {
340
+ // New, non zero value.
341
+ // Set.
342
+ bool plusOrMinus = tree.nodes[treeIndex] <= _value;
343
+ uint256 plusOrMinusValue = plusOrMinus
344
+ ? _value - tree.nodes[treeIndex]
345
+ : tree.nodes[treeIndex] - _value;
346
+ tree.nodes[treeIndex] = _value;
347
+
348
+ _updateParents (_key, treeIndex, plusOrMinus, plusOrMinusValue);
349
+ }
350
+ }
351
+ }
352
+
353
+ /// @dev Packs an account and a court ID into a stake path ID.
354
+ /// @param _account The address of the juror to pack.
355
+ /// @param _courtID The court ID to pack.
356
+ /// @return stakePathID The stake path ID.
357
+ function _accountAndCourtIDToStakePathID (
358
+ address _account ,
359
+ uint96 _courtID
360
+ ) internal pure returns (bytes32 stakePathID ) {
361
+ assembly {
362
+ // solium-disable-line security/no-inline-assembly
363
+ let ptr := mload (0x40 )
364
+ for {
365
+ let i := 0x00
366
+ } lt (i, 0x14 ) {
367
+ i := add (i, 0x01 )
368
+ } {
369
+ mstore8 (add (ptr, i), byte (add (0x0c , i), _account))
370
+ }
371
+ for {
372
+ let i := 0x14
373
+ } lt (i, 0x20 ) {
374
+ i := add (i, 0x01 )
375
+ } {
376
+ mstore8 (add (ptr, i), byte (i, _courtID))
377
+ }
378
+ stakePathID := mload (ptr)
379
+ }
380
+ }
381
+
382
+ // ************************************* //
383
+ // * Errors * //
384
+ // ************************************* //
385
+
386
+ error GovernorOnly ();
387
+ error StakeControllerOnly ();
388
+ error TreeAlreadyExists ();
389
+ error InvalidTreeK ();
39
390
}
0 commit comments