Skip to content

test: integration with slashingRegistryCoordinator #388

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

Merged
merged 14 commits into from
Feb 12, 2025
Merged
60 changes: 57 additions & 3 deletions src/RegistryCoordinator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
pragma solidity ^0.8.27;

import {IPauserRegistry} from "eigenlayer-contracts/src/contracts/interfaces/IPauserRegistry.sol";
import {IAllocationManager} from
"eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
import {
IAllocationManager,
OperatorSet
} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
import {IBLSApkRegistry, IBLSApkRegistryTypes} from "./interfaces/IBLSApkRegistry.sol";
import {IStakeRegistry} from "./interfaces/IStakeRegistry.sol";
import {IIndexRegistry} from "./interfaces/IIndexRegistry.sol";
Expand Down Expand Up @@ -132,7 +134,11 @@ contract RegistryCoordinator is RegistryCoordinatorStorage {
OnlyM2QuorumsAllowed()
);

_deregisterOperator({operator: msg.sender, quorumNumbers: quorumNumbers});
_deregisterOperator({
operator: msg.sender,
quorumNumbers: quorumNumbers,
shouldForceDeregister: false
});
}

/// @inheritdoc IRegistryCoordinator
Expand Down Expand Up @@ -197,6 +203,54 @@ contract RegistryCoordinator is RegistryCoordinatorStorage {
}
}

/**
* @dev Helper function to update operator stakes and deregister loiterers
* Loiterers are AVS registered operators who have force deregistered from the OperatorSet/quorum
* in the core EigenLayer contract AllocationManager but not deregistered from the OperatorSet/quorum
* in this contract. Potentially due to out of gas errors in the deregistration callback. This function
* will handle that edge case by deregistering the operator from the AVS if they are no longer registered
* in the AllocationManager.
*/
function _updateStakesAndDeregisterLoiterers(
address[] memory operators,
bytes32[] memory operatorIds,
uint8 quorumNumber
) internal virtual override {
bytes memory singleQuorumNumber = new bytes(1);
singleQuorumNumber[0] = bytes1(quorumNumber);
bool[] memory doesNotMeetStakeThreshold =
stakeRegistry.updateOperatorsStake(operators, operatorIds, quorumNumber);

for (uint256 i = 0; i < operators.length; ++i) {
bool isM2Quorum = _isM2Quorum(quorumNumber);
bool registeredInCore;
// If its an operatorSet quorum, its possible for registeredInCore to be true/false
// so check for operatorSet inclusion in the AllocationManager
if (!isM2Quorum) {
registeredInCore = allocationManager.isMemberOfOperatorSet(
operators[i], OperatorSet({avs: accountIdentifier, id: uint32(quorumNumber)})
);
}

// Determine if the operator should be deregistered
// If the operator does not have the minimum stake, they need to be force deregistered.
// Additionally, it is possible for an operator to have deregistered from an OperatorSet
// in the core EigenLayer contract AllocationManager but not have the deregistration
// callback succeed here in `deregisterOperator` due to out of gas errors. If that is the case,
// we need to deregister the operator from the OperatorSet in this contract
bool shouldDeregister =
doesNotMeetStakeThreshold[i] || (!registeredInCore && !isM2Quorum);

if (shouldDeregister) {
_deregisterOperator({
operator: operators[i],
quorumNumbers: singleQuorumNumber,
shouldForceDeregister: registeredInCore
});
}
}
}

/// @notice Return bitmap representing all quorums(Legacy M2 and OperatorSet) quorums
function _getTotalQuorumBitmap() internal view returns (uint256) {
// This creates a number where all bits up to quorumCount are set to 1
Expand Down
112 changes: 90 additions & 22 deletions src/SlashingRegistryCoordinator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,11 @@ contract SlashingRegistryCoordinator is
uint32[] memory operatorSetIds
) external override onlyAllocationManager onlyWhenNotPaused(PAUSED_DEREGISTER_OPERATOR) {
bytes memory quorumNumbers = _getQuorumNumbers(operatorSetIds);
_deregisterOperator(operator, quorumNumbers);
_deregisterOperator({
operator: operator,
quorumNumbers: quorumNumbers,
shouldForceDeregister: false
});
}

/// @inheritdoc ISlashingRegistryCoordinator
Expand All @@ -243,16 +247,9 @@ contract SlashingRegistryCoordinator is
bytes memory quorumNumbers = currentBitmap.bitmapToBytesArray();
for (uint256 j = 0; j < quorumNumbers.length; j++) {
// update the operator's stake for each quorum
uint8 quorumNumber = uint8(quorumNumbers[j]);
bool[] memory shouldBeDeregistered = stakeRegistry.updateOperatorsStake(
singleOperator, singleOperatorId, quorumNumber
_updateStakesAndDeregisterLoiterers(
singleOperator, singleOperatorId, uint8(quorumNumbers[j])
);

if (shouldBeDeregistered[0]) {
bytes memory singleQuorumNumber = new bytes(1);
singleQuorumNumber[0] = quorumNumbers[j];
_deregisterOperator(operators[i], singleQuorumNumber);
}
}
}
}
Expand Down Expand Up @@ -303,13 +300,7 @@ contract SlashingRegistryCoordinator is
prevOperatorAddress = operator;
}

