Skip to content

Commit fcb32f5

Browse files
authored
refactor: decouple AVS<>Operator mapping from DelegationManager (#403)
* refactor: initial draft * fix: revert require chages * fix: small nits
1 parent 3e67cd3 commit fcb32f5

File tree

8 files changed

+393
-281
lines changed

8 files changed

+393
-281
lines changed
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity =0.8.12;
3+
4+
import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
5+
import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
6+
import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol";
7+
import "../permissions/Pausable.sol";
8+
import "../libraries/EIP1271SignatureUtils.sol";
9+
import "./AVSDirectoryStorage.sol";
10+
11+
contract AVSDirectory is
12+
Initializable,
13+
OwnableUpgradeable,
14+
Pausable,
15+
AVSDirectoryStorage,
16+
ReentrancyGuardUpgradeable
17+
{
18+
// @dev Index for flag that pauses operator register/deregister to avs when set.
19+
uint8 internal constant PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS = 0;
20+
21+
// @dev Chain ID at the time of contract deployment
22+
uint256 internal immutable ORIGINAL_CHAIN_ID;
23+
24+
/*******************************************************************************
25+
INITIALIZING FUNCTIONS
26+
*******************************************************************************/
27+
28+
/**
29+
* @dev Initializes the immutable addresses of the strategy mananger, delegationManager, slasher,
30+
* and eigenpodManager contracts
31+
*/
32+
constructor(IDelegationManager _delegation) AVSDirectoryStorage(_delegation) {
33+
_disableInitializers();
34+
ORIGINAL_CHAIN_ID = block.chainid;
35+
}
36+
37+
/**
38+
* @dev Initializes the addresses of the initial owner, pauser registry, and paused status.
39+
* minWithdrawalDelayBlocks is set only once here
40+
*/
41+
function initialize(
42+
address initialOwner,
43+
IPauserRegistry _pauserRegistry,
44+
uint256 initialPausedStatus
45+
) external initializer {
46+
_initializePauser(_pauserRegistry, initialPausedStatus);
47+
_DOMAIN_SEPARATOR = _calculateDomainSeparator();
48+
_transferOwnership(initialOwner);
49+
}
50+
51+
/*******************************************************************************
52+
EXTERNAL FUNCTIONS
53+
*******************************************************************************/
54+
55+
56+
/**
57+
* @notice Called by the AVS's service manager contract to register an operator with the avs.
58+
* @param operator The address of the operator to register.
59+
* @param operatorSignature The signature, salt, and expiry of the operator's signature.
60+
*/
61+
function registerOperatorToAVS(
62+
address operator,
63+
ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature
64+
) external onlyWhenNotPaused(PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS) {
65+
66+
require(
67+
operatorSignature.expiry >= block.timestamp,
68+
"AVSDirectory.registerOperatorToAVS: operator signature expired"
69+
);
70+
require(
71+
avsOperatorStatus[msg.sender][operator] != OperatorAVSRegistrationStatus.REGISTERED,
72+
"AVSDirectory.registerOperatorToAVS: operator already registered"
73+
);
74+
require(
75+
!operatorSaltIsSpent[operator][operatorSignature.salt],
76+
"AVSDirectory.registerOperatorToAVS: salt already spent"
77+
);
78+
require(
79+
delegation.isOperator(operator),
80+
"AVSDirectory.registerOperatorToAVS: operator not registered to EigenLayer yet");
81+
82+
// Calculate the digest hash
83+
bytes32 operatorRegistrationDigestHash = calculateOperatorAVSRegistrationDigestHash({
84+
operator: operator,
85+
avs: msg.sender,
86+
salt: operatorSignature.salt,
87+
expiry: operatorSignature.expiry
88+
});
89+
90+
// Check that the signature is valid
91+
EIP1271SignatureUtils.checkSignature_EIP1271(
92+
operator,
93+
operatorRegistrationDigestHash,
94+
operatorSignature.signature
95+
);
96+
97+
// Set the operator as registered
98+
avsOperatorStatus[msg.sender][operator] = OperatorAVSRegistrationStatus.REGISTERED;
99+
100+
// Mark the salt as spent
101+
operatorSaltIsSpent[operator][operatorSignature.salt] = true;
102+
103+
emit OperatorAVSRegistrationStatusUpdated(operator, msg.sender, OperatorAVSRegistrationStatus.REGISTERED);
104+
}
105+
106+
/**
107+
* @notice Called by an avs to deregister an operator with the avs.
108+
* @param operator The address of the operator to deregister.
109+
*/
110+
function deregisterOperatorFromAVS(address operator) external onlyWhenNotPaused(PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS) {
111+
require(
112+
avsOperatorStatus[msg.sender][operator] == OperatorAVSRegistrationStatus.REGISTERED,
113+
"AVSDirectory.deregisterOperatorFromAVS: operator not registered"
114+
);
115+
116+
// Set the operator as deregistered
117+
avsOperatorStatus[msg.sender][operator] = OperatorAVSRegistrationStatus.UNREGISTERED;
118+
119+
emit OperatorAVSRegistrationStatusUpdated(operator, msg.sender, OperatorAVSRegistrationStatus.UNREGISTERED);
120+
}
121+
122+
/**
123+
* @notice Called by an avs to emit an `AVSMetadataURIUpdated` event indicating the information has updated.
124+
* @param metadataURI The URI for metadata associated with an avs
125+
*/
126+
function updateAVSMetadataURI(string calldata metadataURI) external {
127+
emit AVSMetadataURIUpdated(msg.sender, metadataURI);
128+
}
129+
130+
/*******************************************************************************
131+
VIEW FUNCTIONS
132+
*******************************************************************************/
133+
134+
/**
135+
* @notice Calculates the digest hash to be signed by an operator to register with an AVS
136+
* @param operator The account registering as an operator
137+
* @param avs The address of the service manager contract for the AVS that the operator is registering to
138+
* @param salt A unique and single use value associated with the approver signature.
139+
* @param expiry Time after which the approver's signature becomes invalid
140+
*/
141+
function calculateOperatorAVSRegistrationDigestHash(
142+
address operator,
143+
address avs,
144+
bytes32 salt,
145+
uint256 expiry
146+
) public view returns (bytes32) {
147+
// calculate the struct hash
148+
bytes32 structHash = keccak256(
149+
abi.encode(OPERATOR_AVS_REGISTRATION_TYPEHASH, operator, avs, salt, expiry)
150+
);
151+
// calculate the digest hash
152+
bytes32 digestHash = keccak256(
153+
abi.encodePacked("\x19\x01", domainSeparator(), structHash)
154+
);
155+
return digestHash;
156+
}
157+
158+
/**
159+
* @notice Getter function for the current EIP-712 domain separator for this contract.
160+
* @dev The domain separator will change in the event of a fork that changes the ChainID.
161+
*/
162+
function domainSeparator() public view returns (bytes32) {
163+
if (block.chainid == ORIGINAL_CHAIN_ID) {
164+
return _DOMAIN_SEPARATOR;
165+
} else {
166+
return _calculateDomainSeparator();
167+
}
168+
}
169+
170+
// @notice Internal function for calculating the current domain separator of this contract
171+
function _calculateDomainSeparator() internal view returns (bytes32) {
172+
return keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes("EigenLayer")), block.chainid, address(this)));
173+
}
174+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity =0.8.12;
3+
4+
import "../interfaces/IAVSDirectory.sol";
5+
import "../interfaces/IStrategyManager.sol";
6+
import "../interfaces/IDelegationManager.sol";
7+
import "../interfaces/ISlasher.sol";
8+
import "../interfaces/IEigenPodManager.sol";
9+
10+
abstract contract AVSDirectoryStorage is IAVSDirectory {
11+
/// @notice The EIP-712 typehash for the contract's domain
12+
bytes32 public constant DOMAIN_TYPEHASH =
13+
keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");
14+
15+
/// @notice The EIP-712 typehash for the `Registration` struct used by the contract
16+
bytes32 public constant OPERATOR_AVS_REGISTRATION_TYPEHASH =
17+
keccak256("OperatorAVSRegistration(address operator,address avs,bytes32 salt,uint256 expiry)");
18+
19+
/// @notice The DelegationManager contract for EigenLayer
20+
IDelegationManager public immutable delegation;
21+
22+
/**
23+
* @notice Original EIP-712 Domain separator for this contract.
24+
* @dev The domain separator may change in the event of a fork that modifies the ChainID.
25+
* Use the getter function `domainSeparator` to get the current domain separator for this contract.
26+
*/
27+
bytes32 internal _DOMAIN_SEPARATOR;
28+
29+
/// @notice Mapping: AVS => operator => enum of operator status to the AVS
30+
mapping(address => mapping(address => OperatorAVSRegistrationStatus)) public avsOperatorStatus;
31+
32+
/// @notice Mapping: operator => 32-byte salt => whether or not the salt has already been used by the operator.
33+
/// @dev Salt is used in the `registerOperatorToAVS` function.
34+
mapping(address => mapping(bytes32 => bool)) public operatorSaltIsSpent;
35+
36+
constructor(IDelegationManager _delegation) {
37+
delegation = _delegation;
38+
}
39+
40+
/**
41+
* @dev This empty reserved space is put in place to allow future versions to add new
42+
* variables without shifting down storage in the inheritance chain.
43+
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
44+
*/
45+
uint256[47] private __gap;
46+
}

