-
Notifications
You must be signed in to change notification settings - Fork 113
feat: avs registrar #484
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
feat: avs registrar #484
Changes from all commits
d56fdb3
14a752c
25d058c
3b9b7cc
24a6eb3
7a8bd06
ea6407a
c97e5e9
e28691d
d688489
5bb905e
cbec704
ace6fe1
f2956ed
8e6ebee
3c6ade5
c9daea1
320c6cb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity ^0.8.27; | ||
|
||
interface IAVSRegistrarErrors { | ||
/// @notice Thrown when a key is not registered | ||
error KeyNotRegistered(); | ||
/// @notice Thrown when the caller is not the allocation manager | ||
error NotAllocationManager(); | ||
} | ||
|
||
interface IAVSRegistrarEvents { | ||
/// @notice Emitted when a new operator is registered | ||
event OperatorRegistered(address indexed operator, uint32[] operatorSetIds); | ||
|
||
/// @notice Emitted when an operator is deregistered | ||
event OperatorDeregistered(address indexed operator, uint32[] operatorSetIds); | ||
} | ||
|
||
/// @notice Since we have already defined a public interface, we add the events and errors here | ||
interface IAVSRegistrarInternal is IAVSRegistrarErrors, IAVSRegistrarEvents {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity >=0.5.0; | ||
|
||
import {OperatorSet} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol"; | ||
|
||
interface IAllowlistErrors { | ||
/// @notice Thrown when the operator is already in the allowlist | ||
error OperatorAlreadyInAllowlist(); | ||
/// @notice Thrown when the operator is not in the allowlist | ||
error OperatorNotInAllowlist(); | ||
} | ||
|
||
interface IAllowlistEvents { | ||
/// @notice Emitted when an operator is added to the allowlist | ||
event OperatorAddedToAllowlist(OperatorSet indexed operatorSet, address indexed operator); | ||
/// @notice Emitted when an operator is removed from the allowlist | ||
event OperatorRemovedFromAllowlist(OperatorSet indexed operatorSet, address indexed operator); | ||
} | ||
|
||
interface IAllowlist is IAllowlistErrors, IAllowlistEvents { | ||
/** | ||
* @notice Adds an operator to the allowlist | ||
* @param operatorSet The operator set to add the operator to | ||
* @param operator The operator to add to the allowlist | ||
* @dev Only callable by the owner | ||
*/ | ||
function addOperatorToAllowlist(OperatorSet memory operatorSet, address operator) external; | ||
|
||
/** | ||
* @notice Removes an operator from the allowlist | ||
* @param operatorSet The operator set to remove the operator from | ||
* @param operator The operator to remove from the allowlist | ||
* @dev If an operator is removed from the allowlist and is already registered, the avs | ||
* must then handle state changes appropriately (ie. eject the operator) | ||
* @dev Only callable by the owner | ||
*/ | ||
function removeOperatorFromAllowlist( | ||
OperatorSet memory operatorSet, | ||
address operator | ||
) external; | ||
|
||
/** | ||
* @notice Checks if an operator is in the allowlist | ||
* @param operatorSet The operator set to check the operator in | ||
* @param operator The operator to check | ||
* @return True if the operator is in the allowlist, false otherwise | ||
*/ | ||
function isOperatorAllowed( | ||
OperatorSet memory operatorSet, | ||
address operator | ||
) external view returns (bool); | ||
|
||
/** | ||
* @notice Returns all operators in the allowlist | ||
* @param operatorSet The operator set to get the allowed operators from | ||
* @return An array of all operators in the allowlist | ||
* @dev This function should be used with caution, as it can be expensive to call on-chain | ||
*/ | ||
function getAllowedOperators( | ||
OperatorSet memory operatorSet | ||
) external view returns (address[] memory); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity ^0.8.27; | ||
|
||
import "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol"; | ||
|
||
/// @notice A dummy interface for the KeyRegistrar | ||
interface IKeyRegistrar { | ||
enum CurveType { | ||
ECDSA, | ||
BN254 | ||
} | ||
|
||
function checkAndUpdateKey( | ||
OperatorSet calldata operatorSet, | ||
address operator | ||
) external returns (bool); | ||
|
||
function removeKey(OperatorSet calldata operatorSet, address operator) external; | ||
|
||
function isRegistered( | ||
OperatorSet calldata operatorSet, | ||
address operator | ||
) external view returns (bool); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity ^0.8.27; | ||
|
||
interface ISocketRegistryErrors { | ||
/// @notice Thrown when the caller is not the operator | ||
error CallerNotOperator(); | ||
|
||
/// @notice Thrown when the data length mismatch | ||
error DataLengthMismatch(); | ||
} | ||
|
||
interface ISocketRegistryEvents { | ||
/// @notice Emitted when an operator socket is set | ||
event OperatorSocketSet(address indexed operator, string socket); | ||
} | ||
|
||
interface ISocketRegistry is ISocketRegistryErrors, ISocketRegistryEvents { | ||
/** | ||
* @notice Gets the socket for an operator. | ||
* @param operator The operator to get the socket for. | ||
* @return The socket for the operator. | ||
*/ | ||
function getOperatorSocket( | ||
address operator | ||
) external view returns (string memory); | ||
|
||
/** | ||
* @notice Updates the socket for an operator. | ||
* @param operator The operator to set the socket for. | ||
* @param socket The socket to set for the operator. | ||
* @dev This function can only be called by the operator themselves. | ||
*/ | ||
function updateSocket(address operator, string memory socket) external; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
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 { | ||
OperatorSetLib, | ||
OperatorSet | ||
} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol"; | ||
import { | ||
IKeyRegistrarTypes, | ||
IKeyRegistrar | ||
} from "eigenlayer-contracts/src/contracts/interfaces/IKeyRegistrar.sol"; | ||
|
||
import {AVSRegistrarStorage} from "./AVSRegistrarStorage.sol"; | ||
|
||
import {Initializable} from "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; | ||
|
||
/// @notice A minimal AVSRegistrar contract that is used to register/deregister operators for an AVS | ||
contract AVSRegistrar is Initializable, AVSRegistrarStorage { | ||
using OperatorSetLib for OperatorSet; | ||
|
||
modifier onlyAllocationManager() { | ||
require(msg.sender == address(allocationManager), NotAllocationManager()); | ||
_; | ||
} | ||
|
||
constructor( | ||
address _avs, | ||
IAllocationManager _allocationManager, | ||
IKeyRegistrar _keyRegistrar | ||
) AVSRegistrarStorage(_avs, _allocationManager, _keyRegistrar) { | ||
_disableInitializers(); | ||
} | ||
|
||
/// @inheritdoc IAVSRegistrar | ||
function registerOperator( | ||
address operator, | ||
address avs, | ||
uint32[] calldata operatorSetIds, | ||
bytes calldata data | ||
) external virtual onlyAllocationManager { | ||
_beforeRegisterOperator(operator, operatorSetIds, data); | ||
|
||
// Check that the operator has a valid key and update key if needed | ||
_validateOperatorKeys(operator, operatorSetIds); | ||
|
||
_afterRegisterOperator(operator, operatorSetIds, data); | ||
|
||
emit OperatorRegistered(operator, operatorSetIds); | ||
} | ||
|
||
/// @inheritdoc IAVSRegistrar | ||
function deregisterOperator( | ||
address operator, | ||
address avs, | ||
uint32[] calldata operatorSetIds | ||
) external virtual onlyAllocationManager { | ||
_beforeDeregisterOperator(operator, operatorSetIds); | ||
|
||
_afterDeregisterOperator(operator, operatorSetIds); | ||
Comment on lines
+60
to
+62
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't we just need 1 hook here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Leaving it maximally flexible for now, can change later on. One idea rn is that you may want to preempt some gas-heavy optimizations in the before hook |
||
|
||
emit OperatorDeregistered(operator, operatorSetIds); | ||
} | ||
|
||
/// @inheritdoc IAVSRegistrar | ||
function supportsAVS( | ||
address _avs | ||
) public view virtual returns (bool) { | ||
return _avs == avs; | ||
} | ||
|
||
/* | ||
* | ||
* INTERNAL FUNCTIONS | ||
* | ||
*/ | ||
|
||
/** | ||
* @notice Validates that the operator has registered a key for the given operator sets | ||
* @param operator The operator to validate | ||
* @param operatorSetIds The operator sets to validate | ||
* @dev This function assumes the operator has already registered a key in the Key Registrar | ||
*/ | ||
function _validateOperatorKeys(address operator, uint32[] calldata operatorSetIds) internal { | ||
for (uint32 i = 0; i < operatorSetIds.length; i++) { | ||
OperatorSet memory operatorSet = OperatorSet({avs: avs, id: operatorSetIds[i]}); | ||
require(keyRegistrar.checkKey(operatorSet, operator), KeyNotRegistered()); | ||
} | ||
} | ||
|
||
/** | ||
* @notice Hook called before the operator is registered | ||
* @param operator The operator to register | ||
* @param operatorSetIds The operator sets to register | ||
* @param data The data to register | ||
*/ | ||
function _beforeRegisterOperator( | ||
address operator, | ||
uint32[] calldata operatorSetIds, | ||
bytes calldata data | ||
) internal virtual {} | ||
|
||
/** | ||
* @notice Hook called after the operator is registered | ||
* @param operator The operator to register | ||
* @param operatorSetIds The operator sets to register | ||
* @param data The data to register | ||
*/ | ||
function _afterRegisterOperator( | ||
address operator, | ||
uint32[] calldata operatorSetIds, | ||
bytes calldata data | ||
) internal virtual {} | ||
|
||
/** | ||
* @notice Hook called before the operator is deregistered | ||
* @param operator The operator to deregister | ||
* @param operatorSetIds The operator sets to deregister | ||
*/ | ||
function _beforeDeregisterOperator( | ||
address operator, | ||
uint32[] calldata operatorSetIds | ||
) internal virtual {} | ||
|
||
/** | ||
* @notice Hook called after the operator is deregistered | ||
* @param operator The operator to deregister | ||
* @param operatorSetIds The operator sets to deregister | ||
*/ | ||
function _afterDeregisterOperator( | ||
address operator, | ||
uint32[] calldata operatorSetIds | ||
) internal virtual {} | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have we considered at all about upgrading OZ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not yet, are you suggesting for unstructured storage? |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity ^0.8.27; | ||
|
||
import {IAVSRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IAVSRegistrar.sol"; | ||
import {IKeyRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IKeyRegistrar.sol"; | ||
import {IAVSRegistrarInternal} from "../../interfaces/IAVSRegistrarInternal.sol"; | ||
import {IAllocationManager} from | ||
"eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; | ||
|
||
/// @notice A minimal storage contract for the AVSRegistrar | ||
abstract contract AVSRegistrarStorage is IAVSRegistrar, IAVSRegistrarInternal { | ||
/** | ||
* | ||
* CONSTANTS AND IMMUTABLES | ||
* | ||
*/ | ||
|
||
/// @notice The AVS that this registrar is for | ||
/// @dev In practice, the AVS address in EigenLayer core is address that initialized the Metadata URI. | ||
address public immutable avs; | ||
|
||
/// @notice The allocation manager in EigenLayer core | ||
IAllocationManager public immutable allocationManager; | ||
|
||
/// @notice Pointer to the EigenLayer core Key Registrar | ||
IKeyRegistrar public immutable keyRegistrar; | ||
|
||
constructor(address _avs, IAllocationManager _allocationManager, IKeyRegistrar _keyRegistrar) { | ||
avs = _avs; | ||
allocationManager = _allocationManager; | ||
keyRegistrar = _keyRegistrar; | ||
} | ||
|
||
/** | ||
* @dev This empty reserved space is put in place to allow future versions to add new | ||
* variables without shifting down storage in the inheritance chain. | ||
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps | ||
*/ | ||
uint256[50] private __GAP; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity ^0.8.27; | ||
|
||
import {IAllowlist} from "../../../interfaces/IAllowlist.sol"; | ||
import {AllowlistStorage} from "./AllowlistStorage.sol"; | ||
|
||
import {OwnableUpgradeable} from | ||
"openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol"; | ||
import {EnumerableSetUpgradeable} from | ||
"openzeppelin-contracts-upgradeable/contracts/utils/structs/EnumerableSetUpgradeable.sol"; | ||
|
||
import {Initializable} from "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; | ||
import { | ||
OperatorSet, | ||
OperatorSetLib | ||
} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol"; | ||
|
||
abstract contract Allowlist is OwnableUpgradeable, AllowlistStorage { | ||
using OperatorSetLib for OperatorSet; | ||
using EnumerableSetUpgradeable for EnumerableSetUpgradeable.AddressSet; | ||
|
||
function initialize( | ||
address _owner | ||
) public virtual initializer { | ||
_initializeAllowlist(_owner); | ||
} | ||
|
||
function _initializeAllowlist( | ||
address _owner | ||
) internal onlyInitializing { | ||
__Ownable_init(); | ||
_transferOwnership(_owner); | ||
} | ||
|
||
/// @inheritdoc IAllowlist | ||
function addOperatorToAllowlist( | ||
OperatorSet memory operatorSet, | ||
address operator | ||
) external onlyOwner { | ||
require(_allowedOperators[operatorSet.key()].add(operator), OperatorAlreadyInAllowlist()); | ||
emit OperatorAddedToAllowlist(operatorSet, operator); | ||
} | ||
|
||
/// @inheritdoc IAllowlist | ||
function removeOperatorFromAllowlist( | ||
OperatorSet memory operatorSet, | ||
address operator | ||
) external onlyOwner { | ||
require(_allowedOperators[operatorSet.key()].remove(operator), OperatorNotInAllowlist()); | ||
emit OperatorRemovedFromAllowlist(operatorSet, operator); | ||
} | ||
|
||
/// @inheritdoc IAllowlist | ||
function isOperatorAllowed( | ||
OperatorSet memory operatorSet, | ||
address operator | ||
) public view returns (bool) { | ||
return _allowedOperators[operatorSet.key()].contains(operator); | ||
} | ||
|
||
/// @inheritdoc IAllowlist | ||
function getAllowedOperators( | ||
OperatorSet memory operatorSet | ||
) external view returns (address[] memory) { | ||
return _allowedOperators[operatorSet.key()].values(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I needed to make this upgradeable, I'm assuming I would need to inherit and make the child contract upgradeable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also should this be
abstract
or is it intended to be deployed as such if you want the bare functionality?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, see what we do with the presets. Intended to be deployed as such if you want the bare functionality