Skip to content

refactor: slashing #379

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 16 commits into from
Feb 5, 2025
7 changes: 6 additions & 1 deletion src/BLSSignatureChecker.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

import {BitmapUtils} from "./libraries/BitmapUtils.sol";
import {BN254} from "./libraries/BN254.sol";

Expand All @@ -18,7 +20,10 @@ contract BLSSignatureChecker is BLSSignatureCheckerStorage {
/// MODIFIERS

modifier onlyCoordinatorOwner() {
require(msg.sender == registryCoordinator.owner(), OnlyRegistryCoordinatorOwner());
require(
msg.sender == Ownable(address(registryCoordinator)).owner(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is RC interface inheriting IOwnable?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just the owner() interface is needed here but the SlashingRegistryCoordinator does inherit OwnableUpgradeable

OnlyRegistryCoordinatorOwner()
);
_;
}

Expand Down
210 changes: 115 additions & 95 deletions src/RegistryCoordinator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {BitmapUtils} from "./libraries/BitmapUtils.sol";
import {SlashingRegistryCoordinator} from "./SlashingRegistryCoordinator.sol";
import {ISlashingRegistryCoordinator} from "./interfaces/ISlashingRegistryCoordinator.sol";
import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
import {RegistryCoordinatorStorage} from "./RegistryCoordinatorStorage.sol";

/**
* @title A `RegistryCoordinator` that has three registries:
Expand All @@ -24,12 +25,9 @@ import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/Ownabl
*
* @author Layr Labs, Inc.
*/
contract RegistryCoordinator is IRegistryCoordinator, SlashingRegistryCoordinator {
contract RegistryCoordinator is RegistryCoordinatorStorage {
using BitmapUtils for *;

/// @notice the ServiceManager for this AVS, which forwards calls onto EigenLayer's core contracts
IServiceManager public immutable serviceManager;

constructor(
IServiceManager _serviceManager,
IStakeRegistry _stakeRegistry,
Expand All @@ -39,17 +37,16 @@ contract RegistryCoordinator is IRegistryCoordinator, SlashingRegistryCoordinato
IAllocationManager _allocationManager,
IPauserRegistry _pauserRegistry
)
SlashingRegistryCoordinator(
RegistryCoordinatorStorage(
_serviceManager,
_stakeRegistry,
_blsApkRegistry,
_indexRegistry,
_socketRegistry,
_allocationManager,
_pauserRegistry
)
{
serviceManager = _serviceManager;
}
{}

/**
*
Expand All @@ -64,44 +61,28 @@ contract RegistryCoordinator is IRegistryCoordinator, SlashingRegistryCoordinato
IBLSApkRegistryTypes.PubkeyRegistrationParams memory params,
SignatureWithSaltAndExpiry memory operatorSignature
) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) {
require(!m2QuorumsDisabled, M2QuorumsAlreadyDisabled());
/**
* If the operator has NEVER registered a pubkey before, use `params` to register
* their pubkey in blsApkRegistry
*
* If the operator HAS registered a pubkey, `params` is ignored and the pubkey hash
* (operatorId) is fetched instead
*/
bytes32 operatorId = _getOrCreateOperatorId(msg.sender, params);

// Register the operator in each of the registry contracts and update the operator's
// quorum bitmap and registration status
uint32[] memory numOperatorsPerQuorum = _registerOperator({
require(!isM2QuorumRegistrationDisabled, M2QuorumRegistrationIsDisabled());
require(
quorumNumbers.orderedBytesArrayToBitmap().isSubsetOf(m2QuorumBitmap()),
OnlyM2QuorumsAllowed()
);

// Check if the operator has registered before
bool operatorRegisteredBefore =
_operatorInfo[msg.sender].status == OperatorStatus.REGISTERED;

// register the operator with the registry coordinator
_registerOperator({
operator: msg.sender,
operatorId: operatorId,
operatorId: _getOrCreateOperatorId(msg.sender, params),
quorumNumbers: quorumNumbers,
socket: socket
}).numOperatorsPerQuorum;

// For each quorum, validate that the new operator count does not exceed the maximum
// (If it does, an operator needs to be replaced -- see `registerOperatorWithChurn`)
for (uint256 i = 0; i < quorumNumbers.length; i++) {
uint8 quorumNumber = uint8(quorumNumbers[i]);

require(
numOperatorsPerQuorum[i] <= _quorumParams[quorumNumber].maxOperatorCount,
MaxQuorumsReached()
);
}

// If the operator wasn't registered for any quorums, update their status
// and register them with this AVS in EigenLayer core (DelegationManager)
if (_operatorInfo[msg.sender].status != OperatorStatus.REGISTERED) {
_operatorInfo[msg.sender] =
OperatorInfo({operatorId: operatorId, status: OperatorStatus.REGISTERED});
socket: socket,
checkMaxOperatorCount: true
});

// If the operator has never registered before, register them with the AVSDirectory
if (!operatorRegisteredBefore) {
serviceManager.registerOperatorToAVS(msg.sender, operatorSignature);
emit OperatorRegistered(msg.sender, operatorId);
}
}

Expand All @@ -114,34 +95,29 @@ contract RegistryCoordinator is IRegistryCoordinator, SlashingRegistryCoordinato
SignatureWithSaltAndExpiry memory churnApproverSignature,
SignatureWithSaltAndExpiry memory operatorSignature
) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) {
require(!m2QuorumsDisabled, M2QuorumsAlreadyDisabled());
require(!isM2QuorumRegistrationDisabled, M2QuorumRegistrationIsDisabled());
require(
quorumNumbers.orderedBytesArrayToBitmap().isSubsetOf(m2QuorumBitmap()),
OnlyM2QuorumsAllowed()
);

/**
* If the operator has NEVER registered a pubkey before, use `params` to register
* their pubkey in blsApkRegistry
*
* If the operator HAS registered a pubkey, `params` is ignored and the pubkey hash
* (operatorId) is fetched instead
*/
bytes32 operatorId = _getOrCreateOperatorId(msg.sender, params);
// Check if the operator has registered before
bool operatorRegisteredBefore =
_operatorInfo[msg.sender].status == OperatorStatus.REGISTERED;

// register the operator with the registry coordinator with churn
_registerOperatorWithChurn({
operator: msg.sender,
operatorId: operatorId,
operatorId: _getOrCreateOperatorId(msg.sender, params),
quorumNumbers: quorumNumbers,
socket: socket,
operatorKickParams: operatorKickParams,
churnApproverSignature: churnApproverSignature
});

// If the operator wasn't registered for any quorums, update their status
// and register them with this AVS in EigenLayer core (DelegationManager)
if (_operatorInfo[msg.sender].status != OperatorStatus.REGISTERED) {
_operatorInfo[msg.sender] =
OperatorInfo({operatorId: operatorId, status: OperatorStatus.REGISTERED});

// If the operator has never registered before, register them with the AVSDirectory
if (!operatorRegisteredBefore) {
serviceManager.registerOperatorToAVS(msg.sender, operatorSignature);
emit OperatorRegistered(msg.sender, operatorId);
}
}

Expand All @@ -150,56 +126,78 @@ contract RegistryCoordinator is IRegistryCoordinator, SlashingRegistryCoordinato
bytes memory quorumNumbers
) external override onlyWhenNotPaused(PAUSED_DEREGISTER_OPERATOR) {
// Check that the quorum numbers are M2 quorums
for (uint256 i = 0; i < quorumNumbers.length; i++) {
require(
!operatorSetsEnabled || _isM2Quorum(uint8(quorumNumbers[i])), OperatorSetQuorum()
);
}
require(
quorumNumbers.orderedBytesArrayToBitmap().isSubsetOf(m2QuorumBitmap()),
OnlyM2QuorumsAllowed()
);

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

/// @inheritdoc IRegistryCoordinator
function enableOperatorSets() external onlyOwner {
require(!operatorSetsEnabled, OperatorSetsAlreadyEnabled());

// Set the bitmap for M2 quorums
M2quorumBitmap = _getQuorumBitmap(quorumCount);
function disableM2QuorumRegistration() external onlyOwner {
require(!isM2QuorumRegistrationDisabled, M2QuorumRegistrationIsDisabled());

// Enable operator sets mode
operatorSetsEnabled = true;
isM2QuorumRegistrationDisabled = true;

emit OperatorSetsEnabled();
emit M2QuorumRegistrationDisabled();
}

/// @inheritdoc IRegistryCoordinator
function disableM2QuorumRegistration() external onlyOwner {
require(operatorSetsEnabled, OperatorSetsNotEnabled());
/**
*
* INTERNAL FUNCTIONS
*
*/

m2QuorumsDisabled = true;
/// @dev override the _forceDeregisterOperator function to handle M2 quorum deregistration
function _forceDeregisterOperator(
address operator,
bytes memory quorumNumbers
) internal virtual override {
// filter out M2 quorums from the quorum numbers
uint256 operatorSetBitmap =
quorumNumbers.orderedBytesArrayToBitmap().minus(m2QuorumBitmap());
if (!operatorSetBitmap.isEmpty()) {
// call the parent _forceDeregisterOperator function for operator sets quorums
super._forceDeregisterOperator(operator, operatorSetBitmap.bitmapToBytesArray());
}
}

emit M2QuorumsDisabled();
/// @dev Hook to prevent any new quorums from being created if operator sets are not enabled
function _beforeCreateQuorum(
uint8
) internal virtual override {
// If operator sets are not enabled, set the m2 quorum bitmap to the current m2 quorum bitmap
// and enable operator sets
if (!operatorSetsEnabled) {
_m2QuorumBitmap = m2QuorumBitmap();
operatorSetsEnabled = true;
}
}

/// @dev Hook to allow for any post-deregister logic
function _afterDeregisterOperator(
address operator,
bytes32 operatorId,
bytes memory quorumNumbers,
bytes32,
bytes memory,
uint192 newBitmap
) internal virtual override {
uint256 operatorM2QuorumBitmap = newBitmap.minus(M2quorumBitmap);
// Bitmap representing all quorums including M2 and OperatorSet quorums
uint256 totalQuorumBitmap = _getTotalQuorumBitmap();
// Bitmap representing only OperatorSet quorums. Equal to 0 if operatorSets not enabled
uint256 operatorSetQuorumBitmap = totalQuorumBitmap.minus(m2QuorumBitmap());
// Operators updated M2 quorum bitmap, clear all the bits of operatorSetQuorumBitmap which gives the
// operator's M2 quorum bitmap.
uint256 operatorM2QuorumBitmap = newBitmap.minus(operatorSetQuorumBitmap);
// If the operator is no longer registered for any M2 quorums, update their status and deregister
// them from the AVS via the EigenLayer core contracts
if (operatorM2QuorumBitmap.isEmpty()) {
serviceManager.deregisterOperatorFromAVS(operator);
}
}

/// @dev Returns a bitmap with all bits set up to `quorumCount`. Used for bit-masking quorum numbers
/// and differentiating between operator sets and M2 quorums
function _getQuorumBitmap(
uint256 quorumCount
) internal pure returns (uint256) {
/// @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
// For example:
// quorumCount = 3 -> 0111 (7 in decimal)
Expand All @@ -208,6 +206,38 @@ contract RegistryCoordinator is IRegistryCoordinator, SlashingRegistryCoordinato
return (1 << quorumCount) - 1;
}

/// @notice Returns true if the quorum number is an M2 quorum
/// @dev We use bitwise and to check if the quorum number is an M2 quorum
function _isM2Quorum(
uint8 quorumNumber
) internal view returns (bool) {
return m2QuorumBitmap().isSet(quorumNumber);
}

/**
*
* VIEW FUNCTIONS
*
*/

/// @dev Returns a bitmap with all bits set up to `quorumCount`. Used for bit-masking quorum numbers
/// and differentiating between operator sets and M2 quorums
function m2QuorumBitmap() public view returns (uint256) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"avsDirectoryQuorum"?

// If operator sets are enabled, return the current m2 quorum bitmap
if (operatorSetsEnabled) {
return _m2QuorumBitmap;
}

return _getTotalQuorumBitmap();
}

/// @notice Returns true if the quorum number is an M2 quorum
function isM2Quorum(
uint8 quorumNumber
) external view returns (bool) {
return _isM2Quorum(quorumNumber);
}

/**
* @notice Returns the message hash that an operator must sign to register their BLS public key.
* @param operator is the address of the operator registering their BLS public key
Expand All @@ -217,14 +247,4 @@ contract RegistryCoordinator is IRegistryCoordinator, SlashingRegistryCoordinato
) public view returns (bytes32) {
return _hashTypedDataV4(keccak256(abi.encode(PUBKEY_REGISTRATION_TYPEHASH, operator)));
}

/// @dev need to override function here since its defined in both these contracts
function owner()
public
view
override(SlashingRegistryCoordinator, ISlashingRegistryCoordinator)
returns (address)
{
return OwnableUpgradeable.owner();
}
}
Loading