Skip to content

Commit

Permalink
feat: concentrate eigenlayer avs interactions into service manager (#109
Browse files Browse the repository at this point in the history
)

* feat: reintroduce IServiceManager & ServiceManagerBase

These are useful concepts to form the single interaction point
where AVSs push data to EigenLayer.
The previous version focused on (unused) Slashing interactions,
whereas this version focused on the newer registration interactions.
TODO: modify the RegistryCoordinator to call the ServiceManagerBase
instead of calling the DelegationManager directly.

* feat: integrate RegistryCoordinator with ServiceManager(Base)

achieves the goal of a single interaction point with EigenLayer (per AVS).
all calls which push data to EigenLayer core are now forwarded via the ServiceManager(Base)

* fix: use correct addresses in test setup + functions

simple follow-up commit to fix breaking tests from last commit

* chore: restore optimizer_runs to 200

other changes in this PR remove enough from the size
of the RegistryCoordinator contract to make this possible.
200 runs is a wider industry default +
this should slightly decrease gas costs for users.
we can switch back to 100 in the future if we ever need to

* chore: add `setMetadataURI` function to IServiceManager interface

* feat: add operator strategy indexing to service manager (#110)

---------

Co-authored-by: Yash Patil <40046473+ypatil12@users.noreply.github.com>
  • Loading branch information
2 people authored and steven committed Dec 18, 2023
1 parent 23185d8 commit c570c42
Show file tree
Hide file tree
Showing 10 changed files with 230 additions and 52 deletions.
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ fs_permissions = [{ access = "read-write", path = "./" }]
ffi = true

# The number of optimizer runs
optimizer_runs = 100
optimizer_runs = 200

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
32 changes: 9 additions & 23 deletions src/RegistryCoordinator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@ import {EIP712} from "@openzeppelin/contracts/utils/cryptography/draft-EIP712.so
import {EIP1271SignatureUtils} from "eigenlayer-contracts/src/contracts/libraries/EIP1271SignatureUtils.sol";
import {IPauserRegistry} from "eigenlayer-contracts/src/contracts/interfaces/IPauserRegistry.sol";
import {Pausable} from "eigenlayer-contracts/src/contracts/permissions/Pausable.sol";
import {ISlasher} from "eigenlayer-contracts/src/contracts/interfaces/ISlasher.sol";
import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol";

import {IRegistryCoordinator} from "src/interfaces/IRegistryCoordinator.sol";
import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol";
import {IBLSApkRegistry} from "src/interfaces/IBLSApkRegistry.sol";
import {ISocketUpdater} from "src/interfaces/ISocketUpdater.sol";
import {IStakeRegistry} from "src/interfaces/IStakeRegistry.sol";
import {IIndexRegistry} from "src/interfaces/IIndexRegistry.sol";
import {IServiceManager} from "src/interfaces/IServiceManager.sol";

import {BitmapUtils} from "src/libraries/BitmapUtils.sol";
import {BN254} from "src/libraries/BN254.sol";
Expand Down Expand Up @@ -59,16 +58,14 @@ contract RegistryCoordinator is
/// @notice The maximum number of quorums this contract supports
uint8 internal constant MAX_QUORUM_COUNT = 192;

/// @notice the EigenLayer Slasher
ISlasher public immutable slasher;
/// @notice the ServiceManager for this AVS, which forwards calls onto EigenLayer's core contracts
IServiceManager public immutable serviceManager;
/// @notice the BLS Aggregate Pubkey Registry contract that will keep track of operators' aggregate BLS public keys per quorum
IBLSApkRegistry public immutable blsApkRegistry;
/// @notice the Stake Registry contract that will keep track of operators' stakes
IStakeRegistry public immutable stakeRegistry;
/// @notice the Index Registry contract that will keep track of operators' indexes
IIndexRegistry public immutable indexRegistry;
/// @notice The Delegation Manager contract to record operator avs relationships
IDelegationManager public immutable delegationManager;

/// @notice the current number of quorums supported by the registry coordinator
uint8 public quorumCount;
Expand Down Expand Up @@ -105,14 +102,12 @@ contract RegistryCoordinator is
}

constructor(
IDelegationManager _delegationManager,
ISlasher _slasher,
IServiceManager _serviceManager,
IStakeRegistry _stakeRegistry,
IBLSApkRegistry _blsApkRegistry,
IIndexRegistry _indexRegistry
) EIP712("AVSRegistryCoordinator", "v0.0.1") {
delegationManager = _delegationManager;
slasher = _slasher;
serviceManager = _serviceManager;
stakeRegistry = _stakeRegistry;
blsApkRegistry = _blsApkRegistry;
indexRegistry = _indexRegistry;
Expand Down Expand Up @@ -429,15 +424,6 @@ contract RegistryCoordinator is
_setEjector(_ejector);
}

