diff --git a/src/CSModule.sol b/src/CSModule.sol index 5bb4cb98..59f3aeeb 100644 --- a/src/CSModule.sol +++ b/src/CSModule.sol @@ -670,6 +670,7 @@ contract CSModule is ICSModule, CSModuleBase, AccessControl, PausableUntil { info.totalWithdrawnValidators = no.totalWithdrawnKeys; info.totalAddedValidators = no.totalAddedKeys; info.totalDepositedValidators = no.totalDepositedKeys; + info.enqueuedCount = no.enqueuedCount; return info; } diff --git a/src/interfaces/ICSModule.sol b/src/interfaces/ICSModule.sol index 6afd9364..13651bf0 100644 --- a/src/interfaces/ICSModule.sol +++ b/src/interfaces/ICSModule.sol @@ -16,6 +16,7 @@ interface ICSModule is IStakingModule { uint256 totalWithdrawnValidators; uint256 totalAddedValidators; uint256 totalDepositedValidators; + uint256 enqueuedCount; } /// @notice Returns the node operator by id diff --git a/test/CSModule.t.sol b/test/CSModule.t.sol index 84659f26..c466ed75 100644 --- a/test/CSModule.t.sol +++ b/test/CSModule.t.sol @@ -108,6 +108,13 @@ abstract contract CSMFixtures is Test, Fixtures, Utilities, CSModuleBase { csm.decreaseOperatorVettedKeys(UintArr(noId), UintArr(to)); } + function setStuck(uint256 noId, uint256 to) internal { + csm.updateStuckValidatorsCount( + bytes.concat(bytes8(uint64(noId))), + bytes.concat(bytes16(uint128(to))) + ); + } + // Checks that the queue is in the expected state starting from its head. function _assertQueueState(BatchInfo[] memory exp) internal { (uint128 curr, ) = csm.queue(); // queue.head @@ -792,16 +799,31 @@ contract CSMObtainDepositData is CSMCommon { function test_obtainDepositData_counters() public { uint256 noId = createNodeOperator(); - vm.expectEmit(true, true, false, true, address(csm)); + vm.expectEmit(true, true, true, true, address(csm)); emit DepositedSigningKeysCountChanged(noId, 1); csm.obtainDepositData(1, ""); - CSModule.NodeOperatorInfo memory no = csm.getNodeOperator(0); - NodeOperatorSummary memory summary = getNodeOperatorSummary(0); + CSModule.NodeOperatorInfo memory no = csm.getNodeOperator(noId); + NodeOperatorSummary memory summary = getNodeOperatorSummary(noId); + assertEq(no.enqueuedCount, 0); assertEq(no.totalDepositedValidators, 1); assertEq(summary.depositableValidatorsCount, 0); } + function test_obtainDepositData_counters_WhenLessThanLastBatch() public { + uint256 noId = createNodeOperator(7); + + vm.expectEmit(true, true, true, true, address(csm)); + emit DepositedSigningKeysCountChanged(noId, 3); + csm.obtainDepositData(3, ""); + + CSModule.NodeOperatorInfo memory no = csm.getNodeOperator(noId); + NodeOperatorSummary memory summary = getNodeOperatorSummary(noId); + assertEq(no.enqueuedCount, 4); + assertEq(no.totalDepositedValidators, 3); + assertEq(summary.depositableValidatorsCount, 4); + } + function test_obtainDepositData_RevertWhenNoMoreKeys() public { vm.expectRevert(NotEnoughKeys.selector); csm.obtainDepositData(1, ""); @@ -1180,15 +1202,60 @@ contract CsmQueueOps is CSMCommon { csm.cleanDepositQueue(LOOKUP_DEPTH); _assertQueueIsEmpty(); } + + function test_normalizeQueue_NothingToDo() public { + // `normalizeQueue` will be called on creating a node operator and uploading a key. + uint256 noId = createNodeOperator(); + + vm.recordLogs(); + vm.prank(nodeOperator); + csm.normalizeQueue(noId); + Vm.Log[] memory logs = vm.getRecordedLogs(); + assertEq(logs.length, 0); + } + + function test_normalizeQueue_OnSkippedKeys_WhenStuckKeys() public { + uint256 noId = createNodeOperator(7); + csm.obtainDepositData(3, ""); + setStuck(noId, 1); + csm.cleanDepositQueue(1); + setStuck(noId, 0); + + vm.expectEmit(true, true, true, true, address(csm)); + emit BatchEnqueued(noId, 4); + + vm.prank(nodeOperator); + csm.normalizeQueue(noId); + } + + function test_normalizeQueue_OnSkippedKeys_WhenTargetLimit() public { + uint256 noId = createNodeOperator(7); + csm.updateTargetValidatorsLimits({ + nodeOperatorId: noId, + isTargetLimitActive: true, + targetLimit: 0 + }); + csm.cleanDepositQueue(1); + csm.updateTargetValidatorsLimits({ + nodeOperatorId: noId, + isTargetLimitActive: true, + targetLimit: 7 + }); + + vm.expectEmit(true, true, true, true, address(csm)); + emit BatchEnqueued(noId, 7); + + vm.prank(nodeOperator); + csm.normalizeQueue(noId); + } } contract CsmUnvetKeys is CSMCommon { - // TODO: more tests for unvetKeys function test_unvetKeys_counters() public { uint256 noId = createNodeOperator(3); uint256 nonce = csm.getNonce(); - vm.expectEmit(true, true, false, true, address(csm)); + vm.expectEmit(true, true, true, true, address(csm)); emit VettedSigningKeysCountChanged(noId, 1); unvetKeys({ noId: noId, to: 1 }); @@ -1198,6 +1265,30 @@ contract CsmUnvetKeys is CSMCommon { assertEq(no.totalVettedValidators, 1); assertEq(summary.depositableValidatorsCount, 1); } + + function test_unvetKeys_MultipleOperators() public { + createNodeOperator(3); + createNodeOperator(7); + uint256 nonce = csm.getNonce(); + + vm.expectEmit(true, true, true, true, address(csm)); + emit VettedSigningKeysCountChanged(0, 2); + emit VettedSigningKeysCountChanged(1, 3); + csm.decreaseOperatorVettedKeys(UintArr(0, 1), UintArr(2, 3)); + + assertEq(csm.getNonce(), nonce + 1); + CSModule.NodeOperatorInfo memory no; + no = csm.getNodeOperator(0); + assertEq(no.totalVettedValidators, 2); + no = csm.getNodeOperator(1); + assertEq(no.totalVettedValidators, 3); + } + + function test_unvetKeys_RevertIfNodeOperatorDoesntExist() public { + createNodeOperator(); // Make sure there is at least one node operator. + vm.expectRevert(NodeOperatorDoesNotExist.selector); + csm.decreaseOperatorVettedKeys(UintArr(1), UintArr(0)); + } } contract CsmViewKeys is CSMCommon { diff --git a/test/helpers/Utilities.sol b/test/helpers/Utilities.sol index 5efa5900..8864f39a 100644 --- a/test/helpers/Utilities.sol +++ b/test/helpers/Utilities.sol @@ -100,14 +100,22 @@ contract Utilities is CommonBase { return new uint256[](0); } - /// @dev It's super annoying to make a memory array all the time without an array literal, so the function pretends - /// to provide the familiar syntax. By overloading the function, we can have a different number of arguments. function UintArr(uint256 e0) public pure returns (uint256[] memory) { uint256[] memory arr = new uint256[](1); arr[0] = e0; return arr; } + function UintArr( + uint256 e0, + uint256 e1 + ) public pure returns (uint256[] memory) { + uint256[] memory arr = new uint256[](2); + arr[0] = e0; + arr[1] = e1; + return arr; + } + /// See https://github.com/Vectorized/solady - MIT licensed. /// @dev Fills the memory with junk, for more robust testing of inline assembly /// which reads/write to the memory.