Skip to content

Commit 2434168

Browse files
authored
Merge branch 'main' into annotate-rewards-tests
2 parents d4e14f4 + efe2b91 commit 2434168

5 files changed

Lines changed: 514 additions & 49 deletions

File tree

contracts/SortitionTree.sol

Lines changed: 98 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,18 @@ contract SortitionTree {
1919
// level6 256k
2020
// level7 2M
2121
uint256 internal root;
22+
23+
// A 2-index mapping from layer => (index (0-index) => branch). For example,
24+
// to access the 6th branch in the 2nd layer (right below the root node; the
25+
// first branch layer), call branches[2][5]. Mappings are used in place of
26+
// arrays for efficiency. The root is the first layer, the branches occupy
27+
// layers 2 through 7, and layer 8 is for the leaves. Following this
28+
// convention, the first index in `branches` is `2`, and the last index is
29+
// `7`.
2230
mapping(uint256 => mapping(uint256 => uint256)) internal branches;
31+
32+
// A 0-index mapping from index => leaf, acting as an array. For example, to
33+
// access the 42nd leaf, call leaves[41].
2334
mapping(uint256 => uint256) internal leaves;
2435

2536
// the flagged (see setFlag() and unsetFlag() in Position.sol) positions
@@ -28,6 +39,7 @@ contract SortitionTree {
2839

2940
// the leaf after the rightmost occupied leaf of each stack
3041
uint256 internal rightmostLeaf;
42+
3143
// the empty leaves in each stack
3244
// between 0 and the rightmost occupied leaf
3345
uint256[] internal emptyLeaves;
@@ -36,6 +48,7 @@ contract SortitionTree {
3648
// which is allocated when they first join the pool
3749
// and remains unchanged even if they leave and rejoin the pool.
3850
mapping(address => uint32) internal operatorID;
51+
3952
// The idAddress array records the address corresponding to each ID number.
4053
// The ID number 0 is initialized with a zero address and is not used.
4154
address[] internal idAddress;
@@ -46,22 +59,28 @@ contract SortitionTree {
4659
idAddress.push();
4760
}
4861

49-
// Return the ID number of the given operator address.
50-
// An ID number of 0 means the operator has not been allocated an ID number yet.
62+
/// @notice Return the ID number of the given operator address. An ID number
63+
/// of 0 means the operator has not been allocated an ID number yet.
64+
/// @param operator Address of the operator.
65+
/// @return the ID number of the given operator address
5166
function getOperatorID(address operator) public view returns (uint32) {
5267
return operatorID[operator];
5368
}
5469

55-
// Get the operator address corresponding to the given ID number.
56-
// An empty address means the ID number has not been allocated yet.
70+
/// @notice Get the operator address corresponding to the given ID number. A
71+
/// zero address means the ID number has not been allocated yet.
72+
/// @param id ID of the operator
73+
/// @return the address of the operator
5774
function getIDOperator(uint32 id) public view returns (address) {
5875
return idAddress.length > id ? idAddress[id] : address(0);
5976
}
6077

61-
// Gets the operator addresses corresponding to the given ID numbers.
62-
// An empty address means the ID number has not been allocated yet.
63-
// This function works just like getIDOperator except that it allows to fetch
64-
// operator addresses for multiple IDs in one call.
78+
/// @notice Gets the operator addresses corresponding to the given ID
79+
/// numbers. A zero address means the ID number has not been allocated yet.
80+
/// This function works just like getIDOperator except that it allows to fetch
81+
/// operator addresses for multiple IDs in one call.
82+
/// @param ids the array of the operator ids
83+
/// @return an array of the associated operator addresses
6584
function getIDOperators(uint32[] calldata ids)
6685
public
6786
view
@@ -77,12 +96,15 @@ contract SortitionTree {
7796
return operators;
7897
}
7998

80-
// checks if operator is already registered in the pool
99+
/// @notice Checks if operator is already registered in the pool.
100+
/// @param operator the address of the operator
101+
/// @return whether or not the operator is already registered in the pool
81102
function isOperatorRegistered(address operator) public view returns (bool) {
82103
return getFlaggedLeafPosition(operator) != 0;
83104
}
84105

85-
// Sum the number of operators in each trunk
106+
/// @notice Sum the number of operators in each trunk.
107+
/// @return the number of operators in the pool
86108
function operatorsInPool() public view returns (uint256) {
87109
// Get the number of leaves that might be occupied;
88110
// if `rightmostLeaf` equals `firstLeaf()` the tree must be empty,
@@ -96,12 +118,16 @@ contract SortitionTree {
96118
return (nPossiblyUsedLeaves - nEmptyLeaves);
97119
}
98120

121+
/// @notice Convenience method to return the total weight of the pool
122+
/// @return the total weight of the pool
99123
function totalWeight() public view returns (uint256) {
100124
return root.sumWeight();
101125
}
102126

103-
// Give the operator a new ID number
104-
// Does not check if the operator already has an ID number
127+
/// @notice Give the operator a new ID number.
128+
/// Does not check if the operator already has an ID number.
129+
/// @param operator the address of the operator
130+
/// @return a new ID for that operator
105131
function allocateOperatorID(address operator) internal returns (uint256) {
106132
uint256 id = idAddress.length;
107133

@@ -112,36 +138,51 @@ contract SortitionTree {
112138
return id;
113139
}
114140

141+
/// @notice Inserts an operator into the sortition pool
142+
/// @param operator the address of an operator to insert
143+
/// @param weight how much weight that operator has in the pool
115144
function _insertOperator(address operator, uint256 weight) internal {
116145
require(
117146
!isOperatorRegistered(operator),
118147
"Operator is already registered in the pool"
119148
);
120149

150+
// Fetch the operator's ID, and if they don't have one, allocate them one.
121151
uint256 id = getOperatorID(operator);
122152
if (id == 0) {
123153
id = allocateOperatorID(operator);
124154
}
125155

156+
// Determine which leaf to insert them into
126157
uint256 position = getEmptyLeafPosition();
127158
// Record the block the operator was inserted in
128159
uint256 theLeaf = Leaf.make(operator, block.number, id);
129160

161+
// Update the leaf, and propagate the weight changes all the way up to the
162+
// root.
130163
root = setLeaf(position, theLeaf, weight, root);
131164

132165
// Without position flags,
133166
// the position 0x000000 would be treated as empty
134167
flaggedLeafPosition[operator] = position.setFlag();
135168
}
136169

170+
/// @notice Remove an operator (and their weight) from the pool.
171+
/// @param operator the address of the operator to remove
137172
function _removeOperator(address operator) internal {
138173
uint256 flaggedPosition = getFlaggedLeafPosition(operator);
139174
require(flaggedPosition != 0, "Operator is not registered in the pool");
140175
uint256 unflaggedPosition = flaggedPosition.unsetFlag();
176+
177+
// Update the leaf, and propagate the weight changes all the way up to the
178+
// root.
141179
root = removeLeaf(unflaggedPosition, root);
142180
removeLeafPositionRecord(operator);
143181
}
144182

183+
/// @notice Update an operator's weight in the pool.
184+
/// @param operator the address of the operator to update
185+
/// @param weight the new weight
145186
function updateOperator(address operator, uint256 weight) internal {
146187
require(
147188
isOperatorRegistered(operator),
@@ -153,19 +194,28 @@ contract SortitionTree {
153194
root = updateLeaf(unflaggedPosition, weight, root);
154195
}
155196

197+
/// @notice Helper method to remove a leaf position record for an operator.
198+
/// @param operator the address of the operator to remove the record for
156199
function removeLeafPositionRecord(address operator) internal {
157200
flaggedLeafPosition[operator] = 0;
158201
}
159202

203+
/// @notice Removes the data and weight from a particular leaf.
204+
/// @param position the leaf index to remove
205+
/// @param _root the root node containing the leaf
206+
/// @return the updated root node
160207
function removeLeaf(uint256 position, uint256 _root)
161208
internal
162209
returns (uint256)
163210
{
164211
uint256 rightmostSubOne = rightmostLeaf - 1;
165212
bool isRightmost = position == rightmostSubOne;
166213

214+
// Clears out the data in the leaf node, and then propagates the weight
215+
// changes all the way up to the root.
167216
uint256 newRoot = setLeaf(position, 0, 0, _root);
168217

218+
// Infer if need to fall back on emptyLeaves yet
169219
if (isRightmost) {
170220
rightmostLeaf = rightmostSubOne;
171221
} else {
@@ -174,6 +224,11 @@ contract SortitionTree {
174224
return newRoot;
175225
}
176226

227+
/// @notice Updates the tree to give a particular leaf a new weight.
228+
/// @param position the index of the leaf to update
229+
/// @param weight the new weight
230+
/// @param _root the root node containing the leaf
231+
/// @return the updated root node
177232
function updateLeaf(
178233
uint256 position,
179234
uint256 weight,
@@ -186,6 +241,13 @@ contract SortitionTree {
186241
}
187242
}
188243

244+
/// @notice Places a leaf into a particular position, with a given weight and
245+
/// propagates that change.
246+
/// @param position the index to place the leaf in
247+
/// @param theLeaf the new leaf to place in the position
248+
/// @param leafWeight the weight of the leaf
249+
/// @param _root the root containing the new leaf
250+
/// @return the updated root node
189251
function setLeaf(
190252
uint256 position,
191253
uint256 theLeaf,
@@ -198,6 +260,12 @@ contract SortitionTree {
198260
return (updateTree(position, leafWeight, _root));
199261
}
200262

263+
/// @notice Propagates a weight change at a position through the tree,
264+
/// eventually returning the updated root.
265+
/// @param position the index of leaf to update
266+
/// @param weight the new weight of the leaf
267+
/// @param _root the root node containing the leaf
268+
/// @return the updated root node
201269
function updateTree(
202270
uint256 position,
203271
uint256 weight,
@@ -224,6 +292,10 @@ contract SortitionTree {
224292
return _root.setSlot(childSlot, nodeWeight);
225293
}
226294

295+
/// @notice Retrieves the next available empty leaf position. Tries to fill
296+
/// left to right first, ignoring leaf removals, and then fills
297+
/// most-recent-removals first.
298+
/// @return the position of the empty leaf
227299
function getEmptyLeafPosition() internal returns (uint256) {
228300
uint256 rLeaf = rightmostLeaf;
229301
bool spaceOnRight = (rLeaf + 1) < Constants.POOL_CAPACITY;
@@ -239,6 +311,9 @@ contract SortitionTree {
239311
}
240312
}
241313

314+
/// @notice Gets the flagged leaf position for an operator.
315+
/// @param operator the address of the operator
316+
/// @return the leaf position of that operator
242317
function getFlaggedLeafPosition(address operator)
243318
internal
244319
view
@@ -247,13 +322,24 @@ contract SortitionTree {
247322
return flaggedLeafPosition[operator];
248323
}
249324

325+
/// @notice Gets the weight of a leaf at a particular position.
326+
/// @param position the index of the leaf
327+
/// @return the weight of the leaf at that position
250328
function getLeafWeight(uint256 position) internal view returns (uint256) {
251329
uint256 slot = position.slot();
252330
uint256 parent = position.parent();
331+
332+
// A leaf's weight information is stored a 32-bit slot in the branch layer
333+
// directly above the leaf layer. To access it, we calculate that slot and
334+
// parent position, and always know the hard-coded layer index.
253335
uint256 node = branches[Constants.LEVELS][parent];
254336
return node.getSlot(slot);
255337
}
256338

339+
/// @notice Picks a leaf given a random index.
340+
/// @param index a number in `[0, _root.totalWeight())` used to decide
341+
/// between leaves
342+
/// @param _root the root of the tree
257343
function pickWeightedLeaf(uint256 index, uint256 _root)
258344
internal
259345
view

contracts/test/LeafStub.sol

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
pragma solidity 0.8.9;
2+
3+
import "../Leaf.sol";
4+
5+
contract LeafStub {
6+
function make(
7+
address operator,
8+
uint256 creationBlock,
9+
uint256 id
10+
) public pure returns (uint256) {
11+
return Leaf.make(operator, creationBlock, id);
12+
}
13+
14+
function operator(uint256 leaf) public pure returns (address) {
15+
return Leaf.operator(leaf);
16+
}
17+
18+
function creationBlock(uint256 leaf) public pure returns (uint256) {
19+
return Leaf.creationBlock(leaf);
20+
}
21+
22+
function id(uint256 leaf) public pure returns (uint32) {
23+
return Leaf.id(leaf);
24+
}
25+
}

0 commit comments

Comments
 (0)