Skip to content
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

Operator<>AVS Mapping Support #73

Closed
wants to merge 5 commits into from
Closed
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
38 changes: 31 additions & 7 deletions src/BLSRegistryCoordinatorWithIndices.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import "eigenlayer-contracts/src/contracts/interfaces/IPauserRegistry.sol";
import "eigenlayer-contracts/src/contracts/interfaces/ISlasher.sol";
import "eigenlayer-contracts/src/contracts/libraries/EIP1271SignatureUtils.sol";
import "eigenlayer-contracts/src/contracts/permissions/Pausable.sol";
import "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol";

import "src/interfaces/IBLSRegistryCoordinatorWithIndices.sol";
import "src/interfaces/ISocketUpdater.sol";
Expand Down Expand Up @@ -60,6 +61,8 @@ contract BLSRegistryCoordinatorWithIndices is EIP712, Initializable, IBLSRegistr
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 @@ -102,13 +105,15 @@ contract BLSRegistryCoordinatorWithIndices is EIP712, Initializable, IBLSRegistr
IServiceManager _serviceManager,
IStakeRegistry _stakeRegistry,
IBLSPubkeyRegistry _blsPubkeyRegistry,
IIndexRegistry _indexRegistry
IIndexRegistry _indexRegistry,
IDelegationManager _delegationManager
bowenli86 marked this conversation as resolved.
Show resolved Hide resolved
) EIP712("AVSRegistryCoordinator", "v0.0.1") {
slasher = _slasher;
serviceManager = _serviceManager;
stakeRegistry = _stakeRegistry;
blsPubkeyRegistry = _blsPubkeyRegistry;
indexRegistry = _indexRegistry;
delegationManager = _delegationManager;
}

function initialize(
Expand Down Expand Up @@ -152,7 +157,8 @@ contract BLSRegistryCoordinatorWithIndices is EIP712, Initializable, IBLSRegistr
*/
function registerOperator(
bytes calldata quorumNumbers,
string calldata socket
string calldata socket,
SignatureWithSaltAndExpiry memory operatorSignature
) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) {
bytes32 operatorId = blsPubkeyRegistry.getOperatorId(msg.sender);

Expand All @@ -161,7 +167,8 @@ contract BLSRegistryCoordinatorWithIndices is EIP712, Initializable, IBLSRegistr
operator: msg.sender,
operatorId: operatorId,
quorumNumbers: quorumNumbers,
socket: socket
socket: socket,
signatureWithSaltAndExpiry: operatorSignature
});

for (uint256 i = 0; i < quorumNumbers.length; i++) {
Expand All @@ -187,12 +194,14 @@ contract BLSRegistryCoordinatorWithIndices is EIP712, Initializable, IBLSRegistr
* @param operatorKickParams are used to determine which operator is removed to maintain quorum capacity as the
* operator registers for quorums.
* @param churnApproverSignature is the signature of the churnApprover on the operator kick params
* @param operatorSignature is the signature of the operator used by the AVS to register the operator in the delegation manager
*/
function registerOperatorWithChurn(
bytes calldata quorumNumbers,
string calldata socket,
OperatorKickParam[] calldata operatorKickParams,
SignatureWithSaltAndExpiry memory churnApproverSignature
SignatureWithSaltAndExpiry memory churnApproverSignature,
SignatureWithSaltAndExpiry memory operatorSignature
) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) {
require(operatorKickParams.length == quorumNumbers.length, "BLSRegistryCoordinatorWithIndices.registerOperatorWithChurn: input length mismatch");

Expand All @@ -210,7 +219,8 @@ contract BLSRegistryCoordinatorWithIndices is EIP712, Initializable, IBLSRegistr
operator: msg.sender,
operatorId: operatorId,
quorumNumbers: quorumNumbers,
socket: socket
socket: socket,
signatureWithSaltAndExpiry: operatorSignature
});

