Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
5dbd94a
feat: operator set scaffold
ypatil12 Jun 3, 2024
51ff168
fix: impl/storage compile errors; pending updating of tests
ypatil12 Jun 3, 2024
d477943
chore: `forge fmt`
0xClandestine Jun 4, 2024
bcf7d21
fix: `OperatorSet` struct misuse
0xClandestine Jun 4, 2024
3ff5cb6
fix: comment
ypatil12 Jun 5, 2024
b31d4ed
Merge branch 'feat/operator-sets' of https://github.com/Layr-Labs/eig…
0xClandestine Jun 5, 2024
73cf5e2
chore: verbose use of `OperatorSet`
0xClandestine Jun 5, 2024
5d6a28c
test: `registerOperatorToOperatorSet`
0xClandestine Jun 5, 2024
e94c74b
feat: `registerOperatorToOperatorSets`
0xClandestine Jun 6, 2024
331afaa
chore: `forge fmt`
0xClandestine Jun 6, 2024
b4724c0
feat: interface changes
0xClandestine Jun 6, 2024
17e9f78
fix: operator set digest
0xClandestine Jun 6, 2024
6bcc71c
fix: `OPERATOR_SET_REGISTRATION_TYPEHASH`
0xClandestine Jun 6, 2024
08aa8fc
chore: `forge fmt`
0xClandestine Jun 6, 2024
0b07f47
test: wrong avs using signature
0xClandestine Jun 6, 2024
97a6dba
fix: optimize for SSTOREs
0xClandestine Jun 6, 2024
e9359ad
test: `deregisterOperatorFromOperatorSets`
0xClandestine Jun 6, 2024
b92b285
chore: rename `operatorSetStrategies`
0xClandestine Jun 6, 2024
18c7133
test: `addStrategiesToOperatorSet`
0xClandestine Jun 6, 2024
b869392
test: `removeStrategiesFromOperatorSet`
0xClandestine Jun 6, 2024
c4437c6
test: more coverage
0xClandestine Jun 7, 2024
5d09658
chore: improve natspec
0xClandestine Jun 7, 2024
648cc94
WIP: simp mode
0xClandestine Jun 17, 2024
d8c046f
WIP: simp mode
0xClandestine Jun 17, 2024
d12ea4c
WIP: simp mode
0xClandestine Jun 17, 2024
c926f2f
test: simp mode
0xClandestine Jun 18, 2024
92479e2
test: simp mode
0xClandestine Jun 18, 2024
27ed1a1
test: simp mode
0xClandestine Jun 18, 2024
3b0450e
refactor: simp mode storage
0xClandestine Jun 19, 2024
5f90d78
Revert "refactor: simp mode storage"
0xClandestine Jun 19, 2024
f4c3ae5
Reapply "refactor: simp mode storage"
0xClandestine Jun 19, 2024
c190c1b
feat: simp mode
0xClandestine Jun 20, 2024
3b984ab
fix(optimize): salt cancellation
0xClandestine Jun 20, 2024
b944193
test: improvements
0xClandestine Jun 20, 2024
98d0c4f
test: improvements
0xClandestine Jun 20, 2024
d83a870
fix: move `isOperatorSetAVS` update out of loop
0xClandestine Jun 24, 2024
1fa4a42
fix: standby update typehash
0xClandestine Jun 24, 2024
1e9609f
test: cleanup
0xClandestine Jun 25, 2024
1328e23
fix: move mutation out of loop
0xClandestine Jun 25, 2024
0553d18
nit: cleanup
0xClandestine Jun 25, 2024
22ceb9c
fix: remove unused events
ypatil12 Jun 26, 2024
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
188 changes: 186 additions & 2 deletions src/contracts/core/AVSDirectory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ contract AVSDirectory is
// @dev Chain ID at the time of contract deployment
uint256 internal immutable ORIGINAL_CHAIN_ID;

