Skip to content

Commit

Permalink
Feat: Add avs registration & operator/strategy accounting
Browse files Browse the repository at this point in the history
  • Loading branch information
ypatil12 committed Nov 27, 2023
1 parent 50a67a1 commit b70b2be
Show file tree
Hide file tree
Showing 7 changed files with 555 additions and 9 deletions.
119 changes: 113 additions & 6 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
) EIP712("AVSRegistryCoordinator", "v0.0.1") {
slasher = _slasher;
serviceManager = _serviceManager;
stakeRegistry = _stakeRegistry;
blsPubkeyRegistry = _blsPubkeyRegistry;
indexRegistry = _indexRegistry;
delegationManager = _delegationManager;
}

function initialize(
Expand Down Expand Up @@ -148,11 +153,40 @@ contract BLSRegistryCoordinatorWithIndices is EIP712, Initializable, IBLSRegistr
/**
* @notice Registers msg.sender as an operator for one or more quorums. If any quorum reaches its maximum
* operator capacity, this method will fail.
* @param quorumNumbers is an ordered byte array containing the quorum numbers being registered for
* @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
* @dev `registrationData` should be a G1 point representing the operator's BLS public key and their socket
*/
function registerOperator(
bytes calldata quorumNumbers,
string calldata socket
bytes calldata registrationData,
SignatureWithSaltAndExpiry memory signatureWithSaltAndExpiry
) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) {
// get the operator's BLS public key
(BN254.G1Point memory pubkey, string memory socket) = abi.decode(registrationData, (BN254.G1Point, string));
// call internal function to register the operator
_registerOperatorWithCoordinatorAndNoOverfilledQuorums({
operator: msg.sender,
quorumNumbers: quorumNumbers,
pubkey: pubkey,
socket: socket,
signatureWithSaltAndExpiry: signatureWithSaltAndExpiry
});
}

/**
* @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 pubkey is the BLS public key of the operator
* @param socket is the socket of the operator
* @param signatureWithSaltAndExpiry is the signature of the operator used by the avs to register the operator in the delegation manager
*/
function registerOperatorWithCoordinator(
bytes calldata quorumNumbers,
BN254.G1Point memory pubkey,
string calldata socket,
SignatureWithSaltAndExpiry memory signatureWithSaltAndExpiry
) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) {
bytes32 operatorId = blsPubkeyRegistry.getOperatorId(msg.sender);

Expand All @@ -161,9 +195,52 @@ contract BLSRegistryCoordinatorWithIndices is EIP712, Initializable, IBLSRegistr
operator: msg.sender,
operatorId: operatorId,
quorumNumbers: quorumNumbers,
socket: socket
socket: socket,
signatureWithSaltAndExpiry: signatureWithSaltAndExpiry
});
}

/**
* @notice Registers msg.sender as an operator with the middleware when the quorum operator limit is full. To register
* while maintaining the limit, the operator chooses another registered operator with lower stake to kick.
* @param quorumNumbers are the bytes representing the quorum numbers that the operator is registering for
* @param pubkey is the BLS public key of the operator
* @param operatorKickParams are the parameters for the deregistration of the operator that is being kicked from each
* quorum that will be filled after the operator registers. These parameters should include an operator, their pubkey,
* and ids of the operators to swap with the kicked operator.
* @param operatorSignature is the signature of the operator used by the avs to register the operator in the delegation manager
* @param churnApproverSignature is the signature of the churnApprover on the operator kick params with a salt and expiry
*/
function registerOperatorWithCoordinator(
bytes calldata quorumNumbers,
BN254.G1Point memory pubkey,
string calldata socket,
OperatorKickParam[] calldata operatorKickParams,
SignatureWithSaltAndExpiry memory operatorSignature,
SignatureWithSaltAndExpiry memory churnApproverSignature
) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) {
// register the operator
uint32[] memory numOperatorsPerQuorum = _registerOperatorWithCoordinator({
operator: msg.sender,
quorumNumbers: quorumNumbers,
pubkey: pubkey,
socket: socket,
signatureWithSaltAndExpiry: operatorSignature
});

// get the registering operator's operatorId and set the operatorIdsToSwap to it because the registering operator is the one with the greatest index
bytes32[] memory operatorIdsToSwap = new bytes32[](1);
operatorIdsToSwap[0] = pubkey.hashG1Point();

// verify the churnApprover's signature
_verifyChurnApproverSignatureOnOperatorChurnApproval({
registeringOperatorId: operatorIdsToSwap[0],
operatorKickParams: operatorKickParams,
signatureWithSaltAndExpiry: churnApproverSignature
});

uint256 operatorToKickParamsIndex = 0;
// kick the operators
for (uint256 i = 0; i < quorumNumbers.length; i++) {
uint8 quorumNumber = uint8(quorumNumbers[i]);

Expand Down Expand Up @@ -363,6 +440,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 +467,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
) 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 +499,8 @@ contract BLSRegistryCoordinatorWithIndices is EIP712, Initializable, IBLSRegistr
status: OperatorStatus.REGISTERED
});

