@@ -79,6 +79,9 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp
79
79
/// @dev link to the Lido contract
80
80
bytes32 internal constant LIDO_POSITION = keccak256 ("lido.NodeOperatorsRegistry.lido " );
81
81
82
+ /// @dev link to the index of operations with keys
83
+ bytes32 internal constant KEYS_OP_INDEX_POSITION = keccak256 ("lido.NodeOperatorsRegistry.keysOpIndex " );
84
+
82
85
83
86
modifier onlyLido () {
84
87
require (msg .sender == LIDO_POSITION.getStorageAddress (), "APP_AUTH_FAILED " );
@@ -98,18 +101,18 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp
98
101
function initialize (address _lido ) public onlyInit {
99
102
TOTAL_OPERATORS_COUNT_POSITION.setStorageUint256 (0 );
100
103
ACTIVE_OPERATORS_COUNT_POSITION.setStorageUint256 (0 );
104
+ KEYS_OP_INDEX_POSITION.setStorageUint256 (0 );
101
105
LIDO_POSITION.setStorageAddress (_lido);
102
106
initialized ();
103
107
}
104
108
105
109
/**
106
- * @notice Add node operator named `_name` with reward address `_rewardAddress` and staking limit `_stakingLimit`
110
+ * @notice Add node operator named `_name` with reward address `_rewardAddress` and staking limit = 0
107
111
* @param _name Human-readable name
108
112
* @param _rewardAddress Ethereum 1 address which receives stETH rewards for this operator
109
- * @param _stakingLimit the maximum number of validators to stake for this operator
110
113
* @return a unique key of the added operator
111
114
*/
112
- function addNodeOperator (string _name , address _rewardAddress , uint64 _stakingLimit ) external
115
+ function addNodeOperator (string _name , address _rewardAddress ) external
113
116
auth (ADD_NODE_OPERATOR_ROLE)
114
117
validAddress (_rewardAddress)
115
118
returns (uint256 id )
@@ -125,9 +128,9 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp
125
128
operator.active = true ;
126
129
operator.name = _name;
127
130
operator.rewardAddress = _rewardAddress;
128
- operator.stakingLimit = _stakingLimit ;
131
+ operator.stakingLimit = 0 ;
129
132
130
- emit NodeOperatorAdded (id, _name, _rewardAddress, _stakingLimit );
133
+ emit NodeOperatorAdded (id, _name, _rewardAddress, 0 );
131
134
132
135
return id;
133
136
}
@@ -139,6 +142,7 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp
139
142
authP (SET_NODE_OPERATOR_ACTIVE_ROLE, arr (_id, _active ? uint256 (1 ) : uint256 (0 )))
140
143
operatorExists (_id)
141
144
{
145
+ _increaseKeysOpIndex ();
142
146
if (operators[_id].active != _active) {
143
147
uint256 activeOperatorsCount = getActiveNodeOperatorsCount ();
144
148
if (_active)
@@ -182,6 +186,7 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp
182
186
authP (SET_NODE_OPERATOR_LIMIT_ROLE, arr (_id, uint256 (_stakingLimit)))
183
187
operatorExists (_id)
184
188
{
189
+ _increaseKeysOpIndex ();
185
190
operators[_id].stakingLimit = _stakingLimit;
186
191
emit NodeOperatorStakingLimitSet (_id, _stakingLimit);
187
192
}
@@ -207,8 +212,12 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp
207
212
function trimUnusedKeys () external onlyLido {
208
213
uint256 length = getNodeOperatorsCount ();
209
214
for (uint256 operatorId = 0 ; operatorId < length; ++ operatorId) {
210
- if (operators[operatorId].totalSigningKeys != operators[operatorId].usedSigningKeys) // write only if update is needed
211
- operators[operatorId].totalSigningKeys = operators[operatorId].usedSigningKeys; // discard unused keys
215
+ uint64 totalSigningKeys = operators[operatorId].totalSigningKeys;
216
+ uint64 usedSigningKeys = operators[operatorId].usedSigningKeys;
217
+ if (totalSigningKeys != usedSigningKeys) { // write only if update is needed
218
+ operators[operatorId].totalSigningKeys = usedSigningKeys; // discard unused keys
219
+ emit NodeOperatorTotalKeysTrimmed (operatorId, totalSigningKeys - usedSigningKeys);
220
+ }
212
221
}
213
222
}
214
223
@@ -264,6 +273,22 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp
264
273
_removeSigningKey (_operator_id, _index);
265
274
}
266
275
276
+ /**
277
+ * @notice Removes an #`_amount` of validator signing keys starting from #`_index` of operator #`_id` usable keys. Executed on behalf of DAO.
278
+ * @param _operator_id Node Operator id
279
+ * @param _index Index of the key, starting with 0
280
+ * @param _amount Number of keys to remove
281
+ */
282
+ function removeSigningKeys (uint256 _operator_id , uint256 _index , uint256 _amount )
283
+ external
284
+ authP (MANAGE_SIGNING_KEYS, arr (_operator_id))
285
+ {
286
+ // removing from the last index to the highest one, so we won't get outside the array
287
+ for (uint256 i = _index + _amount; i > _index ; -- i) {
288
+ _removeSigningKey (_operator_id, i - 1 );
289
+ }
290
+ }
291
+
267
292
/**
268
293
* @notice Removes a validator signing key #`_index` of operator #`_id` from the set of usable keys. Executed on behalf of Node Operator.
269
294
* @param _operator_id Node Operator id
@@ -274,6 +299,20 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp
274
299
_removeSigningKey (_operator_id, _index);
275
300
}
276
301
302
+ /**
303
+ * @notice Removes an #`_amount` of validator signing keys starting from #`_index` of operator #`_id` usable keys. Executed on behalf of Node Operator.
304
+ * @param _operator_id Node Operator id
305
+ * @param _index Index of the key, starting with 0
306
+ * @param _amount Number of keys to remove
307
+ */
308
+ function removeSigningKeysOperatorBH (uint256 _operator_id , uint256 _index , uint256 _amount ) external {
309
+ require (msg .sender == operators[_operator_id].rewardAddress, "APP_AUTH_FAILED " );
310
+ // removing from the last index to the highest one, so we won't get outside the array
311
+ for (uint256 i = _index + _amount; i > _index ; -- i) {
312
+ _removeSigningKey (_operator_id, i - 1 );
313
+ }
314
+ }
315
+
277
316
/**
278
317
* @notice Selects and returns at most `_numKeys` signing keys (as well as the corresponding
279
318
* signatures) from the set of active keys and marks the selected keys as used.
@@ -484,6 +523,18 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp
484
523
return TOTAL_OPERATORS_COUNT_POSITION.getStorageUint256 ();
485
524
}
486
525
526
+ /**
527
+ * @notice Returns a monotonically increasing counter that gets incremented when any of the following happens:
528
+ * 1. a node operator's key(s) is added;
529
+ * 2. a node operator's key(s) is removed;
530
+ * 3. a node operator's approved keys limit is changed.
531
+ * 4. a node operator was activated/deactivated. Activation or deactivation of node operator
532
+ * might lead to usage of unvalidated keys in the assignNextSigningKeys method.
533
+ */
534
+ function getKeysOpIndex () public view returns (uint256 ) {
535
+ return KEYS_OP_INDEX_POSITION.getStorageUint256 ();
536
+ }
537
+
487
538
function _isEmptySigningKey (bytes memory _key ) internal pure returns (bool ) {
488
539
assert (_key.length == PUBKEY_LENGTH);
489
540
// algorithm applicability constraint
@@ -540,6 +591,8 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp
540
591
require (_pubkeys.length == _quantity.mul (PUBKEY_LENGTH), "INVALID_LENGTH " );
541
592
require (_signatures.length == _quantity.mul (SIGNATURE_LENGTH), "INVALID_LENGTH " );
542
593
594
+ _increaseKeysOpIndex ();
595
+
543
596
for (uint256 i = 0 ; i < _quantity; ++ i) {
544
597
bytes memory key = BytesLib.slice (_pubkeys, i * PUBKEY_LENGTH, PUBKEY_LENGTH);
545
598
require (! _isEmptySigningKey (key), "EMPTY_KEY " );
@@ -558,6 +611,8 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp
558
611
require (_index < operators[_operator_id].totalSigningKeys, "KEY_NOT_FOUND " );
559
612
require (_index >= operators[_operator_id].usedSigningKeys, "KEY_WAS_USED " );
560
613
614
+ _increaseKeysOpIndex ();
615
+
561
616
(bytes memory removedKey , ) = _loadSigningKey (_operator_id, _index);
562
617
563
618
uint256 lastIndex = operators[_operator_id].totalSigningKeys.sub (1 );
@@ -569,6 +624,11 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp
569
624
_deleteSigningKey (_operator_id, lastIndex);
570
625
operators[_operator_id].totalSigningKeys = operators[_operator_id].totalSigningKeys.sub (1 );
571
626
627
+ if (_index < operators[_operator_id].stakingLimit) {
628
+ // decreasing the staking limit so the key at _index can't be used anymore
629
+ operators[_operator_id].stakingLimit = uint64 (_index);
630
+ }
631
+
572
632
emit SigningKeyRemoved (_operator_id, removedKey);
573
633
}
574
634
@@ -634,4 +694,10 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp
634
694
635
695
return cache;
636
696
}
697
+
698
+ function _increaseKeysOpIndex () internal {
699
+ uint256 keysOpIndex = getKeysOpIndex ();
700
+ KEYS_OP_INDEX_POSITION.setStorageUint256 (keysOpIndex + 1 );
701
+ emit KeysOpIndexSet (keysOpIndex + 1 );
702
+ }
637
703
}
0 commit comments