Skip to content

Commit

Permalink
Merge pull request #50 from lidofinance/withdrawal-methods
Browse files Browse the repository at this point in the history
Withdrawal methods
  • Loading branch information
skhomuti authored Jan 11, 2024
2 parents ab851f1 + 2aa0c1e commit 0ec8999
Show file tree
Hide file tree
Showing 2 changed files with 208 additions and 37 deletions.
116 changes: 79 additions & 37 deletions src/CSModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ struct NodeOperator {
uint256 targetLimit;
bool isTargetLimitActive;
uint256 stuckPenaltyEndTimestamp;
uint256 totalExitedKeys;
uint256 totalAddedKeys;
uint256 totalWithdrawnKeys;
uint256 totalDepositedKeys;
uint256 totalVettedKeys;
uint256 stuckValidatorsCount;
uint256 refundedValidatorsCount;
uint256 totalExitedKeys; // @dev only increased
uint256 totalAddedKeys; // @dev only increased
uint256 totalWithdrawnKeys; // @dev only increased
uint256 totalDepositedKeys; // @dev only increased
uint256 totalVettedKeys; // @dev both increased and decreased
uint256 stuckValidatorsCount; // @dev both increased and decreased
uint256 refundedValidatorsCount; // @dev only increased
uint256 queueNonce;
}

Expand Down Expand Up @@ -82,6 +82,10 @@ contract CSModuleBase {
bool isTargetLimitActive,
uint256 targetValidatorsCount
);
event WithdrawalSubmitted(
uint256 indexed validatorId,
uint256 withdrawalBalance
);

