Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/eigenlayer-contracts
2 changes: 0 additions & 2 deletions src/EjectionManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import {
IStakeRegistry
} from "./EjectionManagerStorage.sol";

// TODO: double check order of inheritance since we separated storage from logic...

/**
* @title Used for automated ejection of operators from the SlashingRegistryCoordinator under a ratelimit
* @author Layr Labs, Inc.
Expand Down
72 changes: 40 additions & 32 deletions src/RegistryCoordinator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,35 @@ contract RegistryCoordinator is RegistryCoordinatorStorage {
*
*/

/// @dev override the _kickOperator function to handle M2 quorum ejection
function _kickOperator(
address operator,
bytes memory quorumNumbers
) internal virtual override {
OperatorInfo storage operatorInfo = _operatorInfo[operator];
uint192 quorumsToRemove =
uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount));
if (operatorInfo.status == OperatorStatus.REGISTERED && !quorumsToRemove.isEmpty()) {
// For each quorum number, check if it's an M2 quorum
for (uint256 i = 0; i < quorumNumbers.length; i++) {
bytes memory singleQuorumNumber = new bytes(1);
singleQuorumNumber[0] = quorumNumbers[i];

if (_isM2Quorum(uint8(quorumNumbers[i]))) {
// For M2 quorums, use _deregisterOperator
_deregisterOperator({
operator: operator,
quorumNumbers: singleQuorumNumber,
shouldForceDeregister: true
});
} else {
// For non-M2 quorums, use _forceDeregisterOperator
_forceDeregisterOperator(operator, singleQuorumNumber);
}
}
}
}

