-
Notifications
You must be signed in to change notification settings - Fork 109
/
Copy pathIndexRegistry.sol
346 lines (298 loc) · 13.4 KB
/
IndexRegistry.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
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;
import {IIndexRegistry, IndexRegistryStorage} from "./IndexRegistryStorage.sol";
import {ISlashingRegistryCoordinator} from "./interfaces/ISlashingRegistryCoordinator.sol";
/**
* @title A `Registry` that keeps track of an ordered list of operators for each quorum
* @author Layr Labs, Inc.
*/
contract IndexRegistry is IndexRegistryStorage {
modifier onlyRegistryCoordinator() {
_checkRegistryCoordinator();
_;
}
constructor(
ISlashingRegistryCoordinator _slashingRegistryCoordinator
) IndexRegistryStorage(_slashingRegistryCoordinator) {}
/**
*
* EXTERNAL FUNCTIONS - REGISTRY COORDINATOR
*
*/
/// @inheritdoc IIndexRegistry
function registerOperator(
bytes32 operatorId,
bytes calldata quorumNumbers
) public virtual onlyRegistryCoordinator returns (uint32[] memory) {
uint32[] memory numOperatorsPerQuorum = new uint32[](quorumNumbers.length);
for (uint256 i = 0; i < quorumNumbers.length; i++) {
// Validate quorum exists and get current operator count
uint8 quorumNumber = uint8(quorumNumbers[i]);
uint256 historyLength = _operatorCountHistory[quorumNumber].length;
require(historyLength != 0, QuorumDoesNotExist());
/**
* Increase the number of operators currently active for this quorum,
* and assign the operator to the last operatorIndex available
*/
uint32 newOperatorCount = _increaseOperatorCount(quorumNumber);
_assignOperatorToIndex({
operatorId: operatorId,
quorumNumber: quorumNumber,
operatorIndex: newOperatorCount - 1
});
// Record the current operator count for each quorum
numOperatorsPerQuorum[i] = newOperatorCount;
}
return numOperatorsPerQuorum;
}
/// @inheritdoc IIndexRegistry
function deregisterOperator(
bytes32 operatorId,
bytes calldata quorumNumbers
) public virtual onlyRegistryCoordinator {
for (uint256 i = 0; i < quorumNumbers.length; i++) {
// Validate quorum exists and get the operatorIndex of the operator being deregistered
uint8 quorumNumber = uint8(quorumNumbers[i]);
uint256 historyLength = _operatorCountHistory[quorumNumber].length;
require(historyLength != 0, QuorumDoesNotExist());
uint32 operatorIndexToRemove = currentOperatorIndex[quorumNumber][operatorId];
/**
* "Pop" the operator from the registry:
* 1. Decrease the operator count for the quorum
* 2. Remove the last operator associated with the count
* 3. Place the last operator in the deregistered operator's old position
*/
uint32 newOperatorCount = _decreaseOperatorCount(quorumNumber);
bytes32 lastOperatorId = _popLastOperator(quorumNumber, newOperatorCount);
if (operatorId != lastOperatorId) {
_assignOperatorToIndex({
operatorId: lastOperatorId,
quorumNumber: quorumNumber,
operatorIndex: operatorIndexToRemove
});
}
}
}
/// @inheritdoc IIndexRegistry
function initializeQuorum(
uint8 quorumNumber
) public virtual onlyRegistryCoordinator {
require(_operatorCountHistory[quorumNumber].length == 0, QuorumDoesNotExist());
_operatorCountHistory[quorumNumber].push(
QuorumUpdate({numOperators: 0, fromBlockNumber: uint32(block.number)})
);
}
/**
*
* INTERNAL FUNCTIONS
*
*/
/// @notice Increases the historical operator count by 1 and returns the new count.
function _increaseOperatorCount(
uint8 quorumNumber
) internal returns (uint32) {
QuorumUpdate storage lastUpdate = _latestQuorumUpdate(quorumNumber);
uint32 newOperatorCount = lastUpdate.numOperators + 1;
_updateOperatorCountHistory(quorumNumber, lastUpdate, newOperatorCount);
// If this is the first time we're using this operatorIndex, push its first update
// This maintains an invariant: existing indices have nonzero history
if (_operatorIndexHistory[quorumNumber][newOperatorCount - 1].length == 0) {
_operatorIndexHistory[quorumNumber][newOperatorCount - 1].push(
OperatorUpdate({
operatorId: OPERATOR_DOES_NOT_EXIST_ID,
fromBlockNumber: uint32(block.number)
})
);
}
return newOperatorCount;
}
/// @notice Decreases the historical operator count by 1 and returns the new count.
function _decreaseOperatorCount(
uint8 quorumNumber
) internal returns (uint32) {
QuorumUpdate storage lastUpdate = _latestQuorumUpdate(quorumNumber);
uint32 newOperatorCount = lastUpdate.numOperators - 1;
_updateOperatorCountHistory(quorumNumber, lastUpdate, newOperatorCount);
return newOperatorCount;
}
/// @notice Updates `_operatorCountHistory` with a new operator count.
/// @dev If the lastUpdate was made in this block, update the entry.
/// Otherwise, push a new historical entry.
function _updateOperatorCountHistory(
uint8 quorumNumber,
QuorumUpdate storage lastUpdate,
uint32 newOperatorCount
) internal {
if (lastUpdate.fromBlockNumber == uint32(block.number)) {
lastUpdate.numOperators = newOperatorCount;
} else {
_operatorCountHistory[quorumNumber].push(
QuorumUpdate({numOperators: newOperatorCount, fromBlockNumber: uint32(block.number)})
);
}
}
/// @notice For a given quorum and operatorIndex, pop and return the last operatorId in the history.
/// @dev The last entry's operatorId is updated to OPERATOR_DOES_NOT_EXIST_ID.
/// @return The removed operatorId.
function _popLastOperator(
uint8 quorumNumber,
uint32 operatorIndex
) internal returns (bytes32) {
OperatorUpdate storage lastUpdate = _latestOperatorIndexUpdate(quorumNumber, operatorIndex);
bytes32 removedOperatorId = lastUpdate.operatorId;
// Set the current operator id for this operatorIndex to 0
_updateOperatorIndexHistory(
quorumNumber, operatorIndex, lastUpdate, OPERATOR_DOES_NOT_EXIST_ID
);
return removedOperatorId;
}
/// @notice Assigns an operator to an index and updates the index history.
/// @param operatorId operatorId of the operator to update.
/// @param quorumNumber quorumNumber of the operator to update.
/// @param operatorIndex the latest index of that operator in the list of operators registered for this quorum.
function _assignOperatorToIndex(
bytes32 operatorId,
uint8 quorumNumber,
uint32 operatorIndex
) internal {
OperatorUpdate storage lastUpdate = _latestOperatorIndexUpdate(quorumNumber, operatorIndex);
_updateOperatorIndexHistory(quorumNumber, operatorIndex, lastUpdate, operatorId);
// Assign the operator to their new current operatorIndex
currentOperatorIndex[quorumNumber][operatorId] = operatorIndex;
emit QuorumIndexUpdate(operatorId, quorumNumber, operatorIndex);
}
/// @notice Updates `_operatorIndexHistory` with a new operator id for the current block.
/// @dev If the lastUpdate was made in this block, update the entry.
/// Otherwise, push a new historical entry.
function _updateOperatorIndexHistory(
uint8 quorumNumber,
uint32 operatorIndex,
OperatorUpdate storage lastUpdate,
bytes32 newOperatorId
) internal {
if (lastUpdate.fromBlockNumber == uint32(block.number)) {
lastUpdate.operatorId = newOperatorId;
} else {
_operatorIndexHistory[quorumNumber][operatorIndex].push(
OperatorUpdate({operatorId: newOperatorId, fromBlockNumber: uint32(block.number)})
);
}
}
/// @notice Returns the most recent operator count update for a quorum.
/// @dev Reverts if the quorum does not exist (history length == 0).
function _latestQuorumUpdate(
uint8 quorumNumber
) internal view returns (QuorumUpdate storage) {
uint256 historyLength = _operatorCountHistory[quorumNumber].length;
return _operatorCountHistory[quorumNumber][historyLength - 1];
}
/// @notice Returns the most recent operator id update for an index.
/// @dev Reverts if the index has never been used (history length == 0).
function _latestOperatorIndexUpdate(
uint8 quorumNumber,
uint32 operatorIndex
) internal view returns (OperatorUpdate storage) {
uint256 historyLength = _operatorIndexHistory[quorumNumber][operatorIndex].length;
return _operatorIndexHistory[quorumNumber][operatorIndex][historyLength - 1];
}
/// @notice Returns the total number of operators of the service for the given `quorumNumber` at the given `blockNumber`.
/// @dev Reverts if the quorum does not exist, or if the blockNumber is from before the quorum existed.
function _operatorCountAtBlockNumber(
uint8 quorumNumber,
uint32 blockNumber
) internal view returns (uint32) {
uint256 historyLength = _operatorCountHistory[quorumNumber].length;
// Loop backwards through _operatorCountHistory until we find an entry that preceeds `blockNumber`
for (uint256 i = historyLength; i > 0; i--) {
QuorumUpdate memory quorumUpdate = _operatorCountHistory[quorumNumber][i - 1];
if (quorumUpdate.fromBlockNumber <= blockNumber) {
return quorumUpdate.numOperators;
}
}
revert(
"IndexRegistry._operatorCountAtBlockNumber: quorum did not exist at given block number"
);
}
/// @notice Returns the operatorId at the given `operatorIndex` at the given `blockNumber` for the given `quorumNumber`.
/// @dev Requires that the operatorIndex was active at the given block number for quorum.
function _operatorIdForIndexAtBlockNumber(
uint8 quorumNumber,
uint32 operatorIndex,
uint32 blockNumber
) internal view returns (bytes32) {
uint256 historyLength = _operatorIndexHistory[quorumNumber][operatorIndex].length;
// Loop backward through _operatorIndexHistory until we find an entry that preceeds `blockNumber`
for (uint256 i = historyLength; i > 0; i--) {
OperatorUpdate memory operatorIndexUpdate =
_operatorIndexHistory[quorumNumber][operatorIndex][i - 1];
if (operatorIndexUpdate.fromBlockNumber <= blockNumber) {
// Special case: this will be OPERATOR_DOES_NOT_EXIST_ID if this operatorIndex was not used at the block number
return operatorIndexUpdate.operatorId;
}
}
// we should only hit this if the operatorIndex was never used before blockNumber
return OPERATOR_DOES_NOT_EXIST_ID;
}
/**
*
* VIEW FUNCTIONS
*
*/
/// @inheritdoc IIndexRegistry
function getOperatorUpdateAtIndex(
uint8 quorumNumber,
uint32 operatorIndex,
uint32 arrayIndex
) external view returns (OperatorUpdate memory) {
return _operatorIndexHistory[quorumNumber][operatorIndex][arrayIndex];
}
/// @inheritdoc IIndexRegistry
function getQuorumUpdateAtIndex(
uint8 quorumNumber,
uint32 quorumIndex
) external view returns (QuorumUpdate memory) {
return _operatorCountHistory[quorumNumber][quorumIndex];
}
/// @inheritdoc IIndexRegistry
function getLatestQuorumUpdate(
uint8 quorumNumber
) external view returns (QuorumUpdate memory) {
return _latestQuorumUpdate(quorumNumber);
}
/// @inheritdoc IIndexRegistry
function getLatestOperatorUpdate(
uint8 quorumNumber,
uint32 operatorIndex
) external view returns (OperatorUpdate memory) {
return _latestOperatorIndexUpdate(quorumNumber, operatorIndex);
}
/// @inheritdoc IIndexRegistry
function getOperatorListAtBlockNumber(
uint8 quorumNumber,
uint32 blockNumber
) external view returns (bytes32[] memory) {
uint32 operatorCount = _operatorCountAtBlockNumber(quorumNumber, blockNumber);
bytes32[] memory operatorList = new bytes32[](operatorCount);
for (uint256 i = 0; i < operatorCount; i++) {
operatorList[i] = _operatorIdForIndexAtBlockNumber(quorumNumber, uint32(i), blockNumber);
require(operatorList[i] != OPERATOR_DOES_NOT_EXIST_ID, OperatorIdDoesNotExist());
}
return operatorList;
}
/// @inheritdoc IIndexRegistry
function totalOperatorsForQuorum(
uint8 quorumNumber
) external view returns (uint32) {
return _latestQuorumUpdate(quorumNumber).numOperators;
}
/// @inheritdoc IIndexRegistry
function totalOperatorsForQuorumAtBlockNumber(
uint8 quorumNumber,
uint32 blockNumber
) external view returns (uint32) {
return _operatorCountAtBlockNumber(quorumNumber, blockNumber);
}
function _checkRegistryCoordinator() internal view {
require(msg.sender == address(registryCoordinator), OnlyRegistryCoordinator());
}
}