Skip to content

feat: Add operator set support for ECDSA stake registry #411

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

Open
wants to merge 16 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
87 changes: 85 additions & 2 deletions src/interfaces/IECDSAStakeRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@ interface IECDSAStakeRegistryErrors {
error OperatorAlreadyRegistered();
/// @notice Thrown when de-registering or updating the stake for an unregisted operator.
error OperatorNotRegistered();
/// @notice Thrown when the sender is not the AVS Registrar.
error InvalidSender();
/// @notice Thrown when the M2 quorum registration is disabled.
error M2QuorumRegistrationIsDisabled();
/// @notice Thrown when the operator set ids are invalid.
error InvalidOperatorSetIdsLength();
/// @notice Thrown when the AllocationManager is not correctly configured.
error InvalidAllocationManager();
/// @notice Thrown when the AllocationManager is already initialized.
error AllocationManagerAlreadyInitialized();
}

interface IECDSAStakeRegistryTypes {
Expand All @@ -48,6 +58,13 @@ interface IECDSAStakeRegistryTypes {
uint96 multiplier;
}

/// @notice Struct to represent operator set strategy multiplier
struct OperatorSetStrategyMultiplier {
uint32 operatorSetId;
address strategy;
uint256 multiplier;
}

/// @notice Configuration for a quorum's strategies.
/// @param strategies An array of strategy parameters defining the quorum.
struct Quorum {
Expand Down Expand Up @@ -124,6 +141,15 @@ interface IECDSAStakeRegistryEvents is IECDSAStakeRegistryTypes {
address indexed newSigningKey,
address oldSigningKey
);

/*
* @notice Emitted when the M2 quorum registration is disabled.
*/
event M2QuorumRegistrationDisabled();


/// @notice Event emitted when operator set strategy multipliers are updated
event OperatorSetStrategyMultipliersUpdated(OperatorSetStrategyMultiplier[] params);
}

interface IECDSAStakeRegistry is
Expand All @@ -138,15 +164,30 @@ interface IECDSAStakeRegistry is
* @param operatorSignature Contains the operator's signature, salt, and expiry.
* @param signingKey The signing key to add to the operator's history.
*/
function registerOperatorWithSignature(
function registerOperatorM2Quorum(
ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature,
address signingKey
) external;

/*
* @notice Deregisters an existing operator.
*/
function deregisterOperator() external;
function deregisterOperatorM2Quorum() external;

/*
* @notice called by the AVS Registrar when an operator is registered on the allocation manager.
* @param operator The address of the operator.
* @param signingKey The signing key of the operator.
*/
function onOperatorSetRegistered(address operator, address signingKey) external;

/*
* @notice called by the AVS Registrar when an operator is deregistered on the allocation manager.
* @param operator The address of the operator.
*/
function onOperatorSetDeregistered(
address operator
) external;

/*
* @notice Updates the signing key for an operator.
Expand Down Expand Up @@ -199,6 +240,12 @@ interface IECDSAStakeRegistry is
*/
function quorum() external view returns (IECDSAStakeRegistryTypes.Quorum memory);

/*
* @notice Retrieves the current operator set id
* @return The current operator set id
*/
function getCurrentOperatorSetIds() external view returns (uint32[] memory);

/*
* @notice Retrieves the latest signing key for a given operator.
* @param operator The address of the operator.
Expand Down Expand Up @@ -275,6 +322,42 @@ interface IECDSAStakeRegistry is
address operator
) external view returns (uint256);

/*
* @notice Retrieves the operator's weight in the m2 quorum.
* @param operator The address of the operator.
* @return The operator's weight in the m2 quorum.
*/
function getQuorumWeight(
address operator
) external view returns (uint256);

/*
* @notice Retrieves the operator's weight in the current operator set.
* @param operator The address of the operator.
* @return The operator's weight in the current operator set.
*/
function getOperatorSetWeight(
address operator
) external view returns (uint256);

/*
* @notice Checks if an operator is registered on the AVS Directory(M2).
* @param operator The address of the operator to check.
* @return bool True if the operator is registered on the AVS Directory, false otherwise.
*/
function operatorRegisteredOnAVSDirectory(
address operator
) external view returns (bool);

/*
* @notice Checks if an operator is registered on the current operator set.
* @param operator The address of the operator to check.
* @return bool True if the operator is registered on the current operator set, false otherwise.
*/
function operatorRegisteredOnCurrentOperatorSets(
address operator
) external view returns (bool);

/*
* @notice Updates operators for a specific quorum.
* @param operatorsPerQuorum Array of operator addresses per quorum.
Expand Down
58 changes: 58 additions & 0 deletions src/unaudited/ECDSAAVSRegistrar.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

import {IAVSRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IAVSRegistrar.sol";
import {IAllocationManager} from
"eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IECDSAStakeRegistry} from "../interfaces/IECDSAStakeRegistry.sol";
import {IServiceManager} from "../interfaces/IServiceManager.sol";

contract AVSRegistrar is IAVSRegistrar, Ownable {
IAllocationManager public immutable allocationManager;
IECDSAStakeRegistry public immutable stakeRegistry;
address public immutable avs;

error AVSRegistrar__OnlyAllocationManager();

modifier onlyAllocationManager() {
if (msg.sender != address(allocationManager)) {
revert AVSRegistrar__OnlyAllocationManager();
}
_;
}

constructor(address _allocationManager, address _stakeRegistry, address _avs) Ownable() {
allocationManager = IAllocationManager(_allocationManager);
stakeRegistry = IECDSAStakeRegistry(_stakeRegistry);
avs = _avs;
}

function registerOperator(
address operator,
address avs,
uint32[] calldata operatorSetIds,
bytes calldata data
) external onlyAllocationManager {
// Decode signing key
(address signingKey) = abi.decode(data, (address));

// Call stake registry to register operator
// Weight check will be done in ECDSAStakeRegistry
stakeRegistry.onOperatorSetRegistered(operator, signingKey);
}

function deregisterOperator(
address operator,
address avs,
uint32[] calldata operatorSetIds
) external onlyAllocationManager {
stakeRegistry.onOperatorSetDeregistered(operator);
}

function supportsAVS(
address avsAddr
) external view returns (bool) {
return avs == avsAddr;
}
}
132 changes: 129 additions & 3 deletions src/unaudited/ECDSAServiceManagerBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,13 @@ import {IRewardsCoordinator} from
import {IECDSAStakeRegistryTypes} from "../interfaces/IECDSAStakeRegistry.sol";
import {ECDSAStakeRegistry} from "../unaudited/ECDSAStakeRegistry.sol";
import {IAVSRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IAVSRegistrar.sol";
import {IAllocationManager} from
"eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
import {IPermissionController} from
"eigenlayer-contracts/src/contracts/interfaces/IPermissionController.sol";
import {
IAllocationManager,
IAllocationManagerTypes
} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
import {OperatorSet} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol";

abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable {
using SafeERC20 for IERC20;
Expand All @@ -38,6 +43,9 @@ abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable
/// @notice Address of the delegation manager contract, which manages staker delegations to operators.
address internal immutable delegationManager;

/// @notice Address of the permission controller contract, which manages permissions for the service manager.
address internal immutable permissionController;

/// @notice Address of the rewards initiator, which is allowed to create AVS rewards submissions.
address public rewardsInitiator;

Expand Down Expand Up @@ -74,13 +82,15 @@ abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable
address _stakeRegistry,
address _rewardsCoordinator,
address _delegationManager,
address _allocationManager
address _allocationManager,
address _permissionController
) {
avsDirectory = _avsDirectory;
stakeRegistry = _stakeRegistry;
rewardsCoordinator = _rewardsCoordinator;
delegationManager = _delegationManager;
allocationManager = _allocationManager;
permissionController = _permissionController;
_disableInitializers();
}

Expand Down Expand Up @@ -139,6 +149,81 @@ abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable
_deregisterOperatorFromAVS(operator);
}

/// @notice Deregisters an operator from a set of operator sets.
/// @dev This function is used to deregister an operator from a set of operator sets.
/// @param operator The address of the operator to deregister.
/// @param operatorSetIds The set of operator set ids to deregister from.
function deregisterOperatorFromOperatorSets(
address operator,
uint32[] memory operatorSetIds
) external virtual onlyStakeRegistry {
IAllocationManager.DeregisterParams memory params = IAllocationManagerTypes.DeregisterParams({
operator: operator,
avs: address(this),
operatorSetIds: operatorSetIds
});
IAllocationManager(allocationManager).deregisterFromOperatorSets(params);
}

/// @inheritdoc IServiceManager

function addPendingAdmin(
address admin
) external virtual onlyOwner {
IPermissionController(permissionController).addPendingAdmin({
account: address(this),
admin: admin
});
}

/// @inheritdoc IServiceManager
function removePendingAdmin(
address pendingAdmin
) external virtual onlyOwner {
IPermissionController(permissionController).removePendingAdmin({
account: address(this),
admin: pendingAdmin
});
}

/// @inheritdoc IServiceManager
function removeAdmin(
address admin
) external virtual onlyOwner {
IPermissionController(permissionController).removeAdmin({
account: address(this),
admin: admin
});
}

/// @inheritdoc IServiceManager
function setAppointee(
address appointee,
address target,
bytes4 selector
) external virtual onlyOwner {
IPermissionController(permissionController).setAppointee({
account: address(this),
appointee: appointee,
target: target,
selector: selector
});
}

/// @inheritdoc IServiceManager
function removeAppointee(
address appointee,
address target,
bytes4 selector
) external virtual onlyOwner {
IPermissionController(permissionController).removeAppointee({
account: address(this),
appointee: appointee,
target: target,
selector: selector
});
}

/// @inheritdoc IServiceManagerUI
function getRestakeableStrategies() external view virtual returns (address[] memory) {
return _getRestakeableStrategies();
Expand All @@ -151,6 +236,18 @@ abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable
return _getOperatorRestakedStrategies(_operator);
}

/**
* @notice Returns the strategies from a specific operator set for this AVS
* @dev Uses AllocationManager to fetch strategies from a single operator set
* @param operatorSetId The ID of the operator set to query
* @return Array of strategy addresses from the specified operator set
*/
function getOperatorSetStrategies(
uint32 operatorSetId
) external view virtual returns (address[] memory) {
return _getOperatorSetStrategies(operatorSetId);
}

/**
* @notice Forwards the call to update AVS metadata URI in the AVSDirectory contract.
* @dev This internal function is a proxy to the `updateAVSMetadataURI` function of the AVSDirectory contract.
Expand Down Expand Up @@ -263,6 +360,35 @@ abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable
return strategies;
}

/**
* @notice Retrieves the addresses of strategies from a specific operator set
* @dev Returns an empty array if the operator set is invalid
* @param operatorSetId The ID of the operator set to get strategies from
* @return Array of strategy addresses from the specified operator set
*/
function _getOperatorSetStrategies(
uint32 operatorSetId
) internal view virtual returns (address[] memory) {
OperatorSet memory operatorSet = OperatorSet(address(this), operatorSetId);

// Return empty array if this is not a valid operator set
if (!IAllocationManager(allocationManager).isOperatorSet(operatorSet)) {
return new address[](0);
}

// Get strategies for this operator set
IStrategy[] memory strategies =
IAllocationManager(allocationManager).getStrategiesInOperatorSet(operatorSet);

// Convert IStrategy array to address array
address[] memory strategyAddresses = new address[](strategies.length);
for (uint256 i = 0; i < strategies.length; i++) {
strategyAddresses[i] = address(strategies[i]);
}

return strategyAddresses;
}

/**
* @notice Sets the AVS registrar address in the AllocationManager
* @param registrar The new AVS registrar address
Expand Down
Loading