/// @dev override the _forceDeregisterOperator function to handle M2 quorum deregistration
function _forceDeregisterOperator(
address operator,
Expand Down Expand Up @@ -204,14 +233,16 @@ 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.
* @dev Helper function to update operator stakes and deregister operators with insufficient stake
* This function handles two cases:
* 1. Operators who no longer meet the minimum stake requirement for a quorum
* 2. Operators who have been force-deregistered from the AllocationManager but not from this contract
* (e.g. due to out of gas errors in the deregistration callback)
* @param operators The list of operators to check and update
* @param operatorIds The corresponding operator IDs
* @param quorumNumber The quorum number to check stakes for
*/
function _updateStakesAndDeregisterLoiterers(
function _updateOperatorsStakes(
address[] memory operators,
bytes32[] memory operatorIds,
uint8 quorumNumber
Expand All @@ -222,31 +253,8 @@ contract RegistryCoordinator is RegistryCoordinatorStorage {
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
});
if (doesNotMeetStakeThreshold[i]) {
_kickOperator(operators[i], singleQuorumNumber);
}
}
}
Expand Down
118 changes: 59 additions & 59 deletions src/SlashingRegistryCoordinator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,13 @@ contract SlashingRegistryCoordinator is
address _churnApprover,
address _ejector,
uint256 _initialPausedStatus,
address _accountIdentifier
address _avs
) external initializer {
_transferOwnership(_initialOwner);
_setChurnApprover(_churnApprover);
_setPausedStatus(_initialPausedStatus);
_setEjector(_ejector);
_setAccountIdentifier(_accountIdentifier);
_setAvs(_avs);

// Add registry contracts to the registries array
registries.push(address(stakeRegistry));
Expand Down Expand Up @@ -147,9 +147,11 @@ contract SlashingRegistryCoordinator is
/// @inheritdoc ISlashingRegistryCoordinator
function registerOperator(
address operator,
address avs,
uint32[] memory operatorSetIds,
bytes calldata data
) external override onlyAllocationManager onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) {
require(supportsAVS(avs), InvalidAVS());
bytes memory quorumNumbers = _getQuorumNumbers(operatorSetIds);

(
Expand Down Expand Up @@ -222,8 +224,10 @@ contract SlashingRegistryCoordinator is
/// @inheritdoc ISlashingRegistryCoordinator
function deregisterOperator(
address operator,
address avs,
uint32[] memory operatorSetIds
) external override onlyAllocationManager onlyWhenNotPaused(PAUSED_DEREGISTER_OPERATOR) {
require(supportsAVS(avs), InvalidAVS());
bytes memory quorumNumbers = _getQuorumNumbers(operatorSetIds);
_deregisterOperator({
operator: operator,
Expand All @@ -247,9 +251,7 @@ contract SlashingRegistryCoordinator is
bytes memory quorumNumbers = currentBitmap.bitmapToBytesArray();
for (uint256 j = 0; j < quorumNumbers.length; j++) {
// update the operator's stake for each quorum
_updateStakesAndDeregisterLoiterers(
singleOperator, singleOperatorId, uint8(quorumNumbers[j])
);
_updateOperatorsStakes(singleOperator, singleOperatorId, uint8(quorumNumbers[j]));
}
}
}
Expand Down Expand Up @@ -300,7 +302,7 @@ contract SlashingRegistryCoordinator is
prevOperatorAddress = operator;
}

_updateStakesAndDeregisterLoiterers(currQuorumOperators, operatorIds, quorumNumber);
_updateOperatorsStakes(currQuorumOperators, operatorIds, quorumNumber);

// Update timestamp that all operators in quorum have been updated all at once
quorumUpdateBlockNumber[quorumNumber] = block.number;
Expand All @@ -323,24 +325,12 @@ contract SlashingRegistryCoordinator is
*/

/// @inheritdoc ISlashingRegistryCoordinator
function ejectOperator(address operator, bytes memory quorumNumbers) external onlyEjector {
function ejectOperator(
address operator,
bytes memory quorumNumbers
) public virtual onlyEjector {
lastEjectionTimestamp[operator] = block.timestamp;

OperatorInfo storage operatorInfo = _operatorInfo[operator];
bytes32 operatorId = operatorInfo.operatorId;
uint192 quorumsToRemove =
uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount));
uint192 currentBitmap = _currentOperatorBitmap(operatorId);
if (
operatorInfo.status == OperatorStatus.REGISTERED && !quorumsToRemove.isEmpty()
&& quorumsToRemove.isSubsetOf(currentBitmap)
) {
_deregisterOperator({
operator: operator,
quorumNumbers: quorumNumbers,
shouldForceDeregister: true
});
}
_kickOperator(operator, quorumNumbers);
}

/**
Expand Down Expand Up @@ -377,10 +367,10 @@ contract SlashingRegistryCoordinator is
}

/// @inheritdoc ISlashingRegistryCoordinator
function setAccountIdentifier(
address _accountIdentifier
function setAvs(
address _avs
) external onlyOwner {
_setAccountIdentifier(_accountIdentifier);
_setAvs(_avs);
}

/// @inheritdoc ISlashingRegistryCoordinator
Expand All @@ -396,6 +386,25 @@ contract SlashingRegistryCoordinator is
*
*/

/**
* @notice Internal function to handle operator ejection logic
* @param operator The operator to eject
* @param quorumNumbers The quorum numbers to eject the operator from
*/
function _kickOperator(address operator, bytes memory quorumNumbers) internal virtual {
OperatorInfo storage operatorInfo = _operatorInfo[operator];
bytes32 operatorId = operatorInfo.operatorId;
uint192 quorumsToRemove =
uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount));
uint192 currentBitmap = _currentOperatorBitmap(operatorId);
if (
operatorInfo.status == OperatorStatus.REGISTERED && !quorumsToRemove.isEmpty()
&& quorumsToRemove.isSubsetOf(currentBitmap)
) {
_forceDeregisterOperator(operator, quorumNumbers);
}
}

