Skip to content

Commit edb7923

Browse files
committed
feat: add reportUnexitedValidator method to staking router
1 parent 50cee98 commit edb7923

File tree

4 files changed

+74
-0
lines changed

4 files changed

+74
-0
lines changed

contracts/0.8.9/StakingRouter.sol

+22
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version
133133
bytes32 public constant STAKING_MODULE_MANAGE_ROLE = keccak256("STAKING_MODULE_MANAGE_ROLE");
134134
bytes32 public constant STAKING_MODULE_UNVETTING_ROLE = keccak256("STAKING_MODULE_UNVETTING_ROLE");
135135
bytes32 public constant REPORT_EXITED_VALIDATORS_ROLE = keccak256("REPORT_EXITED_VALIDATORS_ROLE");
136+
bytes32 public constant REPORT_UNEXITED_VALIDATORS_ROLE = keccak256("REPORT_UNEXITED_VALIDATORS_ROLE");
136137
bytes32 public constant UNSAFE_SET_EXITED_VALIDATORS_ROLE = keccak256("UNSAFE_SET_EXITED_VALIDATORS_ROLE");
137138
bytes32 public constant REPORT_REWARDS_MINTED_ROLE = keccak256("REPORT_REWARDS_MINTED_ROLE");
138139

@@ -1321,6 +1322,27 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version
13211322
emit WithdrawalCredentialsSet(_withdrawalCredentials, msg.sender);
13221323
}
13231324

1325+
/// @notice Reports the duration a validator has remained eligible for exit after exit request.
1326+
/// @dev Notify staking module how many seconds have passed since a validator first became eligible
1327+
/// to exit following an exit request but has not yet exited.
1328+
/// @param stakingModuleId The identifier of the staking module.
1329+
/// @param nodeOperatorId The identifier of the node operator.
1330+
/// @param publicKey The public key of the validator being reported.
1331+
/// @param secondsSinceEligibleExitRequest Seconds since the validator first
1332+
/// became eligible to exit following an exit request but has not yet exited.
1333+
function reportUnexitedValidator(
1334+
uint256 stakingModuleId,
1335+
uint256 nodeOperatorId,
1336+
bytes calldata publicKey,
1337+
uint256 secondsSinceEligibleExitRequest
1338+
) external onlyRole(REPORT_UNEXITED_VALIDATORS_ROLE) {
1339+
_getIStakingModuleById(stakingModuleId).reportUnexitedValidator(
1340+
nodeOperatorId,
1341+
publicKey,
1342+
secondsSinceEligibleExitRequest
1343+
);
1344+
}
1345+
13241346
/// @notice Returns current credentials to withdraw ETH on Consensus Layer side.
13251347
/// @return Withdrawal credentials.
13261348
function getWithdrawalCredentials() public view returns (bytes32) {

contracts/0.8.9/interfaces/IStakingModule.sol

+13
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,19 @@ interface IStakingModule {
169169
/// Details about error data: https://docs.soliditylang.org/en/v0.8.9/control-structures.html#error-handling-assert-require-revert-and-exceptions
170170
function onWithdrawalCredentialsChanged() external;
171171

172+
/// @notice Reports the duration a validator has remained unexited after becoming eligible for exit.
173+
/// @dev Notify staking module how many seconds have passed since a validator first became eligible
174+
/// to exit following an exit request but has not yet exited.
175+
/// @param nodeOperatorId Id of the node operator.
176+
/// @param publicKey The public key of the validator being reported.
177+
/// @param secondsSinceEligibleExitRequest Seconds since the validator first
178+
/// became eligible to exit following an exit request but has not yet exited.
179+
function reportUnexitedValidator(
180+
uint256 nodeOperatorId,
181+
bytes calldata publicKey,
182+
uint256 secondsSinceEligibleExitRequest
183+
) external;
184+
172185
/// @dev Event to be emitted on StakingModule's nonce change
173186
event NonceChanged(uint256 nonce);
174187

test/0.8.9/contracts/StakingModule__MockForStakingRouter.sol

+14
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,20 @@ contract StakingModule__MockForStakingRouter is IStakingModule {
216216

217217
event Mock__onExitedAndStuckValidatorsCountsUpdated();
218218

219+
function reportUnexitedValidator(
220+
uint256 nodeOperatorId,
221+
bytes calldata publicKey,
222+
uint256 secondsSinceEligibleExitRequest
223+
) external {
224+
emit Mock__UnexitedValidatorReported(nodeOperatorId, publicKey, secondsSinceEligibleExitRequest);
225+
}
226+
227+
event Mock__UnexitedValidatorReported(
228+
uint256 nodeOperatorId,
229+
bytes publicKey,
230+
uint256 secondsSinceEligibleExitRequest
231+
);
232+
219233
bool private onExitedAndStuckValidatorsCountsUpdatedShouldRevert = false;
220234
bool private onExitedAndStuckValidatorsCountsUpdatedShouldRunOutGas = false;
221235

test/0.8.9/stakingRouter/stakingRouter.module-sync.test.ts

+25
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ describe("StakingRouter.sol:module-sync", () => {
7272
stakingRouter.grantRole(await stakingRouter.STAKING_MODULE_UNVETTING_ROLE(), admin),
7373
stakingRouter.grantRole(await stakingRouter.UNSAFE_SET_EXITED_VALIDATORS_ROLE(), admin),
7474
stakingRouter.grantRole(await stakingRouter.REPORT_REWARDS_MINTED_ROLE(), admin),
75+
stakingRouter.grantRole(await stakingRouter.REPORT_UNEXITED_VALIDATORS_ROLE(), admin),
7576
]);
7677

7778
// add staking module
@@ -864,6 +865,30 @@ describe("StakingRouter.sol:module-sync", () => {
864865
});
865866
});
866867

868+
context("reportUnexitedValidator", () => {
869+
const nodeOperatorId = 2n;
870+
const pubkey = "0x" + "01".repeat(48);
871+
const secondsSinceEligibleExitRequest = 100;
872+
873+
it("Reverts if the caller does not have the role", async () => {
874+
await expect(
875+
stakingRouter
876+
.connect(user)
877+
.reportUnexitedValidator(moduleId, nodeOperatorId, pubkey, secondsSinceEligibleExitRequest),
878+
).to.be.revertedWithOZAccessControlError(user.address, await stakingRouter.REPORT_UNEXITED_VALIDATORS_ROLE());
879+
});
880+
881+
it("Report unexited stuck validator", async () => {
882+
await expect(
883+
stakingRouter
884+
.connect(admin)
885+
.reportUnexitedValidator(moduleId, nodeOperatorId, pubkey, secondsSinceEligibleExitRequest),
886+
)
887+
.to.emit(stakingModule, "Mock__UnexitedValidatorReported")
888+
.withArgs(nodeOperatorId, pubkey, secondsSinceEligibleExitRequest);
889+
});
890+
});
891+
867892
context("onValidatorsCountsByNodeOperatorReportingFinished", () => {
868893
it("Reverts if the caller does not have the role", async () => {
869894
await expect(

0 commit comments

Comments
 (0)