/**
* @notice Sets the metadata URI for the AVS
* @param _metadataURI is the metadata URI for the AVS
* @dev only callable by the owner
*/
function setMetadataURI(string memory _metadataURI) external onlyOwner {
delegationManager.updateAVSMetadataURI(_metadataURI);
}

/*******************************************************************************
INTERNAL FUNCTIONS
*******************************************************************************/
Expand Down Expand Up @@ -489,8 +475,8 @@ contract RegistryCoordinator is
status: OperatorStatus.REGISTERED
});

// Register the operator with the delegation manager
delegationManager.registerOperatorToAVS(operator, operatorSignature);
// Register the operator with the EigenLayer via this AVS's ServiceManager
serviceManager.registerOperatorToAVS(operator, operatorSignature);

emit OperatorRegistered(operator, operatorId);
}
Expand Down Expand Up @@ -577,10 +563,10 @@ contract RegistryCoordinator is
newBitmap: newBitmap
});

// If the operator is no longer registered for any quorums, update their status and deregister from delegationManager
// If the operator is no longer registered for any quorums, update their status and deregister from EigenLayer via this AVS's ServiceManager
if (newBitmap.isEmpty()) {
operatorInfo.status = OperatorStatus.DEREGISTERED;
delegationManager.deregisterOperatorFromAVS(operator);
serviceManager.deregisterOperatorFromAVS(operator);
emit OperatorDeregistered(operator, operatorId);
}

Expand Down
115 changes: 115 additions & 0 deletions src/ServiceManagerBase.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.12;

import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";

import {BitmapUtils} from "src/libraries/BitmapUtils.sol";
import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol";
import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol";

import {IServiceManager} from "src/interfaces/IServiceManager.sol";
import {IRegistryCoordinator} from "src/interfaces/IRegistryCoordinator.sol";
import {IStakeRegistry} from "src/interfaces/IStakeRegistry.sol";

/**
* @title Minimal implementation of a ServiceManager-type contract.
* This contract can inherited from or simply used as a point-of-reference.
* @author Layr Labs, Inc.
*/
contract ServiceManagerBase is IServiceManager, OwnableUpgradeable {
using BitmapUtils for *;

IRegistryCoordinator immutable registryCoordinator;
IDelegationManager immutable delegationManager;
IStakeRegistry immutable stakeRegistry;

/// @notice when applied to a function, only allows the RegistryCoordinator to call it
modifier onlyRegistryCoordinator() {
require(
msg.sender == address(registryCoordinator),
"ServiceManagerBase.onlyRegistryCoordinator: caller is not the registry coordinator"
);
_;
}

/// @notice Sets the (immutable) `registryCoordinator` address
constructor(
IDelegationManager _delegationManager,
IRegistryCoordinator _registryCoordinator,
IStakeRegistry _stakeRegistry
) {
delegationManager = _delegationManager;
registryCoordinator = _registryCoordinator;
stakeRegistry = _stakeRegistry;
_disableInitializers();
}

function initialize(address initialOwner) external initializer {
_transferOwnership(initialOwner);
}

/**
* @notice Sets the metadata URI for the AVS
* @param _metadataURI is the metadata URI for the AVS
* @dev only callable by the owner
*/
function setMetadataURI(string memory _metadataURI) public virtual onlyOwner {
delegationManager.updateAVSMetadataURI(_metadataURI);
}

/**
* @notice Forwards a call to EigenLayer's DelegationManager contract to confirm operator registration with the AVS
* @param operator The address of the operator to register.
* @param operatorSignature The signature, salt, and expiry of the operator's signature.
*/
function registerOperatorToAVS(
address operator,
ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature
) public virtual onlyRegistryCoordinator {
delegationManager.registerOperatorToAVS(operator, operatorSignature);
}

/**
* @notice Forwards a call to EigenLayer's DelegationManager contract to confirm operator deregistration from the AVS
* @param operator The address of the operator to deregister.
*/
function deregisterOperatorFromAVS(address operator) public virtual onlyRegistryCoordinator {
delegationManager.deregisterOperatorFromAVS(operator);
}

/**
* @notice Returns the list of strategies that the operator has potentially restaked on the AVS
* @param operator The address of the operator to get restaked strategies for
* @dev This function is intended to be called off-chain
* @dev No guarantee is made on whether the operator has shares for a strategy in a quorum or uniqueness
* of each element in the returned array. The off-chain service should do that validation separately
*/
function getOperatorRestakedStrategies(address operator) external view returns (address[] memory) {
bytes32 operatorId = registryCoordinator.getOperatorId(operator);
uint192 operatorBitmap = registryCoordinator.getCurrentQuorumBitmap(operatorId);

if (operatorBitmap == 0 || registryCoordinator.quorumCount() == 0) {
return new address[](0);
}

// Get number of strategies for each quorum in operator bitmap
bytes memory operatorRestakedQuorums = BitmapUtils.bitmapToBytesArray(operatorBitmap);
uint256 strategyCount;
for(uint256 i = 0; i < operatorRestakedQuorums.length; i++) {
strategyCount += stakeRegistry.strategyParamsLength(uint8(operatorRestakedQuorums[i]));
}

// Get strategies for each quorum in operator bitmap
address[] memory restakedStrategies = new address[](strategyCount);
uint256 index = 0;
for(uint256 i = 0; i < operatorRestakedQuorums.length; i++) {
uint8 quorum = uint8(operatorRestakedQuorums[i]);
uint256 strategyParamsLength = stakeRegistry.strategyParamsLength(quorum);
for (uint256 j = 0; j < strategyParamsLength; j++) {
restakedStrategies[index] = address(stakeRegistry.strategyParamsByIndex(quorum, j).strategy);
index++;
}
}
return restakedStrategies;
}
}
2 changes: 1 addition & 1 deletion src/StakeRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,7 @@ contract StakeRegistry is StakeRegistryStorage {
) public view returns (StrategyParams memory)
{
return strategyParams[quorumNumber][index];
}
}

