Skip to content

Commit

Permalink
Merge pull request #55 from lidofinance/csmodule-upd-interface
Browse files Browse the repository at this point in the history
feat: update CSModule submitWithdrawal interface
  • Loading branch information
madlabman authored Jan 11, 2024
2 parents 0ec8999 + b3a4038 commit b195225
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 21 deletions.
48 changes: 33 additions & 15 deletions src/CSModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,9 @@ contract CSModuleBase {
uint256 targetValidatorsCount
);
event WithdrawalSubmitted(
uint256 indexed validatorId,
uint256 withdrawalBalance
uint256 indexed nodeOperatorId,
uint256 keyIndex,
uint256 amount
);

event BatchEnqueued(
Expand Down Expand Up @@ -128,6 +129,8 @@ contract CSModuleBase {
error QueueBatchUnvettedKeys(bytes32 batch);

error SigningKeysInvalidOffset();

error WithdrawalAlreadySubmitted();
}

contract CSModule is ICSModule, CSModuleBase {
Expand All @@ -152,6 +155,7 @@ contract CSModule is ICSModule, CSModuleBase {
bytes32 private _moduleType;
uint256 private _nonce;
mapping(uint256 => NodeOperator) private _nodeOperators;
mapping(uint256 noIdWithKeyIndex => bool) private _isValidatorWithdrawn;

uint256 private _totalDepositedValidators;
uint256 private _totalExitedValidators;
Expand Down Expand Up @@ -1107,27 +1111,35 @@ contract CSModule is ICSModule, CSModuleBase {
_checkForOutOfBond(nodeOperatorId);
}

/// @notice Report node operator's key as withdrawn and settle withdrawn amount.
/// @param nodeOperatorId Operator ID in the module.
/// @param keyIndex Index of the withdrawn key in the node operator's keys.
/// @param amount Amount of withdrawn ETH in wei.
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
uint256 keyIndex,
uint256 amount
) external onlyExistingNodeOperator(nodeOperatorId) onlyWithdrawalReporter {
NodeOperator storage no = _nodeOperators[nodeOperatorId];
if (keyIndex >= no.totalDepositedKeys) {
revert SigningKeysInvalidOffset();
}

no.totalWithdrawnKeys += 1;
// NOTE: both nodeOperatorId and keyIndex are limited to uint64 by the contract.
uint256 pointer = (nodeOperatorId << 128) | keyIndex;
if (_isValidatorWithdrawn[pointer]) {
revert WithdrawalAlreadySubmitted();
}

if (withdrawalBalance < DEPOSIT_SIZE) {
accounting.penalize(
nodeOperatorId,
DEPOSIT_SIZE - withdrawalBalance
);
_isValidatorWithdrawn[pointer] = true;
no.totalWithdrawnKeys++;

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

emit WithdrawalSubmitted(validatorId, withdrawalBalance);
emit WithdrawalSubmitted(nodeOperatorId, keyIndex, amount);
}

/// @notice Called when withdrawal credentials changed by DAO
Expand Down Expand Up @@ -1450,4 +1462,10 @@ contract CSModule is ICSModule, CSModuleBase {
// TODO: check the role
_;
}

modifier onlyWithdrawalReporter() {
// Here should be a role granted to the CSVerifier contract and/or to the DAO/Oracle.
// TODO: check the role
_;
}
}
21 changes: 21 additions & 0 deletions src/interfaces/ICSModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,25 @@ interface ICSModule is IStakingModule {
function getNodeOperator(
uint256 nodeOperatorId
) external view returns (NodeOperatorInfo memory);

/// @notice Gets node operator signing keys
/// @param nodeOperatorId ID of the node operator
/// @param startIndex Index of the first key
/// @param keysCount Count of keys to get
/// @return Signing keys
function getNodeOperatorSigningKeys(
uint256 nodeOperatorId,
uint256 startIndex,
uint256 keysCount
) external view returns (bytes memory);

/// @notice Report node operator's key as withdrawn and settle withdrawn amount.
/// @param nodeOperatorId Operator ID in the module.
/// @param keyIndex Index of the withdrawn key in the node operator's keys.
/// @param amount Amount of withdrawn ETH in wei.
function submitWithdrawal(
uint256 nodeOperatorId,
uint256 keyIndex,
uint256 amount
) external;
}
12 changes: 6 additions & 6 deletions test/CSModule.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1889,21 +1889,21 @@ contract CsmSettleELRewardsStealingPenalty is CSMCommon {

contract CsmSubmitWithdrawal is CSMCommon {
function test_submitWithdrawal() public {
uint256 validatorId = 1;
uint256 keyIndex = 0;
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());
emit WithdrawalSubmitted(noId, keyIndex, csm.DEPOSIT_SIZE());
csm.submitWithdrawal(noId, keyIndex, csm.DEPOSIT_SIZE());

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

function test_submitWithdrawal_lowExitBalance() public {
uint256 validatorId = 1;
uint256 keyIndex = 0;
uint256 noId = createNodeOperator();
uint256 depositSize = csm.DEPOSIT_SIZE();
csm.vetKeys(noId, 1);
Expand All @@ -1913,11 +1913,11 @@ contract CsmSubmitWithdrawal is CSMCommon {
address(accounting),
abi.encodeWithSelector(accounting.penalize.selector, noId, 1 ether)
);
csm.submitWithdrawal("", noId, validatorId, depositSize - 1 ether);
csm.submitWithdrawal(noId, keyIndex, depositSize - 1 ether);
}

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

0 comments on commit b195225

Please sign in to comment.