-
Notifications
You must be signed in to change notification settings - Fork 4
/
QueueLib.sol
235 lines (200 loc) · 6.95 KB
/
QueueLib.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
// SPDX-FileCopyrightText: 2024 Lido <info@lido.fi>
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.24;
import { NodeOperator } from "../interfaces/ICSModule.sol";
import { TransientUintUintMap, TransientUintUintMapLib } from "./TransientUintUintMapLib.sol";
// Batch is an uint256 as it's the internal data type used by solidity.
// Batch is a packed value, consisting of the following fields:
// - uint64 nodeOperatorId
// - uint64 keysCount -- count of keys enqueued by the batch
// - uint128 next -- index of the next batch in the queue
type Batch is uint256;
/// @notice Batch of the operator with index 0, with no keys in it and the next Batch' index 0 is meaningless.
function isNil(Batch self) pure returns (bool) {
return Batch.unwrap(self) == 0;
}
/// @dev Syntactic sugar for the type.
function unwrap(Batch self) pure returns (uint256) {
return Batch.unwrap(self);
}
function noId(Batch self) pure returns (uint64 n) {
assembly {
n := shr(192, self)
}
}
function keys(Batch self) pure returns (uint64 n) {
assembly {
n := shl(64, self)
n := shr(192, n)
}
}
function next(Batch self) pure returns (uint128 n) {
assembly {
n := self // uint128(self)
}
}
/// @dev keys count cast is unsafe
function setKeys(Batch self, uint256 keysCount) pure returns (Batch) {
assembly {
self := or(
and(
self,
0xffffffffffffffff0000000000000000ffffffffffffffffffffffffffffffff
),
shl(128, and(keysCount, 0xffffffffffffffff))
) // self.keys = keysCount
}
return self;
}
/// @dev can be unsafe if the From batch is previous to the self
function setNext(Batch self, Batch from) pure returns (Batch) {
assembly {
self := or(
and(
self,
0xffffffffffffffffffffffffffffffff00000000000000000000000000000000
),
and(
from,
0x00000000000000000000000000000000ffffffffffffffffffffffffffffffff
)
) // self.next = from.next
}
return self;
}
/// @dev Instantiate a new Batch to be added to the queue. The `next` field will be determined upon the enqueue.
/// @dev Parameters are uint256 to make usage easier.
function createBatch(
uint256 nodeOperatorId,
uint256 keysCount
) pure returns (Batch item) {
// NOTE: No need to safe cast due to internal logic.
nodeOperatorId = uint64(nodeOperatorId);
keysCount = uint64(keysCount);
assembly {
item := shl(128, keysCount) // `keysCount` in [64:127]
item := or(item, shl(192, nodeOperatorId)) // `nodeOperatorId` in [0:63]
}
}
using { noId, keys, setKeys, setNext, next, isNil, unwrap } for Batch global;
using QueueLib for QueueLib.Queue;
interface IQueueLib {
event BatchEnqueued(uint256 indexed nodeOperatorId, uint256 count);
error QueueIsEmpty();
error QueueLookupNoLimit();
}
/// @author madlabman
library QueueLib {
struct Queue {
// Pointer to the item to be dequeued.
uint128 head;
// Tracks the total number of batches ever enqueued.
uint128 tail;
// Mapping saves a little in costs and allows easily fallback to a zeroed batch on out-of-bounds access.
mapping(uint128 => Batch) queue;
}
//////
/// External methods
//////
function normalize(
Queue storage self,
mapping(uint256 => NodeOperator) storage nodeOperators,
uint256 nodeOperatorId
) external {
NodeOperator storage no = nodeOperators[nodeOperatorId];
uint32 depositable = no.depositableValidatorsCount;
uint32 enqueued = no.enqueuedCount;
if (enqueued < depositable) {
uint32 count;
unchecked {
count = depositable - enqueued;
}
no.enqueuedCount = depositable;
self.enqueue(nodeOperatorId, count);
}
}
function clean(
Queue storage self,
mapping(uint256 => NodeOperator) storage nodeOperators,
uint256 maxItems
) external returns (uint256 toRemove) {
if (maxItems == 0) revert IQueueLib.QueueLookupNoLimit();
Batch prev;
uint128 indexOfPrev;
uint128 head = self.head;
uint128 curr = head;
TransientUintUintMap queueLookup = TransientUintUintMapLib.create();
for (uint256 i; i < maxItems; ++i) {
Batch item = self.queue[curr];
if (item.isNil()) {
return toRemove;
}
NodeOperator storage no = nodeOperators[item.noId()];
if (queueLookup.get(item.noId()) >= no.depositableValidatorsCount) {
// NOTE: Since we reached that point there's no way for a Node Operator to have a depositable batch
// later in the queue, and hence we don't update _queueLookup for the Node Operator.
if (curr == head) {
self.dequeue();
head = self.head;
} else {
// There's no `prev` item while we call `dequeue`, and removing an item will keep the `prev` intact
// other than changing its `next` field.
prev = prev.setNext(item);
self.queue[indexOfPrev] = prev;
}
// We assume that the invariant `enqueuedCount` >= `keys` is kept.
// NOTE: No need to safe cast due to internal logic.
no.enqueuedCount -= uint32(item.keys());
unchecked {
++toRemove;
}
} else {
queueLookup.add(item.noId(), item.keys());
indexOfPrev = curr;
prev = item;
}
curr = item.next();
}
}
/////
/// Internal methods
/////
function enqueue(
Queue storage self,
uint256 nodeOperatorId,
uint256 keysCount
) internal returns (Batch item) {
uint128 tail = self.tail;
item = createBatch(nodeOperatorId, keysCount);
assembly {
item := or(
and(
item,
0xffffffffffffffffffffffffffffffff00000000000000000000000000000000
),
add(tail, 1)
) // item.next = self.tail + 1;
}
self.queue[tail] = item;
unchecked {
++self.tail;
}
emit IQueueLib.BatchEnqueued(nodeOperatorId, keysCount);
}
function dequeue(Queue storage self) internal returns (Batch item) {
item = peek(self);
if (item.isNil()) {
revert IQueueLib.QueueIsEmpty();
}
self.head = item.next();
}
function peek(Queue storage self) internal view returns (Batch) {
return self.queue[self.head];
}
function at(
Queue storage self,
uint128 index
) internal view returns (Batch) {
return self.queue[index];
}
}