uint256 kickIndex = 0;
Expand Down Expand Up @@ -363,6 +373,15 @@ contract BLSRegistryCoordinatorWithIndices is EIP712, Initializable, IBLSRegistr
_setEjector(_ejector);
}

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

/*******************************************************************************
INTERNAL FUNCTIONS
*******************************************************************************/
Expand All @@ -381,8 +400,9 @@ contract BLSRegistryCoordinatorWithIndices is EIP712, Initializable, IBLSRegistr
address operator,
bytes32 operatorId,
bytes calldata quorumNumbers,
string memory socket
) internal virtual returns (RegisterResults memory) {
string memory socket,
SignatureWithSaltAndExpiry memory signatureWithSaltAndExpiry
bowenli86 marked this conversation as resolved.
Show resolved Hide resolved
bowenli86 marked this conversation as resolved.
Show resolved Hide resolved
) internal virtual returns (RegisterResults memory) {
/**
* Get bitmap of quorums to register for and operator's current bitmap. Validate that:
* - we're trying to register for at least 1 quorum
Expand Down Expand Up @@ -412,6 +432,8 @@ contract BLSRegistryCoordinatorWithIndices is EIP712, Initializable, IBLSRegistr
status: OperatorStatus.REGISTERED
});

delegationManager.registerOperatorWithAVS(operator, signatureWithSaltAndExpiry);
bowenli86 marked this conversation as resolved.
Show resolved Hide resolved

emit OperatorRegistered(operator, operatorId);
}

Expand Down Expand Up @@ -493,6 +515,8 @@ contract BLSRegistryCoordinatorWithIndices is EIP712, Initializable, IBLSRegistr
// If the operator is no longer registered for any quorums, update their status
if (newBitmap.isEmpty()) {
operatorInfo.status = OperatorStatus.DEREGISTERED;
delegationManager.deregisterOperatorFromAVS(operator);

emit OperatorDeregistered(operator, operatorId);
}

Expand Down
79 changes: 78 additions & 1 deletion src/StakeRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import "src/StakeRegistryStorage.sol";
* @author Layr Labs, Inc.
*/
contract StakeRegistry is StakeRegistryStorage {

using BitmapUtils for *;
Copy link
Contributor

Choose a reason for hiding this comment

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

hm. why not just uint256

Copy link
Collaborator

Choose a reason for hiding this comment

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

doesn't make a difference to the bytecode 🤷


modifier onlyRegistryCoordinator() {
require(
msg.sender == address(registryCoordinator),
Expand Down Expand Up @@ -201,6 +202,8 @@ contract StakeRegistry is StakeRegistryStorage {
nextUpdateBlockNumber: 0,
stake: 0
}));

quorumBitmap = uint192(quorumBitmap.plus(uint192(1) << quorumNumber));
Comment on lines +205 to +206
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would prefer we add a helper for this, because it's easy to fuck up. I'm using this in another branch:

/**
* @notice Returns a copy of `bitmap` with `bit` set.
* @dev IMPORTANT: we're dealing with stack values here, so this doesn't modify
* the original bitmap. Using this correctly requires an assignment statement:
* `bitmap = bitmap.setBit(bit);`
*/
function setBit(uint256 bitmap, uint8 bit) internal pure returns (uint256) {
return bitmap | (1 << bit);
}

}

function setMinimumStakeForQuorum(
Expand Down Expand Up @@ -245,6 +248,11 @@ contract StakeRegistry is StakeRegistryStorage {
_strategyParams[indicesToRemove[i]] = _strategyParams[_strategyParams.length - 1];
_strategyParams.pop();
}

// If no strategies exist in quorum, turn bit off
if (_strategyParams.length == 0) {
quorumBitmap = uint192(quorumBitmap.minus(uint192(1) << quorumNumber));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would prefer a helper for this. Could use something similar to the setBit method I linked, but with a bool on/off switch?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Or just unsetBit 🤷

}
}

/**
Expand Down Expand Up @@ -704,4 +712,73 @@ contract StakeRegistry is StakeRegistryStorage {
}
return indices;
}

/*******************************************************************************
VIEW FUNCTIONS - Operator & AVS Stakes
*******************************************************************************/

/**
* @notice Returns a list of all strategies that are restakeable for the middleware
* @dev This function is used by an offchain actor to determine which strategies are restakeable
* @dev Strategies may be duplicated in the returned array if the same strategy exists in multiple quorums
*/
function getRestakeableStrategies() external view returns (IStrategy[] memory){
if(quorumBitmap == 0) {
return new IStrategy[](0);
}

bytes memory quorumNumbers = BitmapUtils.bitmapToBytesArray(quorumBitmap);
IStrategy[] memory strategies = new IStrategy[](_getNumStrategiesInBitmap(quorumNumbers));
uint256 index = 0;
for(uint256 i = 0; i < quorumNumbers.length; i++){
Copy link
Contributor

Choose a reason for hiding this comment

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

use quourmCount across the board

StrategyParams[] memory strategyParams = strategyParams[uint8(quorumNumbers[i])];
for(uint256 j = 0; j < strategyParams.length; j++){
strategies[index] = strategyParams[j].strategy;
index++;
}
}

return strategies;
}

/**
* @notice Return a list of all strategies and corresponding share amounts for which the operator has restaked
* @dev There may strategies for which the operator has restaked on the quorum but has no shares. In this case,
Copy link
Contributor

Choose a reason for hiding this comment

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

* the strategy is included in the returned array but the share amount is 0
* @dev Strategies and share amounts may be duplicated in the returned arrays if the same strategy exists in multiple quorums
*/
function getOperatorRestakedStrategies(address operator) external view returns (IStrategy[] memory, uint256[] memory){
bytes32 operatorId = registryCoordinator.getOperatorId(operator);
uint192 operatorBitmap = registryCoordinator.getCurrentQuorumBitmap(operatorId);

// Return empty arrays if operator does not have any shares at stake OR no strategies can be restaked
if (operatorBitmap == 0 || quorumBitmap == 0) {
return (new IStrategy[](0), new uint256[](0));
}

uint192 operatorRestakedQuorumsBitmap = operatorBitmap & quorumBitmap; // get all strategies that are considered restaked
bytes memory restakedQuorums = BitmapUtils.bitmapToBytesArray(operatorRestakedQuorumsBitmap);
IStrategy[] memory strategies = new IStrategy[](_getNumStrategiesInBitmap(restakedQuorums));
uint256[] memory shares = new uint256[](strategies.length);

uint256 index = 0;
for(uint256 i = 0; i < restakedQuorums.length; i++) {
StrategyParams[] memory strategyParams = strategyParams[uint8(restakedQuorums[i])];
for (uint256 j = 0; j < strategyParams.length; j++) {
strategies[index] = strategyParams[j].strategy;
shares[index] = delegation.operatorShares(operator, strategyParams[j].strategy);
index++;
}
}

return (strategies, shares);
Copy link
Contributor

Choose a reason for hiding this comment

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

i don't think it makes sense to return shares? let's just assume all stake of the strategy is staked until slashing is live

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yup, discussed with Bowen and we have a pipeline in the backend for getting operator shares so will remove

}

function _getNumStrategiesInBitmap(bytes memory quorums) internal view returns (uint256) {
uint256 strategyCount;
for(uint256 i = 0; i < quorums.length; i++) {
strategyCount += strategyParams[uint8(quorums[i])].length;
}
return strategyCount;
}
ypatil12 marked this conversation as resolved.
Show resolved Hide resolved
}
3 changes: 3 additions & 0 deletions src/StakeRegistryStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ abstract contract StakeRegistryStorage is IStakeRegistry {
*/
mapping(uint8 => StrategyParams[]) public strategyParams;

/// @notice Bitmap of quorums that have been initialized and more than 1 strategy set
uint192 quorumBitmap;

constructor(
IRegistryCoordinator _registryCoordinator,
IDelegationManager _delegationManager,
Expand Down
19 changes: 18 additions & 1 deletion src/interfaces/IRegistryCoordinator.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.12;

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

/**
* @title Interface for a contract that coordinates between various registries for an AVS.
* @author Layr Labs, Inc.
*/
interface IRegistryCoordinator {
interface IRegistryCoordinator is ISignatureUtils {
// EVENTS
/// Emits when an operator is registered
event OperatorRegistered(address indexed operator, bytes32 indexed operatorId);
Expand Down Expand Up @@ -83,4 +85,19 @@ interface IRegistryCoordinator {

/// @notice Returns the number of registries
function numRegistries() external view returns (uint256);

/**
* @notice Registers msg.sender as an operator with the middleware
* @param quorumNumbers are the bytes representing the quorum numbers that the operator is registering for
* @param registrationData is the data that is decoded to get the operator's registration information
* @param signatureWithSaltAndExpiry is the signature of the operator used by the avs to register the operator in the delegation manager
*/
function registerOperatorWithCoordinator(bytes memory quorumNumbers, bytes calldata registrationData, SignatureWithSaltAndExpiry memory signatureWithSaltAndExpiry) external;

/**
* @notice Deregisters the msg.sender as an operator from the middleware
* @param quorumNumbers are the bytes representing the quorum numbers that the operator is registered for
* @param deregistrationData is the the data that is decoded to get the operator's deregistration information
*/
function deregisterOperatorWithCoordinator(bytes calldata quorumNumbers, bytes calldata deregistrationData) external;
Comment on lines +90 to +102
Copy link
Contributor

@bowenli86 bowenli86 Dec 8, 2023

Choose a reason for hiding this comment

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

QQ: why these 2 api are not implemented? @ypatil12

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Old methods that were not removed correctly on rebase

Copy link
Collaborator

Choose a reason for hiding this comment

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

they're implemented, just renamed registerOperator and deregisterOperator

}
12 changes: 12 additions & 0 deletions src/interfaces/IStakeRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/

import {IRegistry} from "./IRegistry.sol";
import {IServiceManager} from "./IServiceManager.sol";
import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol";

/**
* @title Interface for a `Registry` that keeps track of stakes of operators for up to 256 quorums.
Expand Down Expand Up @@ -249,4 +250,15 @@ interface IStakeRegistry is IRegistry {
bytes32 operatorId,
bytes calldata quorumNumbers
) external returns (uint192);

/**
* @notice Returns a list of strategies that an operator has restaked into the AVS
* @param operator is the address of the operator whose restaked strategies are being queried
*/
function getOperatorRestakedStrategies(address operator) external view returns (IStrategy[] memory, uint256[] memory);

/**
* @notice Returns the strategies that this AVS supports restaking for
*/
function getRestakeableStrategies() external view returns (IStrategy[] memory);
}
5 changes: 3 additions & 2 deletions test/harnesses/BLSRegistryCoordinatorWithIndicesHarness.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ contract BLSRegistryCoordinatorWithIndicesHarness is BLSRegistryCoordinatorWithI
IServiceManager _serviceManager,
IStakeRegistry _stakeRegistry,
IBLSPubkeyRegistry _blsPubkeyRegistry,
IIndexRegistry _indexRegistry
) BLSRegistryCoordinatorWithIndices(_slasher, _serviceManager, _stakeRegistry, _blsPubkeyRegistry, _indexRegistry) {
IIndexRegistry _indexRegistry,
IDelegationManager _delegationManager
) BLSRegistryCoordinatorWithIndices(_slasher, _serviceManager, _stakeRegistry, _blsPubkeyRegistry, _indexRegistry, _delegationManager) {
}

function setQuorumCount(uint8 count) external {
Expand Down
Loading