/*******************************************************************************
VIEW FUNCTIONS - Operator Stake History
Expand Down
42 changes: 42 additions & 0 deletions src/interfaces/IServiceManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.5.0;

import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol";
import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol";

/**
* @title Minimal interface for a ServiceManager-type contract that forms the single point for an AVS to push updates to EigenLayer
* @author Layr Labs, Inc.
*/
interface IServiceManager {
/**
* @notice Sets the metadata URI for the AVS
* @param _metadataURI is the metadata URI for the AVS
*/
function setMetadataURI(string memory _metadataURI) external;

/**
* @notice Forwards a call to EigenLayer's DelegationManager contract to confirm operator registration with the AVS
* @param operator The address of the operator to register.
* @param operatorSignature The signature, salt, and expiry of the operator's signature.
*/
function registerOperatorToAVS(
address operator,
ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature
) external;

/**
* @notice Forwards a call to EigenLayer's DelegationManager contract to confirm operator deregistration from the AVS
* @param operator The address of the operator to deregister.
*/
function deregisterOperatorFromAVS(address operator) external;

/**
* @notice Returns the list of strategies that the operator has potentially restaked on the AVS
* @param operator The address of the operator to get restaked strategies for
* @dev This function is intended to be called off-chain
* @dev No guarantee is made on whether the operator has shares for a strategy in a quorum or uniqueness
* of each element in the returned array. The off-chain service should do that validation separately
*/
function getOperatorRestakedStrategies(address operator) external view returns (address[] memory);
}
5 changes: 2 additions & 3 deletions test/harnesses/RegistryCoordinatorHarness.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@ import "forge-std/Test.sol";
// wrapper around the RegistryCoordinator contract that exposes the internal functions for unit testing.
contract RegistryCoordinatorHarness is RegistryCoordinator, Test {
constructor(
IDelegationManager _delegationManager,
ISlasher _slasher,
IServiceManager _serviceManager,
IStakeRegistry _stakeRegistry,
IBLSApkRegistry _blsApkRegistry,
IIndexRegistry _indexRegistry
) RegistryCoordinator(_delegationManager, _slasher, _stakeRegistry, _blsApkRegistry, _indexRegistry) {
) RegistryCoordinator(_serviceManager, _stakeRegistry, _blsApkRegistry, _indexRegistry) {
_transferOwnership(msg.sender);
}

Expand Down
41 changes: 27 additions & 14 deletions test/integration/CoreRegistration.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,21 +39,31 @@ contract Test_CoreRegistration is MockAVSDeployer {
)
);