bool[] memory shouldBeDeregistered =
stakeRegistry.updateOperatorsStake(currQuorumOperators, operatorIds, quorumNumber);
for (uint256 j = 0; j < currQuorumOperators.length; ++j) {
if (shouldBeDeregistered[j]) {
_deregisterOperator(currQuorumOperators[j], quorumNumbers[i:i + 1]);
}
}
_updateStakesAndDeregisterLoiterers(currQuorumOperators, operatorIds, quorumNumber);

// Update timestamp that all operators in quorum have been updated all at once
quorumUpdateBlockNumber[quorumNumber] = block.number;
Expand Down Expand Up @@ -344,7 +335,11 @@ contract SlashingRegistryCoordinator is
operatorInfo.status == OperatorStatus.REGISTERED && !quorumsToRemove.isEmpty()
&& quorumsToRemove.isSubsetOf(currentBitmap)
) {
_deregisterOperator({operator: operator, quorumNumbers: quorumNumbers});
_deregisterOperator({
operator: operator,
quorumNumbers: quorumNumbers,
shouldForceDeregister: true
});
}
}

Expand Down Expand Up @@ -522,7 +517,11 @@ contract SlashingRegistryCoordinator is

bytes memory singleQuorumNumber = new bytes(1);
singleQuorumNumber[0] = quorumNumbers[i];
_deregisterOperator(operatorKickParams[i].operator, singleQuorumNumber);
_deregisterOperator({
operator: operatorKickParams[i].operator,
quorumNumbers: singleQuorumNumber,
shouldForceDeregister: true
});
}
}
}
Expand All @@ -531,8 +530,16 @@ contract SlashingRegistryCoordinator is
* @dev Deregister the operator from one or more quorums
* This method updates the operator's quorum bitmap and status, then deregisters
* the operator with the BLSApkRegistry, IndexRegistry, and StakeRegistry
* @param operator the operator to deregister
* @param quorumNumbers the quorum numbers to deregister from
* @param shouldForceDeregister whether the operator needs to be deregistered from the OperatorSets of
* the core EigenLayer contract AllocationManager
*/
function _deregisterOperator(address operator, bytes memory quorumNumbers) internal virtual {
function _deregisterOperator(
address operator,
bytes memory quorumNumbers,
bool shouldForceDeregister
) internal virtual {
// Fetch the operator's info and ensure they are registered
OperatorInfo storage operatorInfo = _operatorInfo[operator];
bytes32 operatorId = operatorInfo.operatorId;
Expand Down Expand Up @@ -571,8 +578,9 @@ contract SlashingRegistryCoordinator is
stakeRegistry.deregisterOperator(operatorId, quorumNumbers);
indexRegistry.deregisterOperator(operatorId, quorumNumbers);

// If the caller is not the allocationManager, then this is a force deregistration not consented by the operator
if (msg.sender != address(allocationManager)) {
// If the operator is not deregistered from the EigenLayer core protocol, then we need to force deregister them
// from their respective OperatorSets in the AllocationManager
if (shouldForceDeregister) {
_forceDeregisterOperator(operator, quorumNumbers);
}

Expand All @@ -583,13 +591,35 @@ contract SlashingRegistryCoordinator is
/**
* @notice Helper function to handle operator set deregistration for OperatorSets quorums. This is used
* when an operator is force-deregistered from a set of quorums.
* Due to deregistration being possible in the AllocationManager but not in the AVS as a result of the
* try/catch in `AllocationManager.deregisterFromOperatorSets`, we need to first check that the operator
* is not already deregistered from the OperatorSet in the AllocationManager.
* @param operator The operator to deregister
* @param quorumNumbers The quorum numbers the operator is force-deregistered from
*/
function _forceDeregisterOperator(
address operator,
bytes memory quorumNumbers
) internal virtual {
uint32[] memory operatorSetIds = new uint32[](quorumNumbers.length);
uint256 numDeregister = 0;
for (uint256 i = 0; i < quorumNumbers.length; ++i) {
uint32 operatorSetId = uint32(uint8(quorumNumbers[i]));
if (
allocationManager.isMemberOfOperatorSet(
operator, OperatorSet({avs: accountIdentifier, id: operatorSetId})
)
) {
operatorSetIds[numDeregister] = operatorSetId;
numDeregister++;
}
}

// resize operatorSetIds array length to numDeregister
assembly {
mstore(operatorSetIds, numDeregister)
}

allocationManager.deregisterFromOperatorSets(
IAllocationManagerTypes.DeregisterParams({
operator: operator,
Expand All @@ -599,6 +629,44 @@ contract SlashingRegistryCoordinator is
);
}

/**
* @dev Helper function to update operator stakes and deregister loiterers
* Loiterers are AVS registered operators who have force deregistered from the OperatorSet/quorum
* in the core EigenLayer contract AllocationManager but not deregistered from the OperatorSet/quorum
* in this contract. Potentially due to out of gas errors in the deregistration callback. This function
* will handle that edge case by deregistering the operator from the AVS if they are no longer registered
* in the AllocationManager.
*/
function _updateStakesAndDeregisterLoiterers(
address[] memory operators,
bytes32[] memory operatorIds,
uint8 quorumNumber
) internal virtual {
bytes memory singleQuorumNumber = new bytes(1);
singleQuorumNumber[0] = bytes1(quorumNumber);
bool[] memory doesNotMeetStakeThreshold =
stakeRegistry.updateOperatorsStake(operators, operatorIds, quorumNumber);
for (uint256 j = 0; j < operators.length; ++j) {
// whether the operator is registered in the core EigenLayer contract AllocationManager
bool registeredInCore = allocationManager.isMemberOfOperatorSet(
operators[j], OperatorSet({avs: accountIdentifier, id: uint32(quorumNumber)})
);

// If the operator does not have the minimum stake, they need to be force deregistered.
// Additionally, it is possible for an operator to have deregistered from an OperatorSet
// in the core EigenLayer contract AllocationManager but not have the deregistration
// callback succeed here in `deregisterOperator` due to out of gas errors. If that is the case,
// we need to deregister the operator from the OperatorSet in this contract
if (doesNotMeetStakeThreshold[j] || !registeredInCore) {
_deregisterOperator({
operator: operators[j],
quorumNumbers: singleQuorumNumber,
shouldForceDeregister: registeredInCore
});
}
}
}

/**
* @notice Checks if the caller is the ejector
* @dev Reverts if the caller is not the ejector
Expand Down
6 changes: 0 additions & 6 deletions src/interfaces/IInstantSlasher.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,6 @@ import {ISlasher} from "./ISlasher.sol";
/// @notice A slashing contract that immediately executes slashing requests without any delay or veto period
/// @dev Extends base interfaces to provide access controlled slashing functionality
interface IInstantSlasher is ISlasher {
/// @notice Initializes the contract with a slasher address
/// @param _slasher Address authorized to create and fulfill slashing requests
function initialize(
address _slasher
) external;

/// @notice Immediately executes a slashing request
/// @param _slashingParams Parameters defining the slashing request including operator and amount
/// @dev Can only be called by the authorized slasher
Expand Down
3 changes: 3 additions & 0 deletions src/interfaces/ISlasher.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,7 @@ interface ISlasherEvents is ISlasherTypes {
interface ISlasher is ISlasherErrors, ISlasherEvents {
/// @notice Returns the address authorized to create and fulfill slashing requests
function slasher() external view returns (address);

/// @notice Returns the next slashing request ID
function nextRequestId() external view returns (uint256);
}
10 changes: 2 additions & 8 deletions src/interfaces/IVetoableSlasher.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ interface IVetoableSlasherTypes {
/// @notice Structure containing details about a vetoable slashing request
struct VetoableSlashingRequest {
IAllocationManager.SlashingParams params;
uint256 requestTimestamp;
uint256 requestBlock;
SlashingStatus status;
}
}
Expand Down Expand Up @@ -58,17 +58,11 @@ interface IVetoableSlasher is
IVetoableSlasherEvents
{
/// @notice Duration of the veto period during which the veto committee can cancel slashing requests
/// @dev Set to 3 days (259,200 seconds)
function VETO_PERIOD() external view returns (uint256);
function vetoWindowBlocks() external view returns (uint32);

/// @notice Address of the committee that has veto power over slashing requests
function vetoCommittee() external view returns (address);

/// @notice Initializes the contract with a veto committee and slasher address
/// @param _vetoCommittee Address of the committee that can veto slashing requests
/// @param _slasher Address authorized to create and fulfill slashing requests
function initialize(address _vetoCommittee, address _slasher) external;

/// @notice Queues a new slashing request
/// @param params Parameters defining the slashing request including operator and amount
/// @dev Can only be called by the authorized slasher
Expand Down
9 changes: 1 addition & 8 deletions src/slashers/InstantSlasher.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,7 @@ contract InstantSlasher is IInstantSlasher, SlasherBase {
IAllocationManager _allocationManager,
ISlashingRegistryCoordinator _slashingRegistryCoordinator,
address _slasher
) SlasherBase(_allocationManager, _slashingRegistryCoordinator) {}

/// @inheritdoc IInstantSlasher
function initialize(
address _slasher
) external override initializer {
__SlasherBase_init(_slasher);
}
) SlasherBase(_allocationManager, _slashingRegistryCoordinator, _slasher) {}

/// @inheritdoc IInstantSlasher
function fulfillSlashingRequest(
Expand Down
Loading
Loading