/// @notice Canonical, virtual beacon chain ETH strategy
IStrategy public constant beaconChainETHStrategy = IStrategy(0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0);

/*******************************************************************************
INITIALIZING FUNCTIONS
*******************************************************************************/
Expand All @@ -29,7 +32,7 @@ contract AVSDirectory is
* @dev Initializes the immutable addresses of the strategy mananger, delegationManager, slasher,
* and eigenpodManager contracts
*/
constructor(IDelegationManager _delegation) AVSDirectoryStorage(_delegation) {
constructor(IDelegationManager _delegation, IStrategyManager _strategyManager) AVSDirectoryStorage(_delegation, _strategyManager) {
_disableInitializers();
ORIGINAL_CHAIN_ID = block.chainid;
}
Expand All @@ -52,6 +55,110 @@ contract AVSDirectory is
EXTERNAL FUNCTIONS
*******************************************************************************/

/**
* @notice Called by AVSs to add an operator to an operator set
*
* @param operator the address of the operator to be added to the operator set
* @param operatorSetID the ID of the operator set
* @param operatorSignature the signature of the operator on their intent to register
*
* @dev msg.sender is used as the AVS
* @dev operator must not have a deregistration from the operator set
* @dev if this is the first operator set in the AVS that the operator is
* registering for, a OperatorAVSRegistrationStatusUpdated event is emitted with
* a REGISTERED status
= */
function registerOperatorToOperatorSet(
address operator,
uint32 operatorSetID,
ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature
) external onlyWhenNotPaused(PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS) {
require(
operatorSignature.expiry >= block.timestamp,
"AVSDirectory.registerOperatorToOperatorSet: operator signature expired"
);
require(
operatorSetRegistrations[msg.sender][operator][operatorSetID] != true,
"AVSDirectory.registerOperatorToOperatorSet: operator already registered to operator set"
);
require(
!operatorSaltIsSpent[operator][operatorSignature.salt],
"AVSDirectory.registerOperatorToAVS: salt already spent"
);
require(
delegation.isOperator(operator),
"AVSDirectory.registerOperatorToAVS: operator not registered to EigenLayer yet"
);

// Calculate the digest hash
bytes32 operatorSetRegistrationDigestHash = calculateOperatorAVSRegistrationDigestHash({
operator: operator,
avs: msg.sender,
operatorSetID: operatorSetID,
salt: operatorSignature.salt,
expiry: operatorSignature.expiry
});

// Check that the signature is valid
EIP1271SignatureUtils.checkSignature_EIP1271(
operator,
operatorSetRegistrationDigestHash,
operatorSignature.signature
);

// Set as Operator Set AVS, preventing any further legacy registrations
if (!isOperatorSetAVS[msg.sender]) {
isOperatorSetAVS[msg.sender] = true;
}

// Update operator set registration
operatorSetRegistrations[msg.sender][operator][operatorSetID] = true;
operatorAVSOperatorSetCount[msg.sender][operator] += 1;

// Mark the salt as spent
operatorSaltIsSpent[operator][operatorSignature.salt] = true;

// Set the operator as registered if not already
if (avsOperatorStatus[msg.sender][operator] != OperatorAVSRegistrationStatus.REGISTERED) {
avsOperatorStatus[msg.sender][operator] = OperatorAVSRegistrationStatus.REGISTERED;
emit OperatorAVSRegistrationStatusUpdated(operator, msg.sender, OperatorAVSRegistrationStatus.REGISTERED);
}

emit OperatorAddedToOperatorSet(operator, msg.sender, operatorSetID);
}

/**
* @notice Called by AVSs or operators to remove an operator to from operator set
*
* @param operator the address of the operator to be removed from the
* operator set
* @param operatorSetID the ID of the operator set
*
* @dev msg.sender is used as the AVS
* @dev operator must be registered for msg.sender AVS and the given
* operator set
*/
function deregisterOperatorFromOperatorSet(
address operator,
uint32 operatorSetID
) external onlyWhenNotPaused(PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS) {
require(
operatorSetRegistrations[msg.sender][operator][operatorSetID] == true,
"AVSDirectory.deregisterOperatorFromOperatorSet: operator not registered for operator set"
);

// Update operator set registration
operatorSetRegistrations[msg.sender][operator][operatorSetID] = false;
operatorAVSOperatorSetCount[msg.sender][operator] -= 1;

emit OperatorRemovedFromOperatorSet(operator, msg.sender, operatorSetID);

// Set the operator as deregistered if no longer registered for any operator sets
if (operatorAVSOperatorSetCount[msg.sender][operator] == 0) {
avsOperatorStatus[msg.sender][operator] = OperatorAVSRegistrationStatus.UNREGISTERED;
emit OperatorAVSRegistrationStatusUpdated(operator, msg.sender, OperatorAVSRegistrationStatus.UNREGISTERED);
}
}

/**
* @notice Called by the AVS's service manager contract to register an operator with the avs.
Expand All @@ -77,7 +184,11 @@ contract AVSDirectory is
);
require(
delegation.isOperator(operator),
"AVSDirectory.registerOperatorToAVS: operator not registered to EigenLayer yet");
"AVSDirectory.registerOperatorToAVS: operator not registered to EigenLayer yet"
);
require(
!isOperatorSetAVS[msg.sender]
);

// Calculate the digest hash
bytes32 operatorRegistrationDigestHash = calculateOperatorAVSRegistrationDigestHash({
Expand Down Expand Up @@ -127,6 +238,53 @@ contract AVSDirectory is
emit AVSMetadataURIUpdated(msg.sender, metadataURI);
}

/**
* @notice Adds strategies to an operator set
* @param operatorSetID The ID of the operator set
* @param strategies The strategies to add to the operator set
* TODO: align with team on if we want to keep the two require statements
*/
function addStrategiesToOperatorSet(
uint32 operatorSetID,
IStrategy[] calldata strategies
) external {
uint256 strategiesToAdd = strategies.length;
for (uint256 i = 0; i < strategiesToAdd; i++) {
// Require that the strategy is valid
require(
strategyManager.strategyIsWhitelistedForDeposit(strategies[i]) || strategies[i] == beaconChainETHStrategy,
"AVSDirectory.addStrategiesToOperatorSet: invalid strategy considered"
);
require(
!operatorSetStrategies[msg.sender][operatorSetID][strategies[i]],
"AVSDirectory.addStrategiesToOperatorSet: strategy already added to operator set"
);
operatorSetStrategies[msg.sender][operatorSetID][strategies[i]] = true;
emit OperatorSetStrategyAdded(msg.sender, operatorSetID, strategies[i]);
}
}

/**
* @notice Removes strategies from an operator set
* @param operatorSetID The ID of the operator set
* @param strategies The strategies to remove from the operator set
*/
function removeStrategiesFromOperatorSet(
uint32 operatorSetID,
IStrategy[] calldata strategies
) external {
uint256 strategiesToRemove = strategies.length;
for (uint256 i = 0; i < strategiesToRemove; i++) {
require(
operatorSetStrategies[msg.sender][operatorSetID][strategies[i]],
"AVSDirectory.removeStrategiesFromOperatorSet: strategy not a member of operator set"
);
operatorSetStrategies[msg.sender][operatorSetID][strategies[i]] = false;
emit OperatorSetStrategyRemoved(msg.sender, operatorSetID, strategies[i]);
}
}


/**
* @notice Called by an operator to cancel a salt that has been used to register with an AVS.
* @param salt A unique and single use value associated with the approver signature.
Expand Down Expand Up @@ -164,6 +322,32 @@ contract AVSDirectory is
return digestHash;
}

/**
* @notice Calculates the digest hash to be signed by an operator to register with an operator set
* @param operator The operator set that the operator is registering to
* @param avs The AVS the operator is registering to
* @param operatorSetID The ID of the operator set
* @param salt A unique and single use value associated with the approver signature.
* @param expiry Time after which the approver's signature becomes invalid
*/
function calculateOperatorSetRegistrationDigestHash(
address operator,
address avs,
uint32 operatorSetID,
bytes32 salt,
uint256 expiry
) public view returns (bytes32) {
// calculate the struct hash
bytes32 structHash = keccak256(
abi.encode(OPERATOR_SET_REGISTRATION_TYPEHASH, operator, avs, operatorSetID, salt, expiry)
);
// calculate the digest hash
bytes32 digestHash = keccak256(
abi.encodePacked("\x19\x01", domainSeparator(), structHash)
);
return digestHash;
}

/**
* @notice Getter function for the current EIP-712 domain separator for this contract.
* @dev The domain separator will change in the event of a fork that changes the ChainID.
Expand Down
28 changes: 23 additions & 5 deletions src/contracts/core/AVSDirectoryStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ pragma solidity ^0.8.12;
import "../interfaces/IAVSDirectory.sol";
import "../interfaces/IStrategyManager.sol";
import "../interfaces/IDelegationManager.sol";
import "../interfaces/ISlasher.sol";
import "../interfaces/IEigenPodManager.sol";

abstract contract AVSDirectoryStorage is IAVSDirectory {
/// @notice The EIP-712 typehash for the contract's domain
Expand All @@ -16,9 +14,16 @@ abstract contract AVSDirectoryStorage is IAVSDirectory {
bytes32 public constant OPERATOR_AVS_REGISTRATION_TYPEHASH =
keccak256("OperatorAVSRegistration(address operator,address avs,bytes32 salt,uint256 expiry)");

/// @notice The EIP-712 typehash for the `OperatorSetRegistration` struct used by the contract
bytes32 public constant OPERATOR_SET_REGISTRATION_TYPEHASH =
keccak256("OperatorSetRegistration(address operator,address avs,uint32 operatorSetID,bytes32 salt,uint256 expiry)");

/// @notice The DelegationManager contract for EigenLayer
IDelegationManager public immutable delegation;

/// @notice The StrategyManager contract for EigenLayer
IStrategyManager public immutable strategyManager;

/**
* @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.
Expand All @@ -30,17 +35,30 @@ abstract contract AVSDirectoryStorage is IAVSDirectory {
mapping(address => mapping(address => OperatorAVSRegistrationStatus)) public avsOperatorStatus;

/// @notice Mapping: operator => 32-byte salt => whether or not the salt has already been used by the operator.
/// @dev Salt is used in the `registerOperatorToAVS` function.
/// @dev Salt is used in the `registerOperatorToAVS` and `registerOperatorToOperatorSet` function.
mapping(address => mapping(bytes32 => bool)) public operatorSaltIsSpent;

constructor(IDelegationManager _delegation) {
/// @notice Mapping: AVS => whether or not the AVS uses operator set
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: standardize lower or uppercase avs in comments

mapping(address => bool) public isOperatorSetAVS;

/// @notice Mapping: AVS => operatorSetID => strategy => whether or not the strategy is in the operator set
mapping(address => mapping(uint32 => mapping(IStrategy => bool))) public operatorSetStrategies;

/// @notice Mapping: avs => operator => operatorSetID => whether the operator is registered for the operator set
mapping(address => mapping(address => mapping(uint32 => bool))) public operatorSetRegistrations;

/// @notice Mapping: avs => operator => number of operator sets the operator is registered for the AVS
mapping(address => mapping(address => uint256)) public operatorAVSOperatorSetCount;

constructor(IDelegationManager _delegation, IStrategyManager _strategyManager) {
delegation = _delegation;
strategyManager = _strategyManager;
}

/**
* @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[47] private __gap;
uint256[43] private __gap;
}
Loading