Skip to content

Commit 11191ce

Browse files
authored
Merge branch 'main' into implementation-details-readme
2 parents 48252bd + f9a8e92 commit 11191ce

15 files changed

Lines changed: 55898 additions & 873 deletions

contracts/Branch.sol

Lines changed: 17 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,11 @@
11
pragma solidity 0.8.9;
22

3+
import "./Constants.sol";
4+
35
/// @notice The implicit 8-ary trees of the sortition pool
46
/// rely on packing 8 "slots" of 32-bit values into each uint256.
57
/// The Branch library permits efficient calculations on these slots.
68
library Branch {
7-
////////////////////////////////////////////////////////////////////////////
8-
// Parameters for configuration
9-
10-
// How many bits a position uses per level of the tree;
11-
// each branch of the tree contains 2**SLOT_BITS slots.
12-
uint256 private constant SLOT_BITS = 3;
13-
////////////////////////////////////////////////////////////////////////////
14-
15-
////////////////////////////////////////////////////////////////////////////
16-
// Derived constants, do not touch
17-
uint256 private constant SLOT_COUNT = 2**SLOT_BITS;
18-
uint256 private constant SLOT_WIDTH = 256 / SLOT_COUNT;
19-
uint256 private constant LAST_SLOT = SLOT_COUNT - 1;
20-
uint256 private constant SLOT_MAX = (2**SLOT_WIDTH) - 1;
21-
22-
////////////////////////////////////////////////////////////////////////////
23-
249
/// @notice Calculate the right shift required
2510
/// to make the 32 least significant bits of an uint256
2611
/// be the bits of the `position`th slot
@@ -31,7 +16,7 @@ library Branch {
3116
/// I wish solidity had macros, even C macros.
3217
function slotShift(uint256 position) internal pure returns (uint256) {
3318
unchecked {
34-
return position * SLOT_WIDTH;
19+
return position * Constants.SLOT_WIDTH;
3520
}
3621
}
3722

@@ -43,12 +28,12 @@ library Branch {
4328
returns (uint256)
4429
{
4530
unchecked {
46-
uint256 shiftBits = position * SLOT_WIDTH;
31+
uint256 shiftBits = position * Constants.SLOT_WIDTH;
4732
// Doing a bitwise AND with `SLOT_MAX`
4833
// clears all but the 32 least significant bits.
4934
// Because of the right shift by `slotShift(position)` bits,
5035
// those 32 bits contain the 32 bits in the `position`th slot of `node`.
51-
return (node >> shiftBits) & SLOT_MAX;
36+
return (node >> shiftBits) & Constants.SLOT_MAX;
5237
}
5338
}
5439

@@ -59,7 +44,7 @@ library Branch {
5944
returns (uint256)
6045
{
6146
unchecked {
62-
uint256 shiftBits = position * SLOT_WIDTH;
47+
uint256 shiftBits = position * Constants.SLOT_WIDTH;
6348
// Shifting `SLOT_MAX` left by `slotShift(position)` bits
6449
// gives us a number where all bits of the `position`th slot are set,
6550
// and all other bits are unset.
@@ -71,7 +56,7 @@ library Branch {
7156
// Bitwise ANDing the original `node` with this number
7257
// sets the bits of `position`th slot to zero,
7358
// leaving all other bits unchanged.
74-
return node & ~(SLOT_MAX << shiftBits);
59+
return node & ~(Constants.SLOT_MAX << shiftBits);
7560
}
7661
}
7762

@@ -86,17 +71,17 @@ library Branch {
8671
uint256 weight
8772
) internal pure returns (uint256) {
8873
unchecked {
89-
uint256 shiftBits = position * SLOT_WIDTH;
74+
uint256 shiftBits = position * Constants.SLOT_WIDTH;
9075
// Clear the `position`th slot like in `clearSlot()`.
91-
uint256 clearedNode = node & ~(SLOT_MAX << shiftBits);
76+
uint256 clearedNode = node & ~(Constants.SLOT_MAX << shiftBits);
9277
// Bitwise AND `weight` with `SLOT_MAX`
9378
// to clear all but the 32 least significant bits.
9479
//
9580
// Shift this left by `slotShift(position)` bits
9681
// to obtain a uint256 with all bits unset
9782
// except in the `position`th slot
9883
// which contains the 32-bit value of `weight`.
99-
uint256 shiftedWeight = (weight & SLOT_MAX) << shiftBits;
84+
uint256 shiftedWeight = (weight & Constants.SLOT_MAX) << shiftBits;
10085
// When we bitwise OR these together,
10186
// all other slots except the `position`th one come from the left argument,
10287
// and the `position`th gets filled with `weight` from the right argument.
@@ -107,14 +92,14 @@ library Branch {
10792
/// @notice Calculate the summed weight of all slots in the `node`.
10893
function sumWeight(uint256 node) internal pure returns (uint256 sum) {
10994
unchecked {
110-
sum = node & SLOT_MAX;
95+
sum = node & Constants.SLOT_MAX;
11196
// Iterate through each slot
11297
// by shifting `node` right in increments of 32 bits,
11398
// and adding the 32 least significant bits to the `sum`.
114-
uint256 newNode = node >> SLOT_WIDTH;
99+
uint256 newNode = node >> Constants.SLOT_WIDTH;
115100
while (newNode > 0) {
116-
sum += (newNode & SLOT_MAX);
117-
newNode = newNode >> SLOT_WIDTH;
101+
sum += (newNode & Constants.SLOT_MAX);
102+
newNode = newNode >> Constants.SLOT_WIDTH;
118103
}
119104
return sum;
120105
}
@@ -143,12 +128,12 @@ library Branch {
143128
unchecked {
144129
newIndex = index;
145130
uint256 newNode = node;
146-
uint256 currentSlotWeight = newNode & SLOT_MAX;
131+
uint256 currentSlotWeight = newNode & Constants.SLOT_MAX;
147132
while (newIndex >= currentSlotWeight) {
148133
newIndex -= currentSlotWeight;
149134
slot++;
150-
newNode = newNode >> SLOT_WIDTH;
151-
currentSlotWeight = newNode & SLOT_MAX;
135+
newNode = newNode >> Constants.SLOT_WIDTH;
136+
currentSlotWeight = newNode & Constants.SLOT_MAX;
152137
}
153138
return (slot, newIndex);
154139
}

contracts/Constants.sol

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
pragma solidity 0.8.9;
2+
3+
library Constants {
4+
////////////////////////////////////////////////////////////////////////////
5+
// Parameters for configuration
6+
7+
// How many bits a position uses per level of the tree;
8+
// each branch of the tree contains 2**SLOT_BITS slots.
9+
uint256 constant SLOT_BITS = 3;
10+
uint256 constant LEVELS = 7;
11+
////////////////////////////////////////////////////////////////////////////
12+
13+
////////////////////////////////////////////////////////////////////////////
14+
// Derived constants, do not touch
15+
uint256 constant SLOT_COUNT = 2**SLOT_BITS;
16+
uint256 constant SLOT_WIDTH = 256 / SLOT_COUNT;
17+
uint256 constant LAST_SLOT = SLOT_COUNT - 1;
18+
uint256 constant SLOT_MAX = (2**SLOT_WIDTH) - 1;
19+
uint256 constant POOL_CAPACITY = SLOT_COUNT**LEVELS;
20+
21+
uint256 constant ID_WIDTH = SLOT_WIDTH;
22+
uint256 constant ID_MAX = SLOT_MAX;
23+
24+
uint256 constant BLOCKHEIGHT_WIDTH = 96 - ID_WIDTH;
25+
uint256 constant BLOCKHEIGHT_MAX = (2**BLOCKHEIGHT_WIDTH) - 1;
26+
27+
uint256 constant SLOT_POINTER_MAX = (2**SLOT_BITS) - 1;
28+
uint256 constant LEAF_FLAG = 1 << 255;
29+
30+
uint256 constant WEIGHT_WIDTH = 256 / SLOT_COUNT;
31+
////////////////////////////////////////////////////////////////////////////
32+
}

contracts/Leaf.sol

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,8 @@
11
pragma solidity 0.8.9;
22

3-
library Leaf {
4-
////////////////////////////////////////////////////////////////////////////
5-
// Parameters for configuration
6-
7-
// How many bits a position uses per level of the tree;
8-
// each branch of the tree contains 2**SLOT_BITS slots.
9-
uint256 private constant SLOT_BITS = 3;
10-
////////////////////////////////////////////////////////////////////////////
11-
12-
////////////////////////////////////////////////////////////////////////////
13-
// Derived constants, do not touch
14-
uint256 private constant SLOT_COUNT = 2**SLOT_BITS;
15-
uint256 private constant SLOT_WIDTH = 256 / SLOT_COUNT;
16-
uint256 private constant SLOT_MAX = (2**SLOT_WIDTH) - 1;
17-
18-
uint256 private constant ID_WIDTH = SLOT_WIDTH;
19-
uint256 private constant ID_MAX = SLOT_MAX;
20-
21-
uint256 private constant BLOCKHEIGHT_WIDTH = 96 - ID_WIDTH;
22-
uint256 private constant BLOCKHEIGHT_MAX = (2**BLOCKHEIGHT_WIDTH) - 1;
23-
24-
////////////////////////////////////////////////////////////////////////////
3+
import "./Constants.sol";
254

5+
library Leaf {
266
function make(
277
address _operator,
288
uint256 _creationBlock,
@@ -35,10 +15,11 @@ library Leaf {
3515
uint256 op = uint256(bytes32(bytes20(_operator)));
3616
// Bitwise AND the id to erase
3717
// all but the 32 least significant bits
38-
uint256 uid = _id & ID_MAX;
18+
uint256 uid = _id & Constants.ID_MAX;
3919
// Erase all but the 64 least significant bits,
4020
// then shift left by 32 bits to make room for the id
41-
uint256 cb = (_creationBlock & BLOCKHEIGHT_MAX) << ID_WIDTH;
21+
uint256 cb = (_creationBlock & Constants.BLOCKHEIGHT_MAX) <<
22+
Constants.ID_WIDTH;
4223
// Bitwise OR them all together to get
4324
// [address operator || uint64 creationBlock || uint32 id]
4425
return (op | cb | uid);
@@ -52,12 +33,12 @@ library Leaf {
5233

5334
/// @notice Return the block number the leaf was created in.
5435
function creationBlock(uint256 leaf) internal pure returns (uint256) {
55-
return ((leaf >> ID_WIDTH) & BLOCKHEIGHT_MAX);
36+
return ((leaf >> Constants.ID_WIDTH) & Constants.BLOCKHEIGHT_MAX);
5637
}
5738

5839
function id(uint256 leaf) internal pure returns (uint32) {
5940
// Id is stored in the 32 least significant bits.
6041
// Bitwise AND ensures that we only get the contents of those bits.
61-
return uint32(leaf & ID_MAX);
42+
return uint32(leaf & Constants.ID_MAX);
6243
}
6344
}

contracts/Position.sol

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,30 @@
11
pragma solidity 0.8.9;
22

3-
library Position {
4-
////////////////////////////////////////////////////////////////////////////
5-
// Parameters for configuration
6-
7-
// How many bits a position uses per level of the tree;
8-
// each branch of the tree contains 2**SLOT_BITS slots.
9-
uint256 private constant SLOT_BITS = 3;
10-
////////////////////////////////////////////////////////////////////////////
11-
12-
////////////////////////////////////////////////////////////////////////////
13-
// Derived constants, do not touch
14-
uint256 private constant SLOT_POINTER_MAX = (2**SLOT_BITS) - 1;
15-
uint256 private constant LEAF_FLAG = 1 << 255;
16-
17-
////////////////////////////////////////////////////////////////////////////
3+
import "./Constants.sol";
184

5+
library Position {
196
// Return the last 3 bits of a position number,
207
// corresponding to its slot in its parent
218
function slot(uint256 a) internal pure returns (uint256) {
22-
return a & SLOT_POINTER_MAX;
9+
return a & Constants.SLOT_POINTER_MAX;
2310
}
2411

2512
// Return the parent of a position number
2613
function parent(uint256 a) internal pure returns (uint256) {
27-
return a >> SLOT_BITS;
14+
return a >> Constants.SLOT_BITS;
2815
}
2916

3017
// Return the location of the child of a at the given slot
3118
function child(uint256 a, uint256 s) internal pure returns (uint256) {
32-
return (a << SLOT_BITS) | (s & SLOT_POINTER_MAX); // slot(s)
19+
return (a << Constants.SLOT_BITS) | (s & Constants.SLOT_POINTER_MAX); // slot(s)
3320
}
3421

3522
// Return the uint p as a flagged position uint:
3623
// the least significant 21 bits contain the position
3724
// and the 22nd bit is set as a flag
3825
// to distinguish the position 0x000000 from an empty field.
3926
function setFlag(uint256 p) internal pure returns (uint256) {
40-
return p | LEAF_FLAG;
27+
return p | Constants.LEAF_FLAG;
4128
}
4229

4330
// Turn a flagged position into an unflagged position
@@ -47,6 +34,6 @@ library Position {
4734
// as all position-manipulating code should ignore non-position bits anyway
4835
// but it's cheap to call so might as well do it.
4936
function unsetFlag(uint256 p) internal pure returns (uint256) {
50-
return p & (~LEAF_FLAG);
37+
return p & (~Constants.LEAF_FLAG);
5138
}
5239
}

contracts/RNG.sol

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,9 @@
11
pragma solidity 0.8.9;
22

33
import "./Leaf.sol";
4+
import "./Constants.sol";
45

56
library RNG {
6-
////////////////////////////////////////////////////////////////////////////
7-
// Parameters for configuration
8-
9-
// How many bits a position uses per level of the tree;
10-
// each branch of the tree contains 2**SLOT_BITS slots.
11-
uint256 private constant SLOT_BITS = 3;
12-
////////////////////////////////////////////////////////////////////////////
13-
14-
////////////////////////////////////////////////////////////////////////////
15-
// Derived constants, do not touch
16-
uint256 private constant SLOT_COUNT = 2**SLOT_BITS;
17-
uint256 private constant WEIGHT_WIDTH = 256 / SLOT_COUNT;
18-
19-
////////////////////////////////////////////////////////////////////////////
20-
217
/// @notice Get an index in the range `[0 .. range-1]`
228
/// and the new state of the RNG,
239
/// using the provided `state` of the RNG.
@@ -76,7 +62,7 @@ library RNG {
7662
return 0;
7763
}
7864

79-
uint256 bits = WEIGHT_WIDTH - 1;
65+
uint256 bits = Constants.WEIGHT_WIDTH - 1;
8066

8167
// Left shift by `bits`,
8268
// so we have a 1 in the (bits + 1)th least significant bit

contracts/Rewards.sol

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ contract Rewards {
3434
// The amount of rewards collected by the operator after the latest update.
3535
// The amount the operator could withdraw may equal `available`
3636
// or it may be greater, if more rewards have been paid in since then.
37+
// To evaulate the most recent amount including rewards potentially paid
38+
// since the last update, use `availableRewards` function.
3739
uint96 available;
3840
// If nonzero, the operator is ineligible for rewards
3941
// and may only re-enable rewards after the specified timestamp.
@@ -196,4 +198,21 @@ contract Rewards {
196198
o.ineligibleUntil = 0;
197199
operatorRewards[operator] = o;
198200
}
201+
202+
/// @notice Returns the amount of rewards currently available for withdrawal
203+
/// for the given operator.
204+
function availableRewards(uint32 operator) internal view returns (uint96) {
205+
uint96 acc = globalRewardAccumulator;
206+
OperatorRewards memory o = operatorRewards[operator];
207+
if (o.ineligibleUntil == 0) {
208+
// If operator is not ineligible, calculate newly accrued rewards and add
209+
// them to the available ones, calculated during the last update.
210+
uint96 accruedRewards = (acc - o.accumulated) * uint96(o.weight);
211+
return o.available + accruedRewards;
212+
} else {
213+
// If ineligible, return only the rewards calculated during the last
214+
// update.
215+
return o.available;
216+
}
217+
}
199218
}

contracts/SortitionPool.sol

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,27 @@ contract SortitionPool is SortitionTree, Rewards, Ownable, IReceiveApproval {
5656
Rewards.addRewards(uint96(amount), uint32(root.sumWeight()));
5757
}
5858

59+
/// @notice Withdraws all available rewards for the given operator to the
60+
/// given beneficiary.
61+
/// @dev Can be called only be the owner. Does not validate if the provided
62+
/// beneficiary is associated with the provided operator - this needs to
63+
/// be done by the owner calling this function.
64+
/// @return The amount of rewards withdrawn in this call.
5965
function withdrawRewards(address operator, address beneficiary)
6066
public
6167
onlyOwner
68+
returns (uint96)
6269
{
6370
uint32 id = getOperatorID(operator);
6471
Rewards.updateOperatorRewards(id, uint32(getPoolWeight(operator)));
6572
uint96 earned = Rewards.withdrawOperatorRewards(id);
6673
rewardToken.transfer(beneficiary, uint256(earned));
74+
return earned;
6775
}
6876

77+
/// @notice Withdraws rewards not allocated to operators marked as ineligible
78+
/// to the given recipient address.
79+
/// @dev Can be called only by the owner.
6980
function withdrawIneligible(address recipient) public onlyOwner {
7081
uint96 earned = Rewards.withdrawIneligibleRewards();
7182
rewardToken.transfer(recipient, uint256(earned));
@@ -140,6 +151,12 @@ contract SortitionPool is SortitionTree, Rewards, Ownable, IReceiveApproval {
140151
emit RewardEligibilityRestored(operator, id);
141152
}
142153

154+
/// @notice Returns the amount of rewards withdrawable for the given operator.
155+
function getAvailableRewards(address operator) public view returns (uint96) {
156+
uint32 id = getOperatorID(operator);
157+
return availableRewards(id);
158+
}
159+
143160
/// @notice Return whether the operator is present in the pool.
144161
function isOperatorInPool(address operator) public view returns (bool) {
145162
return getFlaggedLeafPosition(operator) != 0;

0 commit comments

Comments
 (0)