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

Permissioned operator churn #118

Merged
merged 6 commits into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.12;

import "./ISignatureUtils.sol";
import "./IRegistryCoordinator.sol";
import "./IStakeRegistry.sol";
import "./IBLSPubkeyRegistry.sol";
Expand All @@ -10,7 +11,7 @@ import "./IIndexRegistry.sol";
* @title Minimal interface for the `IBLSStakeRegistryCoordinator` contract.
* @author Layr Labs, Inc.
*/
interface IBLSRegistryCoordinatorWithIndices is IRegistryCoordinator {
interface IBLSRegistryCoordinatorWithIndices is ISignatureUtils, IRegistryCoordinator {
// STRUCT

/**
Expand Down
13 changes: 3 additions & 10 deletions src/contracts/interfaces/IDelegationManager.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.5.0;

import "./ISignatureUtils.sol";
import "./IStrategy.sol";

/**
Expand All @@ -13,7 +14,7 @@ import "./IStrategy.sol";
* - enabling any staker to delegate its stake to the operator of its choice (a given staker can only delegate to a single operator at a time)
* - enabling a staker to undelegate its assets from the operator it is delegated to (performed as part of the withdrawal process, initiated through the StrategyManager)
*/
interface IDelegationManager {
interface IDelegationManager is ISignatureUtils {
// @notice Struct used for storing information about a single operator who has registered with EigenLayer
struct OperatorDetails {
// @notice address to receive the rewards that the operator earns via serving applications built on EigenLayer.
Expand Down Expand Up @@ -66,15 +67,7 @@ interface IDelegationManager {
// the expiration timestamp (UTC) of the signature
uint256 expiry;
}

// @notice Struct that bundles together a signature and an expiration time for the signature. Used primarily for stack management.
struct SignatureWithExpiry {
// the signature itself, formatted as a single bytes object
bytes signature;
// the expiration timestamp (UTC) of the signature
uint256 expiry;
}


// @notice Emitted when a new operator registers in EigenLayer and provides their OperatorDetails.
event OperatorRegistered(address indexed operator, OperatorDetails operatorDetails);

Expand Down
17 changes: 17 additions & 0 deletions src/contracts/interfaces/ISignatureUtils.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.5.0;

/**
* @title The interface for common signature utilities.
* @author Layr Labs, Inc.
* @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
*/
interface ISignatureUtils {
// @notice Struct that bundles together a signature and an expiration time for the signature. Used primarily for stack management.
struct SignatureWithExpiry {
// the signature itself, formatted as a single bytes object
bytes signature;
// the expiration timestamp (UTC) of the signature
uint256 expiry;
}
}
77 changes: 73 additions & 4 deletions src/contracts/middleware/BLSRegistryCoordinatorWithIndices.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.12;

import "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol";
import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";

import "../interfaces/IBLSRegistryCoordinatorWithIndices.sol";
Expand All @@ -11,6 +12,7 @@ import "../interfaces/IVoteWeigher.sol";
import "../interfaces/IStakeRegistry.sol";
import "../interfaces/IIndexRegistry.sol";

import "../libraries/EIP1271SignatureUtils.sol";
import "../libraries/BitmapUtils.sol";
import "../libraries/MiddlewareUtils.sol";

Expand All @@ -24,9 +26,13 @@ import "forge-std/Test.sol";
*
* @author Layr Labs, Inc.
*/
contract BLSRegistryCoordinatorWithIndices is Initializable, IBLSRegistryCoordinatorWithIndices, ISocketUpdater {
contract BLSRegistryCoordinatorWithIndices is EIP712, Initializable, IBLSRegistryCoordinatorWithIndices, ISocketUpdater, Test {
using BN254 for BN254.G1Point;

/// @notice The EIP-712 typehash for the `DelegationApproval` struct used by the contract
bytes32 public constant OPERATOR_CHURN_APPROVAL_TYPEHASH =
keccak256("OperatorChurnApproval(bytes32 registeringOperatorId, OperatorKickParam[] operatorKickParams)OperatorKickParam(address operator, BN254.G1Point pubkey, bytes32[] operatorIdsToSwap)BN254.G1Point(uint256 x, uint256 y)");

uint16 internal constant BIPS_DENOMINATOR = 10000;

/// @notice the EigenLayer Slasher
Expand All @@ -39,6 +45,14 @@ contract BLSRegistryCoordinatorWithIndices is Initializable, IBLSRegistryCoordin
IStakeRegistry public immutable stakeRegistry;
/// @notice the Index Registry contract that will keep track of operators' indexes
IIndexRegistry public immutable indexRegistry;

/**
* @notice Original EIP-712 Domain separator for this contract.
* @dev The domain separator may change in the event of a fork that modifies the ChainID.
* Use the getter function `domainSeparator` to get the current domain separator for this contract.
*/
bytes32 internal _DOMAIN_SEPARATOR;
gpsanant marked this conversation as resolved.
Show resolved Hide resolved

/// @notice the mapping from quorum number to the maximum number of operators that can be registered for that quorum
mapping(uint8 => OperatorSetParam) internal _quorumOperatorSetParams;
/// @notice the mapping from operator's operatorId to the updates of the bitmap of quorums they are registered for
Expand All @@ -47,6 +61,10 @@ contract BLSRegistryCoordinatorWithIndices is Initializable, IBLSRegistryCoordin
mapping(address => Operator) internal _operators;
/// @notice the dynamic-length array of the registries this coordinator is coordinating
address[] public registries;
/// @notice the address of the entity allowed to sign off on operators getting kicked out of the AVS during registration
address public churner;
/// @notice the nonce of the churner used in EIP-712 signatures
uint256 public churnerNonce;
Copy link
Contributor

Choose a reason for hiding this comment

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

for naming: the description suggests that this entity is really a churnApprover or similar.

am also thinking that 'salts' may make more sense than nonces here, but we can debate a bit -- the intended usage is important to consider

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed to churn approver, would prefer to keep the nonce logic for now since we expect this to not have many race conditions

Copy link
Contributor

Choose a reason for hiding this comment

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

agreed offline that salt is more flexible here / nonce is not needed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added


modifier onlyServiceManagerOwner {
require(msg.sender == serviceManager.owner(), "BLSRegistryCoordinatorWithIndices.onlyServiceManagerOwner: caller is not the service manager owner");
Expand All @@ -59,15 +77,17 @@ contract BLSRegistryCoordinatorWithIndices is Initializable, IBLSRegistryCoordin
IStakeRegistry _stakeRegistry,
IBLSPubkeyRegistry _blsPubkeyRegistry,
IIndexRegistry _indexRegistry
) {
) EIP712("AVSRegistryCoordinator", "v0.0.1") {
slasher = _slasher;
serviceManager = _serviceManager;
stakeRegistry = _stakeRegistry;
blsPubkeyRegistry = _blsPubkeyRegistry;
indexRegistry = _indexRegistry;
}

function initialize(OperatorSetParam[] memory _operatorSetParams) external initializer {
function initialize(address _churner, OperatorSetParam[] memory _operatorSetParams) external initializer {
// set the churner
churner = _churner;
Copy link
Contributor

Choose a reason for hiding this comment

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

should presumably have an event associated to this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added

// the stake registry is this contract itself
registries.push(address(stakeRegistry));
registries.push(address(blsPubkeyRegistry));
Expand All @@ -79,6 +99,8 @@ contract BLSRegistryCoordinatorWithIndices is Initializable, IBLSRegistryCoordin
_setOperatorSetParams(i, _operatorSetParams[i]);
}
}

// VIEW FUNCTIONS

/// @notice Returns the operator set params for the given `quorumNumber`
function getOperatorSetParams(uint8 quorumNumber) external view returns (OperatorSetParam memory) {
Expand Down Expand Up @@ -158,6 +180,40 @@ contract BLSRegistryCoordinatorWithIndices is Initializable, IBLSRegistryCoordin
return registries.length;
}

/**
* @notice Public function for the the churner signature hash calculation when operators are being kicked from quorums
* @param registeringOperatorId The is of the registering operator
* @param operatorKickParams The parameters needed to kick the operator from the quorums that have reached their caps
* @param expiry The desired expiry time of the churner's signature
*/
function calculateCurrentOperatorChurnApprovalDigestHash(
bytes32 registeringOperatorId,
OperatorKickParam[] memory operatorKickParams,
uint256 expiry
) public view returns (bytes32) {
// calculate the digest hash
return calculateOperatorChurnApprovalDigestHash(registeringOperatorId, operatorKickParams, churnerNonce, expiry);
}

/**
* @notice Public function for the the churner signature hash calculation when operators are being kicked from quorums
* @param registeringOperatorId The is of the registering operator
* @param operatorKickParams The parameters needed to kick the operator from the quorums that have reached their caps
* @param churnerNonceToUse nonce of the churner. In practice we use the churner's current nonce, stored at `churnerNonce`
* @param expiry The desired expiry time of the churner's signature
*/
function calculateOperatorChurnApprovalDigestHash(
bytes32 registeringOperatorId,
OperatorKickParam[] memory operatorKickParams,
uint256 churnerNonceToUse,
uint256 expiry
) public view returns (bytes32) {
// calculate the digest hash
return _hashTypedDataV4(keccak256(abi.encode(OPERATOR_CHURN_APPROVAL_TYPEHASH, registeringOperatorId, operatorKickParams, churnerNonceToUse, expiry)));
}

// STATE CHANGING FUNCTIONS

/**
* @notice Sets parameters of the operator set for the given `quorumNumber`
* @param quorumNumber is the quorum number to set the maximum number of operators for
Expand Down Expand Up @@ -203,14 +259,18 @@ contract BLSRegistryCoordinatorWithIndices is Initializable, IBLSRegistryCoordin
bytes calldata quorumNumbers,
BN254.G1Point memory pubkey,
string calldata socket,
OperatorKickParam[] calldata operatorKickParams
OperatorKickParam[] calldata operatorKickParams,
SignatureWithExpiry memory signatureAndExpiry
) external {
// register the operator
uint32[] memory numOperatorsPerQuorum = _registerOperatorWithCoordinator(msg.sender, quorumNumbers, pubkey, socket);

// get the registering operator's operatorId
bytes32 registeringOperatorId = _operators[msg.sender].operatorId;

// verify the churner's signature
_verifyChurnerSignatureOnOperatorChurnApproval(registeringOperatorId, operatorKickParams, signatureAndExpiry);

// kick the operators
for (uint256 i = 0; i < quorumNumbers.length; i++) {
// check that the quorum has reached the max operator count
Expand Down Expand Up @@ -398,4 +458,13 @@ contract BLSRegistryCoordinatorWithIndices is Initializable, IBLSRegistryCoordin
_operators[operator].status = OperatorStatus.DEREGISTERED;
}
}

/// @notice verifies churner's signature on operator churn approval and increments the churner nonce
function _verifyChurnerSignatureOnOperatorChurnApproval(bytes32 registeringOperatorId, OperatorKickParam[] memory operatorKickParams, SignatureWithExpiry memory signatureWithExpiry) internal {
uint256 churnerNonceMem = churnerNonce;
require(signatureWithExpiry.expiry >= block.timestamp, "BLSRegistryCoordinatorWithIndices._verifyChurnerSignatureOnOperatorChurnApproval: churner signature expired");
ChaoticWalrus marked this conversation as resolved.
Show resolved Hide resolved
EIP1271SignatureUtils.checkSignature_EIP1271(churner, calculateOperatorChurnApprovalDigestHash(registeringOperatorId, operatorKickParams, churnerNonceMem, signatureWithExpiry.expiry), signatureWithExpiry.signature);
// increment the churner nonce
churnerNonce = churnerNonceMem + 1;
}
}
Loading
Loading