Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: update stuck keys #41

Merged
merged 10 commits into from
Dec 4, 2023
131 changes: 119 additions & 12 deletions src/CSModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ILido } from "./interfaces/ILido.sol";

import { QueueLib } from "./lib/QueueLib.sol";
import { Batch } from "./lib/Batch.sol";
import { ValidatorCountsReport } from "./lib/ValidatorCountsReport.sol";

import "./lib/SigningKeys.sol";

Expand All @@ -23,7 +24,7 @@ struct NodeOperator {
address proposedRewardAddress;
bool active;
uint256 targetLimit;
uint256 targetLimitTimestamp;
bool isTargetLimitActive;
uint256 stuckPenaltyEndTimestamp;
uint256 totalExitedKeys;
uint256 totalAddedKeys;
Expand All @@ -32,7 +33,6 @@ struct NodeOperator {
uint256 totalVettedKeys;
uint256 stuckValidatorsCount;
uint256 refundedValidatorsCount;
bool isTargetLimitActive;
uint256 queueNonce;
}

Expand Down Expand Up @@ -84,6 +84,15 @@ contract CSModuleBase {
uint256 indexed nodeOperatorId,
uint256 totalValidatorsCount
);
event StuckSigningKeysCountChanged(
uint256 indexed nodeOperatorId,
uint256 stuckValidatorsCount
);
event TargetValidatorsCountChanged(
uint256 indexed nodeOperatorId,
bool isTargetLimitActive,
uint256 targetValidatorsCount
);

event BatchEnqueued(
uint256 indexed nodeOperatorId,
Expand All @@ -105,6 +114,10 @@ contract CSModuleBase {
error SameAddress();
error AlreadyProposed();
error InvalidVetKeysPointer();
error TargetLimitExceeded();
error StuckKeysPresent();
error InvalidTargetLimit();
error StuckKeysHigherThanTotalDeposited();

error QueueLookupNoLimit();
error QueueEmptyBatch();
Expand Down Expand Up @@ -559,7 +572,19 @@ contract CSModule is IStakingModule, CSModuleBase {
stuckPenaltyEndTimestamp = no.stuckPenaltyEndTimestamp;
totalExitedValidators = no.totalExitedKeys;
totalDepositedValidators = no.totalDepositedKeys;
depositableValidatorsCount = no.totalVettedKeys - no.totalExitedKeys;
// TODO: it should be more clear and probably revisited later
depositableValidatorsCount =
no.totalVettedKeys -
totalDepositedValidators;
if (no.isTargetLimitActive) {
depositableValidatorsCount = (totalExitedValidators +
targetValidatorsCount) <= no.totalVettedKeys
? 0
: Math.min(
totalExitedValidators + targetValidatorsCount,
no.totalVettedKeys
) - totalDepositedValidators;
skhomuti marked this conversation as resolved.
Show resolved Hide resolved
}
}

function getNodeOperatorSigningKeys(
Expand Down Expand Up @@ -620,11 +645,44 @@ contract CSModule is IStakingModule, CSModuleBase {
// TODO: staking router role only
}

// @notice update stuck validators count by StakingRouter
// Presence of stuck validators leads to stop vetting for the node operator
// to prevent further deposits and clean batches from the deposit queue.
// @param nodeOperatorIds - bytes packed array of node operator ids
// @param stuckValidatorsCounts - bytes packed array of stuck validators counts
function updateStuckValidatorsCount(
bytes calldata /*_nodeOperatorIds*/,
bytes calldata /*_stuckValidatorsCounts*/
) external {
// TODO: implement
bytes calldata nodeOperatorIds,
bytes calldata stuckValidatorsCounts
) external onlyStakingRouter {
ValidatorCountsReport.validate(nodeOperatorIds, stuckValidatorsCounts);
dgusakov marked this conversation as resolved.
Show resolved Hide resolved

for (
uint256 i = 0;
i < ValidatorCountsReport.count(nodeOperatorIds);
i++
) {
(
uint256 nodeOperatorId,
uint256 stuckValidatorsCount
) = ValidatorCountsReport.next(
nodeOperatorIds,
stuckValidatorsCounts,
i
);
if (nodeOperatorId >= _nodeOperatorsCount)
revert NodeOperatorDoesNotExist();
NodeOperator storage no = _nodeOperators[nodeOperatorId];
if (stuckValidatorsCount > no.totalDepositedKeys)
revert StuckKeysHigherThanTotalDeposited();
if (stuckValidatorsCount == no.stuckValidatorsCount) continue;

no.stuckValidatorsCount = stuckValidatorsCount;
emit StuckSigningKeysCountChanged(
nodeOperatorId,
stuckValidatorsCount
);
}
_incrementModuleNonce();
madlabman marked this conversation as resolved.
Show resolved Hide resolved
}

function updateExitedValidatorsCount(
Expand All @@ -645,12 +703,42 @@ contract CSModule is IStakingModule, CSModuleBase {
// TODO: implement
}

// @notice update target limits with event emission
// target limit decreasing (or appearing) must unvet node operator's keys from the queue
function updateTargetValidatorsLimits(
uint256 /*_nodeOperatorId*/,
bool /*_isTargetLimitActive*/,
uint256 /*_targetLimit*/
) external {
// TODO: implement
uint256 nodeOperatorId,
bool isTargetLimitActive,
uint256 targetLimit
) external onlyExistingNodeOperator(nodeOperatorId) onlyStakingRouter {
NodeOperator storage no = _nodeOperators[nodeOperatorId];

if (
no.isTargetLimitActive == isTargetLimitActive &&
no.targetLimit == targetLimit
) return;

if (
(!no.isTargetLimitActive && isTargetLimitActive) ||
targetLimit < no.targetLimit
) {
_unvetKeys(nodeOperatorId);
}

if (no.isTargetLimitActive != isTargetLimitActive) {
no.isTargetLimitActive = isTargetLimitActive;
}

if (no.targetLimit != targetLimit) {
no.targetLimit = targetLimit;
}

emit TargetValidatorsCountChanged(
nodeOperatorId,
isTargetLimitActive,
targetLimit
);

_incrementModuleNonce();
}

function onExitedAndStuckValidatorsCountsUpdated() external {
Expand All @@ -674,6 +762,7 @@ contract CSModule is IStakingModule, CSModuleBase {
if (vetKeysPointer <= no.totalVettedKeys)
revert InvalidVetKeysPointer();
if (vetKeysPointer > no.totalAddedKeys) revert InvalidVetKeysPointer();
_validateVetKeys(nodeOperatorId, vetKeysPointer);
madlabman marked this conversation as resolved.
Show resolved Hide resolved

uint64 count = SafeCast.toUint64(vetKeysPointer - no.totalVettedKeys);
uint64 start = SafeCast.toUint64(no.totalVettedKeys);
Expand All @@ -694,6 +783,19 @@ contract CSModule is IStakingModule, CSModuleBase {
_incrementModuleNonce();
}

function _validateVetKeys(
dgusakov marked this conversation as resolved.
Show resolved Hide resolved
uint256 nodeOperatorId,
uint64 vetKeysPointer
) internal view {
NodeOperator storage no = _nodeOperators[nodeOperatorId];

if (
no.isTargetLimitActive &&
vetKeysPointer > (no.totalExitedKeys + no.targetLimit)
) revert TargetLimitExceeded();
if (no.stuckValidatorsCount > 0) revert StuckKeysPresent();
}

function unvetKeys(
uint256 nodeOperatorId
)
Expand Down Expand Up @@ -1042,4 +1144,9 @@ contract CSModule is IStakingModule, CSModuleBase {
// TODO: check the role
_;
}

modifier onlyStakingRouter() {
// TODO check the role
_;
}
}
30 changes: 30 additions & 0 deletions src/lib/ValidatorCountsReport.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: 2023 Lido <[email protected]>
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.21;

/// @author skhomuti
library ValidatorCountsReport {
error InvalidReportData();


function count(bytes calldata ids) internal pure returns (uint256) {
return ids.length / 8;
}

function validate(bytes calldata ids, bytes calldata counts) internal pure {
if (
vgorkavenko marked this conversation as resolved.
Show resolved Hide resolved
counts.length / 16 != count(ids) ||
ids.length % 8 != 0 ||
counts.length % 16 != 0
) {
revert InvalidReportData();
}
}

function next(
bytes calldata ids, bytes calldata counts, uint256 offset
) internal pure returns (uint256 nodeOperatorId, uint256 keysCount) {
nodeOperatorId = uint256(bytes32(ids[8 * offset:8 * offset + 8]) >> 192);
keysCount = uint256(bytes32(counts[16 * offset:16 * offset + 16]) >> 128);
}
}
Loading
Loading