delegationManager.registerOperatorWithAVS(operator, signatureWithSaltAndExpiry);

emit OperatorRegistered(operator, operatorId);
}

Expand All @@ -431,6 +520,22 @@ contract BLSRegistryCoordinatorWithIndices is EIP712, Initializable, IBLSRegistr
});
}

function _registerOperatorWithCoordinatorAndNoOverfilledQuorums(
address operator,
bytes calldata quorumNumbers,
BN254.G1Point memory pubkey,
string memory socket,
SignatureWithSaltAndExpiry memory signatureWithSaltAndExpiry
) internal {
uint32[] memory numOperatorsPerQuorum = _registerOperatorWithCoordinator({
operator: operator,
quorumNumbers: quorumNumbers,
pubkey: pubkey,
socket: socket,
signatureWithSaltAndExpiry: signatureWithSaltAndExpiry
});
}

function _validateChurn(
uint8 quorumNumber,
uint96 totalQuorumStake,
Expand Down Expand Up @@ -493,6 +598,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
103 changes: 103 additions & 0 deletions src/StakeRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -704,4 +704,107 @@ contract StakeRegistry is StakeRegistryStorage {
}
return indices;
}

/**
* @notice Returns the most recent stake weight for the `operatorId` for quorum `quorumNumber`
* @dev Function returns weight of **0** in the event that the operator has no stake history
*/
function getCurrentOperatorStakeForQuorum(bytes32 operatorId, uint8 quorumNumber) public view returns (uint96) {
OperatorStakeUpdate memory operatorStakeUpdate = getMostRecentStakeUpdateByOperatorId(operatorId, quorumNumber);
return operatorStakeUpdate.stake;
}

/// @notice Returns the stake of the operator for the provided `quorumNumber` at the given `blockNumber`
function getStakeForOperatorIdForQuorumAtBlockNumber(
bytes32 operatorId,
uint8 quorumNumber,
uint32 blockNumber
) external view returns (uint96) {
return
operatorIdToStakeHistory[operatorId][quorumNumber][
_getStakeUpdateIndexForOperatorIdForQuorumAtBlockNumber(operatorId, quorumNumber, blockNumber)
].stake;
}

/**
* @notice Returns the stake weight from the latest entry in `_totalStakeHistory` for quorum `quorumNumber`.
* @dev Will revert if `_totalStakeHistory[quorumNumber]` is empty.
*/
function getCurrentTotalStakeForQuorum(uint8 quorumNumber) external view returns (uint96) {
return _totalStakeHistory[quorumNumber][_totalStakeHistory[quorumNumber].length - 1].stake;
}

function getLengthOfOperatorIdStakeHistoryForQuorum(
bytes32 operatorId,
uint8 quorumNumber
) external view returns (uint256) {
return operatorIdToStakeHistory[operatorId][quorumNumber].length;
}

function getLengthOfTotalStakeHistoryForQuorum(uint8 quorumNumber) external view returns (uint256) {
return _totalStakeHistory[quorumNumber].length;
}

/**
* @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
*/
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++){
StrategyAndWeightingMultiplier[] memory strategiesAndMultipliers = strategiesConsideredAndMultipliers[uint8(quorumNumbers[i])];
for(uint256 j = 0; j < strategiesAndMultipliers.length; j++){
strategies[index] = strategiesAndMultipliers[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,
* the strategy is included in the returned array but the share amount is 0
*/
function getOperatorRestakedStrategies(address operator) external view returns (IStrategy[] memory, uint96[] memory){
bytes32 operatorId = registryCoordinator.getOperatorId(operator);
uint192 operatorBitmap = registryCoordinator.getCurrentQuorumBitmapByOperatorId(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 uint96[](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));
uint96[] memory shares = new uint96[](strategies.length);

uint256 index = 0;
for(uint256 i = 0; i < restakedQuorums.length; i++) {
StrategyAndWeightingMultiplier[] memory strategiesAndMultipliers = strategiesConsideredAndMultipliers[uint8(restakedQuorums[i])];
for (uint256 j = 0; j < strategiesAndMultipliers.length; j++) {
strategies[index] = strategiesAndMultipliers[j].strategy;
shares[index] = getCurrentOperatorStakeForQuorum(operatorId, uint8(restakedQuorums[i]));
index++;
}
}

return (strategies, shares);
}

function _getNumStrategiesInBitmap(bytes memory quorums) internal view returns (uint256) {
uint256 strategyCount;
for(uint256 i = 0; i < quorums.length; i++) {
strategyCount += strategiesConsideredAndMultipliersLength(uint8(quorums[i]));
}
return strategyCount;
}
}
Loading

0 comments on commit b70b2be

Please sign in to comment.