/**
* @notice Register the operator for one or more quorums. This method updates the
* operator's quorum bitmap, socket, and status, then registers them with each registry.
Expand Down Expand Up @@ -517,11 +526,7 @@ contract SlashingRegistryCoordinator is

bytes memory singleQuorumNumber = new bytes(1);
singleQuorumNumber[0] = quorumNumbers[i];
_deregisterOperator({
operator: operatorKickParams[i].operator,
quorumNumbers: singleQuorumNumber,
shouldForceDeregister: true
});
_kickOperator(operatorKickParams[i].operator, singleQuorumNumber);
}
}
}
Expand Down Expand Up @@ -607,7 +612,7 @@ contract SlashingRegistryCoordinator is
uint32 operatorSetId = uint32(uint8(quorumNumbers[i]));
if (
allocationManager.isMemberOfOperatorSet(
operator, OperatorSet({avs: accountIdentifier, id: operatorSetId})
operator, OperatorSet({avs: avs, id: operatorSetId})
)
) {
operatorSetIds[numDeregister] = operatorSetId;
Expand All @@ -623,21 +628,23 @@ contract SlashingRegistryCoordinator is
allocationManager.deregisterFromOperatorSets(
IAllocationManagerTypes.DeregisterParams({
operator: operator,
avs: accountIdentifier,
avs: avs,
operatorSetIds: _getOperatorSetIds(quorumNumbers)
})
);
}

/**
* @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.
* @dev Helper function to update operator stakes and deregister operators with insufficient stake
* This function handles two cases:
* 1. Operators who no longer meet the minimum stake requirement for a quorum
* 2. Operators who have been force-deregistered from the AllocationManager but not from this contract
* (e.g. due to out of gas errors in the deregistration callback)
* @param operators The list of operators to check and update
* @param operatorIds The corresponding operator IDs
* @param quorumNumber The quorum number to check stakes for
*/
function _updateStakesAndDeregisterLoiterers(
function _updateOperatorsStakes(
address[] memory operators,
bytes32[] memory operatorIds,
uint8 quorumNumber
Expand All @@ -647,22 +654,9 @@ contract SlashingRegistryCoordinator is
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
});
if (doesNotMeetStakeThreshold[j]) {
_kickOperator(operators[j], singleQuorumNumber);
}
}
}
Expand Down Expand Up @@ -859,7 +853,7 @@ contract SlashingRegistryCoordinator is
operatorSetId: quorumNumber,
strategies: strategies
});
allocationManager.createOperatorSets({avs: accountIdentifier, params: createSetParams});
allocationManager.createOperatorSets({avs: avs, params: createSetParams});

// Initialize stake registry based on stake type
if (stakeType == IStakeRegistryTypes.StakeType.TOTAL_DELEGATED) {
Expand Down Expand Up @@ -951,10 +945,10 @@ contract SlashingRegistryCoordinator is
ejector = newEjector;
}

function _setAccountIdentifier(
address _accountIdentifier
function _setAvs(
address _avs
) internal {
accountIdentifier = _accountIdentifier;
avs = _avs;
}

/// @dev Hook to allow for any pre-create quorum logic
Expand Down Expand Up @@ -1147,4 +1141,10 @@ contract SlashingRegistryCoordinator is
) public view returns (bytes32) {
return _hashTypedDataV4(keccak256(abi.encode(PUBKEY_REGISTRATION_TYPEHASH, operator)));
}

function supportsAVS(
address _avs
) public view virtual returns (bool) {
return _avs == address(avs);
}
}
4 changes: 2 additions & 2 deletions src/SlashingRegistryCoordinatorStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,10 @@ abstract contract SlashingRegistryCoordinatorStorage is ISlashingRegistryCoordin
/// @notice the delay in seconds before an operator can reregister after being ejected
uint256 public ejectionCooldown;

/// @notice The account identifier for this AVS (used for UAM integration in EigenLayer)
/// @notice The avs address for this AVS (used for UAM integration in EigenLayer)
/// @dev NOTE: Updating this value will break existing OperatorSets and UAM integration.
/// This value should only be set once.
address public accountIdentifier;
address public avs;

constructor(
IStakeRegistry _stakeRegistry,
Expand Down
6 changes: 3 additions & 3 deletions src/StakeRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ contract StakeRegistry is StakeRegistryStorage {

uint256 numStratsToAdd = _strategyParams.length;

address avs = registryCoordinator.accountIdentifier();
address avs = registryCoordinator.avs();
if (allocationManager.isOperatorSet(OperatorSet(avs, quorumNumber))) {
IStrategy[] memory strategiesToAdd = new IStrategy[](numStratsToAdd);
for (uint256 i = 0; i < numStratsToAdd; i++) {
Expand Down Expand Up @@ -283,7 +283,7 @@ contract StakeRegistry is StakeRegistryStorage {
_strategiesPerQuorum.pop();
}

address avs = registryCoordinator.accountIdentifier();
address avs = registryCoordinator.avs();
if (allocationManager.isOperatorSet(OperatorSet(avs, quorumNumber))) {
allocationManager.removeStrategiesFromOperatorSet({
avs: avs,
Expand Down Expand Up @@ -515,7 +515,7 @@ contract StakeRegistry is StakeRegistryStorage {
uint32 beforeBlock = uint32(block.number + slashableStakeLookAheadPerQuorum[quorumNumber]);

uint256[][] memory slashableShares = allocationManager.getMinimumSlashableStake(
OperatorSet(registryCoordinator.accountIdentifier(), quorumNumber),
OperatorSet(registryCoordinator.avs(), quorumNumber),
operators,
strategiesPerQuorum[quorumNumber],
beforeBlock
Expand Down
Loading
Loading