event BatchEnqueued(
uint256 indexed nodeOperatorId,
Expand Down Expand Up @@ -113,6 +117,8 @@ contract CSModuleBase {
error UnbondedKeysPresent();
error InvalidTargetLimit();
error StuckKeysHigherThanTotalDeposited();
error ExitedKeysHigherThanTotalDeposited();
error ExitedKeysDecrease();

error QueueLookupNoLimit();
error QueueEmptyBatch();
Expand All @@ -130,6 +136,7 @@ contract CSModule is ICSModule, CSModuleBase {
// @dev max number of node operators is limited by uint64 due to Batch serialization in 32 bytes
// it seems to be enough
uint64 public constant MAX_NODE_OPERATORS_COUNT = type(uint64).max;
uint256 public constant DEPOSIT_SIZE = 32 ether;
bytes32 public constant SIGNING_KEYS_POSITION =
keccak256("lido.CommunityStakingModule.signingKeysPosition");

Expand Down Expand Up @@ -212,6 +219,7 @@ contract CSModule is ICSModule, CSModuleBase {
uint256 /* depositableValidatorsCount */
)
{
// TODO: need to be implemented properly
return (
_totalExitedValidators,
_totalDepositedValidators,
Expand Down Expand Up @@ -673,13 +681,14 @@ contract CSModule is ICSModule, CSModuleBase {
no.totalVettedKeys -
totalDepositedValidators;
if (no.isTargetLimitActive) {
depositableValidatorsCount = (totalExitedValidators +
targetValidatorsCount) <= no.totalVettedKeys
? 0
: Math.min(
totalExitedValidators + targetValidatorsCount,
no.totalVettedKeys
) - totalDepositedValidators;
uint256 activeValidatorsCount = no.totalDepositedKeys -
no.totalExitedKeys;
depositableValidatorsCount = Math.min(
targetValidatorsCount > activeValidatorsCount
? targetValidatorsCount - activeValidatorsCount
: 0,
depositableValidatorsCount
);
}
}

Expand Down Expand Up @@ -800,31 +809,41 @@ contract CSModule is ICSModule, CSModuleBase {
function updateExitedValidatorsCount(
bytes calldata nodeOperatorIds,
bytes calldata exitedValidatorsCounts
) external {
// TODO: implement
// emit ExitedSigningKeysCountChanged(
// nodeOperatorId,
// exitedValidatorsCount
// );
}
) external onlyStakingRouter {
ValidatorCountsReport.validate(nodeOperatorIds, exitedValidatorsCounts);

/// @notice Reports withdrawn validator for node operator
/// @param withdrawProof Withdraw proof
/// @param validatorId ID of the validator
/// @param nodeOperatorId ID of the node operator
/// @param withdrawnBalance Amount of withdrawn balance
function reportWithdrawnValidator(
bytes32[] memory withdrawProof,
uint256 validatorId,
uint256 nodeOperatorId,
uint256 withdrawnBalance
) external {
// TODO: implement me
}
for (
uint256 i = 0;
i < ValidatorCountsReport.count(nodeOperatorIds);
i++
) {
(
uint256 nodeOperatorId,
uint256 exitedValidatorsCount
) = ValidatorCountsReport.next(
nodeOperatorIds,
exitedValidatorsCounts,
i
);
if (nodeOperatorId >= _nodeOperatorsCount)
revert NodeOperatorDoesNotExist();

/// @notice Triggers the node operator's unbonded validators to exit
function exitUnbondedValidators(uint256 nodeOperatorId) external {
// TODO: implement me
NodeOperator storage no = _nodeOperators[nodeOperatorId];
if (exitedValidatorsCount > no.totalDepositedKeys)
revert ExitedKeysHigherThanTotalDeposited();
if (exitedValidatorsCount < no.totalExitedKeys)
revert ExitedKeysDecrease();
if (exitedValidatorsCount == no.totalExitedKeys) continue;

_totalExitedValidators +=
exitedValidatorsCount -
no.totalExitedKeys;
no.totalExitedKeys = exitedValidatorsCount;
emit ExitedSigningKeysCountChanged(
nodeOperatorId,
exitedValidatorsCount
);
}
}

/// @notice Triggers the node operator's validator to exit by DAO decision
Expand Down Expand Up @@ -1088,6 +1107,29 @@ contract CSModule is ICSModule, CSModuleBase {
_checkForOutOfBond(nodeOperatorId);
}

function submitWithdrawal(
bytes32 /*withdrawalProof*/,
uint256 nodeOperatorId,
uint256 validatorId,
uint256 withdrawalBalance
) external onlyExistingNodeOperator(nodeOperatorId) {
// TODO: check for withdrawal proof
// TODO: consider asserting that withdrawn keys count is not higher than exited keys count
NodeOperator storage no = _nodeOperators[nodeOperatorId];

no.totalWithdrawnKeys += 1;

if (withdrawalBalance < DEPOSIT_SIZE) {
accounting.penalize(
nodeOperatorId,
DEPOSIT_SIZE - withdrawalBalance
);
_checkForOutOfBond(nodeOperatorId);
}

emit WithdrawalSubmitted(validatorId, withdrawalBalance);
}

/// @notice Called when withdrawal credentials changed by DAO
function onWithdrawalCredentialsChanged() external {
revert("NOT_IMPLEMENTED");
Expand Down
129 changes: 129 additions & 0 deletions test/CSModule.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1631,6 +1631,100 @@ contract CsmUpdateStuckValidatorsCount is CSMCommon {
}
}

contract CsmUpdateExitedValidatorsCount is CSMCommon {
function test_updateExitedValidatorsCount_NonZero() public {
uint256 noId = createNodeOperator(1);
csm.vetKeys(noId, 1);
csm.obtainDepositData(1, "");

vm.expectEmit(true, true, false, true, address(csm));
emit ExitedSigningKeysCountChanged(noId, 1);
csm.updateExitedValidatorsCount(
bytes.concat(bytes8(0x0000000000000000)),
bytes.concat(bytes16(0x00000000000000000000000000000001))
);

NodeOperatorSummary memory noSummary = getNodeOperatorSummary(noId);
assertEq(
noSummary.totalExitedValidators,
1,
"totalExitedValidators not increased"
);

(uint256 totalExitedValidators, , ) = csm.getStakingModuleSummary();
assertEq(
totalExitedValidators,
1,
"totalExitedValidators not increased"
);
}

function test_updateExitedValidatorsCount_RevertIfNoNodeOperator() public {
vm.expectRevert(NodeOperatorDoesNotExist.selector);
csm.updateExitedValidatorsCount(
bytes.concat(bytes8(0x0000000000000000)),
bytes.concat(bytes16(0x00000000000000000000000000000001))
);
}

function test_updateExitedValidatorsCount_RevertIfNotStakingRouter()
public
{
// TODO implement
vm.skip(true);
}

function test_updateExitedValidatorsCount_RevertIfCountMoreThanDeposited()
public
{
uint256 noId = createNodeOperator(1);

vm.expectRevert(ExitedKeysHigherThanTotalDeposited.selector);
csm.updateExitedValidatorsCount(
bytes.concat(bytes8(0x0000000000000000)),
bytes.concat(bytes16(0x00000000000000000000000000000001))
);
}

function test_updateExitedValidatorsCount_RevertIfExitedKeysDecrease()
public
{
uint256 noId = createNodeOperator(1);
csm.vetKeys(noId, 1);
csm.obtainDepositData(1, "");

csm.updateExitedValidatorsCount(
bytes.concat(bytes8(0x0000000000000000)),
bytes.concat(bytes16(0x00000000000000000000000000000001))
);

vm.expectRevert(ExitedKeysDecrease.selector);
csm.updateExitedValidatorsCount(
bytes.concat(bytes8(0x0000000000000000)),
bytes.concat(bytes16(0x00000000000000000000000000000000))
);
}

function test_updateExitedValidatorsCount_NoEventIfSameValue() public {
uint256 noId = createNodeOperator(1);
csm.vetKeys(noId, 1);
csm.obtainDepositData(1, "");

csm.updateExitedValidatorsCount(
bytes.concat(bytes8(0x0000000000000000)),
bytes.concat(bytes16(0x00000000000000000000000000000001))
);

vm.recordLogs();
csm.updateExitedValidatorsCount(
bytes.concat(bytes8(0x0000000000000000)),
bytes.concat(bytes16(0x00000000000000000000000000000001))
);
Vm.Log[] memory logs = vm.getRecordedLogs();
assertEq(logs.length, 0);
}
}

contract CsmPenalize is CSMCommon {
function test_penalize_NoUnvet() public {
uint256 noId = createNodeOperator();
Expand Down Expand Up @@ -1792,3 +1886,38 @@ contract CsmSettleELRewardsStealingPenalty is CSMCommon {
assertEq(lock.retentionUntil, 0);
}
}

contract CsmSubmitWithdrawal is CSMCommon {
function test_submitWithdrawal() public {
uint256 validatorId = 1;
uint256 noId = createNodeOperator();
csm.vetKeys(noId, 1);
csm.obtainDepositData(1, "");

vm.expectEmit(true, true, true, true, address(csm));
emit WithdrawalSubmitted(validatorId, csm.DEPOSIT_SIZE());
csm.submitWithdrawal("", noId, validatorId, csm.DEPOSIT_SIZE());

CSModule.NodeOperatorInfo memory no = csm.getNodeOperator(noId);
assertEq(no.totalWithdrawnValidators, 1);
}

function test_submitWithdrawal_lowExitBalance() public {
uint256 validatorId = 1;
uint256 noId = createNodeOperator();
uint256 depositSize = csm.DEPOSIT_SIZE();
csm.vetKeys(noId, 1);
csm.obtainDepositData(1, "");

vm.expectCall(
address(accounting),
abi.encodeWithSelector(accounting.penalize.selector, noId, 1 ether)
);
csm.submitWithdrawal("", noId, validatorId, depositSize - 1 ether);
}

function test_submitWithdrawal_RevertWhenNoNodeOperator() public {
vm.expectRevert(NodeOperatorDoesNotExist.selector);
csm.submitWithdrawal("", 0, 0, 0);
}
}

0 comments on commit 0ec8999

Please sign in to comment.