src/contracts/core/DelegationManager.sol

Lines changed: 1 addition & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@ pragma solidity =0.8.12;
44
import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
55
import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
66
import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol";
7-
import "../interfaces/ISlasher.sol";
8-
import "./DelegationManagerStorage.sol";
97
import "../permissions/Pausable.sol";
108
import "../libraries/EIP1271SignatureUtils.sol";
9+
import "./DelegationManagerStorage.sol";
1110

1211
/**
1312
* @title DelegationManager
@@ -29,9 +28,6 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg
2928
// @dev Index for flag that pauses completing existing withdrawals when set.
3029
uint8 internal constant PAUSED_EXIT_WITHDRAWAL_QUEUE = 2;
3130

32-
// @dev Index for flag that pauses operator register/deregister to avs when set.
33-
uint8 internal constant PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS = 3;
34-
3531
// @dev Chain ID at the time of contract deployment
3632
uint256 internal immutable ORIGINAL_CHAIN_ID;
3733

@@ -136,14 +132,6 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg
136132
emit OperatorMetadataURIUpdated(msg.sender, metadataURI);
137133
}
138134

139-
/**
140-
* @notice Called by an avs to emit an `AVSMetadataURIUpdated` event indicating the information has updated.
141-
* @param metadataURI The URI for metadata associated with an avs
142-
*/
143-
function updateAVSMetadataURI(string calldata metadataURI) external {
144-
emit AVSMetadataURIUpdated(msg.sender, metadataURI);
145-
}
146-
147135
/**
148136
* @notice Caller delegates their stake to an operator.
149137
* @param operator The account (`msg.sender`) is delegating its assets to for use in serving applications built on EigenLayer.
@@ -435,72 +423,6 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg
435423
}
436424
}
437425

438-
/**
439-
* @notice Called by the AVS's service manager contract to register an operator with the avs.
440-
* @param operator The address of the operator to register.
441-
* @param operatorSignature The signature, salt, and expiry of the operator's signature.
442-
*/
443-
function registerOperatorToAVS(
444-
address operator,
445-
ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature
446-
) external onlyWhenNotPaused(PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS) {
447-
448-
require(
449-
operatorSignature.expiry >= block.timestamp,
450-
"DelegationManager.registerOperatorToAVS: operator signature expired"
451-
);
452-
require(
453-
avsOperatorStatus[msg.sender][operator] != OperatorAVSRegistrationStatus.REGISTERED,
454-
"DelegationManager.registerOperatorToAVS: operator already registered"
455-
);
456-
require(
457-
!operatorSaltIsSpent[operator][operatorSignature.salt],
458-
"DelegationManager.registerOperatorToAVS: salt already spent"
459-
);
460-
require(
461-
isOperator(operator),
462-
"DelegationManager.registerOperatorToAVS: operator not registered to EigenLayer yet");
463-
464-
// Calculate the digest hash
465-
bytes32 operatorRegistrationDigestHash = calculateOperatorAVSRegistrationDigestHash({
466-
operator: operator,
467-
avs: msg.sender,
468-
salt: operatorSignature.salt,
469-
expiry: operatorSignature.expiry
470-
});
471-
472-
// Check that the signature is valid
473-
EIP1271SignatureUtils.checkSignature_EIP1271(
474-
operator,
475-
operatorRegistrationDigestHash,
476-
operatorSignature.signature
477-
);
478-
479-
// Set the operator as registered
480-
avsOperatorStatus[msg.sender][operator] = OperatorAVSRegistrationStatus.REGISTERED;
481-
482-
// Mark the salt as spent
483-
operatorSaltIsSpent[operator][operatorSignature.salt] = true;
484-
485-
emit OperatorAVSRegistrationStatusUpdated(operator, msg.sender, OperatorAVSRegistrationStatus.REGISTERED);
486-
}
487-
488-
/**
489-
* @notice Called by an avs to deregister an operator with the avs.
490-
* @param operator The address of the operator to deregister.
491-
*/
492-
function deregisterOperatorFromAVS(address operator) external onlyWhenNotPaused(PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS) {
493-
require(
494-
avsOperatorStatus[msg.sender][operator] == OperatorAVSRegistrationStatus.REGISTERED,
495-
"DelegationManager.deregisterOperatorFromAVS: operator not registered"
496-
);
497-
498-
// Set the operator as deregistered
499-
avsOperatorStatus[msg.sender][operator] = OperatorAVSRegistrationStatus.UNREGISTERED;
500-
501-
emit OperatorAVSRegistrationStatusUpdated(operator, msg.sender, OperatorAVSRegistrationStatus.UNREGISTERED);
502-
}
503-
504426
/**
505427
* @notice Called by owner to set the minimum withdrawal delay blocks for each passed in strategy
506428
* Note that the min number of blocks to complete a withdrawal of a strategy is
@@ -1061,30 +983,6 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg
1061983
return approverDigestHash;
1062984
}
1063985

1064-
/**
1065-
* @notice Calculates the digest hash to be signed by an operator to register with an AVS
1066-
* @param operator The account registering as an operator
1067-
* @param avs The address of the service manager contract for the AVS that the operator is registering to
1068-
* @param salt A unique and single use value associated with the approver signature.
1069-
* @param expiry Time after which the approver's signature becomes invalid
1070-
*/
1071-
function calculateOperatorAVSRegistrationDigestHash(
1072-
address operator,
1073-
address avs,
1074-
bytes32 salt,
1075-
uint256 expiry
1076-
) public view returns (bytes32) {
1077-
// calculate the struct hash
1078-
bytes32 structHash = keccak256(
1079-
abi.encode(OPERATOR_AVS_REGISTRATION_TYPEHASH, operator, avs, salt, expiry)
1080-
);
1081-
// calculate the digest hash
1082-
bytes32 digestHash = keccak256(
1083-
abi.encodePacked("\x19\x01", domainSeparator(), structHash)
1084-
);
1085-
return digestHash;
1086-
}
1087-
1088986
/**
1089987
* @dev Recalculates the domain separator when the chainid changes due to a fork.
1090988
*/

