Skip to content

Commit 9abcf64

Browse files
committed
refactor: removed unnecessary base contract for Stake Controller and Sortition Sum Tree
1 parent 0eb3292 commit 9abcf64

File tree

6 files changed

+383
-450
lines changed

6 files changed

+383
-450
lines changed

contracts/src/arbitration/KlerosCoreBase.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,7 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable
469469
}
470470

471471
/// @notice Executes a stake change initiated by the system (e.g., processing a delayed stake).
472-
/// @dev Called by StakeControllerBase during executeDelayedStakes. Assumes KlerosCore holds pre-funded PNK if _depositPreFunded is true.
472+
/// @dev Called by StakeController during executeDelayedStakes. Assumes KlerosCore holds pre-funded PNK if _depositPreFunded is true.
473473
/// @param _account The juror's account.
474474
/// @param _courtID The ID of the court.
475475
/// @param _newStake The new stake amount for the juror in the court.

contracts/src/arbitration/SortitionSumTree.sol

Lines changed: 355 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,60 @@
22

33
pragma solidity 0.8.24;
44

5-
import {SortitionSumTreeBase} from "./SortitionSumTreeBase.sol";
5+
import {ISortitionSumTree} from "./interfaces/ISortitionSumTree.sol";
66
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";
711

812
/// @title SortitionSumTree
9-
/// @notice Basic implementation of the pure sortition module
13+
/// @notice Responsible for sortition operations
1014
/// @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 {
1216
string public constant override version = "2.0.0";
1317

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+
1459
// ************************************* //
1560
// * Constructor * //
1661
// ************************************* //
@@ -24,7 +69,8 @@ contract SortitionSumTree is SortitionSumTreeBase {
2469
/// @param _governor The governor's address.
2570
/// @param _stakeController The StakeController contract.
2671
function initialize(address _governor, IStakeController _stakeController) external initializer {
27-
__SortitionSumTreeBase_initialize(_governor, _stakeController);
72+
governor = _governor;
73+
stakeController = _stakeController;
2874
}
2975

3076
// ************************************* //
@@ -36,4 +82,309 @@ contract SortitionSumTree is SortitionSumTreeBase {
3682
function _authorizeUpgrade(address) internal view override onlyByGovernor {
3783
// NOP
3884
}
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();
39390
}

0 commit comments

Comments
 (0)