// Deploy New RegistryCoordinator
registryCoordinatorImplementation = new RegistryCoordinatorHarness(
// Deploy New ServiceManager & RegistryCoordinator implementations
serviceManagerImplementation = new ServiceManagerBase(
delegationManager,
slasher,
registryCoordinator,
stakeRegistry
);

registryCoordinatorImplementation = new RegistryCoordinatorHarness(
serviceManager,
stakeRegistry,
blsApkRegistry,
indexRegistry
);

// Upgrade Registry Coordinator
cheats.prank(proxyAdminOwner);
// Upgrade Registry Coordinator & ServiceManager
cheats.startPrank(proxyAdminOwner);
proxyAdmin.upgrade(
TransparentUpgradeableProxy(payable(address(registryCoordinator))),
address(registryCoordinatorImplementation)
);
proxyAdmin.upgrade(
TransparentUpgradeableProxy(payable(address(serviceManager))),
address(serviceManagerImplementation)
);
cheats.stopPrank();

// Set operator address
operator = cheats.addr(operatorPrivateKey);
Expand Down Expand Up @@ -84,7 +94,7 @@ contract Test_CoreRegistration is MockAVSDeployer {
ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature = _getOperatorSignature(
operatorPrivateKey,
operator,
address(registryCoordinator),
address(serviceManager),
emptySalt,
maxExpiry
);
Expand All @@ -94,7 +104,7 @@ contract Test_CoreRegistration is MockAVSDeployer {
registryCoordinator.registerOperator(quorumNumbers, defaultSocket, pubkeyRegistrationParams, operatorSignature);

// Check operator is registered
IDelegationManager.OperatorAVSRegistrationStatus operatorStatus = delegationManager.avsOperatorStatus(address(registryCoordinator), operator);
IDelegationManager.OperatorAVSRegistrationStatus operatorStatus = delegationManager.avsOperatorStatus(address(serviceManager), operator);
assertEq(uint8(operatorStatus), uint8(IDelegationManager.OperatorAVSRegistrationStatus.REGISTERED));
}

Expand All @@ -108,7 +118,7 @@ contract Test_CoreRegistration is MockAVSDeployer {
registryCoordinator.deregisterOperator(quorumNumbers);

// Check operator is deregistered
IDelegationManager.OperatorAVSRegistrationStatus operatorStatus = delegationManager.avsOperatorStatus(address(registryCoordinator), operator);
IDelegationManager.OperatorAVSRegistrationStatus operatorStatus = delegationManager.avsOperatorStatus(address(serviceManager), operator);
assertEq(uint8(operatorStatus), uint8(IDelegationManager.OperatorAVSRegistrationStatus.UNREGISTERED));
}

Expand All @@ -124,19 +134,22 @@ contract Test_CoreRegistration is MockAVSDeployer {
registryCoordinator.deregisterOperator(quorumNumbers);

// Check operator is still registered
IDelegationManager.OperatorAVSRegistrationStatus operatorStatus = delegationManager.avsOperatorStatus(address(registryCoordinator), operator);
IDelegationManager.OperatorAVSRegistrationStatus operatorStatus = delegationManager.avsOperatorStatus(address(serviceManager), operator);
assertEq(uint8(operatorStatus), uint8(IDelegationManager.OperatorAVSRegistrationStatus.REGISTERED));
}

function test_setMetadataURI_fail_notServiceManagerOwner() public {
require(operator != serviceManager.owner(), "bad test setup");
cheats.prank(operator);
cheats.expectRevert("Ownable: caller is not the owner");
registryCoordinator.setMetadataURI("Test MetadataURI");
serviceManager.setMetadataURI("Test MetadataURI");
}

function test_setMetadataURI() public {
cheats.prank(registryCoordinatorOwner);
registryCoordinator.setMetadataURI("Test MetadataURI");
function test_setMetadataURI() public {
address toPrankFrom = serviceManager.owner();
cheats.prank(toPrankFrom);
serviceManager.setMetadataURI("Test MetadataURI");
// TODO: check effects here
}

// Utils
Expand All @@ -145,7 +158,7 @@ contract Test_CoreRegistration is MockAVSDeployer {
ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature = _getOperatorSignature(
operatorPrivateKey,
operator,
address(registryCoordinator),
address(serviceManager),
emptySalt,
maxExpiry
);
Expand Down
2 changes: 1 addition & 1 deletion test/unit/RegistryCoordinatorUnit.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ contract RegistryCoordinatorUnit is MockAVSDeployer {
assertEq(address(registryCoordinator.stakeRegistry()), address(stakeRegistry));
assertEq(address(registryCoordinator.blsApkRegistry()), address(blsApkRegistry));
assertEq(address(registryCoordinator.indexRegistry()), address(indexRegistry));
assertEq(address(registryCoordinator.slasher()), address(slasher));
assertEq(address(registryCoordinator.serviceManager()), address(serviceManager));

for (uint i = 0; i < numQuorums; i++) {
assertEq(
Expand Down
Loading

0 comments on commit c570c42

Please sign in to comment.