From 268a1c8fe2388a5f08a24566cb769dfeeaa40d71 Mon Sep 17 00:00:00 2001 From: jaxxjj Date: Wed, 26 Feb 2025 17:12:02 -0700 Subject: [PATCH 01/12] feat: add operator set support to ecdsa stake registry --- src/interfaces/IECDSAStakeRegistry.sol | 74 ++++- src/unaudited/ECDSAAVSRegistrar.sol | 64 ++++ src/unaudited/ECDSAServiceManagerBase.sol | 129 +++++++- src/unaudited/ECDSAStakeRegistry.sol | 288 ++++++++++++++++-- src/unaudited/ECDSAStakeRegistryStorage.sol | 40 ++- .../ECDSAStakeRegistryEqualWeight.sol | 21 +- .../ECDSAStakeRegistryPermissioned.sol | 29 +- test/mocks/AVSDirectoryMock.sol | 5 + test/mocks/ECDSAServiceManagerMock.sol | 44 +-- test/mocks/ECDSAStakeRegistryMock.sol | 9 +- test/unit/ECDSAServiceManager.t.sol | 121 ++++++-- .../ECDSAStakeRegistryEqualWeightUnit.t.sol | 29 +- .../ECDSAStakeRegistryPermissionedUnit.t.sol | 38 ++- test/unit/ECDSAStakeRegistryUnit.t.sol | 121 ++++++-- 14 files changed, 858 insertions(+), 154 deletions(-) create mode 100644 src/unaudited/ECDSAAVSRegistrar.sol diff --git a/src/interfaces/IECDSAStakeRegistry.sol b/src/interfaces/IECDSAStakeRegistry.sol index e17086f8..7b7dfeeb 100644 --- a/src/interfaces/IECDSAStakeRegistry.sol +++ b/src/interfaces/IECDSAStakeRegistry.sol @@ -37,6 +37,12 @@ interface IECDSAStakeRegistryErrors { error OperatorAlreadyRegistered(); /// @notice Thrown when de-registering or updating the stake for an unregisted operator. error OperatorNotRegistered(); + /// @notice Thrown when the sender is not the AVS Registrar. + error InvalidSender(); + /// @notice Thrown when the M2 quorum registration is disabled. + error M2QuorumRegistrationIsDisabled(); + /// @notice Thrown when the operator set ids are invalid. + error InvalidOperatorSetIdsLength(); } interface IECDSAStakeRegistryTypes { @@ -124,6 +130,11 @@ interface IECDSAStakeRegistryEvents is IECDSAStakeRegistryTypes { address indexed newSigningKey, address oldSigningKey ); + + /* + * @notice Emitted when the M2 quorum registration is disabled. + */ + event M2QuorumRegistrationDisabled(); } interface IECDSAStakeRegistry is @@ -138,7 +149,7 @@ interface IECDSAStakeRegistry is * @param operatorSignature Contains the operator's signature, salt, and expiry. * @param signingKey The signing key to add to the operator's history. */ - function registerOperatorWithSignature( + function registerOperatorM2Quorum( ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature, address signingKey ) external; @@ -146,7 +157,22 @@ interface IECDSAStakeRegistry is /* * @notice Deregisters an existing operator. */ - function deregisterOperator() external; + function deregisterOperatorM2Quorum() external; + + /* + * @notice called by the AVS Registrar when an operator is registered on the allocation manager. + * @param operator The address of the operator. + * @param signingKey The signing key of the operator. + */ + function onOperatorSetRegistered(address operator, address signingKey) external; + + /* + * @notice called by the AVS Registrar when an operator is deregistered on the allocation manager. + * @param operator The address of the operator. + */ + function onOperatorSetDeregistered( + address operator + ) external; /* * @notice Updates the signing key for an operator. @@ -199,6 +225,12 @@ interface IECDSAStakeRegistry is */ function quorum() external view returns (IECDSAStakeRegistryTypes.Quorum memory); + /* + * @notice Retrieves the current operator set id + * @return The current operator set id + */ + function getCurrentOperatorSetIds() external view returns (uint32[] memory); + /* * @notice Retrieves the latest signing key for a given operator. * @param operator The address of the operator. @@ -275,6 +307,42 @@ interface IECDSAStakeRegistry is address operator ) external view returns (uint256); + /* + * @notice Retrieves the operator's weight in the m2 quorum. + * @param operator The address of the operator. + * @return The operator's weight in the m2 quorum. + */ + function getQuorumWeight( + address operator + ) external view returns (uint256); + + /* + * @notice Retrieves the operator's weight in the current operator set. + * @param operator The address of the operator. + * @return The operator's weight in the current operator set. + */ + function getOperatorSetWeight( + address operator + ) external view returns (uint256); + + /* + * @notice Checks if an operator is registered on the AVS Directory(M2). + * @param operator The address of the operator to check. + * @return bool True if the operator is registered on the AVS Directory, false otherwise. + */ + function operatorRegisteredOnAVSDirectory( + address operator + ) external view returns (bool); + + /* + * @notice Checks if an operator is registered on the current operator set. + * @param operator The address of the operator to check. + * @return bool True if the operator is registered on the current operator set, false otherwise. + */ + function operatorRegisteredOnCurrentOperatorSets( + address operator + ) external view returns (bool); + /* * @notice Updates operators for a specific quorum. * @param operatorsPerQuorum Array of operator addresses per quorum. @@ -302,4 +370,4 @@ interface IECDSAStakeRegistry is function getLastCheckpointThresholdWeightAtBlock( uint32 blockNumber ) external view returns (uint256); -} +} \ No newline at end of file diff --git a/src/unaudited/ECDSAAVSRegistrar.sol b/src/unaudited/ECDSAAVSRegistrar.sol new file mode 100644 index 00000000..ce36aa08 --- /dev/null +++ b/src/unaudited/ECDSAAVSRegistrar.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +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 {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {IECDSAStakeRegistry} from "../interfaces/IECDSAStakeRegistry.sol"; +import {IServiceManager} from "../interfaces/IServiceManager.sol"; + +contract AVSRegistrar is IAVSRegistrar, Ownable { + IAllocationManager public allocationManager; + IECDSAStakeRegistry public immutable stakeRegistry; + address public immutable avs; + + error AVSRegistrar__OnlyAllocationManager(); + + modifier onlyAllocationManager() { + if (msg.sender != address(allocationManager)) { + revert AVSRegistrar__OnlyAllocationManager(); + } + _; + } + + constructor(address _allocationManager, address _stakeRegistry, address _avs) Ownable() { + allocationManager = IAllocationManager(_allocationManager); + stakeRegistry = IECDSAStakeRegistry(_stakeRegistry); + avs = _avs; + } + + function registerOperator( + address operator, + address avs, + uint32[] calldata operatorSetIds, + bytes calldata data + ) external onlyAllocationManager { + // Decode signing key + (address signingKey) = abi.decode(data, (address)); + + // Call stake registry to register operator + // Weight check will be done in ECDSAStakeRegistry + stakeRegistry.onOperatorSetRegistered(operator, signingKey); + } + + function deregisterOperator( + address operator, + address avs, + uint32[] calldata operatorSetIds + ) external onlyAllocationManager { + stakeRegistry.onOperatorSetDeregistered(operator); + } + + function updateAllocationManager( + address _allocationManager + ) external onlyOwner { + allocationManager = IAllocationManager(_allocationManager); + } + + function supportsAVS( + address avsAddr + ) external view returns (bool){ + return avs == avsAddr; + } +} diff --git a/src/unaudited/ECDSAServiceManagerBase.sol b/src/unaudited/ECDSAServiceManagerBase.sol index d5bf2c55..d2358442 100644 --- a/src/unaudited/ECDSAServiceManagerBase.sol +++ b/src/unaudited/ECDSAServiceManagerBase.sol @@ -17,8 +17,13 @@ import {IRewardsCoordinator} from import {IECDSAStakeRegistryTypes} from "../interfaces/IECDSAStakeRegistry.sol"; import {ECDSAStakeRegistry} from "../unaudited/ECDSAStakeRegistry.sol"; import {IAVSRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IAVSRegistrar.sol"; -import {IAllocationManager} from - "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {IPermissionController} from + "eigenlayer-contracts/src/contracts/interfaces/IPermissionController.sol"; +import { + IAllocationManager, + IAllocationManagerTypes +} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {OperatorSet} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol"; abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable { using SafeERC20 for IERC20; @@ -38,6 +43,9 @@ abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable /// @notice Address of the delegation manager contract, which manages staker delegations to operators. address internal immutable delegationManager; + /// @notice Address of the permission controller contract, which manages permissions for the service manager. + address internal immutable permissionController; + /// @notice Address of the rewards initiator, which is allowed to create AVS rewards submissions. address public rewardsInitiator; @@ -74,13 +82,15 @@ abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable address _stakeRegistry, address _rewardsCoordinator, address _delegationManager, - address _allocationManager + address _allocationManager, + address _permissionController ) { avsDirectory = _avsDirectory; stakeRegistry = _stakeRegistry; rewardsCoordinator = _rewardsCoordinator; delegationManager = _delegationManager; allocationManager = _allocationManager; + permissionController = _permissionController; _disableInitializers(); } @@ -139,6 +149,81 @@ abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable _deregisterOperatorFromAVS(operator); } + /// @notice Deregisters an operator from a set of operator sets. + /// @dev This function is used to deregister an operator from a set of operator sets. + /// @param operator The address of the operator to deregister. + /// @param operatorSetIds The set of operator set ids to deregister from. + function deregisterOperatorFromOperatorSets( + address operator, + uint32[] memory operatorSetIds + ) external virtual onlyStakeRegistry { + IAllocationManager.DeregisterParams memory params = IAllocationManagerTypes.DeregisterParams({ + operator: operator, + avs: address(this), + operatorSetIds: operatorSetIds + }); + IAllocationManager(allocationManager).deregisterFromOperatorSets(params); + } + + /// @inheritdoc IServiceManager + + function addPendingAdmin( + address admin + ) external virtual onlyOwner { + IPermissionController(permissionController).addPendingAdmin({ + account: address(this), + admin: admin + }); + } + + /// @inheritdoc IServiceManager + function removePendingAdmin( + address pendingAdmin + ) external virtual onlyOwner { + IPermissionController(permissionController).removePendingAdmin({ + account: address(this), + admin: pendingAdmin + }); + } + + /// @inheritdoc IServiceManager + function removeAdmin( + address admin + ) external virtual onlyOwner { + IPermissionController(permissionController).removeAdmin({ + account: address(this), + admin: admin + }); + } + + /// @inheritdoc IServiceManager + function setAppointee( + address appointee, + address target, + bytes4 selector + ) external virtual onlyOwner { + IPermissionController(permissionController).setAppointee({ + account: address(this), + appointee: appointee, + target: target, + selector: selector + }); + } + + /// @inheritdoc IServiceManager + function removeAppointee( + address appointee, + address target, + bytes4 selector + ) external virtual onlyOwner { + IPermissionController(permissionController).removeAppointee({ + account: address(this), + appointee: appointee, + target: target, + selector: selector + }); + } + /// @inheritdoc IServiceManagerUI function getRestakeableStrategies() external view virtual returns (address[] memory) { return _getRestakeableStrategies(); @@ -151,6 +236,16 @@ abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable return _getOperatorRestakedStrategies(_operator); } + /** + * @notice Returns the strategies from a specific operator set for this AVS + * @dev Uses AllocationManager to fetch strategies from a single operator set + * @param operatorSetId The ID of the operator set to query + * @return Array of strategy addresses from the specified operator set + */ + function getOperatorSetStrategies(uint32 operatorSetId) external view virtual returns (address[] memory) { + return _getOperatorSetStrategies(operatorSetId); + } + /** * @notice Forwards the call to update AVS metadata URI in the AVSDirectory contract. * @dev This internal function is a proxy to the `updateAVSMetadataURI` function of the AVSDirectory contract. @@ -263,6 +358,32 @@ abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable return strategies; } + /** + * @notice Retrieves the addresses of strategies from a specific operator set + * @dev Returns an empty array if the operator set is invalid + * @param operatorSetId The ID of the operator set to get strategies from + * @return Array of strategy addresses from the specified operator set + */ + function _getOperatorSetStrategies(uint32 operatorSetId) internal view virtual returns (address[] memory) { + OperatorSet memory operatorSet = OperatorSet(address(this), operatorSetId); + + // Return empty array if this is not a valid operator set + if (!IAllocationManager(allocationManager).isOperatorSet(operatorSet)) { + return new address[](0); + } + + // Get strategies for this operator set + IStrategy[] memory strategies = IAllocationManager(allocationManager).getStrategiesInOperatorSet(operatorSet); + + // Convert IStrategy array to address array + address[] memory strategyAddresses = new address[](strategies.length); + for (uint256 i = 0; i < strategies.length; i++) { + strategyAddresses[i] = address(strategies[i]); + } + + return strategyAddresses; + } + /** * @notice Sets the AVS registrar address in the AllocationManager * @param registrar The new AVS registrar address @@ -334,4 +455,4 @@ abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable // storage gap for upgradeability // slither-disable-next-line shadowing-state uint256[49] private __GAP; -} +} \ No newline at end of file diff --git a/src/unaudited/ECDSAStakeRegistry.sol b/src/unaudited/ECDSAStakeRegistry.sol index c961c83e..497b6937 100644 --- a/src/unaudited/ECDSAStakeRegistry.sol +++ b/src/unaudited/ECDSAStakeRegistry.sol @@ -19,6 +19,16 @@ import {SignatureCheckerUpgradeable} from "@openzeppelin-upgrades/contracts/utils/cryptography/SignatureCheckerUpgradeable.sol"; import {IERC1271Upgradeable} from "@openzeppelin-upgrades/contracts/interfaces/IERC1271Upgradeable.sol"; +import { + IAVSDirectoryTypes +} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; +import {IAllocationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {OperatorSet} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol"; + +interface IAVSDirectory { + function avsOperatorStatus(address avs, address operator) external view returns (IAVSDirectoryTypes.OperatorAVSRegistrationStatus); +} /// @title ECDSA Stake Registry /// @dev THIS CONTRACT IS NOT AUDITED @@ -34,11 +44,24 @@ contract ECDSAStakeRegistry is /// @dev Constructor to create ECDSAStakeRegistry. /// @param _delegationManager Address of the DelegationManager contract that this registry interacts with. constructor( - IDelegationManager _delegationManager - ) ECDSAStakeRegistryStorage(_delegationManager) { + IDelegationManager _delegationManager, + IAllocationManager _allocationManager, + address _avsRegistrar, + IAVSDirectory _avsDirectory + ) + ECDSAStakeRegistryStorage(_delegationManager, _allocationManager, _avsRegistrar, _avsDirectory) + { // _disableInitializers(); } + /// @notice Modifier to ensure the caller is the AVS Registrar. + modifier onlyAVSRegistrar() { + if (msg.sender != address(avsRegistrar)) { + revert InvalidSender(); + } + _; + } + /// @notice Initializes the contract with the given parameters. /// @param _serviceManager The address of the service manager. /// @param thresholdWeight The threshold weight in basis points. @@ -64,24 +87,70 @@ contract ECDSAStakeRegistry is __Ownable_init(); } + function disableM2QuorumRegistration() external onlyOwner { + if (isM2QuorumRegistrationDisabled) { + revert M2QuorumRegistrationIsDisabled(); + } + + isM2QuorumRegistrationDisabled = true; + emit M2QuorumRegistrationDisabled(); + } + /// @inheritdoc IECDSAStakeRegistry - function registerOperatorWithSignature( + function registerOperatorM2Quorum( ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature, address signingKey ) external { - _registerOperatorWithSig(msg.sender, operatorSignature, signingKey); + if (isM2QuorumRegistrationDisabled) { + revert M2QuorumRegistrationIsDisabled(); + } + if (operatorRegisteredOnAVSDirectory(msg.sender)) { + revert OperatorAlreadyRegistered(); + } + _registerOperatorM2Quorum(msg.sender, operatorSignature, signingKey); } /// @inheritdoc IECDSAStakeRegistry - function deregisterOperator() external { - _deregisterOperator(msg.sender); + function deregisterOperatorM2Quorum() external { + if (!operatorRegisteredOnAVSDirectory(msg.sender)) { + revert OperatorNotRegistered(); + } + + _deregisterOperatorM2Quorum(msg.sender); + } + + function onOperatorSetRegistered( + address operator, + address signingKey + ) external onlyAVSRegistrar { + // Update operator weight + _updateOperatorWeight(operator); + + // Update signing key and p2p key + _updateOperatorSigningKey(operator, signingKey); + + if (!operatorRegisteredOnAVSDirectory(operator)) { + emit OperatorRegistered(operator, _serviceManager); + } + } + + function onOperatorSetDeregistered( + address operator + ) external onlyAVSRegistrar { + // Update weights + _updateOperatorWeight(operator); + + // Emit event + if (!operatorRegisteredOnAVSDirectory(operator)) { + emit OperatorDeregistered(operator, _serviceManager); + } } /// @inheritdoc IECDSAStakeRegistry function updateOperatorSigningKey( address newSigningKey ) external { - if (!_operatorRegistered[msg.sender]) { + if (!operatorRegistered(msg.sender)) { revert OperatorNotRegistered(); } _updateOperatorSigningKey(msg.sender, newSigningKey); @@ -119,6 +188,33 @@ contract ECDSAStakeRegistry is _updateStakeThreshold(thresholdWeight); } + /// @notice Sets the current operator set ids + /// @param _ids The ids of the operator sets to set + function setCurrentOperatorSetIds( + uint32[] calldata _ids + ) external onlyOwner { + if (_ids.length == 0 || _ids.length > 10) { + revert InvalidOperatorSetIdsLength(); + } + currentOperatorSetIds = _ids; + } + + /// @notice Sets the allocation manager + /// @param _allocationManager The allocation manager to set + function setAllocationManager( + IAllocationManager _allocationManager + ) external onlyOwner { + allocationManager = _allocationManager; + } + + /// @notice Sets the AVS Registrar + /// @param _avsRegistrar The AVS Registrar to set + function setAVSRegistrar( + address _avsRegistrar + ) external onlyOwner { + avsRegistrar = _avsRegistrar; + } + function isValidSignature( bytes32 digest, bytes memory _signatureData @@ -134,6 +230,12 @@ contract ECDSAStakeRegistry is return _quorum; } + /// @notice Gets the current operator set ids + /// @return The current operator set ids + function getCurrentOperatorSetIds() external view returns (uint32[] memory) { + return currentOperatorSetIds; + } + /// @inheritdoc IECDSAStakeRegistry function getLatestOperatorSigningKey( address operator @@ -188,20 +290,29 @@ contract ECDSAStakeRegistry is return _thresholdWeightHistory.getAtBlock(blockNumber); } - /// @inheritdoc IECDSAStakeRegistry - function operatorRegistered( - address operator - ) external view returns (bool) { - return _operatorRegistered[operator]; - } - /// @inheritdoc IECDSAStakeRegistry function minimumWeight() external view returns (uint256) { return _minimumWeight; } - /// @inheritdoc IECDSAStakeRegistry + /// @notice Calculates an operator's current weight based on their delegated stake + /// @param _operator Address of the operator to calculate weight for + /// @return Current weight of the operator (0 if below minimum threshold) + /// @dev Queries mainnet delegation manager for current shares function getOperatorWeight( + address _operator + ) public view returns (uint256) { + uint256 quorumWeight = getQuorumWeight(_operator); + // uint256 operatorSetWeight = getOperatorSetWeight(_operator); + + // return quorumWeight + operatorSetWeight; + return quorumWeight; + } + + /// @notice Calculates operator's weight in the quorum + /// @param operator The operator address to calculate weight for + /// @return The operator's weight in quorum, or 0 if below minimum + function getQuorumWeight( address operator ) public view returns (uint256) { StrategyParams[] memory strategyParams = _quorum.strategies; @@ -223,6 +334,73 @@ contract ECDSAStakeRegistry is } } + /// @notice Calculates operator's available weight in current operator set + /// @dev Weight calculation: + /// 1. Check operator set membership + /// 2. Get shares and allocation for each strategy + /// 3. Calculate available proportion (currentMagnitude/maxMagnitude) + /// 4. Sum up available shares weighted by proportion + /// @param operator The operator address to calculate weight for + /// @return The operator's available weight in set, or 0 if below minimum + function getOperatorSetWeight( + address operator + ) public view returns (uint256) { + // Return 0 if allocation manager not set + if (address(allocationManager) == address(0)) { + return 0; + } + + uint256 totalWeight; + + // Loop through all operator sets + for (uint256 setIndex = 0; setIndex < currentOperatorSetIds.length; setIndex++) { + // Create operator set struct for current id + OperatorSet memory operatorSet = + OperatorSet({avs: address(_serviceManager), id: currentOperatorSetIds[setIndex]}); + + // Check operator set membership + if (!allocationManager.isMemberOfOperatorSet(operator, operatorSet)) { + continue; + } + + // Get strategies from operator set + IStrategy[] memory strategies = + allocationManager.getStrategiesInOperatorSet(operatorSet); + if (strategies.length == 0) { + continue; + } + + // Get operator's shares for all strategies + uint256[] memory shares = DELEGATION_MANAGER.getOperatorShares(operator, strategies); + + // Calculate available weight for each strategy + for (uint256 i = 0; i < strategies.length; i++) { + // Get allocation and max magnitude + IAllocationManager.Allocation memory allocation = + allocationManager.getAllocation(operator, operatorSet, strategies[i]); + uint64 maxMagnitude = allocationManager.getMaxMagnitude(operator, strategies[i]); + + if (maxMagnitude == 0) { + continue; + } + + // Calculate available proportion + uint256 slashableProportion = + uint256(allocation.currentMagnitude) * WAD / maxMagnitude; + + // Add weighted shares to total + totalWeight += shares[i] * slashableProportion / WAD; + } + } + + // Return 0 if below minimum weight + if (totalWeight >= _minimumWeight) { + return totalWeight; + } else { + return 0; + } + } + /// @inheritdoc IECDSAStakeRegistry function updateOperatorsForQuorum( address[][] memory operatorsPerQuorum, @@ -231,6 +409,47 @@ contract ECDSAStakeRegistry is _updateAllOperators(operatorsPerQuorum[0]); } + /// @notice Checks if an operator is registered on the AVS Directory(M2). + /// @param operator The address of the operator to check. + /// @return bool True if the operator is registered on the AVS Directory, false otherwise. + function operatorRegisteredOnAVSDirectory( + address operator + ) public view returns (bool) { + return AVS_DIRECTORY.avsOperatorStatus(_serviceManager, operator) + == IAVSDirectoryTypes.OperatorAVSRegistrationStatus.REGISTERED; + } + + /// @notice Checks if an operator is registered on the current operator set. + /// @param operator The address of the operator to check. + /// @return bool True if the operator is registered on the current operator set, false otherwise. + function operatorRegisteredOnCurrentOperatorSets( + address operator + ) public view returns (bool) { + if (address(allocationManager) == address(0)) { + return false; + } + + // Check if operator is registered in any current set + for (uint256 i = 0; i < currentOperatorSetIds.length; i++) { + OperatorSet memory operatorSet = + OperatorSet({avs: address(_serviceManager), id: currentOperatorSetIds[i]}); + if (allocationManager.isMemberOfOperatorSet(operator, operatorSet)) { + return true; + } + } + return false; + } + + /// @notice Checks if an operator is registered on the AVS Directory or the current operator set. + /// @param operator The address of the operator to check. + /// @return bool True if the operator is registered on the AVS Directory or the current operator set, false otherwise. + function operatorRegistered( + address operator + ) public view returns (bool) { + return operatorRegisteredOnAVSDirectory(operator) + || operatorRegisteredOnCurrentOperatorSets(operator); + } + /// @dev Updates the list of operators if the provided list has the correct number of operators. /// Reverts if the provided list of operators does not match the expected total count of operators. /// @param operators The list of operator addresses to update. @@ -296,38 +515,53 @@ contract ECDSAStakeRegistry is /// @dev Internal function to deregister an operator /// @param operator The operator's address to deregister - function _deregisterOperator( + function _deregisterOperatorM2Quorum( address operator ) internal { - if (!_operatorRegistered[operator]) { + if (!operatorRegisteredOnAVSDirectory(operator)) { revert OperatorNotRegistered(); } - _totalOperators--; - delete _operatorRegistered[operator]; + int256 delta = _updateOperatorWeight(operator); _updateTotalWeight(delta); IServiceManager(_serviceManager).deregisterOperatorFromAVS(operator); - emit OperatorDeregistered(operator, address(_serviceManager)); + if (!operatorRegisteredOnCurrentOperatorSets(operator)) { + _totalOperators--; + emit OperatorDeregistered(operator, address(_serviceManager)); + } } /// @dev registers an operator through a provided signature /// @param operatorSignature Contains the operator's signature, salt, and expiry /// @param signingKey The signing key to add to the operator's history - function _registerOperatorWithSig( + function _registerOperatorM2Quorum( address operator, ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature, address signingKey ) internal virtual { - if (_operatorRegistered[operator]) { + if (operatorRegisteredOnAVSDirectory(operator)) { revert OperatorAlreadyRegistered(); } - _totalOperators++; - _operatorRegistered[operator] = true; + int256 delta = _updateOperatorWeight(operator); _updateTotalWeight(delta); _updateOperatorSigningKey(operator, signingKey); IServiceManager(_serviceManager).registerOperatorToAVS(operator, operatorSignature); - emit OperatorRegistered(operator, _serviceManager); + if (!operatorRegisteredOnCurrentOperatorSets(operator)) { + _totalOperators++; + emit OperatorRegistered(operator, _serviceManager); + } + } + + /// @notice Deregisters an operator from a set of operator sets. + /// @dev This function is used to deregister an operator from a set of operator sets. + /// @param operator The address of the operator to deregister. + function _deregisterOperatorFromOperatorSets( + address operator + ) internal virtual { + IServiceManager(_serviceManager).deregisterOperatorFromOperatorSets( + operator, currentOperatorSetIds + ); } /// @dev Internal function to update an operator's signing key @@ -350,7 +584,7 @@ contract ECDSAStakeRegistry is int256 delta; uint256 newWeight; uint256 oldWeight = _operatorWeightHistory[operator].latest(); - if (!_operatorRegistered[operator]) { + if (!operatorRegistered(operator)) { delta -= int256(oldWeight); if (delta == 0) { return delta; @@ -549,4 +783,4 @@ contract ECDSAStakeRegistry is revert InsufficientSignedStake(); } } -} +} \ No newline at end of file diff --git a/src/unaudited/ECDSAStakeRegistryStorage.sol b/src/unaudited/ECDSAStakeRegistryStorage.sol index 8d9d69d8..d7573107 100644 --- a/src/unaudited/ECDSAStakeRegistryStorage.sol +++ b/src/unaudited/ECDSAStakeRegistryStorage.sol @@ -8,11 +8,33 @@ import {CheckpointsUpgradeable} from import { IECDSAStakeRegistry, IECDSAStakeRegistryTypes } from "../interfaces/IECDSAStakeRegistry.sol"; - +import {IAllocationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {IAVSRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IAVSRegistrar.sol"; +import {IAVSDirectoryTypes} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; +import {IAVSDirectory} from "./ECDSAStakeRegistry.sol"; abstract contract ECDSAStakeRegistryStorage is IECDSAStakeRegistry { /// @notice Manages staking delegations through the DelegationManager interface IDelegationManager internal immutable DELEGATION_MANAGER; + /// @notice The AVS Directory contract + IAVSDirectory internal immutable AVS_DIRECTORY; + + /// @notice Manages staking delegations through the DelegationManager interface + IAllocationManager internal allocationManager; + + /// @notice The address of the AVS Registrar of the AVS + address internal avsRegistrar; + + /// @notice Whether M2 quorum registration is disabled + bool public isM2QuorumRegistrationDisabled; + + /// @notice The current operator set ids + uint32[] public currentOperatorSetIds; + + /// @notice The total amount of multipliers to weigh stakes + uint256 public constant WAD = 1e18; + /// @dev The total amount of multipliers to weigh stakes uint256 internal constant BPS = 10000; @@ -28,9 +50,6 @@ abstract contract ECDSAStakeRegistryStorage is IECDSAStakeRegistry { /// @notice Holds the address of the service manager address internal _serviceManager; - /// @notice Defines the duration after which the stake's weight expires. - uint256 internal _stakeExpiry; - /// @notice Maps an operator to their signing key history using checkpoints mapping(address => CheckpointsUpgradeable.History) internal _operatorSigningKeyHistory; @@ -43,18 +62,21 @@ abstract contract ECDSAStakeRegistryStorage is IECDSAStakeRegistry { /// @notice Maps operator addresses to their respective stake histories using checkpoints mapping(address => CheckpointsUpgradeable.History) internal _operatorWeightHistory; - /// @notice Maps an operator to their registration status - mapping(address => bool) internal _operatorRegistered; - /// @param _delegationManager Connects this registry with the DelegationManager constructor( - IDelegationManager _delegationManager + IDelegationManager _delegationManager, + IAllocationManager _allocationManager, + address _avsRegistrar, + IAVSDirectory _avsDirectory ) { DELEGATION_MANAGER = _delegationManager; + allocationManager = _allocationManager; + avsRegistrar = _avsRegistrar; + AVS_DIRECTORY = _avsDirectory; } // slither-disable-next-line shadowing-state /// @dev Reserves storage slots for future upgrades // solhint-disable-next-line uint256[40] private __gap; -} +} \ No newline at end of file diff --git a/src/unaudited/examples/ECDSAStakeRegistryEqualWeight.sol b/src/unaudited/examples/ECDSAStakeRegistryEqualWeight.sol index 3edd6278..adfc1dab 100644 --- a/src/unaudited/examples/ECDSAStakeRegistryEqualWeight.sol +++ b/src/unaudited/examples/ECDSAStakeRegistryEqualWeight.sol @@ -7,6 +7,9 @@ import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; import {CheckpointsUpgradeable} from "@openzeppelin-upgrades/contracts/utils/CheckpointsUpgradeable.sol"; +import {IAllocationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {IAVSDirectory} from "../ECDSAStakeRegistry.sol"; /// @title ECDSA Stake Registry with Equal Weight /// @dev THIS CONTRACT IS NOT AUDITED @@ -18,8 +21,18 @@ contract ECDSAStakeRegistryEqualWeight is ECDSAStakeRegistryPermissioned { /// @dev Passes the delegation manager to the parent constructor. /// @param _delegationManager The address of the delegation manager contract. constructor( - IDelegationManager _delegationManager - ) ECDSAStakeRegistryPermissioned(_delegationManager) { + IDelegationManager _delegationManager, + IAllocationManager _allocationManager, + address _avsRegistrar, + IAVSDirectory _avsDirectory + ) + ECDSAStakeRegistryPermissioned( + _delegationManager, + _allocationManager, + _avsRegistrar, + _avsDirectory + ) + { // _disableInitializers(); } @@ -33,7 +46,7 @@ contract ECDSAStakeRegistryEqualWeight is ECDSAStakeRegistryPermissioned { uint256 oldWeight; uint256 newWeight; int256 delta; - if (_operatorRegistered[_operator]) { + if (operatorRegistered(_operator)) { (oldWeight,) = _operatorWeightHistory[_operator].push(1); delta = int256(1) - int256(oldWeight); // handles if they were already registered } else { @@ -43,4 +56,4 @@ contract ECDSAStakeRegistryEqualWeight is ECDSAStakeRegistryPermissioned { emit OperatorWeightUpdated(_operator, oldWeight, newWeight); return delta; } -} +} \ No newline at end of file diff --git a/src/unaudited/examples/ECDSAStakeRegistryPermissioned.sol b/src/unaudited/examples/ECDSAStakeRegistryPermissioned.sol index f6afffdf..6d61d0fe 100644 --- a/src/unaudited/examples/ECDSAStakeRegistryPermissioned.sol +++ b/src/unaudited/examples/ECDSAStakeRegistryPermissioned.sol @@ -1,3 +1,4 @@ + // SPDX-License-Identifier: MIT pragma solidity ^0.8.27; @@ -5,6 +6,12 @@ import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISi import {ECDSAStakeRegistry} from "../ECDSAStakeRegistry.sol"; import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; +import {IAllocationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {IAVSDirectory} from + "../ECDSAStakeRegistry.sol"; +import {IAVSDirectoryTypes} from + "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; /// @title ECDSA Stake Registry with an Operator Allowlist /// @dev THIS CONTRACT IS NOT AUDITED @@ -29,8 +36,11 @@ contract ECDSAStakeRegistryPermissioned is ECDSAStakeRegistry { error OperatorAlreadyAllowlisted(); constructor( - IDelegationManager _delegationManager - ) ECDSAStakeRegistry(_delegationManager) { + IDelegationManager _delegationManager, + IAllocationManager _allocationManager, + address _avsRegistrar, + IAVSDirectory _avsDirectory + ) ECDSAStakeRegistry(_delegationManager, _allocationManager, _avsRegistrar, _avsDirectory) { // _disableInitializers(); } @@ -66,7 +76,12 @@ contract ECDSAStakeRegistryPermissioned is ECDSAStakeRegistry { function _ejectOperator( address _operator ) internal { - _deregisterOperator(_operator); + if (operatorRegisteredOnAVSDirectory(_operator)) { + _deregisterOperatorM2Quorum(_operator); + } + if (operatorRegisteredOnCurrentOperatorSets(_operator)) { + _deregisterOperatorFromOperatorSets(_operator); + } emit OperatorEjected(_operator); } @@ -94,13 +109,11 @@ contract ECDSAStakeRegistryPermissioned is ECDSAStakeRegistry { } delete allowlistedOperators[_operator]; emit OperatorRevoked(_operator); - if (_operatorRegistered[_operator]) { - _ejectOperator(_operator); - } + _ejectOperator(_operator); } /// @inheritdoc ECDSAStakeRegistry - function _registerOperatorWithSig( + function _registerOperatorM2Quorum( address _operator, ISignatureUtils.SignatureWithSaltAndExpiry memory _operatorSignature, address _operatorSigningKey @@ -108,6 +121,6 @@ contract ECDSAStakeRegistryPermissioned is ECDSAStakeRegistry { if (allowlistedOperators[_operator] != true) { revert OperatorNotAllowlisted(); } - super._registerOperatorWithSig(_operator, _operatorSignature, _operatorSigningKey); + super._registerOperatorM2Quorum(_operator, _operatorSignature, _operatorSigningKey); } } diff --git a/test/mocks/AVSDirectoryMock.sol b/test/mocks/AVSDirectoryMock.sol index d76f1abf..cc745507 100644 --- a/test/mocks/AVSDirectoryMock.sol +++ b/test/mocks/AVSDirectoryMock.sol @@ -151,4 +151,9 @@ contract AVSDirectoryMock is IAVSDirectory { function isOperatorSetBatch( OperatorSet[] calldata operatorSets ) external view returns (bool) {} + + function avsOperatorStatus( + address avs, + address operator + ) external view returns (OperatorAVSRegistrationStatus) {} } diff --git a/test/mocks/ECDSAServiceManagerMock.sol b/test/mocks/ECDSAServiceManagerMock.sol index 07a263cf..a05f2232 100644 --- a/test/mocks/ECDSAServiceManagerMock.sol +++ b/test/mocks/ECDSAServiceManagerMock.sol @@ -11,42 +11,30 @@ contract ECDSAServiceManagerMock is ECDSAServiceManagerBase { address _stakeRegistry, address _rewardsCoordinator, address _delegationManager, - address _allocationManager + address _allocationManager, + address _permissionController, + address _initialOwner, + address _rewardsInitiator ) ECDSAServiceManagerBase( _avsDirectory, _stakeRegistry, _rewardsCoordinator, _delegationManager, - _allocationManager + _allocationManager, + _permissionController ) - {} - - function initialize( - address initialOwner, - address rewardsInitiator - ) public virtual initializer { - __ServiceManagerBase_init(initialOwner, rewardsInitiator); + { + // disable initializer and directly set owner and rewardsInitiator for testing + _transferOwnership(_initialOwner); + _setRewardsInitiator(_rewardsInitiator); } - function addPendingAdmin( - address admin - ) external {} - - function removePendingAdmin( - address pendingAdmin - ) external {} - - function deregisterOperatorFromOperatorSets( - address operator, - uint32[] memory operatorSetIds - ) external {} - - function removeAdmin( - address admin - ) external {} - - function setAppointee(address appointee, address target, bytes4 selector) external {} + // function initialize( + // address initialOwner, + // address rewardsInitiator + // ) public virtual initializer { + // __ServiceManagerBase_init(initialOwner, rewardsInitiator); + // } - function removeAppointee(address appointee, address target, bytes4 selector) external {} } diff --git a/test/mocks/ECDSAStakeRegistryMock.sol b/test/mocks/ECDSAStakeRegistryMock.sol index fb43079b..da670557 100644 --- a/test/mocks/ECDSAStakeRegistryMock.sol +++ b/test/mocks/ECDSAStakeRegistryMock.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.27; import "../../src/unaudited/ECDSAStakeRegistry.sol"; +import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol"; /** * @title Mock for ECDSAStakeRegistry @@ -9,6 +10,10 @@ import "../../src/unaudited/ECDSAStakeRegistry.sol"; */ contract ECDSAStakeRegistryMock is ECDSAStakeRegistry { constructor( - IDelegationManager _delegationManager - ) ECDSAStakeRegistry(_delegationManager) {} + IDelegationManager _delegationManager, + IAllocationManager _allocationManager, + address _avsRegistrar, + IAVSDirectory _avsDirectory + ) ECDSAStakeRegistry(_delegationManager, _allocationManager, _avsRegistrar, _avsDirectory) {} + } diff --git a/test/unit/ECDSAServiceManager.t.sol b/test/unit/ECDSAServiceManager.t.sol index 5afc891e..2282f96a 100644 --- a/test/unit/ECDSAServiceManager.t.sol +++ b/test/unit/ECDSAServiceManager.t.sol @@ -13,7 +13,22 @@ import {IAVSRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IAVSR import {ECDSAServiceManagerMock} from "../mocks/ECDSAServiceManagerMock.sol"; import {ECDSAStakeRegistryMock} from "../mocks/ECDSAStakeRegistryMock.sol"; +import {AVSDirectoryMock} from "../mocks/AVSDirectoryMock.sol"; import {IECDSAStakeRegistryTypes} from "../../src/interfaces/IECDSAStakeRegistry.sol"; +import {IPermissionController} from + "eigenlayer-contracts/src/contracts/interfaces/IPermissionController.sol"; +import {IAllocationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {IAVSDirectory, IAVSDirectoryTypes} from "../../src/unaudited/ECDSAStakeRegistry.sol"; +import {OperatorSet} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol"; + +contract MockPermissionController { + function addPendingAdmin(address account, address admin) external {} + function removePendingAdmin(address account, address admin) external {} + function removeAdmin(address account, address admin) external {} + function setAppointee(address account, address appointee, address target, bytes4 selector) external {} + function removeAppointee(address account, address appointee, address target, bytes4 selector) external {} +} contract MockDelegationManager { function operatorShares(address, address) external pure returns (uint256) { @@ -32,23 +47,19 @@ contract MockDelegationManager { } } -contract MockAVSDirectory { - function registerOperatorToAVS( - address, - ISignatureUtils.SignatureWithSaltAndExpiry memory - ) external pure {} - - function deregisterOperatorFromAVS( - address - ) external pure {} - - function updateAVSMetadataURI( - string memory - ) external pure {} -} - contract MockAllocationManager { function setAVSRegistrar(address avs, address registrar) external {} + + function isOperatorSet(OperatorSet memory operatorSet) external pure returns (bool) { + return true; + } + + function getStrategiesInOperatorSet(OperatorSet memory operatorSet) external pure returns (IStrategy[] memory) { + IStrategy[] memory strategies = new IStrategy[](2); + strategies[0] = IStrategy(address(900)); + strategies[1] = IStrategy(address(901)); + return strategies; + } } contract MockRewardsCoordinator { @@ -69,11 +80,15 @@ contract MockRewardsCoordinator { contract ECDSAServiceManagerSetup is Test { MockDelegationManager public mockDelegationManager; - MockAVSDirectory public mockAVSDirectory; + AVSDirectoryMock public mockAVSDirectory; MockAllocationManager public mockAllocationManager; ECDSAStakeRegistryMock public mockStakeRegistry; MockRewardsCoordinator public mockRewardsCoordinator; ECDSAServiceManagerMock public serviceManager; + MockPermissionController public mockPermissionController; + address public mockAVSRegistrarAddr; + address public owner = makeAddr("owner"); + address public rewardsInitiator = makeAddr("rewardsInitiator"); address internal operator1; address internal operator2; uint256 internal operator1Pk; @@ -81,18 +96,29 @@ contract ECDSAServiceManagerSetup is Test { function setUp() public { mockDelegationManager = new MockDelegationManager(); - mockAVSDirectory = new MockAVSDirectory(); + mockAVSDirectory = new AVSDirectoryMock(); mockAllocationManager = new MockAllocationManager(); + mockAVSRegistrarAddr = makeAddr("mockAVSRegistrar"); mockStakeRegistry = - new ECDSAStakeRegistryMock(IDelegationManager(address(mockDelegationManager))); + new ECDSAStakeRegistryMock( + IDelegationManager(address(mockDelegationManager)), + IAllocationManager(address(mockAllocationManager)), + mockAVSRegistrarAddr, + IAVSDirectory(address(mockAVSDirectory)) + ); mockRewardsCoordinator = new MockRewardsCoordinator(); + mockPermissionController = new MockPermissionController(); + serviceManager = new ECDSAServiceManagerMock( address(mockAVSDirectory), address(mockStakeRegistry), address(mockRewardsCoordinator), address(mockDelegationManager), - address(mockAllocationManager) + address(mockAllocationManager), + address(mockPermissionController), + owner, + rewardsInitiator ); operator1Pk = 1; @@ -114,7 +140,7 @@ contract ECDSAServiceManagerSetup is Test { }); address[] memory operators = new address[](0); - vm.prank(mockStakeRegistry.owner()); + vm.prank(owner); mockStakeRegistry.initialize( address(serviceManager), 10000, // Assuming a threshold weight of 10000 basis points @@ -123,10 +149,10 @@ contract ECDSAServiceManagerSetup is Test { ISignatureUtils.SignatureWithSaltAndExpiry memory dummySignature; vm.prank(operator1); - mockStakeRegistry.registerOperatorWithSignature(dummySignature, operator1); + mockStakeRegistry.registerOperatorM2Quorum(dummySignature, operator1); vm.prank(operator2); - mockStakeRegistry.registerOperatorWithSignature(dummySignature, operator2); + mockStakeRegistry.registerOperatorM2Quorum(dummySignature, operator2); } function testRegisterOperatorToAVS() public { @@ -204,7 +230,7 @@ contract ECDSAServiceManagerSetup is Test { function testSetClaimerFor() public { address claimer = address(0x123); - vm.prank(mockStakeRegistry.owner()); + vm.prank(owner); serviceManager.setClaimerFor(claimer); } @@ -214,4 +240,53 @@ contract ECDSAServiceManagerSetup is Test { vm.prank(mockStakeRegistry.owner()); serviceManager.setAVSRegistrar(IAVSRegistrar(registrar)); } + + function testGetOperatorSetStrategies() public { + uint32 operatorSetId = 1; + + address[] memory strategies = serviceManager.getOperatorSetStrategies(operatorSetId); + + assertEq(strategies.length, 2, "Should return 2 strategies"); + assertEq(strategies[0], address(900), "First strategy should match"); + assertEq(strategies[1], address(901), "Second strategy should match"); + } + + function testAddPendingAdmin() public { + address admin = makeAddr("admin"); + + vm.prank(owner); + serviceManager.addPendingAdmin(admin); + } + + function testRemovePendingAdmin() public { + address pendingAdmin = makeAddr("pendingAdmin"); + + vm.prank(owner); + serviceManager.removePendingAdmin(pendingAdmin); + } + + function testRemoveAdmin() public { + address admin = makeAddr("admin"); + + vm.prank(owner); + serviceManager.removeAdmin(admin); + } + + function testSetAppointee() public { + address appointee = makeAddr("appointee"); + address target = makeAddr("target"); + bytes4 selector = bytes4(keccak256("someFunction()")); + + vm.prank(owner); + serviceManager.setAppointee(appointee, target, selector); + } + + function testRemoveAppointee() public { + address appointee = makeAddr("appointee"); + address target = makeAddr("target"); + bytes4 selector = bytes4(keccak256("someFunction()")); + + vm.prank(owner); + serviceManager.removeAppointee(appointee, target, selector); + } } diff --git a/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol b/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol index befc76c4..bf226960 100644 --- a/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol +++ b/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol @@ -5,14 +5,17 @@ import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISi import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; +import {IAllocationManager} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; import { IECDSAStakeRegistry, - IECDSAStakeRegistryTypes + IECDSAStakeRegistryTypes, + IECDSAStakeRegistryErrors } from "../../src/interfaces/IECDSAStakeRegistry.sol"; import {ECDSAStakeRegistrySetup} from "./ECDSAStakeRegistryUnit.t.sol"; import {ECDSAStakeRegistryEqualWeight} from "../../src/unaudited/examples/ECDSAStakeRegistryEqualWeight.sol"; +import {IAVSDirectory} from "../../src/unaudited/ECDSAStakeRegistry.sol"; contract EqualWeightECDSARegistry is ECDSAStakeRegistrySetup { ECDSAStakeRegistryEqualWeight internal fixedWeightRegistry; @@ -20,20 +23,30 @@ contract EqualWeightECDSARegistry is ECDSAStakeRegistrySetup { function setUp() public virtual override { super.setUp(); fixedWeightRegistry = - new ECDSAStakeRegistryEqualWeight(IDelegationManager(address(mockDelegationManager))); + new ECDSAStakeRegistryEqualWeight( + IDelegationManager(address(mockDelegationManager)), + IAllocationManager(address(mockAllocationManager)), + mockAVSRegistrarAddr, + IAVSDirectory(address(mockAVSDirectory)) + ); + IStrategy mockStrategy = IStrategy(address(0x1234)); IECDSAStakeRegistryTypes.Quorum memory quorum = - IECDSAStakeRegistryTypes.Quorum({strategies: new StrategyParams[](1)}); - quorum.strategies[0] = StrategyParams({strategy: mockStrategy, multiplier: 10000}); + IECDSAStakeRegistryTypes.Quorum({strategies: new IECDSAStakeRegistryTypes.StrategyParams[](1)}); + quorum.strategies[0] = IECDSAStakeRegistryTypes.StrategyParams({strategy: mockStrategy, multiplier: 10000}); + fixedWeightRegistry.initialize(address(mockServiceManager), 100, quorum); fixedWeightRegistry.permitOperator(operator1); fixedWeightRegistry.permitOperator(operator2); + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; + vm.prank(operator1); - fixedWeightRegistry.registerOperatorWithSignature(operatorSignature, operator1); + fixedWeightRegistry.registerOperatorM2Quorum(operatorSignature, operator1); + vm.prank(operator2); - fixedWeightRegistry.registerOperatorWithSignature(operatorSignature, operator2); + fixedWeightRegistry.registerOperatorM2Quorum(operatorSignature, operator2); } function test_FixedStakeUpdates() public { @@ -43,7 +56,7 @@ contract EqualWeightECDSARegistry is ECDSAStakeRegistrySetup { vm.roll(block.number + 1); vm.prank(operator1); - fixedWeightRegistry.deregisterOperator(); + fixedWeightRegistry.deregisterOperatorM2Quorum(); assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator1), 0); assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator2), 1); @@ -52,7 +65,7 @@ contract EqualWeightECDSARegistry is ECDSAStakeRegistrySetup { vm.roll(block.number + 1); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; vm.prank(operator1); - fixedWeightRegistry.registerOperatorWithSignature(operatorSignature, operator1); + fixedWeightRegistry.registerOperatorM2Quorum(operatorSignature, operator1); assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator1), 1); assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator2), 1); diff --git a/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol b/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol index 7269e1ce..c77010d3 100644 --- a/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol +++ b/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol @@ -5,6 +5,7 @@ import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISi import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; +import {IAllocationManager} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; import { IECDSAStakeRegistry, @@ -14,6 +15,7 @@ import { import {ECDSAStakeRegistrySetup} from "./ECDSAStakeRegistryUnit.t.sol"; import {ECDSAStakeRegistryPermissioned} from "../../src/unaudited/examples/ECDSAStakeRegistryPermissioned.sol"; +import {IAVSDirectory} from "../../src/unaudited/ECDSAStakeRegistry.sol"; contract PermissionedECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { ECDSAStakeRegistryPermissioned internal permissionedRegistry; @@ -21,20 +23,32 @@ contract PermissionedECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { function setUp() public virtual override { super.setUp(); permissionedRegistry = - new ECDSAStakeRegistryPermissioned(IDelegationManager(address(mockDelegationManager))); + new ECDSAStakeRegistryPermissioned( + IDelegationManager(address(mockDelegationManager)), + IAllocationManager(address(mockAllocationManager)), + mockAVSRegistrarAddr, + IAVSDirectory(address(mockAVSDirectory)) + ); + IStrategy mockStrategy = IStrategy(address(0x1234)); IECDSAStakeRegistryTypes.Quorum memory quorum = - IECDSAStakeRegistryTypes.Quorum({strategies: new StrategyParams[](1)}); - quorum.strategies[0] = StrategyParams({strategy: mockStrategy, multiplier: 10000}); + IECDSAStakeRegistryTypes.Quorum({strategies: new IECDSAStakeRegistryTypes.StrategyParams[](1)}); + quorum.strategies[0] = IECDSAStakeRegistryTypes.StrategyParams({strategy: mockStrategy, multiplier: 10000}); + permissionedRegistry.initialize(address(mockServiceManager), 100, quorum); permissionedRegistry.permitOperator(operator1); permissionedRegistry.permitOperator(operator2); + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; + vm.prank(operator1); - permissionedRegistry.registerOperatorWithSignature(operatorSignature, operator1); + permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator1); + vm.prank(operator2); - permissionedRegistry.registerOperatorWithSignature(operatorSignature, operator1); + permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator1); + + vm.roll(block.number + 1); } function test_RevertsWhen_NotOwner_PermitOperator() public { @@ -86,7 +100,7 @@ contract PermissionedECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { permissionedRegistry.ejectOperator(operator1); } - function test_RevertsWhen_NotAllowlisted_RegisterOperatorWithSig() public { + function test_RevertsWhen_NotAllowlisted_RegisterOperatorM2Quorum() public { address operator3 = address(0xBEEF); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; @@ -94,25 +108,25 @@ contract PermissionedECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { abi.encodeWithSelector(ECDSAStakeRegistryPermissioned.OperatorNotAllowlisted.selector) ); vm.prank(operator3); - permissionedRegistry.registerOperatorWithSignature(operatorSignature, operator3); + permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator3); } - function test_WhenAllowlisted_RegisterOperatorWithSig() public { + function test_WhenAllowlisted_RegisterOperatorM2Quorum() public { address operator3 = address(0xBEEF); permissionedRegistry.permitOperator(operator3); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; vm.prank(operator3); - permissionedRegistry.registerOperatorWithSignature(operatorSignature, operator3); + permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator3); } - function test_DeregisterOperator() public { + function test_DeregisterOperatorM2Quorum() public { address operator3 = address(0xBEEF); permissionedRegistry.permitOperator(operator3); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; vm.prank(operator3); - permissionedRegistry.registerOperatorWithSignature(operatorSignature, operator3); + permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator3); vm.prank(operator3); - permissionedRegistry.deregisterOperator(); + permissionedRegistry.deregisterOperatorM2Quorum(); } } diff --git a/test/unit/ECDSAStakeRegistryUnit.t.sol b/test/unit/ECDSAStakeRegistryUnit.t.sol index 540f8dcc..d2481f6b 100644 --- a/test/unit/ECDSAStakeRegistryUnit.t.sol +++ b/test/unit/ECDSAStakeRegistryUnit.t.sol @@ -15,6 +15,9 @@ import { IECDSAStakeRegistryTypes, IECDSAStakeRegistryEvents } from "../../src/interfaces/IECDSAStakeRegistry.sol"; +import {IAllocationManager} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {IAVSDirectory, IAVSDirectoryTypes} from "../../src/unaudited/ECDSAStakeRegistry.sol"; + contract MockServiceManager { // solhint-disable-next-line @@ -45,8 +48,54 @@ contract MockDelegationManager { } } +contract MockAVSDirectory { + mapping(address => mapping(address => IAVSDirectoryTypes.OperatorAVSRegistrationStatus)) + private operatorStatus; + + function registerOperatorToAVS( + address, + ISignatureUtils.SignatureWithSaltAndExpiry memory + ) external pure {} + + function deregisterOperatorFromAVS( + address + ) external pure {} + + function updateAVSMetadataURI( + string memory + ) external pure {} + function setAvsOperatorStatus( + address avs, + address operator, + IAVSDirectoryTypes.OperatorAVSRegistrationStatus status + ) external { + operatorStatus[avs][operator] = status; + } + + function avsOperatorStatus(address avs, address operator) external view returns (IAVSDirectoryTypes.OperatorAVSRegistrationStatus) { + return operatorStatus[avs][operator]; + } +} + +contract MockAllocationManager { + function setAVSRegistrar(address avs, address registrar) external {} + + function isOperatorSet(bytes memory operatorSet) external pure returns (bool) { + return true; + } + + function getStrategiesInOperatorSet(bytes memory operatorSet) external pure returns (IStrategy[] memory) { + IStrategy[] memory strategies = new IStrategy[](2); + strategies[0] = IStrategy(address(900)); + strategies[1] = IStrategy(address(901)); + return strategies; + } +} + contract ECDSAStakeRegistrySetup is Test, IECDSAStakeRegistryEvents { MockDelegationManager public mockDelegationManager; + MockAVSDirectory public mockAVSDirectory; + MockAllocationManager public mockAllocationManager; MockServiceManager public mockServiceManager; ECDSAStakeRegistry public registry; address internal operator1; @@ -58,25 +107,43 @@ contract ECDSAStakeRegistrySetup is Test, IECDSAStakeRegistryEvents { address[] internal signers; bytes[] internal signatures; bytes32 internal msgHash; + address public mockAVSRegistrarAddr; function setUp() public virtual { (operator1, operator1Pk) = makeAddrAndKey("Signer 1"); (operator2, operator2Pk) = makeAddrAndKey("Signer 2"); mockDelegationManager = new MockDelegationManager(); mockServiceManager = new MockServiceManager(); + mockAllocationManager = new MockAllocationManager(); + mockAVSDirectory = new MockAVSDirectory(); + mockAVSRegistrarAddr = makeAddr("mockAVSRegistrar"); + IStrategy mockStrategy = IStrategy(address(0x1234)); IECDSAStakeRegistryTypes.Quorum memory quorum = IECDSAStakeRegistryTypes.Quorum({ strategies: new IECDSAStakeRegistryTypes.StrategyParams[](1) }); - quorum.strategies[0] = - IECDSAStakeRegistryTypes.StrategyParams({strategy: mockStrategy, multiplier: 10000}); - registry = new ECDSAStakeRegistry(IDelegationManager(address(mockDelegationManager))); + quorum.strategies[0] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: mockStrategy, + multiplier: 10000 + }); + + registry = new ECDSAStakeRegistry( + IDelegationManager(address(mockDelegationManager)), + IAllocationManager(address(mockAllocationManager)), + mockAVSRegistrarAddr, + IAVSDirectory(address(mockAVSDirectory)) + ); + registry.initialize(address(mockServiceManager), 100, quorum); + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; + vm.prank(operator1); - registry.registerOperatorWithSignature(operatorSignature, operator1); + registry.registerOperatorM2Quorum(operatorSignature, operator1); + vm.prank(operator2); - registry.registerOperatorWithSignature(operatorSignature, operator2); + registry.registerOperatorM2Quorum(operatorSignature, operator2); + vm.roll(block.number + 1); } } @@ -191,26 +258,26 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { registry.updateQuorumConfig(invalidQuorum, operators); } - function test_RegisterOperatorWithSignature() public { + function test_RegisterOperatorM2Quorum() public { address operator3 = address(0x125); ISignatureUtils.SignatureWithSaltAndExpiry memory signature; vm.prank(operator3); - registry.registerOperatorWithSignature(signature, operator3); + registry.registerOperatorM2Quorum(signature, operator3); assertTrue(registry.operatorRegistered(operator3)); assertEq(registry.getLastCheckpointOperatorWeight(operator3), 1000); } - function test_RevertsWhen_AlreadyRegistered_RegisterOperatorWithSignature() public { + function test_RevertsWhen_AlreadyRegistered_RegisterOperatorM2Quorum() public { assertEq(registry.getLastCheckpointOperatorWeight(operator1), 1000); assertEq(registry.getLastCheckpointTotalWeight(), 2000); ISignatureUtils.SignatureWithSaltAndExpiry memory signature; vm.expectRevert(IECDSAStakeRegistryErrors.OperatorAlreadyRegistered.selector); vm.prank(operator1); - registry.registerOperatorWithSignature(signature, operator1); + registry.registerOperatorM2Quorum(signature, operator1); } - function test_RevertsWhen_SignatureIsInvalid_RegisterOperatorWithSignature() public { + function test_RevertsWhen_SignatureIsInvalid_RegisterOperatorM2Quorum() public { bytes memory signatureData; vm.mockCall( address(mockServiceManager), @@ -227,22 +294,22 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { ); } - function test_DeregisterOperator() public { + function test_DeregisterOperatorM2Quorum() public { assertEq(registry.getLastCheckpointOperatorWeight(operator1), 1000); assertEq(registry.getLastCheckpointTotalWeight(), 2000); vm.prank(operator1); - registry.deregisterOperator(); + registry.deregisterOperatorM2Quorum(); assertEq(registry.getLastCheckpointOperatorWeight(operator1), 0); assertEq(registry.getLastCheckpointTotalWeight(), 1000); } - function test_RevertsWhen_NotOperator_DeregisterOperator() public { + function test_RevertsWhen_NotOperator_DeregisterOperatorM2Quorum() public { address notOperator = address(0x2); vm.prank(notOperator); vm.expectRevert(IECDSAStakeRegistryErrors.OperatorNotRegistered.selector); - registry.deregisterOperator(); + registry.deregisterOperatorM2Quorum(); } function test_When_Empty_UpdateOperators() public { @@ -426,7 +493,9 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { signatures[0] = abi.encodePacked(r, s, v); (v, r, s) = vm.sign(operator2Pk, msgHash); signatures[1] = abi.encodePacked(r, s, v); - + console.log("operator1 weight", registry.getOperatorWeight(operator1)); + console.log("operator2 weight", registry.getOperatorWeight(operator2)); + console.log("threshold weight", registry.getLastCheckpointThresholdWeight()); registry.isValidSignature(msgHash, abi.encode(signers, signatures, block.number - 1)); } @@ -595,16 +664,16 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { uint256 before = gasleft(); vm.pauseGasMetering(); vm.prank(operator1); - registry.deregisterOperator(); + registry.deregisterOperatorM2Quorum(); vm.prank(operator2); - registry.deregisterOperator(); + registry.deregisterOperatorM2Quorum(); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; address[] memory operators = new address[](30); for (uint256 i; i < operators.length; i++) { operators[i] = address(uint160(i)); vm.prank(operators[i]); - registry.registerOperatorWithSignature(operatorSignature, operators[i]); + registry.registerOperatorM2Quorum(operatorSignature, operators[i]); } vm.resumeGasMetering(); registry.updateOperators(operators); @@ -616,9 +685,9 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { uint256 before = gasleft(); vm.pauseGasMetering(); vm.prank(operator1); - registry.deregisterOperator(); + registry.deregisterOperatorM2Quorum(); vm.prank(operator2); - registry.deregisterOperator(); + registry.deregisterOperatorM2Quorum(); msgHash = keccak256("data"); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; @@ -630,7 +699,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { for (uint256 i = 1; i < operators.length + 1; i++) { operators[i - 1] = address(vm.addr(i)); vm.prank(operators[i - 1]); - registry.registerOperatorWithSignature(operatorSignature, operators[i - 1]); + registry.registerOperatorM2Quorum(operatorSignature, operators[i - 1]); (v, r, s) = vm.sign(i, msgHash); signatures[i - 1] = abi.encodePacked(r, s, v); } @@ -657,7 +726,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { // Register operator with a different signing key vm.prank(operator); - registry.registerOperatorWithSignature(operatorSignature, signer); + registry.registerOperatorM2Quorum(operatorSignature, signer); // Verify that the signing key has been successfully registered for the operator address registeredSigningKey = registry.getLatestOperatorSigningKey(operator); @@ -675,7 +744,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { // Register operator with a different signing key vm.prank(operator); - registry.registerOperatorWithSignature(operatorSignature, signer); + registry.registerOperatorM2Quorum(operatorSignature, signer); /// Register a second time vm.prank(operator); @@ -701,7 +770,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { // Register operator with a different signing key vm.prank(operator); - registry.registerOperatorWithSignature(operatorSignature, signer); + registry.registerOperatorM2Quorum(operatorSignature, signer); vm.roll(block.number + 1); // Prepare data for signature @@ -727,7 +796,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { // Register operator with the initial signing key vm.prank(operator); - registry.registerOperatorWithSignature(operatorSignature, initialSigningKey); + registry.registerOperatorM2Quorum(operatorSignature, initialSigningKey); vm.roll(block.number + 1); // Prepare data for signature with initial signing key @@ -768,7 +837,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { // Register operator with the initial signing key vm.prank(operator); - registry.registerOperatorWithSignature(operatorSignature, initialSigningKey); + registry.registerOperatorM2Quorum(operatorSignature, initialSigningKey); vm.roll(block.number + 1); // Prepare data for signature with initial signing key From 31d2a867d950379f44b31f3e7f69fbb25268e587 Mon Sep 17 00:00:00 2001 From: jaxxjj Date: Wed, 26 Feb 2025 23:34:31 -0700 Subject: [PATCH 02/12] fix: resolve failing tests --- src/unaudited/ECDSAStakeRegistry.sol | 27 +- .../ECDSAStakeRegistryPermissioned.sol | 8 +- test/unit/ECDSAServiceManager.t.sol | 40 +++ .../ECDSAStakeRegistryEqualWeightUnit.t.sol | 41 +-- .../ECDSAStakeRegistryPermissionedUnit.t.sol | 53 ++-- test/unit/ECDSAStakeRegistryUnit.t.sol | 265 +++++++++++++++--- 6 files changed, 330 insertions(+), 104 deletions(-) diff --git a/src/unaudited/ECDSAStakeRegistry.sol b/src/unaudited/ECDSAStakeRegistry.sol index 497b6937..05a3e46c 100644 --- a/src/unaudited/ECDSAStakeRegistry.sol +++ b/src/unaudited/ECDSAStakeRegistry.sol @@ -104,18 +104,11 @@ contract ECDSAStakeRegistry is if (isM2QuorumRegistrationDisabled) { revert M2QuorumRegistrationIsDisabled(); } - if (operatorRegisteredOnAVSDirectory(msg.sender)) { - revert OperatorAlreadyRegistered(); - } _registerOperatorM2Quorum(msg.sender, operatorSignature, signingKey); } /// @inheritdoc IECDSAStakeRegistry function deregisterOperatorM2Quorum() external { - if (!operatorRegisteredOnAVSDirectory(msg.sender)) { - revert OperatorNotRegistered(); - } - _deregisterOperatorM2Quorum(msg.sender); } @@ -303,10 +296,9 @@ contract ECDSAStakeRegistry is address _operator ) public view returns (uint256) { uint256 quorumWeight = getQuorumWeight(_operator); - // uint256 operatorSetWeight = getOperatorSetWeight(_operator); + uint256 operatorSetWeight = getOperatorSetWeight(_operator); - // return quorumWeight + operatorSetWeight; - return quorumWeight; + return quorumWeight + operatorSetWeight; } /// @notice Calculates operator's weight in the quorum @@ -518,13 +510,11 @@ contract ECDSAStakeRegistry is function _deregisterOperatorM2Quorum( address operator ) internal { - if (!operatorRegisteredOnAVSDirectory(operator)) { - revert OperatorNotRegistered(); - } - + // need to first remove the operator from the AVS Directory to correctly update the operator weight + IServiceManager(_serviceManager).deregisterOperatorFromAVS(operator); int256 delta = _updateOperatorWeight(operator); _updateTotalWeight(delta); - IServiceManager(_serviceManager).deregisterOperatorFromAVS(operator); + if (!operatorRegisteredOnCurrentOperatorSets(operator)) { _totalOperators--; emit OperatorDeregistered(operator, address(_serviceManager)); @@ -539,14 +529,11 @@ contract ECDSAStakeRegistry is ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature, address signingKey ) internal virtual { - if (operatorRegisteredOnAVSDirectory(operator)) { - revert OperatorAlreadyRegistered(); - } - + // need to first register the operator to the AVS Directory to correctly update the operator weight + IServiceManager(_serviceManager).registerOperatorToAVS(operator, operatorSignature); int256 delta = _updateOperatorWeight(operator); _updateTotalWeight(delta); _updateOperatorSigningKey(operator, signingKey); - IServiceManager(_serviceManager).registerOperatorToAVS(operator, operatorSignature); if (!operatorRegisteredOnCurrentOperatorSets(operator)) { _totalOperators++; emit OperatorRegistered(operator, _serviceManager); diff --git a/src/unaudited/examples/ECDSAStakeRegistryPermissioned.sol b/src/unaudited/examples/ECDSAStakeRegistryPermissioned.sol index 6d61d0fe..02a259a3 100644 --- a/src/unaudited/examples/ECDSAStakeRegistryPermissioned.sol +++ b/src/unaudited/examples/ECDSAStakeRegistryPermissioned.sol @@ -76,6 +76,10 @@ contract ECDSAStakeRegistryPermissioned is ECDSAStakeRegistry { function _ejectOperator( address _operator ) internal { + if(!operatorRegistered(_operator)){ + revert OperatorNotRegistered(); + } + if (operatorRegisteredOnAVSDirectory(_operator)) { _deregisterOperatorM2Quorum(_operator); } @@ -109,7 +113,9 @@ contract ECDSAStakeRegistryPermissioned is ECDSAStakeRegistry { } delete allowlistedOperators[_operator]; emit OperatorRevoked(_operator); - _ejectOperator(_operator); + if(operatorRegistered(_operator)){ + _ejectOperator(_operator); + } } /// @inheritdoc ECDSAStakeRegistry diff --git a/test/unit/ECDSAServiceManager.t.sol b/test/unit/ECDSAServiceManager.t.sol index 2282f96a..f8e043df 100644 --- a/test/unit/ECDSAServiceManager.t.sol +++ b/test/unit/ECDSAServiceManager.t.sol @@ -78,6 +78,46 @@ contract MockRewardsCoordinator { ) external pure {} } +contract MockAVSDirectory { + // 使用 mapping 存储每个 operator 的状态 + mapping(address => mapping(address => IAVSDirectoryTypes.OperatorAVSRegistrationStatus)) + private operatorStatus; + + function registerOperatorToAVS( + address operator, + ISignatureUtils.SignatureWithSaltAndExpiry memory + ) external { + // 设置特定 operator 的状态为 REGISTERED + operatorStatus[msg.sender][operator] = IAVSDirectoryTypes.OperatorAVSRegistrationStatus.REGISTERED; + } + + function deregisterOperatorFromAVS( + address operator + ) external { + // 设置特定 operator 的状态为 UNREGISTERED + operatorStatus[msg.sender][operator] = IAVSDirectoryTypes.OperatorAVSRegistrationStatus.UNREGISTERED; + } + + function updateAVSMetadataURI( + string memory + ) external pure {} + + function setAvsOperatorStatus( + address avs, + address operator, + IAVSDirectoryTypes.OperatorAVSRegistrationStatus status + ) external { + operatorStatus[avs][operator] = status; + } + + function avsOperatorStatus( + address avs, + address operator + ) external view returns (IAVSDirectoryTypes.OperatorAVSRegistrationStatus) { + return operatorStatus[avs][operator]; + } +} + contract ECDSAServiceManagerSetup is Test { MockDelegationManager public mockDelegationManager; AVSDirectoryMock public mockAVSDirectory; diff --git a/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol b/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol index bf226960..8a688f9a 100644 --- a/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol +++ b/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol @@ -19,7 +19,8 @@ import {IAVSDirectory} from "../../src/unaudited/ECDSAStakeRegistry.sol"; contract EqualWeightECDSARegistry is ECDSAStakeRegistrySetup { ECDSAStakeRegistryEqualWeight internal fixedWeightRegistry; - + address internal operator6 = makeAddr("operator6"); + address internal operator7 = makeAddr("operator7"); function setUp() public virtual override { super.setUp(); fixedWeightRegistry = @@ -37,48 +38,48 @@ contract EqualWeightECDSARegistry is ECDSAStakeRegistrySetup { fixedWeightRegistry.initialize(address(mockServiceManager), 100, quorum); - fixedWeightRegistry.permitOperator(operator1); - fixedWeightRegistry.permitOperator(operator2); + fixedWeightRegistry.permitOperator(operator6); + fixedWeightRegistry.permitOperator(operator7); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; - vm.prank(operator1); - fixedWeightRegistry.registerOperatorM2Quorum(operatorSignature, operator1); + vm.prank(operator6); + fixedWeightRegistry.registerOperatorM2Quorum(operatorSignature, operator6); - vm.prank(operator2); - fixedWeightRegistry.registerOperatorM2Quorum(operatorSignature, operator2); + vm.prank(operator7); + fixedWeightRegistry.registerOperatorM2Quorum(operatorSignature, operator7); } function test_FixedStakeUpdates() public { - assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator1), 1); - assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator2), 1); + assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator6), 1); + assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator7), 1); assertEq(fixedWeightRegistry.getLastCheckpointTotalWeight(), 2); vm.roll(block.number + 1); - vm.prank(operator1); + vm.prank(operator6); fixedWeightRegistry.deregisterOperatorM2Quorum(); - assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator1), 0); - assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator2), 1); + assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator6), 0); + assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator7), 1); assertEq(fixedWeightRegistry.getLastCheckpointTotalWeight(), 1); vm.roll(block.number + 1); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; - vm.prank(operator1); - fixedWeightRegistry.registerOperatorM2Quorum(operatorSignature, operator1); + vm.prank(operator6); + fixedWeightRegistry.registerOperatorM2Quorum(operatorSignature, operator6); - assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator1), 1); - assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator2), 1); + assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator6), 1); + assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator7), 1); assertEq(fixedWeightRegistry.getLastCheckpointTotalWeight(), 2); vm.roll(block.number + 1); address[] memory operators = new address[](2); - operators[0] = operator1; - operators[1] = operator2; + operators[0] = operator6; + operators[1] = operator7; fixedWeightRegistry.updateOperators(operators); - assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator1), 1); - assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator2), 1); + assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator6), 1); + assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator7), 1); assertEq(fixedWeightRegistry.getLastCheckpointTotalWeight(), 2); } } diff --git a/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol b/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol index c77010d3..fc7026f7 100644 --- a/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol +++ b/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol @@ -19,7 +19,8 @@ import {IAVSDirectory} from "../../src/unaudited/ECDSAStakeRegistry.sol"; contract PermissionedECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { ECDSAStakeRegistryPermissioned internal permissionedRegistry; - + address internal operator6 = makeAddr("operator6"); + address internal operator7 = makeAddr("operator7"); function setUp() public virtual override { super.setUp(); permissionedRegistry = @@ -37,16 +38,16 @@ contract PermissionedECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { permissionedRegistry.initialize(address(mockServiceManager), 100, quorum); - permissionedRegistry.permitOperator(operator1); - permissionedRegistry.permitOperator(operator2); + permissionedRegistry.permitOperator(operator6); + permissionedRegistry.permitOperator(operator7); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; - vm.prank(operator1); - permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator1); + vm.prank(operator6); + permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator6); - vm.prank(operator2); - permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator1); + vm.prank(operator7); + permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator7); vm.roll(block.number + 1); } @@ -59,8 +60,8 @@ contract PermissionedECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { } function test_When_Owner_PermitOperator() public { - address operator3 = address(0xBEEF); - permissionedRegistry.permitOperator(operator3); + address operator8 = address(0xBEEF); + permissionedRegistry.permitOperator(operator8); } function test_RevertsWhen_NotOwner_RevokeOperator() public { @@ -78,55 +79,53 @@ contract PermissionedECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { } function test_When_Owner_RevokeOperator() public { - permissionedRegistry.revokeOperator(operator1); + permissionedRegistry.revokeOperator(operator6); } function test_RevertsWhen_NotOwner_EjectOperator() public { address notOwner = address(0xBEEF); vm.prank(notOwner); vm.expectRevert("Ownable: caller is not the owner"); - permissionedRegistry.ejectOperator(operator1); + permissionedRegistry.ejectOperator(operator6); } function test_RevertsWhen_NotOperator_EjectOperator() public { address notOperator = address(0xBEEF); - vm.expectRevert( - abi.encodeWithSelector(IECDSAStakeRegistryErrors.OperatorNotRegistered.selector) - ); + vm.expectRevert(); permissionedRegistry.ejectOperator(notOperator); } function test_When_Owner_EjectOperator() public { - permissionedRegistry.ejectOperator(operator1); + permissionedRegistry.ejectOperator(operator6); } function test_RevertsWhen_NotAllowlisted_RegisterOperatorM2Quorum() public { - address operator3 = address(0xBEEF); + address operator8 = address(0xBEEF); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; vm.expectRevert( abi.encodeWithSelector(ECDSAStakeRegistryPermissioned.OperatorNotAllowlisted.selector) ); - vm.prank(operator3); - permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator3); + vm.prank(operator8); + permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator8); } function test_WhenAllowlisted_RegisterOperatorM2Quorum() public { - address operator3 = address(0xBEEF); - permissionedRegistry.permitOperator(operator3); + address operator8 = address(0xBEEF); + permissionedRegistry.permitOperator(operator8); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; - vm.prank(operator3); - permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator3); + vm.prank(operator8); + permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator8); } function test_DeregisterOperatorM2Quorum() public { - address operator3 = address(0xBEEF); - permissionedRegistry.permitOperator(operator3); + address operator8 = address(0xBEEF); + permissionedRegistry.permitOperator(operator8); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; - vm.prank(operator3); - permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator3); + vm.prank(operator8); + permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator8); - vm.prank(operator3); + vm.prank(operator8); permissionedRegistry.deregisterOperatorM2Quorum(); } } diff --git a/test/unit/ECDSAStakeRegistryUnit.t.sol b/test/unit/ECDSAStakeRegistryUnit.t.sol index d2481f6b..f37006f7 100644 --- a/test/unit/ECDSAStakeRegistryUnit.t.sol +++ b/test/unit/ECDSAStakeRegistryUnit.t.sol @@ -17,18 +17,26 @@ import { } from "../../src/interfaces/IECDSAStakeRegistry.sol"; import {IAllocationManager} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; import {IAVSDirectory, IAVSDirectoryTypes} from "../../src/unaudited/ECDSAStakeRegistry.sol"; +import {OperatorSet} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol"; contract MockServiceManager { - // solhint-disable-next-line + MockAVSDirectory public immutable avsDirectory; + constructor(address _avsDirectory) { + avsDirectory = MockAVSDirectory(_avsDirectory); + } function deregisterOperatorFromAVS( - address - ) external {} + address operator + ) external { + avsDirectory.deregisterOperatorFromAVS(operator); + } function registerOperatorToAVS( - address, - ISignatureUtils.SignatureWithSaltAndExpiry memory // solhint-disable-next-line - ) external {} + address operator, + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature + ) external { + avsDirectory.registerOperatorToAVS(operator, operatorSignature); + } } contract MockDelegationManager { @@ -49,42 +57,127 @@ contract MockDelegationManager { } contract MockAVSDirectory { - mapping(address => mapping(address => IAVSDirectoryTypes.OperatorAVSRegistrationStatus)) - private operatorStatus; + error OperatorAlreadyRegistered(); + error OperatorNotRegistered(); + + // 简化为只记录operator的状态 + mapping(address => IAVSDirectoryTypes.OperatorAVSRegistrationStatus) private operatorStatus; function registerOperatorToAVS( - address, + address operator, ISignatureUtils.SignatureWithSaltAndExpiry memory - ) external pure {} + ) external { + if(operatorStatus[operator] == IAVSDirectoryTypes.OperatorAVSRegistrationStatus.REGISTERED) { + revert OperatorAlreadyRegistered(); + } + operatorStatus[operator] = IAVSDirectoryTypes.OperatorAVSRegistrationStatus.REGISTERED; + } function deregisterOperatorFromAVS( - address - ) external pure {} + address operator + ) external { + if(operatorStatus[operator] == IAVSDirectoryTypes.OperatorAVSRegistrationStatus.UNREGISTERED) { + revert OperatorNotRegistered(); + } + operatorStatus[operator] = IAVSDirectoryTypes.OperatorAVSRegistrationStatus.UNREGISTERED; + } function updateAVSMetadataURI( string memory ) external pure {} + function setAvsOperatorStatus( address avs, address operator, IAVSDirectoryTypes.OperatorAVSRegistrationStatus status ) external { - operatorStatus[avs][operator] = status; + operatorStatus[operator] = status; } - function avsOperatorStatus(address avs, address operator) external view returns (IAVSDirectoryTypes.OperatorAVSRegistrationStatus) { - return operatorStatus[avs][operator]; + function avsOperatorStatus( + address avs, + address operator + ) external view returns (IAVSDirectoryTypes.OperatorAVSRegistrationStatus) { + return operatorStatus[operator]; } } contract MockAllocationManager { + // 定义 Allocation 结构体 + struct Allocation { + uint64 currentMagnitude; + uint32 lastUpdatedBlock; + } + + // 存储 allocation 信息 + mapping(address => mapping(bytes32 => Allocation)) public allocations; + mapping(address => mapping(address => uint64)) public maxMagnitudes; + mapping(address => mapping(address => mapping(uint32 => bool))) public operatorSetMembers; + + // 设置 operator set membership + function setOperatorSetMembership( + address operator, + address avs, + uint32 setId, + bool isMember + ) external { + operatorSetMembers[operator][avs][setId] = isMember; + } + + // 检查 operator set membership + function isMemberOfOperatorSet( + address operator, + OperatorSet memory operatorSet + ) external view returns (bool) { + return operatorSetMembers[operator][operatorSet.avs][operatorSet.id]; + } + + // 设置 allocation + function setAllocation( + address operator, + OperatorSet memory operatorSet, + address strategy, + Allocation memory allocation + ) external { + bytes32 key = keccak256(abi.encode(operator, operatorSet, strategy)); + allocations[operator][key] = allocation; + } + + // 获取 allocation + function getAllocation( + address operator, + OperatorSet memory operatorSet, + IStrategy strategy + ) external view returns (Allocation memory) { + bytes32 key = keccak256(abi.encode(operator, operatorSet, address(strategy))); + return allocations[operator][key]; + } + + // 设置 maxMagnitude + function setMaxMagnitude( + address operator, + address strategy, + uint64 magnitude + ) external { + maxMagnitudes[operator][strategy] = magnitude; + } + + // 获取 maxMagnitude + function getMaxMagnitude( + address operator, + IStrategy strategy + ) external view returns (uint64) { + return maxMagnitudes[operator][address(strategy)]; + } + + // 其他必要的函数 function setAVSRegistrar(address avs, address registrar) external {} - function isOperatorSet(bytes memory operatorSet) external pure returns (bool) { + function isOperatorSet(OperatorSet memory operatorSet) external pure returns (bool) { return true; } - function getStrategiesInOperatorSet(bytes memory operatorSet) external pure returns (IStrategy[] memory) { + function getStrategiesInOperatorSet(OperatorSet memory operatorSet) external pure returns (IStrategy[] memory) { IStrategy[] memory strategies = new IStrategy[](2); strategies[0] = IStrategy(address(900)); strategies[1] = IStrategy(address(901)); @@ -98,12 +191,19 @@ contract ECDSAStakeRegistrySetup is Test, IECDSAStakeRegistryEvents { MockAllocationManager public mockAllocationManager; MockServiceManager public mockServiceManager; ECDSAStakeRegistry public registry; + address public owner = makeAddr("owner"); address internal operator1; address internal operator2; + address internal operator3; + address internal operator4; uint256 internal operator1Pk; uint256 internal operator2Pk; + uint256 internal operator3Pk; + uint256 internal operator4Pk; bytes internal signature1; bytes internal signature2; + bytes internal signature3; + bytes internal signature4; address[] internal signers; bytes[] internal signatures; bytes32 internal msgHash; @@ -112,11 +212,19 @@ contract ECDSAStakeRegistrySetup is Test, IECDSAStakeRegistryEvents { function setUp() public virtual { (operator1, operator1Pk) = makeAddrAndKey("Signer 1"); (operator2, operator2Pk) = makeAddrAndKey("Signer 2"); + (operator3, operator3Pk) = makeAddrAndKey("Signer 3"); + (operator4, operator4Pk) = makeAddrAndKey("Signer 4"); + + // 1. 先创建 mockAVSDirectory + mockAVSDirectory = new MockAVSDirectory(); + + // 2. 再创建 mockServiceManager,并传入 mockAVSDirectory 的地址 + mockServiceManager = new MockServiceManager(address(mockAVSDirectory)); + + // 3. 创建其他 mock 合约 mockDelegationManager = new MockDelegationManager(); - mockServiceManager = new MockServiceManager(); mockAllocationManager = new MockAllocationManager(); - mockAVSDirectory = new MockAVSDirectory(); - mockAVSRegistrarAddr = makeAddr("mockAVSRegistrar"); + mockAVSRegistrarAddr = makeAddr("mockAVSRegistrar"); IStrategy mockStrategy = IStrategy(address(0x1234)); IECDSAStakeRegistryTypes.Quorum memory quorum = IECDSAStakeRegistryTypes.Quorum({ @@ -134,15 +242,86 @@ contract ECDSAStakeRegistrySetup is Test, IECDSAStakeRegistryEvents { IAVSDirectory(address(mockAVSDirectory)) ); + vm.prank(owner); registry.initialize(address(mockServiceManager), 100, quorum); + + // Set up 3 operator sets + uint32[] memory operatorSetIds = new uint32[](3); + operatorSetIds[0] = 1; + operatorSetIds[1] = 2; + operatorSetIds[2] = 3; + vm.prank(owner); + registry.setCurrentOperatorSetIds(operatorSetIds); + + // Set up operator set membership + for(uint32 setId = 1; setId <= 3; setId++) { + MockAllocationManager(address(mockAllocationManager)).setOperatorSetMembership( + operator3, + address(mockServiceManager), + setId, + true + ); + MockAllocationManager(address(mockAllocationManager)).setOperatorSetMembership( + operator4, + address(mockServiceManager), + setId, + true + ); + + // 设置 allocation 和 maxMagnitude + OperatorSet memory operatorSet = OperatorSet({ + avs: address(mockServiceManager), + id: setId + }); + + // 为每个 strategy 设置 allocation + IStrategy[] memory strategies = mockAllocationManager.getStrategiesInOperatorSet(operatorSet); + for(uint i = 0; i < strategies.length; i++) { + // 设置 allocation + MockAllocationManager.Allocation memory allocation = MockAllocationManager.Allocation({ + currentMagnitude: 1000, + lastUpdatedBlock: uint32(block.number) + }); + + MockAllocationManager(address(mockAllocationManager)).setAllocation( + operator3, + operatorSet, + address(strategies[i]), + allocation + ); + MockAllocationManager(address(mockAllocationManager)).setAllocation( + operator4, + operatorSet, + address(strategies[i]), + allocation + ); + + // 设置 maxMagnitude + MockAllocationManager(address(mockAllocationManager)).setMaxMagnitude( + operator3, + address(strategies[i]), + 1000 + ); + MockAllocationManager(address(mockAllocationManager)).setMaxMagnitude( + operator4, + address(strategies[i]), + 1000 + ); + } + } + + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; + vm.prank(operator1); registry.registerOperatorM2Quorum(operatorSignature, operator1); + vm.prank(operator2); registry.registerOperatorM2Quorum(operatorSignature, operator2); + vm.roll(block.number + 1); } @@ -165,6 +344,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { vm.expectEmit(true, true, false, true); emit QuorumUpdated(oldQuorum, newQuorum); + vm.prank(owner); registry.updateQuorumConfig(newQuorum, operators); } @@ -182,6 +362,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { operators[1] = operator2; vm.expectRevert(IECDSAStakeRegistryErrors.InvalidQuorum.selector); + vm.prank(owner); registry.updateQuorumConfig(invalidQuorum, operators); } @@ -212,6 +393,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { operators[1] = operator2; /// Showing this doesnt revert + vm.prank(owner); registry.updateQuorumConfig(quorum, operators); } @@ -227,6 +409,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { invalidQuorum.strategies[1] = StrategyParams({strategy: IStrategy(address(420)), multiplier: 5000}); vm.expectRevert(IECDSAStakeRegistryErrors.NotSorted.selector); + vm.prank(owner); registry.updateQuorumConfig(invalidQuorum, operators); } @@ -242,6 +425,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { invalidQuorum.strategies[1] = StrategyParams({strategy: IStrategy(address(419)), multiplier: 5000}); vm.expectRevert(IECDSAStakeRegistryErrors.NotSorted.selector); + vm.prank(owner); registry.updateQuorumConfig(invalidQuorum, operators); } @@ -255,16 +439,17 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { operators[1] = operator2; vm.expectRevert(IECDSAStakeRegistryErrors.InvalidQuorum.selector); + vm.prank(owner); registry.updateQuorumConfig(invalidQuorum, operators); } function test_RegisterOperatorM2Quorum() public { - address operator3 = address(0x125); + address operator5 = address(0x125); ISignatureUtils.SignatureWithSaltAndExpiry memory signature; - vm.prank(operator3); - registry.registerOperatorM2Quorum(signature, operator3); - assertTrue(registry.operatorRegistered(operator3)); - assertEq(registry.getLastCheckpointOperatorWeight(operator3), 1000); + vm.prank(operator5); + registry.registerOperatorM2Quorum(signature, operator5); + assertTrue(registry.operatorRegistered(operator5)); + assertEq(registry.getLastCheckpointOperatorWeight(operator5), 1000); } function test_RevertsWhen_AlreadyRegistered_RegisterOperatorM2Quorum() public { @@ -272,7 +457,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { assertEq(registry.getLastCheckpointTotalWeight(), 2000); ISignatureUtils.SignatureWithSaltAndExpiry memory signature; - vm.expectRevert(IECDSAStakeRegistryErrors.OperatorAlreadyRegistered.selector); + vm.expectRevert(); vm.prank(operator1); registry.registerOperatorM2Quorum(signature, operator1); } @@ -319,12 +504,12 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { function test_When_OperatorNotRegistered_UpdateOperators() public { address[] memory operators = new address[](3); - address operator3 = address(0xBEEF); + address operator5 = address(0xBEEF); operators[0] = operator1; operators[1] = operator2; - operators[2] = operator3; + operators[2] = operator5; registry.updateOperators(operators); - assertEq(registry.getLastCheckpointOperatorWeight(operator3), 0); + assertEq(registry.getLastCheckpointOperatorWeight(operator5), 0); } function test_When_SingleOperator_UpdateOperators() public { @@ -394,6 +579,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { operators[0] = operator1; operators[1] = operator2; + vm.prank(owner); registry.updateQuorumConfig(quorum, operators); address[] memory strategies = new address[](2); @@ -428,6 +614,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { address[] memory operators = new address[](2); operators[0] = operator1; operators[1] = operator2; + vm.prank(owner); registry.updateMinimumWeight(newMinimumWeight, operators); uint256 updatedMinimumWeight = registry.minimumWeight(); @@ -449,6 +636,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { address[] memory operators = new address[](2); operators[0] = operator1; operators[1] = operator2; + vm.prank(owner); registry.updateMinimumWeight(initialMinimumWeight, operators); uint256 updatedMinimumWeight = registry.minimumWeight(); @@ -460,10 +648,12 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { address[] memory operators = new address[](2); operators[0] = operator1; operators[1] = operator2; + vm.prank(owner); registry.updateMinimumWeight(initialMinimumWeight, operators); uint256 newMinimumWeight = 0; + vm.prank(owner); registry.updateMinimumWeight(newMinimumWeight, operators); uint256 updatedMinimumWeight = registry.minimumWeight(); @@ -496,6 +686,8 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { console.log("operator1 weight", registry.getOperatorWeight(operator1)); console.log("operator2 weight", registry.getOperatorWeight(operator2)); console.log("threshold weight", registry.getLastCheckpointThresholdWeight()); + console.log("operator1 status", registry.operatorRegisteredOnAVSDirectory(operator1)); + console.log("operator2 status", registry.operatorRegisteredOnAVSDirectory(operator2)); registry.isValidSignature(msgHash, abi.encode(signers, signatures, block.number - 1)); } @@ -714,13 +906,14 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { } // Define private and public keys for operator3 and signer - uint256 private operator3Pk = 3; - address private operator3 = address(vm.addr(operator3Pk)); + // Define private and public keys for operator5 and signer + uint256 private operator5Pk = 3; + address private operator5 = address(vm.addr(operator5Pk)); uint256 private signerPk = 4; address private signer = address(vm.addr(signerPk)); function test_WhenUsingSigningKey_RegierOperatorWithSignature() public { - address operator = operator3; + address operator = operator5; ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; @@ -738,7 +931,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { } function test_Twice_RegierOperatorWithSignature() public { - address operator = operator3; + address operator = operator5; ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; @@ -764,7 +957,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { } function test_WhenUsingSigningKey_CheckSignatures() public { - address operator = operator3; + address operator = operator5; ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; @@ -788,7 +981,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { } function test_WhenUsingSigningKey_CheckSignaturesAtBlock() public { - address operator = operator3; + address operator = operator5; address initialSigningKey = address(vm.addr(signerPk)); address updatedSigningKey = address(vm.addr(signerPk + 1)); @@ -829,7 +1022,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { } function test_WhenUsingPriorSigningKey_CheckSignaturesAtBlock() public { - address operator = operator3; + address operator = operator5; address initialSigningKey = address(vm.addr(signerPk)); address updatedSigningKey = address(vm.addr(signerPk + 1)); From 6bb07f17f1f610f514522f80dd0cdc7dc7535ea4 Mon Sep 17 00:00:00 2001 From: jaxxjj Date: Wed, 26 Feb 2025 23:37:17 -0700 Subject: [PATCH 03/12] chore: fmt --- src/interfaces/IECDSAStakeRegistry.sol | 2 +- src/unaudited/ECDSAAVSRegistrar.sol | 2 +- src/unaudited/ECDSAServiceManagerBase.sol | 21 ++- src/unaudited/ECDSAStakeRegistry.sol | 13 +- src/unaudited/ECDSAStakeRegistryStorage.sol | 3 +- .../ECDSAStakeRegistryEqualWeight.sol | 2 +- .../ECDSAStakeRegistryPermissioned.sol | 11 +- test/mocks/ECDSAServiceManagerMock.sol | 1 - test/mocks/ECDSAStakeRegistryMock.sol | 1 - test/unit/ECDSAServiceManager.t.sol | 85 +++++----- .../ECDSAStakeRegistryEqualWeightUnit.t.sol | 35 +++-- .../ECDSAStakeRegistryPermissionedUnit.t.sol | 37 +++-- test/unit/ECDSAStakeRegistryUnit.t.sol | 147 +++++++----------- 13 files changed, 172 insertions(+), 188 deletions(-) diff --git a/src/interfaces/IECDSAStakeRegistry.sol b/src/interfaces/IECDSAStakeRegistry.sol index 7b7dfeeb..8115164d 100644 --- a/src/interfaces/IECDSAStakeRegistry.sol +++ b/src/interfaces/IECDSAStakeRegistry.sol @@ -370,4 +370,4 @@ interface IECDSAStakeRegistry is function getLastCheckpointThresholdWeightAtBlock( uint32 blockNumber ) external view returns (uint256); -} \ No newline at end of file +} diff --git a/src/unaudited/ECDSAAVSRegistrar.sol b/src/unaudited/ECDSAAVSRegistrar.sol index ce36aa08..8bbaa66f 100644 --- a/src/unaudited/ECDSAAVSRegistrar.sol +++ b/src/unaudited/ECDSAAVSRegistrar.sol @@ -58,7 +58,7 @@ contract AVSRegistrar is IAVSRegistrar, Ownable { function supportsAVS( address avsAddr - ) external view returns (bool){ + ) external view returns (bool) { return avs == avsAddr; } } diff --git a/src/unaudited/ECDSAServiceManagerBase.sol b/src/unaudited/ECDSAServiceManagerBase.sol index d2358442..e16b1476 100644 --- a/src/unaudited/ECDSAServiceManagerBase.sol +++ b/src/unaudited/ECDSAServiceManagerBase.sol @@ -242,7 +242,9 @@ abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable * @param operatorSetId The ID of the operator set to query * @return Array of strategy addresses from the specified operator set */ - function getOperatorSetStrategies(uint32 operatorSetId) external view virtual returns (address[] memory) { + function getOperatorSetStrategies( + uint32 operatorSetId + ) external view virtual returns (address[] memory) { return _getOperatorSetStrategies(operatorSetId); } @@ -364,23 +366,26 @@ abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable * @param operatorSetId The ID of the operator set to get strategies from * @return Array of strategy addresses from the specified operator set */ - function _getOperatorSetStrategies(uint32 operatorSetId) internal view virtual returns (address[] memory) { + function _getOperatorSetStrategies( + uint32 operatorSetId + ) internal view virtual returns (address[] memory) { OperatorSet memory operatorSet = OperatorSet(address(this), operatorSetId); - + // Return empty array if this is not a valid operator set if (!IAllocationManager(allocationManager).isOperatorSet(operatorSet)) { return new address[](0); } - + // Get strategies for this operator set - IStrategy[] memory strategies = IAllocationManager(allocationManager).getStrategiesInOperatorSet(operatorSet); - + IStrategy[] memory strategies = + IAllocationManager(allocationManager).getStrategiesInOperatorSet(operatorSet); + // Convert IStrategy array to address array address[] memory strategyAddresses = new address[](strategies.length); for (uint256 i = 0; i < strategies.length; i++) { strategyAddresses[i] = address(strategies[i]); } - + return strategyAddresses; } @@ -455,4 +460,4 @@ abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable // storage gap for upgradeability // slither-disable-next-line shadowing-state uint256[49] private __GAP; -} \ No newline at end of file +} diff --git a/src/unaudited/ECDSAStakeRegistry.sol b/src/unaudited/ECDSAStakeRegistry.sol index 05a3e46c..61beae2d 100644 --- a/src/unaudited/ECDSAStakeRegistry.sol +++ b/src/unaudited/ECDSAStakeRegistry.sol @@ -19,15 +19,16 @@ import {SignatureCheckerUpgradeable} from "@openzeppelin-upgrades/contracts/utils/cryptography/SignatureCheckerUpgradeable.sol"; import {IERC1271Upgradeable} from "@openzeppelin-upgrades/contracts/interfaces/IERC1271Upgradeable.sol"; -import { - IAVSDirectoryTypes -} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; +import {IAVSDirectoryTypes} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; import {IAllocationManager} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; import {OperatorSet} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol"; interface IAVSDirectory { - function avsOperatorStatus(address avs, address operator) external view returns (IAVSDirectoryTypes.OperatorAVSRegistrationStatus); + function avsOperatorStatus( + address avs, + address operator + ) external view returns (IAVSDirectoryTypes.OperatorAVSRegistrationStatus); } /// @title ECDSA Stake Registry @@ -514,7 +515,7 @@ contract ECDSAStakeRegistry is IServiceManager(_serviceManager).deregisterOperatorFromAVS(operator); int256 delta = _updateOperatorWeight(operator); _updateTotalWeight(delta); - + if (!operatorRegisteredOnCurrentOperatorSets(operator)) { _totalOperators--; emit OperatorDeregistered(operator, address(_serviceManager)); @@ -770,4 +771,4 @@ contract ECDSAStakeRegistry is revert InsufficientSignedStake(); } } -} \ No newline at end of file +} diff --git a/src/unaudited/ECDSAStakeRegistryStorage.sol b/src/unaudited/ECDSAStakeRegistryStorage.sol index d7573107..6ebc84b9 100644 --- a/src/unaudited/ECDSAStakeRegistryStorage.sol +++ b/src/unaudited/ECDSAStakeRegistryStorage.sol @@ -13,6 +13,7 @@ import {IAllocationManager} from import {IAVSRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IAVSRegistrar.sol"; import {IAVSDirectoryTypes} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; import {IAVSDirectory} from "./ECDSAStakeRegistry.sol"; + abstract contract ECDSAStakeRegistryStorage is IECDSAStakeRegistry { /// @notice Manages staking delegations through the DelegationManager interface IDelegationManager internal immutable DELEGATION_MANAGER; @@ -79,4 +80,4 @@ abstract contract ECDSAStakeRegistryStorage is IECDSAStakeRegistry { /// @dev Reserves storage slots for future upgrades // solhint-disable-next-line uint256[40] private __gap; -} \ No newline at end of file +} diff --git a/src/unaudited/examples/ECDSAStakeRegistryEqualWeight.sol b/src/unaudited/examples/ECDSAStakeRegistryEqualWeight.sol index adfc1dab..985cb6ee 100644 --- a/src/unaudited/examples/ECDSAStakeRegistryEqualWeight.sol +++ b/src/unaudited/examples/ECDSAStakeRegistryEqualWeight.sol @@ -56,4 +56,4 @@ contract ECDSAStakeRegistryEqualWeight is ECDSAStakeRegistryPermissioned { emit OperatorWeightUpdated(_operator, oldWeight, newWeight); return delta; } -} \ No newline at end of file +} diff --git a/src/unaudited/examples/ECDSAStakeRegistryPermissioned.sol b/src/unaudited/examples/ECDSAStakeRegistryPermissioned.sol index 02a259a3..4a504705 100644 --- a/src/unaudited/examples/ECDSAStakeRegistryPermissioned.sol +++ b/src/unaudited/examples/ECDSAStakeRegistryPermissioned.sol @@ -1,4 +1,3 @@ - // SPDX-License-Identifier: MIT pragma solidity ^0.8.27; @@ -8,10 +7,8 @@ import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; import {IAllocationManager} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; -import {IAVSDirectory} from - "../ECDSAStakeRegistry.sol"; -import {IAVSDirectoryTypes} from - "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; +import {IAVSDirectory} from "../ECDSAStakeRegistry.sol"; +import {IAVSDirectoryTypes} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; /// @title ECDSA Stake Registry with an Operator Allowlist /// @dev THIS CONTRACT IS NOT AUDITED @@ -76,7 +73,7 @@ contract ECDSAStakeRegistryPermissioned is ECDSAStakeRegistry { function _ejectOperator( address _operator ) internal { - if(!operatorRegistered(_operator)){ + if (!operatorRegistered(_operator)) { revert OperatorNotRegistered(); } @@ -113,7 +110,7 @@ contract ECDSAStakeRegistryPermissioned is ECDSAStakeRegistry { } delete allowlistedOperators[_operator]; emit OperatorRevoked(_operator); - if(operatorRegistered(_operator)){ + if (operatorRegistered(_operator)) { _ejectOperator(_operator); } } diff --git a/test/mocks/ECDSAServiceManagerMock.sol b/test/mocks/ECDSAServiceManagerMock.sol index a05f2232..091bdcd7 100644 --- a/test/mocks/ECDSAServiceManagerMock.sol +++ b/test/mocks/ECDSAServiceManagerMock.sol @@ -36,5 +36,4 @@ contract ECDSAServiceManagerMock is ECDSAServiceManagerBase { // ) public virtual initializer { // __ServiceManagerBase_init(initialOwner, rewardsInitiator); // } - } diff --git a/test/mocks/ECDSAStakeRegistryMock.sol b/test/mocks/ECDSAStakeRegistryMock.sol index da670557..19007010 100644 --- a/test/mocks/ECDSAStakeRegistryMock.sol +++ b/test/mocks/ECDSAStakeRegistryMock.sol @@ -15,5 +15,4 @@ contract ECDSAStakeRegistryMock is ECDSAStakeRegistry { address _avsRegistrar, IAVSDirectory _avsDirectory ) ECDSAStakeRegistry(_delegationManager, _allocationManager, _avsRegistrar, _avsDirectory) {} - } diff --git a/test/unit/ECDSAServiceManager.t.sol b/test/unit/ECDSAServiceManager.t.sol index f8e043df..c47d828c 100644 --- a/test/unit/ECDSAServiceManager.t.sol +++ b/test/unit/ECDSAServiceManager.t.sol @@ -23,11 +23,21 @@ import {IAVSDirectory, IAVSDirectoryTypes} from "../../src/unaudited/ECDSAStakeR import {OperatorSet} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol"; contract MockPermissionController { - function addPendingAdmin(address account, address admin) external {} - function removePendingAdmin(address account, address admin) external {} - function removeAdmin(address account, address admin) external {} - function setAppointee(address account, address appointee, address target, bytes4 selector) external {} - function removeAppointee(address account, address appointee, address target, bytes4 selector) external {} + function addPendingAdmin(address account, address admin) external {} + function removePendingAdmin(address account, address admin) external {} + function removeAdmin(address account, address admin) external {} + function setAppointee( + address account, + address appointee, + address target, + bytes4 selector + ) external {} + function removeAppointee( + address account, + address appointee, + address target, + bytes4 selector + ) external {} } contract MockDelegationManager { @@ -49,12 +59,16 @@ contract MockDelegationManager { contract MockAllocationManager { function setAVSRegistrar(address avs, address registrar) external {} - - function isOperatorSet(OperatorSet memory operatorSet) external pure returns (bool) { + + function isOperatorSet( + OperatorSet memory operatorSet + ) external pure returns (bool) { return true; } - - function getStrategiesInOperatorSet(OperatorSet memory operatorSet) external pure returns (IStrategy[] memory) { + + function getStrategiesInOperatorSet( + OperatorSet memory operatorSet + ) external pure returns (IStrategy[] memory) { IStrategy[] memory strategies = new IStrategy[](2); strategies[0] = IStrategy(address(900)); strategies[1] = IStrategy(address(901)); @@ -80,22 +94,24 @@ contract MockRewardsCoordinator { contract MockAVSDirectory { // 使用 mapping 存储每个 operator 的状态 - mapping(address => mapping(address => IAVSDirectoryTypes.OperatorAVSRegistrationStatus)) - private operatorStatus; + mapping(address => mapping(address => IAVSDirectoryTypes.OperatorAVSRegistrationStatus)) private + operatorStatus; function registerOperatorToAVS( address operator, ISignatureUtils.SignatureWithSaltAndExpiry memory ) external { // 设置特定 operator 的状态为 REGISTERED - operatorStatus[msg.sender][operator] = IAVSDirectoryTypes.OperatorAVSRegistrationStatus.REGISTERED; + operatorStatus[msg.sender][operator] = + IAVSDirectoryTypes.OperatorAVSRegistrationStatus.REGISTERED; } function deregisterOperatorFromAVS( address operator ) external { // 设置特定 operator 的状态为 UNREGISTERED - operatorStatus[msg.sender][operator] = IAVSDirectoryTypes.OperatorAVSRegistrationStatus.UNREGISTERED; + operatorStatus[msg.sender][operator] = + IAVSDirectoryTypes.OperatorAVSRegistrationStatus.UNREGISTERED; } function updateAVSMetadataURI( @@ -103,13 +119,13 @@ contract MockAVSDirectory { ) external pure {} function setAvsOperatorStatus( - address avs, - address operator, + address avs, + address operator, IAVSDirectoryTypes.OperatorAVSRegistrationStatus status ) external { operatorStatus[avs][operator] = status; } - + function avsOperatorStatus( address avs, address operator @@ -139,13 +155,12 @@ contract ECDSAServiceManagerSetup is Test { mockAVSDirectory = new AVSDirectoryMock(); mockAllocationManager = new MockAllocationManager(); mockAVSRegistrarAddr = makeAddr("mockAVSRegistrar"); - mockStakeRegistry = - new ECDSAStakeRegistryMock( - IDelegationManager(address(mockDelegationManager)), - IAllocationManager(address(mockAllocationManager)), - mockAVSRegistrarAddr, - IAVSDirectory(address(mockAVSDirectory)) - ); + mockStakeRegistry = new ECDSAStakeRegistryMock( + IDelegationManager(address(mockDelegationManager)), + IAllocationManager(address(mockAllocationManager)), + mockAVSRegistrarAddr, + IAVSDirectory(address(mockAVSDirectory)) + ); mockRewardsCoordinator = new MockRewardsCoordinator(); mockPermissionController = new MockPermissionController(); @@ -283,49 +298,49 @@ contract ECDSAServiceManagerSetup is Test { function testGetOperatorSetStrategies() public { uint32 operatorSetId = 1; - + address[] memory strategies = serviceManager.getOperatorSetStrategies(operatorSetId); - + assertEq(strategies.length, 2, "Should return 2 strategies"); assertEq(strategies[0], address(900), "First strategy should match"); assertEq(strategies[1], address(901), "Second strategy should match"); } - + function testAddPendingAdmin() public { address admin = makeAddr("admin"); - + vm.prank(owner); serviceManager.addPendingAdmin(admin); } - + function testRemovePendingAdmin() public { address pendingAdmin = makeAddr("pendingAdmin"); - + vm.prank(owner); serviceManager.removePendingAdmin(pendingAdmin); } - + function testRemoveAdmin() public { address admin = makeAddr("admin"); - + vm.prank(owner); serviceManager.removeAdmin(admin); } - + function testSetAppointee() public { address appointee = makeAddr("appointee"); address target = makeAddr("target"); bytes4 selector = bytes4(keccak256("someFunction()")); - + vm.prank(owner); serviceManager.setAppointee(appointee, target, selector); } - + function testRemoveAppointee() public { address appointee = makeAddr("appointee"); address target = makeAddr("target"); bytes4 selector = bytes4(keccak256("someFunction()")); - + vm.prank(owner); serviceManager.removeAppointee(appointee, target, selector); } diff --git a/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol b/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol index 8a688f9a..c5c681a8 100644 --- a/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol +++ b/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol @@ -5,7 +5,8 @@ import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISi import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; -import {IAllocationManager} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {IAllocationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; import { IECDSAStakeRegistry, @@ -21,31 +22,33 @@ contract EqualWeightECDSARegistry is ECDSAStakeRegistrySetup { ECDSAStakeRegistryEqualWeight internal fixedWeightRegistry; address internal operator6 = makeAddr("operator6"); address internal operator7 = makeAddr("operator7"); + function setUp() public virtual override { super.setUp(); - fixedWeightRegistry = - new ECDSAStakeRegistryEqualWeight( - IDelegationManager(address(mockDelegationManager)), - IAllocationManager(address(mockAllocationManager)), - mockAVSRegistrarAddr, - IAVSDirectory(address(mockAVSDirectory)) - ); - + fixedWeightRegistry = new ECDSAStakeRegistryEqualWeight( + IDelegationManager(address(mockDelegationManager)), + IAllocationManager(address(mockAllocationManager)), + mockAVSRegistrarAddr, + IAVSDirectory(address(mockAVSDirectory)) + ); + IStrategy mockStrategy = IStrategy(address(0x1234)); - IECDSAStakeRegistryTypes.Quorum memory quorum = - IECDSAStakeRegistryTypes.Quorum({strategies: new IECDSAStakeRegistryTypes.StrategyParams[](1)}); - quorum.strategies[0] = IECDSAStakeRegistryTypes.StrategyParams({strategy: mockStrategy, multiplier: 10000}); - + IECDSAStakeRegistryTypes.Quorum memory quorum = IECDSAStakeRegistryTypes.Quorum({ + strategies: new IECDSAStakeRegistryTypes.StrategyParams[](1) + }); + quorum.strategies[0] = + IECDSAStakeRegistryTypes.StrategyParams({strategy: mockStrategy, multiplier: 10000}); + fixedWeightRegistry.initialize(address(mockServiceManager), 100, quorum); fixedWeightRegistry.permitOperator(operator6); fixedWeightRegistry.permitOperator(operator7); - + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; - + vm.prank(operator6); fixedWeightRegistry.registerOperatorM2Quorum(operatorSignature, operator6); - + vm.prank(operator7); fixedWeightRegistry.registerOperatorM2Quorum(operatorSignature, operator7); } diff --git a/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol b/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol index fc7026f7..ebcb9b7a 100644 --- a/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol +++ b/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol @@ -5,7 +5,8 @@ import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISi import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; -import {IAllocationManager} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {IAllocationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; import { IECDSAStakeRegistry, @@ -21,34 +22,36 @@ contract PermissionedECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { ECDSAStakeRegistryPermissioned internal permissionedRegistry; address internal operator6 = makeAddr("operator6"); address internal operator7 = makeAddr("operator7"); + function setUp() public virtual override { super.setUp(); - permissionedRegistry = - new ECDSAStakeRegistryPermissioned( - IDelegationManager(address(mockDelegationManager)), - IAllocationManager(address(mockAllocationManager)), - mockAVSRegistrarAddr, - IAVSDirectory(address(mockAVSDirectory)) - ); - + permissionedRegistry = new ECDSAStakeRegistryPermissioned( + IDelegationManager(address(mockDelegationManager)), + IAllocationManager(address(mockAllocationManager)), + mockAVSRegistrarAddr, + IAVSDirectory(address(mockAVSDirectory)) + ); + IStrategy mockStrategy = IStrategy(address(0x1234)); - IECDSAStakeRegistryTypes.Quorum memory quorum = - IECDSAStakeRegistryTypes.Quorum({strategies: new IECDSAStakeRegistryTypes.StrategyParams[](1)}); - quorum.strategies[0] = IECDSAStakeRegistryTypes.StrategyParams({strategy: mockStrategy, multiplier: 10000}); - + IECDSAStakeRegistryTypes.Quorum memory quorum = IECDSAStakeRegistryTypes.Quorum({ + strategies: new IECDSAStakeRegistryTypes.StrategyParams[](1) + }); + quorum.strategies[0] = + IECDSAStakeRegistryTypes.StrategyParams({strategy: mockStrategy, multiplier: 10000}); + permissionedRegistry.initialize(address(mockServiceManager), 100, quorum); permissionedRegistry.permitOperator(operator6); permissionedRegistry.permitOperator(operator7); - + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; - + vm.prank(operator6); permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator6); - + vm.prank(operator7); permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator7); - + vm.roll(block.number + 1); } diff --git a/test/unit/ECDSAStakeRegistryUnit.t.sol b/test/unit/ECDSAStakeRegistryUnit.t.sol index f37006f7..0a39e637 100644 --- a/test/unit/ECDSAStakeRegistryUnit.t.sol +++ b/test/unit/ECDSAStakeRegistryUnit.t.sol @@ -15,16 +15,20 @@ import { IECDSAStakeRegistryTypes, IECDSAStakeRegistryEvents } from "../../src/interfaces/IECDSAStakeRegistry.sol"; -import {IAllocationManager} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {IAllocationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; import {IAVSDirectory, IAVSDirectoryTypes} from "../../src/unaudited/ECDSAStakeRegistry.sol"; import {OperatorSet} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol"; - contract MockServiceManager { MockAVSDirectory public immutable avsDirectory; - constructor(address _avsDirectory) { + + constructor( + address _avsDirectory + ) { avsDirectory = MockAVSDirectory(_avsDirectory); } + function deregisterOperatorFromAVS( address operator ) external { @@ -59,15 +63,15 @@ contract MockDelegationManager { contract MockAVSDirectory { error OperatorAlreadyRegistered(); error OperatorNotRegistered(); - - // 简化为只记录operator的状态 + mapping(address => IAVSDirectoryTypes.OperatorAVSRegistrationStatus) private operatorStatus; function registerOperatorToAVS( address operator, ISignatureUtils.SignatureWithSaltAndExpiry memory ) external { - if(operatorStatus[operator] == IAVSDirectoryTypes.OperatorAVSRegistrationStatus.REGISTERED) { + if (operatorStatus[operator] == IAVSDirectoryTypes.OperatorAVSRegistrationStatus.REGISTERED) + { revert OperatorAlreadyRegistered(); } operatorStatus[operator] = IAVSDirectoryTypes.OperatorAVSRegistrationStatus.REGISTERED; @@ -76,7 +80,10 @@ contract MockAVSDirectory { function deregisterOperatorFromAVS( address operator ) external { - if(operatorStatus[operator] == IAVSDirectoryTypes.OperatorAVSRegistrationStatus.UNREGISTERED) { + if ( + operatorStatus[operator] + == IAVSDirectoryTypes.OperatorAVSRegistrationStatus.UNREGISTERED + ) { revert OperatorNotRegistered(); } operatorStatus[operator] = IAVSDirectoryTypes.OperatorAVSRegistrationStatus.UNREGISTERED; @@ -87,13 +94,13 @@ contract MockAVSDirectory { ) external pure {} function setAvsOperatorStatus( - address avs, - address operator, + address avs, + address operator, IAVSDirectoryTypes.OperatorAVSRegistrationStatus status ) external { operatorStatus[operator] = status; } - + function avsOperatorStatus( address avs, address operator @@ -103,18 +110,15 @@ contract MockAVSDirectory { } contract MockAllocationManager { - // 定义 Allocation 结构体 struct Allocation { uint64 currentMagnitude; uint32 lastUpdatedBlock; } - // 存储 allocation 信息 mapping(address => mapping(bytes32 => Allocation)) public allocations; mapping(address => mapping(address => uint64)) public maxMagnitudes; mapping(address => mapping(address => mapping(uint32 => bool))) public operatorSetMembers; - // 设置 operator set membership function setOperatorSetMembership( address operator, address avs, @@ -123,8 +127,7 @@ contract MockAllocationManager { ) external { operatorSetMembers[operator][avs][setId] = isMember; } - - // 检查 operator set membership + function isMemberOfOperatorSet( address operator, OperatorSet memory operatorSet @@ -132,7 +135,6 @@ contract MockAllocationManager { return operatorSetMembers[operator][operatorSet.avs][operatorSet.id]; } - // 设置 allocation function setAllocation( address operator, OperatorSet memory operatorSet, @@ -143,7 +145,6 @@ contract MockAllocationManager { allocations[operator][key] = allocation; } - // 获取 allocation function getAllocation( address operator, OperatorSet memory operatorSet, @@ -153,31 +154,25 @@ contract MockAllocationManager { return allocations[operator][key]; } - // 设置 maxMagnitude - function setMaxMagnitude( - address operator, - address strategy, - uint64 magnitude - ) external { + function setMaxMagnitude(address operator, address strategy, uint64 magnitude) external { maxMagnitudes[operator][strategy] = magnitude; } - // 获取 maxMagnitude - function getMaxMagnitude( - address operator, - IStrategy strategy - ) external view returns (uint64) { + function getMaxMagnitude(address operator, IStrategy strategy) external view returns (uint64) { return maxMagnitudes[operator][address(strategy)]; } - // 其他必要的函数 function setAVSRegistrar(address avs, address registrar) external {} - - function isOperatorSet(OperatorSet memory operatorSet) external pure returns (bool) { + + function isOperatorSet( + OperatorSet memory operatorSet + ) external pure returns (bool) { return true; } - - function getStrategiesInOperatorSet(OperatorSet memory operatorSet) external pure returns (IStrategy[] memory) { + + function getStrategiesInOperatorSet( + OperatorSet memory operatorSet + ) external pure returns (IStrategy[] memory) { IStrategy[] memory strategies = new IStrategy[](2); strategies[0] = IStrategy(address(900)); strategies[1] = IStrategy(address(901)); @@ -212,117 +207,83 @@ contract ECDSAStakeRegistrySetup is Test, IECDSAStakeRegistryEvents { function setUp() public virtual { (operator1, operator1Pk) = makeAddrAndKey("Signer 1"); (operator2, operator2Pk) = makeAddrAndKey("Signer 2"); - (operator3, operator3Pk) = makeAddrAndKey("Signer 3"); + (operator3, operator3Pk) = makeAddrAndKey("Signer 3"); (operator4, operator4Pk) = makeAddrAndKey("Signer 4"); - - // 1. 先创建 mockAVSDirectory + mockAVSDirectory = new MockAVSDirectory(); - - // 2. 再创建 mockServiceManager,并传入 mockAVSDirectory 的地址 mockServiceManager = new MockServiceManager(address(mockAVSDirectory)); - - // 3. 创建其他 mock 合约 + mockDelegationManager = new MockDelegationManager(); mockAllocationManager = new MockAllocationManager(); mockAVSRegistrarAddr = makeAddr("mockAVSRegistrar"); - + IStrategy mockStrategy = IStrategy(address(0x1234)); IECDSAStakeRegistryTypes.Quorum memory quorum = IECDSAStakeRegistryTypes.Quorum({ strategies: new IECDSAStakeRegistryTypes.StrategyParams[](1) }); - quorum.strategies[0] = IECDSAStakeRegistryTypes.StrategyParams({ - strategy: mockStrategy, - multiplier: 10000 - }); - + quorum.strategies[0] = + IECDSAStakeRegistryTypes.StrategyParams({strategy: mockStrategy, multiplier: 10000}); + registry = new ECDSAStakeRegistry( IDelegationManager(address(mockDelegationManager)), IAllocationManager(address(mockAllocationManager)), mockAVSRegistrarAddr, IAVSDirectory(address(mockAVSDirectory)) ); - + vm.prank(owner); registry.initialize(address(mockServiceManager), 100, quorum); // Set up 3 operator sets uint32[] memory operatorSetIds = new uint32[](3); operatorSetIds[0] = 1; - operatorSetIds[1] = 2; + operatorSetIds[1] = 2; operatorSetIds[2] = 3; vm.prank(owner); registry.setCurrentOperatorSetIds(operatorSetIds); // Set up operator set membership - for(uint32 setId = 1; setId <= 3; setId++) { + for (uint32 setId = 1; setId <= 3; setId++) { MockAllocationManager(address(mockAllocationManager)).setOperatorSetMembership( - operator3, - address(mockServiceManager), - setId, - true + operator3, address(mockServiceManager), setId, true ); MockAllocationManager(address(mockAllocationManager)).setOperatorSetMembership( - operator4, - address(mockServiceManager), - setId, - true + operator4, address(mockServiceManager), setId, true ); - // 设置 allocation 和 maxMagnitude - OperatorSet memory operatorSet = OperatorSet({ - avs: address(mockServiceManager), - id: setId - }); - - // 为每个 strategy 设置 allocation - IStrategy[] memory strategies = mockAllocationManager.getStrategiesInOperatorSet(operatorSet); - for(uint i = 0; i < strategies.length; i++) { - // 设置 allocation - MockAllocationManager.Allocation memory allocation = MockAllocationManager.Allocation({ - currentMagnitude: 1000, - lastUpdatedBlock: uint32(block.number) - }); - + OperatorSet memory operatorSet = + OperatorSet({avs: address(mockServiceManager), id: setId}); + + IStrategy[] memory strategies = + mockAllocationManager.getStrategiesInOperatorSet(operatorSet); + for (uint256 i = 0; i < strategies.length; i++) { + MockAllocationManager.Allocation memory allocation = MockAllocationManager + .Allocation({currentMagnitude: 1000, lastUpdatedBlock: uint32(block.number)}); + MockAllocationManager(address(mockAllocationManager)).setAllocation( - operator3, - operatorSet, - address(strategies[i]), - allocation + operator3, operatorSet, address(strategies[i]), allocation ); MockAllocationManager(address(mockAllocationManager)).setAllocation( - operator4, - operatorSet, - address(strategies[i]), - allocation + operator4, operatorSet, address(strategies[i]), allocation ); - - // 设置 maxMagnitude + MockAllocationManager(address(mockAllocationManager)).setMaxMagnitude( - operator3, - address(strategies[i]), - 1000 + operator3, address(strategies[i]), 1000 ); MockAllocationManager(address(mockAllocationManager)).setMaxMagnitude( - operator4, - address(strategies[i]), - 1000 + operator4, address(strategies[i]), 1000 ); } } - - ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; - vm.prank(operator1); registry.registerOperatorM2Quorum(operatorSignature, operator1); - vm.prank(operator2); registry.registerOperatorM2Quorum(operatorSignature, operator2); - vm.roll(block.number + 1); } } From 96d36e032a9770d1046e2029cc92f65aac4b95b5 Mon Sep 17 00:00:00 2001 From: jaxxjj Date: Sat, 1 Mar 2025 00:30:42 -0700 Subject: [PATCH 04/12] feat: add operator strategy config and test cases --- src/interfaces/IECDSAStakeRegistry.sol | 11 + src/unaudited/ECDSAStakeRegistry.sol | 146 +++-- src/unaudited/ECDSAStakeRegistryStorage.sol | 3 + .../ECDSAStakeRegistryRatioWeight.sol | 75 +++ test/unit/ECDSAServiceManager.t.sol | 35 +- .../ECDSAStakeRegistryEqualWeightUnit.t.sol | 30 +- .../ECDSAStakeRegistryPermissionedUnit.t.sol | 33 +- .../ECDSAStakeRegistryRatioWeightUnit.t.sol | 128 +++++ test/unit/ECDSAStakeRegistryUnit.t.sol | 518 +++++++++++++++--- 9 files changed, 847 insertions(+), 132 deletions(-) create mode 100644 src/unaudited/examples/ECDSAStakeRegistryRatioWeight.sol create mode 100644 test/unit/ECDSAStakeRegistryRatioWeightUnit.t.sol diff --git a/src/interfaces/IECDSAStakeRegistry.sol b/src/interfaces/IECDSAStakeRegistry.sol index 8115164d..ffaf0db2 100644 --- a/src/interfaces/IECDSAStakeRegistry.sol +++ b/src/interfaces/IECDSAStakeRegistry.sol @@ -43,6 +43,10 @@ interface IECDSAStakeRegistryErrors { error M2QuorumRegistrationIsDisabled(); /// @notice Thrown when the operator set ids are invalid. error InvalidOperatorSetIdsLength(); + /// @notice Thrown when the AllocationManager is not correctly configured. + error InvalidAllocationManager(); + /// @notice Thrown when the AllocationManager is already initialized. + error AllocationManagerAlreadyInitialized(); } interface IECDSAStakeRegistryTypes { @@ -135,6 +139,13 @@ interface IECDSAStakeRegistryEvents is IECDSAStakeRegistryTypes { * @notice Emitted when the M2 quorum registration is disabled. */ event M2QuorumRegistrationDisabled(); + + /* + * @notice Emitted when operator set strategy parameters are updated + * @param operatorSetId The ID of the operator set that was updated + * @param params The new strategy parameters for the operator set + */ + event OperatorSetStrategyParamsUpdated(uint32 indexed operatorSetId, StrategyParams[] params); } interface IECDSAStakeRegistry is diff --git a/src/unaudited/ECDSAStakeRegistry.sol b/src/unaudited/ECDSAStakeRegistry.sol index 61beae2d..8d411c15 100644 --- a/src/unaudited/ECDSAStakeRegistry.sol +++ b/src/unaudited/ECDSAStakeRegistry.sol @@ -70,9 +70,13 @@ contract ECDSAStakeRegistry is function initialize( address _serviceManager, uint256 thresholdWeight, - IECDSAStakeRegistryTypes.Quorum memory quorum + IECDSAStakeRegistryTypes.Quorum memory quorum, + uint32[] calldata operatorSetIds, + StrategyParams[][] calldata strategyParamsArray ) external initializer { - __ECDSAStakeRegistry_init(_serviceManager, thresholdWeight, quorum); + __ECDSAStakeRegistry_init( + _serviceManager, thresholdWeight, quorum, operatorSetIds, strategyParamsArray + ); } /// @notice Initializes state for the StakeRegistry @@ -80,11 +84,15 @@ contract ECDSAStakeRegistry is function __ECDSAStakeRegistry_init( address _serviceManagerAddr, uint256 thresholdWeight, - IECDSAStakeRegistryTypes.Quorum memory quorum + IECDSAStakeRegistryTypes.Quorum memory quorum, + uint32[] calldata operatorSetIds, + StrategyParams[][] calldata strategyParamsArray ) internal onlyInitializing { _serviceManager = _serviceManagerAddr; _updateStakeThreshold(thresholdWeight); _updateQuorumConfig(quorum); + _setCurrentOperatorSetIds(operatorSetIds); + _updateOperatorSetsConfig(operatorSetIds, strategyParamsArray); __Ownable_init(); } @@ -117,13 +125,11 @@ contract ECDSAStakeRegistry is address operator, address signingKey ) external onlyAVSRegistrar { - // Update operator weight - _updateOperatorWeight(operator); - - // Update signing key and p2p key + int256 delta = _updateOperatorWeight(operator); + _updateTotalWeight(delta); _updateOperatorSigningKey(operator, signingKey); - if (!operatorRegisteredOnAVSDirectory(operator)) { + _totalOperators++; emit OperatorRegistered(operator, _serviceManager); } } @@ -131,11 +137,10 @@ contract ECDSAStakeRegistry is function onOperatorSetDeregistered( address operator ) external onlyAVSRegistrar { - // Update weights - _updateOperatorWeight(operator); - - // Emit event + int256 delta = _updateOperatorWeight(operator); + _updateTotalWeight(delta); if (!operatorRegisteredOnAVSDirectory(operator)) { + _totalOperators--; emit OperatorDeregistered(operator, _serviceManager); } } @@ -166,6 +171,21 @@ contract ECDSAStakeRegistry is _updateOperators(operators); } + /** + * @notice Updates strategy parameters for multiple operator sets (from stake registry view) + * @notice Strategy params must align with operator set ids on the allocation manager + * @param operatorSetIds Array of operator set IDs to update + * @param strategyParamsArray Array of strategy parameters arrays for each operator set + */ + function updateOperatorSetsConfig( + uint32[] calldata operatorSetIds, + StrategyParams[][] calldata strategyParamsArray, + address[] calldata operators + ) external onlyOwner { + _updateOperatorSetsConfig(operatorSetIds, strategyParamsArray); + _updateOperators(operators); + } + /// @inheritdoc IECDSAStakeRegistry function updateMinimumWeight( uint256 newMinimumWeight, @@ -187,17 +207,20 @@ contract ECDSAStakeRegistry is function setCurrentOperatorSetIds( uint32[] calldata _ids ) external onlyOwner { - if (_ids.length == 0 || _ids.length > 10) { - revert InvalidOperatorSetIdsLength(); - } - currentOperatorSetIds = _ids; + _setCurrentOperatorSetIds(_ids); } /// @notice Sets the allocation manager /// @param _allocationManager The allocation manager to set - function setAllocationManager( + function initAllocationManager( IAllocationManager _allocationManager ) external onlyOwner { + if (address(allocationManager) != address(0)) { + revert AllocationManagerAlreadyInitialized(); + } + if (address(_allocationManager) == address(0)) { + revert InvalidAllocationManager(); + } allocationManager = _allocationManager; } @@ -230,6 +253,15 @@ contract ECDSAStakeRegistry is return currentOperatorSetIds; } + /// @notice Gets the strategy parameters for a specific operator set + /// @param operatorSetId The ID of the operator set to query + /// @return The array of strategy parameters for the operator set + function getOperatorSetConfig( + uint32 operatorSetId + ) external view returns (StrategyParams[] memory) { + return operatorSetStrategyParams[operatorSetId]; + } + /// @inheritdoc IECDSAStakeRegistry function getLatestOperatorSigningKey( address operator @@ -295,10 +327,9 @@ contract ECDSAStakeRegistry is /// @dev Queries mainnet delegation manager for current shares function getOperatorWeight( address _operator - ) public view returns (uint256) { + ) public view virtual returns (uint256) { uint256 quorumWeight = getQuorumWeight(_operator); uint256 operatorSetWeight = getOperatorSetWeight(_operator); - return quorumWeight + operatorSetWeight; } @@ -308,6 +339,9 @@ contract ECDSAStakeRegistry is function getQuorumWeight( address operator ) public view returns (uint256) { + if (!operatorRegisteredOnAVSDirectory(operator)) { + return 0; + } StrategyParams[] memory strategyParams = _quorum.strategies; uint256 weight; IStrategy[] memory strategies = new IStrategy[](strategyParams.length); @@ -337,38 +371,30 @@ contract ECDSAStakeRegistry is /// @return The operator's available weight in set, or 0 if below minimum function getOperatorSetWeight( address operator - ) public view returns (uint256) { - // Return 0 if allocation manager not set + ) public view virtual returns (uint256) { if (address(allocationManager) == address(0)) { return 0; } - uint256 totalWeight; - // Loop through all operator sets for (uint256 setIndex = 0; setIndex < currentOperatorSetIds.length; setIndex++) { - // Create operator set struct for current id + uint32 operatorSetId = currentOperatorSetIds[setIndex]; OperatorSet memory operatorSet = - OperatorSet({avs: address(_serviceManager), id: currentOperatorSetIds[setIndex]}); - - // Check operator set membership + OperatorSet({avs: address(_serviceManager), id: operatorSetId}); if (!allocationManager.isMemberOfOperatorSet(operator, operatorSet)) { continue; } - // Get strategies from operator set IStrategy[] memory strategies = allocationManager.getStrategiesInOperatorSet(operatorSet); if (strategies.length == 0) { continue; } - // Get operator's shares for all strategies + StrategyParams[] memory strategyParams = operatorSetStrategyParams[operatorSetId]; uint256[] memory shares = DELEGATION_MANAGER.getOperatorShares(operator, strategies); - // Calculate available weight for each strategy for (uint256 i = 0; i < strategies.length; i++) { - // Get allocation and max magnitude IAllocationManager.Allocation memory allocation = allocationManager.getAllocation(operator, operatorSet, strategies[i]); uint64 maxMagnitude = allocationManager.getMaxMagnitude(operator, strategies[i]); @@ -377,16 +403,12 @@ contract ECDSAStakeRegistry is continue; } - // Calculate available proportion uint256 slashableProportion = uint256(allocation.currentMagnitude) * WAD / maxMagnitude; - - // Add weighted shares to total - totalWeight += shares[i] * slashableProportion / WAD; + totalWeight += + shares[i] * slashableProportion * strategyParams[i].multiplier / WAD / BPS; } } - - // Return 0 if below minimum weight if (totalWeight >= _minimumWeight) { return totalWeight; } else { @@ -421,7 +443,6 @@ contract ECDSAStakeRegistry is if (address(allocationManager) == address(0)) { return false; } - // Check if operator is registered in any current set for (uint256 i = 0; i < currentOperatorSetIds.length; i++) { OperatorSet memory operatorSet = @@ -631,6 +652,17 @@ contract ECDSAStakeRegistry is } } + /// @notice Internal function to set the current operator set ids + /// @param _ids The ids of the operator sets to set + function _setCurrentOperatorSetIds( + uint32[] calldata _ids + ) internal { + if (_ids.length == 0 || _ids.length > 10) { + revert InvalidOperatorSetIdsLength(); + } + currentOperatorSetIds = _ids; + } + /** * @notice Common logic to verify a batch of ECDSA signatures against a hash, using either last stake weight or at a specific block. * @param digest The hash of the data the signers endorsed. @@ -771,4 +803,42 @@ contract ECDSAStakeRegistry is revert InsufficientSignedStake(); } } + + /// @notice Internal function to update strategy parameters for multiple operator sets + /// @param operatorSetIds Array of operator set IDs to update + /// @param strategyParamsArray Array of strategy parameters arrays for each operator set + function _updateOperatorSetsConfig( + uint32[] calldata operatorSetIds, + StrategyParams[][] calldata strategyParamsArray + ) internal { + if (operatorSetIds.length != strategyParamsArray.length) { + revert InvalidOperatorSetIdsLength(); + } + + for (uint256 i = 0; i < operatorSetIds.length; i++) { + _updateOperatorSetConfig(operatorSetIds[i], strategyParamsArray[i]); + } + } + + /// @notice Internal function to set strategy parameters for an operator set + ///@param operatorSetId The ID of the operator set + ///@param params The strategy parameters to set + + function _updateOperatorSetConfig( + uint32 operatorSetId, + StrategyParams[] memory params + ) internal { + address lastStrategy; + for (uint256 i = 0; i < params.length; i++) { + address currentStrategy = address(params[i].strategy); + if (lastStrategy >= currentStrategy) revert NotSorted(); + lastStrategy = currentStrategy; + } + + delete operatorSetStrategyParams[operatorSetId]; + for (uint256 i = 0; i < params.length; i++) { + operatorSetStrategyParams[operatorSetId].push(params[i]); + } + emit OperatorSetStrategyParamsUpdated(operatorSetId, params); + } } diff --git a/src/unaudited/ECDSAStakeRegistryStorage.sol b/src/unaudited/ECDSAStakeRegistryStorage.sol index 6ebc84b9..b3035ae6 100644 --- a/src/unaudited/ECDSAStakeRegistryStorage.sol +++ b/src/unaudited/ECDSAStakeRegistryStorage.sol @@ -33,6 +33,9 @@ abstract contract ECDSAStakeRegistryStorage is IECDSAStakeRegistry { /// @notice The current operator set ids uint32[] public currentOperatorSetIds; + /// @notice Maps operator set IDs to their strategy parameters + mapping(uint32 => StrategyParams[]) public operatorSetStrategyParams; + /// @notice The total amount of multipliers to weigh stakes uint256 public constant WAD = 1e18; diff --git a/src/unaudited/examples/ECDSAStakeRegistryRatioWeight.sol b/src/unaudited/examples/ECDSAStakeRegistryRatioWeight.sol new file mode 100644 index 00000000..5a0fdb91 --- /dev/null +++ b/src/unaudited/examples/ECDSAStakeRegistryRatioWeight.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {ECDSAStakeRegistry} from "../ECDSAStakeRegistry.sol"; +import {IDelegationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; +import {IAllocationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {IAVSDirectory} from "../ECDSAStakeRegistry.sol"; + +/// @title ECDSAStakeRegistryRatioWeight +/// @notice An example implementation of ECDSAStakeRegistry that allows setting different weight ratios +/// for quorum weight and operator set weight +/// @dev The total weight ratio is 10000 (100%). The quorum weight ratio can be set between 0 and 10000, +/// and the operator set weight ratio will be (10000 - quorumWeightRatio) +contract ECDSAStakeRegistryRatioWeight is ECDSAStakeRegistry { + /// @notice Event emitted when the weight ratio is updated + event WeightRatioUpdated(uint256 oldRatio, uint256 newRatio); + + /// @notice The weight ratio for quorum weight (0-10000) + uint256 private _quorumWeightRatio; + + error InvalidWeightRatio(uint256 ratio); + + constructor( + IDelegationManager _delegationManager, + IAllocationManager _allocationManager, + address _avsRegistrar, + IAVSDirectory _avsDirectory + ) ECDSAStakeRegistry(_delegationManager, _allocationManager, _avsRegistrar, _avsDirectory) {} + + /// @notice External function to set the weight ratio, only callable by owner + /// @param ratio The new ratio (0-10000) + /// @dev The operator set weight ratio will be (10000 - ratio) + function setWeightRatio( + uint256 ratio + ) external onlyOwner { + _setWeightRatio(ratio); + } + + /// @notice Returns the current weight ratio + /// @return The current quorum weight ratio (0-10000) + function getWeightRatio() external view returns (uint256) { + return _quorumWeightRatio; + } + + /// @inheritdoc ECDSAStakeRegistry + /// @dev Overrides the weight calculation to apply the configured ratios + function getOperatorWeight( + address _operator + ) public view virtual override returns (uint256) { + uint256 quorumWeight = getQuorumWeight(_operator); + uint256 operatorSetWeight = getOperatorSetWeight(_operator); + + uint256 weightedQuorumWeight = (quorumWeight * _quorumWeightRatio) / 10000; + uint256 weightedOperatorSetWeight = + (operatorSetWeight * (10000 - _quorumWeightRatio)) / 10000; + + return weightedQuorumWeight + weightedOperatorSetWeight; + } + + /// @notice Sets the weight ratio for quorum weight + /// @param ratio The new ratio (0-10000) + /// @dev The operator set weight ratio will be (10000 - ratio) + function _setWeightRatio( + uint256 ratio + ) internal { + if (ratio > 10000) { + revert InvalidWeightRatio(ratio); + } + uint256 oldRatio = _quorumWeightRatio; + _quorumWeightRatio = ratio; + emit WeightRatioUpdated(oldRatio, ratio); + } +} diff --git a/test/unit/ECDSAServiceManager.t.sol b/test/unit/ECDSAServiceManager.t.sol index c47d828c..69daf8ff 100644 --- a/test/unit/ECDSAServiceManager.t.sol +++ b/test/unit/ECDSAServiceManager.t.sol @@ -10,11 +10,12 @@ import {IRewardsCoordinator} from "eigenlayer-contracts/src/contracts/interfaces/IRewardsCoordinator.sol"; import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; import {IAVSRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IAVSRegistrar.sol"; +import {IECDSAStakeRegistryTypes} from "../../src/interfaces/IECDSAStakeRegistry.sol"; +import {IECDSAStakeRegistry} from "../../src/interfaces/IECDSAStakeRegistry.sol"; import {ECDSAServiceManagerMock} from "../mocks/ECDSAServiceManagerMock.sol"; import {ECDSAStakeRegistryMock} from "../mocks/ECDSAStakeRegistryMock.sol"; import {AVSDirectoryMock} from "../mocks/AVSDirectoryMock.sol"; -import {IECDSAStakeRegistryTypes} from "../../src/interfaces/IECDSAStakeRegistry.sol"; import {IPermissionController} from "eigenlayer-contracts/src/contracts/interfaces/IPermissionController.sol"; import {IAllocationManager} from @@ -193,14 +194,42 @@ contract ECDSAServiceManagerSetup is Test { strategy: IStrategy(address(421)), multiplier: 5000 }); - address[] memory operators = new address[](0); + uint32[] memory operatorSetIds = new uint32[](2); + operatorSetIds[0] = 1; + operatorSetIds[1] = 2; + + IECDSAStakeRegistryTypes.StrategyParams[][] memory strategyParamsArray = + new IECDSAStakeRegistryTypes.StrategyParams[][](2); + + strategyParamsArray[0] = new IECDSAStakeRegistryTypes.StrategyParams[](2); + strategyParamsArray[0][0] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(900)), + multiplier: 3000 + }); + strategyParamsArray[0][1] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(901)), + multiplier: 3000 + }); + // Strategy params for second operator set + strategyParamsArray[1] = new IECDSAStakeRegistryTypes.StrategyParams[](2); + strategyParamsArray[1][0] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(902)), + multiplier: 3000 + }); + strategyParamsArray[1][1] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(903)), + multiplier: 3000 + }); vm.prank(owner); mockStakeRegistry.initialize( address(serviceManager), 10000, // Assuming a threshold weight of 10000 basis points - quorum + quorum, + operatorSetIds, + strategyParamsArray ); + ISignatureUtils.SignatureWithSaltAndExpiry memory dummySignature; vm.prank(operator1); diff --git a/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol b/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol index c5c681a8..076c7393 100644 --- a/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol +++ b/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol @@ -39,7 +39,35 @@ contract EqualWeightECDSARegistry is ECDSAStakeRegistrySetup { quorum.strategies[0] = IECDSAStakeRegistryTypes.StrategyParams({strategy: mockStrategy, multiplier: 10000}); - fixedWeightRegistry.initialize(address(mockServiceManager), 100, quorum); + uint32[] memory operatorSetIds = new uint32[](2); + operatorSetIds[0] = 1; + operatorSetIds[1] = 2; + IECDSAStakeRegistryTypes.StrategyParams[][] memory strategyParamsArray = + new IECDSAStakeRegistryTypes.StrategyParams[][](2); + + strategyParamsArray[0] = new IECDSAStakeRegistryTypes.StrategyParams[](2); + strategyParamsArray[0][0] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(900)), + multiplier: 3000 + }); + strategyParamsArray[0][1] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(901)), + multiplier: 3000 + }); + + strategyParamsArray[1] = new IECDSAStakeRegistryTypes.StrategyParams[](2); + strategyParamsArray[1][0] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(902)), + multiplier: 3000 + }); + strategyParamsArray[1][1] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(903)), + multiplier: 3000 + }); + + fixedWeightRegistry.initialize( + address(mockServiceManager), 100, quorum, operatorSetIds, strategyParamsArray + ); fixedWeightRegistry.permitOperator(operator6); fixedWeightRegistry.permitOperator(operator7); diff --git a/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol b/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol index ebcb9b7a..e5968505 100644 --- a/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol +++ b/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol @@ -39,7 +39,38 @@ contract PermissionedECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { quorum.strategies[0] = IECDSAStakeRegistryTypes.StrategyParams({strategy: mockStrategy, multiplier: 10000}); - permissionedRegistry.initialize(address(mockServiceManager), 100, quorum); + // Create operator set IDs and strategy params + uint32[] memory operatorSetIds = new uint32[](2); + operatorSetIds[0] = 1; + operatorSetIds[1] = 2; + IECDSAStakeRegistryTypes.StrategyParams[][] memory strategyParamsArray = + new IECDSAStakeRegistryTypes.StrategyParams[][](2); + + // Strategy params for first operator set + strategyParamsArray[0] = new IECDSAStakeRegistryTypes.StrategyParams[](2); + strategyParamsArray[0][0] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(900)), // Lower address + multiplier: 3000 + }); + strategyParamsArray[0][1] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(901)), // Higher address + multiplier: 3000 + }); + + // Strategy params for second operator set + strategyParamsArray[1] = new IECDSAStakeRegistryTypes.StrategyParams[](2); + strategyParamsArray[1][0] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(902)), // Lower address + multiplier: 3000 + }); + strategyParamsArray[1][1] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(903)), // Higher address + multiplier: 3000 + }); + + permissionedRegistry.initialize( + address(mockServiceManager), 100, quorum, operatorSetIds, strategyParamsArray + ); permissionedRegistry.permitOperator(operator6); permissionedRegistry.permitOperator(operator7); diff --git a/test/unit/ECDSAStakeRegistryRatioWeightUnit.t.sol b/test/unit/ECDSAStakeRegistryRatioWeightUnit.t.sol new file mode 100644 index 00000000..cd6bf6d7 --- /dev/null +++ b/test/unit/ECDSAStakeRegistryRatioWeightUnit.t.sol @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol"; +import {IDelegationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; +import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; +import {IAllocationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; + +import { + IECDSAStakeRegistry, + IECDSAStakeRegistryTypes, + IECDSAStakeRegistryErrors +} from "../../src/interfaces/IECDSAStakeRegistry.sol"; +import {ECDSAStakeRegistrySetup} from "./ECDSAStakeRegistryUnit.t.sol"; +import {ECDSAStakeRegistryRatioWeight} from + "../../src/unaudited/examples/ECDSAStakeRegistryRatioWeight.sol"; +import {IAVSDirectory} from "../../src/unaudited/ECDSAStakeRegistry.sol"; + +contract RatioWeightECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { + /// @notice Event emitted when the weight ratio is updated + event WeightRatioUpdated(uint256 oldRatio, uint256 newRatio); + + ECDSAStakeRegistryRatioWeight internal ratioWeightRegistry; + + function setUp() public virtual override { + super.setUp(); + ratioWeightRegistry = new ECDSAStakeRegistryRatioWeight( + IDelegationManager(address(mockDelegationManager)), + IAllocationManager(address(mockAllocationManager)), + mockAVSRegistrarAddr, + IAVSDirectory(address(mockAVSDirectory)) + ); + + IStrategy mockStrategy = IStrategy(address(0x1234)); + IECDSAStakeRegistryTypes.Quorum memory quorum = IECDSAStakeRegistryTypes.Quorum({ + strategies: new IECDSAStakeRegistryTypes.StrategyParams[](1) + }); + quorum.strategies[0] = + IECDSAStakeRegistryTypes.StrategyParams({strategy: mockStrategy, multiplier: 10000}); + + uint32[] memory operatorSetIds = new uint32[](2); + operatorSetIds[0] = 1; + operatorSetIds[1] = 2; + IECDSAStakeRegistryTypes.StrategyParams[][] memory strategyParamsArray = + new IECDSAStakeRegistryTypes.StrategyParams[][](2); + + strategyParamsArray[0] = new IECDSAStakeRegistryTypes.StrategyParams[](2); + strategyParamsArray[0][0] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(900)), + multiplier: 3000 + }); + strategyParamsArray[0][1] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(901)), + multiplier: 3000 + }); + + strategyParamsArray[1] = new IECDSAStakeRegistryTypes.StrategyParams[](2); + strategyParamsArray[1][0] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(902)), + multiplier: 3000 + }); + strategyParamsArray[1][1] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(903)), + multiplier: 3000 + }); + ratioWeightRegistry.initialize( + address(mockServiceManager), 100, quorum, operatorSetIds, strategyParamsArray + ); + // Register operator3 and operator4 to M2 quorum + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; + vm.prank(operator3); + ratioWeightRegistry.registerOperatorM2Quorum(operatorSignature, operator3); + vm.prank(operator4); + ratioWeightRegistry.registerOperatorM2Quorum(operatorSignature, operator4); + } + + function test_RevertsWhen_NotOwner_SetWeightRatio() public { + address notOwner = address(0xBEEF); + vm.prank(notOwner); + vm.expectRevert("Ownable: caller is not the owner"); + ratioWeightRegistry.setWeightRatio(5000); + } + + function test_RevertsWhen_InvalidRatio_SetWeightRatio() public { + vm.expectRevert( + abi.encodeWithSelector(ECDSAStakeRegistryRatioWeight.InvalidWeightRatio.selector, 10001) + ); + ratioWeightRegistry.setWeightRatio(10001); + } + + function test_When_Owner_SetWeightRatio() public { + // Test setting ratio to 70% quorum weight, 30% operator set weight + vm.expectEmit(true, true, true, true); + emit WeightRatioUpdated(0, 7000); + ratioWeightRegistry.setWeightRatio(7000); + assertEq(ratioWeightRegistry.getWeightRatio(), 7000); + } + + function test_WeightCalculation_WithDifferentRatios() public { + // Initial weights should be equal since ratio is 0 + uint256 initialWeight = ratioWeightRegistry.getOperatorWeight(operator3); + + // Set ratio to 70% quorum weight, 30% operator set weight + ratioWeightRegistry.setWeightRatio(7000); + uint256 quorumWeight = ratioWeightRegistry.getQuorumWeight(operator3); + uint256 operatorSetWeight = ratioWeightRegistry.getOperatorSetWeight(operator3); + uint256 expectedWeight = (quorumWeight * 7000 / 10000) + (operatorSetWeight * 3000 / 10000); + assertEq(ratioWeightRegistry.getOperatorWeight(operator3), expectedWeight); + + // Change ratio to 30% quorum weight, 70% operator set weight + ratioWeightRegistry.setWeightRatio(3000); + + expectedWeight = (quorumWeight * 3000 / 10000) + (operatorSetWeight * 7000 / 10000); + assertEq(ratioWeightRegistry.getOperatorWeight(operator3), expectedWeight); + } + + function test_WeightCalculation_WithEqualRatio() public { + // Set ratio to 50% quorum weight, 50% operator set weight + ratioWeightRegistry.setWeightRatio(5000); + uint256 quorumWeight = ratioWeightRegistry.getQuorumWeight(operator3); + uint256 operatorSetWeight = ratioWeightRegistry.getOperatorSetWeight(operator3); + + uint256 expectedWeight = (quorumWeight * 5000 / 10000) + (operatorSetWeight * 5000 / 10000); + assertEq(ratioWeightRegistry.getOperatorWeight(operator3), expectedWeight); + } +} diff --git a/test/unit/ECDSAStakeRegistryUnit.t.sol b/test/unit/ECDSAStakeRegistryUnit.t.sol index 0a39e637..9f83dca4 100644 --- a/test/unit/ECDSAStakeRegistryUnit.t.sol +++ b/test/unit/ECDSAStakeRegistryUnit.t.sol @@ -15,8 +15,10 @@ import { IECDSAStakeRegistryTypes, IECDSAStakeRegistryEvents } from "../../src/interfaces/IECDSAStakeRegistry.sol"; -import {IAllocationManager} from - "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import { + IAllocationManager, + IAllocationManagerTypes +} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; import {IAVSDirectory, IAVSDirectoryTypes} from "../../src/unaudited/ECDSAStakeRegistry.sol"; import {OperatorSet} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol"; @@ -110,74 +112,76 @@ contract MockAVSDirectory { } contract MockAllocationManager { - struct Allocation { - uint64 currentMagnitude; - uint32 lastUpdatedBlock; - } + mapping(address => mapping(bytes32 => IAllocationManagerTypes.Allocation)) public allocations; + mapping(address => mapping(IStrategy => uint64)) public maxMagnitudes; + mapping(address => mapping(bytes32 => bool)) public operatorSetMembership; - mapping(address => mapping(bytes32 => Allocation)) public allocations; - mapping(address => mapping(address => uint64)) public maxMagnitudes; - mapping(address => mapping(address => mapping(uint32 => bool))) public operatorSetMembers; + function getAllocation( + address operator, + OperatorSet memory operatorSet, + IStrategy strategy + ) external view returns (IAllocationManagerTypes.Allocation memory) { + bytes32 key = keccak256(abi.encode(operator, operatorSet, address(strategy))); + return allocations[operator][key]; + } - function setOperatorSetMembership( + function setOperatorWeight( address operator, - address avs, - uint32 setId, - bool isMember + OperatorSet memory operatorSet, + address strategy, + uint256 weight ) external { - operatorSetMembers[operator][avs][setId] = isMember; + bytes32 key = keccak256(abi.encode(operator, operatorSet, address(strategy))); + allocations[operator][key] = IAllocationManagerTypes.Allocation({ + currentMagnitude: uint64(weight), + pendingDiff: 0, + effectBlock: 0 + }); + bytes32 membershipKey = keccak256(abi.encode(operator, operatorSet)); + operatorSetMembership[operator][membershipKey] = true; } function isMemberOfOperatorSet( address operator, OperatorSet memory operatorSet ) external view returns (bool) { - return operatorSetMembers[operator][operatorSet.avs][operatorSet.id]; + bytes32 key = keccak256(abi.encode(operator, operatorSet)); + return operatorSetMembership[operator][key]; } - function setAllocation( + function setOperatorSetMembership( address operator, OperatorSet memory operatorSet, - address strategy, - Allocation memory allocation + bool isMember ) external { - bytes32 key = keccak256(abi.encode(operator, operatorSet, strategy)); - allocations[operator][key] = allocation; - } - - function getAllocation( - address operator, - OperatorSet memory operatorSet, - IStrategy strategy - ) external view returns (Allocation memory) { - bytes32 key = keccak256(abi.encode(operator, operatorSet, address(strategy))); - return allocations[operator][key]; - } - - function setMaxMagnitude(address operator, address strategy, uint64 magnitude) external { - maxMagnitudes[operator][strategy] = magnitude; - } - - function getMaxMagnitude(address operator, IStrategy strategy) external view returns (uint64) { - return maxMagnitudes[operator][address(strategy)]; + bytes32 key = keccak256(abi.encode(operator, operatorSet)); + operatorSetMembership[operator][key] = isMember; } - function setAVSRegistrar(address avs, address registrar) external {} - function isOperatorSet( - OperatorSet memory operatorSet + OperatorSet memory ) external pure returns (bool) { return true; } function getStrategiesInOperatorSet( - OperatorSet memory operatorSet + OperatorSet memory ) external pure returns (IStrategy[] memory) { IStrategy[] memory strategies = new IStrategy[](2); strategies[0] = IStrategy(address(900)); strategies[1] = IStrategy(address(901)); return strategies; } + + function setAVSRegistrar(address, address) external {} + + function getMaxMagnitude(address operator, IStrategy strategy) external view returns (uint64) { + return maxMagnitudes[operator][strategy]; + } + + function setMaxMagnitude(address operator, IStrategy strategy, uint64 magnitude) external { + maxMagnitudes[operator][strategy] = magnitude; + } } contract ECDSAStakeRegistrySetup is Test, IECDSAStakeRegistryEvents { @@ -205,8 +209,10 @@ contract ECDSAStakeRegistrySetup is Test, IECDSAStakeRegistryEvents { address public mockAVSRegistrarAddr; function setUp() public virtual { + // operator 1 and 2 are registered on M2 quorum (operator1, operator1Pk) = makeAddrAndKey("Signer 1"); (operator2, operator2Pk) = makeAddrAndKey("Signer 2"); + // operator 3 and 4 are registered on operator sets (operator3, operator3Pk) = makeAddrAndKey("Signer 3"); (operator4, operator4Pk) = makeAddrAndKey("Signer 4"); @@ -232,49 +238,73 @@ contract ECDSAStakeRegistrySetup is Test, IECDSAStakeRegistryEvents { ); vm.prank(owner); - registry.initialize(address(mockServiceManager), 100, quorum); - - // Set up 3 operator sets - uint32[] memory operatorSetIds = new uint32[](3); + uint32[] memory operatorSetIds = new uint32[](2); operatorSetIds[0] = 1; operatorSetIds[1] = 2; - operatorSetIds[2] = 3; + IECDSAStakeRegistryTypes.StrategyParams[][] memory strategyParamsArray = + new IECDSAStakeRegistryTypes.StrategyParams[][](2); + + // Strategy params for first operator set + strategyParamsArray[0] = new IECDSAStakeRegistryTypes.StrategyParams[](2); + strategyParamsArray[0][0] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(900)), + multiplier: 3000 + }); + strategyParamsArray[0][1] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(901)), + multiplier: 3000 + }); + + // Strategy params for second operator set + strategyParamsArray[1] = new IECDSAStakeRegistryTypes.StrategyParams[](2); + strategyParamsArray[1][0] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(902)), + multiplier: 3000 + }); + strategyParamsArray[1][1] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(903)), + multiplier: 3000 + }); + + registry.initialize( + address(mockServiceManager), 100, quorum, operatorSetIds, strategyParamsArray + ); + vm.prank(owner); registry.setCurrentOperatorSetIds(operatorSetIds); - // Set up operator set membership - for (uint32 setId = 1; setId <= 3; setId++) { - MockAllocationManager(address(mockAllocationManager)).setOperatorSetMembership( - operator3, address(mockServiceManager), setId, true - ); - MockAllocationManager(address(mockAllocationManager)).setOperatorSetMembership( - operator4, address(mockServiceManager), setId, true - ); + // Set up operator weights for operator3 and operator4 + address[] memory operators = new address[](2); + operators[0] = operator3; + operators[1] = operator4; + // Set weights for each operator in each set + for (uint32 setId = 1; setId <= 2; setId++) { OperatorSet memory operatorSet = OperatorSet({avs: address(mockServiceManager), id: setId}); + // Get strategies for this set IStrategy[] memory strategies = mockAllocationManager.getStrategiesInOperatorSet(operatorSet); - for (uint256 i = 0; i < strategies.length; i++) { - MockAllocationManager.Allocation memory allocation = MockAllocationManager - .Allocation({currentMagnitude: 1000, lastUpdatedBlock: uint32(block.number)}); - MockAllocationManager(address(mockAllocationManager)).setAllocation( - operator3, operatorSet, address(strategies[i]), allocation - ); - MockAllocationManager(address(mockAllocationManager)).setAllocation( - operator4, operatorSet, address(strategies[i]), allocation - ); - - MockAllocationManager(address(mockAllocationManager)).setMaxMagnitude( - operator3, address(strategies[i]), 1000 - ); - MockAllocationManager(address(mockAllocationManager)).setMaxMagnitude( - operator4, address(strategies[i]), 1000 - ); + // Set weights and maxMagnitudes for each operator and strategy + for (uint256 i = 0; i < operators.length; i++) { + for (uint256 j = 0; j < strategies.length; j++) { + // Set operator weight/allocation + mockAllocationManager.setOperatorWeight( + operators[i], + operatorSet, + address(strategies[j]), + 3e17 // 30% in WAD + ); + mockAllocationManager.setMaxMagnitude(operators[i], strategies[j], 1e18); + } } } + vm.startPrank(mockAVSRegistrarAddr); + registry.onOperatorSetRegistered(operator3, operator3); + registry.onOperatorSetRegistered(operator4, operator3); + vm.stopPrank(); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; @@ -405,17 +435,17 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { } function test_RegisterOperatorM2Quorum() public { - address operator5 = address(0x125); + address operator6 = address(0x125); ISignatureUtils.SignatureWithSaltAndExpiry memory signature; - vm.prank(operator5); - registry.registerOperatorM2Quorum(signature, operator5); - assertTrue(registry.operatorRegistered(operator5)); - assertEq(registry.getLastCheckpointOperatorWeight(operator5), 1000); + vm.prank(operator6); + registry.registerOperatorM2Quorum(signature, operator6); + assertTrue(registry.operatorRegistered(operator6)); + assertEq(registry.getLastCheckpointOperatorWeight(operator6), 1000); } function test_RevertsWhen_AlreadyRegistered_RegisterOperatorM2Quorum() public { assertEq(registry.getLastCheckpointOperatorWeight(operator1), 1000); - assertEq(registry.getLastCheckpointTotalWeight(), 2000); + assertEq(registry.getLastCheckpointTotalWeight(), 2720); ISignatureUtils.SignatureWithSaltAndExpiry memory signature; vm.expectRevert(); @@ -442,13 +472,14 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { function test_DeregisterOperatorM2Quorum() public { assertEq(registry.getLastCheckpointOperatorWeight(operator1), 1000); - assertEq(registry.getLastCheckpointTotalWeight(), 2000); + assertEq(registry.getLastCheckpointTotalWeight(), 2720); vm.prank(operator1); registry.deregisterOperatorM2Quorum(); + console.log("operator1 weight", registry.getLastCheckpointOperatorWeight(operator1)); assertEq(registry.getLastCheckpointOperatorWeight(operator1), 0); - assertEq(registry.getLastCheckpointTotalWeight(), 1000); + assertEq(registry.getLastCheckpointTotalWeight(), 1720); } function test_RevertsWhen_NotOperator_DeregisterOperatorM2Quorum() public { @@ -465,12 +496,12 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { function test_When_OperatorNotRegistered_UpdateOperators() public { address[] memory operators = new address[](3); - address operator5 = address(0xBEEF); + address operator6 = address(0xBEEF); operators[0] = operator1; operators[1] = operator2; - operators[2] = operator5; + operators[2] = operator6; registry.updateOperators(operators); - assertEq(registry.getLastCheckpointOperatorWeight(operator5), 0); + assertEq(registry.getLastCheckpointOperatorWeight(operator6), 0); } function test_When_SingleOperator_UpdateOperators() public { @@ -488,17 +519,17 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { operators[1] = operator2; assertEq(registry.getLastCheckpointOperatorWeight(operator1), 1000); - assertEq(registry.getLastCheckpointTotalWeight(), 2000); + assertEq(registry.getLastCheckpointTotalWeight(), 2720); registry.updateOperators(operators); assertEq(registry.getLastCheckpointOperatorWeight(operator1), 1000); - assertEq(registry.getLastCheckpointTotalWeight(), 2000); + assertEq(registry.getLastCheckpointTotalWeight(), 2720); registry.updateOperators(operators); assertEq(registry.getLastCheckpointOperatorWeight(operator1), 1000); - assertEq(registry.getLastCheckpointTotalWeight(), 2000); + assertEq(registry.getLastCheckpointTotalWeight(), 2720); /// TODO: Need to confirm we always pull the last if the block numbers are the same for checkpoints /// in the getAtBlock function or if we need to prevent this behavior } @@ -644,11 +675,6 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { signatures[0] = abi.encodePacked(r, s, v); (v, r, s) = vm.sign(operator2Pk, msgHash); signatures[1] = abi.encodePacked(r, s, v); - console.log("operator1 weight", registry.getOperatorWeight(operator1)); - console.log("operator2 weight", registry.getOperatorWeight(operator2)); - console.log("threshold weight", registry.getLastCheckpointThresholdWeight()); - console.log("operator1 status", registry.operatorRegisteredOnAVSDirectory(operator1)); - console.log("operator2 status", registry.operatorRegisteredOnAVSDirectory(operator2)); registry.isValidSignature(msgHash, abi.encode(signers, signatures, block.number - 1)); } @@ -1083,4 +1109,318 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { } return (operators, signatures); } + + function test_OnOperatorSetDeregistered() public { + address operator = operator3; + uint256 initialTotalWeight = registry.getLastCheckpointTotalWeight(); + + for (uint32 setId = 1; setId <= 2; setId++) { + OperatorSet memory operatorSet = + OperatorSet({avs: address(mockServiceManager), id: setId}); + IStrategy[] memory strategies = + mockAllocationManager.getStrategiesInOperatorSet(operatorSet); + for (uint256 j = 0; j < strategies.length; j++) { + mockAllocationManager.setOperatorWeight( + operator, + operatorSet, + address(strategies[j]), + 0 // Set currentMagnitude to 0 + ); + } + } + + vm.expectEmit(); + emit OperatorDeregistered(operator, address(mockServiceManager)); + + vm.prank(mockAVSRegistrarAddr); + registry.onOperatorSetDeregistered(operator); + + assertEq(registry.getLastCheckpointOperatorWeight(operator), 0); + assertEq(registry.getLastCheckpointTotalWeight(), initialTotalWeight - 360); + console.log(registry.operatorRegisteredOnAVSDirectory(operator)); + } + + function test_MultipleOperatorRegistration() public { + address operator6 = makeAddr("operator6"); + + address[] memory operators = new address[](1); + operators[0] = operator6; + + for (uint32 setId = 1; setId <= 2; setId++) { + OperatorSet memory operatorSet = + OperatorSet({avs: address(mockServiceManager), id: setId}); + + IStrategy[] memory strategies = + mockAllocationManager.getStrategiesInOperatorSet(operatorSet); + for (uint256 i = 0; i < operators.length; i++) { + for (uint256 j = 0; j < strategies.length; j++) { + // Set operator weight/allocation + mockAllocationManager.setOperatorWeight( + operators[i], + operatorSet, + address(strategies[j]), + 3e17 // 30% in WAD + ); + mockAllocationManager.setMaxMagnitude(operators[i], strategies[j], 1e18); + } + } + } + + uint256 initialTotalWeight = registry.getLastCheckpointTotalWeight(); + for (uint256 i = 0; i < operators.length; i++) { + vm.prank(mockAVSRegistrarAddr); + registry.onOperatorSetRegistered(operators[i], operators[i]); + assertEq(registry.getLastCheckpointOperatorWeight(operators[i]), 360); + } + + assertEq(registry.getLastCheckpointTotalWeight(), initialTotalWeight + 360); + } + + function test_SetAndGetCurrentOperatorSetIds() public { + uint32[] memory newIds = new uint32[](2); + newIds[0] = 3; + newIds[1] = 4; + + vm.prank(owner); + registry.setCurrentOperatorSetIds(newIds); + + uint32[] memory retrievedIds = registry.getCurrentOperatorSetIds(); + assertEq(retrievedIds.length, 2); + assertEq(retrievedIds[0], 3); + assertEq(retrievedIds[1], 4); + } + + function test_RevertsWhen_NotOwner_SetCurrentOperatorSetIds() public { + uint32[] memory newIds = new uint32[](2); + newIds[0] = 3; + newIds[1] = 4; + + address notOwner = makeAddr("notOwner"); + vm.prank(notOwner); + vm.expectRevert("Ownable: caller is not the owner"); + registry.setCurrentOperatorSetIds(newIds); + } + + function test_UpdateOperatorSetsConfig() public { + uint32[] memory operatorSetIds = new uint32[](2); + operatorSetIds[0] = 1; + operatorSetIds[1] = 2; + + StrategyParams[][] memory strategyParamsArray = new StrategyParams[][](2); + + strategyParamsArray[0] = new StrategyParams[](2); + strategyParamsArray[0][0] = + StrategyParams({strategy: IStrategy(address(800)), multiplier: 4000}); + strategyParamsArray[0][1] = + StrategyParams({strategy: IStrategy(address(801)), multiplier: 4000}); + strategyParamsArray[1] = new StrategyParams[](2); + strategyParamsArray[1][0] = + StrategyParams({strategy: IStrategy(address(802)), multiplier: 4000}); + strategyParamsArray[1][1] = + StrategyParams({strategy: IStrategy(address(803)), multiplier: 4000}); + + address[] memory operators = new address[](2); + operators[0] = operator3; + operators[1] = operator4; + + vm.prank(owner); + registry.updateOperatorSetsConfig(operatorSetIds, strategyParamsArray, operators); + StrategyParams[] memory params = registry.getOperatorSetConfig(1); + assertEq(params.length, 2); + assertEq(address(params[0].strategy), address(800)); + assertEq(params[0].multiplier, 4000); + assertEq(address(params[1].strategy), address(801)); + assertEq(params[1].multiplier, 4000); + + params = registry.getOperatorSetConfig(2); + assertEq(params.length, 2); + assertEq(address(params[0].strategy), address(802)); + assertEq(params[0].multiplier, 4000); + assertEq(address(params[1].strategy), address(803)); + assertEq(params[1].multiplier, 4000); + } + + function test_RevertsWhen_NotOwner_UpdateOperatorSetsConfig() public { + uint32[] memory operatorSetIds = new uint32[](1); + operatorSetIds[0] = 1; + + StrategyParams[][] memory strategyParamsArray = new StrategyParams[][](1); + strategyParamsArray[0] = new StrategyParams[](1); + strategyParamsArray[0][0] = + StrategyParams({strategy: IStrategy(address(800)), multiplier: 4000}); + + address[] memory operators = new address[](1); + operators[0] = operator3; + + address notOwner = makeAddr("notOwner"); + vm.prank(notOwner); + vm.expectRevert("Ownable: caller is not the owner"); + registry.updateOperatorSetsConfig(operatorSetIds, strategyParamsArray, operators); + } + + function test_RevertsWhen_LengthMismatch_UpdateOperatorSetsConfig() public { + uint32[] memory operatorSetIds = new uint32[](2); + operatorSetIds[0] = 1; + operatorSetIds[1] = 2; + + StrategyParams[][] memory strategyParamsArray = new StrategyParams[][](1); + strategyParamsArray[0] = new StrategyParams[](1); + strategyParamsArray[0][0] = + StrategyParams({strategy: IStrategy(address(800)), multiplier: 4000}); + + address[] memory operators = new address[](1); + operators[0] = operator3; + + vm.prank(owner); + vm.expectRevert(IECDSAStakeRegistryErrors.InvalidOperatorSetIdsLength.selector); + registry.updateOperatorSetsConfig(operatorSetIds, strategyParamsArray, operators); + } + + function test_RevertsWhen_UnsortedStrategies_UpdateOperatorSetsConfig() public { + uint32[] memory operatorSetIds = new uint32[](1); + operatorSetIds[0] = 1; + + StrategyParams[][] memory strategyParamsArray = new StrategyParams[][](1); + strategyParamsArray[0] = new StrategyParams[](2); + strategyParamsArray[0][0] = StrategyParams({ + strategy: IStrategy(address(801)), // Higher address first + multiplier: 4000 + }); + strategyParamsArray[0][1] = StrategyParams({ + strategy: IStrategy(address(800)), // Lower address second + multiplier: 4000 + }); + + address[] memory operators = new address[](1); + operators[0] = operator3; + + vm.prank(owner); + vm.expectRevert(IECDSAStakeRegistryErrors.NotSorted.selector); + registry.updateOperatorSetsConfig(operatorSetIds, strategyParamsArray, operators); + } + + function test_RegisterOperatorSetAfterM2Quorum() public { + // operator1 is already registered in M2 quorum from setUp() + assertEq(registry.getLastCheckpointOperatorWeight(operator1), 1000); + assertTrue(registry.operatorRegistered(operator1)); + + for (uint32 setId = 1; setId <= 2; setId++) { + OperatorSet memory operatorSet = + OperatorSet({avs: address(mockServiceManager), id: setId}); + + IStrategy[] memory strategies = + mockAllocationManager.getStrategiesInOperatorSet(operatorSet); + for (uint256 j = 0; j < strategies.length; j++) { + mockAllocationManager.setOperatorWeight( + operator1, + operatorSet, + address(strategies[j]), + 3e17 // 30% in WAD + ); + mockAllocationManager.setMaxMagnitude(operator1, strategies[j], 1e18); + } + } + + vm.prank(mockAVSRegistrarAddr); + registry.onOperatorSetRegistered(operator1, operator1); + + assertEq(registry.getLastCheckpointOperatorWeight(operator1), 1360); // 1000 from M2 + 360 from operator sets + assertTrue(registry.operatorRegistered(operator1)); + assertTrue(registry.operatorRegisteredOnAVSDirectory(operator1)); + } + + function test_RegisterM2QuorumAfterOperatorSet() public { + // operator3 is already registered in operator sets from setUp() + assertEq(registry.getLastCheckpointOperatorWeight(operator3), 360); + assertTrue(registry.operatorRegistered(operator3)); + + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; + vm.prank(operator3); + registry.registerOperatorM2Quorum(operatorSignature, operator3); + + assertEq(registry.getLastCheckpointOperatorWeight(operator3), 1360); // 360 from operator sets + 1000 from M2 + assertTrue(registry.operatorRegistered(operator3)); + assertTrue(registry.operatorRegisteredOnAVSDirectory(operator3)); + } + + function test_OperatorRegistrationStatus() public { + // Test operator1: Only M2 quorum registration + assertTrue(registry.operatorRegisteredOnAVSDirectory(operator1)); + assertFalse(registry.operatorRegisteredOnCurrentOperatorSets(operator1)); + assertTrue(registry.operatorRegistered(operator1)); + + // Test operator3: Only Operator Sets registration + assertFalse(registry.operatorRegisteredOnAVSDirectory(operator3)); + assertTrue(registry.operatorRegisteredOnCurrentOperatorSets(operator3)); + assertTrue(registry.operatorRegistered(operator3)); + + // Test unregistered operator + address unregisteredOperator = makeAddr("unregistered"); + assertFalse(registry.operatorRegisteredOnAVSDirectory(unregisteredOperator)); + assertFalse(registry.operatorRegisteredOnCurrentOperatorSets(unregisteredOperator)); + assertFalse(registry.operatorRegistered(unregisteredOperator)); + } + + function test_GetOperatorSetWeight_WithMagnitudeChanges() public { + assertEq(registry.getOperatorSetWeight(operator3), 360); + OperatorSet memory operatorSet = OperatorSet({avs: address(mockServiceManager), id: 1}); + IStrategy[] memory strategies = + mockAllocationManager.getStrategiesInOperatorSet(operatorSet); + + for (uint256 j = 0; j < strategies.length; j++) { + mockAllocationManager.setOperatorWeight( + operator3, + operatorSet, + address(strategies[j]), + 1.5e17 // 15% in WAD (half of original 30%) + ); + } + + address[] memory operators = new address[](1); + operators[0] = operator3; + registry.updateOperators(operators); + + // Weight should be halved for first set (180->90) plus full weight from second set (180) = 270 + assertEq(registry.getOperatorSetWeight(operator3), 270); + mockAllocationManager.setOperatorSetMembership(operator3, operatorSet, false); + registry.updateOperators(operators); + + assertEq(registry.getOperatorSetWeight(operator3), 180); + } + + function test_DisableM2QuorumRegistration() public { + // Verify initial state - can register + address newOperator = makeAddr("newOperator"); + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; + vm.prank(newOperator); + registry.registerOperatorM2Quorum(operatorSignature, newOperator); + assertTrue(registry.operatorRegisteredOnAVSDirectory(newOperator)); + + vm.expectEmit(true, true, true, true); + emit M2QuorumRegistrationDisabled(); + vm.prank(owner); + registry.disableM2QuorumRegistration(); + + // Try to register new operator after disable - should fail + address anotherOperator = makeAddr("anotherOperator"); + vm.prank(anotherOperator); + vm.expectRevert(IECDSAStakeRegistryErrors.M2QuorumRegistrationIsDisabled.selector); + registry.registerOperatorM2Quorum(operatorSignature, anotherOperator); + + assertTrue(registry.operatorRegisteredOnAVSDirectory(newOperator)); + assertTrue(registry.operatorRegisteredOnAVSDirectory(operator1)); + assertTrue(registry.operatorRegisteredOnAVSDirectory(operator2)); + + // Try to disable again - should fail + vm.prank(owner); + vm.expectRevert(IECDSAStakeRegistryErrors.M2QuorumRegistrationIsDisabled.selector); + registry.disableM2QuorumRegistration(); + } + + function test_RevertsWhen_NotOwner_DisableM2QuorumRegistration() public { + address notOwner = makeAddr("notOwner"); + vm.prank(notOwner); + vm.expectRevert("Ownable: caller is not the owner"); + registry.disableM2QuorumRegistration(); + } } From 9f6a8a76ef6a5a29fa1ae086c3b8de51500dfc2a Mon Sep 17 00:00:00 2001 From: jaxxjj Date: Wed, 26 Feb 2025 17:12:02 -0700 Subject: [PATCH 05/12] feat: add operator set support to ecdsa stake registry --- src/interfaces/IECDSAStakeRegistry.sol | 74 ++++- src/unaudited/ECDSAAVSRegistrar.sol | 64 ++++ src/unaudited/ECDSAServiceManagerBase.sol | 129 +++++++- src/unaudited/ECDSAStakeRegistry.sol | 288 ++++++++++++++++-- src/unaudited/ECDSAStakeRegistryStorage.sol | 40 ++- .../ECDSAStakeRegistryEqualWeight.sol | 21 +- .../ECDSAStakeRegistryPermissioned.sol | 29 +- test/mocks/AVSDirectoryMock.sol | 5 + test/mocks/ECDSAServiceManagerMock.sol | 44 +-- test/mocks/ECDSAStakeRegistryMock.sol | 9 +- test/unit/ECDSAServiceManager.t.sol | 121 ++++++-- .../ECDSAStakeRegistryEqualWeightUnit.t.sol | 29 +- .../ECDSAStakeRegistryPermissionedUnit.t.sol | 38 ++- test/unit/ECDSAStakeRegistryUnit.t.sol | 121 ++++++-- 14 files changed, 858 insertions(+), 154 deletions(-) create mode 100644 src/unaudited/ECDSAAVSRegistrar.sol diff --git a/src/interfaces/IECDSAStakeRegistry.sol b/src/interfaces/IECDSAStakeRegistry.sol index e17086f8..7b7dfeeb 100644 --- a/src/interfaces/IECDSAStakeRegistry.sol +++ b/src/interfaces/IECDSAStakeRegistry.sol @@ -37,6 +37,12 @@ interface IECDSAStakeRegistryErrors { error OperatorAlreadyRegistered(); /// @notice Thrown when de-registering or updating the stake for an unregisted operator. error OperatorNotRegistered(); + /// @notice Thrown when the sender is not the AVS Registrar. + error InvalidSender(); + /// @notice Thrown when the M2 quorum registration is disabled. + error M2QuorumRegistrationIsDisabled(); + /// @notice Thrown when the operator set ids are invalid. + error InvalidOperatorSetIdsLength(); } interface IECDSAStakeRegistryTypes { @@ -124,6 +130,11 @@ interface IECDSAStakeRegistryEvents is IECDSAStakeRegistryTypes { address indexed newSigningKey, address oldSigningKey ); + + /* + * @notice Emitted when the M2 quorum registration is disabled. + */ + event M2QuorumRegistrationDisabled(); } interface IECDSAStakeRegistry is @@ -138,7 +149,7 @@ interface IECDSAStakeRegistry is * @param operatorSignature Contains the operator's signature, salt, and expiry. * @param signingKey The signing key to add to the operator's history. */ - function registerOperatorWithSignature( + function registerOperatorM2Quorum( ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature, address signingKey ) external; @@ -146,7 +157,22 @@ interface IECDSAStakeRegistry is /* * @notice Deregisters an existing operator. */ - function deregisterOperator() external; + function deregisterOperatorM2Quorum() external; + + /* + * @notice called by the AVS Registrar when an operator is registered on the allocation manager. + * @param operator The address of the operator. + * @param signingKey The signing key of the operator. + */ + function onOperatorSetRegistered(address operator, address signingKey) external; + + /* + * @notice called by the AVS Registrar when an operator is deregistered on the allocation manager. + * @param operator The address of the operator. + */ + function onOperatorSetDeregistered( + address operator + ) external; /* * @notice Updates the signing key for an operator. @@ -199,6 +225,12 @@ interface IECDSAStakeRegistry is */ function quorum() external view returns (IECDSAStakeRegistryTypes.Quorum memory); + /* + * @notice Retrieves the current operator set id + * @return The current operator set id + */ + function getCurrentOperatorSetIds() external view returns (uint32[] memory); + /* * @notice Retrieves the latest signing key for a given operator. * @param operator The address of the operator. @@ -275,6 +307,42 @@ interface IECDSAStakeRegistry is address operator ) external view returns (uint256); + /* + * @notice Retrieves the operator's weight in the m2 quorum. + * @param operator The address of the operator. + * @return The operator's weight in the m2 quorum. + */ + function getQuorumWeight( + address operator + ) external view returns (uint256); + + /* + * @notice Retrieves the operator's weight in the current operator set. + * @param operator The address of the operator. + * @return The operator's weight in the current operator set. + */ + function getOperatorSetWeight( + address operator + ) external view returns (uint256); + + /* + * @notice Checks if an operator is registered on the AVS Directory(M2). + * @param operator The address of the operator to check. + * @return bool True if the operator is registered on the AVS Directory, false otherwise. + */ + function operatorRegisteredOnAVSDirectory( + address operator + ) external view returns (bool); + + /* + * @notice Checks if an operator is registered on the current operator set. + * @param operator The address of the operator to check. + * @return bool True if the operator is registered on the current operator set, false otherwise. + */ + function operatorRegisteredOnCurrentOperatorSets( + address operator + ) external view returns (bool); + /* * @notice Updates operators for a specific quorum. * @param operatorsPerQuorum Array of operator addresses per quorum. @@ -302,4 +370,4 @@ interface IECDSAStakeRegistry is function getLastCheckpointThresholdWeightAtBlock( uint32 blockNumber ) external view returns (uint256); -} +} \ No newline at end of file diff --git a/src/unaudited/ECDSAAVSRegistrar.sol b/src/unaudited/ECDSAAVSRegistrar.sol new file mode 100644 index 00000000..ce36aa08 --- /dev/null +++ b/src/unaudited/ECDSAAVSRegistrar.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +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 {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {IECDSAStakeRegistry} from "../interfaces/IECDSAStakeRegistry.sol"; +import {IServiceManager} from "../interfaces/IServiceManager.sol"; + +contract AVSRegistrar is IAVSRegistrar, Ownable { + IAllocationManager public allocationManager; + IECDSAStakeRegistry public immutable stakeRegistry; + address public immutable avs; + + error AVSRegistrar__OnlyAllocationManager(); + + modifier onlyAllocationManager() { + if (msg.sender != address(allocationManager)) { + revert AVSRegistrar__OnlyAllocationManager(); + } + _; + } + + constructor(address _allocationManager, address _stakeRegistry, address _avs) Ownable() { + allocationManager = IAllocationManager(_allocationManager); + stakeRegistry = IECDSAStakeRegistry(_stakeRegistry); + avs = _avs; + } + + function registerOperator( + address operator, + address avs, + uint32[] calldata operatorSetIds, + bytes calldata data + ) external onlyAllocationManager { + // Decode signing key + (address signingKey) = abi.decode(data, (address)); + + // Call stake registry to register operator + // Weight check will be done in ECDSAStakeRegistry + stakeRegistry.onOperatorSetRegistered(operator, signingKey); + } + + function deregisterOperator( + address operator, + address avs, + uint32[] calldata operatorSetIds + ) external onlyAllocationManager { + stakeRegistry.onOperatorSetDeregistered(operator); + } + + function updateAllocationManager( + address _allocationManager + ) external onlyOwner { + allocationManager = IAllocationManager(_allocationManager); + } + + function supportsAVS( + address avsAddr + ) external view returns (bool){ + return avs == avsAddr; + } +} diff --git a/src/unaudited/ECDSAServiceManagerBase.sol b/src/unaudited/ECDSAServiceManagerBase.sol index d5bf2c55..d2358442 100644 --- a/src/unaudited/ECDSAServiceManagerBase.sol +++ b/src/unaudited/ECDSAServiceManagerBase.sol @@ -17,8 +17,13 @@ import {IRewardsCoordinator} from import {IECDSAStakeRegistryTypes} from "../interfaces/IECDSAStakeRegistry.sol"; import {ECDSAStakeRegistry} from "../unaudited/ECDSAStakeRegistry.sol"; import {IAVSRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IAVSRegistrar.sol"; -import {IAllocationManager} from - "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {IPermissionController} from + "eigenlayer-contracts/src/contracts/interfaces/IPermissionController.sol"; +import { + IAllocationManager, + IAllocationManagerTypes +} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {OperatorSet} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol"; abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable { using SafeERC20 for IERC20; @@ -38,6 +43,9 @@ abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable /// @notice Address of the delegation manager contract, which manages staker delegations to operators. address internal immutable delegationManager; + /// @notice Address of the permission controller contract, which manages permissions for the service manager. + address internal immutable permissionController; + /// @notice Address of the rewards initiator, which is allowed to create AVS rewards submissions. address public rewardsInitiator; @@ -74,13 +82,15 @@ abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable address _stakeRegistry, address _rewardsCoordinator, address _delegationManager, - address _allocationManager + address _allocationManager, + address _permissionController ) { avsDirectory = _avsDirectory; stakeRegistry = _stakeRegistry; rewardsCoordinator = _rewardsCoordinator; delegationManager = _delegationManager; allocationManager = _allocationManager; + permissionController = _permissionController; _disableInitializers(); } @@ -139,6 +149,81 @@ abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable _deregisterOperatorFromAVS(operator); } + /// @notice Deregisters an operator from a set of operator sets. + /// @dev This function is used to deregister an operator from a set of operator sets. + /// @param operator The address of the operator to deregister. + /// @param operatorSetIds The set of operator set ids to deregister from. + function deregisterOperatorFromOperatorSets( + address operator, + uint32[] memory operatorSetIds + ) external virtual onlyStakeRegistry { + IAllocationManager.DeregisterParams memory params = IAllocationManagerTypes.DeregisterParams({ + operator: operator, + avs: address(this), + operatorSetIds: operatorSetIds + }); + IAllocationManager(allocationManager).deregisterFromOperatorSets(params); + } + + /// @inheritdoc IServiceManager + + function addPendingAdmin( + address admin + ) external virtual onlyOwner { + IPermissionController(permissionController).addPendingAdmin({ + account: address(this), + admin: admin + }); + } + + /// @inheritdoc IServiceManager + function removePendingAdmin( + address pendingAdmin + ) external virtual onlyOwner { + IPermissionController(permissionController).removePendingAdmin({ + account: address(this), + admin: pendingAdmin + }); + } + + /// @inheritdoc IServiceManager + function removeAdmin( + address admin + ) external virtual onlyOwner { + IPermissionController(permissionController).removeAdmin({ + account: address(this), + admin: admin + }); + } + + /// @inheritdoc IServiceManager + function setAppointee( + address appointee, + address target, + bytes4 selector + ) external virtual onlyOwner { + IPermissionController(permissionController).setAppointee({ + account: address(this), + appointee: appointee, + target: target, + selector: selector + }); + } + + /// @inheritdoc IServiceManager + function removeAppointee( + address appointee, + address target, + bytes4 selector + ) external virtual onlyOwner { + IPermissionController(permissionController).removeAppointee({ + account: address(this), + appointee: appointee, + target: target, + selector: selector + }); + } + /// @inheritdoc IServiceManagerUI function getRestakeableStrategies() external view virtual returns (address[] memory) { return _getRestakeableStrategies(); @@ -151,6 +236,16 @@ abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable return _getOperatorRestakedStrategies(_operator); } + /** + * @notice Returns the strategies from a specific operator set for this AVS + * @dev Uses AllocationManager to fetch strategies from a single operator set + * @param operatorSetId The ID of the operator set to query + * @return Array of strategy addresses from the specified operator set + */ + function getOperatorSetStrategies(uint32 operatorSetId) external view virtual returns (address[] memory) { + return _getOperatorSetStrategies(operatorSetId); + } + /** * @notice Forwards the call to update AVS metadata URI in the AVSDirectory contract. * @dev This internal function is a proxy to the `updateAVSMetadataURI` function of the AVSDirectory contract. @@ -263,6 +358,32 @@ abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable return strategies; } + /** + * @notice Retrieves the addresses of strategies from a specific operator set + * @dev Returns an empty array if the operator set is invalid + * @param operatorSetId The ID of the operator set to get strategies from + * @return Array of strategy addresses from the specified operator set + */ + function _getOperatorSetStrategies(uint32 operatorSetId) internal view virtual returns (address[] memory) { + OperatorSet memory operatorSet = OperatorSet(address(this), operatorSetId); + + // Return empty array if this is not a valid operator set + if (!IAllocationManager(allocationManager).isOperatorSet(operatorSet)) { + return new address[](0); + } + + // Get strategies for this operator set + IStrategy[] memory strategies = IAllocationManager(allocationManager).getStrategiesInOperatorSet(operatorSet); + + // Convert IStrategy array to address array + address[] memory strategyAddresses = new address[](strategies.length); + for (uint256 i = 0; i < strategies.length; i++) { + strategyAddresses[i] = address(strategies[i]); + } + + return strategyAddresses; + } + /** * @notice Sets the AVS registrar address in the AllocationManager * @param registrar The new AVS registrar address @@ -334,4 +455,4 @@ abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable // storage gap for upgradeability // slither-disable-next-line shadowing-state uint256[49] private __GAP; -} +} \ No newline at end of file diff --git a/src/unaudited/ECDSAStakeRegistry.sol b/src/unaudited/ECDSAStakeRegistry.sol index c961c83e..497b6937 100644 --- a/src/unaudited/ECDSAStakeRegistry.sol +++ b/src/unaudited/ECDSAStakeRegistry.sol @@ -19,6 +19,16 @@ import {SignatureCheckerUpgradeable} from "@openzeppelin-upgrades/contracts/utils/cryptography/SignatureCheckerUpgradeable.sol"; import {IERC1271Upgradeable} from "@openzeppelin-upgrades/contracts/interfaces/IERC1271Upgradeable.sol"; +import { + IAVSDirectoryTypes +} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; +import {IAllocationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {OperatorSet} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol"; + +interface IAVSDirectory { + function avsOperatorStatus(address avs, address operator) external view returns (IAVSDirectoryTypes.OperatorAVSRegistrationStatus); +} /// @title ECDSA Stake Registry /// @dev THIS CONTRACT IS NOT AUDITED @@ -34,11 +44,24 @@ contract ECDSAStakeRegistry is /// @dev Constructor to create ECDSAStakeRegistry. /// @param _delegationManager Address of the DelegationManager contract that this registry interacts with. constructor( - IDelegationManager _delegationManager - ) ECDSAStakeRegistryStorage(_delegationManager) { + IDelegationManager _delegationManager, + IAllocationManager _allocationManager, + address _avsRegistrar, + IAVSDirectory _avsDirectory + ) + ECDSAStakeRegistryStorage(_delegationManager, _allocationManager, _avsRegistrar, _avsDirectory) + { // _disableInitializers(); } + /// @notice Modifier to ensure the caller is the AVS Registrar. + modifier onlyAVSRegistrar() { + if (msg.sender != address(avsRegistrar)) { + revert InvalidSender(); + } + _; + } + /// @notice Initializes the contract with the given parameters. /// @param _serviceManager The address of the service manager. /// @param thresholdWeight The threshold weight in basis points. @@ -64,24 +87,70 @@ contract ECDSAStakeRegistry is __Ownable_init(); } + function disableM2QuorumRegistration() external onlyOwner { + if (isM2QuorumRegistrationDisabled) { + revert M2QuorumRegistrationIsDisabled(); + } + + isM2QuorumRegistrationDisabled = true; + emit M2QuorumRegistrationDisabled(); + } + /// @inheritdoc IECDSAStakeRegistry - function registerOperatorWithSignature( + function registerOperatorM2Quorum( ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature, address signingKey ) external { - _registerOperatorWithSig(msg.sender, operatorSignature, signingKey); + if (isM2QuorumRegistrationDisabled) { + revert M2QuorumRegistrationIsDisabled(); + } + if (operatorRegisteredOnAVSDirectory(msg.sender)) { + revert OperatorAlreadyRegistered(); + } + _registerOperatorM2Quorum(msg.sender, operatorSignature, signingKey); } /// @inheritdoc IECDSAStakeRegistry - function deregisterOperator() external { - _deregisterOperator(msg.sender); + function deregisterOperatorM2Quorum() external { + if (!operatorRegisteredOnAVSDirectory(msg.sender)) { + revert OperatorNotRegistered(); + } + + _deregisterOperatorM2Quorum(msg.sender); + } + + function onOperatorSetRegistered( + address operator, + address signingKey + ) external onlyAVSRegistrar { + // Update operator weight + _updateOperatorWeight(operator); + + // Update signing key and p2p key + _updateOperatorSigningKey(operator, signingKey); + + if (!operatorRegisteredOnAVSDirectory(operator)) { + emit OperatorRegistered(operator, _serviceManager); + } + } + + function onOperatorSetDeregistered( + address operator + ) external onlyAVSRegistrar { + // Update weights + _updateOperatorWeight(operator); + + // Emit event + if (!operatorRegisteredOnAVSDirectory(operator)) { + emit OperatorDeregistered(operator, _serviceManager); + } } /// @inheritdoc IECDSAStakeRegistry function updateOperatorSigningKey( address newSigningKey ) external { - if (!_operatorRegistered[msg.sender]) { + if (!operatorRegistered(msg.sender)) { revert OperatorNotRegistered(); } _updateOperatorSigningKey(msg.sender, newSigningKey); @@ -119,6 +188,33 @@ contract ECDSAStakeRegistry is _updateStakeThreshold(thresholdWeight); } + /// @notice Sets the current operator set ids + /// @param _ids The ids of the operator sets to set + function setCurrentOperatorSetIds( + uint32[] calldata _ids + ) external onlyOwner { + if (_ids.length == 0 || _ids.length > 10) { + revert InvalidOperatorSetIdsLength(); + } + currentOperatorSetIds = _ids; + } + + /// @notice Sets the allocation manager + /// @param _allocationManager The allocation manager to set + function setAllocationManager( + IAllocationManager _allocationManager + ) external onlyOwner { + allocationManager = _allocationManager; + } + + /// @notice Sets the AVS Registrar + /// @param _avsRegistrar The AVS Registrar to set + function setAVSRegistrar( + address _avsRegistrar + ) external onlyOwner { + avsRegistrar = _avsRegistrar; + } + function isValidSignature( bytes32 digest, bytes memory _signatureData @@ -134,6 +230,12 @@ contract ECDSAStakeRegistry is return _quorum; } + /// @notice Gets the current operator set ids + /// @return The current operator set ids + function getCurrentOperatorSetIds() external view returns (uint32[] memory) { + return currentOperatorSetIds; + } + /// @inheritdoc IECDSAStakeRegistry function getLatestOperatorSigningKey( address operator @@ -188,20 +290,29 @@ contract ECDSAStakeRegistry is return _thresholdWeightHistory.getAtBlock(blockNumber); } - /// @inheritdoc IECDSAStakeRegistry - function operatorRegistered( - address operator - ) external view returns (bool) { - return _operatorRegistered[operator]; - } - /// @inheritdoc IECDSAStakeRegistry function minimumWeight() external view returns (uint256) { return _minimumWeight; } - /// @inheritdoc IECDSAStakeRegistry + /// @notice Calculates an operator's current weight based on their delegated stake + /// @param _operator Address of the operator to calculate weight for + /// @return Current weight of the operator (0 if below minimum threshold) + /// @dev Queries mainnet delegation manager for current shares function getOperatorWeight( + address _operator + ) public view returns (uint256) { + uint256 quorumWeight = getQuorumWeight(_operator); + // uint256 operatorSetWeight = getOperatorSetWeight(_operator); + + // return quorumWeight + operatorSetWeight; + return quorumWeight; + } + + /// @notice Calculates operator's weight in the quorum + /// @param operator The operator address to calculate weight for + /// @return The operator's weight in quorum, or 0 if below minimum + function getQuorumWeight( address operator ) public view returns (uint256) { StrategyParams[] memory strategyParams = _quorum.strategies; @@ -223,6 +334,73 @@ contract ECDSAStakeRegistry is } } + /// @notice Calculates operator's available weight in current operator set + /// @dev Weight calculation: + /// 1. Check operator set membership + /// 2. Get shares and allocation for each strategy + /// 3. Calculate available proportion (currentMagnitude/maxMagnitude) + /// 4. Sum up available shares weighted by proportion + /// @param operator The operator address to calculate weight for + /// @return The operator's available weight in set, or 0 if below minimum + function getOperatorSetWeight( + address operator + ) public view returns (uint256) { + // Return 0 if allocation manager not set + if (address(allocationManager) == address(0)) { + return 0; + } + + uint256 totalWeight; + + // Loop through all operator sets + for (uint256 setIndex = 0; setIndex < currentOperatorSetIds.length; setIndex++) { + // Create operator set struct for current id + OperatorSet memory operatorSet = + OperatorSet({avs: address(_serviceManager), id: currentOperatorSetIds[setIndex]}); + + // Check operator set membership + if (!allocationManager.isMemberOfOperatorSet(operator, operatorSet)) { + continue; + } + + // Get strategies from operator set + IStrategy[] memory strategies = + allocationManager.getStrategiesInOperatorSet(operatorSet); + if (strategies.length == 0) { + continue; + } + + // Get operator's shares for all strategies + uint256[] memory shares = DELEGATION_MANAGER.getOperatorShares(operator, strategies); + + // Calculate available weight for each strategy + for (uint256 i = 0; i < strategies.length; i++) { + // Get allocation and max magnitude + IAllocationManager.Allocation memory allocation = + allocationManager.getAllocation(operator, operatorSet, strategies[i]); + uint64 maxMagnitude = allocationManager.getMaxMagnitude(operator, strategies[i]); + + if (maxMagnitude == 0) { + continue; + } + + // Calculate available proportion + uint256 slashableProportion = + uint256(allocation.currentMagnitude) * WAD / maxMagnitude; + + // Add weighted shares to total + totalWeight += shares[i] * slashableProportion / WAD; + } + } + + // Return 0 if below minimum weight + if (totalWeight >= _minimumWeight) { + return totalWeight; + } else { + return 0; + } + } + /// @inheritdoc IECDSAStakeRegistry function updateOperatorsForQuorum( address[][] memory operatorsPerQuorum, @@ -231,6 +409,47 @@ contract ECDSAStakeRegistry is _updateAllOperators(operatorsPerQuorum[0]); } + /// @notice Checks if an operator is registered on the AVS Directory(M2). + /// @param operator The address of the operator to check. + /// @return bool True if the operator is registered on the AVS Directory, false otherwise. + function operatorRegisteredOnAVSDirectory( + address operator + ) public view returns (bool) { + return AVS_DIRECTORY.avsOperatorStatus(_serviceManager, operator) + == IAVSDirectoryTypes.OperatorAVSRegistrationStatus.REGISTERED; + } + + /// @notice Checks if an operator is registered on the current operator set. + /// @param operator The address of the operator to check. + /// @return bool True if the operator is registered on the current operator set, false otherwise. + function operatorRegisteredOnCurrentOperatorSets( + address operator + ) public view returns (bool) { + if (address(allocationManager) == address(0)) { + return false; + } + + // Check if operator is registered in any current set + for (uint256 i = 0; i < currentOperatorSetIds.length; i++) { + OperatorSet memory operatorSet = + OperatorSet({avs: address(_serviceManager), id: currentOperatorSetIds[i]}); + if (allocationManager.isMemberOfOperatorSet(operator, operatorSet)) { + return true; + } + } + return false; + } + + /// @notice Checks if an operator is registered on the AVS Directory or the current operator set. + /// @param operator The address of the operator to check. + /// @return bool True if the operator is registered on the AVS Directory or the current operator set, false otherwise. + function operatorRegistered( + address operator + ) public view returns (bool) { + return operatorRegisteredOnAVSDirectory(operator) + || operatorRegisteredOnCurrentOperatorSets(operator); + } + /// @dev Updates the list of operators if the provided list has the correct number of operators. /// Reverts if the provided list of operators does not match the expected total count of operators. /// @param operators The list of operator addresses to update. @@ -296,38 +515,53 @@ contract ECDSAStakeRegistry is /// @dev Internal function to deregister an operator /// @param operator The operator's address to deregister - function _deregisterOperator( + function _deregisterOperatorM2Quorum( address operator ) internal { - if (!_operatorRegistered[operator]) { + if (!operatorRegisteredOnAVSDirectory(operator)) { revert OperatorNotRegistered(); } - _totalOperators--; - delete _operatorRegistered[operator]; + int256 delta = _updateOperatorWeight(operator); _updateTotalWeight(delta); IServiceManager(_serviceManager).deregisterOperatorFromAVS(operator); - emit OperatorDeregistered(operator, address(_serviceManager)); + if (!operatorRegisteredOnCurrentOperatorSets(operator)) { + _totalOperators--; + emit OperatorDeregistered(operator, address(_serviceManager)); + } } /// @dev registers an operator through a provided signature /// @param operatorSignature Contains the operator's signature, salt, and expiry /// @param signingKey The signing key to add to the operator's history - function _registerOperatorWithSig( + function _registerOperatorM2Quorum( address operator, ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature, address signingKey ) internal virtual { - if (_operatorRegistered[operator]) { + if (operatorRegisteredOnAVSDirectory(operator)) { revert OperatorAlreadyRegistered(); } - _totalOperators++; - _operatorRegistered[operator] = true; + int256 delta = _updateOperatorWeight(operator); _updateTotalWeight(delta); _updateOperatorSigningKey(operator, signingKey); IServiceManager(_serviceManager).registerOperatorToAVS(operator, operatorSignature); - emit OperatorRegistered(operator, _serviceManager); + if (!operatorRegisteredOnCurrentOperatorSets(operator)) { + _totalOperators++; + emit OperatorRegistered(operator, _serviceManager); + } + } + + /// @notice Deregisters an operator from a set of operator sets. + /// @dev This function is used to deregister an operator from a set of operator sets. + /// @param operator The address of the operator to deregister. + function _deregisterOperatorFromOperatorSets( + address operator + ) internal virtual { + IServiceManager(_serviceManager).deregisterOperatorFromOperatorSets( + operator, currentOperatorSetIds + ); } /// @dev Internal function to update an operator's signing key @@ -350,7 +584,7 @@ contract ECDSAStakeRegistry is int256 delta; uint256 newWeight; uint256 oldWeight = _operatorWeightHistory[operator].latest(); - if (!_operatorRegistered[operator]) { + if (!operatorRegistered(operator)) { delta -= int256(oldWeight); if (delta == 0) { return delta; @@ -549,4 +783,4 @@ contract ECDSAStakeRegistry is revert InsufficientSignedStake(); } } -} +} \ No newline at end of file diff --git a/src/unaudited/ECDSAStakeRegistryStorage.sol b/src/unaudited/ECDSAStakeRegistryStorage.sol index 8d9d69d8..d7573107 100644 --- a/src/unaudited/ECDSAStakeRegistryStorage.sol +++ b/src/unaudited/ECDSAStakeRegistryStorage.sol @@ -8,11 +8,33 @@ import {CheckpointsUpgradeable} from import { IECDSAStakeRegistry, IECDSAStakeRegistryTypes } from "../interfaces/IECDSAStakeRegistry.sol"; - +import {IAllocationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {IAVSRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IAVSRegistrar.sol"; +import {IAVSDirectoryTypes} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; +import {IAVSDirectory} from "./ECDSAStakeRegistry.sol"; abstract contract ECDSAStakeRegistryStorage is IECDSAStakeRegistry { /// @notice Manages staking delegations through the DelegationManager interface IDelegationManager internal immutable DELEGATION_MANAGER; + /// @notice The AVS Directory contract + IAVSDirectory internal immutable AVS_DIRECTORY; + + /// @notice Manages staking delegations through the DelegationManager interface + IAllocationManager internal allocationManager; + + /// @notice The address of the AVS Registrar of the AVS + address internal avsRegistrar; + + /// @notice Whether M2 quorum registration is disabled + bool public isM2QuorumRegistrationDisabled; + + /// @notice The current operator set ids + uint32[] public currentOperatorSetIds; + + /// @notice The total amount of multipliers to weigh stakes + uint256 public constant WAD = 1e18; + /// @dev The total amount of multipliers to weigh stakes uint256 internal constant BPS = 10000; @@ -28,9 +50,6 @@ abstract contract ECDSAStakeRegistryStorage is IECDSAStakeRegistry { /// @notice Holds the address of the service manager address internal _serviceManager; - /// @notice Defines the duration after which the stake's weight expires. - uint256 internal _stakeExpiry; - /// @notice Maps an operator to their signing key history using checkpoints mapping(address => CheckpointsUpgradeable.History) internal _operatorSigningKeyHistory; @@ -43,18 +62,21 @@ abstract contract ECDSAStakeRegistryStorage is IECDSAStakeRegistry { /// @notice Maps operator addresses to their respective stake histories using checkpoints mapping(address => CheckpointsUpgradeable.History) internal _operatorWeightHistory; - /// @notice Maps an operator to their registration status - mapping(address => bool) internal _operatorRegistered; - /// @param _delegationManager Connects this registry with the DelegationManager constructor( - IDelegationManager _delegationManager + IDelegationManager _delegationManager, + IAllocationManager _allocationManager, + address _avsRegistrar, + IAVSDirectory _avsDirectory ) { DELEGATION_MANAGER = _delegationManager; + allocationManager = _allocationManager; + avsRegistrar = _avsRegistrar; + AVS_DIRECTORY = _avsDirectory; } // slither-disable-next-line shadowing-state /// @dev Reserves storage slots for future upgrades // solhint-disable-next-line uint256[40] private __gap; -} +} \ No newline at end of file diff --git a/src/unaudited/examples/ECDSAStakeRegistryEqualWeight.sol b/src/unaudited/examples/ECDSAStakeRegistryEqualWeight.sol index 3edd6278..adfc1dab 100644 --- a/src/unaudited/examples/ECDSAStakeRegistryEqualWeight.sol +++ b/src/unaudited/examples/ECDSAStakeRegistryEqualWeight.sol @@ -7,6 +7,9 @@ import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; import {CheckpointsUpgradeable} from "@openzeppelin-upgrades/contracts/utils/CheckpointsUpgradeable.sol"; +import {IAllocationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {IAVSDirectory} from "../ECDSAStakeRegistry.sol"; /// @title ECDSA Stake Registry with Equal Weight /// @dev THIS CONTRACT IS NOT AUDITED @@ -18,8 +21,18 @@ contract ECDSAStakeRegistryEqualWeight is ECDSAStakeRegistryPermissioned { /// @dev Passes the delegation manager to the parent constructor. /// @param _delegationManager The address of the delegation manager contract. constructor( - IDelegationManager _delegationManager - ) ECDSAStakeRegistryPermissioned(_delegationManager) { + IDelegationManager _delegationManager, + IAllocationManager _allocationManager, + address _avsRegistrar, + IAVSDirectory _avsDirectory + ) + ECDSAStakeRegistryPermissioned( + _delegationManager, + _allocationManager, + _avsRegistrar, + _avsDirectory + ) + { // _disableInitializers(); } @@ -33,7 +46,7 @@ contract ECDSAStakeRegistryEqualWeight is ECDSAStakeRegistryPermissioned { uint256 oldWeight; uint256 newWeight; int256 delta; - if (_operatorRegistered[_operator]) { + if (operatorRegistered(_operator)) { (oldWeight,) = _operatorWeightHistory[_operator].push(1); delta = int256(1) - int256(oldWeight); // handles if they were already registered } else { @@ -43,4 +56,4 @@ contract ECDSAStakeRegistryEqualWeight is ECDSAStakeRegistryPermissioned { emit OperatorWeightUpdated(_operator, oldWeight, newWeight); return delta; } -} +} \ No newline at end of file diff --git a/src/unaudited/examples/ECDSAStakeRegistryPermissioned.sol b/src/unaudited/examples/ECDSAStakeRegistryPermissioned.sol index f6afffdf..6d61d0fe 100644 --- a/src/unaudited/examples/ECDSAStakeRegistryPermissioned.sol +++ b/src/unaudited/examples/ECDSAStakeRegistryPermissioned.sol @@ -1,3 +1,4 @@ + // SPDX-License-Identifier: MIT pragma solidity ^0.8.27; @@ -5,6 +6,12 @@ import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISi import {ECDSAStakeRegistry} from "../ECDSAStakeRegistry.sol"; import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; +import {IAllocationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {IAVSDirectory} from + "../ECDSAStakeRegistry.sol"; +import {IAVSDirectoryTypes} from + "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; /// @title ECDSA Stake Registry with an Operator Allowlist /// @dev THIS CONTRACT IS NOT AUDITED @@ -29,8 +36,11 @@ contract ECDSAStakeRegistryPermissioned is ECDSAStakeRegistry { error OperatorAlreadyAllowlisted(); constructor( - IDelegationManager _delegationManager - ) ECDSAStakeRegistry(_delegationManager) { + IDelegationManager _delegationManager, + IAllocationManager _allocationManager, + address _avsRegistrar, + IAVSDirectory _avsDirectory + ) ECDSAStakeRegistry(_delegationManager, _allocationManager, _avsRegistrar, _avsDirectory) { // _disableInitializers(); } @@ -66,7 +76,12 @@ contract ECDSAStakeRegistryPermissioned is ECDSAStakeRegistry { function _ejectOperator( address _operator ) internal { - _deregisterOperator(_operator); + if (operatorRegisteredOnAVSDirectory(_operator)) { + _deregisterOperatorM2Quorum(_operator); + } + if (operatorRegisteredOnCurrentOperatorSets(_operator)) { + _deregisterOperatorFromOperatorSets(_operator); + } emit OperatorEjected(_operator); } @@ -94,13 +109,11 @@ contract ECDSAStakeRegistryPermissioned is ECDSAStakeRegistry { } delete allowlistedOperators[_operator]; emit OperatorRevoked(_operator); - if (_operatorRegistered[_operator]) { - _ejectOperator(_operator); - } + _ejectOperator(_operator); } /// @inheritdoc ECDSAStakeRegistry - function _registerOperatorWithSig( + function _registerOperatorM2Quorum( address _operator, ISignatureUtils.SignatureWithSaltAndExpiry memory _operatorSignature, address _operatorSigningKey @@ -108,6 +121,6 @@ contract ECDSAStakeRegistryPermissioned is ECDSAStakeRegistry { if (allowlistedOperators[_operator] != true) { revert OperatorNotAllowlisted(); } - super._registerOperatorWithSig(_operator, _operatorSignature, _operatorSigningKey); + super._registerOperatorM2Quorum(_operator, _operatorSignature, _operatorSigningKey); } } diff --git a/test/mocks/AVSDirectoryMock.sol b/test/mocks/AVSDirectoryMock.sol index d76f1abf..cc745507 100644 --- a/test/mocks/AVSDirectoryMock.sol +++ b/test/mocks/AVSDirectoryMock.sol @@ -151,4 +151,9 @@ contract AVSDirectoryMock is IAVSDirectory { function isOperatorSetBatch( OperatorSet[] calldata operatorSets ) external view returns (bool) {} + + function avsOperatorStatus( + address avs, + address operator + ) external view returns (OperatorAVSRegistrationStatus) {} } diff --git a/test/mocks/ECDSAServiceManagerMock.sol b/test/mocks/ECDSAServiceManagerMock.sol index 07a263cf..a05f2232 100644 --- a/test/mocks/ECDSAServiceManagerMock.sol +++ b/test/mocks/ECDSAServiceManagerMock.sol @@ -11,42 +11,30 @@ contract ECDSAServiceManagerMock is ECDSAServiceManagerBase { address _stakeRegistry, address _rewardsCoordinator, address _delegationManager, - address _allocationManager + address _allocationManager, + address _permissionController, + address _initialOwner, + address _rewardsInitiator ) ECDSAServiceManagerBase( _avsDirectory, _stakeRegistry, _rewardsCoordinator, _delegationManager, - _allocationManager + _allocationManager, + _permissionController ) - {} - - function initialize( - address initialOwner, - address rewardsInitiator - ) public virtual initializer { - __ServiceManagerBase_init(initialOwner, rewardsInitiator); + { + // disable initializer and directly set owner and rewardsInitiator for testing + _transferOwnership(_initialOwner); + _setRewardsInitiator(_rewardsInitiator); } - function addPendingAdmin( - address admin - ) external {} - - function removePendingAdmin( - address pendingAdmin - ) external {} - - function deregisterOperatorFromOperatorSets( - address operator, - uint32[] memory operatorSetIds - ) external {} - - function removeAdmin( - address admin - ) external {} - - function setAppointee(address appointee, address target, bytes4 selector) external {} + // function initialize( + // address initialOwner, + // address rewardsInitiator + // ) public virtual initializer { + // __ServiceManagerBase_init(initialOwner, rewardsInitiator); + // } - function removeAppointee(address appointee, address target, bytes4 selector) external {} } diff --git a/test/mocks/ECDSAStakeRegistryMock.sol b/test/mocks/ECDSAStakeRegistryMock.sol index fb43079b..da670557 100644 --- a/test/mocks/ECDSAStakeRegistryMock.sol +++ b/test/mocks/ECDSAStakeRegistryMock.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.27; import "../../src/unaudited/ECDSAStakeRegistry.sol"; +import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol"; /** * @title Mock for ECDSAStakeRegistry @@ -9,6 +10,10 @@ import "../../src/unaudited/ECDSAStakeRegistry.sol"; */ contract ECDSAStakeRegistryMock is ECDSAStakeRegistry { constructor( - IDelegationManager _delegationManager - ) ECDSAStakeRegistry(_delegationManager) {} + IDelegationManager _delegationManager, + IAllocationManager _allocationManager, + address _avsRegistrar, + IAVSDirectory _avsDirectory + ) ECDSAStakeRegistry(_delegationManager, _allocationManager, _avsRegistrar, _avsDirectory) {} + } diff --git a/test/unit/ECDSAServiceManager.t.sol b/test/unit/ECDSAServiceManager.t.sol index 5afc891e..2282f96a 100644 --- a/test/unit/ECDSAServiceManager.t.sol +++ b/test/unit/ECDSAServiceManager.t.sol @@ -13,7 +13,22 @@ import {IAVSRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IAVSR import {ECDSAServiceManagerMock} from "../mocks/ECDSAServiceManagerMock.sol"; import {ECDSAStakeRegistryMock} from "../mocks/ECDSAStakeRegistryMock.sol"; +import {AVSDirectoryMock} from "../mocks/AVSDirectoryMock.sol"; import {IECDSAStakeRegistryTypes} from "../../src/interfaces/IECDSAStakeRegistry.sol"; +import {IPermissionController} from + "eigenlayer-contracts/src/contracts/interfaces/IPermissionController.sol"; +import {IAllocationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {IAVSDirectory, IAVSDirectoryTypes} from "../../src/unaudited/ECDSAStakeRegistry.sol"; +import {OperatorSet} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol"; + +contract MockPermissionController { + function addPendingAdmin(address account, address admin) external {} + function removePendingAdmin(address account, address admin) external {} + function removeAdmin(address account, address admin) external {} + function setAppointee(address account, address appointee, address target, bytes4 selector) external {} + function removeAppointee(address account, address appointee, address target, bytes4 selector) external {} +} contract MockDelegationManager { function operatorShares(address, address) external pure returns (uint256) { @@ -32,23 +47,19 @@ contract MockDelegationManager { } } -contract MockAVSDirectory { - function registerOperatorToAVS( - address, - ISignatureUtils.SignatureWithSaltAndExpiry memory - ) external pure {} - - function deregisterOperatorFromAVS( - address - ) external pure {} - - function updateAVSMetadataURI( - string memory - ) external pure {} -} - contract MockAllocationManager { function setAVSRegistrar(address avs, address registrar) external {} + + function isOperatorSet(OperatorSet memory operatorSet) external pure returns (bool) { + return true; + } + + function getStrategiesInOperatorSet(OperatorSet memory operatorSet) external pure returns (IStrategy[] memory) { + IStrategy[] memory strategies = new IStrategy[](2); + strategies[0] = IStrategy(address(900)); + strategies[1] = IStrategy(address(901)); + return strategies; + } } contract MockRewardsCoordinator { @@ -69,11 +80,15 @@ contract MockRewardsCoordinator { contract ECDSAServiceManagerSetup is Test { MockDelegationManager public mockDelegationManager; - MockAVSDirectory public mockAVSDirectory; + AVSDirectoryMock public mockAVSDirectory; MockAllocationManager public mockAllocationManager; ECDSAStakeRegistryMock public mockStakeRegistry; MockRewardsCoordinator public mockRewardsCoordinator; ECDSAServiceManagerMock public serviceManager; + MockPermissionController public mockPermissionController; + address public mockAVSRegistrarAddr; + address public owner = makeAddr("owner"); + address public rewardsInitiator = makeAddr("rewardsInitiator"); address internal operator1; address internal operator2; uint256 internal operator1Pk; @@ -81,18 +96,29 @@ contract ECDSAServiceManagerSetup is Test { function setUp() public { mockDelegationManager = new MockDelegationManager(); - mockAVSDirectory = new MockAVSDirectory(); + mockAVSDirectory = new AVSDirectoryMock(); mockAllocationManager = new MockAllocationManager(); + mockAVSRegistrarAddr = makeAddr("mockAVSRegistrar"); mockStakeRegistry = - new ECDSAStakeRegistryMock(IDelegationManager(address(mockDelegationManager))); + new ECDSAStakeRegistryMock( + IDelegationManager(address(mockDelegationManager)), + IAllocationManager(address(mockAllocationManager)), + mockAVSRegistrarAddr, + IAVSDirectory(address(mockAVSDirectory)) + ); mockRewardsCoordinator = new MockRewardsCoordinator(); + mockPermissionController = new MockPermissionController(); + serviceManager = new ECDSAServiceManagerMock( address(mockAVSDirectory), address(mockStakeRegistry), address(mockRewardsCoordinator), address(mockDelegationManager), - address(mockAllocationManager) + address(mockAllocationManager), + address(mockPermissionController), + owner, + rewardsInitiator ); operator1Pk = 1; @@ -114,7 +140,7 @@ contract ECDSAServiceManagerSetup is Test { }); address[] memory operators = new address[](0); - vm.prank(mockStakeRegistry.owner()); + vm.prank(owner); mockStakeRegistry.initialize( address(serviceManager), 10000, // Assuming a threshold weight of 10000 basis points @@ -123,10 +149,10 @@ contract ECDSAServiceManagerSetup is Test { ISignatureUtils.SignatureWithSaltAndExpiry memory dummySignature; vm.prank(operator1); - mockStakeRegistry.registerOperatorWithSignature(dummySignature, operator1); + mockStakeRegistry.registerOperatorM2Quorum(dummySignature, operator1); vm.prank(operator2); - mockStakeRegistry.registerOperatorWithSignature(dummySignature, operator2); + mockStakeRegistry.registerOperatorM2Quorum(dummySignature, operator2); } function testRegisterOperatorToAVS() public { @@ -204,7 +230,7 @@ contract ECDSAServiceManagerSetup is Test { function testSetClaimerFor() public { address claimer = address(0x123); - vm.prank(mockStakeRegistry.owner()); + vm.prank(owner); serviceManager.setClaimerFor(claimer); } @@ -214,4 +240,53 @@ contract ECDSAServiceManagerSetup is Test { vm.prank(mockStakeRegistry.owner()); serviceManager.setAVSRegistrar(IAVSRegistrar(registrar)); } + + function testGetOperatorSetStrategies() public { + uint32 operatorSetId = 1; + + address[] memory strategies = serviceManager.getOperatorSetStrategies(operatorSetId); + + assertEq(strategies.length, 2, "Should return 2 strategies"); + assertEq(strategies[0], address(900), "First strategy should match"); + assertEq(strategies[1], address(901), "Second strategy should match"); + } + + function testAddPendingAdmin() public { + address admin = makeAddr("admin"); + + vm.prank(owner); + serviceManager.addPendingAdmin(admin); + } + + function testRemovePendingAdmin() public { + address pendingAdmin = makeAddr("pendingAdmin"); + + vm.prank(owner); + serviceManager.removePendingAdmin(pendingAdmin); + } + + function testRemoveAdmin() public { + address admin = makeAddr("admin"); + + vm.prank(owner); + serviceManager.removeAdmin(admin); + } + + function testSetAppointee() public { + address appointee = makeAddr("appointee"); + address target = makeAddr("target"); + bytes4 selector = bytes4(keccak256("someFunction()")); + + vm.prank(owner); + serviceManager.setAppointee(appointee, target, selector); + } + + function testRemoveAppointee() public { + address appointee = makeAddr("appointee"); + address target = makeAddr("target"); + bytes4 selector = bytes4(keccak256("someFunction()")); + + vm.prank(owner); + serviceManager.removeAppointee(appointee, target, selector); + } } diff --git a/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol b/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol index befc76c4..bf226960 100644 --- a/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol +++ b/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol @@ -5,14 +5,17 @@ import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISi import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; +import {IAllocationManager} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; import { IECDSAStakeRegistry, - IECDSAStakeRegistryTypes + IECDSAStakeRegistryTypes, + IECDSAStakeRegistryErrors } from "../../src/interfaces/IECDSAStakeRegistry.sol"; import {ECDSAStakeRegistrySetup} from "./ECDSAStakeRegistryUnit.t.sol"; import {ECDSAStakeRegistryEqualWeight} from "../../src/unaudited/examples/ECDSAStakeRegistryEqualWeight.sol"; +import {IAVSDirectory} from "../../src/unaudited/ECDSAStakeRegistry.sol"; contract EqualWeightECDSARegistry is ECDSAStakeRegistrySetup { ECDSAStakeRegistryEqualWeight internal fixedWeightRegistry; @@ -20,20 +23,30 @@ contract EqualWeightECDSARegistry is ECDSAStakeRegistrySetup { function setUp() public virtual override { super.setUp(); fixedWeightRegistry = - new ECDSAStakeRegistryEqualWeight(IDelegationManager(address(mockDelegationManager))); + new ECDSAStakeRegistryEqualWeight( + IDelegationManager(address(mockDelegationManager)), + IAllocationManager(address(mockAllocationManager)), + mockAVSRegistrarAddr, + IAVSDirectory(address(mockAVSDirectory)) + ); + IStrategy mockStrategy = IStrategy(address(0x1234)); IECDSAStakeRegistryTypes.Quorum memory quorum = - IECDSAStakeRegistryTypes.Quorum({strategies: new StrategyParams[](1)}); - quorum.strategies[0] = StrategyParams({strategy: mockStrategy, multiplier: 10000}); + IECDSAStakeRegistryTypes.Quorum({strategies: new IECDSAStakeRegistryTypes.StrategyParams[](1)}); + quorum.strategies[0] = IECDSAStakeRegistryTypes.StrategyParams({strategy: mockStrategy, multiplier: 10000}); + fixedWeightRegistry.initialize(address(mockServiceManager), 100, quorum); fixedWeightRegistry.permitOperator(operator1); fixedWeightRegistry.permitOperator(operator2); + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; + vm.prank(operator1); - fixedWeightRegistry.registerOperatorWithSignature(operatorSignature, operator1); + fixedWeightRegistry.registerOperatorM2Quorum(operatorSignature, operator1); + vm.prank(operator2); - fixedWeightRegistry.registerOperatorWithSignature(operatorSignature, operator2); + fixedWeightRegistry.registerOperatorM2Quorum(operatorSignature, operator2); } function test_FixedStakeUpdates() public { @@ -43,7 +56,7 @@ contract EqualWeightECDSARegistry is ECDSAStakeRegistrySetup { vm.roll(block.number + 1); vm.prank(operator1); - fixedWeightRegistry.deregisterOperator(); + fixedWeightRegistry.deregisterOperatorM2Quorum(); assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator1), 0); assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator2), 1); @@ -52,7 +65,7 @@ contract EqualWeightECDSARegistry is ECDSAStakeRegistrySetup { vm.roll(block.number + 1); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; vm.prank(operator1); - fixedWeightRegistry.registerOperatorWithSignature(operatorSignature, operator1); + fixedWeightRegistry.registerOperatorM2Quorum(operatorSignature, operator1); assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator1), 1); assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator2), 1); diff --git a/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol b/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol index 7269e1ce..c77010d3 100644 --- a/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol +++ b/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol @@ -5,6 +5,7 @@ import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISi import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; +import {IAllocationManager} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; import { IECDSAStakeRegistry, @@ -14,6 +15,7 @@ import { import {ECDSAStakeRegistrySetup} from "./ECDSAStakeRegistryUnit.t.sol"; import {ECDSAStakeRegistryPermissioned} from "../../src/unaudited/examples/ECDSAStakeRegistryPermissioned.sol"; +import {IAVSDirectory} from "../../src/unaudited/ECDSAStakeRegistry.sol"; contract PermissionedECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { ECDSAStakeRegistryPermissioned internal permissionedRegistry; @@ -21,20 +23,32 @@ contract PermissionedECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { function setUp() public virtual override { super.setUp(); permissionedRegistry = - new ECDSAStakeRegistryPermissioned(IDelegationManager(address(mockDelegationManager))); + new ECDSAStakeRegistryPermissioned( + IDelegationManager(address(mockDelegationManager)), + IAllocationManager(address(mockAllocationManager)), + mockAVSRegistrarAddr, + IAVSDirectory(address(mockAVSDirectory)) + ); + IStrategy mockStrategy = IStrategy(address(0x1234)); IECDSAStakeRegistryTypes.Quorum memory quorum = - IECDSAStakeRegistryTypes.Quorum({strategies: new StrategyParams[](1)}); - quorum.strategies[0] = StrategyParams({strategy: mockStrategy, multiplier: 10000}); + IECDSAStakeRegistryTypes.Quorum({strategies: new IECDSAStakeRegistryTypes.StrategyParams[](1)}); + quorum.strategies[0] = IECDSAStakeRegistryTypes.StrategyParams({strategy: mockStrategy, multiplier: 10000}); + permissionedRegistry.initialize(address(mockServiceManager), 100, quorum); permissionedRegistry.permitOperator(operator1); permissionedRegistry.permitOperator(operator2); + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; + vm.prank(operator1); - permissionedRegistry.registerOperatorWithSignature(operatorSignature, operator1); + permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator1); + vm.prank(operator2); - permissionedRegistry.registerOperatorWithSignature(operatorSignature, operator1); + permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator1); + + vm.roll(block.number + 1); } function test_RevertsWhen_NotOwner_PermitOperator() public { @@ -86,7 +100,7 @@ contract PermissionedECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { permissionedRegistry.ejectOperator(operator1); } - function test_RevertsWhen_NotAllowlisted_RegisterOperatorWithSig() public { + function test_RevertsWhen_NotAllowlisted_RegisterOperatorM2Quorum() public { address operator3 = address(0xBEEF); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; @@ -94,25 +108,25 @@ contract PermissionedECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { abi.encodeWithSelector(ECDSAStakeRegistryPermissioned.OperatorNotAllowlisted.selector) ); vm.prank(operator3); - permissionedRegistry.registerOperatorWithSignature(operatorSignature, operator3); + permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator3); } - function test_WhenAllowlisted_RegisterOperatorWithSig() public { + function test_WhenAllowlisted_RegisterOperatorM2Quorum() public { address operator3 = address(0xBEEF); permissionedRegistry.permitOperator(operator3); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; vm.prank(operator3); - permissionedRegistry.registerOperatorWithSignature(operatorSignature, operator3); + permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator3); } - function test_DeregisterOperator() public { + function test_DeregisterOperatorM2Quorum() public { address operator3 = address(0xBEEF); permissionedRegistry.permitOperator(operator3); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; vm.prank(operator3); - permissionedRegistry.registerOperatorWithSignature(operatorSignature, operator3); + permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator3); vm.prank(operator3); - permissionedRegistry.deregisterOperator(); + permissionedRegistry.deregisterOperatorM2Quorum(); } } diff --git a/test/unit/ECDSAStakeRegistryUnit.t.sol b/test/unit/ECDSAStakeRegistryUnit.t.sol index 540f8dcc..d2481f6b 100644 --- a/test/unit/ECDSAStakeRegistryUnit.t.sol +++ b/test/unit/ECDSAStakeRegistryUnit.t.sol @@ -15,6 +15,9 @@ import { IECDSAStakeRegistryTypes, IECDSAStakeRegistryEvents } from "../../src/interfaces/IECDSAStakeRegistry.sol"; +import {IAllocationManager} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {IAVSDirectory, IAVSDirectoryTypes} from "../../src/unaudited/ECDSAStakeRegistry.sol"; + contract MockServiceManager { // solhint-disable-next-line @@ -45,8 +48,54 @@ contract MockDelegationManager { } } +contract MockAVSDirectory { + mapping(address => mapping(address => IAVSDirectoryTypes.OperatorAVSRegistrationStatus)) + private operatorStatus; + + function registerOperatorToAVS( + address, + ISignatureUtils.SignatureWithSaltAndExpiry memory + ) external pure {} + + function deregisterOperatorFromAVS( + address + ) external pure {} + + function updateAVSMetadataURI( + string memory + ) external pure {} + function setAvsOperatorStatus( + address avs, + address operator, + IAVSDirectoryTypes.OperatorAVSRegistrationStatus status + ) external { + operatorStatus[avs][operator] = status; + } + + function avsOperatorStatus(address avs, address operator) external view returns (IAVSDirectoryTypes.OperatorAVSRegistrationStatus) { + return operatorStatus[avs][operator]; + } +} + +contract MockAllocationManager { + function setAVSRegistrar(address avs, address registrar) external {} + + function isOperatorSet(bytes memory operatorSet) external pure returns (bool) { + return true; + } + + function getStrategiesInOperatorSet(bytes memory operatorSet) external pure returns (IStrategy[] memory) { + IStrategy[] memory strategies = new IStrategy[](2); + strategies[0] = IStrategy(address(900)); + strategies[1] = IStrategy(address(901)); + return strategies; + } +} + contract ECDSAStakeRegistrySetup is Test, IECDSAStakeRegistryEvents { MockDelegationManager public mockDelegationManager; + MockAVSDirectory public mockAVSDirectory; + MockAllocationManager public mockAllocationManager; MockServiceManager public mockServiceManager; ECDSAStakeRegistry public registry; address internal operator1; @@ -58,25 +107,43 @@ contract ECDSAStakeRegistrySetup is Test, IECDSAStakeRegistryEvents { address[] internal signers; bytes[] internal signatures; bytes32 internal msgHash; + address public mockAVSRegistrarAddr; function setUp() public virtual { (operator1, operator1Pk) = makeAddrAndKey("Signer 1"); (operator2, operator2Pk) = makeAddrAndKey("Signer 2"); mockDelegationManager = new MockDelegationManager(); mockServiceManager = new MockServiceManager(); + mockAllocationManager = new MockAllocationManager(); + mockAVSDirectory = new MockAVSDirectory(); + mockAVSRegistrarAddr = makeAddr("mockAVSRegistrar"); + IStrategy mockStrategy = IStrategy(address(0x1234)); IECDSAStakeRegistryTypes.Quorum memory quorum = IECDSAStakeRegistryTypes.Quorum({ strategies: new IECDSAStakeRegistryTypes.StrategyParams[](1) }); - quorum.strategies[0] = - IECDSAStakeRegistryTypes.StrategyParams({strategy: mockStrategy, multiplier: 10000}); - registry = new ECDSAStakeRegistry(IDelegationManager(address(mockDelegationManager))); + quorum.strategies[0] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: mockStrategy, + multiplier: 10000 + }); + + registry = new ECDSAStakeRegistry( + IDelegationManager(address(mockDelegationManager)), + IAllocationManager(address(mockAllocationManager)), + mockAVSRegistrarAddr, + IAVSDirectory(address(mockAVSDirectory)) + ); + registry.initialize(address(mockServiceManager), 100, quorum); + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; + vm.prank(operator1); - registry.registerOperatorWithSignature(operatorSignature, operator1); + registry.registerOperatorM2Quorum(operatorSignature, operator1); + vm.prank(operator2); - registry.registerOperatorWithSignature(operatorSignature, operator2); + registry.registerOperatorM2Quorum(operatorSignature, operator2); + vm.roll(block.number + 1); } } @@ -191,26 +258,26 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { registry.updateQuorumConfig(invalidQuorum, operators); } - function test_RegisterOperatorWithSignature() public { + function test_RegisterOperatorM2Quorum() public { address operator3 = address(0x125); ISignatureUtils.SignatureWithSaltAndExpiry memory signature; vm.prank(operator3); - registry.registerOperatorWithSignature(signature, operator3); + registry.registerOperatorM2Quorum(signature, operator3); assertTrue(registry.operatorRegistered(operator3)); assertEq(registry.getLastCheckpointOperatorWeight(operator3), 1000); } - function test_RevertsWhen_AlreadyRegistered_RegisterOperatorWithSignature() public { + function test_RevertsWhen_AlreadyRegistered_RegisterOperatorM2Quorum() public { assertEq(registry.getLastCheckpointOperatorWeight(operator1), 1000); assertEq(registry.getLastCheckpointTotalWeight(), 2000); ISignatureUtils.SignatureWithSaltAndExpiry memory signature; vm.expectRevert(IECDSAStakeRegistryErrors.OperatorAlreadyRegistered.selector); vm.prank(operator1); - registry.registerOperatorWithSignature(signature, operator1); + registry.registerOperatorM2Quorum(signature, operator1); } - function test_RevertsWhen_SignatureIsInvalid_RegisterOperatorWithSignature() public { + function test_RevertsWhen_SignatureIsInvalid_RegisterOperatorM2Quorum() public { bytes memory signatureData; vm.mockCall( address(mockServiceManager), @@ -227,22 +294,22 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { ); } - function test_DeregisterOperator() public { + function test_DeregisterOperatorM2Quorum() public { assertEq(registry.getLastCheckpointOperatorWeight(operator1), 1000); assertEq(registry.getLastCheckpointTotalWeight(), 2000); vm.prank(operator1); - registry.deregisterOperator(); + registry.deregisterOperatorM2Quorum(); assertEq(registry.getLastCheckpointOperatorWeight(operator1), 0); assertEq(registry.getLastCheckpointTotalWeight(), 1000); } - function test_RevertsWhen_NotOperator_DeregisterOperator() public { + function test_RevertsWhen_NotOperator_DeregisterOperatorM2Quorum() public { address notOperator = address(0x2); vm.prank(notOperator); vm.expectRevert(IECDSAStakeRegistryErrors.OperatorNotRegistered.selector); - registry.deregisterOperator(); + registry.deregisterOperatorM2Quorum(); } function test_When_Empty_UpdateOperators() public { @@ -426,7 +493,9 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { signatures[0] = abi.encodePacked(r, s, v); (v, r, s) = vm.sign(operator2Pk, msgHash); signatures[1] = abi.encodePacked(r, s, v); - + console.log("operator1 weight", registry.getOperatorWeight(operator1)); + console.log("operator2 weight", registry.getOperatorWeight(operator2)); + console.log("threshold weight", registry.getLastCheckpointThresholdWeight()); registry.isValidSignature(msgHash, abi.encode(signers, signatures, block.number - 1)); } @@ -595,16 +664,16 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { uint256 before = gasleft(); vm.pauseGasMetering(); vm.prank(operator1); - registry.deregisterOperator(); + registry.deregisterOperatorM2Quorum(); vm.prank(operator2); - registry.deregisterOperator(); + registry.deregisterOperatorM2Quorum(); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; address[] memory operators = new address[](30); for (uint256 i; i < operators.length; i++) { operators[i] = address(uint160(i)); vm.prank(operators[i]); - registry.registerOperatorWithSignature(operatorSignature, operators[i]); + registry.registerOperatorM2Quorum(operatorSignature, operators[i]); } vm.resumeGasMetering(); registry.updateOperators(operators); @@ -616,9 +685,9 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { uint256 before = gasleft(); vm.pauseGasMetering(); vm.prank(operator1); - registry.deregisterOperator(); + registry.deregisterOperatorM2Quorum(); vm.prank(operator2); - registry.deregisterOperator(); + registry.deregisterOperatorM2Quorum(); msgHash = keccak256("data"); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; @@ -630,7 +699,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { for (uint256 i = 1; i < operators.length + 1; i++) { operators[i - 1] = address(vm.addr(i)); vm.prank(operators[i - 1]); - registry.registerOperatorWithSignature(operatorSignature, operators[i - 1]); + registry.registerOperatorM2Quorum(operatorSignature, operators[i - 1]); (v, r, s) = vm.sign(i, msgHash); signatures[i - 1] = abi.encodePacked(r, s, v); } @@ -657,7 +726,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { // Register operator with a different signing key vm.prank(operator); - registry.registerOperatorWithSignature(operatorSignature, signer); + registry.registerOperatorM2Quorum(operatorSignature, signer); // Verify that the signing key has been successfully registered for the operator address registeredSigningKey = registry.getLatestOperatorSigningKey(operator); @@ -675,7 +744,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { // Register operator with a different signing key vm.prank(operator); - registry.registerOperatorWithSignature(operatorSignature, signer); + registry.registerOperatorM2Quorum(operatorSignature, signer); /// Register a second time vm.prank(operator); @@ -701,7 +770,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { // Register operator with a different signing key vm.prank(operator); - registry.registerOperatorWithSignature(operatorSignature, signer); + registry.registerOperatorM2Quorum(operatorSignature, signer); vm.roll(block.number + 1); // Prepare data for signature @@ -727,7 +796,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { // Register operator with the initial signing key vm.prank(operator); - registry.registerOperatorWithSignature(operatorSignature, initialSigningKey); + registry.registerOperatorM2Quorum(operatorSignature, initialSigningKey); vm.roll(block.number + 1); // Prepare data for signature with initial signing key @@ -768,7 +837,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { // Register operator with the initial signing key vm.prank(operator); - registry.registerOperatorWithSignature(operatorSignature, initialSigningKey); + registry.registerOperatorM2Quorum(operatorSignature, initialSigningKey); vm.roll(block.number + 1); // Prepare data for signature with initial signing key From bacf42a8133db591466fb922fa275c4f13005752 Mon Sep 17 00:00:00 2001 From: jaxxjj Date: Wed, 26 Feb 2025 23:34:31 -0700 Subject: [PATCH 06/12] fix: resolve failing tests --- src/unaudited/ECDSAStakeRegistry.sol | 27 +- .../ECDSAStakeRegistryPermissioned.sol | 8 +- test/unit/ECDSAServiceManager.t.sol | 40 +++ .../ECDSAStakeRegistryEqualWeightUnit.t.sol | 41 +-- .../ECDSAStakeRegistryPermissionedUnit.t.sol | 53 ++-- test/unit/ECDSAStakeRegistryUnit.t.sol | 265 +++++++++++++++--- 6 files changed, 330 insertions(+), 104 deletions(-) diff --git a/src/unaudited/ECDSAStakeRegistry.sol b/src/unaudited/ECDSAStakeRegistry.sol index 497b6937..05a3e46c 100644 --- a/src/unaudited/ECDSAStakeRegistry.sol +++ b/src/unaudited/ECDSAStakeRegistry.sol @@ -104,18 +104,11 @@ contract ECDSAStakeRegistry is if (isM2QuorumRegistrationDisabled) { revert M2QuorumRegistrationIsDisabled(); } - if (operatorRegisteredOnAVSDirectory(msg.sender)) { - revert OperatorAlreadyRegistered(); - } _registerOperatorM2Quorum(msg.sender, operatorSignature, signingKey); } /// @inheritdoc IECDSAStakeRegistry function deregisterOperatorM2Quorum() external { - if (!operatorRegisteredOnAVSDirectory(msg.sender)) { - revert OperatorNotRegistered(); - } - _deregisterOperatorM2Quorum(msg.sender); } @@ -303,10 +296,9 @@ contract ECDSAStakeRegistry is address _operator ) public view returns (uint256) { uint256 quorumWeight = getQuorumWeight(_operator); - // uint256 operatorSetWeight = getOperatorSetWeight(_operator); + uint256 operatorSetWeight = getOperatorSetWeight(_operator); - // return quorumWeight + operatorSetWeight; - return quorumWeight; + return quorumWeight + operatorSetWeight; } /// @notice Calculates operator's weight in the quorum @@ -518,13 +510,11 @@ contract ECDSAStakeRegistry is function _deregisterOperatorM2Quorum( address operator ) internal { - if (!operatorRegisteredOnAVSDirectory(operator)) { - revert OperatorNotRegistered(); - } - + // need to first remove the operator from the AVS Directory to correctly update the operator weight + IServiceManager(_serviceManager).deregisterOperatorFromAVS(operator); int256 delta = _updateOperatorWeight(operator); _updateTotalWeight(delta); - IServiceManager(_serviceManager).deregisterOperatorFromAVS(operator); + if (!operatorRegisteredOnCurrentOperatorSets(operator)) { _totalOperators--; emit OperatorDeregistered(operator, address(_serviceManager)); @@ -539,14 +529,11 @@ contract ECDSAStakeRegistry is ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature, address signingKey ) internal virtual { - if (operatorRegisteredOnAVSDirectory(operator)) { - revert OperatorAlreadyRegistered(); - } - + // need to first register the operator to the AVS Directory to correctly update the operator weight + IServiceManager(_serviceManager).registerOperatorToAVS(operator, operatorSignature); int256 delta = _updateOperatorWeight(operator); _updateTotalWeight(delta); _updateOperatorSigningKey(operator, signingKey); - IServiceManager(_serviceManager).registerOperatorToAVS(operator, operatorSignature); if (!operatorRegisteredOnCurrentOperatorSets(operator)) { _totalOperators++; emit OperatorRegistered(operator, _serviceManager); diff --git a/src/unaudited/examples/ECDSAStakeRegistryPermissioned.sol b/src/unaudited/examples/ECDSAStakeRegistryPermissioned.sol index 6d61d0fe..02a259a3 100644 --- a/src/unaudited/examples/ECDSAStakeRegistryPermissioned.sol +++ b/src/unaudited/examples/ECDSAStakeRegistryPermissioned.sol @@ -76,6 +76,10 @@ contract ECDSAStakeRegistryPermissioned is ECDSAStakeRegistry { function _ejectOperator( address _operator ) internal { + if(!operatorRegistered(_operator)){ + revert OperatorNotRegistered(); + } + if (operatorRegisteredOnAVSDirectory(_operator)) { _deregisterOperatorM2Quorum(_operator); } @@ -109,7 +113,9 @@ contract ECDSAStakeRegistryPermissioned is ECDSAStakeRegistry { } delete allowlistedOperators[_operator]; emit OperatorRevoked(_operator); - _ejectOperator(_operator); + if(operatorRegistered(_operator)){ + _ejectOperator(_operator); + } } /// @inheritdoc ECDSAStakeRegistry diff --git a/test/unit/ECDSAServiceManager.t.sol b/test/unit/ECDSAServiceManager.t.sol index 2282f96a..f8e043df 100644 --- a/test/unit/ECDSAServiceManager.t.sol +++ b/test/unit/ECDSAServiceManager.t.sol @@ -78,6 +78,46 @@ contract MockRewardsCoordinator { ) external pure {} } +contract MockAVSDirectory { + // 使用 mapping 存储每个 operator 的状态 + mapping(address => mapping(address => IAVSDirectoryTypes.OperatorAVSRegistrationStatus)) + private operatorStatus; + + function registerOperatorToAVS( + address operator, + ISignatureUtils.SignatureWithSaltAndExpiry memory + ) external { + // 设置特定 operator 的状态为 REGISTERED + operatorStatus[msg.sender][operator] = IAVSDirectoryTypes.OperatorAVSRegistrationStatus.REGISTERED; + } + + function deregisterOperatorFromAVS( + address operator + ) external { + // 设置特定 operator 的状态为 UNREGISTERED + operatorStatus[msg.sender][operator] = IAVSDirectoryTypes.OperatorAVSRegistrationStatus.UNREGISTERED; + } + + function updateAVSMetadataURI( + string memory + ) external pure {} + + function setAvsOperatorStatus( + address avs, + address operator, + IAVSDirectoryTypes.OperatorAVSRegistrationStatus status + ) external { + operatorStatus[avs][operator] = status; + } + + function avsOperatorStatus( + address avs, + address operator + ) external view returns (IAVSDirectoryTypes.OperatorAVSRegistrationStatus) { + return operatorStatus[avs][operator]; + } +} + contract ECDSAServiceManagerSetup is Test { MockDelegationManager public mockDelegationManager; AVSDirectoryMock public mockAVSDirectory; diff --git a/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol b/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol index bf226960..8a688f9a 100644 --- a/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol +++ b/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol @@ -19,7 +19,8 @@ import {IAVSDirectory} from "../../src/unaudited/ECDSAStakeRegistry.sol"; contract EqualWeightECDSARegistry is ECDSAStakeRegistrySetup { ECDSAStakeRegistryEqualWeight internal fixedWeightRegistry; - + address internal operator6 = makeAddr("operator6"); + address internal operator7 = makeAddr("operator7"); function setUp() public virtual override { super.setUp(); fixedWeightRegistry = @@ -37,48 +38,48 @@ contract EqualWeightECDSARegistry is ECDSAStakeRegistrySetup { fixedWeightRegistry.initialize(address(mockServiceManager), 100, quorum); - fixedWeightRegistry.permitOperator(operator1); - fixedWeightRegistry.permitOperator(operator2); + fixedWeightRegistry.permitOperator(operator6); + fixedWeightRegistry.permitOperator(operator7); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; - vm.prank(operator1); - fixedWeightRegistry.registerOperatorM2Quorum(operatorSignature, operator1); + vm.prank(operator6); + fixedWeightRegistry.registerOperatorM2Quorum(operatorSignature, operator6); - vm.prank(operator2); - fixedWeightRegistry.registerOperatorM2Quorum(operatorSignature, operator2); + vm.prank(operator7); + fixedWeightRegistry.registerOperatorM2Quorum(operatorSignature, operator7); } function test_FixedStakeUpdates() public { - assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator1), 1); - assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator2), 1); + assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator6), 1); + assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator7), 1); assertEq(fixedWeightRegistry.getLastCheckpointTotalWeight(), 2); vm.roll(block.number + 1); - vm.prank(operator1); + vm.prank(operator6); fixedWeightRegistry.deregisterOperatorM2Quorum(); - assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator1), 0); - assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator2), 1); + assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator6), 0); + assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator7), 1); assertEq(fixedWeightRegistry.getLastCheckpointTotalWeight(), 1); vm.roll(block.number + 1); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; - vm.prank(operator1); - fixedWeightRegistry.registerOperatorM2Quorum(operatorSignature, operator1); + vm.prank(operator6); + fixedWeightRegistry.registerOperatorM2Quorum(operatorSignature, operator6); - assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator1), 1); - assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator2), 1); + assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator6), 1); + assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator7), 1); assertEq(fixedWeightRegistry.getLastCheckpointTotalWeight(), 2); vm.roll(block.number + 1); address[] memory operators = new address[](2); - operators[0] = operator1; - operators[1] = operator2; + operators[0] = operator6; + operators[1] = operator7; fixedWeightRegistry.updateOperators(operators); - assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator1), 1); - assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator2), 1); + assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator6), 1); + assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator7), 1); assertEq(fixedWeightRegistry.getLastCheckpointTotalWeight(), 2); } } diff --git a/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol b/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol index c77010d3..fc7026f7 100644 --- a/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol +++ b/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol @@ -19,7 +19,8 @@ import {IAVSDirectory} from "../../src/unaudited/ECDSAStakeRegistry.sol"; contract PermissionedECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { ECDSAStakeRegistryPermissioned internal permissionedRegistry; - + address internal operator6 = makeAddr("operator6"); + address internal operator7 = makeAddr("operator7"); function setUp() public virtual override { super.setUp(); permissionedRegistry = @@ -37,16 +38,16 @@ contract PermissionedECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { permissionedRegistry.initialize(address(mockServiceManager), 100, quorum); - permissionedRegistry.permitOperator(operator1); - permissionedRegistry.permitOperator(operator2); + permissionedRegistry.permitOperator(operator6); + permissionedRegistry.permitOperator(operator7); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; - vm.prank(operator1); - permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator1); + vm.prank(operator6); + permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator6); - vm.prank(operator2); - permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator1); + vm.prank(operator7); + permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator7); vm.roll(block.number + 1); } @@ -59,8 +60,8 @@ contract PermissionedECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { } function test_When_Owner_PermitOperator() public { - address operator3 = address(0xBEEF); - permissionedRegistry.permitOperator(operator3); + address operator8 = address(0xBEEF); + permissionedRegistry.permitOperator(operator8); } function test_RevertsWhen_NotOwner_RevokeOperator() public { @@ -78,55 +79,53 @@ contract PermissionedECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { } function test_When_Owner_RevokeOperator() public { - permissionedRegistry.revokeOperator(operator1); + permissionedRegistry.revokeOperator(operator6); } function test_RevertsWhen_NotOwner_EjectOperator() public { address notOwner = address(0xBEEF); vm.prank(notOwner); vm.expectRevert("Ownable: caller is not the owner"); - permissionedRegistry.ejectOperator(operator1); + permissionedRegistry.ejectOperator(operator6); } function test_RevertsWhen_NotOperator_EjectOperator() public { address notOperator = address(0xBEEF); - vm.expectRevert( - abi.encodeWithSelector(IECDSAStakeRegistryErrors.OperatorNotRegistered.selector) - ); + vm.expectRevert(); permissionedRegistry.ejectOperator(notOperator); } function test_When_Owner_EjectOperator() public { - permissionedRegistry.ejectOperator(operator1); + permissionedRegistry.ejectOperator(operator6); } function test_RevertsWhen_NotAllowlisted_RegisterOperatorM2Quorum() public { - address operator3 = address(0xBEEF); + address operator8 = address(0xBEEF); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; vm.expectRevert( abi.encodeWithSelector(ECDSAStakeRegistryPermissioned.OperatorNotAllowlisted.selector) ); - vm.prank(operator3); - permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator3); + vm.prank(operator8); + permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator8); } function test_WhenAllowlisted_RegisterOperatorM2Quorum() public { - address operator3 = address(0xBEEF); - permissionedRegistry.permitOperator(operator3); + address operator8 = address(0xBEEF); + permissionedRegistry.permitOperator(operator8); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; - vm.prank(operator3); - permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator3); + vm.prank(operator8); + permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator8); } function test_DeregisterOperatorM2Quorum() public { - address operator3 = address(0xBEEF); - permissionedRegistry.permitOperator(operator3); + address operator8 = address(0xBEEF); + permissionedRegistry.permitOperator(operator8); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; - vm.prank(operator3); - permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator3); + vm.prank(operator8); + permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator8); - vm.prank(operator3); + vm.prank(operator8); permissionedRegistry.deregisterOperatorM2Quorum(); } } diff --git a/test/unit/ECDSAStakeRegistryUnit.t.sol b/test/unit/ECDSAStakeRegistryUnit.t.sol index d2481f6b..f37006f7 100644 --- a/test/unit/ECDSAStakeRegistryUnit.t.sol +++ b/test/unit/ECDSAStakeRegistryUnit.t.sol @@ -17,18 +17,26 @@ import { } from "../../src/interfaces/IECDSAStakeRegistry.sol"; import {IAllocationManager} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; import {IAVSDirectory, IAVSDirectoryTypes} from "../../src/unaudited/ECDSAStakeRegistry.sol"; +import {OperatorSet} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol"; contract MockServiceManager { - // solhint-disable-next-line + MockAVSDirectory public immutable avsDirectory; + constructor(address _avsDirectory) { + avsDirectory = MockAVSDirectory(_avsDirectory); + } function deregisterOperatorFromAVS( - address - ) external {} + address operator + ) external { + avsDirectory.deregisterOperatorFromAVS(operator); + } function registerOperatorToAVS( - address, - ISignatureUtils.SignatureWithSaltAndExpiry memory // solhint-disable-next-line - ) external {} + address operator, + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature + ) external { + avsDirectory.registerOperatorToAVS(operator, operatorSignature); + } } contract MockDelegationManager { @@ -49,42 +57,127 @@ contract MockDelegationManager { } contract MockAVSDirectory { - mapping(address => mapping(address => IAVSDirectoryTypes.OperatorAVSRegistrationStatus)) - private operatorStatus; + error OperatorAlreadyRegistered(); + error OperatorNotRegistered(); + + // 简化为只记录operator的状态 + mapping(address => IAVSDirectoryTypes.OperatorAVSRegistrationStatus) private operatorStatus; function registerOperatorToAVS( - address, + address operator, ISignatureUtils.SignatureWithSaltAndExpiry memory - ) external pure {} + ) external { + if(operatorStatus[operator] == IAVSDirectoryTypes.OperatorAVSRegistrationStatus.REGISTERED) { + revert OperatorAlreadyRegistered(); + } + operatorStatus[operator] = IAVSDirectoryTypes.OperatorAVSRegistrationStatus.REGISTERED; + } function deregisterOperatorFromAVS( - address - ) external pure {} + address operator + ) external { + if(operatorStatus[operator] == IAVSDirectoryTypes.OperatorAVSRegistrationStatus.UNREGISTERED) { + revert OperatorNotRegistered(); + } + operatorStatus[operator] = IAVSDirectoryTypes.OperatorAVSRegistrationStatus.UNREGISTERED; + } function updateAVSMetadataURI( string memory ) external pure {} + function setAvsOperatorStatus( address avs, address operator, IAVSDirectoryTypes.OperatorAVSRegistrationStatus status ) external { - operatorStatus[avs][operator] = status; + operatorStatus[operator] = status; } - function avsOperatorStatus(address avs, address operator) external view returns (IAVSDirectoryTypes.OperatorAVSRegistrationStatus) { - return operatorStatus[avs][operator]; + function avsOperatorStatus( + address avs, + address operator + ) external view returns (IAVSDirectoryTypes.OperatorAVSRegistrationStatus) { + return operatorStatus[operator]; } } contract MockAllocationManager { + // 定义 Allocation 结构体 + struct Allocation { + uint64 currentMagnitude; + uint32 lastUpdatedBlock; + } + + // 存储 allocation 信息 + mapping(address => mapping(bytes32 => Allocation)) public allocations; + mapping(address => mapping(address => uint64)) public maxMagnitudes; + mapping(address => mapping(address => mapping(uint32 => bool))) public operatorSetMembers; + + // 设置 operator set membership + function setOperatorSetMembership( + address operator, + address avs, + uint32 setId, + bool isMember + ) external { + operatorSetMembers[operator][avs][setId] = isMember; + } + + // 检查 operator set membership + function isMemberOfOperatorSet( + address operator, + OperatorSet memory operatorSet + ) external view returns (bool) { + return operatorSetMembers[operator][operatorSet.avs][operatorSet.id]; + } + + // 设置 allocation + function setAllocation( + address operator, + OperatorSet memory operatorSet, + address strategy, + Allocation memory allocation + ) external { + bytes32 key = keccak256(abi.encode(operator, operatorSet, strategy)); + allocations[operator][key] = allocation; + } + + // 获取 allocation + function getAllocation( + address operator, + OperatorSet memory operatorSet, + IStrategy strategy + ) external view returns (Allocation memory) { + bytes32 key = keccak256(abi.encode(operator, operatorSet, address(strategy))); + return allocations[operator][key]; + } + + // 设置 maxMagnitude + function setMaxMagnitude( + address operator, + address strategy, + uint64 magnitude + ) external { + maxMagnitudes[operator][strategy] = magnitude; + } + + // 获取 maxMagnitude + function getMaxMagnitude( + address operator, + IStrategy strategy + ) external view returns (uint64) { + return maxMagnitudes[operator][address(strategy)]; + } + + // 其他必要的函数 function setAVSRegistrar(address avs, address registrar) external {} - function isOperatorSet(bytes memory operatorSet) external pure returns (bool) { + function isOperatorSet(OperatorSet memory operatorSet) external pure returns (bool) { return true; } - function getStrategiesInOperatorSet(bytes memory operatorSet) external pure returns (IStrategy[] memory) { + function getStrategiesInOperatorSet(OperatorSet memory operatorSet) external pure returns (IStrategy[] memory) { IStrategy[] memory strategies = new IStrategy[](2); strategies[0] = IStrategy(address(900)); strategies[1] = IStrategy(address(901)); @@ -98,12 +191,19 @@ contract ECDSAStakeRegistrySetup is Test, IECDSAStakeRegistryEvents { MockAllocationManager public mockAllocationManager; MockServiceManager public mockServiceManager; ECDSAStakeRegistry public registry; + address public owner = makeAddr("owner"); address internal operator1; address internal operator2; + address internal operator3; + address internal operator4; uint256 internal operator1Pk; uint256 internal operator2Pk; + uint256 internal operator3Pk; + uint256 internal operator4Pk; bytes internal signature1; bytes internal signature2; + bytes internal signature3; + bytes internal signature4; address[] internal signers; bytes[] internal signatures; bytes32 internal msgHash; @@ -112,11 +212,19 @@ contract ECDSAStakeRegistrySetup is Test, IECDSAStakeRegistryEvents { function setUp() public virtual { (operator1, operator1Pk) = makeAddrAndKey("Signer 1"); (operator2, operator2Pk) = makeAddrAndKey("Signer 2"); + (operator3, operator3Pk) = makeAddrAndKey("Signer 3"); + (operator4, operator4Pk) = makeAddrAndKey("Signer 4"); + + // 1. 先创建 mockAVSDirectory + mockAVSDirectory = new MockAVSDirectory(); + + // 2. 再创建 mockServiceManager,并传入 mockAVSDirectory 的地址 + mockServiceManager = new MockServiceManager(address(mockAVSDirectory)); + + // 3. 创建其他 mock 合约 mockDelegationManager = new MockDelegationManager(); - mockServiceManager = new MockServiceManager(); mockAllocationManager = new MockAllocationManager(); - mockAVSDirectory = new MockAVSDirectory(); - mockAVSRegistrarAddr = makeAddr("mockAVSRegistrar"); + mockAVSRegistrarAddr = makeAddr("mockAVSRegistrar"); IStrategy mockStrategy = IStrategy(address(0x1234)); IECDSAStakeRegistryTypes.Quorum memory quorum = IECDSAStakeRegistryTypes.Quorum({ @@ -134,15 +242,86 @@ contract ECDSAStakeRegistrySetup is Test, IECDSAStakeRegistryEvents { IAVSDirectory(address(mockAVSDirectory)) ); + vm.prank(owner); registry.initialize(address(mockServiceManager), 100, quorum); + + // Set up 3 operator sets + uint32[] memory operatorSetIds = new uint32[](3); + operatorSetIds[0] = 1; + operatorSetIds[1] = 2; + operatorSetIds[2] = 3; + vm.prank(owner); + registry.setCurrentOperatorSetIds(operatorSetIds); + + // Set up operator set membership + for(uint32 setId = 1; setId <= 3; setId++) { + MockAllocationManager(address(mockAllocationManager)).setOperatorSetMembership( + operator3, + address(mockServiceManager), + setId, + true + ); + MockAllocationManager(address(mockAllocationManager)).setOperatorSetMembership( + operator4, + address(mockServiceManager), + setId, + true + ); + + // 设置 allocation 和 maxMagnitude + OperatorSet memory operatorSet = OperatorSet({ + avs: address(mockServiceManager), + id: setId + }); + + // 为每个 strategy 设置 allocation + IStrategy[] memory strategies = mockAllocationManager.getStrategiesInOperatorSet(operatorSet); + for(uint i = 0; i < strategies.length; i++) { + // 设置 allocation + MockAllocationManager.Allocation memory allocation = MockAllocationManager.Allocation({ + currentMagnitude: 1000, + lastUpdatedBlock: uint32(block.number) + }); + + MockAllocationManager(address(mockAllocationManager)).setAllocation( + operator3, + operatorSet, + address(strategies[i]), + allocation + ); + MockAllocationManager(address(mockAllocationManager)).setAllocation( + operator4, + operatorSet, + address(strategies[i]), + allocation + ); + + // 设置 maxMagnitude + MockAllocationManager(address(mockAllocationManager)).setMaxMagnitude( + operator3, + address(strategies[i]), + 1000 + ); + MockAllocationManager(address(mockAllocationManager)).setMaxMagnitude( + operator4, + address(strategies[i]), + 1000 + ); + } + } + + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; + vm.prank(operator1); registry.registerOperatorM2Quorum(operatorSignature, operator1); + vm.prank(operator2); registry.registerOperatorM2Quorum(operatorSignature, operator2); + vm.roll(block.number + 1); } @@ -165,6 +344,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { vm.expectEmit(true, true, false, true); emit QuorumUpdated(oldQuorum, newQuorum); + vm.prank(owner); registry.updateQuorumConfig(newQuorum, operators); } @@ -182,6 +362,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { operators[1] = operator2; vm.expectRevert(IECDSAStakeRegistryErrors.InvalidQuorum.selector); + vm.prank(owner); registry.updateQuorumConfig(invalidQuorum, operators); } @@ -212,6 +393,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { operators[1] = operator2; /// Showing this doesnt revert + vm.prank(owner); registry.updateQuorumConfig(quorum, operators); } @@ -227,6 +409,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { invalidQuorum.strategies[1] = StrategyParams({strategy: IStrategy(address(420)), multiplier: 5000}); vm.expectRevert(IECDSAStakeRegistryErrors.NotSorted.selector); + vm.prank(owner); registry.updateQuorumConfig(invalidQuorum, operators); } @@ -242,6 +425,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { invalidQuorum.strategies[1] = StrategyParams({strategy: IStrategy(address(419)), multiplier: 5000}); vm.expectRevert(IECDSAStakeRegistryErrors.NotSorted.selector); + vm.prank(owner); registry.updateQuorumConfig(invalidQuorum, operators); } @@ -255,16 +439,17 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { operators[1] = operator2; vm.expectRevert(IECDSAStakeRegistryErrors.InvalidQuorum.selector); + vm.prank(owner); registry.updateQuorumConfig(invalidQuorum, operators); } function test_RegisterOperatorM2Quorum() public { - address operator3 = address(0x125); + address operator5 = address(0x125); ISignatureUtils.SignatureWithSaltAndExpiry memory signature; - vm.prank(operator3); - registry.registerOperatorM2Quorum(signature, operator3); - assertTrue(registry.operatorRegistered(operator3)); - assertEq(registry.getLastCheckpointOperatorWeight(operator3), 1000); + vm.prank(operator5); + registry.registerOperatorM2Quorum(signature, operator5); + assertTrue(registry.operatorRegistered(operator5)); + assertEq(registry.getLastCheckpointOperatorWeight(operator5), 1000); } function test_RevertsWhen_AlreadyRegistered_RegisterOperatorM2Quorum() public { @@ -272,7 +457,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { assertEq(registry.getLastCheckpointTotalWeight(), 2000); ISignatureUtils.SignatureWithSaltAndExpiry memory signature; - vm.expectRevert(IECDSAStakeRegistryErrors.OperatorAlreadyRegistered.selector); + vm.expectRevert(); vm.prank(operator1); registry.registerOperatorM2Quorum(signature, operator1); } @@ -319,12 +504,12 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { function test_When_OperatorNotRegistered_UpdateOperators() public { address[] memory operators = new address[](3); - address operator3 = address(0xBEEF); + address operator5 = address(0xBEEF); operators[0] = operator1; operators[1] = operator2; - operators[2] = operator3; + operators[2] = operator5; registry.updateOperators(operators); - assertEq(registry.getLastCheckpointOperatorWeight(operator3), 0); + assertEq(registry.getLastCheckpointOperatorWeight(operator5), 0); } function test_When_SingleOperator_UpdateOperators() public { @@ -394,6 +579,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { operators[0] = operator1; operators[1] = operator2; + vm.prank(owner); registry.updateQuorumConfig(quorum, operators); address[] memory strategies = new address[](2); @@ -428,6 +614,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { address[] memory operators = new address[](2); operators[0] = operator1; operators[1] = operator2; + vm.prank(owner); registry.updateMinimumWeight(newMinimumWeight, operators); uint256 updatedMinimumWeight = registry.minimumWeight(); @@ -449,6 +636,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { address[] memory operators = new address[](2); operators[0] = operator1; operators[1] = operator2; + vm.prank(owner); registry.updateMinimumWeight(initialMinimumWeight, operators); uint256 updatedMinimumWeight = registry.minimumWeight(); @@ -460,10 +648,12 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { address[] memory operators = new address[](2); operators[0] = operator1; operators[1] = operator2; + vm.prank(owner); registry.updateMinimumWeight(initialMinimumWeight, operators); uint256 newMinimumWeight = 0; + vm.prank(owner); registry.updateMinimumWeight(newMinimumWeight, operators); uint256 updatedMinimumWeight = registry.minimumWeight(); @@ -496,6 +686,8 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { console.log("operator1 weight", registry.getOperatorWeight(operator1)); console.log("operator2 weight", registry.getOperatorWeight(operator2)); console.log("threshold weight", registry.getLastCheckpointThresholdWeight()); + console.log("operator1 status", registry.operatorRegisteredOnAVSDirectory(operator1)); + console.log("operator2 status", registry.operatorRegisteredOnAVSDirectory(operator2)); registry.isValidSignature(msgHash, abi.encode(signers, signatures, block.number - 1)); } @@ -714,13 +906,14 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { } // Define private and public keys for operator3 and signer - uint256 private operator3Pk = 3; - address private operator3 = address(vm.addr(operator3Pk)); + // Define private and public keys for operator5 and signer + uint256 private operator5Pk = 3; + address private operator5 = address(vm.addr(operator5Pk)); uint256 private signerPk = 4; address private signer = address(vm.addr(signerPk)); function test_WhenUsingSigningKey_RegierOperatorWithSignature() public { - address operator = operator3; + address operator = operator5; ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; @@ -738,7 +931,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { } function test_Twice_RegierOperatorWithSignature() public { - address operator = operator3; + address operator = operator5; ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; @@ -764,7 +957,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { } function test_WhenUsingSigningKey_CheckSignatures() public { - address operator = operator3; + address operator = operator5; ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; @@ -788,7 +981,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { } function test_WhenUsingSigningKey_CheckSignaturesAtBlock() public { - address operator = operator3; + address operator = operator5; address initialSigningKey = address(vm.addr(signerPk)); address updatedSigningKey = address(vm.addr(signerPk + 1)); @@ -829,7 +1022,7 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { } function test_WhenUsingPriorSigningKey_CheckSignaturesAtBlock() public { - address operator = operator3; + address operator = operator5; address initialSigningKey = address(vm.addr(signerPk)); address updatedSigningKey = address(vm.addr(signerPk + 1)); From 061aad2ea279ba537ef380d4daf36452690e428b Mon Sep 17 00:00:00 2001 From: jaxxjj Date: Wed, 26 Feb 2025 23:37:17 -0700 Subject: [PATCH 07/12] chore: fmt --- src/interfaces/IECDSAStakeRegistry.sol | 2 +- src/unaudited/ECDSAAVSRegistrar.sol | 2 +- src/unaudited/ECDSAServiceManagerBase.sol | 21 ++- src/unaudited/ECDSAStakeRegistry.sol | 13 +- src/unaudited/ECDSAStakeRegistryStorage.sol | 3 +- .../ECDSAStakeRegistryEqualWeight.sol | 2 +- .../ECDSAStakeRegistryPermissioned.sol | 11 +- test/mocks/ECDSAServiceManagerMock.sol | 1 - test/mocks/ECDSAStakeRegistryMock.sol | 1 - test/unit/ECDSAServiceManager.t.sol | 85 +++++----- .../ECDSAStakeRegistryEqualWeightUnit.t.sol | 35 +++-- .../ECDSAStakeRegistryPermissionedUnit.t.sol | 37 +++-- test/unit/ECDSAStakeRegistryUnit.t.sol | 147 +++++++----------- 13 files changed, 172 insertions(+), 188 deletions(-) diff --git a/src/interfaces/IECDSAStakeRegistry.sol b/src/interfaces/IECDSAStakeRegistry.sol index 7b7dfeeb..8115164d 100644 --- a/src/interfaces/IECDSAStakeRegistry.sol +++ b/src/interfaces/IECDSAStakeRegistry.sol @@ -370,4 +370,4 @@ interface IECDSAStakeRegistry is function getLastCheckpointThresholdWeightAtBlock( uint32 blockNumber ) external view returns (uint256); -} \ No newline at end of file +} diff --git a/src/unaudited/ECDSAAVSRegistrar.sol b/src/unaudited/ECDSAAVSRegistrar.sol index ce36aa08..8bbaa66f 100644 --- a/src/unaudited/ECDSAAVSRegistrar.sol +++ b/src/unaudited/ECDSAAVSRegistrar.sol @@ -58,7 +58,7 @@ contract AVSRegistrar is IAVSRegistrar, Ownable { function supportsAVS( address avsAddr - ) external view returns (bool){ + ) external view returns (bool) { return avs == avsAddr; } } diff --git a/src/unaudited/ECDSAServiceManagerBase.sol b/src/unaudited/ECDSAServiceManagerBase.sol index d2358442..e16b1476 100644 --- a/src/unaudited/ECDSAServiceManagerBase.sol +++ b/src/unaudited/ECDSAServiceManagerBase.sol @@ -242,7 +242,9 @@ abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable * @param operatorSetId The ID of the operator set to query * @return Array of strategy addresses from the specified operator set */ - function getOperatorSetStrategies(uint32 operatorSetId) external view virtual returns (address[] memory) { + function getOperatorSetStrategies( + uint32 operatorSetId + ) external view virtual returns (address[] memory) { return _getOperatorSetStrategies(operatorSetId); } @@ -364,23 +366,26 @@ abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable * @param operatorSetId The ID of the operator set to get strategies from * @return Array of strategy addresses from the specified operator set */ - function _getOperatorSetStrategies(uint32 operatorSetId) internal view virtual returns (address[] memory) { + function _getOperatorSetStrategies( + uint32 operatorSetId + ) internal view virtual returns (address[] memory) { OperatorSet memory operatorSet = OperatorSet(address(this), operatorSetId); - + // Return empty array if this is not a valid operator set if (!IAllocationManager(allocationManager).isOperatorSet(operatorSet)) { return new address[](0); } - + // Get strategies for this operator set - IStrategy[] memory strategies = IAllocationManager(allocationManager).getStrategiesInOperatorSet(operatorSet); - + IStrategy[] memory strategies = + IAllocationManager(allocationManager).getStrategiesInOperatorSet(operatorSet); + // Convert IStrategy array to address array address[] memory strategyAddresses = new address[](strategies.length); for (uint256 i = 0; i < strategies.length; i++) { strategyAddresses[i] = address(strategies[i]); } - + return strategyAddresses; } @@ -455,4 +460,4 @@ abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable // storage gap for upgradeability // slither-disable-next-line shadowing-state uint256[49] private __GAP; -} \ No newline at end of file +} diff --git a/src/unaudited/ECDSAStakeRegistry.sol b/src/unaudited/ECDSAStakeRegistry.sol index 05a3e46c..61beae2d 100644 --- a/src/unaudited/ECDSAStakeRegistry.sol +++ b/src/unaudited/ECDSAStakeRegistry.sol @@ -19,15 +19,16 @@ import {SignatureCheckerUpgradeable} from "@openzeppelin-upgrades/contracts/utils/cryptography/SignatureCheckerUpgradeable.sol"; import {IERC1271Upgradeable} from "@openzeppelin-upgrades/contracts/interfaces/IERC1271Upgradeable.sol"; -import { - IAVSDirectoryTypes -} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; +import {IAVSDirectoryTypes} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; import {IAllocationManager} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; import {OperatorSet} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol"; interface IAVSDirectory { - function avsOperatorStatus(address avs, address operator) external view returns (IAVSDirectoryTypes.OperatorAVSRegistrationStatus); + function avsOperatorStatus( + address avs, + address operator + ) external view returns (IAVSDirectoryTypes.OperatorAVSRegistrationStatus); } /// @title ECDSA Stake Registry @@ -514,7 +515,7 @@ contract ECDSAStakeRegistry is IServiceManager(_serviceManager).deregisterOperatorFromAVS(operator); int256 delta = _updateOperatorWeight(operator); _updateTotalWeight(delta); - + if (!operatorRegisteredOnCurrentOperatorSets(operator)) { _totalOperators--; emit OperatorDeregistered(operator, address(_serviceManager)); @@ -770,4 +771,4 @@ contract ECDSAStakeRegistry is revert InsufficientSignedStake(); } } -} \ No newline at end of file +} diff --git a/src/unaudited/ECDSAStakeRegistryStorage.sol b/src/unaudited/ECDSAStakeRegistryStorage.sol index d7573107..6ebc84b9 100644 --- a/src/unaudited/ECDSAStakeRegistryStorage.sol +++ b/src/unaudited/ECDSAStakeRegistryStorage.sol @@ -13,6 +13,7 @@ import {IAllocationManager} from import {IAVSRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IAVSRegistrar.sol"; import {IAVSDirectoryTypes} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; import {IAVSDirectory} from "./ECDSAStakeRegistry.sol"; + abstract contract ECDSAStakeRegistryStorage is IECDSAStakeRegistry { /// @notice Manages staking delegations through the DelegationManager interface IDelegationManager internal immutable DELEGATION_MANAGER; @@ -79,4 +80,4 @@ abstract contract ECDSAStakeRegistryStorage is IECDSAStakeRegistry { /// @dev Reserves storage slots for future upgrades // solhint-disable-next-line uint256[40] private __gap; -} \ No newline at end of file +} diff --git a/src/unaudited/examples/ECDSAStakeRegistryEqualWeight.sol b/src/unaudited/examples/ECDSAStakeRegistryEqualWeight.sol index adfc1dab..985cb6ee 100644 --- a/src/unaudited/examples/ECDSAStakeRegistryEqualWeight.sol +++ b/src/unaudited/examples/ECDSAStakeRegistryEqualWeight.sol @@ -56,4 +56,4 @@ contract ECDSAStakeRegistryEqualWeight is ECDSAStakeRegistryPermissioned { emit OperatorWeightUpdated(_operator, oldWeight, newWeight); return delta; } -} \ No newline at end of file +} diff --git a/src/unaudited/examples/ECDSAStakeRegistryPermissioned.sol b/src/unaudited/examples/ECDSAStakeRegistryPermissioned.sol index 02a259a3..4a504705 100644 --- a/src/unaudited/examples/ECDSAStakeRegistryPermissioned.sol +++ b/src/unaudited/examples/ECDSAStakeRegistryPermissioned.sol @@ -1,4 +1,3 @@ - // SPDX-License-Identifier: MIT pragma solidity ^0.8.27; @@ -8,10 +7,8 @@ import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; import {IAllocationManager} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; -import {IAVSDirectory} from - "../ECDSAStakeRegistry.sol"; -import {IAVSDirectoryTypes} from - "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; +import {IAVSDirectory} from "../ECDSAStakeRegistry.sol"; +import {IAVSDirectoryTypes} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; /// @title ECDSA Stake Registry with an Operator Allowlist /// @dev THIS CONTRACT IS NOT AUDITED @@ -76,7 +73,7 @@ contract ECDSAStakeRegistryPermissioned is ECDSAStakeRegistry { function _ejectOperator( address _operator ) internal { - if(!operatorRegistered(_operator)){ + if (!operatorRegistered(_operator)) { revert OperatorNotRegistered(); } @@ -113,7 +110,7 @@ contract ECDSAStakeRegistryPermissioned is ECDSAStakeRegistry { } delete allowlistedOperators[_operator]; emit OperatorRevoked(_operator); - if(operatorRegistered(_operator)){ + if (operatorRegistered(_operator)) { _ejectOperator(_operator); } } diff --git a/test/mocks/ECDSAServiceManagerMock.sol b/test/mocks/ECDSAServiceManagerMock.sol index a05f2232..091bdcd7 100644 --- a/test/mocks/ECDSAServiceManagerMock.sol +++ b/test/mocks/ECDSAServiceManagerMock.sol @@ -36,5 +36,4 @@ contract ECDSAServiceManagerMock is ECDSAServiceManagerBase { // ) public virtual initializer { // __ServiceManagerBase_init(initialOwner, rewardsInitiator); // } - } diff --git a/test/mocks/ECDSAStakeRegistryMock.sol b/test/mocks/ECDSAStakeRegistryMock.sol index da670557..19007010 100644 --- a/test/mocks/ECDSAStakeRegistryMock.sol +++ b/test/mocks/ECDSAStakeRegistryMock.sol @@ -15,5 +15,4 @@ contract ECDSAStakeRegistryMock is ECDSAStakeRegistry { address _avsRegistrar, IAVSDirectory _avsDirectory ) ECDSAStakeRegistry(_delegationManager, _allocationManager, _avsRegistrar, _avsDirectory) {} - } diff --git a/test/unit/ECDSAServiceManager.t.sol b/test/unit/ECDSAServiceManager.t.sol index f8e043df..c47d828c 100644 --- a/test/unit/ECDSAServiceManager.t.sol +++ b/test/unit/ECDSAServiceManager.t.sol @@ -23,11 +23,21 @@ import {IAVSDirectory, IAVSDirectoryTypes} from "../../src/unaudited/ECDSAStakeR import {OperatorSet} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol"; contract MockPermissionController { - function addPendingAdmin(address account, address admin) external {} - function removePendingAdmin(address account, address admin) external {} - function removeAdmin(address account, address admin) external {} - function setAppointee(address account, address appointee, address target, bytes4 selector) external {} - function removeAppointee(address account, address appointee, address target, bytes4 selector) external {} + function addPendingAdmin(address account, address admin) external {} + function removePendingAdmin(address account, address admin) external {} + function removeAdmin(address account, address admin) external {} + function setAppointee( + address account, + address appointee, + address target, + bytes4 selector + ) external {} + function removeAppointee( + address account, + address appointee, + address target, + bytes4 selector + ) external {} } contract MockDelegationManager { @@ -49,12 +59,16 @@ contract MockDelegationManager { contract MockAllocationManager { function setAVSRegistrar(address avs, address registrar) external {} - - function isOperatorSet(OperatorSet memory operatorSet) external pure returns (bool) { + + function isOperatorSet( + OperatorSet memory operatorSet + ) external pure returns (bool) { return true; } - - function getStrategiesInOperatorSet(OperatorSet memory operatorSet) external pure returns (IStrategy[] memory) { + + function getStrategiesInOperatorSet( + OperatorSet memory operatorSet + ) external pure returns (IStrategy[] memory) { IStrategy[] memory strategies = new IStrategy[](2); strategies[0] = IStrategy(address(900)); strategies[1] = IStrategy(address(901)); @@ -80,22 +94,24 @@ contract MockRewardsCoordinator { contract MockAVSDirectory { // 使用 mapping 存储每个 operator 的状态 - mapping(address => mapping(address => IAVSDirectoryTypes.OperatorAVSRegistrationStatus)) - private operatorStatus; + mapping(address => mapping(address => IAVSDirectoryTypes.OperatorAVSRegistrationStatus)) private + operatorStatus; function registerOperatorToAVS( address operator, ISignatureUtils.SignatureWithSaltAndExpiry memory ) external { // 设置特定 operator 的状态为 REGISTERED - operatorStatus[msg.sender][operator] = IAVSDirectoryTypes.OperatorAVSRegistrationStatus.REGISTERED; + operatorStatus[msg.sender][operator] = + IAVSDirectoryTypes.OperatorAVSRegistrationStatus.REGISTERED; } function deregisterOperatorFromAVS( address operator ) external { // 设置特定 operator 的状态为 UNREGISTERED - operatorStatus[msg.sender][operator] = IAVSDirectoryTypes.OperatorAVSRegistrationStatus.UNREGISTERED; + operatorStatus[msg.sender][operator] = + IAVSDirectoryTypes.OperatorAVSRegistrationStatus.UNREGISTERED; } function updateAVSMetadataURI( @@ -103,13 +119,13 @@ contract MockAVSDirectory { ) external pure {} function setAvsOperatorStatus( - address avs, - address operator, + address avs, + address operator, IAVSDirectoryTypes.OperatorAVSRegistrationStatus status ) external { operatorStatus[avs][operator] = status; } - + function avsOperatorStatus( address avs, address operator @@ -139,13 +155,12 @@ contract ECDSAServiceManagerSetup is Test { mockAVSDirectory = new AVSDirectoryMock(); mockAllocationManager = new MockAllocationManager(); mockAVSRegistrarAddr = makeAddr("mockAVSRegistrar"); - mockStakeRegistry = - new ECDSAStakeRegistryMock( - IDelegationManager(address(mockDelegationManager)), - IAllocationManager(address(mockAllocationManager)), - mockAVSRegistrarAddr, - IAVSDirectory(address(mockAVSDirectory)) - ); + mockStakeRegistry = new ECDSAStakeRegistryMock( + IDelegationManager(address(mockDelegationManager)), + IAllocationManager(address(mockAllocationManager)), + mockAVSRegistrarAddr, + IAVSDirectory(address(mockAVSDirectory)) + ); mockRewardsCoordinator = new MockRewardsCoordinator(); mockPermissionController = new MockPermissionController(); @@ -283,49 +298,49 @@ contract ECDSAServiceManagerSetup is Test { function testGetOperatorSetStrategies() public { uint32 operatorSetId = 1; - + address[] memory strategies = serviceManager.getOperatorSetStrategies(operatorSetId); - + assertEq(strategies.length, 2, "Should return 2 strategies"); assertEq(strategies[0], address(900), "First strategy should match"); assertEq(strategies[1], address(901), "Second strategy should match"); } - + function testAddPendingAdmin() public { address admin = makeAddr("admin"); - + vm.prank(owner); serviceManager.addPendingAdmin(admin); } - + function testRemovePendingAdmin() public { address pendingAdmin = makeAddr("pendingAdmin"); - + vm.prank(owner); serviceManager.removePendingAdmin(pendingAdmin); } - + function testRemoveAdmin() public { address admin = makeAddr("admin"); - + vm.prank(owner); serviceManager.removeAdmin(admin); } - + function testSetAppointee() public { address appointee = makeAddr("appointee"); address target = makeAddr("target"); bytes4 selector = bytes4(keccak256("someFunction()")); - + vm.prank(owner); serviceManager.setAppointee(appointee, target, selector); } - + function testRemoveAppointee() public { address appointee = makeAddr("appointee"); address target = makeAddr("target"); bytes4 selector = bytes4(keccak256("someFunction()")); - + vm.prank(owner); serviceManager.removeAppointee(appointee, target, selector); } diff --git a/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol b/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol index 8a688f9a..c5c681a8 100644 --- a/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol +++ b/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol @@ -5,7 +5,8 @@ import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISi import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; -import {IAllocationManager} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {IAllocationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; import { IECDSAStakeRegistry, @@ -21,31 +22,33 @@ contract EqualWeightECDSARegistry is ECDSAStakeRegistrySetup { ECDSAStakeRegistryEqualWeight internal fixedWeightRegistry; address internal operator6 = makeAddr("operator6"); address internal operator7 = makeAddr("operator7"); + function setUp() public virtual override { super.setUp(); - fixedWeightRegistry = - new ECDSAStakeRegistryEqualWeight( - IDelegationManager(address(mockDelegationManager)), - IAllocationManager(address(mockAllocationManager)), - mockAVSRegistrarAddr, - IAVSDirectory(address(mockAVSDirectory)) - ); - + fixedWeightRegistry = new ECDSAStakeRegistryEqualWeight( + IDelegationManager(address(mockDelegationManager)), + IAllocationManager(address(mockAllocationManager)), + mockAVSRegistrarAddr, + IAVSDirectory(address(mockAVSDirectory)) + ); + IStrategy mockStrategy = IStrategy(address(0x1234)); - IECDSAStakeRegistryTypes.Quorum memory quorum = - IECDSAStakeRegistryTypes.Quorum({strategies: new IECDSAStakeRegistryTypes.StrategyParams[](1)}); - quorum.strategies[0] = IECDSAStakeRegistryTypes.StrategyParams({strategy: mockStrategy, multiplier: 10000}); - + IECDSAStakeRegistryTypes.Quorum memory quorum = IECDSAStakeRegistryTypes.Quorum({ + strategies: new IECDSAStakeRegistryTypes.StrategyParams[](1) + }); + quorum.strategies[0] = + IECDSAStakeRegistryTypes.StrategyParams({strategy: mockStrategy, multiplier: 10000}); + fixedWeightRegistry.initialize(address(mockServiceManager), 100, quorum); fixedWeightRegistry.permitOperator(operator6); fixedWeightRegistry.permitOperator(operator7); - + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; - + vm.prank(operator6); fixedWeightRegistry.registerOperatorM2Quorum(operatorSignature, operator6); - + vm.prank(operator7); fixedWeightRegistry.registerOperatorM2Quorum(operatorSignature, operator7); } diff --git a/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol b/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol index fc7026f7..ebcb9b7a 100644 --- a/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol +++ b/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol @@ -5,7 +5,8 @@ import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISi import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; -import {IAllocationManager} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {IAllocationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; import { IECDSAStakeRegistry, @@ -21,34 +22,36 @@ contract PermissionedECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { ECDSAStakeRegistryPermissioned internal permissionedRegistry; address internal operator6 = makeAddr("operator6"); address internal operator7 = makeAddr("operator7"); + function setUp() public virtual override { super.setUp(); - permissionedRegistry = - new ECDSAStakeRegistryPermissioned( - IDelegationManager(address(mockDelegationManager)), - IAllocationManager(address(mockAllocationManager)), - mockAVSRegistrarAddr, - IAVSDirectory(address(mockAVSDirectory)) - ); - + permissionedRegistry = new ECDSAStakeRegistryPermissioned( + IDelegationManager(address(mockDelegationManager)), + IAllocationManager(address(mockAllocationManager)), + mockAVSRegistrarAddr, + IAVSDirectory(address(mockAVSDirectory)) + ); + IStrategy mockStrategy = IStrategy(address(0x1234)); - IECDSAStakeRegistryTypes.Quorum memory quorum = - IECDSAStakeRegistryTypes.Quorum({strategies: new IECDSAStakeRegistryTypes.StrategyParams[](1)}); - quorum.strategies[0] = IECDSAStakeRegistryTypes.StrategyParams({strategy: mockStrategy, multiplier: 10000}); - + IECDSAStakeRegistryTypes.Quorum memory quorum = IECDSAStakeRegistryTypes.Quorum({ + strategies: new IECDSAStakeRegistryTypes.StrategyParams[](1) + }); + quorum.strategies[0] = + IECDSAStakeRegistryTypes.StrategyParams({strategy: mockStrategy, multiplier: 10000}); + permissionedRegistry.initialize(address(mockServiceManager), 100, quorum); permissionedRegistry.permitOperator(operator6); permissionedRegistry.permitOperator(operator7); - + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; - + vm.prank(operator6); permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator6); - + vm.prank(operator7); permissionedRegistry.registerOperatorM2Quorum(operatorSignature, operator7); - + vm.roll(block.number + 1); } diff --git a/test/unit/ECDSAStakeRegistryUnit.t.sol b/test/unit/ECDSAStakeRegistryUnit.t.sol index f37006f7..0a39e637 100644 --- a/test/unit/ECDSAStakeRegistryUnit.t.sol +++ b/test/unit/ECDSAStakeRegistryUnit.t.sol @@ -15,16 +15,20 @@ import { IECDSAStakeRegistryTypes, IECDSAStakeRegistryEvents } from "../../src/interfaces/IECDSAStakeRegistry.sol"; -import {IAllocationManager} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {IAllocationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; import {IAVSDirectory, IAVSDirectoryTypes} from "../../src/unaudited/ECDSAStakeRegistry.sol"; import {OperatorSet} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol"; - contract MockServiceManager { MockAVSDirectory public immutable avsDirectory; - constructor(address _avsDirectory) { + + constructor( + address _avsDirectory + ) { avsDirectory = MockAVSDirectory(_avsDirectory); } + function deregisterOperatorFromAVS( address operator ) external { @@ -59,15 +63,15 @@ contract MockDelegationManager { contract MockAVSDirectory { error OperatorAlreadyRegistered(); error OperatorNotRegistered(); - - // 简化为只记录operator的状态 + mapping(address => IAVSDirectoryTypes.OperatorAVSRegistrationStatus) private operatorStatus; function registerOperatorToAVS( address operator, ISignatureUtils.SignatureWithSaltAndExpiry memory ) external { - if(operatorStatus[operator] == IAVSDirectoryTypes.OperatorAVSRegistrationStatus.REGISTERED) { + if (operatorStatus[operator] == IAVSDirectoryTypes.OperatorAVSRegistrationStatus.REGISTERED) + { revert OperatorAlreadyRegistered(); } operatorStatus[operator] = IAVSDirectoryTypes.OperatorAVSRegistrationStatus.REGISTERED; @@ -76,7 +80,10 @@ contract MockAVSDirectory { function deregisterOperatorFromAVS( address operator ) external { - if(operatorStatus[operator] == IAVSDirectoryTypes.OperatorAVSRegistrationStatus.UNREGISTERED) { + if ( + operatorStatus[operator] + == IAVSDirectoryTypes.OperatorAVSRegistrationStatus.UNREGISTERED + ) { revert OperatorNotRegistered(); } operatorStatus[operator] = IAVSDirectoryTypes.OperatorAVSRegistrationStatus.UNREGISTERED; @@ -87,13 +94,13 @@ contract MockAVSDirectory { ) external pure {} function setAvsOperatorStatus( - address avs, - address operator, + address avs, + address operator, IAVSDirectoryTypes.OperatorAVSRegistrationStatus status ) external { operatorStatus[operator] = status; } - + function avsOperatorStatus( address avs, address operator @@ -103,18 +110,15 @@ contract MockAVSDirectory { } contract MockAllocationManager { - // 定义 Allocation 结构体 struct Allocation { uint64 currentMagnitude; uint32 lastUpdatedBlock; } - // 存储 allocation 信息 mapping(address => mapping(bytes32 => Allocation)) public allocations; mapping(address => mapping(address => uint64)) public maxMagnitudes; mapping(address => mapping(address => mapping(uint32 => bool))) public operatorSetMembers; - // 设置 operator set membership function setOperatorSetMembership( address operator, address avs, @@ -123,8 +127,7 @@ contract MockAllocationManager { ) external { operatorSetMembers[operator][avs][setId] = isMember; } - - // 检查 operator set membership + function isMemberOfOperatorSet( address operator, OperatorSet memory operatorSet @@ -132,7 +135,6 @@ contract MockAllocationManager { return operatorSetMembers[operator][operatorSet.avs][operatorSet.id]; } - // 设置 allocation function setAllocation( address operator, OperatorSet memory operatorSet, @@ -143,7 +145,6 @@ contract MockAllocationManager { allocations[operator][key] = allocation; } - // 获取 allocation function getAllocation( address operator, OperatorSet memory operatorSet, @@ -153,31 +154,25 @@ contract MockAllocationManager { return allocations[operator][key]; } - // 设置 maxMagnitude - function setMaxMagnitude( - address operator, - address strategy, - uint64 magnitude - ) external { + function setMaxMagnitude(address operator, address strategy, uint64 magnitude) external { maxMagnitudes[operator][strategy] = magnitude; } - // 获取 maxMagnitude - function getMaxMagnitude( - address operator, - IStrategy strategy - ) external view returns (uint64) { + function getMaxMagnitude(address operator, IStrategy strategy) external view returns (uint64) { return maxMagnitudes[operator][address(strategy)]; } - // 其他必要的函数 function setAVSRegistrar(address avs, address registrar) external {} - - function isOperatorSet(OperatorSet memory operatorSet) external pure returns (bool) { + + function isOperatorSet( + OperatorSet memory operatorSet + ) external pure returns (bool) { return true; } - - function getStrategiesInOperatorSet(OperatorSet memory operatorSet) external pure returns (IStrategy[] memory) { + + function getStrategiesInOperatorSet( + OperatorSet memory operatorSet + ) external pure returns (IStrategy[] memory) { IStrategy[] memory strategies = new IStrategy[](2); strategies[0] = IStrategy(address(900)); strategies[1] = IStrategy(address(901)); @@ -212,117 +207,83 @@ contract ECDSAStakeRegistrySetup is Test, IECDSAStakeRegistryEvents { function setUp() public virtual { (operator1, operator1Pk) = makeAddrAndKey("Signer 1"); (operator2, operator2Pk) = makeAddrAndKey("Signer 2"); - (operator3, operator3Pk) = makeAddrAndKey("Signer 3"); + (operator3, operator3Pk) = makeAddrAndKey("Signer 3"); (operator4, operator4Pk) = makeAddrAndKey("Signer 4"); - - // 1. 先创建 mockAVSDirectory + mockAVSDirectory = new MockAVSDirectory(); - - // 2. 再创建 mockServiceManager,并传入 mockAVSDirectory 的地址 mockServiceManager = new MockServiceManager(address(mockAVSDirectory)); - - // 3. 创建其他 mock 合约 + mockDelegationManager = new MockDelegationManager(); mockAllocationManager = new MockAllocationManager(); mockAVSRegistrarAddr = makeAddr("mockAVSRegistrar"); - + IStrategy mockStrategy = IStrategy(address(0x1234)); IECDSAStakeRegistryTypes.Quorum memory quorum = IECDSAStakeRegistryTypes.Quorum({ strategies: new IECDSAStakeRegistryTypes.StrategyParams[](1) }); - quorum.strategies[0] = IECDSAStakeRegistryTypes.StrategyParams({ - strategy: mockStrategy, - multiplier: 10000 - }); - + quorum.strategies[0] = + IECDSAStakeRegistryTypes.StrategyParams({strategy: mockStrategy, multiplier: 10000}); + registry = new ECDSAStakeRegistry( IDelegationManager(address(mockDelegationManager)), IAllocationManager(address(mockAllocationManager)), mockAVSRegistrarAddr, IAVSDirectory(address(mockAVSDirectory)) ); - + vm.prank(owner); registry.initialize(address(mockServiceManager), 100, quorum); // Set up 3 operator sets uint32[] memory operatorSetIds = new uint32[](3); operatorSetIds[0] = 1; - operatorSetIds[1] = 2; + operatorSetIds[1] = 2; operatorSetIds[2] = 3; vm.prank(owner); registry.setCurrentOperatorSetIds(operatorSetIds); // Set up operator set membership - for(uint32 setId = 1; setId <= 3; setId++) { + for (uint32 setId = 1; setId <= 3; setId++) { MockAllocationManager(address(mockAllocationManager)).setOperatorSetMembership( - operator3, - address(mockServiceManager), - setId, - true + operator3, address(mockServiceManager), setId, true ); MockAllocationManager(address(mockAllocationManager)).setOperatorSetMembership( - operator4, - address(mockServiceManager), - setId, - true + operator4, address(mockServiceManager), setId, true ); - // 设置 allocation 和 maxMagnitude - OperatorSet memory operatorSet = OperatorSet({ - avs: address(mockServiceManager), - id: setId - }); - - // 为每个 strategy 设置 allocation - IStrategy[] memory strategies = mockAllocationManager.getStrategiesInOperatorSet(operatorSet); - for(uint i = 0; i < strategies.length; i++) { - // 设置 allocation - MockAllocationManager.Allocation memory allocation = MockAllocationManager.Allocation({ - currentMagnitude: 1000, - lastUpdatedBlock: uint32(block.number) - }); - + OperatorSet memory operatorSet = + OperatorSet({avs: address(mockServiceManager), id: setId}); + + IStrategy[] memory strategies = + mockAllocationManager.getStrategiesInOperatorSet(operatorSet); + for (uint256 i = 0; i < strategies.length; i++) { + MockAllocationManager.Allocation memory allocation = MockAllocationManager + .Allocation({currentMagnitude: 1000, lastUpdatedBlock: uint32(block.number)}); + MockAllocationManager(address(mockAllocationManager)).setAllocation( - operator3, - operatorSet, - address(strategies[i]), - allocation + operator3, operatorSet, address(strategies[i]), allocation ); MockAllocationManager(address(mockAllocationManager)).setAllocation( - operator4, - operatorSet, - address(strategies[i]), - allocation + operator4, operatorSet, address(strategies[i]), allocation ); - - // 设置 maxMagnitude + MockAllocationManager(address(mockAllocationManager)).setMaxMagnitude( - operator3, - address(strategies[i]), - 1000 + operator3, address(strategies[i]), 1000 ); MockAllocationManager(address(mockAllocationManager)).setMaxMagnitude( - operator4, - address(strategies[i]), - 1000 + operator4, address(strategies[i]), 1000 ); } } - - ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; - vm.prank(operator1); registry.registerOperatorM2Quorum(operatorSignature, operator1); - vm.prank(operator2); registry.registerOperatorM2Quorum(operatorSignature, operator2); - vm.roll(block.number + 1); } } From a3046ae98d6a29f4f643e16b6ff398fb4cdd7e88 Mon Sep 17 00:00:00 2001 From: jaxxjj Date: Sat, 1 Mar 2025 00:30:42 -0700 Subject: [PATCH 08/12] feat: add operator strategy config and test cases --- src/interfaces/IECDSAStakeRegistry.sol | 11 + src/unaudited/ECDSAStakeRegistry.sol | 146 +++-- src/unaudited/ECDSAStakeRegistryStorage.sol | 3 + .../ECDSAStakeRegistryRatioWeight.sol | 75 +++ test/unit/ECDSAServiceManager.t.sol | 35 +- .../ECDSAStakeRegistryEqualWeightUnit.t.sol | 30 +- .../ECDSAStakeRegistryPermissionedUnit.t.sol | 33 +- .../ECDSAStakeRegistryRatioWeightUnit.t.sol | 128 +++++ test/unit/ECDSAStakeRegistryUnit.t.sol | 518 +++++++++++++++--- 9 files changed, 847 insertions(+), 132 deletions(-) create mode 100644 src/unaudited/examples/ECDSAStakeRegistryRatioWeight.sol create mode 100644 test/unit/ECDSAStakeRegistryRatioWeightUnit.t.sol diff --git a/src/interfaces/IECDSAStakeRegistry.sol b/src/interfaces/IECDSAStakeRegistry.sol index 8115164d..ffaf0db2 100644 --- a/src/interfaces/IECDSAStakeRegistry.sol +++ b/src/interfaces/IECDSAStakeRegistry.sol @@ -43,6 +43,10 @@ interface IECDSAStakeRegistryErrors { error M2QuorumRegistrationIsDisabled(); /// @notice Thrown when the operator set ids are invalid. error InvalidOperatorSetIdsLength(); + /// @notice Thrown when the AllocationManager is not correctly configured. + error InvalidAllocationManager(); + /// @notice Thrown when the AllocationManager is already initialized. + error AllocationManagerAlreadyInitialized(); } interface IECDSAStakeRegistryTypes { @@ -135,6 +139,13 @@ interface IECDSAStakeRegistryEvents is IECDSAStakeRegistryTypes { * @notice Emitted when the M2 quorum registration is disabled. */ event M2QuorumRegistrationDisabled(); + + /* + * @notice Emitted when operator set strategy parameters are updated + * @param operatorSetId The ID of the operator set that was updated + * @param params The new strategy parameters for the operator set + */ + event OperatorSetStrategyParamsUpdated(uint32 indexed operatorSetId, StrategyParams[] params); } interface IECDSAStakeRegistry is diff --git a/src/unaudited/ECDSAStakeRegistry.sol b/src/unaudited/ECDSAStakeRegistry.sol index 61beae2d..8d411c15 100644 --- a/src/unaudited/ECDSAStakeRegistry.sol +++ b/src/unaudited/ECDSAStakeRegistry.sol @@ -70,9 +70,13 @@ contract ECDSAStakeRegistry is function initialize( address _serviceManager, uint256 thresholdWeight, - IECDSAStakeRegistryTypes.Quorum memory quorum + IECDSAStakeRegistryTypes.Quorum memory quorum, + uint32[] calldata operatorSetIds, + StrategyParams[][] calldata strategyParamsArray ) external initializer { - __ECDSAStakeRegistry_init(_serviceManager, thresholdWeight, quorum); + __ECDSAStakeRegistry_init( + _serviceManager, thresholdWeight, quorum, operatorSetIds, strategyParamsArray + ); } /// @notice Initializes state for the StakeRegistry @@ -80,11 +84,15 @@ contract ECDSAStakeRegistry is function __ECDSAStakeRegistry_init( address _serviceManagerAddr, uint256 thresholdWeight, - IECDSAStakeRegistryTypes.Quorum memory quorum + IECDSAStakeRegistryTypes.Quorum memory quorum, + uint32[] calldata operatorSetIds, + StrategyParams[][] calldata strategyParamsArray ) internal onlyInitializing { _serviceManager = _serviceManagerAddr; _updateStakeThreshold(thresholdWeight); _updateQuorumConfig(quorum); + _setCurrentOperatorSetIds(operatorSetIds); + _updateOperatorSetsConfig(operatorSetIds, strategyParamsArray); __Ownable_init(); } @@ -117,13 +125,11 @@ contract ECDSAStakeRegistry is address operator, address signingKey ) external onlyAVSRegistrar { - // Update operator weight - _updateOperatorWeight(operator); - - // Update signing key and p2p key + int256 delta = _updateOperatorWeight(operator); + _updateTotalWeight(delta); _updateOperatorSigningKey(operator, signingKey); - if (!operatorRegisteredOnAVSDirectory(operator)) { + _totalOperators++; emit OperatorRegistered(operator, _serviceManager); } } @@ -131,11 +137,10 @@ contract ECDSAStakeRegistry is function onOperatorSetDeregistered( address operator ) external onlyAVSRegistrar { - // Update weights - _updateOperatorWeight(operator); - - // Emit event + int256 delta = _updateOperatorWeight(operator); + _updateTotalWeight(delta); if (!operatorRegisteredOnAVSDirectory(operator)) { + _totalOperators--; emit OperatorDeregistered(operator, _serviceManager); } } @@ -166,6 +171,21 @@ contract ECDSAStakeRegistry is _updateOperators(operators); } + /** + * @notice Updates strategy parameters for multiple operator sets (from stake registry view) + * @notice Strategy params must align with operator set ids on the allocation manager + * @param operatorSetIds Array of operator set IDs to update + * @param strategyParamsArray Array of strategy parameters arrays for each operator set + */ + function updateOperatorSetsConfig( + uint32[] calldata operatorSetIds, + StrategyParams[][] calldata strategyParamsArray, + address[] calldata operators + ) external onlyOwner { + _updateOperatorSetsConfig(operatorSetIds, strategyParamsArray); + _updateOperators(operators); + } + /// @inheritdoc IECDSAStakeRegistry function updateMinimumWeight( uint256 newMinimumWeight, @@ -187,17 +207,20 @@ contract ECDSAStakeRegistry is function setCurrentOperatorSetIds( uint32[] calldata _ids ) external onlyOwner { - if (_ids.length == 0 || _ids.length > 10) { - revert InvalidOperatorSetIdsLength(); - } - currentOperatorSetIds = _ids; + _setCurrentOperatorSetIds(_ids); } /// @notice Sets the allocation manager /// @param _allocationManager The allocation manager to set - function setAllocationManager( + function initAllocationManager( IAllocationManager _allocationManager ) external onlyOwner { + if (address(allocationManager) != address(0)) { + revert AllocationManagerAlreadyInitialized(); + } + if (address(_allocationManager) == address(0)) { + revert InvalidAllocationManager(); + } allocationManager = _allocationManager; } @@ -230,6 +253,15 @@ contract ECDSAStakeRegistry is return currentOperatorSetIds; } + /// @notice Gets the strategy parameters for a specific operator set + /// @param operatorSetId The ID of the operator set to query + /// @return The array of strategy parameters for the operator set + function getOperatorSetConfig( + uint32 operatorSetId + ) external view returns (StrategyParams[] memory) { + return operatorSetStrategyParams[operatorSetId]; + } + /// @inheritdoc IECDSAStakeRegistry function getLatestOperatorSigningKey( address operator @@ -295,10 +327,9 @@ contract ECDSAStakeRegistry is /// @dev Queries mainnet delegation manager for current shares function getOperatorWeight( address _operator - ) public view returns (uint256) { + ) public view virtual returns (uint256) { uint256 quorumWeight = getQuorumWeight(_operator); uint256 operatorSetWeight = getOperatorSetWeight(_operator); - return quorumWeight + operatorSetWeight; } @@ -308,6 +339,9 @@ contract ECDSAStakeRegistry is function getQuorumWeight( address operator ) public view returns (uint256) { + if (!operatorRegisteredOnAVSDirectory(operator)) { + return 0; + } StrategyParams[] memory strategyParams = _quorum.strategies; uint256 weight; IStrategy[] memory strategies = new IStrategy[](strategyParams.length); @@ -337,38 +371,30 @@ contract ECDSAStakeRegistry is /// @return The operator's available weight in set, or 0 if below minimum function getOperatorSetWeight( address operator - ) public view returns (uint256) { - // Return 0 if allocation manager not set + ) public view virtual returns (uint256) { if (address(allocationManager) == address(0)) { return 0; } - uint256 totalWeight; - // Loop through all operator sets for (uint256 setIndex = 0; setIndex < currentOperatorSetIds.length; setIndex++) { - // Create operator set struct for current id + uint32 operatorSetId = currentOperatorSetIds[setIndex]; OperatorSet memory operatorSet = - OperatorSet({avs: address(_serviceManager), id: currentOperatorSetIds[setIndex]}); - - // Check operator set membership + OperatorSet({avs: address(_serviceManager), id: operatorSetId}); if (!allocationManager.isMemberOfOperatorSet(operator, operatorSet)) { continue; } - // Get strategies from operator set IStrategy[] memory strategies = allocationManager.getStrategiesInOperatorSet(operatorSet); if (strategies.length == 0) { continue; } - // Get operator's shares for all strategies + StrategyParams[] memory strategyParams = operatorSetStrategyParams[operatorSetId]; uint256[] memory shares = DELEGATION_MANAGER.getOperatorShares(operator, strategies); - // Calculate available weight for each strategy for (uint256 i = 0; i < strategies.length; i++) { - // Get allocation and max magnitude IAllocationManager.Allocation memory allocation = allocationManager.getAllocation(operator, operatorSet, strategies[i]); uint64 maxMagnitude = allocationManager.getMaxMagnitude(operator, strategies[i]); @@ -377,16 +403,12 @@ contract ECDSAStakeRegistry is continue; } - // Calculate available proportion uint256 slashableProportion = uint256(allocation.currentMagnitude) * WAD / maxMagnitude; - - // Add weighted shares to total - totalWeight += shares[i] * slashableProportion / WAD; + totalWeight += + shares[i] * slashableProportion * strategyParams[i].multiplier / WAD / BPS; } } - - // Return 0 if below minimum weight if (totalWeight >= _minimumWeight) { return totalWeight; } else { @@ -421,7 +443,6 @@ contract ECDSAStakeRegistry is if (address(allocationManager) == address(0)) { return false; } - // Check if operator is registered in any current set for (uint256 i = 0; i < currentOperatorSetIds.length; i++) { OperatorSet memory operatorSet = @@ -631,6 +652,17 @@ contract ECDSAStakeRegistry is } } + /// @notice Internal function to set the current operator set ids + /// @param _ids The ids of the operator sets to set + function _setCurrentOperatorSetIds( + uint32[] calldata _ids + ) internal { + if (_ids.length == 0 || _ids.length > 10) { + revert InvalidOperatorSetIdsLength(); + } + currentOperatorSetIds = _ids; + } + /** * @notice Common logic to verify a batch of ECDSA signatures against a hash, using either last stake weight or at a specific block. * @param digest The hash of the data the signers endorsed. @@ -771,4 +803,42 @@ contract ECDSAStakeRegistry is revert InsufficientSignedStake(); } } + + /// @notice Internal function to update strategy parameters for multiple operator sets + /// @param operatorSetIds Array of operator set IDs to update + /// @param strategyParamsArray Array of strategy parameters arrays for each operator set + function _updateOperatorSetsConfig( + uint32[] calldata operatorSetIds, + StrategyParams[][] calldata strategyParamsArray + ) internal { + if (operatorSetIds.length != strategyParamsArray.length) { + revert InvalidOperatorSetIdsLength(); + } + + for (uint256 i = 0; i < operatorSetIds.length; i++) { + _updateOperatorSetConfig(operatorSetIds[i], strategyParamsArray[i]); + } + } + + /// @notice Internal function to set strategy parameters for an operator set + ///@param operatorSetId The ID of the operator set + ///@param params The strategy parameters to set + + function _updateOperatorSetConfig( + uint32 operatorSetId, + StrategyParams[] memory params + ) internal { + address lastStrategy; + for (uint256 i = 0; i < params.length; i++) { + address currentStrategy = address(params[i].strategy); + if (lastStrategy >= currentStrategy) revert NotSorted(); + lastStrategy = currentStrategy; + } + + delete operatorSetStrategyParams[operatorSetId]; + for (uint256 i = 0; i < params.length; i++) { + operatorSetStrategyParams[operatorSetId].push(params[i]); + } + emit OperatorSetStrategyParamsUpdated(operatorSetId, params); + } } diff --git a/src/unaudited/ECDSAStakeRegistryStorage.sol b/src/unaudited/ECDSAStakeRegistryStorage.sol index 6ebc84b9..b3035ae6 100644 --- a/src/unaudited/ECDSAStakeRegistryStorage.sol +++ b/src/unaudited/ECDSAStakeRegistryStorage.sol @@ -33,6 +33,9 @@ abstract contract ECDSAStakeRegistryStorage is IECDSAStakeRegistry { /// @notice The current operator set ids uint32[] public currentOperatorSetIds; + /// @notice Maps operator set IDs to their strategy parameters + mapping(uint32 => StrategyParams[]) public operatorSetStrategyParams; + /// @notice The total amount of multipliers to weigh stakes uint256 public constant WAD = 1e18; diff --git a/src/unaudited/examples/ECDSAStakeRegistryRatioWeight.sol b/src/unaudited/examples/ECDSAStakeRegistryRatioWeight.sol new file mode 100644 index 00000000..5a0fdb91 --- /dev/null +++ b/src/unaudited/examples/ECDSAStakeRegistryRatioWeight.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {ECDSAStakeRegistry} from "../ECDSAStakeRegistry.sol"; +import {IDelegationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; +import {IAllocationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {IAVSDirectory} from "../ECDSAStakeRegistry.sol"; + +/// @title ECDSAStakeRegistryRatioWeight +/// @notice An example implementation of ECDSAStakeRegistry that allows setting different weight ratios +/// for quorum weight and operator set weight +/// @dev The total weight ratio is 10000 (100%). The quorum weight ratio can be set between 0 and 10000, +/// and the operator set weight ratio will be (10000 - quorumWeightRatio) +contract ECDSAStakeRegistryRatioWeight is ECDSAStakeRegistry { + /// @notice Event emitted when the weight ratio is updated + event WeightRatioUpdated(uint256 oldRatio, uint256 newRatio); + + /// @notice The weight ratio for quorum weight (0-10000) + uint256 private _quorumWeightRatio; + + error InvalidWeightRatio(uint256 ratio); + + constructor( + IDelegationManager _delegationManager, + IAllocationManager _allocationManager, + address _avsRegistrar, + IAVSDirectory _avsDirectory + ) ECDSAStakeRegistry(_delegationManager, _allocationManager, _avsRegistrar, _avsDirectory) {} + + /// @notice External function to set the weight ratio, only callable by owner + /// @param ratio The new ratio (0-10000) + /// @dev The operator set weight ratio will be (10000 - ratio) + function setWeightRatio( + uint256 ratio + ) external onlyOwner { + _setWeightRatio(ratio); + } + + /// @notice Returns the current weight ratio + /// @return The current quorum weight ratio (0-10000) + function getWeightRatio() external view returns (uint256) { + return _quorumWeightRatio; + } + + /// @inheritdoc ECDSAStakeRegistry + /// @dev Overrides the weight calculation to apply the configured ratios + function getOperatorWeight( + address _operator + ) public view virtual override returns (uint256) { + uint256 quorumWeight = getQuorumWeight(_operator); + uint256 operatorSetWeight = getOperatorSetWeight(_operator); + + uint256 weightedQuorumWeight = (quorumWeight * _quorumWeightRatio) / 10000; + uint256 weightedOperatorSetWeight = + (operatorSetWeight * (10000 - _quorumWeightRatio)) / 10000; + + return weightedQuorumWeight + weightedOperatorSetWeight; + } + + /// @notice Sets the weight ratio for quorum weight + /// @param ratio The new ratio (0-10000) + /// @dev The operator set weight ratio will be (10000 - ratio) + function _setWeightRatio( + uint256 ratio + ) internal { + if (ratio > 10000) { + revert InvalidWeightRatio(ratio); + } + uint256 oldRatio = _quorumWeightRatio; + _quorumWeightRatio = ratio; + emit WeightRatioUpdated(oldRatio, ratio); + } +} diff --git a/test/unit/ECDSAServiceManager.t.sol b/test/unit/ECDSAServiceManager.t.sol index c47d828c..69daf8ff 100644 --- a/test/unit/ECDSAServiceManager.t.sol +++ b/test/unit/ECDSAServiceManager.t.sol @@ -10,11 +10,12 @@ import {IRewardsCoordinator} from "eigenlayer-contracts/src/contracts/interfaces/IRewardsCoordinator.sol"; import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; import {IAVSRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IAVSRegistrar.sol"; +import {IECDSAStakeRegistryTypes} from "../../src/interfaces/IECDSAStakeRegistry.sol"; +import {IECDSAStakeRegistry} from "../../src/interfaces/IECDSAStakeRegistry.sol"; import {ECDSAServiceManagerMock} from "../mocks/ECDSAServiceManagerMock.sol"; import {ECDSAStakeRegistryMock} from "../mocks/ECDSAStakeRegistryMock.sol"; import {AVSDirectoryMock} from "../mocks/AVSDirectoryMock.sol"; -import {IECDSAStakeRegistryTypes} from "../../src/interfaces/IECDSAStakeRegistry.sol"; import {IPermissionController} from "eigenlayer-contracts/src/contracts/interfaces/IPermissionController.sol"; import {IAllocationManager} from @@ -193,14 +194,42 @@ contract ECDSAServiceManagerSetup is Test { strategy: IStrategy(address(421)), multiplier: 5000 }); - address[] memory operators = new address[](0); + uint32[] memory operatorSetIds = new uint32[](2); + operatorSetIds[0] = 1; + operatorSetIds[1] = 2; + + IECDSAStakeRegistryTypes.StrategyParams[][] memory strategyParamsArray = + new IECDSAStakeRegistryTypes.StrategyParams[][](2); + + strategyParamsArray[0] = new IECDSAStakeRegistryTypes.StrategyParams[](2); + strategyParamsArray[0][0] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(900)), + multiplier: 3000 + }); + strategyParamsArray[0][1] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(901)), + multiplier: 3000 + }); + // Strategy params for second operator set + strategyParamsArray[1] = new IECDSAStakeRegistryTypes.StrategyParams[](2); + strategyParamsArray[1][0] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(902)), + multiplier: 3000 + }); + strategyParamsArray[1][1] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(903)), + multiplier: 3000 + }); vm.prank(owner); mockStakeRegistry.initialize( address(serviceManager), 10000, // Assuming a threshold weight of 10000 basis points - quorum + quorum, + operatorSetIds, + strategyParamsArray ); + ISignatureUtils.SignatureWithSaltAndExpiry memory dummySignature; vm.prank(operator1); diff --git a/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol b/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol index c5c681a8..076c7393 100644 --- a/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol +++ b/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol @@ -39,7 +39,35 @@ contract EqualWeightECDSARegistry is ECDSAStakeRegistrySetup { quorum.strategies[0] = IECDSAStakeRegistryTypes.StrategyParams({strategy: mockStrategy, multiplier: 10000}); - fixedWeightRegistry.initialize(address(mockServiceManager), 100, quorum); + uint32[] memory operatorSetIds = new uint32[](2); + operatorSetIds[0] = 1; + operatorSetIds[1] = 2; + IECDSAStakeRegistryTypes.StrategyParams[][] memory strategyParamsArray = + new IECDSAStakeRegistryTypes.StrategyParams[][](2); + + strategyParamsArray[0] = new IECDSAStakeRegistryTypes.StrategyParams[](2); + strategyParamsArray[0][0] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(900)), + multiplier: 3000 + }); + strategyParamsArray[0][1] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(901)), + multiplier: 3000 + }); + + strategyParamsArray[1] = new IECDSAStakeRegistryTypes.StrategyParams[](2); + strategyParamsArray[1][0] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(902)), + multiplier: 3000 + }); + strategyParamsArray[1][1] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(903)), + multiplier: 3000 + }); + + fixedWeightRegistry.initialize( + address(mockServiceManager), 100, quorum, operatorSetIds, strategyParamsArray + ); fixedWeightRegistry.permitOperator(operator6); fixedWeightRegistry.permitOperator(operator7); diff --git a/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol b/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol index ebcb9b7a..e5968505 100644 --- a/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol +++ b/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol @@ -39,7 +39,38 @@ contract PermissionedECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { quorum.strategies[0] = IECDSAStakeRegistryTypes.StrategyParams({strategy: mockStrategy, multiplier: 10000}); - permissionedRegistry.initialize(address(mockServiceManager), 100, quorum); + // Create operator set IDs and strategy params + uint32[] memory operatorSetIds = new uint32[](2); + operatorSetIds[0] = 1; + operatorSetIds[1] = 2; + IECDSAStakeRegistryTypes.StrategyParams[][] memory strategyParamsArray = + new IECDSAStakeRegistryTypes.StrategyParams[][](2); + + // Strategy params for first operator set + strategyParamsArray[0] = new IECDSAStakeRegistryTypes.StrategyParams[](2); + strategyParamsArray[0][0] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(900)), // Lower address + multiplier: 3000 + }); + strategyParamsArray[0][1] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(901)), // Higher address + multiplier: 3000 + }); + + // Strategy params for second operator set + strategyParamsArray[1] = new IECDSAStakeRegistryTypes.StrategyParams[](2); + strategyParamsArray[1][0] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(902)), // Lower address + multiplier: 3000 + }); + strategyParamsArray[1][1] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(903)), // Higher address + multiplier: 3000 + }); + + permissionedRegistry.initialize( + address(mockServiceManager), 100, quorum, operatorSetIds, strategyParamsArray + ); permissionedRegistry.permitOperator(operator6); permissionedRegistry.permitOperator(operator7); diff --git a/test/unit/ECDSAStakeRegistryRatioWeightUnit.t.sol b/test/unit/ECDSAStakeRegistryRatioWeightUnit.t.sol new file mode 100644 index 00000000..cd6bf6d7 --- /dev/null +++ b/test/unit/ECDSAStakeRegistryRatioWeightUnit.t.sol @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol"; +import {IDelegationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; +import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; +import {IAllocationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; + +import { + IECDSAStakeRegistry, + IECDSAStakeRegistryTypes, + IECDSAStakeRegistryErrors +} from "../../src/interfaces/IECDSAStakeRegistry.sol"; +import {ECDSAStakeRegistrySetup} from "./ECDSAStakeRegistryUnit.t.sol"; +import {ECDSAStakeRegistryRatioWeight} from + "../../src/unaudited/examples/ECDSAStakeRegistryRatioWeight.sol"; +import {IAVSDirectory} from "../../src/unaudited/ECDSAStakeRegistry.sol"; + +contract RatioWeightECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { + /// @notice Event emitted when the weight ratio is updated + event WeightRatioUpdated(uint256 oldRatio, uint256 newRatio); + + ECDSAStakeRegistryRatioWeight internal ratioWeightRegistry; + + function setUp() public virtual override { + super.setUp(); + ratioWeightRegistry = new ECDSAStakeRegistryRatioWeight( + IDelegationManager(address(mockDelegationManager)), + IAllocationManager(address(mockAllocationManager)), + mockAVSRegistrarAddr, + IAVSDirectory(address(mockAVSDirectory)) + ); + + IStrategy mockStrategy = IStrategy(address(0x1234)); + IECDSAStakeRegistryTypes.Quorum memory quorum = IECDSAStakeRegistryTypes.Quorum({ + strategies: new IECDSAStakeRegistryTypes.StrategyParams[](1) + }); + quorum.strategies[0] = + IECDSAStakeRegistryTypes.StrategyParams({strategy: mockStrategy, multiplier: 10000}); + + uint32[] memory operatorSetIds = new uint32[](2); + operatorSetIds[0] = 1; + operatorSetIds[1] = 2; + IECDSAStakeRegistryTypes.StrategyParams[][] memory strategyParamsArray = + new IECDSAStakeRegistryTypes.StrategyParams[][](2); + + strategyParamsArray[0] = new IECDSAStakeRegistryTypes.StrategyParams[](2); + strategyParamsArray[0][0] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(900)), + multiplier: 3000 + }); + strategyParamsArray[0][1] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(901)), + multiplier: 3000 + }); + + strategyParamsArray[1] = new IECDSAStakeRegistryTypes.StrategyParams[](2); + strategyParamsArray[1][0] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(902)), + multiplier: 3000 + }); + strategyParamsArray[1][1] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(903)), + multiplier: 3000 + }); + ratioWeightRegistry.initialize( + address(mockServiceManager), 100, quorum, operatorSetIds, strategyParamsArray + ); + // Register operator3 and operator4 to M2 quorum + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; + vm.prank(operator3); + ratioWeightRegistry.registerOperatorM2Quorum(operatorSignature, operator3); + vm.prank(operator4); + ratioWeightRegistry.registerOperatorM2Quorum(operatorSignature, operator4); + } + + function test_RevertsWhen_NotOwner_SetWeightRatio() public { + address notOwner = address(0xBEEF); + vm.prank(notOwner); + vm.expectRevert("Ownable: caller is not the owner"); + ratioWeightRegistry.setWeightRatio(5000); + } + + function test_RevertsWhen_InvalidRatio_SetWeightRatio() public { + vm.expectRevert( + abi.encodeWithSelector(ECDSAStakeRegistryRatioWeight.InvalidWeightRatio.selector, 10001) + ); + ratioWeightRegistry.setWeightRatio(10001); + } + + function test_When_Owner_SetWeightRatio() public { + // Test setting ratio to 70% quorum weight, 30% operator set weight + vm.expectEmit(true, true, true, true); + emit WeightRatioUpdated(0, 7000); + ratioWeightRegistry.setWeightRatio(7000); + assertEq(ratioWeightRegistry.getWeightRatio(), 7000); + } + + function test_WeightCalculation_WithDifferentRatios() public { + // Initial weights should be equal since ratio is 0 + uint256 initialWeight = ratioWeightRegistry.getOperatorWeight(operator3); + + // Set ratio to 70% quorum weight, 30% operator set weight + ratioWeightRegistry.setWeightRatio(7000); + uint256 quorumWeight = ratioWeightRegistry.getQuorumWeight(operator3); + uint256 operatorSetWeight = ratioWeightRegistry.getOperatorSetWeight(operator3); + uint256 expectedWeight = (quorumWeight * 7000 / 10000) + (operatorSetWeight * 3000 / 10000); + assertEq(ratioWeightRegistry.getOperatorWeight(operator3), expectedWeight); + + // Change ratio to 30% quorum weight, 70% operator set weight + ratioWeightRegistry.setWeightRatio(3000); + + expectedWeight = (quorumWeight * 3000 / 10000) + (operatorSetWeight * 7000 / 10000); + assertEq(ratioWeightRegistry.getOperatorWeight(operator3), expectedWeight); + } + + function test_WeightCalculation_WithEqualRatio() public { + // Set ratio to 50% quorum weight, 50% operator set weight + ratioWeightRegistry.setWeightRatio(5000); + uint256 quorumWeight = ratioWeightRegistry.getQuorumWeight(operator3); + uint256 operatorSetWeight = ratioWeightRegistry.getOperatorSetWeight(operator3); + + uint256 expectedWeight = (quorumWeight * 5000 / 10000) + (operatorSetWeight * 5000 / 10000); + assertEq(ratioWeightRegistry.getOperatorWeight(operator3), expectedWeight); + } +} diff --git a/test/unit/ECDSAStakeRegistryUnit.t.sol b/test/unit/ECDSAStakeRegistryUnit.t.sol index 0a39e637..9f83dca4 100644 --- a/test/unit/ECDSAStakeRegistryUnit.t.sol +++ b/test/unit/ECDSAStakeRegistryUnit.t.sol @@ -15,8 +15,10 @@ import { IECDSAStakeRegistryTypes, IECDSAStakeRegistryEvents } from "../../src/interfaces/IECDSAStakeRegistry.sol"; -import {IAllocationManager} from - "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import { + IAllocationManager, + IAllocationManagerTypes +} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; import {IAVSDirectory, IAVSDirectoryTypes} from "../../src/unaudited/ECDSAStakeRegistry.sol"; import {OperatorSet} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol"; @@ -110,74 +112,76 @@ contract MockAVSDirectory { } contract MockAllocationManager { - struct Allocation { - uint64 currentMagnitude; - uint32 lastUpdatedBlock; - } + mapping(address => mapping(bytes32 => IAllocationManagerTypes.Allocation)) public allocations; + mapping(address => mapping(IStrategy => uint64)) public maxMagnitudes; + mapping(address => mapping(bytes32 => bool)) public operatorSetMembership; - mapping(address => mapping(bytes32 => Allocation)) public allocations; - mapping(address => mapping(address => uint64)) public maxMagnitudes; - mapping(address => mapping(address => mapping(uint32 => bool))) public operatorSetMembers; + function getAllocation( + address operator, + OperatorSet memory operatorSet, + IStrategy strategy + ) external view returns (IAllocationManagerTypes.Allocation memory) { + bytes32 key = keccak256(abi.encode(operator, operatorSet, address(strategy))); + return allocations[operator][key]; + } - function setOperatorSetMembership( + function setOperatorWeight( address operator, - address avs, - uint32 setId, - bool isMember + OperatorSet memory operatorSet, + address strategy, + uint256 weight ) external { - operatorSetMembers[operator][avs][setId] = isMember; + bytes32 key = keccak256(abi.encode(operator, operatorSet, address(strategy))); + allocations[operator][key] = IAllocationManagerTypes.Allocation({ + currentMagnitude: uint64(weight), + pendingDiff: 0, + effectBlock: 0 + }); + bytes32 membershipKey = keccak256(abi.encode(operator, operatorSet)); + operatorSetMembership[operator][membershipKey] = true; } function isMemberOfOperatorSet( address operator, OperatorSet memory operatorSet ) external view returns (bool) { - return operatorSetMembers[operator][operatorSet.avs][operatorSet.id]; + bytes32 key = keccak256(abi.encode(operator, operatorSet)); + return operatorSetMembership[operator][key]; } - function setAllocation( + function setOperatorSetMembership( address operator, OperatorSet memory operatorSet, - address strategy, - Allocation memory allocation + bool isMember ) external { - bytes32 key = keccak256(abi.encode(operator, operatorSet, strategy)); - allocations[operator][key] = allocation; - } - - function getAllocation( - address operator, - OperatorSet memory operatorSet, - IStrategy strategy - ) external view returns (Allocation memory) { - bytes32 key = keccak256(abi.encode(operator, operatorSet, address(strategy))); - return allocations[operator][key]; - } - - function setMaxMagnitude(address operator, address strategy, uint64 magnitude) external { - maxMagnitudes[operator][strategy] = magnitude; - } - - function getMaxMagnitude(address operator, IStrategy strategy) external view returns (uint64) { - return maxMagnitudes[operator][address(strategy)]; + bytes32 key = keccak256(abi.encode(operator, operatorSet)); + operatorSetMembership[operator][key] = isMember; } - function setAVSRegistrar(address avs, address registrar) external {} - function isOperatorSet( - OperatorSet memory operatorSet + OperatorSet memory ) external pure returns (bool) { return true; } function getStrategiesInOperatorSet( - OperatorSet memory operatorSet + OperatorSet memory ) external pure returns (IStrategy[] memory) { IStrategy[] memory strategies = new IStrategy[](2); strategies[0] = IStrategy(address(900)); strategies[1] = IStrategy(address(901)); return strategies; } + + function setAVSRegistrar(address, address) external {} + + function getMaxMagnitude(address operator, IStrategy strategy) external view returns (uint64) { + return maxMagnitudes[operator][strategy]; + } + + function setMaxMagnitude(address operator, IStrategy strategy, uint64 magnitude) external { + maxMagnitudes[operator][strategy] = magnitude; + } } contract ECDSAStakeRegistrySetup is Test, IECDSAStakeRegistryEvents { @@ -205,8 +209,10 @@ contract ECDSAStakeRegistrySetup is Test, IECDSAStakeRegistryEvents { address public mockAVSRegistrarAddr; function setUp() public virtual { + // operator 1 and 2 are registered on M2 quorum (operator1, operator1Pk) = makeAddrAndKey("Signer 1"); (operator2, operator2Pk) = makeAddrAndKey("Signer 2"); + // operator 3 and 4 are registered on operator sets (operator3, operator3Pk) = makeAddrAndKey("Signer 3"); (operator4, operator4Pk) = makeAddrAndKey("Signer 4"); @@ -232,49 +238,73 @@ contract ECDSAStakeRegistrySetup is Test, IECDSAStakeRegistryEvents { ); vm.prank(owner); - registry.initialize(address(mockServiceManager), 100, quorum); - - // Set up 3 operator sets - uint32[] memory operatorSetIds = new uint32[](3); + uint32[] memory operatorSetIds = new uint32[](2); operatorSetIds[0] = 1; operatorSetIds[1] = 2; - operatorSetIds[2] = 3; + IECDSAStakeRegistryTypes.StrategyParams[][] memory strategyParamsArray = + new IECDSAStakeRegistryTypes.StrategyParams[][](2); + + // Strategy params for first operator set + strategyParamsArray[0] = new IECDSAStakeRegistryTypes.StrategyParams[](2); + strategyParamsArray[0][0] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(900)), + multiplier: 3000 + }); + strategyParamsArray[0][1] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(901)), + multiplier: 3000 + }); + + // Strategy params for second operator set + strategyParamsArray[1] = new IECDSAStakeRegistryTypes.StrategyParams[](2); + strategyParamsArray[1][0] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(902)), + multiplier: 3000 + }); + strategyParamsArray[1][1] = IECDSAStakeRegistryTypes.StrategyParams({ + strategy: IStrategy(address(903)), + multiplier: 3000 + }); + + registry.initialize( + address(mockServiceManager), 100, quorum, operatorSetIds, strategyParamsArray + ); + vm.prank(owner); registry.setCurrentOperatorSetIds(operatorSetIds); - // Set up operator set membership - for (uint32 setId = 1; setId <= 3; setId++) { - MockAllocationManager(address(mockAllocationManager)).setOperatorSetMembership( - operator3, address(mockServiceManager), setId, true - ); - MockAllocationManager(address(mockAllocationManager)).setOperatorSetMembership( - operator4, address(mockServiceManager), setId, true - ); + // Set up operator weights for operator3 and operator4 + address[] memory operators = new address[](2); + operators[0] = operator3; + operators[1] = operator4; + // Set weights for each operator in each set + for (uint32 setId = 1; setId <= 2; setId++) { OperatorSet memory operatorSet = OperatorSet({avs: address(mockServiceManager), id: setId}); + // Get strategies for this set IStrategy[] memory strategies = mockAllocationManager.getStrategiesInOperatorSet(operatorSet); - for (uint256 i = 0; i < strategies.length; i++) { - MockAllocationManager.Allocation memory allocation = MockAllocationManager - .Allocation({currentMagnitude: 1000, lastUpdatedBlock: uint32(block.number)}); - MockAllocationManager(address(mockAllocationManager)).setAllocation( - operator3, operatorSet, address(strategies[i]), allocation - ); - MockAllocationManager(address(mockAllocationManager)).setAllocation( - operator4, operatorSet, address(strategies[i]), allocation - ); - - MockAllocationManager(address(mockAllocationManager)).setMaxMagnitude( - operator3, address(strategies[i]), 1000 - ); - MockAllocationManager(address(mockAllocationManager)).setMaxMagnitude( - operator4, address(strategies[i]), 1000 - ); + // Set weights and maxMagnitudes for each operator and strategy + for (uint256 i = 0; i < operators.length; i++) { + for (uint256 j = 0; j < strategies.length; j++) { + // Set operator weight/allocation + mockAllocationManager.setOperatorWeight( + operators[i], + operatorSet, + address(strategies[j]), + 3e17 // 30% in WAD + ); + mockAllocationManager.setMaxMagnitude(operators[i], strategies[j], 1e18); + } } } + vm.startPrank(mockAVSRegistrarAddr); + registry.onOperatorSetRegistered(operator3, operator3); + registry.onOperatorSetRegistered(operator4, operator3); + vm.stopPrank(); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; @@ -405,17 +435,17 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { } function test_RegisterOperatorM2Quorum() public { - address operator5 = address(0x125); + address operator6 = address(0x125); ISignatureUtils.SignatureWithSaltAndExpiry memory signature; - vm.prank(operator5); - registry.registerOperatorM2Quorum(signature, operator5); - assertTrue(registry.operatorRegistered(operator5)); - assertEq(registry.getLastCheckpointOperatorWeight(operator5), 1000); + vm.prank(operator6); + registry.registerOperatorM2Quorum(signature, operator6); + assertTrue(registry.operatorRegistered(operator6)); + assertEq(registry.getLastCheckpointOperatorWeight(operator6), 1000); } function test_RevertsWhen_AlreadyRegistered_RegisterOperatorM2Quorum() public { assertEq(registry.getLastCheckpointOperatorWeight(operator1), 1000); - assertEq(registry.getLastCheckpointTotalWeight(), 2000); + assertEq(registry.getLastCheckpointTotalWeight(), 2720); ISignatureUtils.SignatureWithSaltAndExpiry memory signature; vm.expectRevert(); @@ -442,13 +472,14 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { function test_DeregisterOperatorM2Quorum() public { assertEq(registry.getLastCheckpointOperatorWeight(operator1), 1000); - assertEq(registry.getLastCheckpointTotalWeight(), 2000); + assertEq(registry.getLastCheckpointTotalWeight(), 2720); vm.prank(operator1); registry.deregisterOperatorM2Quorum(); + console.log("operator1 weight", registry.getLastCheckpointOperatorWeight(operator1)); assertEq(registry.getLastCheckpointOperatorWeight(operator1), 0); - assertEq(registry.getLastCheckpointTotalWeight(), 1000); + assertEq(registry.getLastCheckpointTotalWeight(), 1720); } function test_RevertsWhen_NotOperator_DeregisterOperatorM2Quorum() public { @@ -465,12 +496,12 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { function test_When_OperatorNotRegistered_UpdateOperators() public { address[] memory operators = new address[](3); - address operator5 = address(0xBEEF); + address operator6 = address(0xBEEF); operators[0] = operator1; operators[1] = operator2; - operators[2] = operator5; + operators[2] = operator6; registry.updateOperators(operators); - assertEq(registry.getLastCheckpointOperatorWeight(operator5), 0); + assertEq(registry.getLastCheckpointOperatorWeight(operator6), 0); } function test_When_SingleOperator_UpdateOperators() public { @@ -488,17 +519,17 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { operators[1] = operator2; assertEq(registry.getLastCheckpointOperatorWeight(operator1), 1000); - assertEq(registry.getLastCheckpointTotalWeight(), 2000); + assertEq(registry.getLastCheckpointTotalWeight(), 2720); registry.updateOperators(operators); assertEq(registry.getLastCheckpointOperatorWeight(operator1), 1000); - assertEq(registry.getLastCheckpointTotalWeight(), 2000); + assertEq(registry.getLastCheckpointTotalWeight(), 2720); registry.updateOperators(operators); assertEq(registry.getLastCheckpointOperatorWeight(operator1), 1000); - assertEq(registry.getLastCheckpointTotalWeight(), 2000); + assertEq(registry.getLastCheckpointTotalWeight(), 2720); /// TODO: Need to confirm we always pull the last if the block numbers are the same for checkpoints /// in the getAtBlock function or if we need to prevent this behavior } @@ -644,11 +675,6 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { signatures[0] = abi.encodePacked(r, s, v); (v, r, s) = vm.sign(operator2Pk, msgHash); signatures[1] = abi.encodePacked(r, s, v); - console.log("operator1 weight", registry.getOperatorWeight(operator1)); - console.log("operator2 weight", registry.getOperatorWeight(operator2)); - console.log("threshold weight", registry.getLastCheckpointThresholdWeight()); - console.log("operator1 status", registry.operatorRegisteredOnAVSDirectory(operator1)); - console.log("operator2 status", registry.operatorRegisteredOnAVSDirectory(operator2)); registry.isValidSignature(msgHash, abi.encode(signers, signatures, block.number - 1)); } @@ -1083,4 +1109,318 @@ contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { } return (operators, signatures); } + + function test_OnOperatorSetDeregistered() public { + address operator = operator3; + uint256 initialTotalWeight = registry.getLastCheckpointTotalWeight(); + + for (uint32 setId = 1; setId <= 2; setId++) { + OperatorSet memory operatorSet = + OperatorSet({avs: address(mockServiceManager), id: setId}); + IStrategy[] memory strategies = + mockAllocationManager.getStrategiesInOperatorSet(operatorSet); + for (uint256 j = 0; j < strategies.length; j++) { + mockAllocationManager.setOperatorWeight( + operator, + operatorSet, + address(strategies[j]), + 0 // Set currentMagnitude to 0 + ); + } + } + + vm.expectEmit(); + emit OperatorDeregistered(operator, address(mockServiceManager)); + + vm.prank(mockAVSRegistrarAddr); + registry.onOperatorSetDeregistered(operator); + + assertEq(registry.getLastCheckpointOperatorWeight(operator), 0); + assertEq(registry.getLastCheckpointTotalWeight(), initialTotalWeight - 360); + console.log(registry.operatorRegisteredOnAVSDirectory(operator)); + } + + function test_MultipleOperatorRegistration() public { + address operator6 = makeAddr("operator6"); + + address[] memory operators = new address[](1); + operators[0] = operator6; + + for (uint32 setId = 1; setId <= 2; setId++) { + OperatorSet memory operatorSet = + OperatorSet({avs: address(mockServiceManager), id: setId}); + + IStrategy[] memory strategies = + mockAllocationManager.getStrategiesInOperatorSet(operatorSet); + for (uint256 i = 0; i < operators.length; i++) { + for (uint256 j = 0; j < strategies.length; j++) { + // Set operator weight/allocation + mockAllocationManager.setOperatorWeight( + operators[i], + operatorSet, + address(strategies[j]), + 3e17 // 30% in WAD + ); + mockAllocationManager.setMaxMagnitude(operators[i], strategies[j], 1e18); + } + } + } + + uint256 initialTotalWeight = registry.getLastCheckpointTotalWeight(); + for (uint256 i = 0; i < operators.length; i++) { + vm.prank(mockAVSRegistrarAddr); + registry.onOperatorSetRegistered(operators[i], operators[i]); + assertEq(registry.getLastCheckpointOperatorWeight(operators[i]), 360); + } + + assertEq(registry.getLastCheckpointTotalWeight(), initialTotalWeight + 360); + } + + function test_SetAndGetCurrentOperatorSetIds() public { + uint32[] memory newIds = new uint32[](2); + newIds[0] = 3; + newIds[1] = 4; + + vm.prank(owner); + registry.setCurrentOperatorSetIds(newIds); + + uint32[] memory retrievedIds = registry.getCurrentOperatorSetIds(); + assertEq(retrievedIds.length, 2); + assertEq(retrievedIds[0], 3); + assertEq(retrievedIds[1], 4); + } + + function test_RevertsWhen_NotOwner_SetCurrentOperatorSetIds() public { + uint32[] memory newIds = new uint32[](2); + newIds[0] = 3; + newIds[1] = 4; + + address notOwner = makeAddr("notOwner"); + vm.prank(notOwner); + vm.expectRevert("Ownable: caller is not the owner"); + registry.setCurrentOperatorSetIds(newIds); + } + + function test_UpdateOperatorSetsConfig() public { + uint32[] memory operatorSetIds = new uint32[](2); + operatorSetIds[0] = 1; + operatorSetIds[1] = 2; + + StrategyParams[][] memory strategyParamsArray = new StrategyParams[][](2); + + strategyParamsArray[0] = new StrategyParams[](2); + strategyParamsArray[0][0] = + StrategyParams({strategy: IStrategy(address(800)), multiplier: 4000}); + strategyParamsArray[0][1] = + StrategyParams({strategy: IStrategy(address(801)), multiplier: 4000}); + strategyParamsArray[1] = new StrategyParams[](2); + strategyParamsArray[1][0] = + StrategyParams({strategy: IStrategy(address(802)), multiplier: 4000}); + strategyParamsArray[1][1] = + StrategyParams({strategy: IStrategy(address(803)), multiplier: 4000}); + + address[] memory operators = new address[](2); + operators[0] = operator3; + operators[1] = operator4; + + vm.prank(owner); + registry.updateOperatorSetsConfig(operatorSetIds, strategyParamsArray, operators); + StrategyParams[] memory params = registry.getOperatorSetConfig(1); + assertEq(params.length, 2); + assertEq(address(params[0].strategy), address(800)); + assertEq(params[0].multiplier, 4000); + assertEq(address(params[1].strategy), address(801)); + assertEq(params[1].multiplier, 4000); + + params = registry.getOperatorSetConfig(2); + assertEq(params.length, 2); + assertEq(address(params[0].strategy), address(802)); + assertEq(params[0].multiplier, 4000); + assertEq(address(params[1].strategy), address(803)); + assertEq(params[1].multiplier, 4000); + } + + function test_RevertsWhen_NotOwner_UpdateOperatorSetsConfig() public { + uint32[] memory operatorSetIds = new uint32[](1); + operatorSetIds[0] = 1; + + StrategyParams[][] memory strategyParamsArray = new StrategyParams[][](1); + strategyParamsArray[0] = new StrategyParams[](1); + strategyParamsArray[0][0] = + StrategyParams({strategy: IStrategy(address(800)), multiplier: 4000}); + + address[] memory operators = new address[](1); + operators[0] = operator3; + + address notOwner = makeAddr("notOwner"); + vm.prank(notOwner); + vm.expectRevert("Ownable: caller is not the owner"); + registry.updateOperatorSetsConfig(operatorSetIds, strategyParamsArray, operators); + } + + function test_RevertsWhen_LengthMismatch_UpdateOperatorSetsConfig() public { + uint32[] memory operatorSetIds = new uint32[](2); + operatorSetIds[0] = 1; + operatorSetIds[1] = 2; + + StrategyParams[][] memory strategyParamsArray = new StrategyParams[][](1); + strategyParamsArray[0] = new StrategyParams[](1); + strategyParamsArray[0][0] = + StrategyParams({strategy: IStrategy(address(800)), multiplier: 4000}); + + address[] memory operators = new address[](1); + operators[0] = operator3; + + vm.prank(owner); + vm.expectRevert(IECDSAStakeRegistryErrors.InvalidOperatorSetIdsLength.selector); + registry.updateOperatorSetsConfig(operatorSetIds, strategyParamsArray, operators); + } + + function test_RevertsWhen_UnsortedStrategies_UpdateOperatorSetsConfig() public { + uint32[] memory operatorSetIds = new uint32[](1); + operatorSetIds[0] = 1; + + StrategyParams[][] memory strategyParamsArray = new StrategyParams[][](1); + strategyParamsArray[0] = new StrategyParams[](2); + strategyParamsArray[0][0] = StrategyParams({ + strategy: IStrategy(address(801)), // Higher address first + multiplier: 4000 + }); + strategyParamsArray[0][1] = StrategyParams({ + strategy: IStrategy(address(800)), // Lower address second + multiplier: 4000 + }); + + address[] memory operators = new address[](1); + operators[0] = operator3; + + vm.prank(owner); + vm.expectRevert(IECDSAStakeRegistryErrors.NotSorted.selector); + registry.updateOperatorSetsConfig(operatorSetIds, strategyParamsArray, operators); + } + + function test_RegisterOperatorSetAfterM2Quorum() public { + // operator1 is already registered in M2 quorum from setUp() + assertEq(registry.getLastCheckpointOperatorWeight(operator1), 1000); + assertTrue(registry.operatorRegistered(operator1)); + + for (uint32 setId = 1; setId <= 2; setId++) { + OperatorSet memory operatorSet = + OperatorSet({avs: address(mockServiceManager), id: setId}); + + IStrategy[] memory strategies = + mockAllocationManager.getStrategiesInOperatorSet(operatorSet); + for (uint256 j = 0; j < strategies.length; j++) { + mockAllocationManager.setOperatorWeight( + operator1, + operatorSet, + address(strategies[j]), + 3e17 // 30% in WAD + ); + mockAllocationManager.setMaxMagnitude(operator1, strategies[j], 1e18); + } + } + + vm.prank(mockAVSRegistrarAddr); + registry.onOperatorSetRegistered(operator1, operator1); + + assertEq(registry.getLastCheckpointOperatorWeight(operator1), 1360); // 1000 from M2 + 360 from operator sets + assertTrue(registry.operatorRegistered(operator1)); + assertTrue(registry.operatorRegisteredOnAVSDirectory(operator1)); + } + + function test_RegisterM2QuorumAfterOperatorSet() public { + // operator3 is already registered in operator sets from setUp() + assertEq(registry.getLastCheckpointOperatorWeight(operator3), 360); + assertTrue(registry.operatorRegistered(operator3)); + + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; + vm.prank(operator3); + registry.registerOperatorM2Quorum(operatorSignature, operator3); + + assertEq(registry.getLastCheckpointOperatorWeight(operator3), 1360); // 360 from operator sets + 1000 from M2 + assertTrue(registry.operatorRegistered(operator3)); + assertTrue(registry.operatorRegisteredOnAVSDirectory(operator3)); + } + + function test_OperatorRegistrationStatus() public { + // Test operator1: Only M2 quorum registration + assertTrue(registry.operatorRegisteredOnAVSDirectory(operator1)); + assertFalse(registry.operatorRegisteredOnCurrentOperatorSets(operator1)); + assertTrue(registry.operatorRegistered(operator1)); + + // Test operator3: Only Operator Sets registration + assertFalse(registry.operatorRegisteredOnAVSDirectory(operator3)); + assertTrue(registry.operatorRegisteredOnCurrentOperatorSets(operator3)); + assertTrue(registry.operatorRegistered(operator3)); + + // Test unregistered operator + address unregisteredOperator = makeAddr("unregistered"); + assertFalse(registry.operatorRegisteredOnAVSDirectory(unregisteredOperator)); + assertFalse(registry.operatorRegisteredOnCurrentOperatorSets(unregisteredOperator)); + assertFalse(registry.operatorRegistered(unregisteredOperator)); + } + + function test_GetOperatorSetWeight_WithMagnitudeChanges() public { + assertEq(registry.getOperatorSetWeight(operator3), 360); + OperatorSet memory operatorSet = OperatorSet({avs: address(mockServiceManager), id: 1}); + IStrategy[] memory strategies = + mockAllocationManager.getStrategiesInOperatorSet(operatorSet); + + for (uint256 j = 0; j < strategies.length; j++) { + mockAllocationManager.setOperatorWeight( + operator3, + operatorSet, + address(strategies[j]), + 1.5e17 // 15% in WAD (half of original 30%) + ); + } + + address[] memory operators = new address[](1); + operators[0] = operator3; + registry.updateOperators(operators); + + // Weight should be halved for first set (180->90) plus full weight from second set (180) = 270 + assertEq(registry.getOperatorSetWeight(operator3), 270); + mockAllocationManager.setOperatorSetMembership(operator3, operatorSet, false); + registry.updateOperators(operators); + + assertEq(registry.getOperatorSetWeight(operator3), 180); + } + + function test_DisableM2QuorumRegistration() public { + // Verify initial state - can register + address newOperator = makeAddr("newOperator"); + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; + vm.prank(newOperator); + registry.registerOperatorM2Quorum(operatorSignature, newOperator); + assertTrue(registry.operatorRegisteredOnAVSDirectory(newOperator)); + + vm.expectEmit(true, true, true, true); + emit M2QuorumRegistrationDisabled(); + vm.prank(owner); + registry.disableM2QuorumRegistration(); + + // Try to register new operator after disable - should fail + address anotherOperator = makeAddr("anotherOperator"); + vm.prank(anotherOperator); + vm.expectRevert(IECDSAStakeRegistryErrors.M2QuorumRegistrationIsDisabled.selector); + registry.registerOperatorM2Quorum(operatorSignature, anotherOperator); + + assertTrue(registry.operatorRegisteredOnAVSDirectory(newOperator)); + assertTrue(registry.operatorRegisteredOnAVSDirectory(operator1)); + assertTrue(registry.operatorRegisteredOnAVSDirectory(operator2)); + + // Try to disable again - should fail + vm.prank(owner); + vm.expectRevert(IECDSAStakeRegistryErrors.M2QuorumRegistrationIsDisabled.selector); + registry.disableM2QuorumRegistration(); + } + + function test_RevertsWhen_NotOwner_DisableM2QuorumRegistration() public { + address notOwner = makeAddr("notOwner"); + vm.prank(notOwner); + vm.expectRevert("Ownable: caller is not the owner"); + registry.disableM2QuorumRegistration(); + } } From b82d329125e12b7598048398964cc9828420d27f Mon Sep 17 00:00:00 2001 From: jaxxjj Date: Sat, 1 Mar 2025 01:20:31 -0700 Subject: [PATCH 09/12] fix: ecdsa service manager test --- test/unit/ECDSAServiceManager.t.sol | 65 +++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/test/unit/ECDSAServiceManager.t.sol b/test/unit/ECDSAServiceManager.t.sol index 69daf8ff..4bea34b4 100644 --- a/test/unit/ECDSAServiceManager.t.sol +++ b/test/unit/ECDSAServiceManager.t.sol @@ -18,7 +18,7 @@ import {ECDSAStakeRegistryMock} from "../mocks/ECDSAStakeRegistryMock.sol"; import {AVSDirectoryMock} from "../mocks/AVSDirectoryMock.sol"; import {IPermissionController} from "eigenlayer-contracts/src/contracts/interfaces/IPermissionController.sol"; -import {IAllocationManager} from +import {IAllocationManager, IAllocationManagerTypes} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; import {IAVSDirectory, IAVSDirectoryTypes} from "../../src/unaudited/ECDSAStakeRegistry.sol"; import {OperatorSet} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol"; @@ -59,22 +59,76 @@ contract MockDelegationManager { } contract MockAllocationManager { - function setAVSRegistrar(address avs, address registrar) external {} + mapping(address => mapping(bytes32 => IAllocationManagerTypes.Allocation)) public allocations; + mapping(address => mapping(IStrategy => uint64)) public maxMagnitudes; + mapping(address => mapping(bytes32 => bool)) public operatorSetMembership; - function isOperatorSet( + function getAllocation( + address operator, + OperatorSet memory operatorSet, + IStrategy strategy + ) external view returns (IAllocationManagerTypes.Allocation memory) { + bytes32 key = keccak256(abi.encode(operator, operatorSet, address(strategy))); + return allocations[operator][key]; + } + + function setOperatorWeight( + address operator, + OperatorSet memory operatorSet, + address strategy, + uint256 weight + ) external { + bytes32 key = keccak256(abi.encode(operator, operatorSet, address(strategy))); + allocations[operator][key] = IAllocationManagerTypes.Allocation({ + currentMagnitude: uint64(weight), + pendingDiff: 0, + effectBlock: 0 + }); + bytes32 membershipKey = keccak256(abi.encode(operator, operatorSet)); + operatorSetMembership[operator][membershipKey] = true; + } + + function isMemberOfOperatorSet( + address operator, OperatorSet memory operatorSet + ) external view returns (bool) { + bytes32 key = keccak256(abi.encode(operator, operatorSet)); + return operatorSetMembership[operator][key]; + } + + function setOperatorSetMembership( + address operator, + OperatorSet memory operatorSet, + bool isMember + ) external { + bytes32 key = keccak256(abi.encode(operator, operatorSet)); + operatorSetMembership[operator][key] = isMember; + } + + function isOperatorSet( + OperatorSet memory ) external pure returns (bool) { return true; } function getStrategiesInOperatorSet( - OperatorSet memory operatorSet + OperatorSet memory ) external pure returns (IStrategy[] memory) { IStrategy[] memory strategies = new IStrategy[](2); strategies[0] = IStrategy(address(900)); strategies[1] = IStrategy(address(901)); return strategies; } + + function setAVSRegistrar(address, address) external {} + + function getMaxMagnitude(address operator, IStrategy strategy) external view returns (uint64) { + return maxMagnitudes[operator][strategy]; + } + + function setMaxMagnitude(address operator, IStrategy strategy, uint64 magnitude) external { + maxMagnitudes[operator][strategy] = magnitude; + } } contract MockRewardsCoordinator { @@ -94,7 +148,6 @@ contract MockRewardsCoordinator { } contract MockAVSDirectory { - // 使用 mapping 存储每个 operator 的状态 mapping(address => mapping(address => IAVSDirectoryTypes.OperatorAVSRegistrationStatus)) private operatorStatus; @@ -102,7 +155,6 @@ contract MockAVSDirectory { address operator, ISignatureUtils.SignatureWithSaltAndExpiry memory ) external { - // 设置特定 operator 的状态为 REGISTERED operatorStatus[msg.sender][operator] = IAVSDirectoryTypes.OperatorAVSRegistrationStatus.REGISTERED; } @@ -110,7 +162,6 @@ contract MockAVSDirectory { function deregisterOperatorFromAVS( address operator ) external { - // 设置特定 operator 的状态为 UNREGISTERED operatorStatus[msg.sender][operator] = IAVSDirectoryTypes.OperatorAVSRegistrationStatus.UNREGISTERED; } From 10071f536a3a39f7b366ebe68acc287ddb8ad5af Mon Sep 17 00:00:00 2001 From: jaxxjj Date: Sat, 1 Mar 2025 01:58:24 -0700 Subject: [PATCH 10/12] fix: ecdsa service manager test --- test/unit/ECDSAServiceManager.t.sol | 107 +--------------------------- 1 file changed, 1 insertion(+), 106 deletions(-) diff --git a/test/unit/ECDSAServiceManager.t.sol b/test/unit/ECDSAServiceManager.t.sol index 51eb817c..45c09e90 100644 --- a/test/unit/ECDSAServiceManager.t.sol +++ b/test/unit/ECDSAServiceManager.t.sol @@ -12,8 +12,6 @@ import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy import {IAVSRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IAVSRegistrar.sol"; import {IECDSAStakeRegistryTypes} from "../../src/interfaces/IECDSAStakeRegistry.sol"; import {IECDSAStakeRegistry} from "../../src/interfaces/IECDSAStakeRegistry.sol"; -import {IECDSAStakeRegistryTypes} from "../../src/interfaces/IECDSAStakeRegistry.sol"; -import {IECDSAStakeRegistry} from "../../src/interfaces/IECDSAStakeRegistry.sol"; import {ECDSAServiceManagerMock} from "../mocks/ECDSAServiceManagerMock.sol"; import {ECDSAStakeRegistryMock} from "../mocks/ECDSAStakeRegistryMock.sol"; @@ -191,7 +189,6 @@ contract MockAVSDirectory { contract ECDSAServiceManagerSetup is Test { MockDelegationManager public mockDelegationManager; AVSDirectoryMock public mockAVSDirectory; - AVSDirectoryMock public mockAVSDirectory; MockAllocationManager public mockAllocationManager; ECDSAStakeRegistryMock public mockStakeRegistry; MockRewardsCoordinator public mockRewardsCoordinator; @@ -200,10 +197,6 @@ contract ECDSAServiceManagerSetup is Test { address public mockAVSRegistrarAddr; address public owner = makeAddr("owner"); address public rewardsInitiator = makeAddr("rewardsInitiator"); - MockPermissionController public mockPermissionController; - address public mockAVSRegistrarAddr; - address public owner = makeAddr("owner"); - address public rewardsInitiator = makeAddr("rewardsInitiator"); address internal operator1; address internal operator2; uint256 internal operator1Pk; @@ -212,7 +205,6 @@ contract ECDSAServiceManagerSetup is Test { function setUp() public { mockDelegationManager = new MockDelegationManager(); mockAVSDirectory = new AVSDirectoryMock(); - mockAVSDirectory = new AVSDirectoryMock(); mockAllocationManager = new MockAllocationManager(); mockAVSRegistrarAddr = makeAddr("mockAVSRegistrar"); mockStakeRegistry = new ECDSAStakeRegistryMock( @@ -221,19 +213,10 @@ contract ECDSAServiceManagerSetup is Test { mockAVSRegistrarAddr, IAVSDirectory(address(mockAVSDirectory)) ); - mockAVSRegistrarAddr = makeAddr("mockAVSRegistrar"); - mockStakeRegistry = new ECDSAStakeRegistryMock( - IDelegationManager(address(mockDelegationManager)), - IAllocationManager(address(mockAllocationManager)), - mockAVSRegistrarAddr, - IAVSDirectory(address(mockAVSDirectory)) - ); mockRewardsCoordinator = new MockRewardsCoordinator(); mockPermissionController = new MockPermissionController(); - mockPermissionController = new MockPermissionController(); - serviceManager = new ECDSAServiceManagerMock( address(mockAVSDirectory), address(mockStakeRegistry), @@ -243,10 +226,6 @@ contract ECDSAServiceManagerSetup is Test { address(mockPermissionController), owner, rewardsInitiator - address(mockAllocationManager), - address(mockPermissionController), - owner, - rewardsInitiator ); operator1Pk = 1; @@ -293,34 +272,6 @@ contract ECDSAServiceManagerSetup is Test { multiplier: 3000 }); - vm.prank(owner); - uint32[] memory operatorSetIds = new uint32[](2); - operatorSetIds[0] = 1; - operatorSetIds[1] = 2; - - IECDSAStakeRegistryTypes.StrategyParams[][] memory strategyParamsArray = - new IECDSAStakeRegistryTypes.StrategyParams[][](2); - - strategyParamsArray[0] = new IECDSAStakeRegistryTypes.StrategyParams[](2); - strategyParamsArray[0][0] = IECDSAStakeRegistryTypes.StrategyParams({ - strategy: IStrategy(address(900)), - multiplier: 3000 - }); - strategyParamsArray[0][1] = IECDSAStakeRegistryTypes.StrategyParams({ - strategy: IStrategy(address(901)), - multiplier: 3000 - }); - // Strategy params for second operator set - strategyParamsArray[1] = new IECDSAStakeRegistryTypes.StrategyParams[](2); - strategyParamsArray[1][0] = IECDSAStakeRegistryTypes.StrategyParams({ - strategy: IStrategy(address(902)), - multiplier: 3000 - }); - strategyParamsArray[1][1] = IECDSAStakeRegistryTypes.StrategyParams({ - strategy: IStrategy(address(903)), - multiplier: 3000 - }); - vm.prank(owner); mockStakeRegistry.initialize( address(serviceManager), @@ -328,21 +279,15 @@ contract ECDSAServiceManagerSetup is Test { quorum, operatorSetIds, strategyParamsArray - quorum, - operatorSetIds, - strategyParamsArray ); - ISignatureUtils.SignatureWithSaltAndExpiry memory dummySignature; vm.prank(operator1); mockStakeRegistry.registerOperatorM2Quorum(dummySignature, operator1); - mockStakeRegistry.registerOperatorM2Quorum(dummySignature, operator1); vm.prank(operator2); mockStakeRegistry.registerOperatorM2Quorum(dummySignature, operator2); - mockStakeRegistry.registerOperatorM2Quorum(dummySignature, operator2); } function testRegisterOperatorToAVS() public { @@ -420,7 +365,6 @@ contract ECDSAServiceManagerSetup is Test { function testSetClaimerFor() public { address claimer = address(0x123); - vm.prank(owner); vm.prank(owner); serviceManager.setClaimerFor(claimer); } @@ -480,53 +424,4 @@ contract ECDSAServiceManagerSetup is Test { vm.prank(owner); serviceManager.removeAppointee(appointee, target, selector); } - - function testGetOperatorSetStrategies() public { - uint32 operatorSetId = 1; - - address[] memory strategies = serviceManager.getOperatorSetStrategies(operatorSetId); - - assertEq(strategies.length, 2, "Should return 2 strategies"); - assertEq(strategies[0], address(900), "First strategy should match"); - assertEq(strategies[1], address(901), "Second strategy should match"); - } - - function testAddPendingAdmin() public { - address admin = makeAddr("admin"); - - vm.prank(owner); - serviceManager.addPendingAdmin(admin); - } - - function testRemovePendingAdmin() public { - address pendingAdmin = makeAddr("pendingAdmin"); - - vm.prank(owner); - serviceManager.removePendingAdmin(pendingAdmin); - } - - function testRemoveAdmin() public { - address admin = makeAddr("admin"); - - vm.prank(owner); - serviceManager.removeAdmin(admin); - } - - function testSetAppointee() public { - address appointee = makeAddr("appointee"); - address target = makeAddr("target"); - bytes4 selector = bytes4(keccak256("someFunction()")); - - vm.prank(owner); - serviceManager.setAppointee(appointee, target, selector); - } - - function testRemoveAppointee() public { - address appointee = makeAddr("appointee"); - address target = makeAddr("target"); - bytes4 selector = bytes4(keccak256("someFunction()")); - - vm.prank(owner); - serviceManager.removeAppointee(appointee, target, selector); - } -} +} \ No newline at end of file From fe83672b811923f6b0ab47e86a8c8a174e7fba77 Mon Sep 17 00:00:00 2001 From: jaxxjj Date: Sat, 1 Mar 2025 02:13:18 -0700 Subject: [PATCH 11/12] perf: change immutability and import format --- src/unaudited/ECDSAAVSRegistrar.sol | 8 +------- test/unit/ECDSAServiceManager.t.sol | 8 +++++--- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/unaudited/ECDSAAVSRegistrar.sol b/src/unaudited/ECDSAAVSRegistrar.sol index 8bbaa66f..826e88d8 100644 --- a/src/unaudited/ECDSAAVSRegistrar.sol +++ b/src/unaudited/ECDSAAVSRegistrar.sol @@ -9,7 +9,7 @@ import {IECDSAStakeRegistry} from "../interfaces/IECDSAStakeRegistry.sol"; import {IServiceManager} from "../interfaces/IServiceManager.sol"; contract AVSRegistrar is IAVSRegistrar, Ownable { - IAllocationManager public allocationManager; + IAllocationManager public immutable allocationManager; IECDSAStakeRegistry public immutable stakeRegistry; address public immutable avs; @@ -50,12 +50,6 @@ contract AVSRegistrar is IAVSRegistrar, Ownable { stakeRegistry.onOperatorSetDeregistered(operator); } - function updateAllocationManager( - address _allocationManager - ) external onlyOwner { - allocationManager = IAllocationManager(_allocationManager); - } - function supportsAVS( address avsAddr ) external view returns (bool) { diff --git a/test/unit/ECDSAServiceManager.t.sol b/test/unit/ECDSAServiceManager.t.sol index 45c09e90..d6497c21 100644 --- a/test/unit/ECDSAServiceManager.t.sol +++ b/test/unit/ECDSAServiceManager.t.sol @@ -18,8 +18,10 @@ import {ECDSAStakeRegistryMock} from "../mocks/ECDSAStakeRegistryMock.sol"; import {AVSDirectoryMock} from "../mocks/AVSDirectoryMock.sol"; import {IPermissionController} from "eigenlayer-contracts/src/contracts/interfaces/IPermissionController.sol"; -import {IAllocationManager, IAllocationManagerTypes} from - "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import { + IAllocationManager, + IAllocationManagerTypes +} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; import {IAVSDirectory, IAVSDirectoryTypes} from "../../src/unaudited/ECDSAStakeRegistry.sol"; import {OperatorSet} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol"; @@ -424,4 +426,4 @@ contract ECDSAServiceManagerSetup is Test { vm.prank(owner); serviceManager.removeAppointee(appointee, target, selector); } -} \ No newline at end of file +} From 32c309742ff019bbe66cf96a395b421d0ff23179 Mon Sep 17 00:00:00 2001 From: jaxxjj Date: Wed, 19 Mar 2025 15:49:07 -0400 Subject: [PATCH 12/12] fix: operator set strategies multipliers --- src/interfaces/IECDSAStakeRegistry.sol | 16 +-- src/unaudited/ECDSAStakeRegistry.sol | 102 +++++++------------- src/unaudited/ECDSAStakeRegistryStorage.sol | 6 +- 3 files changed, 46 insertions(+), 78 deletions(-) diff --git a/src/interfaces/IECDSAStakeRegistry.sol b/src/interfaces/IECDSAStakeRegistry.sol index ffaf0db2..c6da054c 100644 --- a/src/interfaces/IECDSAStakeRegistry.sol +++ b/src/interfaces/IECDSAStakeRegistry.sol @@ -58,6 +58,13 @@ interface IECDSAStakeRegistryTypes { uint96 multiplier; } + /// @notice Struct to represent operator set strategy multiplier + struct OperatorSetStrategyMultiplier { + uint32 operatorSetId; + address strategy; + uint256 multiplier; + } + /// @notice Configuration for a quorum's strategies. /// @param strategies An array of strategy parameters defining the quorum. struct Quorum { @@ -140,12 +147,9 @@ interface IECDSAStakeRegistryEvents is IECDSAStakeRegistryTypes { */ event M2QuorumRegistrationDisabled(); - /* - * @notice Emitted when operator set strategy parameters are updated - * @param operatorSetId The ID of the operator set that was updated - * @param params The new strategy parameters for the operator set - */ - event OperatorSetStrategyParamsUpdated(uint32 indexed operatorSetId, StrategyParams[] params); + + /// @notice Event emitted when operator set strategy multipliers are updated + event OperatorSetStrategyMultipliersUpdated(OperatorSetStrategyMultiplier[] params); } interface IECDSAStakeRegistry is diff --git a/src/unaudited/ECDSAStakeRegistry.sol b/src/unaudited/ECDSAStakeRegistry.sol index 8d411c15..e3f3dd80 100644 --- a/src/unaudited/ECDSAStakeRegistry.sol +++ b/src/unaudited/ECDSAStakeRegistry.sol @@ -72,10 +72,10 @@ contract ECDSAStakeRegistry is uint256 thresholdWeight, IECDSAStakeRegistryTypes.Quorum memory quorum, uint32[] calldata operatorSetIds, - StrategyParams[][] calldata strategyParamsArray + OperatorSetStrategyMultiplier[] calldata operatorSetStrategyMultipliers ) external initializer { __ECDSAStakeRegistry_init( - _serviceManager, thresholdWeight, quorum, operatorSetIds, strategyParamsArray + _serviceManager, thresholdWeight, quorum, operatorSetIds, operatorSetStrategyMultipliers ); } @@ -86,13 +86,13 @@ contract ECDSAStakeRegistry is uint256 thresholdWeight, IECDSAStakeRegistryTypes.Quorum memory quorum, uint32[] calldata operatorSetIds, - StrategyParams[][] calldata strategyParamsArray + OperatorSetStrategyMultiplier[] calldata operatorSetStrategyMultipliers ) internal onlyInitializing { _serviceManager = _serviceManagerAddr; _updateStakeThreshold(thresholdWeight); _updateQuorumConfig(quorum); _setCurrentOperatorSetIds(operatorSetIds); - _updateOperatorSetsConfig(operatorSetIds, strategyParamsArray); + _updateOperatorSetsConfig(operatorSetStrategyMultipliers); __Ownable_init(); } @@ -174,15 +174,13 @@ contract ECDSAStakeRegistry is /** * @notice Updates strategy parameters for multiple operator sets (from stake registry view) * @notice Strategy params must align with operator set ids on the allocation manager - * @param operatorSetIds Array of operator set IDs to update - * @param strategyParamsArray Array of strategy parameters arrays for each operator set + * @param operatorSetStrategyMultipliers Array of operator set strategy multipliers to update */ function updateOperatorSetsConfig( - uint32[] calldata operatorSetIds, - StrategyParams[][] calldata strategyParamsArray, + OperatorSetStrategyMultiplier[] calldata operatorSetStrategyMultipliers, address[] calldata operators ) external onlyOwner { - _updateOperatorSetsConfig(operatorSetIds, strategyParamsArray); + _updateOperatorSetsConfig(operatorSetStrategyMultipliers); _updateOperators(operators); } @@ -253,15 +251,6 @@ contract ECDSAStakeRegistry is return currentOperatorSetIds; } - /// @notice Gets the strategy parameters for a specific operator set - /// @param operatorSetId The ID of the operator set to query - /// @return The array of strategy parameters for the operator set - function getOperatorSetConfig( - uint32 operatorSetId - ) external view returns (StrategyParams[] memory) { - return operatorSetStrategyParams[operatorSetId]; - } - /// @inheritdoc IECDSAStakeRegistry function getLatestOperatorSigningKey( address operator @@ -330,12 +319,18 @@ contract ECDSAStakeRegistry is ) public view virtual returns (uint256) { uint256 quorumWeight = getQuorumWeight(_operator); uint256 operatorSetWeight = getOperatorSetWeight(_operator); - return quorumWeight + operatorSetWeight; + uint256 totalWeight = quorumWeight + operatorSetWeight; + + if (totalWeight >= _minimumWeight) { + return totalWeight; + } else { + return 0; + } } /// @notice Calculates operator's weight in the quorum /// @param operator The operator address to calculate weight for - /// @return The operator's weight in quorum, or 0 if below minimum + /// @return The operator's weight in quorum function getQuorumWeight( address operator ) public view returns (uint256) { @@ -354,11 +349,7 @@ contract ECDSAStakeRegistry is } weight = weight / BPS; - if (weight >= _minimumWeight) { - return weight; - } else { - return 0; - } + return weight; } /// @notice Calculates operator's available weight in current operator set @@ -368,7 +359,7 @@ contract ECDSAStakeRegistry is /// 3. Calculate available proportion (currentMagnitude/maxMagnitude) /// 4. Sum up available shares weighted by proportion /// @param operator The operator address to calculate weight for - /// @return The operator's available weight in set, or 0 if below minimum + /// @return The operator's available weight in set function getOperatorSetWeight( address operator ) public view virtual returns (uint256) { @@ -391,7 +382,6 @@ contract ECDSAStakeRegistry is continue; } - StrategyParams[] memory strategyParams = operatorSetStrategyParams[operatorSetId]; uint256[] memory shares = DELEGATION_MANAGER.getOperatorShares(operator, strategies); for (uint256 i = 0; i < strategies.length; i++) { @@ -403,17 +393,14 @@ contract ECDSAStakeRegistry is continue; } - uint256 slashableProportion = - uint256(allocation.currentMagnitude) * WAD / maxMagnitude; - totalWeight += - shares[i] * slashableProportion * strategyParams[i].multiplier / WAD / BPS; + uint256 slashableProportion = uint256(allocation.currentMagnitude) * WAD / maxMagnitude; + uint256 multiplier = operatorSetStrategyMultipliers[operatorSetId][address(strategies[i])]; + + totalWeight += shares[i] * slashableProportion * multiplier / WAD / BPS; } } - if (totalWeight >= _minimumWeight) { - return totalWeight; - } else { - return 0; - } + + return totalWeight; } /// @inheritdoc IECDSAStakeRegistry @@ -804,41 +791,18 @@ contract ECDSAStakeRegistry is } } - /// @notice Internal function to update strategy parameters for multiple operator sets - /// @param operatorSetIds Array of operator set IDs to update - /// @param strategyParamsArray Array of strategy parameters arrays for each operator set + /// @notice Internal function to update strategy multipliers for multiple operator sets + /// @param params Array of operator set strategy multipliers to update function _updateOperatorSetsConfig( - uint32[] calldata operatorSetIds, - StrategyParams[][] calldata strategyParamsArray - ) internal { - if (operatorSetIds.length != strategyParamsArray.length) { - revert InvalidOperatorSetIdsLength(); - } - - for (uint256 i = 0; i < operatorSetIds.length; i++) { - _updateOperatorSetConfig(operatorSetIds[i], strategyParamsArray[i]); - } - } - - /// @notice Internal function to set strategy parameters for an operator set - ///@param operatorSetId The ID of the operator set - ///@param params The strategy parameters to set - - function _updateOperatorSetConfig( - uint32 operatorSetId, - StrategyParams[] memory params + OperatorSetStrategyMultiplier[] calldata params ) internal { - address lastStrategy; - for (uint256 i = 0; i < params.length; i++) { - address currentStrategy = address(params[i].strategy); - if (lastStrategy >= currentStrategy) revert NotSorted(); - lastStrategy = currentStrategy; - } - - delete operatorSetStrategyParams[operatorSetId]; for (uint256 i = 0; i < params.length; i++) { - operatorSetStrategyParams[operatorSetId].push(params[i]); - } - emit OperatorSetStrategyParamsUpdated(operatorSetId, params); + uint32 operatorSetId = params[i].operatorSetId; + address strategy = params[i].strategy; + uint256 multiplier = params[i].multiplier; + operatorSetStrategyMultipliers[operatorSetId][strategy] = multiplier; + } + + emit OperatorSetStrategyMultipliersUpdated(params); } } diff --git a/src/unaudited/ECDSAStakeRegistryStorage.sol b/src/unaudited/ECDSAStakeRegistryStorage.sol index b3035ae6..54648df6 100644 --- a/src/unaudited/ECDSAStakeRegistryStorage.sol +++ b/src/unaudited/ECDSAStakeRegistryStorage.sol @@ -33,9 +33,6 @@ abstract contract ECDSAStakeRegistryStorage is IECDSAStakeRegistry { /// @notice The current operator set ids uint32[] public currentOperatorSetIds; - /// @notice Maps operator set IDs to their strategy parameters - mapping(uint32 => StrategyParams[]) public operatorSetStrategyParams; - /// @notice The total amount of multipliers to weigh stakes uint256 public constant WAD = 1e18; @@ -48,6 +45,9 @@ abstract contract ECDSAStakeRegistryStorage is IECDSAStakeRegistry { /// @notice Stores the current quorum configuration IECDSAStakeRegistryTypes.Quorum internal _quorum; + /// @notice strategy parameters for operator sets, mapping from operatorSetId to strategy to multiplier + mapping(uint32 => mapping(address => uint256)) internal operatorSetStrategyMultipliers; + /// @notice Specifies the weight required to become an operator uint256 internal _minimumWeight;