src/contracts/core/DelegationManagerStorage.sol

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,6 @@ abstract contract DelegationManagerStorage is IDelegationManager {
2525
bytes32 public constant DELEGATION_APPROVAL_TYPEHASH =
2626
keccak256("DelegationApproval(address staker,address operator,bytes32 salt,uint256 expiry)");
2727

28-
/// @notice The EIP-712 typehash for the `Registration` struct used by the contract
29-
bytes32 public constant OPERATOR_AVS_REGISTRATION_TYPEHASH =
30-
keccak256("OperatorAVSRegistration(address operator,address avs,bytes32 salt,uint256 expiry)");
31-
3228
/**
3329
* @notice Original EIP-712 Domain separator for this contract.
3430
* @dev The domain separator may change in the event of a fork that modifies the ChainID.
@@ -99,13 +95,6 @@ abstract contract DelegationManagerStorage is IDelegationManager {
9995
/// See conversation here: https://github.com/Layr-Labs/eigenlayer-contracts/pull/365/files#r1417525270
10096
address private __deprecated_stakeRegistry;
10197

102-
/// @notice Mapping: AVS => operator => enum of operator status to the AVS
103-
mapping(address => mapping(address => OperatorAVSRegistrationStatus)) public avsOperatorStatus;
104-
105-
/// @notice Mapping: operator => 32-byte salt => whether or not the salt has already been used by the operator.
106-
/// @dev Salt is used in the `registerOperatorToAVS` function.
107-
mapping(address => mapping(bytes32 => bool)) public operatorSaltIsSpent;
108-
10998
/**
11099
* @notice Minimum delay enforced by this contract per Strategy for completing queued withdrawals. Measured in blocks, and adjustable by this contract's owner,
111100
* up to a maximum of `MAX_WITHDRAWAL_DELAY_BLOCKS`. Minimum value is 0 (i.e. no delay enforced).
@@ -123,5 +112,5 @@ abstract contract DelegationManagerStorage is IDelegationManager {
123112
* variables without shifting down storage in the inheritance chain.
124113
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
125114
*/
126-
uint256[37] private __gap;
115+
uint256[39] private __gap;
127116
}

0 commit comments

Comments
 (0)