diff --git a/foundry.toml b/foundry.toml index 5f427191..4e13d2e8 100644 --- a/foundry.toml +++ b/foundry.toml @@ -3,6 +3,7 @@ src = "src" out = "out" libs = ["lib"] fs_permissions = [{ access = "read-write", path = "./" }] +gas_limit = 5000000000 ffi = true no-match-contract = "FFI" diff --git a/lib/eigenlayer-contracts b/lib/eigenlayer-contracts index a888a1cd..ac57bc1b 160000 --- a/lib/eigenlayer-contracts +++ b/lib/eigenlayer-contracts @@ -1 +1 @@ -Subproject commit a888a1cd1479438dda4b138245a69177b125a973 +Subproject commit ac57bc1b28c83d9d7143c0da19167c148c3596a3 diff --git a/src/BLSApkRegistry.sol b/src/BLSApkRegistry.sol index 4fd0e2fb..2bad724b 100644 --- a/src/BLSApkRegistry.sol +++ b/src/BLSApkRegistry.sol @@ -12,10 +12,7 @@ contract BLSApkRegistry is BLSApkRegistryStorage { /// @notice when applied to a function, only allows the RegistryCoordinator to call it modifier onlyRegistryCoordinator() { - require( - msg.sender == address(registryCoordinator), - "BLSApkRegistry.onlyRegistryCoordinator: caller is not the registry coordinator" - ); + _checkRegistryCoordinator(); _; } @@ -281,4 +278,11 @@ contract BLSApkRegistry is BLSApkRegistryStorage { function getOperatorId(address operator) public view returns (bytes32) { return operatorToPubkeyHash[operator]; } + + function _checkRegistryCoordinator() internal view { + require( + msg.sender == address(registryCoordinator), + "BLSApkRegistry.onlyRegistryCoordinator: caller is not the registry coordinator" + ); + } } diff --git a/src/EjectionManager.sol b/src/EjectionManager.sol index 4668c4c8..ca81676b 100644 --- a/src/EjectionManager.sol +++ b/src/EjectionManager.sol @@ -74,36 +74,45 @@ contract EjectionManager is IEjectionManager, OwnableUpgradeable{ uint32 ejectedOperators; bool ratelimitHit; - for(uint8 j = 0; j < _operatorIds[i].length; ++j) { - uint256 operatorStake = stakeRegistry.getCurrentStake(_operatorIds[i][j], quorumNumber); - - //if caller is ejector enforce ratelimit - if( - isEjector[msg.sender] && - quorumEjectionParams[quorumNumber].rateLimitWindow > 0 && - stakeForEjection + operatorStake > amountEjectable - ){ - stakeEjectedForQuorum[quorumNumber].push(StakeEjection({ - timestamp: block.timestamp, - stakeEjected: stakeForEjection - })); - ratelimitHit = true; - break; + if(amountEjectable > 0 || msg.sender == owner()){ + for(uint8 j = 0; j < _operatorIds[i].length; ++j) { + uint256 operatorStake = stakeRegistry.getCurrentStake(_operatorIds[i][j], quorumNumber); + + //if caller is ejector enforce ratelimit + if( + isEjector[msg.sender] && + quorumEjectionParams[quorumNumber].rateLimitWindow > 0 && + stakeForEjection + operatorStake > amountEjectable + ){ + ratelimitHit = true; + + stakeForEjection += operatorStake; + ++ejectedOperators; + + registryCoordinator.ejectOperator( + registryCoordinator.getOperatorFromId(_operatorIds[i][j]), + abi.encodePacked(quorumNumber) + ); + + emit OperatorEjected(_operatorIds[i][j], quorumNumber); + + break; + } + + stakeForEjection += operatorStake; + ++ejectedOperators; + + registryCoordinator.ejectOperator( + registryCoordinator.getOperatorFromId(_operatorIds[i][j]), + abi.encodePacked(quorumNumber) + ); + + emit OperatorEjected(_operatorIds[i][j], quorumNumber); } - - stakeForEjection += operatorStake; - ++ejectedOperators; - - registryCoordinator.ejectOperator( - registryCoordinator.getOperatorFromId(_operatorIds[i][j]), - abi.encodePacked(quorumNumber) - ); - - emit OperatorEjected(_operatorIds[i][j], quorumNumber); } //record the stake ejected if ejector and ratelimit enforced - if(!ratelimitHit && isEjector[msg.sender]){ + if(isEjector[msg.sender] && stakeForEjection > 0){ stakeEjectedForQuorum[quorumNumber].push(StakeEjection({ timestamp: block.timestamp, stakeEjected: stakeForEjection @@ -150,7 +159,7 @@ contract EjectionManager is IEjectionManager, OwnableUpgradeable{ */ function amountEjectableForQuorum(uint8 _quorumNumber) public view returns (uint256) { uint256 cutoffTime = block.timestamp - quorumEjectionParams[_quorumNumber].rateLimitWindow; - uint256 totalEjectable = quorumEjectionParams[_quorumNumber].ejectableStakePercent * stakeRegistry.getCurrentTotalStake(_quorumNumber) / BIPS_DENOMINATOR; + uint256 totalEjectable = uint256(quorumEjectionParams[_quorumNumber].ejectableStakePercent) * uint256(stakeRegistry.getCurrentTotalStake(_quorumNumber)) / uint256(BIPS_DENOMINATOR); uint256 totalEjected; uint256 i; if (stakeEjectedForQuorum[_quorumNumber].length == 0) { @@ -172,4 +181,4 @@ contract EjectionManager is IEjectionManager, OwnableUpgradeable{ } return totalEjectable - totalEjected; } -} +} \ No newline at end of file diff --git a/src/IndexRegistry.sol b/src/IndexRegistry.sol index 4e63e78a..8df2c0a1 100644 --- a/src/IndexRegistry.sol +++ b/src/IndexRegistry.sol @@ -12,7 +12,7 @@ contract IndexRegistry is IndexRegistryStorage { /// @notice when applied to a function, only allows the RegistryCoordinator to call it modifier onlyRegistryCoordinator() { - require(msg.sender == address(registryCoordinator), "IndexRegistry.onlyRegistryCoordinator: caller is not the registry coordinator"); + _checkRegistryCoordinator(); _; } @@ -340,4 +340,8 @@ contract IndexRegistry is IndexRegistryStorage { function totalOperatorsForQuorum(uint8 quorumNumber) external view returns (uint32){ return _latestQuorumUpdate(quorumNumber).numOperators; } + + function _checkRegistryCoordinator() internal view { + require(msg.sender == address(registryCoordinator), "IndexRegistry.onlyRegistryCoordinator: caller is not the registry coordinator"); + } } diff --git a/src/RegistryCoordinator.sol b/src/RegistryCoordinator.sol index 4006d9f4..a774fb19 100644 --- a/src/RegistryCoordinator.sol +++ b/src/RegistryCoordinator.sol @@ -3,12 +3,12 @@ pragma solidity ^0.8.12; import {IPauserRegistry} from "eigenlayer-contracts/src/contracts/interfaces/IPauserRegistry.sol"; import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol"; -import {ISocketUpdater} from "./interfaces/ISocketUpdater.sol"; import {IBLSApkRegistry} from "./interfaces/IBLSApkRegistry.sol"; import {IStakeRegistry} from "./interfaces/IStakeRegistry.sol"; import {IIndexRegistry} from "./interfaces/IIndexRegistry.sol"; import {IServiceManager} from "./interfaces/IServiceManager.sol"; import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol"; +import {ISocketRegistry} from "./interfaces/ISocketRegistry.sol"; import {EIP1271SignatureUtils} from "eigenlayer-contracts/src/contracts/libraries/EIP1271SignatureUtils.sol"; import {BitmapUtils} from "./libraries/BitmapUtils.sol"; @@ -35,24 +35,20 @@ contract RegistryCoordinator is Pausable, OwnableUpgradeable, RegistryCoordinatorStorage, - ISocketUpdater, ISignatureUtils { using BitmapUtils for *; using BN254 for BN254.G1Point; modifier onlyEjector { - require(msg.sender == ejector, "RegistryCoordinator.onlyEjector: caller is not the ejector"); + _checkEjector(); _; } /// @dev Checks that `quorumNumber` corresponds to a quorum that has been created /// via `initialize` or `createQuorum` modifier quorumExists(uint8 quorumNumber) { - require( - quorumNumber < quorumCount, - "RegistryCoordinator.quorumExists: quorum does not exist" - ); + _checkQuorumExists(quorumNumber); _; } @@ -60,9 +56,10 @@ contract RegistryCoordinator is IServiceManager _serviceManager, IStakeRegistry _stakeRegistry, IBLSApkRegistry _blsApkRegistry, - IIndexRegistry _indexRegistry + IIndexRegistry _indexRegistry, + ISocketRegistry _socketRegistry ) - RegistryCoordinatorStorage(_serviceManager, _stakeRegistry, _blsApkRegistry, _indexRegistry) + RegistryCoordinatorStorage(_serviceManager, _stakeRegistry, _blsApkRegistry, _indexRegistry, _socketRegistry) EIP712("AVSRegistryCoordinator", "v0.0.1") { _disableInitializers(); @@ -91,7 +88,7 @@ contract RegistryCoordinator is ) external initializer { require( _operatorSetParams.length == _minimumStakes.length && _minimumStakes.length == _strategyParams.length, - "RegistryCoordinator.initialize: input length mismatch" + "RegCoord.initialize: input length mismatch" ); // Initialize roles @@ -157,7 +154,7 @@ contract RegistryCoordinator is require( numOperatorsPerQuorum[i] <= _quorumParams[quorumNumber].maxOperatorCount, - "RegistryCoordinator.registerOperator: operator count exceeds maximum" + "RegCoord.registerOperator: operator count exceeds maximum" ); } } @@ -182,7 +179,7 @@ contract RegistryCoordinator is SignatureWithSaltAndExpiry memory churnApproverSignature, SignatureWithSaltAndExpiry memory operatorSignature ) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) { - require(operatorKickParams.length == quorumNumbers.length, "RegistryCoordinator.registerOperatorWithChurn: input length mismatch"); + require(operatorKickParams.length == quorumNumbers.length, "RegCoord.registerOperatorWithChurn: input length mismatch"); /** * If the operator has NEVER registered a pubkey before, use `params` to register @@ -292,7 +289,7 @@ contract RegistryCoordinator is uint192 quorumBitmap = uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount)); require( operatorsPerQuorum.length == quorumNumbers.length, - "RegistryCoordinator.updateOperatorsForQuorum: input length mismatch" + "RegCoord.updateOperatorsForQuorum: input length mismatch" ); // For each quorum, update ALL registered operators @@ -303,7 +300,7 @@ contract RegistryCoordinator is address[] calldata currQuorumOperators = operatorsPerQuorum[i]; require( currQuorumOperators.length == indexRegistry.totalOperatorsForQuorum(quorumNumber), - "RegistryCoordinator.updateOperatorsForQuorum: number of updated operators does not match quorum total" + "RegCoord.updateOperatorsForQuorum: number of updated operators does not match quorum total" ); address prevOperatorAddress = address(0); @@ -322,12 +319,12 @@ contract RegistryCoordinator is // Check that the operator is registered require( BitmapUtils.isSet(currentBitmap, quorumNumber), - "RegistryCoordinator.updateOperatorsForQuorum: operator not in quorum" + "RegCoord.updateOperatorsForQuorum: operator not in quorum" ); // Prevent duplicate operators require( operator > prevOperatorAddress, - "RegistryCoordinator.updateOperatorsForQuorum: operators array must be sorted in ascending address order" + "RegCoord.updateOperatorsForQuorum: operators array must be sorted in ascending address order" ); } @@ -347,8 +344,8 @@ contract RegistryCoordinator is * @param socket is the new socket of the operator */ function updateSocket(string memory socket) external { - require(_operatorInfo[msg.sender].status == OperatorStatus.REGISTERED, "RegistryCoordinator.updateSocket: operator is not registered"); - emit OperatorSocketUpdate(_operatorInfo[msg.sender].operatorId, socket); + require(_operatorInfo[msg.sender].status == OperatorStatus.REGISTERED, "RegCoord.updateSocket: operator not registered"); + _setOperatorSocket(_operatorInfo[msg.sender].operatorId, socket); } /******************************************************************************* @@ -476,12 +473,12 @@ contract RegistryCoordinator is */ uint192 quorumsToAdd = uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount)); uint192 currentBitmap = _currentOperatorBitmap(operatorId); - require(!quorumsToAdd.isEmpty(), "RegistryCoordinator._registerOperator: bitmap cannot be 0"); - require(quorumsToAdd.noBitsInCommon(currentBitmap), "RegistryCoordinator._registerOperator: operator already registered for some quorums being registered for"); + require(!quorumsToAdd.isEmpty(), "RegCoord._registerOperator: bitmap cannot be 0"); + require(quorumsToAdd.noBitsInCommon(currentBitmap), "RegCoord._registerOperator: operator already registered for some quorums"); uint192 newBitmap = uint192(currentBitmap.plus(quorumsToAdd)); // Check that the operator can reregister if ejected - require(lastEjectionTimestamp[operator] + ejectionCooldown < block.timestamp, "RegistryCoordinator._registerOperator: operator cannot reregister yet"); + require(lastEjectionTimestamp[operator] + ejectionCooldown < block.timestamp, "RegCoord._registerOperator: operator cannot reregister yet"); /** * Update operator's bitmap, socket, and status. Only update operatorInfo if needed: @@ -492,8 +489,6 @@ contract RegistryCoordinator is newBitmap: newBitmap }); - emit OperatorSocketUpdate(operatorId, socket); - // If the operator wasn't registered for any quorums, update their status // and register them with this AVS in EigenLayer core (DelegationManager) if (_operatorInfo[operator].status != OperatorStatus.REGISTERED) { @@ -505,6 +500,8 @@ contract RegistryCoordinator is // Register the operator with the EigenLayer core contracts via this AVS's ServiceManager serviceManager.registerOperatorToAVS(operator, operatorSignature); + _setOperatorSocket(operatorId, socket); + emit OperatorRegistered(operator, operatorId); } @@ -517,6 +514,26 @@ contract RegistryCoordinator is return results; } + /** + * @notice Checks if the caller is the ejector + * @dev Reverts if the caller is not the ejector + */ + function _checkEjector() internal view { + require(msg.sender == ejector, "RegCoord.onlyEjector: caller is not the ejector"); + } + + /** + * @notice Checks if a quorum exists + * @param quorumNumber The quorum number to check + * @dev Reverts if the quorum does not exist + */ + function _checkQuorumExists(uint8 quorumNumber) internal view { + require( + quorumNumber < quorumCount, + "RegCoord.quorumExists: quorum does not exist" + ); + } + /** * @notice Fetches an operator's pubkey hash from the BLSApkRegistry. If the * operator has not registered a pubkey, attempts to register a pubkey using @@ -564,18 +581,18 @@ contract RegistryCoordinator is ) internal view { address operatorToKick = kickParams.operator; bytes32 idToKick = _operatorInfo[operatorToKick].operatorId; - require(newOperator != operatorToKick, "RegistryCoordinator._validateChurn: cannot churn self"); - require(kickParams.quorumNumber == quorumNumber, "RegistryCoordinator._validateChurn: quorumNumber not the same as signed"); + require(newOperator != operatorToKick, "RegCoord._validateChurn: cannot churn self"); + require(kickParams.quorumNumber == quorumNumber, "RegCoord._validateChurn: quorumNumber not the same as signed"); // Get the target operator's stake and check that it is below the kick thresholds uint96 operatorToKickStake = stakeRegistry.getCurrentStake(idToKick, quorumNumber); require( newOperatorStake > _individualKickThreshold(operatorToKickStake, setParams), - "RegistryCoordinator._validateChurn: incoming operator has insufficient stake for churn" + "RegCoord._validateChurn: incoming operator has insufficient stake for churn" ); require( operatorToKickStake < _totalKickThreshold(totalQuorumStake, setParams), - "RegistryCoordinator._validateChurn: cannot kick operator with more than kickBIPsOfTotalStake" + "RegCoord._validateChurn: cannot kick operator with more than kickBIPsOfTotalStake" ); } @@ -591,7 +608,7 @@ contract RegistryCoordinator is // Fetch the operator's info and ensure they are registered OperatorInfo storage operatorInfo = _operatorInfo[operator]; bytes32 operatorId = operatorInfo.operatorId; - require(operatorInfo.status == OperatorStatus.REGISTERED, "RegistryCoordinator._deregisterOperator: operator is not registered"); + require(operatorInfo.status == OperatorStatus.REGISTERED, "RegCoord._deregisterOperator: operator is not registered"); /** * Get bitmap of quorums to deregister from and operator's current bitmap. Validate that: @@ -602,8 +619,8 @@ contract RegistryCoordinator is */ uint192 quorumsToRemove = uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount)); uint192 currentBitmap = _currentOperatorBitmap(operatorId); - require(!quorumsToRemove.isEmpty(), "RegistryCoordinator._deregisterOperator: bitmap cannot be 0"); - require(quorumsToRemove.isSubsetOf(currentBitmap), "RegistryCoordinator._deregisterOperator: operator is not registered for specified quorums"); + require(!quorumsToRemove.isEmpty(), "RegCoord._deregisterOperator: bitmap cannot be 0"); + require(quorumsToRemove.isSubsetOf(currentBitmap), "RegCoord._deregisterOperator: operator is not registered for quorums"); uint192 newBitmap = uint192(currentBitmap.minus(quorumsToRemove)); // Update operator's bitmap and status @@ -675,8 +692,8 @@ contract RegistryCoordinator is SignatureWithSaltAndExpiry memory churnApproverSignature ) internal { // make sure the salt hasn't been used already - require(!isChurnApproverSaltUsed[churnApproverSignature.salt], "RegistryCoordinator._verifyChurnApproverSignature: churnApprover salt already used"); - require(churnApproverSignature.expiry >= block.timestamp, "RegistryCoordinator._verifyChurnApproverSignature: churnApprover signature expired"); + require(!isChurnApproverSaltUsed[churnApproverSignature.salt], "RegCoord._verifyChurnApproverSignature: churnApprover salt already used"); + require(churnApproverSignature.expiry >= block.timestamp, "RegCoord._verifyChurnApproverSignature: churnApprover signature expired"); // set salt used to true isChurnApproverSaltUsed[churnApproverSignature.salt] = true; @@ -704,7 +721,7 @@ contract RegistryCoordinator is ) internal { // Increment the total quorum count. Fails if we're already at the max uint8 prevQuorumCount = quorumCount; - require(prevQuorumCount < MAX_QUORUM_COUNT, "RegistryCoordinator.createQuorum: max quorums reached"); + require(prevQuorumCount < MAX_QUORUM_COUNT, "RegCoord.createQuorum: max quorums reached"); quorumCount = prevQuorumCount + 1; // The previous count is the new quorum's number @@ -786,7 +803,7 @@ contract RegistryCoordinator is } revert( - "RegistryCoordinator.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operatorId at block number" + "RegCoord.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operator at blockNumber" ); } @@ -805,6 +822,11 @@ contract RegistryCoordinator is ejector = newEjector; } + function _setOperatorSocket(bytes32 operatorId, string memory socket) internal { + socketRegistry.setOperatorSocket(operatorId, socket); + emit OperatorSocketUpdate(operatorId, socket); + } + /******************************************************************************* VIEW FUNCTIONS *******************************************************************************/ @@ -870,11 +892,11 @@ contract RegistryCoordinator is */ require( blockNumber >= quorumBitmapUpdate.updateBlockNumber, - "RegistryCoordinator.getQuorumBitmapAtBlockNumberByIndex: quorumBitmapUpdate is from after blockNumber" + "RegCoord.getQuorumBitmapAtBlockNumberByIndex: quorumBitmapUpdate is from after blockNumber" ); require( quorumBitmapUpdate.nextUpdateBlockNumber == 0 || blockNumber < quorumBitmapUpdate.nextUpdateBlockNumber, - "RegistryCoordinator.getQuorumBitmapAtBlockNumberByIndex: quorumBitmapUpdate is from before blockNumber" + "RegCoord.getQuorumBitmapAtBlockNumberByIndex: quorumBitmapUpdate is from before blockNumber" ); return quorumBitmapUpdate.quorumBitmap; diff --git a/src/RegistryCoordinatorStorage.sol b/src/RegistryCoordinatorStorage.sol index 5451efb3..a8b4fced 100644 --- a/src/RegistryCoordinatorStorage.sol +++ b/src/RegistryCoordinatorStorage.sol @@ -6,6 +6,7 @@ import {IStakeRegistry} from "./interfaces/IStakeRegistry.sol"; import {IIndexRegistry} from "./interfaces/IIndexRegistry.sol"; import {IServiceManager} from "./interfaces/IServiceManager.sol"; import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol"; +import {ISocketRegistry} from "./interfaces/ISocketRegistry.sol"; abstract contract RegistryCoordinatorStorage is IRegistryCoordinator { @@ -39,6 +40,8 @@ abstract contract RegistryCoordinatorStorage is IRegistryCoordinator { IStakeRegistry public immutable stakeRegistry; /// @notice the Index Registry contract that will keep track of operators' indexes IIndexRegistry public immutable indexRegistry; + /// @notice the Socket Registry contract that will keep track of operators' sockets + ISocketRegistry public immutable socketRegistry; /******************************************************************************* STATE @@ -73,12 +76,14 @@ abstract contract RegistryCoordinatorStorage is IRegistryCoordinator { IServiceManager _serviceManager, IStakeRegistry _stakeRegistry, IBLSApkRegistry _blsApkRegistry, - IIndexRegistry _indexRegistry + IIndexRegistry _indexRegistry, + ISocketRegistry _socketRegistry ) { serviceManager = _serviceManager; stakeRegistry = _stakeRegistry; blsApkRegistry = _blsApkRegistry; indexRegistry = _indexRegistry; + socketRegistry = _socketRegistry; } // storage gap for upgradeability diff --git a/src/ServiceManagerBase.sol b/src/ServiceManagerBase.sol index a2874ac0..76a3f6ad 100644 --- a/src/ServiceManagerBase.sol +++ b/src/ServiceManagerBase.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.12; -import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; import {Initializable} from "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol"; import {IAVSDirectory} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; @@ -18,7 +17,7 @@ import {BitmapUtils} from "./libraries/BitmapUtils.sol"; * This contract can be inherited from or simply used as a point-of-reference. * @author Layr Labs, Inc. */ -abstract contract ServiceManagerBase is OwnableUpgradeable, ServiceManagerBaseStorage { +abstract contract ServiceManagerBase is ServiceManagerBaseStorage { using BitmapUtils for *; /// @notice when applied to a function, only allows the RegistryCoordinator to call it @@ -32,11 +31,15 @@ abstract contract ServiceManagerBase is OwnableUpgradeable, ServiceManagerBaseSt /// @notice only rewardsInitiator can call createAVSRewardsSubmission modifier onlyRewardsInitiator() { + _checkRewardsInitiator(); + _; + } + + function _checkRewardsInitiator() internal view { require( msg.sender == rewardsInitiator, "ServiceManagerBase.onlyRewardsInitiator: caller is not the rewards initiator" ); - _; } /// @notice Sets the (immutable) `_registryCoordinator` address @@ -69,7 +72,9 @@ abstract contract ServiceManagerBase is OwnableUpgradeable, ServiceManagerBaseSt * @param _metadataURI is the metadata URI for the AVS * @dev only callable by the owner */ - function updateAVSMetadataURI(string memory _metadataURI) public virtual onlyOwner { + function updateAVSMetadataURI( + string memory _metadataURI + ) public virtual onlyOwner { _avsDirectory.updateAVSMetadataURI(_metadataURI); } @@ -77,32 +82,106 @@ abstract contract ServiceManagerBase is OwnableUpgradeable, ServiceManagerBaseSt * @notice Creates a new rewards submission to the EigenLayer RewardsCoordinator contract, to be split amongst the * set of stakers delegated to operators who are registered to this `avs` * @param rewardsSubmissions The rewards submissions being created - * @dev Only callabe by the permissioned rewardsInitiator address + * @dev Only callable by the permissioned rewardsInitiator address * @dev The duration of the `rewardsSubmission` cannot exceed `MAX_REWARDS_DURATION` * @dev The tokens are sent to the `RewardsCoordinator` contract * @dev Strategies must be in ascending order of addresses to check for duplicates * @dev This function will revert if the `rewardsSubmission` is malformed, * e.g. if the `strategies` and `weights` arrays are of non-equal lengths + * @dev This function may fail to execute with a large number of submissions due to gas limits. Use a + * smaller array of submissions if necessary. */ - function createAVSRewardsSubmission(IRewardsCoordinator.RewardsSubmission[] calldata rewardsSubmissions) - public - virtual - onlyRewardsInitiator - { + function createAVSRewardsSubmission( + IRewardsCoordinator.RewardsSubmission[] calldata rewardsSubmissions + ) public virtual onlyRewardsInitiator { for (uint256 i = 0; i < rewardsSubmissions.length; ++i) { // transfer token to ServiceManager and approve RewardsCoordinator to transfer again // in createAVSRewardsSubmission() call - rewardsSubmissions[i].token.transferFrom(msg.sender, address(this), rewardsSubmissions[i].amount); - uint256 allowance = - rewardsSubmissions[i].token.allowance(address(this), address(_rewardsCoordinator)); + rewardsSubmissions[i].token.transferFrom( + msg.sender, + address(this), + rewardsSubmissions[i].amount + ); + uint256 allowance = rewardsSubmissions[i].token.allowance( + address(this), + address(_rewardsCoordinator) + ); rewardsSubmissions[i].token.approve( - address(_rewardsCoordinator), rewardsSubmissions[i].amount + allowance + address(_rewardsCoordinator), + rewardsSubmissions[i].amount + allowance ); } _rewardsCoordinator.createAVSRewardsSubmission(rewardsSubmissions); } + /** + * @notice Creates a new operator-directed rewards submission, to be split amongst the operators and + * set of stakers delegated to operators who are registered to this `avs`. + * @param operatorDirectedRewardsSubmissions The operator-directed rewards submissions being created. + * @dev Only callable by the permissioned rewardsInitiator address + * @dev The duration of the `rewardsSubmission` cannot exceed `MAX_REWARDS_DURATION` + * @dev The tokens are sent to the `RewardsCoordinator` contract + * @dev This contract needs a token approval of sum of all `operatorRewards` in the `operatorDirectedRewardsSubmissions`, before calling this function. + * @dev Strategies must be in ascending order of addresses to check for duplicates + * @dev Operators must be in ascending order of addresses to check for duplicates. + * @dev This function will revert if the `operatorDirectedRewardsSubmissions` is malformed. + * @dev This function may fail to execute with a large number of submissions due to gas limits. Use a + * smaller array of submissions if necessary. + */ + function createOperatorDirectedAVSRewardsSubmission( + IRewardsCoordinator.OperatorDirectedRewardsSubmission[] + calldata operatorDirectedRewardsSubmissions + ) public virtual onlyRewardsInitiator { + for ( + uint256 i = 0; + i < operatorDirectedRewardsSubmissions.length; + ++i + ) { + // Calculate total amount of token to transfer + uint256 totalAmount = 0; + for ( + uint256 j = 0; + j < + operatorDirectedRewardsSubmissions[i].operatorRewards.length; + ++j + ) { + totalAmount += operatorDirectedRewardsSubmissions[i] + .operatorRewards[j] + .amount; + } + + // Transfer token to ServiceManager and approve RewardsCoordinator to transfer again + // in createAVSPerformanceRewardsSubmission() call + operatorDirectedRewardsSubmissions[i].token.transferFrom( + msg.sender, + address(this), + totalAmount + ); + uint256 allowance = operatorDirectedRewardsSubmissions[i] + .token + .allowance(address(this), address(_rewardsCoordinator)); + operatorDirectedRewardsSubmissions[i].token.approve( + address(_rewardsCoordinator), + totalAmount + allowance + ); + } + + _rewardsCoordinator.createOperatorDirectedAVSRewardsSubmission( + address(this), + operatorDirectedRewardsSubmissions + ); + } + + /** + * @notice Forwards a call to Eigenlayer's RewardsCoordinator contract to set the address of the entity that can call `processClaim` on behalf of this contract. + * @param claimer The address of the entity that can call `processClaim` on behalf of the earner + * @dev Only callable by the owner. + */ + function setClaimerFor(address claimer) public virtual onlyOwner { + _rewardsCoordinator.setClaimerFor(claimer); + } + /** * @notice Forwards a call to EigenLayer's AVSDirectory contract to confirm operator registration with the AVS * @param operator The address of the operator to register. @@ -119,7 +198,9 @@ abstract contract ServiceManagerBase is OwnableUpgradeable, ServiceManagerBaseSt * @notice Forwards a call to EigenLayer's AVSDirectory contract to confirm operator deregistration from the AVS * @param operator The address of the operator to deregister. */ - function deregisterOperatorFromAVS(address operator) public virtual onlyRegistryCoordinator { + function deregisterOperatorFromAVS( + address operator + ) public virtual onlyRegistryCoordinator { _avsDirectory.deregisterOperatorFromAVS(operator); } @@ -128,7 +209,9 @@ abstract contract ServiceManagerBase is OwnableUpgradeable, ServiceManagerBaseSt * @param newRewardsInitiator The new rewards initiator address * @dev only callable by the owner */ - function setRewardsInitiator(address newRewardsInitiator) external onlyOwner { + function setRewardsInitiator( + address newRewardsInitiator + ) external onlyOwner { _setRewardsInitiator(newRewardsInitiator); } @@ -143,7 +226,12 @@ abstract contract ServiceManagerBase is OwnableUpgradeable, ServiceManagerBaseSt * @dev No guarantee is made on uniqueness of each element in the returned array. * The off-chain service should do that validation separately */ - function getRestakeableStrategies() external view returns (address[] memory) { + function getRestakeableStrategies() + external + view + virtual + returns (address[] memory) + { uint256 quorumCount = _registryCoordinator.quorumCount(); if (quorumCount == 0) { @@ -158,10 +246,13 @@ abstract contract ServiceManagerBase is OwnableUpgradeable, ServiceManagerBaseSt address[] memory restakedStrategies = new address[](strategyCount); uint256 index = 0; for (uint256 i = 0; i < _registryCoordinator.quorumCount(); i++) { - uint256 strategyParamsLength = _stakeRegistry.strategyParamsLength(uint8(i)); + uint256 strategyParamsLength = _stakeRegistry.strategyParamsLength( + uint8(i) + ); for (uint256 j = 0; j < strategyParamsLength; j++) { - restakedStrategies[index] = - address(_stakeRegistry.strategyParamsByIndex(uint8(i), j).strategy); + restakedStrategies[index] = address( + _stakeRegistry.strategyParamsByIndex(uint8(i), j).strategy + ); index++; } } @@ -175,23 +266,27 @@ abstract contract ServiceManagerBase is OwnableUpgradeable, ServiceManagerBaseSt * @dev No guarantee is made on whether the operator has shares for a strategy in a quorum or uniqueness * of each element in the returned array. The off-chain service should do that validation separately */ - function getOperatorRestakedStrategies(address operator) - external - view - returns (address[] memory) - { + function getOperatorRestakedStrategies( + address operator + ) external view virtual returns (address[] memory) { bytes32 operatorId = _registryCoordinator.getOperatorId(operator); - uint192 operatorBitmap = _registryCoordinator.getCurrentQuorumBitmap(operatorId); + uint192 operatorBitmap = _registryCoordinator.getCurrentQuorumBitmap( + operatorId + ); if (operatorBitmap == 0 || _registryCoordinator.quorumCount() == 0) { return new address[](0); } // Get number of strategies for each quorum in operator bitmap - bytes memory operatorRestakedQuorums = BitmapUtils.bitmapToBytesArray(operatorBitmap); + bytes memory operatorRestakedQuorums = BitmapUtils.bitmapToBytesArray( + operatorBitmap + ); uint256 strategyCount; for (uint256 i = 0; i < operatorRestakedQuorums.length; i++) { - strategyCount += _stakeRegistry.strategyParamsLength(uint8(operatorRestakedQuorums[i])); + strategyCount += _stakeRegistry.strategyParamsLength( + uint8(operatorRestakedQuorums[i]) + ); } // Get strategies for each quorum in operator bitmap @@ -199,10 +294,13 @@ abstract contract ServiceManagerBase is OwnableUpgradeable, ServiceManagerBaseSt uint256 index = 0; for (uint256 i = 0; i < operatorRestakedQuorums.length; i++) { uint8 quorum = uint8(operatorRestakedQuorums[i]); - uint256 strategyParamsLength = _stakeRegistry.strategyParamsLength(quorum); + uint256 strategyParamsLength = _stakeRegistry.strategyParamsLength( + quorum + ); for (uint256 j = 0; j < strategyParamsLength; j++) { - restakedStrategies[index] = - address(_stakeRegistry.strategyParamsByIndex(quorum, j).strategy); + restakedStrategies[index] = address( + _stakeRegistry.strategyParamsByIndex(quorum, j).strategy + ); index++; } } diff --git a/src/ServiceManagerBaseStorage.sol b/src/ServiceManagerBaseStorage.sol index 4f5360eb..e0c1d86a 100644 --- a/src/ServiceManagerBaseStorage.sol +++ b/src/ServiceManagerBaseStorage.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.12; +import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; + import {IServiceManager} from "./interfaces/IServiceManager.sol"; import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol"; import {IStakeRegistry} from "./interfaces/IStakeRegistry.sol"; @@ -13,7 +15,7 @@ import {IRewardsCoordinator} from "eigenlayer-contracts/src/contracts/interfaces * @author Layr Labs, Inc. * @notice This storage contract is separate from the logic to simplify the upgrade process. */ -abstract contract ServiceManagerBaseStorage is IServiceManager { +abstract contract ServiceManagerBaseStorage is IServiceManager, OwnableUpgradeable { /** * * CONSTANTS AND IMMUTABLES diff --git a/src/SocketRegistry.sol b/src/SocketRegistry.sol new file mode 100644 index 00000000..8471553a --- /dev/null +++ b/src/SocketRegistry.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.12; + +import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol"; +import {ISocketRegistry} from "./interfaces/ISocketRegistry.sol"; + +/** + * @title A `Registry` that keeps track of operator sockets. + * @author Layr Labs, Inc. + */ +contract SocketRegistry is ISocketRegistry { + + /// @notice The address of the RegistryCoordinator + address public immutable registryCoordinator; + + /// @notice A mapping from operator IDs to their sockets + mapping(bytes32 => string) public operatorIdToSocket; + + /// @notice A modifier that only allows the RegistryCoordinator to call a function + modifier onlyRegistryCoordinator() { + require(msg.sender == address(registryCoordinator), "SocketRegistry.onlyRegistryCoordinator: caller is not the RegistryCoordinator"); + _; + } + + /// @notice A modifier that only allows the owner of the RegistryCoordinator to call a function + modifier onlyCoordinatorOwner() { + require(msg.sender == IRegistryCoordinator(registryCoordinator).owner(), "SocketRegistry.onlyCoordinatorOwner: caller is not the owner of the registryCoordinator"); + _; + } + + constructor(IRegistryCoordinator _registryCoordinator) { + registryCoordinator = address(_registryCoordinator); + } + + /// @notice sets the socket for an operator only callable by the RegistryCoordinator + function setOperatorSocket(bytes32 _operatorId, string memory _socket) external onlyRegistryCoordinator { + operatorIdToSocket[_operatorId] = _socket; + } + + /// @notice migrates the sockets for a list of operators only callable by the owner of the RegistryCoordinator + function migrateOperatorSockets(bytes32[] memory _operatorIds, string[] memory _sockets) external onlyCoordinatorOwner { + for (uint256 i = 0; i < _operatorIds.length; i++) { + operatorIdToSocket[_operatorIds[i]] = _sockets[i]; + } + } + + /// @notice gets the stored socket for an operator + function getOperatorSocket(bytes32 _operatorId) external view returns (string memory) { + return operatorIdToSocket[_operatorId]; + } + +} diff --git a/src/StakeRegistry.sol b/src/StakeRegistry.sol index 87e04fdf..929b67d9 100644 --- a/src/StakeRegistry.sol +++ b/src/StakeRegistry.sol @@ -24,20 +24,17 @@ contract StakeRegistry is StakeRegistryStorage { using BitmapUtils for *; modifier onlyRegistryCoordinator() { - require( - msg.sender == address(registryCoordinator), - "StakeRegistry.onlyRegistryCoordinator: caller is not the RegistryCoordinator" - ); + _checkRegistryCoordinator(); _; } modifier onlyCoordinatorOwner() { - require(msg.sender == IRegistryCoordinator(registryCoordinator).owner(), "StakeRegistry.onlyCoordinatorOwner: caller is not the owner of the registryCoordinator"); + _checkRegistryCoordinatorOwner(); _; } modifier quorumExists(uint8 quorumNumber) { - require(_quorumExists(quorumNumber), "StakeRegistry.quorumExists: quorum does not exist"); + _checkQuorumExists(quorumNumber); _; } @@ -74,7 +71,7 @@ contract StakeRegistry is StakeRegistryStorage { for (uint256 i = 0; i < quorumNumbers.length; i++) { uint8 quorumNumber = uint8(quorumNumbers[i]); - require(_quorumExists(quorumNumber), "StakeRegistry.registerOperator: quorum does not exist"); + _checkQuorumExists(quorumNumber); // Retrieve the operator's current weighted stake for the quorum, reverting if they have not met // the minimum. @@ -121,7 +118,7 @@ contract StakeRegistry is StakeRegistryStorage { */ for (uint256 i = 0; i < quorumNumbers.length; i++) { uint8 quorumNumber = uint8(quorumNumbers[i]); - require(_quorumExists(quorumNumber), "StakeRegistry.deregisterOperator: quorum does not exist"); + _checkQuorumExists(quorumNumber); // Update the operator's stake for the quorum and retrieve the shares removed int256 stakeDelta = _recordOperatorStakeUpdate({ @@ -161,7 +158,7 @@ contract StakeRegistry is StakeRegistryStorage { */ for (uint256 i = 0; i < quorumNumbers.length; i++) { uint8 quorumNumber = uint8(quorumNumbers[i]); - require(_quorumExists(quorumNumber), "StakeRegistry.updateOperatorStake: quorum does not exist"); + _checkQuorumExists(quorumNumber); // Fetch the operator's current stake, applying weighting parameters and checking // against the minimum stake requirements for the quorum. @@ -697,7 +694,7 @@ contract StakeRegistry is StakeRegistryStorage { uint32[] memory indices = new uint32[](quorumNumbers.length); for (uint256 i = 0; i < quorumNumbers.length; i++) { uint8 quorumNumber = uint8(quorumNumbers[i]); - require(_quorumExists(quorumNumber), "StakeRegistry.getTotalStakeIndicesAtBlockNumber: quorum does not exist"); + _checkQuorumExists(quorumNumber); require( _totalStakeHistory[quorumNumber][0].updateBlockNumber <= blockNumber, "StakeRegistry.getTotalStakeIndicesAtBlockNumber: quorum has no stake history at blockNumber" @@ -712,4 +709,19 @@ contract StakeRegistry is StakeRegistryStorage { } return indices; } + + function _checkRegistryCoordinator() internal view { + require( + msg.sender == address(registryCoordinator), + "StakeRegistry.onlyRegistryCoordinator: caller is not the RegistryCoordinator" + ); + } + + function _checkRegistryCoordinatorOwner() internal view { + require(msg.sender == IRegistryCoordinator(registryCoordinator).owner(), "StakeRegistry.onlyCoordinatorOwner: caller is not the owner of the registryCoordinator"); + } + + function _checkQuorumExists(uint8 quorumNumber) internal view { + require(_quorumExists(quorumNumber), "StakeRegistry.quorumExists: quorum does not exist"); + } } diff --git a/src/interfaces/IEjectionManager.sol b/src/interfaces/IEjectionManager.sol index 9a02991e..6649e75b 100644 --- a/src/interfaces/IEjectionManager.sol +++ b/src/interfaces/IEjectionManager.sol @@ -52,4 +52,4 @@ interface IEjectionManager { * @param _quorumNumber The quorum number to view ejectable stake for */ function amountEjectableForQuorum(uint8 _quorumNumber) external view returns (uint256); -} +} \ No newline at end of file diff --git a/src/interfaces/IRegistryCoordinator.sol b/src/interfaces/IRegistryCoordinator.sol index 43fa7e0a..631ca3a3 100644 --- a/src/interfaces/IRegistryCoordinator.sol +++ b/src/interfaces/IRegistryCoordinator.sol @@ -25,6 +25,8 @@ interface IRegistryCoordinator { event EjectorUpdated(address prevEjector, address newEjector); + event OperatorSocketUpdate(bytes32 indexed operatorId, string socket); + /// @notice emitted when all the operators for a quorum are updated at once event QuorumBlockNumberUpdated(uint8 indexed quorumNumber, uint256 blocknumber); @@ -150,4 +152,10 @@ interface IRegistryCoordinator { /// @notice The owner of the registry coordinator function owner() external view returns (address); + + /** + * @notice Updates the socket of the msg.sender given they are a registered operator + * @param socket is the new socket of the operator + */ + function updateSocket(string memory socket) external; } diff --git a/src/interfaces/IServiceManager.sol b/src/interfaces/IServiceManager.sol index ad953ec0..c8f49166 100644 --- a/src/interfaces/IServiceManager.sol +++ b/src/interfaces/IServiceManager.sol @@ -13,15 +13,44 @@ interface IServiceManager is IServiceManagerUI { * @notice Creates a new rewards submission to the EigenLayer RewardsCoordinator contract, to be split amongst the * set of stakers delegated to operators who are registered to this `avs` * @param rewardsSubmissions The rewards submissions being created - * @dev Only callabe by the permissioned rewardsInitiator address + * @dev Only callable by the permissioned rewardsInitiator address * @dev The duration of the `rewardsSubmission` cannot exceed `MAX_REWARDS_DURATION` * @dev The tokens are sent to the `RewardsCoordinator` contract * @dev Strategies must be in ascending order of addresses to check for duplicates * @dev This function will revert if the `rewardsSubmission` is malformed, * e.g. if the `strategies` and `weights` arrays are of non-equal lengths */ - function createAVSRewardsSubmission(IRewardsCoordinator.RewardsSubmission[] calldata rewardsSubmissions) external; + function createAVSRewardsSubmission( + IRewardsCoordinator.RewardsSubmission[] calldata rewardsSubmissions + ) external; + + /** + * @notice Creates a new operator-directed rewards submission on behalf of an AVS, to be split amongst the operators and + * set of stakers delegated to operators who are registered to the `avs`. + * @param operatorDirectedRewardsSubmissions The operator-directed rewards submissions being created + * @dev Only callable by the permissioned rewardsInitiator address + * @dev The duration of the `rewardsSubmission` cannot exceed `MAX_REWARDS_DURATION` + * @dev The tokens are sent to the `RewardsCoordinator` contract + * @dev This contract needs a token approval of sum of all `operatorRewards` in the `operatorDirectedRewardsSubmissions`, before calling this function. + * @dev Strategies must be in ascending order of addresses to check for duplicates + * @dev Operators must be in ascending order of addresses to check for duplicates. + * @dev This function will revert if the `operatorDirectedRewardsSubmissions` is malformed. + */ + function createOperatorDirectedAVSRewardsSubmission( + IRewardsCoordinator.OperatorDirectedRewardsSubmission[] + calldata operatorDirectedRewardsSubmissions + ) external; + + /** + * @notice Forwards a call to Eigenlayer's RewardsCoordinator contract to set the address of the entity that can call `processClaim` on behalf of this contract. + * @param claimer The address of the entity that can call `processClaim` on behalf of the earner + * @dev Only callable by the owner. + */ + function setClaimerFor(address claimer) external; // EVENTS - event RewardsInitiatorUpdated(address prevRewardsInitiator, address newRewardsInitiator); + event RewardsInitiatorUpdated( + address prevRewardsInitiator, + address newRewardsInitiator + ); } diff --git a/src/interfaces/IServiceManagerUI.sol b/src/interfaces/IServiceManagerUI.sol index 9f06766c..92cdce9c 100644 --- a/src/interfaces/IServiceManagerUI.sol +++ b/src/interfaces/IServiceManagerUI.sol @@ -2,7 +2,6 @@ pragma solidity >=0.5.0; import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol"; -import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; /** * @title Minimal interface for a ServiceManager-type contract that AVS ServiceManager contracts must implement @@ -25,7 +24,7 @@ interface IServiceManagerUI { function updateAVSMetadataURI(string memory _metadataURI) external; /** - * @notice Forwards a call to EigenLayer's DelegationManager contract to confirm operator registration with the AVS + * @notice Forwards a call to EigenLayer's AVSDirectory contract to confirm operator registration with the AVS * @param operator The address of the operator to register. * @param operatorSignature The signature, salt, and expiry of the operator's signature. */ @@ -35,7 +34,7 @@ interface IServiceManagerUI { ) external; /** - * @notice Forwards a call to EigenLayer's DelegationManager contract to confirm operator deregistration from the AVS + * @notice Forwards a call to EigenLayer's AVSDirectory contract to confirm operator deregistration from the AVS * @param operator The address of the operator to deregister. */ function deregisterOperatorFromAVS(address operator) external; diff --git a/src/interfaces/ISocketRegistry.sol b/src/interfaces/ISocketRegistry.sol new file mode 100644 index 00000000..136a345b --- /dev/null +++ b/src/interfaces/ISocketRegistry.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface ISocketRegistry { + /// @notice sets the socket for an operator only callable by the RegistryCoordinator + function setOperatorSocket(bytes32 _operatorId, string memory _socket) external; + + /// @notice gets the stored socket for an operator + function getOperatorSocket(bytes32 _operatorId) external view returns (string memory); +} diff --git a/src/interfaces/ISocketUpdater.sol b/src/interfaces/ISocketUpdater.sol deleted file mode 100644 index dcf5a865..00000000 --- a/src/interfaces/ISocketUpdater.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.12; - -/** - * @title Interface for an `ISocketUpdater` where operators can update their sockets. - * @author Layr Labs, Inc. - */ -interface ISocketUpdater { - // EVENTS - - event OperatorSocketUpdate(bytes32 indexed operatorId, string socket); - - // FUNCTIONS - - /** - * @notice Updates the socket of the msg.sender given they are a registered operator - * @param socket is the new socket of the operator - */ - function updateSocket(string memory socket) external; -} diff --git a/src/unaudited/ECDSAServiceManagerBase.sol b/src/unaudited/ECDSAServiceManagerBase.sol index 23a6dc06..6ad640b4 100644 --- a/src/unaudited/ECDSAServiceManagerBase.sol +++ b/src/unaudited/ECDSAServiceManagerBase.sol @@ -2,10 +2,8 @@ pragma solidity ^0.8.12; import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; - import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol"; import {IAVSDirectory} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; - import {IServiceManager} from "../interfaces/IServiceManager.sol"; import {IServiceManagerUI} from "../interfaces/IServiceManagerUI.sol"; import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; @@ -30,6 +28,10 @@ abstract contract ECDSAServiceManagerBase is /// @notice Address of the delegation manager contract, which manages staker delegations to operators. address internal immutable delegationManager; + + /// @notice Address of the rewards initiator, which is allowed to create AVS rewards submissions. + address public rewardsInitiator; + /** * @dev Ensures that the function is only callable by the `stakeRegistry` contract. * This is used to restrict certain registration and deregistration functionality to the `stakeRegistry` @@ -42,6 +44,21 @@ abstract contract ECDSAServiceManagerBase is _; } + /** + * @dev Ensures that the function is only callable by the `rewardsInitiator`. + */ + modifier onlyRewardsInitiator() { + _checkRewardsInitiator(); + _; + } + + function _checkRewardsInitiator() internal view { + require( + msg.sender == rewardsInitiator, + "ECDSAServiceManagerBase.onlyRewardsInitiator: caller is not the rewards initiator" + ); + } + /** * @dev Constructor for ECDSAServiceManagerBase, initializing immutable contract addresses and disabling initializers. * @param _avsDirectory The address of the AVS directory contract, managing AVS-related data for registered operators. @@ -65,11 +82,14 @@ abstract contract ECDSAServiceManagerBase is /** * @dev Initializes the base service manager by transferring ownership to the initial owner. * @param initialOwner The address to which the ownership of the contract will be transferred. + * @param _rewardsInitiator The address which is allowed to create AVS rewards submissions. */ function __ServiceManagerBase_init( - address initialOwner + address initialOwner, + address _rewardsInitiator ) internal virtual onlyInitializing { _transferOwnership(initialOwner); + _setRewardsInitiator(_rewardsInitiator); } /// @inheritdoc IServiceManagerUI @@ -82,10 +102,25 @@ abstract contract ECDSAServiceManagerBase is /// @inheritdoc IServiceManager function createAVSRewardsSubmission( IRewardsCoordinator.RewardsSubmission[] calldata rewardsSubmissions - ) external virtual onlyOwner { + ) external virtual onlyRewardsInitiator { _createAVSRewardsSubmission(rewardsSubmissions); } + /// @inheritdoc IServiceManager + function createOperatorDirectedAVSRewardsSubmission( + IRewardsCoordinator.OperatorDirectedRewardsSubmission[] + calldata operatorDirectedRewardsSubmissions + ) external virtual onlyRewardsInitiator { + _createOperatorDirectedAVSRewardsSubmission( + operatorDirectedRewardsSubmissions + ); + } + + /// @inheritdoc IServiceManager + function setClaimerFor(address claimer) external virtual onlyOwner { + _setClaimerFor(claimer); + } + /// @inheritdoc IServiceManagerUI function registerOperatorToAVS( address operator, @@ -168,13 +203,77 @@ abstract contract ECDSAServiceManagerBase is address(this), rewardsSubmissions[i].amount ); + uint256 allowance = rewardsSubmissions[i].token.allowance( + address(this), + rewardsCoordinator + ); rewardsSubmissions[i].token.approve( rewardsCoordinator, - rewardsSubmissions[i].amount + rewardsSubmissions[i].amount + allowance ); } - IRewardsCoordinator(rewardsCoordinator).createAVSRewardsSubmission(rewardsSubmissions); + IRewardsCoordinator(rewardsCoordinator).createAVSRewardsSubmission( + rewardsSubmissions + ); + } + + /** + * @notice Creates a new operator-directed rewards submission, to be split amongst the operators and + * set of stakers delegated to operators who are registered to this `avs`. + * @param operatorDirectedRewardsSubmissions The operator-directed rewards submissions being created. + */ + function _createOperatorDirectedAVSRewardsSubmission( + IRewardsCoordinator.OperatorDirectedRewardsSubmission[] + calldata operatorDirectedRewardsSubmissions + ) internal virtual { + for ( + uint256 i = 0; + i < operatorDirectedRewardsSubmissions.length; + ++i + ) { + // Calculate total amount of token to transfer + uint256 totalAmount = 0; + for ( + uint256 j = 0; + j < + operatorDirectedRewardsSubmissions[i].operatorRewards.length; + ++j + ) { + totalAmount += operatorDirectedRewardsSubmissions[i] + .operatorRewards[j] + .amount; + } + + // Transfer token to ServiceManager and approve RewardsCoordinator to transfer again + // in createOperatorDirectedAVSRewardsSubmission() call + operatorDirectedRewardsSubmissions[i].token.transferFrom( + msg.sender, + address(this), + totalAmount + ); + uint256 allowance = operatorDirectedRewardsSubmissions[i] + .token + .allowance(address(this), rewardsCoordinator); + operatorDirectedRewardsSubmissions[i].token.approve( + rewardsCoordinator, + totalAmount + allowance + ); + } + + IRewardsCoordinator(rewardsCoordinator) + .createOperatorDirectedAVSRewardsSubmission( + address(this), + operatorDirectedRewardsSubmissions + ); + } + + /** + * @notice Forwards a call to Eigenlayer's RewardsCoordinator contract to set the address of the entity that can call `processClaim` on behalf of this contract. + * @param claimer The address of the entity that can call `processClaim` on behalf of the earner. + */ + function _setClaimerFor(address claimer) internal virtual { + IRewardsCoordinator(rewardsCoordinator).setClaimerFor(claimer); } /** @@ -215,7 +314,6 @@ abstract contract ECDSAServiceManagerBase is uint256[] memory shares = IDelegationManager(delegationManager) .getOperatorShares(_operator, strategies); - address[] memory activeStrategies = new address[](count); uint256 activeCount; for (uint256 i; i < count; i++) { if (shares[i] > 0) { @@ -225,16 +323,34 @@ abstract contract ECDSAServiceManagerBase is // Resize the array to fit only the active strategies address[] memory restakedStrategies = new address[](activeCount); + uint256 index; for (uint256 j = 0; j < count; j++) { if (shares[j] > 0) { - restakedStrategies[j] = activeStrategies[j]; + restakedStrategies[index] = address(strategies[j]); + index++; } } return restakedStrategies; } + /** + * @notice Sets the rewards initiator address. + * @param newRewardsInitiator The new rewards initiator address. + * @dev Only callable by the owner. + */ + function setRewardsInitiator( + address newRewardsInitiator + ) external onlyOwner { + _setRewardsInitiator(newRewardsInitiator); + } + + function _setRewardsInitiator(address newRewardsInitiator) internal { + emit RewardsInitiatorUpdated(rewardsInitiator, newRewardsInitiator); + rewardsInitiator = newRewardsInitiator; + } + // storage gap for upgradeability // slither-disable-next-line shadowing-state - uint256[50] private __GAP; + uint256[49] private __GAP; } diff --git a/test/events/IServiceManagerBaseEvents.sol b/test/events/IServiceManagerBaseEvents.sol index 6defff0d..4ae9b92e 100644 --- a/test/events/IServiceManagerBaseEvents.sol +++ b/test/events/IServiceManagerBaseEvents.sol @@ -1,10 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.12; -import { - IRewardsCoordinator, - IERC20 -} from "eigenlayer-contracts/src/contracts/interfaces/IRewardsCoordinator.sol"; +import {IRewardsCoordinator, IERC20} from "eigenlayer-contracts/src/contracts/interfaces/IRewardsCoordinator.sol"; interface IServiceManagerBaseEvents { /// RewardsCoordinator EVENTS /// @@ -24,15 +21,28 @@ interface IServiceManagerBaseEvents { IRewardsCoordinator.RewardsSubmission rewardsSubmission ); /// @notice rewardsUpdater is responsible for submiting DistributionRoots, only owner can set rewardsUpdater - event RewardsUpdaterSet(address indexed oldRewardsUpdater, address indexed newRewardsUpdater); + event RewardsUpdaterSet( + address indexed oldRewardsUpdater, + address indexed newRewardsUpdater + ); event RewardsForAllSubmitterSet( address indexed rewardsForAllSubmitter, bool indexed oldValue, bool indexed newValue ); - event ActivationDelaySet(uint32 oldActivationDelay, uint32 newActivationDelay); - event GlobalCommissionBipsSet(uint16 oldGlobalCommissionBips, uint16 newGlobalCommissionBips); - event ClaimerForSet(address indexed earner, address indexed oldClaimer, address indexed claimer); + event ActivationDelaySet( + uint32 oldActivationDelay, + uint32 newActivationDelay + ); + event DefaultOperatorSplitBipsSet( + uint16 oldDefaultOperatorSplitBips, + uint16 newDefaultOperatorSplitBips + ); + event ClaimerForSet( + address indexed earner, + address indexed oldClaimer, + address indexed claimer + ); /// @notice rootIndex is the specific array index of the newly created root in the storage array event DistributionRootSubmitted( uint32 indexed rootIndex, @@ -49,8 +59,53 @@ interface IServiceManagerBaseEvents { IERC20 token, uint256 claimedAmount ); - - + /** + * @notice Emitted when an AVS creates a valid `OperatorDirectedRewardsSubmission` + * @param caller The address calling `createOperatorDirectedAVSRewardsSubmission`. + * @param avs The avs on behalf of which the operator-directed rewards are being submitted. + * @param operatorDirectedRewardsSubmissionHash Keccak256 hash of (`avs`, `submissionNonce` and `operatorDirectedRewardsSubmission`). + * @param submissionNonce Current nonce of the avs. Used to generate a unique submission hash. + * @param operatorDirectedRewardsSubmission The Operator-Directed Rewards Submission. Contains the token, start timestamp, duration, operator rewards, description and, strategy and multipliers. + */ + event OperatorDirectedAVSRewardsSubmissionCreated( + address indexed caller, + address indexed avs, + bytes32 indexed operatorDirectedRewardsSubmissionHash, + uint256 submissionNonce, + IRewardsCoordinator.OperatorDirectedRewardsSubmission operatorDirectedRewardsSubmission + ); + /** + * @notice Emitted when the operator split for an AVS is set. + * @param caller The address calling `setOperatorAVSSplit`. + * @param operator The operator on behalf of which the split is being set. + * @param avs The avs for which the split is being set by the operator. + * @param activatedAt The timestamp at which the split will be activated. + * @param oldOperatorAVSSplitBips The old split for the operator for the AVS. + * @param newOperatorAVSSplitBips The new split for the operator for the AVS. + */ + event OperatorAVSSplitBipsSet( + address indexed caller, + address indexed operator, + address indexed avs, + uint32 activatedAt, + uint16 oldOperatorAVSSplitBips, + uint16 newOperatorAVSSplitBips + ); + /** + * @notice Emitted when the operator split for Programmatic Incentives is set. + * @param caller The address calling `setOperatorPISplit`. + * @param operator The operator on behalf of which the split is being set. + * @param activatedAt The timestamp at which the split will be activated. + * @param oldOperatorPISplitBips The old split for the operator for Programmatic Incentives. + * @param newOperatorPISplitBips The new split for the operator for Programmatic Incentives. + */ + event OperatorPISplitBipsSet( + address indexed caller, + address indexed operator, + uint32 activatedAt, + uint16 oldOperatorPISplitBips, + uint16 newOperatorPISplitBips + ); /// TOKEN EVENTS FOR TESTING /// /** @@ -65,5 +120,9 @@ interface IServiceManagerBaseEvents { * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ - event Approval(address indexed owner, address indexed spender, uint256 value); + event Approval( + address indexed owner, + address indexed spender, + uint256 value + ); } diff --git a/test/harnesses/RegistryCoordinatorHarness.t.sol b/test/harnesses/RegistryCoordinatorHarness.t.sol index d7ae81ae..80b38ca2 100644 --- a/test/harnesses/RegistryCoordinatorHarness.t.sol +++ b/test/harnesses/RegistryCoordinatorHarness.t.sol @@ -11,8 +11,9 @@ contract RegistryCoordinatorHarness is RegistryCoordinator, Test { IServiceManager _serviceManager, IStakeRegistry _stakeRegistry, IBLSApkRegistry _blsApkRegistry, - IIndexRegistry _indexRegistry - ) RegistryCoordinator(_serviceManager, _stakeRegistry, _blsApkRegistry, _indexRegistry) { + IIndexRegistry _indexRegistry, + ISocketRegistry _socketRegistry + ) RegistryCoordinator(_serviceManager, _stakeRegistry, _blsApkRegistry, _indexRegistry, _socketRegistry) { _transferOwnership(msg.sender); } diff --git a/test/integration/CoreRegistration.t.sol b/test/integration/CoreRegistration.t.sol index 5998025c..64730b17 100644 --- a/test/integration/CoreRegistration.t.sol +++ b/test/integration/CoreRegistration.t.sol @@ -79,7 +79,8 @@ contract Test_CoreRegistration is MockAVSDeployer { serviceManager, stakeRegistry, blsApkRegistry, - indexRegistry + indexRegistry, + socketRegistry ); // Upgrade Registry Coordinator & ServiceManager diff --git a/test/integration/IntegrationDeployer.t.sol b/test/integration/IntegrationDeployer.t.sol index ec9075e9..d69a908b 100644 --- a/test/integration/IntegrationDeployer.t.sol +++ b/test/integration/IntegrationDeployer.t.sol @@ -20,11 +20,8 @@ import "eigenlayer-contracts/src/contracts/core/RewardsCoordinator.sol"; import "eigenlayer-contracts/src/contracts/strategies/StrategyBase.sol"; import "eigenlayer-contracts/src/contracts/pods/EigenPodManager.sol"; import "eigenlayer-contracts/src/contracts/pods/EigenPod.sol"; -import "eigenlayer-contracts/src/contracts/pods/DelayedWithdrawalRouter.sol"; import "eigenlayer-contracts/src/contracts/permissions/PauserRegistry.sol"; import "eigenlayer-contracts/src/test/mocks/ETHDepositMock.sol"; -// import "eigenlayer-contracts/src/test/integration/mocks/BeaconChainOracleMock.t.sol"; -import "test/integration/mocks/BeaconChainOracleMock.t.sol"; // Middleware contracts import "src/RegistryCoordinator.sol"; @@ -33,6 +30,7 @@ import "src/IndexRegistry.sol"; import "src/BLSApkRegistry.sol"; import "test/mocks/ServiceManagerMock.sol"; import "src/OperatorStateRetriever.sol"; +import "src/SocketRegistry.sol"; // Mocks and More import "src/libraries/BN254.sol"; @@ -57,9 +55,7 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { Slasher slasher; IBeacon eigenPodBeacon; EigenPod pod; - DelayedWithdrawalRouter delayedWithdrawalRouter; ETHPOSDepositMock ethPOSDeposit; - BeaconChainOracleMock beaconChainOracle; // Base strategy implementation in case we want to create more strategies later StrategyBase baseStrategyImplementation; @@ -70,6 +66,7 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { BLSApkRegistry blsApkRegistry; StakeRegistry stakeRegistry; IndexRegistry indexRegistry; + SocketRegistry socketRegistry; OperatorStateRetriever operatorStateRetriever; TimeMachine public timeMachine; @@ -92,7 +89,7 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { address rewardsUpdater = address(uint160(uint256(keccak256("rewardsUpdater")))); // Constants/Defaults - uint64 constant MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR = 32e9; + uint64 constant GENESIS_TIME_LOCAL = 1 hours * 12; uint256 constant MIN_BALANCE = 1e6; uint256 constant MAX_BALANCE = 5e6; uint256 constant MAX_STRATEGY_COUNT = 32; // From StakeRegistry.MAX_WEIGHING_FUNCTION_LENGTH @@ -121,7 +118,6 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { // Deploy mocks EmptyContract emptyContract = new EmptyContract(); ethPOSDeposit = new ETHPOSDepositMock(); - beaconChainOracle = new BeaconChainOracleMock(); /** * First, deploy upgradeable proxy contracts that **will point** to the implementations. Since the implementation contracts are @@ -147,11 +143,6 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "") ) ); - delayedWithdrawalRouter = DelayedWithdrawalRouter( - address( - new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "") - ) - ); avsDirectory = AVSDirectory( address( new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "") @@ -164,10 +155,8 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { // Deploy EigenPod Contracts pod = new EigenPod( ethPOSDeposit, - delayedWithdrawalRouter, eigenPodManager, - MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR, - 0 + GENESIS_TIME_LOCAL ); eigenPodBeacon = new UpgradeableBeacon(address(pod)); @@ -181,8 +170,6 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { EigenPodManager eigenPodManagerImplementation = new EigenPodManager( ethPOSDeposit, eigenPodBeacon, strategyManager, slasher, delegationManager ); - DelayedWithdrawalRouter delayedWithdrawalRouterImplementation = - new DelayedWithdrawalRouter(eigenPodManager); AVSDirectory avsDirectoryImplemntation = new AVSDirectory(delegationManager); // RewardsCoordinator rewardsCoordinatorImplementation = new RewardsCoordinator( // delegationManager, @@ -240,24 +227,11 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { address(eigenPodManagerImplementation), abi.encodeWithSelector( EigenPodManager.initialize.selector, - address(beaconChainOracle), eigenLayerReputedMultisig, // initialOwner pauserRegistry, 0 // initialPausedStatus ) ); - // Delayed Withdrawal Router - proxyAdmin.upgradeAndCall( - TransparentUpgradeableProxy(payable(address(delayedWithdrawalRouter))), - address(delayedWithdrawalRouterImplementation), - abi.encodeWithSelector( - DelayedWithdrawalRouter.initialize.selector, - eigenLayerReputedMultisig, // initialOwner - pauserRegistry, - 0, // initialPausedStatus - minWithdrawalDelayBlocks - ) - ); // AVSDirectory proxyAdmin.upgradeAndCall( TransparentUpgradeableProxy(payable(address(avsDirectory))), @@ -310,6 +284,12 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { ) ); + socketRegistry = SocketRegistry( + address( + new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "") + ) + ); + indexRegistry = IndexRegistry( address( new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "") @@ -342,6 +322,9 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { IRegistryCoordinator(registryCoordinator), stakeRegistry ); + SocketRegistry socketRegistryImplementation = new SocketRegistry( + IRegistryCoordinator(registryCoordinator) + ); proxyAdmin.upgrade( TransparentUpgradeableProxy(payable(address(stakeRegistry))), @@ -363,13 +346,18 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { address(serviceManagerImplementation) ); + proxyAdmin.upgrade( + TransparentUpgradeableProxy(payable(address(socketRegistry))), + address(socketRegistryImplementation) + ); + serviceManager.initialize({ initialOwner: registryCoordinatorOwner, rewardsInitiator: address(msg.sender) }); RegistryCoordinator registryCoordinatorImplementation = - new RegistryCoordinator(serviceManager, stakeRegistry, blsApkRegistry, indexRegistry); + new RegistryCoordinator(serviceManager, stakeRegistry, blsApkRegistry, indexRegistry, socketRegistry); proxyAdmin.upgradeAndCall( TransparentUpgradeableProxy(payable(address(registryCoordinator))), address(registryCoordinatorImplementation), diff --git a/test/integration/User.t.sol b/test/integration/User.t.sol index 2f89ab1a..9e5e5990 100644 --- a/test/integration/User.t.sol +++ b/test/integration/User.t.sol @@ -399,7 +399,7 @@ contract User_AltMethods is User { operatorsPerQuorum[i][j] = blsApkRegistry.getOperatorFromPubkeyHash(operatorIds[j]); } - Sort.sort(operatorsPerQuorum[i]); + operatorsPerQuorum[i] = Sort.sortAddresses(operatorsPerQuorum[i]); } registryCoordinator.updateOperatorsForQuorum(operatorsPerQuorum, allQuorums); diff --git a/test/integration/mocks/BeaconChainOracleMock.t.sol b/test/integration/mocks/BeaconChainOracleMock.t.sol deleted file mode 100644 index dabd6b6a..00000000 --- a/test/integration/mocks/BeaconChainOracleMock.t.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.12; - -import "eigenlayer-contracts/src/contracts/interfaces/IBeaconChainOracle.sol"; - -// NOTE: There's a copy of this file in the core repo, but importing that was causing -// the compiler to complain for an unfathomable reason. Apparently reimplementing it -// here fixes the issue. -contract BeaconChainOracleMock is IBeaconChainOracle { - - mapping(uint64 => bytes32) blockRoots; - - function timestampToBlockRoot(uint timestamp) public view returns (bytes32) { - return blockRoots[uint64(timestamp)]; - } - - function setBlockRoot(uint64 timestamp, bytes32 blockRoot) public { - blockRoots[timestamp] = blockRoot; - } -} diff --git a/test/integration/utils/Sort.t.sol b/test/integration/utils/Sort.t.sol index 3853e544..46a2fc2b 100644 --- a/test/integration/utils/Sort.t.sol +++ b/test/integration/utils/Sort.t.sol @@ -2,23 +2,24 @@ pragma solidity ^0.8.12; library Sort { - - /// @dev In-place insertion sort of addrs, h/t ChatGPT - function sort(address[] memory addrs) internal pure { - for (uint i = 1; i < addrs.length; i++) { - address key = addrs[i]; - uint j = i - 1; - - // Move elements of addrs[0..i-1], that are greater than key, - // to one position ahead of their current position - while (j >= 0 && addrs[j] > key) { - addrs[j + 1] = addrs[j]; - if(j == 0) { - break; + /** + * @notice Sorts an array of addresses in ascending order. h/t ChatGPT take 2 + * @dev This function uses the Bubble Sort algorithm, which is simple but has O(n^2) complexity. + * @param addresses The array of addresses to be sorted. + * @return sortedAddresses The array of addresses sorted in ascending order. + */ + function sortAddresses(address[] memory addresses) internal pure returns (address[] memory) { + uint256 n = addresses.length; + for (uint256 i = 0; i < n; i++) { + for (uint256 j = 0; j < n - 1; j++) { + // Compare and swap if the current address is greater than the next one + if (addresses[j] > addresses[j + 1]) { + address temp = addresses[j]; + addresses[j] = addresses[j + 1]; + addresses[j + 1] = temp; } - j--; } - addrs[j + 1] = key; } + return addresses; } } diff --git a/test/mocks/AVSDirectoryMock.sol b/test/mocks/AVSDirectoryMock.sol index 813e913a..deef83da 100644 --- a/test/mocks/AVSDirectoryMock.sol +++ b/test/mocks/AVSDirectoryMock.sol @@ -4,42 +4,21 @@ pragma solidity ^0.8.12; import {IAVSDirectory, ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; contract AVSDirectoryMock is IAVSDirectory { - /** - * @notice Called by an avs to register an operator with the avs. - * @param operator The address of the operator to register. - * @param operatorSignature The signature, salt, and expiry of the operator's signature. - */ + mapping(address => mapping(bytes32 => bool)) public operatorSaltIsSpentMapping; + function registerOperatorToAVS( address operator, ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature ) external {} - /** - * @notice Called by an avs to deregister an operator with the avs. - * @param operator The address of the operator to deregister. - */ function deregisterOperatorFromAVS(address operator) external {} - /** - * @notice Called by an AVS to emit an `AVSMetadataURIUpdated` event indicating the information has updated. - * @param metadataURI The URI for metadata associated with an AVS - * @dev Note that the `metadataURI` is *never stored * and is only emitted in the `AVSMetadataURIUpdated` event - */ function updateAVSMetadataURI(string calldata metadataURI) external {} - /** - * @notice Returns whether or not the salt has already been used by the operator. - * @dev Salts is used in the `registerOperatorToAVS` function. - */ - function operatorSaltIsSpent(address operator, bytes32 salt) external view returns (bool) {} - - /** - * @notice Calculates the digest hash to be signed by an operator to register with an AVS - * @param operator The account registering as an operator - * @param avs The AVS the operator is registering to - * @param salt A unique and single use value associated with the approver signature. - * @param expiry Time after which the approver's signature becomes invalid - */ + function operatorSaltIsSpent(address operator, bytes32 salt) external view returns (bool) { + return operatorSaltIsSpentMapping[operator][salt]; + } + function calculateOperatorAVSRegistrationDigestHash( address operator, address avs, @@ -47,6 +26,9 @@ contract AVSDirectoryMock is IAVSDirectory { uint256 expiry ) external view returns (bytes32) {} - /// @notice The EIP-712 typehash for the Registration struct used by the contract function OPERATOR_AVS_REGISTRATION_TYPEHASH() external view returns (bytes32) {} -} + + function cancelSalt(bytes32 salt) external {} + + function domainSeparator() external view returns (bytes32) {} +} \ No newline at end of file diff --git a/test/mocks/DelegationMock.sol b/test/mocks/DelegationMock.sol index 88cd9d20..190e1aa5 100644 --- a/test/mocks/DelegationMock.sol +++ b/test/mocks/DelegationMock.sol @@ -1,17 +1,20 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.12; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; +import "eigenlayer-contracts/src/contracts/interfaces/IStrategyManager.sol"; -import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; -import {IStrategyManager} from "eigenlayer-contracts/src/contracts/interfaces/IStrategyManager.sol"; -import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; -import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol"; contract DelegationMock is IDelegationManager { mapping(address => bool) public isOperator; mapping(address => mapping(IStrategy => uint256)) public operatorShares; + function getDelegatableShares(address staker) external view returns (IStrategy[] memory, uint256[] memory) {} + + function setMinWithdrawalDelayBlocks(uint256 newMinWithdrawalDelayBlocks) external {} + + function setStrategyWithdrawalDelayBlocks(IStrategy[] calldata strategies, uint256[] calldata withdrawalDelayBlocks) external {} + function setIsOperator(address operator, bool _isOperatorReturnValue) external { isOperator[operator] = _isOperatorReturnValue; } @@ -58,19 +61,13 @@ contract DelegationMock is IDelegationManager { function operatorDetails(address operator) external pure returns (OperatorDetails memory) { OperatorDetails memory returnValue = OperatorDetails({ - __deprecated_earningsReceiver: operator, + __deprecated_earningsReceiver: operator, delegationApprover: operator, stakerOptOutWindowBlocks: 0 }); return returnValue; } - function beaconChainETHStrategy() external pure returns (IStrategy) {} - - function earningsReceiver(address operator) external pure returns (address) { - return operator; - } - function delegationApprover(address operator) external pure returns (address) { return operator; } @@ -79,31 +76,34 @@ contract DelegationMock is IDelegationManager { return 0; } - function minWithdrawalDelayBlocks() external view returns (uint256) { - return 50400; + function minWithdrawalDelayBlocks() external pure returns (uint256) { + return 0; } + /// @notice return address of the beaconChainETHStrategy + function beaconChainETHStrategy() external view returns (IStrategy) {} + /** * @notice Minimum delay enforced by this contract per Strategy for completing queued withdrawals. Measured in blocks, and adjustable by this contract's owner, * up to a maximum of `MAX_WITHDRAWAL_DELAY_BLOCKS`. Minimum value is 0 (i.e. no delay enforced). */ - function strategyWithdrawalDelayBlocks(IStrategy /*strategy*/) external view returns (uint256) { + function strategyWithdrawalDelayBlocks(IStrategy /*strategy*/) external pure returns (uint256) { return 0; } - + function getOperatorShares( address operator, IStrategy[] memory strategies ) external view returns (uint256[] memory) { uint256[] memory shares = new uint256[](strategies.length); - for (uint256 i = 0; i < strategies.length; ++i) { + for (uint256 i = 0; i < strategies.length; i++) { shares[i] = operatorShares[operator][strategies[i]]; } return shares; } - function getWithdrawalDelay(IStrategy[] calldata /*strategies*/) public view returns (uint256) { - return 0; + function getWithdrawalDelay(IStrategy[] calldata /*strategies*/) public pure returns (uint256) { + return type(uint256).max; } function isDelegated(address staker) external view returns (bool) { @@ -145,14 +145,20 @@ contract DelegationMock is IDelegationManager { function DELEGATION_APPROVAL_TYPEHASH() external view returns (bytes32) {} - function domainSeparator() external view returns (bytes32) {} + function OPERATOR_AVS_REGISTRATION_TYPEHASH() external view returns (bytes32) {} function cumulativeWithdrawalsQueued(address staker) external view returns (uint256) {} function calculateWithdrawalRoot(Withdrawal memory withdrawal) external pure returns (bytes32) {} + function registerOperatorToAVS(address operator, ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature) external {} + + function deregisterOperatorFromAVS(address operator) external {} + function operatorSaltIsSpent(address avs, bytes32 salt) external view returns (bool) {} + function domainSeparator() external view returns (bytes32) {} + function queueWithdrawals( QueuedWithdrawalParams[] calldata queuedWithdrawalParams ) external returns (bytes32[] memory) {} @@ -170,7 +176,7 @@ contract DelegationMock is IDelegationManager { uint256[] calldata middlewareTimesIndexes, bool[] calldata receiveAsTokens ) external {} - + // onlyDelegationManager functions in StrategyManager function addShares( IStrategyManager strategyManager, diff --git a/test/mocks/ECDSAServiceManagerMock.sol b/test/mocks/ECDSAServiceManagerMock.sol new file mode 100644 index 00000000..528270ae --- /dev/null +++ b/test/mocks/ECDSAServiceManagerMock.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.12; + +import "../../src/unaudited/ECDSAServiceManagerBase.sol"; + +contract ECDSAServiceManagerMock is ECDSAServiceManagerBase { + constructor( + address _avsDirectory, + address _stakeRegistry, + address _rewardsCoordinator, + address _delegationManager + ) + ECDSAServiceManagerBase(_avsDirectory, _stakeRegistry, _rewardsCoordinator, _delegationManager) + {} + + function initialize( + address initialOwner, + address rewardsInitiator + ) public virtual initializer { + __ServiceManagerBase_init(initialOwner, rewardsInitiator); + } +} diff --git a/test/mocks/ECDSAStakeRegistryMock.sol b/test/mocks/ECDSAStakeRegistryMock.sol new file mode 100644 index 00000000..7ad6043e --- /dev/null +++ b/test/mocks/ECDSAStakeRegistryMock.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.12; + +import "../../src/unaudited/ECDSAStakeRegistry.sol"; + +/** + * @title Mock for ECDSAStakeRegistry + * @dev This contract is a mock implementation of the ECDSAStakeRegistry for testing purposes. + */ +contract ECDSAStakeRegistryMock is ECDSAStakeRegistry { + + constructor(IDelegationManager _delegationManager) ECDSAStakeRegistry(_delegationManager) { + } +} diff --git a/test/mocks/RegistryCoordinatorMock.sol b/test/mocks/RegistryCoordinatorMock.sol index abee1a6a..378d357e 100644 --- a/test/mocks/RegistryCoordinatorMock.sol +++ b/test/mocks/RegistryCoordinatorMock.sol @@ -68,4 +68,6 @@ contract RegistryCoordinatorMock is IRegistryCoordinator { function quorumUpdateBlockNumber(uint8 quorumNumber) external view returns (uint256) {} function owner() external view returns (address) {} + + function updateSocket(string memory socket) external {} } diff --git a/test/mocks/RewardsCoordinatorMock.sol b/test/mocks/RewardsCoordinatorMock.sol index a18d281e..c3e36490 100644 --- a/test/mocks/RewardsCoordinatorMock.sol +++ b/test/mocks/RewardsCoordinatorMock.sol @@ -6,7 +6,6 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IRewardsCoordinator} from "eigenlayer-contracts/src/contracts/interfaces/IRewardsCoordinator.sol"; contract RewardsCoordinatorMock is IRewardsCoordinator { - /// @notice The address of the entity that can update the contract with new merkle roots function rewardsUpdater() external view returns (address) {} function CALCULATION_INTERVAL_SECONDS() external view returns (uint32) {} @@ -23,50 +22,117 @@ contract RewardsCoordinatorMock is IRewardsCoordinator { function claimerFor(address earner) external view returns (address) {} - function cumulativeClaimed(address claimer, IERC20 token) external view returns (uint256) {} + function cumulativeClaimed( + address claimer, + IERC20 token + ) external view returns (uint256) {} - function globalOperatorCommissionBips() external view returns (uint16) {} + function defaultOperatorSplitBips() external view returns (uint16) {} - function operatorCommissionBips(address operator, address avs) external view returns (uint16) {} + function calculateEarnerLeafHash( + EarnerTreeMerkleLeaf calldata leaf + ) external pure returns (bytes32) {} - function calculateEarnerLeafHash(EarnerTreeMerkleLeaf calldata leaf) external pure returns (bytes32) {} + function calculateTokenLeafHash( + TokenTreeMerkleLeaf calldata leaf + ) external pure returns (bytes32) {} - function calculateTokenLeafHash(TokenTreeMerkleLeaf calldata leaf) external pure returns (bytes32) {} + function checkClaim( + RewardsMerkleClaim calldata claim + ) external view returns (bool) {} - function checkClaim(RewardsMerkleClaim calldata claim) external view returns (bool) {} + function currRewardsCalculationEndTimestamp() + external + view + returns (uint32) + {} - function currRewardsCalculationEndTimestamp() external view returns (uint32) {} + function getDistributionRootsLength() external view returns (uint256) {} - function getRootIndexFromHash(bytes32 rootHash) external view returns (uint32) {} + function getDistributionRootAtIndex( + uint256 index + ) external view returns (DistributionRoot memory) {} - function getDistributionRootsLength() external view returns (uint256) {} + function getCurrentDistributionRoot() + external + view + returns (DistributionRoot memory) + {} + + function getCurrentClaimableDistributionRoot() + external + view + returns (DistributionRoot memory) + {} + + function getRootIndexFromHash( + bytes32 rootHash + ) external view returns (uint32) {} + + function domainSeparator() external view returns (bytes32) {} + + function getOperatorAVSSplit( + address operator, + address avs + ) external view returns (uint16) {} - /// EXTERNAL FUNCTIONS /// + function getOperatorPISplit( + address operator + ) external view returns (uint16) {} + + function createAVSRewardsSubmission( + RewardsSubmission[] calldata rewardsSubmissions + ) external {} + + function createRewardsForAllSubmission( + RewardsSubmission[] calldata rewardsSubmission + ) external {} + + function createRewardsForAllEarners( + RewardsSubmission[] calldata rewardsSubmissions + ) external {} - function createAVSRewardsSubmission(RewardsSubmission[] calldata rewardsSubmissions) external {} + function createOperatorDirectedAVSRewardsSubmission( + address avs, + OperatorDirectedRewardsSubmission[] + calldata operatorDirectedRewardsSubmissions + ) external {} - function createRewardsForAllSubmission(RewardsSubmission[] calldata rewardsSubmission) external {} + function processClaim( + RewardsMerkleClaim calldata claim, + address recipient + ) external {} - function processClaim(RewardsMerkleClaim calldata claim, address recipient) external {} + function processClaims( + RewardsMerkleClaim[] calldata claims, + address recipient + ) external {} function submitRoot( bytes32 root, uint32 rewardsCalculationEndTimestamp ) external {} - function setRewardsUpdater(address _rewardsUpdater) external {} + function disableRoot(uint32 rootIndex) external {} + + function setClaimerFor(address claimer) external {} function setActivationDelay(uint32 _activationDelay) external {} - function setGlobalOperatorCommission(uint16 _globalCommissionBips) external {} + function setDefaultOperatorSplit(uint16 split) external {} - function setClaimerFor(address claimer) external {} + function setRewardsUpdater(address _rewardsUpdater) external {} + + function setRewardsForAllSubmitter( + address _submitter, + bool _newValue + ) external {} + + function setOperatorAVSSplit( + address operator, + address avs, + uint16 split + ) external {} - /** - * @notice Sets the permissioned `payAllForRangeSubmitter` address which can submit payAllForRange - * @dev Only callable by the contract owner - * @param _submitter The address of the payAllForRangeSubmitter - * @param _newValue The new value for isPayAllForRangeSubmitter - */ - function setRewardsForAllSubmitter(address _submitter, bool _newValue) external {} -} \ No newline at end of file + function setOperatorPISplit(address operator, uint16 split) external {} +} diff --git a/test/unit/BLSSignatureCheckerUnit.t.sol b/test/unit/BLSSignatureCheckerUnit.t.sol index 29369b3f..3b5cf6e1 100644 --- a/test/unit/BLSSignatureCheckerUnit.t.sol +++ b/test/unit/BLSSignatureCheckerUnit.t.sol @@ -294,6 +294,7 @@ contract BLSSignatureCheckerUnitTests is BLSMockAVSDeployer { } function test_checkSignatures_revert_staleStakes() public { + vm.skip(true); uint256 numNonSigners = 2; uint256 quorumBitmap = 1; uint256 nonRandomNumber = 777; @@ -353,7 +354,7 @@ contract BLSSignatureCheckerUnitTests is BLSMockAVSDeployer { // set the nonSignerQuorumBitmapIndices to a different value nonSignerStakesAndSignature.nonSignerQuorumBitmapIndices[0] = 1; - cheats.expectRevert("RegistryCoordinator.getQuorumBitmapAtBlockNumberByIndex: quorumBitmapUpdate is from after blockNumber"); + cheats.expectRevert("RegCoord.getQuorumBitmapAtBlockNumberByIndex: quorumBitmapUpdate is from after blockNumber"); blsSignatureChecker.checkSignatures( msgHash, quorumNumbers, diff --git a/test/unit/ECDSAServiceManager.t.sol b/test/unit/ECDSAServiceManager.t.sol new file mode 100644 index 00000000..3b533d47 --- /dev/null +++ b/test/unit/ECDSAServiceManager.t.sol @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import {Test, console} from "forge-std/Test.sol"; + +import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol"; +import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; +import {IRewardsCoordinator} from "eigenlayer-contracts/src/contracts/interfaces/IRewardsCoordinator.sol"; +import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; + +import {ECDSAServiceManagerMock} from "../mocks/ECDSAServiceManagerMock.sol"; +import {ECDSAStakeRegistryMock} from "../mocks/ECDSAStakeRegistryMock.sol"; +import {Quorum, StrategyParams} from "../../src/interfaces/IECDSAStakeRegistryEventsAndErrors.sol"; + +contract MockDelegationManager { + function operatorShares(address, address) external pure returns (uint256) { + return 1000; // Return a dummy value for simplicity + } + + function getOperatorShares( + address, + IStrategy[] memory strategies + ) external pure returns (uint256[] memory) { + uint256[] memory response = new uint256[](strategies.length); + for (uint256 i; i < strategies.length; i++) { + response[i] = 1000; + } + return response; // Return a dummy value for simplicity + } +} + +contract MockAVSDirectory { + function registerOperatorToAVS( + address, + ISignatureUtils.SignatureWithSaltAndExpiry memory + ) external pure {} + + function deregisterOperatorFromAVS(address) external pure {} + + function updateAVSMetadataURI(string memory) external pure {} +} + +contract MockRewardsCoordinator { + function createAVSRewardsSubmission( + IRewardsCoordinator.RewardsSubmission[] calldata + ) external pure {} +} + +contract ECDSAServiceManagerSetup is Test { + MockDelegationManager public mockDelegationManager; + MockAVSDirectory public mockAVSDirectory; + ECDSAStakeRegistryMock public mockStakeRegistry; + MockRewardsCoordinator public mockRewardsCoordinator; + ECDSAServiceManagerMock public serviceManager; + address internal operator1; + address internal operator2; + uint256 internal operator1Pk; + uint256 internal operator2Pk; + + function setUp() public { + mockDelegationManager = new MockDelegationManager(); + mockAVSDirectory = new MockAVSDirectory(); + mockStakeRegistry = new ECDSAStakeRegistryMock( + IDelegationManager(address(mockDelegationManager)) + ); + mockRewardsCoordinator = new MockRewardsCoordinator(); + + serviceManager = new ECDSAServiceManagerMock( + address(mockAVSDirectory), + address(mockStakeRegistry), + address(mockRewardsCoordinator), + address(mockDelegationManager) + ); + + operator1Pk = 1; + operator2Pk = 2; + operator1 = vm.addr(operator1Pk); + operator2 = vm.addr(operator2Pk); + + // Create a quorum + Quorum memory quorum = Quorum({strategies: new StrategyParams[](2)}); + quorum.strategies[0] = StrategyParams({ + strategy: IStrategy(address(420)), + multiplier: 5000 + }); + quorum.strategies[1] = StrategyParams({ + strategy: IStrategy(address(421)), + multiplier: 5000 + }); + address[] memory operators = new address[](0); + + vm.prank(mockStakeRegistry.owner()); + mockStakeRegistry.initialize( + address(serviceManager), + 10_000, // Assuming a threshold weight of 10000 basis points + quorum + ); + ISignatureUtils.SignatureWithSaltAndExpiry memory dummySignature; + + vm.prank(operator1); + mockStakeRegistry.registerOperatorWithSignature( + dummySignature, + operator1 + ); + + vm.prank(operator2); + mockStakeRegistry.registerOperatorWithSignature( + dummySignature, + operator2 + ); + } + + function testRegisterOperatorToAVS() public { + address operator = operator1; + ISignatureUtils.SignatureWithSaltAndExpiry memory signature; + + vm.prank(address(mockStakeRegistry)); + serviceManager.registerOperatorToAVS(operator, signature); + } + + function testDeregisterOperatorFromAVS() public { + address operator = operator1; + + vm.prank(address(mockStakeRegistry)); + serviceManager.deregisterOperatorFromAVS(operator); + } + + function testGetRestakeableStrategies() public { + address[] memory strategies = serviceManager.getRestakeableStrategies(); + } + + function testGetOperatorRestakedStrategies() public { + address operator = operator1; + address[] memory strategies = serviceManager + .getOperatorRestakedStrategies(operator); + } + + function test_Regression_GetOperatorRestakedStrategies_NoShares() public { + address operator = operator1; + IStrategy[] memory strategies = new IStrategy[](2); + strategies[0] = IStrategy(address(420)); + strategies[1] = IStrategy(address(421)); + + uint256[] memory shares = new uint256[](2); + shares[0] = 0; + shares[1] = 1; + + vm.mockCall( + address(mockDelegationManager), + abi.encodeCall( + IDelegationManager.getOperatorShares, + (operator, strategies) + ), + abi.encode(shares) + ); + + address[] memory restakedStrategies = serviceManager + .getOperatorRestakedStrategies(operator); + assertEq( + restakedStrategies.length, + 1, + "Expected no restaked strategies" + ); + } + + function testUpdateAVSMetadataURI() public { + string memory newURI = "https://new-metadata-uri.com"; + + vm.prank(mockStakeRegistry.owner()); + serviceManager.updateAVSMetadataURI(newURI); + } + + function testCreateAVSRewardsSubmission() public { + IRewardsCoordinator.RewardsSubmission[] memory submissions; + + vm.prank(serviceManager.rewardsInitiator()); + serviceManager.createAVSRewardsSubmission(submissions); + } + + function testSetRewardsInitiator() public { + address newInitiator = address(0x123); + + vm.prank(mockStakeRegistry.owner()); + serviceManager.setRewardsInitiator(newInitiator); + } +} diff --git a/test/unit/EjectionManagerUnit.t.sol b/test/unit/EjectionManagerUnit.t.sol index 7fcd4905..6ec539fc 100644 --- a/test/unit/EjectionManagerUnit.t.sol +++ b/test/unit/EjectionManagerUnit.t.sol @@ -127,7 +127,7 @@ contract EjectionManagerUnitTests is MockAVSDeployer { } function testEjectOperators_MultipleOperatorOutsideRatelimit() public { - uint8 operatorsCanEject = 1; + uint8 operatorsCanEject = 2; uint8 operatorsToEject = 10; uint8 numOperators = 10; uint96 stake = 1 ether; @@ -164,6 +164,55 @@ contract EjectionManagerUnitTests is MockAVSDeployer { } } + function testEjectOperators_NoEjectionForNoEjectableStake() public { + uint8 operatorsCanEject = 2; + uint8 operatorsToEject = 10; + uint8 numOperators = 10; + uint96 stake = 1 ether; + _registerOperaters(numOperators, stake); + + bytes32[][] memory operatorIds = new bytes32[][](numQuorums); + for (uint8 i = 0; i < numQuorums; i++) { + operatorIds[i] = new bytes32[](operatorsToEject); + for (uint j = 0; j < operatorsToEject; j++) { + operatorIds[i][j] = registryCoordinator.getOperatorId(_incrementAddress(defaultOperator, j)); + } + } + + for(uint8 i = 0; i < operatorsToEject; i++) { + assertEq(uint8(registryCoordinator.getOperatorStatus(_incrementAddress(defaultOperator, i))), uint8(IRegistryCoordinator.OperatorStatus.REGISTERED)); + } + + for(uint8 i = 0; i < numQuorums; i++) { + for(uint8 j = 0; j < operatorsCanEject; j++) { + cheats.expectEmit(true, true, true, true, address(ejectionManager)); + emit OperatorEjected(operatorIds[i][j], i); + } + } + + cheats.prank(ejector); + ejectionManager.ejectOperators(operatorIds); + + for(uint8 i = 0; i < operatorsCanEject; i++) { + assertEq(uint8(registryCoordinator.getOperatorStatus(_incrementAddress(defaultOperator, i))), uint8(IRegistryCoordinator.OperatorStatus.DEREGISTERED)); + } + + for(uint8 i = operatorsCanEject; i < operatorsToEject; i++) { + assertEq(uint8(registryCoordinator.getOperatorStatus(_incrementAddress(defaultOperator, i))), uint8(IRegistryCoordinator.OperatorStatus.REGISTERED)); + } + + cheats.prank(ejector); + ejectionManager.ejectOperators(operatorIds); + + for(uint8 i = 0; i < operatorsCanEject; i++) { + assertEq(uint8(registryCoordinator.getOperatorStatus(_incrementAddress(defaultOperator, i))), uint8(IRegistryCoordinator.OperatorStatus.DEREGISTERED)); + } + + for(uint8 i = operatorsCanEject; i < operatorsToEject; i++) { + assertEq(uint8(registryCoordinator.getOperatorStatus(_incrementAddress(defaultOperator, i))), uint8(IRegistryCoordinator.OperatorStatus.REGISTERED)); + } + } + function testEjectOperators_MultipleOperatorMultipleTimesInsideRatelimit() public { uint8 operatorsToEject = 4; uint8 numOperators = 100; @@ -377,6 +426,18 @@ contract EjectionManagerUnitTests is MockAVSDeployer { ejectionManager.setEjector(address(0), true); } + function test_Overflow_Regression() public { + cheats.prank(registryCoordinatorOwner); + ejectionManager.setQuorumEjectionParams(0, IEjectionManager.QuorumEjectionParams({ + rateLimitWindow: 7 days, + ejectableStakePercent: 9999 + })); + + stakeRegistry.recordTotalStakeUpdate(1, 2_000_000_000 * 1 ether); + + ejectionManager.amountEjectableForQuorum(1); + } + function _registerOperaters(uint8 numOperators, uint96 stake) internal { for (uint i = 0; i < numOperators; i++) { BN254.G1Point memory pubKey = BN254.hashToG1(keccak256(abi.encodePacked(i))); @@ -384,4 +445,4 @@ contract EjectionManagerUnitTests is MockAVSDeployer { _registerOperatorWithCoordinator(operator, MAX_QUORUM_BITMAP, pubKey, stake); } } -} +} \ No newline at end of file diff --git a/test/unit/OperatorStateRetrieverUnit.t.sol b/test/unit/OperatorStateRetrieverUnit.t.sol index 89a9d94a..f79c345c 100644 --- a/test/unit/OperatorStateRetrieverUnit.t.sol +++ b/test/unit/OperatorStateRetrieverUnit.t.sol @@ -6,15 +6,18 @@ import "../utils/MockAVSDeployer.sol"; contract OperatorStateRetrieverUnitTests is MockAVSDeployer { using BN254 for BN254.G1Point; - - function setUp() virtual public { + function setUp() public virtual { numQuorums = 8; _deployMockEigenLayerAndAVS(numQuorums); } function test_getOperatorState_revert_neverRegistered() public { - cheats.expectRevert("RegistryCoordinator.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operatorId at block number"); - operatorStateRetriever.getOperatorState(registryCoordinator, defaultOperatorId, uint32(block.number)); + cheats.expectRevert( + "RegCoord.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operator at blockNumber" + ); + operatorStateRetriever.getOperatorState( + registryCoordinator, defaultOperatorId, uint32(block.number) + ); } function test_getOperatorState_revert_registeredFirstAfterReferenceBlockNumber() public { @@ -22,8 +25,12 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { _registerOperatorWithCoordinator(defaultOperator, 1, defaultPubKey); // should revert because the operator was registered for the first time after the reference block number - cheats.expectRevert("RegistryCoordinator.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operatorId at block number"); - operatorStateRetriever.getOperatorState(registryCoordinator, defaultOperatorId, registrationBlockNumber - 1); + cheats.expectRevert( + "RegCoord.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operator at blockNumber" + ); + operatorStateRetriever.getOperatorState( + registryCoordinator, defaultOperatorId, registrationBlockNumber - 1 + ); } function test_getOperatorState_deregisteredBeforeReferenceBlockNumber() public { @@ -35,7 +42,10 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { cheats.prank(defaultOperator); registryCoordinator.deregisterOperator(BitmapUtils.bitmapToBytesArray(quorumBitmap)); - (uint256 fetchedQuorumBitmap, OperatorStateRetriever.Operator[][] memory operators) = operatorStateRetriever.getOperatorState(registryCoordinator, defaultOperatorId, uint32(block.number)); + (uint256 fetchedQuorumBitmap, OperatorStateRetriever.Operator[][] memory operators) = + operatorStateRetriever.getOperatorState( + registryCoordinator, defaultOperatorId, uint32(block.number) + ); assertEq(fetchedQuorumBitmap, 0); assertEq(operators.length, 0); } @@ -45,7 +55,10 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { cheats.roll(registrationBlockNumber); _registerOperatorWithCoordinator(defaultOperator, quorumBitmap, defaultPubKey); - (uint256 fetchedQuorumBitmap, OperatorStateRetriever.Operator[][] memory operators) = operatorStateRetriever.getOperatorState(registryCoordinator, defaultOperatorId, uint32(block.number)); + (uint256 fetchedQuorumBitmap, OperatorStateRetriever.Operator[][] memory operators) = + operatorStateRetriever.getOperatorState( + registryCoordinator, defaultOperatorId, uint32(block.number) + ); assertEq(fetchedQuorumBitmap, 1); assertEq(operators.length, 1); assertEq(operators[0].length, 1); @@ -55,31 +68,41 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { } function test_getOperatorState_revert_quorumNotCreatedAtCallTime() public { - cheats.expectRevert("IndexRegistry._operatorCountAtBlockNumber: quorum did not exist at given block number"); - operatorStateRetriever.getOperatorState(registryCoordinator, BitmapUtils.bitmapToBytesArray(1 << numQuorums), uint32(block.number)); + cheats.expectRevert( + "IndexRegistry._operatorCountAtBlockNumber: quorum did not exist at given block number" + ); + operatorStateRetriever.getOperatorState( + registryCoordinator, + BitmapUtils.bitmapToBytesArray(1 << numQuorums), + uint32(block.number) + ); } function test_getOperatorState_revert_quorumNotCreatedAtReferenceBlockNumber() public { cheats.roll(registrationBlockNumber); - IRegistryCoordinator.OperatorSetParam memory operatorSetParams = - IRegistryCoordinator.OperatorSetParam({ - maxOperatorCount: defaultMaxOperatorCount, - kickBIPsOfOperatorStake: defaultKickBIPsOfOperatorStake, - kickBIPsOfTotalStake: defaultKickBIPsOfTotalStake - }); + IRegistryCoordinator.OperatorSetParam memory operatorSetParams = IRegistryCoordinator + .OperatorSetParam({ + maxOperatorCount: defaultMaxOperatorCount, + kickBIPsOfOperatorStake: defaultKickBIPsOfOperatorStake, + kickBIPsOfTotalStake: defaultKickBIPsOfTotalStake + }); uint96 minimumStake = 1; - IStakeRegistry.StrategyParams[] memory strategyParams = new IStakeRegistry.StrategyParams[](1); + IStakeRegistry.StrategyParams[] memory strategyParams = + new IStakeRegistry.StrategyParams[](1); strategyParams[0] = - IStakeRegistry.StrategyParams({ - strategy: IStrategy(address(1000)), - multiplier: 1e16 - }); + IStakeRegistry.StrategyParams({strategy: IStrategy(address(1000)), multiplier: 1e16}); cheats.prank(registryCoordinator.owner()); registryCoordinator.createQuorum(operatorSetParams, minimumStake, strategyParams); - cheats.expectRevert("IndexRegistry._operatorCountAtBlockNumber: quorum did not exist at given block number"); - operatorStateRetriever.getOperatorState(registryCoordinator, BitmapUtils.bitmapToBytesArray(1 << numQuorums), uint32(registrationBlockNumber - 1)); + cheats.expectRevert( + "IndexRegistry._operatorCountAtBlockNumber: quorum did not exist at given block number" + ); + operatorStateRetriever.getOperatorState( + registryCoordinator, + BitmapUtils.bitmapToBytesArray(1 << numQuorums), + uint32(registrationBlockNumber - 1) + ); } function test_getOperatorState_returnsCorrect() public { @@ -91,9 +114,16 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { address otherOperator = _incrementAddress(defaultOperator, 1); BN254.G1Point memory otherPubKey = BN254.G1Point(1, 2); bytes32 otherOperatorId = BN254.hashG1Point(otherPubKey); - _registerOperatorWithCoordinator(otherOperator, quorumBitmapThree, otherPubKey, defaultStake -1); + _registerOperatorWithCoordinator( + otherOperator, quorumBitmapThree, otherPubKey, defaultStake - 1 + ); - OperatorStateRetriever.Operator[][] memory operators = operatorStateRetriever.getOperatorState(registryCoordinator, BitmapUtils.bitmapToBytesArray(quorumBitmapThree), uint32(block.number)); + OperatorStateRetriever.Operator[][] memory operators = operatorStateRetriever + .getOperatorState( + registryCoordinator, + BitmapUtils.bitmapToBytesArray(quorumBitmapThree), + uint32(block.number) + ); assertEq(operators.length, 2); assertEq(operators[0].length, 2); assertEq(operators[1].length, 1); @@ -112,11 +142,20 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { bytes32[] memory nonSignerOperatorIds = new bytes32[](1); nonSignerOperatorIds[0] = defaultOperatorId; - cheats.expectRevert("RegistryCoordinator.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operatorId at block number"); - operatorStateRetriever.getCheckSignaturesIndices(registryCoordinator, uint32(block.number), BitmapUtils.bitmapToBytesArray(1), nonSignerOperatorIds); + cheats.expectRevert( + "RegCoord.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operator at blockNumber" + ); + operatorStateRetriever.getCheckSignaturesIndices( + registryCoordinator, + uint32(block.number), + BitmapUtils.bitmapToBytesArray(1), + nonSignerOperatorIds + ); } - function test_getCheckSignaturesIndices_revert_registeredFirstAfterReferenceBlockNumber() public { + function test_getCheckSignaturesIndices_revert_registeredFirstAfterReferenceBlockNumber() + public + { bytes32[] memory nonSignerOperatorIds = new bytes32[](1); nonSignerOperatorIds[0] = defaultOperatorId; @@ -124,8 +163,15 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { _registerOperatorWithCoordinator(defaultOperator, 1, defaultPubKey); // should revert because the operator was registered for the first time after the reference block number - cheats.expectRevert("RegistryCoordinator.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operatorId at block number"); - operatorStateRetriever.getCheckSignaturesIndices(registryCoordinator, registrationBlockNumber - 1, BitmapUtils.bitmapToBytesArray(1), nonSignerOperatorIds); + cheats.expectRevert( + "RegCoord.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operator at blockNumber" + ); + operatorStateRetriever.getCheckSignaturesIndices( + registryCoordinator, + registrationBlockNumber - 1, + BitmapUtils.bitmapToBytesArray(1), + nonSignerOperatorIds + ); } function test_getCheckSignaturesIndices_revert_deregisteredAtReferenceBlockNumber() public { @@ -140,8 +186,15 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { registryCoordinator.deregisterOperator(BitmapUtils.bitmapToBytesArray(1)); // should revert because the operator was registered for the first time after the reference block number - cheats.expectRevert("OperatorStateRetriever.getCheckSignaturesIndices: operator must be registered at blocknumber"); - operatorStateRetriever.getCheckSignaturesIndices(registryCoordinator, uint32(block.number), BitmapUtils.bitmapToBytesArray(1), nonSignerOperatorIds); + cheats.expectRevert( + "OperatorStateRetriever.getCheckSignaturesIndices: operator must be registered at blocknumber" + ); + operatorStateRetriever.getCheckSignaturesIndices( + registryCoordinator, + uint32(block.number), + BitmapUtils.bitmapToBytesArray(1), + nonSignerOperatorIds + ); } function test_getCheckSignaturesIndices_revert_quorumNotCreatedAtCallTime() public { @@ -150,11 +203,18 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { _registerOperatorWithCoordinator(defaultOperator, 1, defaultPubKey); - cheats.expectRevert("StakeRegistry.getTotalStakeIndicesAtBlockNumber: quorum does not exist"); - operatorStateRetriever.getCheckSignaturesIndices(registryCoordinator, uint32(block.number), BitmapUtils.bitmapToBytesArray(1 << numQuorums), nonSignerOperatorIds); + cheats.expectRevert("StakeRegistry.quorumExists: quorum does not exist"); + operatorStateRetriever.getCheckSignaturesIndices( + registryCoordinator, + uint32(block.number), + BitmapUtils.bitmapToBytesArray(1 << numQuorums), + nonSignerOperatorIds + ); } - function test_getCheckSignaturesIndices_revert_quorumNotCreatedAtReferenceBlockNumber() public { + function test_getCheckSignaturesIndices_revert_quorumNotCreatedAtReferenceBlockNumber() + public + { cheats.roll(registrationBlockNumber); _registerOperatorWithCoordinator(defaultOperator, 1, defaultPubKey); @@ -162,25 +222,30 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { bytes32[] memory nonSignerOperatorIds = new bytes32[](1); nonSignerOperatorIds[0] = defaultOperatorId; - IRegistryCoordinator.OperatorSetParam memory operatorSetParams = - IRegistryCoordinator.OperatorSetParam({ - maxOperatorCount: defaultMaxOperatorCount, - kickBIPsOfOperatorStake: defaultKickBIPsOfOperatorStake, - kickBIPsOfTotalStake: defaultKickBIPsOfTotalStake - }); + IRegistryCoordinator.OperatorSetParam memory operatorSetParams = IRegistryCoordinator + .OperatorSetParam({ + maxOperatorCount: defaultMaxOperatorCount, + kickBIPsOfOperatorStake: defaultKickBIPsOfOperatorStake, + kickBIPsOfTotalStake: defaultKickBIPsOfTotalStake + }); uint96 minimumStake = 1; - IStakeRegistry.StrategyParams[] memory strategyParams = new IStakeRegistry.StrategyParams[](1); + IStakeRegistry.StrategyParams[] memory strategyParams = + new IStakeRegistry.StrategyParams[](1); strategyParams[0] = - IStakeRegistry.StrategyParams({ - strategy: IStrategy(address(1000)), - multiplier: 1e16 - }); + IStakeRegistry.StrategyParams({strategy: IStrategy(address(1000)), multiplier: 1e16}); cheats.prank(registryCoordinator.owner()); registryCoordinator.createQuorum(operatorSetParams, minimumStake, strategyParams); - cheats.expectRevert("StakeRegistry.getTotalStakeIndicesAtBlockNumber: quorum has no stake history at blockNumber"); - operatorStateRetriever.getCheckSignaturesIndices(registryCoordinator, registrationBlockNumber + 5, BitmapUtils.bitmapToBytesArray(1 << numQuorums), nonSignerOperatorIds); + cheats.expectRevert( + "StakeRegistry.getTotalStakeIndicesAtBlockNumber: quorum has no stake history at blockNumber" + ); + operatorStateRetriever.getCheckSignaturesIndices( + registryCoordinator, + registrationBlockNumber + 5, + BitmapUtils.bitmapToBytesArray(1 << numQuorums), + nonSignerOperatorIds + ); } function test_getCheckSignaturesIndices_returnsCorrect() public { @@ -195,7 +260,9 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { address otherOperator = _incrementAddress(defaultOperator, 1); BN254.G1Point memory otherPubKey = BN254.G1Point(1, 2); bytes32 otherOperatorId = BN254.hashG1Point(otherPubKey); - _registerOperatorWithCoordinator(otherOperator, quorumBitmapThree, otherPubKey, defaultStake -1); + _registerOperatorWithCoordinator( + otherOperator, quorumBitmapThree, otherPubKey, defaultStake - 1 + ); cheats.roll(registrationBlockNumber + 15); cheats.prank(defaultOperator); @@ -209,13 +276,21 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { registryCoordinator.deregisterOperator(BitmapUtils.bitmapToBytesArray(quorumBitmapTwo)); cheats.roll(registrationBlockNumber + 30); - _registerOperatorWithCoordinator(otherOperator, quorumBitmapTwo, otherPubKey, defaultStake - 2); + _registerOperatorWithCoordinator( + otherOperator, quorumBitmapTwo, otherPubKey, defaultStake - 2 + ); bytes32[] memory nonSignerOperatorIds = new bytes32[](2); nonSignerOperatorIds[0] = defaultOperatorId; nonSignerOperatorIds[1] = otherOperatorId; - OperatorStateRetriever.CheckSignaturesIndices memory checkSignaturesIndices = operatorStateRetriever.getCheckSignaturesIndices(registryCoordinator, uint32(block.number), BitmapUtils.bitmapToBytesArray(quorumBitmapThree), nonSignerOperatorIds); + OperatorStateRetriever.CheckSignaturesIndices memory checkSignaturesIndices = + operatorStateRetriever.getCheckSignaturesIndices( + registryCoordinator, + uint32(block.number), + BitmapUtils.bitmapToBytesArray(quorumBitmapThree), + nonSignerOperatorIds + ); // we're querying for 2 operators, so there should be 2 nonSignerQuorumBitmapIndices assertEq(checkSignaturesIndices.nonSignerQuorumBitmapIndices.length, 2); // the first operator (0) registered for quorum 1, (1) deregistered from quorum 1, and (2) registered for quorum 2 @@ -250,7 +325,12 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { nonSignerOperatorIds = new bytes32[](1); nonSignerOperatorIds[0] = otherOperatorId; // taking only the deregistration into account - checkSignaturesIndices = operatorStateRetriever.getCheckSignaturesIndices(registryCoordinator, registrationBlockNumber + 15, BitmapUtils.bitmapToBytesArray(quorumBitmapThree), nonSignerOperatorIds); + checkSignaturesIndices = operatorStateRetriever.getCheckSignaturesIndices( + registryCoordinator, + registrationBlockNumber + 15, + BitmapUtils.bitmapToBytesArray(quorumBitmapThree), + nonSignerOperatorIds + ); // we're querying for 1 operator, so there should be 1 nonSignerQuorumBitmapIndices assertEq(checkSignaturesIndices.nonSignerQuorumBitmapIndices.length, 1); // the second operator (0) registered for quorum 1 and 2 @@ -286,44 +366,47 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { uint256[][] memory expectedOperatorOverallIndices ) = _registerRandomOperators(pseudoRandomNumber); - for (uint i = 0; i < operatorMetadatas.length; i++) { + for (uint256 i = 0; i < operatorMetadatas.length; i++) { uint32 blockNumber = uint32(registrationBlockNumber + blocksBetweenRegistrations * i); uint256 gasBefore = gasleft(); // retrieve the ordered list of operators for each quorum along with their id and stake - (uint256 quorumBitmap, OperatorStateRetriever.Operator[][] memory operators) = - operatorStateRetriever.getOperatorState(registryCoordinator, operatorMetadatas[i].operatorId, blockNumber); + (uint256 quorumBitmap, OperatorStateRetriever.Operator[][] memory operators) = + operatorStateRetriever.getOperatorState( + registryCoordinator, operatorMetadatas[i].operatorId, blockNumber + ); uint256 gasAfter = gasleft(); emit log_named_uint("gasUsed", gasBefore - gasAfter); assertEq(operatorMetadatas[i].quorumBitmap, quorumBitmap); bytes memory quorumNumbers = BitmapUtils.bitmapToBytesArray(quorumBitmap); - + // assert that the operators returned are the expected ones _assertExpectedOperators( - quorumNumbers, - operators, - expectedOperatorOverallIndices, - operatorMetadatas + quorumNumbers, operators, expectedOperatorOverallIndices, operatorMetadatas ); } // choose a random operator to deregister uint256 operatorIndexToDeregister = pseudoRandomNumber % maxOperatorsToRegister; - bytes memory quorumNumbersToDeregister = BitmapUtils.bitmapToBytesArray(operatorMetadatas[operatorIndexToDeregister].quorumBitmap); + bytes memory quorumNumbersToDeregister = BitmapUtils.bitmapToBytesArray( + operatorMetadatas[operatorIndexToDeregister].quorumBitmap + ); - uint32 deregistrationBlockNumber = registrationBlockNumber + blocksBetweenRegistrations * (uint32(operatorMetadatas.length) + 1); + uint32 deregistrationBlockNumber = registrationBlockNumber + + blocksBetweenRegistrations * (uint32(operatorMetadatas.length) + 1); cheats.roll(deregistrationBlockNumber); cheats.prank(_incrementAddress(defaultOperator, operatorIndexToDeregister)); registryCoordinator.deregisterOperator(quorumNumbersToDeregister); // modify expectedOperatorOverallIndices by moving th operatorIdsToSwap to the index where the operatorIndexToDeregister was - for (uint i = 0; i < quorumNumbersToDeregister.length; i++) { + for (uint256 i = 0; i < quorumNumbersToDeregister.length; i++) { uint8 quorumNumber = uint8(quorumNumbersToDeregister[i]); // loop through indices till we find operatorIndexToDeregister, then move that last operator into that index - for (uint j = 0; j < expectedOperatorOverallIndices[quorumNumber].length; j++) { + for (uint256 j = 0; j < expectedOperatorOverallIndices[quorumNumber].length; j++) { if (expectedOperatorOverallIndices[quorumNumber][j] == operatorIndexToDeregister) { - expectedOperatorOverallIndices[quorumNumber][j] = expectedOperatorOverallIndices[quorumNumber][expectedOperatorOverallIndices[quorumNumber].length - 1]; + expectedOperatorOverallIndices[quorumNumber][j] = expectedOperatorOverallIndices[quorumNumber][expectedOperatorOverallIndices[quorumNumber] + .length - 1]; break; } } @@ -334,10 +417,12 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { for (uint8 i = 0; i < allQuorumNumbers.length; i++) { allQuorumNumbers[i] = bytes1(i); } - + _assertExpectedOperators( allQuorumNumbers, - operatorStateRetriever.getOperatorState(registryCoordinator, allQuorumNumbers, deregistrationBlockNumber), + operatorStateRetriever.getOperatorState( + registryCoordinator, allQuorumNumbers, deregistrationBlockNumber + ), expectedOperatorOverallIndices, operatorMetadatas ); @@ -349,7 +434,8 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { uint256[][] memory expectedOperatorOverallIndices ) = _registerRandomOperators(pseudoRandomNumber); - uint32 cumulativeBlockNumber = registrationBlockNumber + blocksBetweenRegistrations * uint32(operatorMetadatas.length); + uint32 cumulativeBlockNumber = + registrationBlockNumber + blocksBetweenRegistrations * uint32(operatorMetadatas.length); // get the quorum bitmap for which there is at least 1 operator uint256 allInclusiveQuorumBitmap = 0; @@ -357,28 +443,53 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { allInclusiveQuorumBitmap |= operatorMetadatas[i].quorumBitmap; } - bytes memory allInclusiveQuorumNumbers = BitmapUtils.bitmapToBytesArray(allInclusiveQuorumBitmap); + bytes memory allInclusiveQuorumNumbers = + BitmapUtils.bitmapToBytesArray(allInclusiveQuorumBitmap); bytes32[] memory nonSignerOperatorIds = new bytes32[](0); OperatorStateRetriever.CheckSignaturesIndices memory checkSignaturesIndices = - operatorStateRetriever.getCheckSignaturesIndices( - registryCoordinator, - cumulativeBlockNumber, - allInclusiveQuorumNumbers, - nonSignerOperatorIds - ); + operatorStateRetriever.getCheckSignaturesIndices( + registryCoordinator, + cumulativeBlockNumber, + allInclusiveQuorumNumbers, + nonSignerOperatorIds + ); - assertEq(checkSignaturesIndices.nonSignerQuorumBitmapIndices.length, 0, "nonSignerQuorumBitmapIndices should be empty if no nonsigners"); - assertEq(checkSignaturesIndices.quorumApkIndices.length, allInclusiveQuorumNumbers.length, "quorumApkIndices should be the number of quorums queried for"); - assertEq(checkSignaturesIndices.totalStakeIndices.length, allInclusiveQuorumNumbers.length, "totalStakeIndices should be the number of quorums queried for"); - assertEq(checkSignaturesIndices.nonSignerStakeIndices.length, allInclusiveQuorumNumbers.length, "nonSignerStakeIndices should be the number of quorums queried for"); + assertEq( + checkSignaturesIndices.nonSignerQuorumBitmapIndices.length, + 0, + "nonSignerQuorumBitmapIndices should be empty if no nonsigners" + ); + assertEq( + checkSignaturesIndices.quorumApkIndices.length, + allInclusiveQuorumNumbers.length, + "quorumApkIndices should be the number of quorums queried for" + ); + assertEq( + checkSignaturesIndices.totalStakeIndices.length, + allInclusiveQuorumNumbers.length, + "totalStakeIndices should be the number of quorums queried for" + ); + assertEq( + checkSignaturesIndices.nonSignerStakeIndices.length, + allInclusiveQuorumNumbers.length, + "nonSignerStakeIndices should be the number of quorums queried for" + ); // assert the indices are the number of registered operators for the quorum minus 1 for (uint8 i = 0; i < allInclusiveQuorumNumbers.length; i++) { uint8 quorumNumber = uint8(allInclusiveQuorumNumbers[i]); - assertEq(checkSignaturesIndices.quorumApkIndices[i], expectedOperatorOverallIndices[quorumNumber].length, "quorumApkIndex should be the number of registered operators for the quorum"); - assertEq(checkSignaturesIndices.totalStakeIndices[i], expectedOperatorOverallIndices[quorumNumber].length, "totalStakeIndex should be the number of registered operators for the quorum"); + assertEq( + checkSignaturesIndices.quorumApkIndices[i], + expectedOperatorOverallIndices[quorumNumber].length, + "quorumApkIndex should be the number of registered operators for the quorum" + ); + assertEq( + checkSignaturesIndices.totalStakeIndices[i], + expectedOperatorOverallIndices[quorumNumber].length, + "totalStakeIndex should be the number of registered operators for the quorum" + ); } } @@ -388,7 +499,8 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { uint256[][] memory expectedOperatorOverallIndices ) = _registerRandomOperators(pseudoRandomNumber); - uint32 cumulativeBlockNumber = registrationBlockNumber + blocksBetweenRegistrations * uint32(operatorMetadatas.length); + uint32 cumulativeBlockNumber = + registrationBlockNumber + blocksBetweenRegistrations * uint32(operatorMetadatas.length); // get the quorum bitmap for which there is at least 1 operator uint256 allInclusiveQuorumBitmap = 0; @@ -396,41 +508,78 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { allInclusiveQuorumBitmap |= operatorMetadatas[i].quorumBitmap; } - bytes memory allInclusiveQuorumNumbers = BitmapUtils.bitmapToBytesArray(allInclusiveQuorumBitmap); - - bytes32[] memory nonSignerOperatorIds = new bytes32[](pseudoRandomNumber % (operatorMetadatas.length - 1) + 1); - uint256 randomIndex = uint256(keccak256(abi.encodePacked("nonSignerOperatorIds", pseudoRandomNumber))) % operatorMetadatas.length; - for (uint i = 0; i < nonSignerOperatorIds.length; i++) { - nonSignerOperatorIds[i] = operatorMetadatas[(randomIndex + i) % operatorMetadatas.length].operatorId; + bytes memory allInclusiveQuorumNumbers = + BitmapUtils.bitmapToBytesArray(allInclusiveQuorumBitmap); + + bytes32[] memory nonSignerOperatorIds = + new bytes32[](pseudoRandomNumber % (operatorMetadatas.length - 1) + 1); + uint256 randomIndex = uint256( + keccak256(abi.encodePacked("nonSignerOperatorIds", pseudoRandomNumber)) + ) % operatorMetadatas.length; + for (uint256 i = 0; i < nonSignerOperatorIds.length; i++) { + nonSignerOperatorIds[i] = + operatorMetadatas[(randomIndex + i) % operatorMetadatas.length].operatorId; } OperatorStateRetriever.CheckSignaturesIndices memory checkSignaturesIndices = - operatorStateRetriever.getCheckSignaturesIndices( - registryCoordinator, - cumulativeBlockNumber, - allInclusiveQuorumNumbers, - nonSignerOperatorIds - ); + operatorStateRetriever.getCheckSignaturesIndices( + registryCoordinator, + cumulativeBlockNumber, + allInclusiveQuorumNumbers, + nonSignerOperatorIds + ); - assertEq(checkSignaturesIndices.nonSignerQuorumBitmapIndices.length, nonSignerOperatorIds.length, "nonSignerQuorumBitmapIndices should be the number of nonsigners"); - assertEq(checkSignaturesIndices.quorumApkIndices.length, allInclusiveQuorumNumbers.length, "quorumApkIndices should be the number of quorums queried for"); - assertEq(checkSignaturesIndices.totalStakeIndices.length, allInclusiveQuorumNumbers.length, "totalStakeIndices should be the number of quorums queried for"); - assertEq(checkSignaturesIndices.nonSignerStakeIndices.length, allInclusiveQuorumNumbers.length, "nonSignerStakeIndices should be the number of quorums queried for"); + assertEq( + checkSignaturesIndices.nonSignerQuorumBitmapIndices.length, + nonSignerOperatorIds.length, + "nonSignerQuorumBitmapIndices should be the number of nonsigners" + ); + assertEq( + checkSignaturesIndices.quorumApkIndices.length, + allInclusiveQuorumNumbers.length, + "quorumApkIndices should be the number of quorums queried for" + ); + assertEq( + checkSignaturesIndices.totalStakeIndices.length, + allInclusiveQuorumNumbers.length, + "totalStakeIndices should be the number of quorums queried for" + ); + assertEq( + checkSignaturesIndices.nonSignerStakeIndices.length, + allInclusiveQuorumNumbers.length, + "nonSignerStakeIndices should be the number of quorums queried for" + ); // assert the indices are the number of registered operators for the quorum minus 1 for (uint8 i = 0; i < allInclusiveQuorumNumbers.length; i++) { uint8 quorumNumber = uint8(allInclusiveQuorumNumbers[i]); - assertEq(checkSignaturesIndices.quorumApkIndices[i], expectedOperatorOverallIndices[quorumNumber].length, "quorumApkIndex should be the number of registered operators for the quorum"); - assertEq(checkSignaturesIndices.totalStakeIndices[i], expectedOperatorOverallIndices[quorumNumber].length, "totalStakeIndex should be the number of registered operators for the quorum"); + assertEq( + checkSignaturesIndices.quorumApkIndices[i], + expectedOperatorOverallIndices[quorumNumber].length, + "quorumApkIndex should be the number of registered operators for the quorum" + ); + assertEq( + checkSignaturesIndices.totalStakeIndices[i], + expectedOperatorOverallIndices[quorumNumber].length, + "totalStakeIndex should be the number of registered operators for the quorum" + ); } // assert the quorum bitmap and stake indices are zero because there have been no kicks or stake updates - for (uint i = 0; i < nonSignerOperatorIds.length; i++) { - assertEq(checkSignaturesIndices.nonSignerQuorumBitmapIndices[i], 0, "nonSignerQuorumBitmapIndices should be zero because there have been no kicks"); + for (uint256 i = 0; i < nonSignerOperatorIds.length; i++) { + assertEq( + checkSignaturesIndices.nonSignerQuorumBitmapIndices[i], + 0, + "nonSignerQuorumBitmapIndices should be zero because there have been no kicks" + ); } - for (uint i = 0; i < checkSignaturesIndices.nonSignerStakeIndices.length; i++) { - for (uint j = 0; j < checkSignaturesIndices.nonSignerStakeIndices[i].length; j++) { - assertEq(checkSignaturesIndices.nonSignerStakeIndices[i][j], 0, "nonSignerStakeIndices should be zero because there have been no stake updates past the first one"); + for (uint256 i = 0; i < checkSignaturesIndices.nonSignerStakeIndices.length; i++) { + for (uint256 j = 0; j < checkSignaturesIndices.nonSignerStakeIndices[i].length; j++) { + assertEq( + checkSignaturesIndices.nonSignerStakeIndices[i][j], + 0, + "nonSignerStakeIndices should be zero because there have been no stake updates past the first one" + ); } } } @@ -444,12 +593,16 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { address otherOperator = _incrementAddress(defaultOperator, 1); BN254.G1Point memory otherPubKey = BN254.G1Point(1, 2); bytes32 otherOperatorId = BN254.hashG1Point(otherPubKey); - _registerOperatorWithCoordinator(otherOperator, quorumBitmapThree, otherPubKey, defaultStake -1); + _registerOperatorWithCoordinator( + otherOperator, quorumBitmapThree, otherPubKey, defaultStake - 1 + ); bytes32[] memory operatorIds = new bytes32[](2); operatorIds[0] = defaultOperatorId; operatorIds[1] = otherOperatorId; - uint256[] memory quorumBitmaps = operatorStateRetriever.getQuorumBitmapsAtBlockNumber(registryCoordinator, operatorIds, uint32(block.number)); + uint256[] memory quorumBitmaps = operatorStateRetriever.getQuorumBitmapsAtBlockNumber( + registryCoordinator, operatorIds, uint32(block.number) + ); assertEq(quorumBitmaps.length, 2); assertEq(quorumBitmaps[0], quorumBitmapOne); @@ -463,11 +616,14 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { OperatorMetadata[] memory operatorMetadatas ) internal { // for each quorum - for (uint j = 0; j < quorumNumbers.length; j++) { + for (uint256 j = 0; j < quorumNumbers.length; j++) { // make sure the each operator id and stake is correct - for (uint k = 0; k < operators[j].length; k++) { + for (uint256 k = 0; k < operators[j].length; k++) { uint8 quorumNumber = uint8(quorumNumbers[j]); - assertEq(operators[j][k].operatorId, operatorMetadatas[expectedOperatorOverallIndices[quorumNumber][k]].operatorId); + assertEq( + operators[j][k].operatorId, + operatorMetadatas[expectedOperatorOverallIndices[quorumNumber][k]].operatorId + ); // using assertApprox to account for rounding errors assertApproxEqAbs( operators[j][k].stake, diff --git a/test/unit/RegistryCoordinatorUnit.t.sol b/test/unit/RegistryCoordinatorUnit.t.sol index c262b0da..cd67e7a2 100644 --- a/test/unit/RegistryCoordinatorUnit.t.sol +++ b/test/unit/RegistryCoordinatorUnit.t.sol @@ -192,12 +192,12 @@ contract RegistryCoordinatorUnitTests_Initialization_Setters is RegistryCoordina cheats.expectEmit(true, true, true, true, address(registryCoordinator)); emit OperatorSocketUpdate(defaultOperatorId, "localhost:32004"); registryCoordinator.updateSocket("localhost:32004"); - + assertEq(socketRegistry.getOperatorSocket(defaultOperatorId), "localhost:32004"); } function test_updateSocket_revert_notRegistered() public { cheats.prank(defaultOperator); - cheats.expectRevert("RegistryCoordinator.updateSocket: operator is not registered"); + cheats.expectRevert("RegCoord.updateSocket: operator not registered"); registryCoordinator.updateSocket("localhost:32004"); } @@ -268,7 +268,7 @@ contract RegistryCoordinatorUnitTests_RegisterOperator is RegistryCoordinatorUni bytes memory emptyQuorumNumbers = new bytes(0); ISignatureUtils.SignatureWithSaltAndExpiry memory emptySig; - cheats.expectRevert("RegistryCoordinator._registerOperator: bitmap cannot be 0"); + cheats.expectRevert("RegCoord._registerOperator: bitmap cannot be 0"); cheats.prank(defaultOperator); registryCoordinator.registerOperator(emptyQuorumNumbers, defaultSocket, pubkeyRegistrationParams, emptySig); } @@ -414,8 +414,8 @@ contract RegistryCoordinatorUnitTests_RegisterOperator is RegistryCoordinatorUni newQuorumNumbers[0] = bytes1(defaultQuorumNumber+1); uint96 actualStake = _setOperatorWeight(defaultOperator, uint8(newQuorumNumbers[0]), defaultStake); - cheats.expectEmit(true, true, true, true, address(registryCoordinator)); - emit OperatorSocketUpdate(defaultOperatorId, defaultSocket); + //cheats.expectEmit(true, true, true, true, address(registryCoordinator)); + //emit OperatorSocketUpdate(defaultOperatorId, defaultSocket); cheats.expectEmit(true, true, true, true, address(blsApkRegistry)); emit OperatorAddedToQuorums(defaultOperator, defaultOperatorId, newQuorumNumbers); cheats.expectEmit(true, true, true, true, address(stakeRegistry)); @@ -482,7 +482,7 @@ contract RegistryCoordinatorUnitTests_RegisterOperator is RegistryCoordinatorUni _setOperatorWeight(operatorToRegister, defaultQuorumNumber, defaultStake); cheats.prank(operatorToRegister); - cheats.expectRevert("RegistryCoordinator.registerOperator: operator count exceeds maximum"); + cheats.expectRevert("RegCoord.registerOperator: operator count exceeds maximum"); registryCoordinator.registerOperator(quorumNumbers, defaultSocket, pubkeyRegistrationParams, emptySig); } @@ -502,7 +502,7 @@ contract RegistryCoordinatorUnitTests_RegisterOperator is RegistryCoordinatorUni cheats.prank(defaultOperator); cheats.roll(nextRegistrationBlockNumber); - cheats.expectRevert("RegistryCoordinator._registerOperator: operator already registered for some quorums being registered for"); + cheats.expectRevert("RegCoord._registerOperator: operator already registered for some quorums"); registryCoordinator.registerOperator(quorumNumbers, defaultSocket, pubkeyRegistrationParams, emptySig); } @@ -512,7 +512,7 @@ contract RegistryCoordinatorUnitTests_RegisterOperator is RegistryCoordinatorUni bytes memory emptyQuorumNumbers = new bytes(0); ISignatureUtils.SignatureWithSaltAndExpiry memory emptySig; - cheats.expectRevert("RegistryCoordinator._registerOperator: bitmap cannot be 0"); + cheats.expectRevert("RegCoord._registerOperator: bitmap cannot be 0"); registryCoordinator._registerOperatorExternal(defaultOperator, defaultOperatorId, emptyQuorumNumbers, defaultSocket, emptySig); } @@ -534,7 +534,7 @@ contract RegistryCoordinatorUnitTests_RegisterOperator is RegistryCoordinatorUni _setOperatorWeight(defaultOperator, uint8(quorumNumbers[0]), defaultStake); registryCoordinator._registerOperatorExternal(defaultOperator, defaultOperatorId, quorumNumbers, defaultSocket, emptySig); - cheats.expectRevert("RegistryCoordinator._registerOperator: operator already registered for some quorums being registered for"); + cheats.expectRevert("RegCoord._registerOperator: operator already registered for some quorums"); registryCoordinator._registerOperatorExternal(defaultOperator, defaultOperatorId, quorumNumbers, defaultSocket, emptySig); } @@ -600,7 +600,7 @@ contract RegistryCoordinatorUnitTests_DeregisterOperator_EjectOperator is Regist bytes memory quorumNumbers = new bytes(1); quorumNumbers[0] = bytes1(defaultQuorumNumber); - cheats.expectRevert("RegistryCoordinator._deregisterOperator: operator is not registered"); + cheats.expectRevert("RegCoord._deregisterOperator: operator is not registered"); cheats.prank(defaultOperator); registryCoordinator.deregisterOperator(quorumNumbers); } @@ -616,7 +616,7 @@ contract RegistryCoordinatorUnitTests_DeregisterOperator_EjectOperator is Regist quorumNumbers[0] = bytes1(defaultQuorumNumber + 1); quorumNumbers[1] = bytes1(defaultQuorumNumber + 2); - cheats.expectRevert("RegistryCoordinator._deregisterOperator: operator is not registered for specified quorums"); + cheats.expectRevert("RegCoord._deregisterOperator: operator is not registered for quorums"); cheats.prank(defaultOperator); registryCoordinator.deregisterOperator(quorumNumbers); } @@ -956,13 +956,13 @@ contract RegistryCoordinatorUnitTests_DeregisterOperator_EjectOperator is Regist bytes memory emptyQuorumNumbers = new bytes(0); cheats.roll(deregistrationBlockNumber); - cheats.expectRevert("RegistryCoordinator._deregisterOperator: bitmap cannot be 0"); + cheats.expectRevert("RegCoord._deregisterOperator: bitmap cannot be 0"); registryCoordinator._deregisterOperatorExternal(defaultOperator, emptyQuorumNumbers); } function test_deregisterOperatorExternal_revert_notRegistered() public { bytes memory emptyQuorumNumbers = new bytes(0); - cheats.expectRevert("RegistryCoordinator._deregisterOperator: operator is not registered"); + cheats.expectRevert("RegCoord._deregisterOperator: operator is not registered"); registryCoordinator._deregisterOperatorExternal(defaultOperator, emptyQuorumNumbers); } @@ -984,7 +984,7 @@ contract RegistryCoordinatorUnitTests_DeregisterOperator_EjectOperator is Regist incorrectQuorum[0] = bytes1(defaultQuorumNumber + 1); cheats.roll(deregistrationBlockNumber); - cheats.expectRevert("RegistryCoordinator._deregisterOperator: operator is not registered for specified quorums"); + cheats.expectRevert("RegCoord._deregisterOperator: operator is not registered for quorums"); registryCoordinator._deregisterOperatorExternal(defaultOperator, incorrectQuorum); } @@ -1012,7 +1012,7 @@ contract RegistryCoordinatorUnitTests_DeregisterOperator_EjectOperator is Regist cheats.prank(defaultOperator); cheats.roll(reregistrationBlockNumber); - cheats.expectRevert("RegistryCoordinator._registerOperator: operator cannot reregister yet"); + cheats.expectRevert("RegCoord._registerOperator: operator cannot reregister yet"); registryCoordinator.registerOperator(quorumNumbers, defaultSocket, pubkeyRegistrationParams, emptySig); } @@ -1213,7 +1213,7 @@ contract RegistryCoordinatorUnitTests_DeregisterOperator_EjectOperator is Regist cheats.prank(defaultOperator); registryCoordinator.registerOperator(quorumNumbers, defaultSocket, pubkeyRegistrationParams, emptySig); - cheats.expectRevert("RegistryCoordinator.onlyEjector: caller is not the ejector"); + cheats.expectRevert("RegCoord.onlyEjector: caller is not the ejector"); cheats.prank(defaultOperator); registryCoordinator.ejectOperator(defaultOperator, quorumNumbers); } @@ -1221,7 +1221,7 @@ contract RegistryCoordinatorUnitTests_DeregisterOperator_EjectOperator is Regist function test_getQuorumBitmapIndicesAtBlockNumber_revert_notRegistered() public { uint32 blockNumber; bytes32[] memory operatorIds = new bytes32[](1); - cheats.expectRevert("RegistryCoordinator.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operatorId at block number"); + cheats.expectRevert("RegCoord.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operator at blockNumber"); registryCoordinator.getQuorumBitmapIndicesAtBlockNumber(blockNumber, operatorIds); } @@ -1242,7 +1242,7 @@ contract RegistryCoordinatorUnitTests_DeregisterOperator_EjectOperator is Regist operatorIds[0] = defaultOperatorId; uint32[] memory returnArray; - cheats.expectRevert("RegistryCoordinator.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operatorId at block number"); + cheats.expectRevert("RegCoord.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operator at blockNumber"); registryCoordinator.getQuorumBitmapIndicesAtBlockNumber(blockNumber, operatorIds); blockNumber = registrationBlockNumber; @@ -1264,7 +1264,7 @@ contract RegistryCoordinatorUnitTests_DeregisterOperator_EjectOperator is Regist operatorIds[0] = defaultOperatorId; uint32[] memory returnArray; - cheats.expectRevert("RegistryCoordinator.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operatorId at block number"); + cheats.expectRevert("RegCoord.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operator at blockNumber"); registryCoordinator.getQuorumBitmapIndicesAtBlockNumber(blockNumber, operatorIds); blockNumber = registrationBlockNumber; @@ -1297,7 +1297,7 @@ contract RegistryCoordinatorUnitTests_DeregisterOperator_EjectOperator is Regist uint192 emptyBitmap = 0; // try an incorrect blockNumber input and confirm reversion - cheats.expectRevert("RegistryCoordinator.getQuorumBitmapAtBlockNumberByIndex: quorumBitmapUpdate is from after blockNumber"); + cheats.expectRevert("RegCoord.getQuorumBitmapAtBlockNumberByIndex: quorumBitmapUpdate is from after blockNumber"); uint192 returnVal = registryCoordinator.getQuorumBitmapAtBlockNumberByIndex(operatorId, blockNumber, index); blockNumber = registrationBlockNumber; @@ -1310,7 +1310,7 @@ contract RegistryCoordinatorUnitTests_DeregisterOperator_EjectOperator is Regist // try an incorrect index input and confirm reversion index = 1; - cheats.expectRevert("RegistryCoordinator.getQuorumBitmapAtBlockNumberByIndex: quorumBitmapUpdate is from after blockNumber"); + cheats.expectRevert("RegCoord.getQuorumBitmapAtBlockNumberByIndex: quorumBitmapUpdate is from after blockNumber"); returnVal = registryCoordinator.getQuorumBitmapAtBlockNumberByIndex(operatorId, blockNumber, index); blockNumber = deregistrationBlockNumber; @@ -1323,7 +1323,7 @@ contract RegistryCoordinatorUnitTests_DeregisterOperator_EjectOperator is Regist // try an incorrect index input and confirm reversion index = 0; - cheats.expectRevert("RegistryCoordinator.getQuorumBitmapAtBlockNumberByIndex: quorumBitmapUpdate is from before blockNumber"); + cheats.expectRevert("RegCoord.getQuorumBitmapAtBlockNumberByIndex: quorumBitmapUpdate is from before blockNumber"); returnVal = registryCoordinator.getQuorumBitmapAtBlockNumberByIndex(operatorId, blockNumber, index); } } @@ -1462,7 +1462,7 @@ contract RegistryCoordinatorUnitTests_RegisterOperatorWithChurn is RegistryCoord ISignatureUtils.SignatureWithSaltAndExpiry memory signatureWithExpiry = _signOperatorChurnApproval(operatorToRegister, operatorToRegisterId, operatorKickParams, defaultSalt, block.timestamp + 10); cheats.prank(operatorToRegister); - cheats.expectRevert("RegistryCoordinator._validateChurn: incoming operator has insufficient stake for churn"); + cheats.expectRevert("RegCoord._validateChurn: incoming operator has insufficient stake for churn"); registryCoordinator.registerOperatorWithChurn( quorumNumbers, defaultSocket, @@ -1494,7 +1494,7 @@ contract RegistryCoordinatorUnitTests_RegisterOperatorWithChurn is RegistryCoord ISignatureUtils.SignatureWithSaltAndExpiry memory signatureWithExpiry = _signOperatorChurnApproval(operatorToRegister, operatorToRegisterId, operatorKickParams, defaultSalt, block.timestamp + 10); cheats.prank(operatorToRegister); - cheats.expectRevert("RegistryCoordinator._validateChurn: cannot kick operator with more than kickBIPsOfTotalStake"); + cheats.expectRevert("RegCoord._validateChurn: cannot kick operator with more than kickBIPsOfTotalStake"); registryCoordinator.registerOperatorWithChurn( quorumNumbers, defaultSocket, @@ -1556,7 +1556,7 @@ contract RegistryCoordinatorUnitTests_RegisterOperatorWithChurn is RegistryCoord ISignatureUtils.SignatureWithSaltAndExpiry memory signatureWithSaltAndExpiry = _signOperatorChurnApproval(operatorToRegister, operatorToRegisterId, operatorKickParams, defaultSalt, block.timestamp - 1); cheats.prank(operatorToRegister); - cheats.expectRevert("RegistryCoordinator._verifyChurnApproverSignature: churnApprover signature expired"); + cheats.expectRevert("RegCoord._verifyChurnApproverSignature: churnApprover signature expired"); registryCoordinator.registerOperatorWithChurn( quorumNumbers, defaultSocket, @@ -1677,7 +1677,7 @@ contract RegistryCoordinatorUnitTests_UpdateOperators is RegistryCoordinatorUnit bytes memory quorumNumbers = new bytes(1); quorumNumbers[0] = bytes1(defaultQuorumNumber); - cheats.expectRevert(bytes("RegistryCoordinator.updateOperatorsForQuorum: input length mismatch")); + cheats.expectRevert(bytes("RegCoord.updateOperatorsForQuorum: input length mismatch")); registryCoordinator.updateOperatorsForQuorum(operatorsToUpdate, quorumNumbers); } @@ -1689,7 +1689,7 @@ contract RegistryCoordinatorUnitTests_UpdateOperators is RegistryCoordinatorUnit bytes memory quorumNumbers = new bytes(1); quorumNumbers[0] = bytes1(defaultQuorumNumber); - cheats.expectRevert(bytes("RegistryCoordinator.updateOperatorsForQuorum: number of updated operators does not match quorum total")); + cheats.expectRevert(bytes("RegCoord.updateOperatorsForQuorum: number of updated operators does not match quorum total")); registryCoordinator.updateOperatorsForQuorum(operatorsToUpdate, quorumNumbers); } @@ -1710,7 +1710,7 @@ contract RegistryCoordinatorUnitTests_UpdateOperators is RegistryCoordinatorUnit operatorArray[0] = _incrementAddress(defaultOperator, 1); operatorsToUpdate[0] = operatorArray; - cheats.expectRevert(bytes("RegistryCoordinator.updateOperatorsForQuorum: operator not in quorum")); + cheats.expectRevert(bytes("RegCoord.updateOperatorsForQuorum: operator not in quorum")); registryCoordinator.updateOperatorsForQuorum(operatorsToUpdate, quorumNumbers); } @@ -1738,7 +1738,7 @@ contract RegistryCoordinatorUnitTests_UpdateOperators is RegistryCoordinatorUnit operatorsToUpdate[0] = operatorArray; // note: there is not an explicit check for duplicates, as checking for explicit ordering covers this - cheats.expectRevert(bytes("RegistryCoordinator.updateOperatorsForQuorum: operators array must be sorted in ascending address order")); + cheats.expectRevert(bytes("RegCoord.updateOperatorsForQuorum: operators array must be sorted in ascending address order")); registryCoordinator.updateOperatorsForQuorum(operatorsToUpdate, quorumNumbers); } @@ -1764,7 +1764,7 @@ contract RegistryCoordinatorUnitTests_UpdateOperators is RegistryCoordinatorUnit operatorArray[1] = defaultOperator; operatorsToUpdate[0] = operatorArray; - cheats.expectRevert(bytes("RegistryCoordinator.updateOperatorsForQuorum: operators array must be sorted in ascending address order")); + cheats.expectRevert(bytes("RegCoord.updateOperatorsForQuorum: operators array must be sorted in ascending address order")); registryCoordinator.updateOperatorsForQuorum(operatorsToUpdate, quorumNumbers); } diff --git a/test/unit/ServiceManagerBase.t.sol b/test/unit/ServiceManagerBase.t.sol index 2b04dabd..a7f6464a 100644 --- a/test/unit/ServiceManagerBase.t.sol +++ b/test/unit/ServiceManagerBase.t.sol @@ -2,19 +2,19 @@ pragma solidity ^0.8.12; import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol"; -import { - RewardsCoordinator, - IRewardsCoordinator, - IERC20 -} from "eigenlayer-contracts/src/contracts/core/RewardsCoordinator.sol"; +import {RewardsCoordinator, IRewardsCoordinator, IERC20} from "eigenlayer-contracts/src/contracts/core/RewardsCoordinator.sol"; import {StrategyBase} from "eigenlayer-contracts/src/contracts/strategies/StrategyBase.sol"; import {IServiceManagerBaseEvents} from "../events/IServiceManagerBaseEvents.sol"; import "../utils/MockAVSDeployer.sol"; -contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEvents { +contract ServiceManagerBase_UnitTests is + MockAVSDeployer, + IServiceManagerBaseEvents +{ // RewardsCoordinator config - address rewardsUpdater = address(uint160(uint256(keccak256("rewardsUpdater")))); + address rewardsUpdater = + address(uint160(uint256(keccak256("rewardsUpdater")))); uint32 CALCULATION_INTERVAL_SECONDS = 7 days; uint32 MAX_REWARDS_DURATION = 70 days; uint32 MAX_RETROACTIVE_LENGTH = 84 days; @@ -28,7 +28,8 @@ contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEve // Testing Config and Mocks address serviceManagerOwner; - address rewardsInitiator = address(uint160(uint256(keccak256("rewardsInitiator")))); + address rewardsInitiator = + address(uint160(uint256(keccak256("rewardsInitiator")))); IERC20[] rewardTokens; uint256 mockTokenInitialSupply = 10e50; IStrategy strategyMock1; @@ -67,7 +68,7 @@ contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEve RewardsCoordinator.initialize.selector, msg.sender, pauserRegistry, - 0, /*initialPausedStatus*/ + 0 /*initialPausedStatus*/, rewardsUpdater, activationDelay, globalCommissionBips @@ -89,7 +90,9 @@ contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEve address(serviceManagerImplementation), address(proxyAdmin), abi.encodeWithSelector( - ServiceManagerMock.initialize.selector, msg.sender, msg.sender + ServiceManagerMock.initialize.selector, + msg.sender, + msg.sender ) ) ) @@ -108,11 +111,18 @@ contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEve } /// @notice deploy token to owner and approve ServiceManager. Used for deploying reward tokens - function _deployMockRewardTokens(address owner, uint256 numTokens) internal virtual { + function _deployMockRewardTokens( + address owner, + uint256 numTokens + ) internal virtual { cheats.startPrank(owner); for (uint256 i = 0; i < numTokens; ++i) { - IERC20 token = - new ERC20PresetFixedSupply("dog wif hat", "MOCK1", mockTokenInitialSupply, owner); + IERC20 token = new ERC20PresetFixedSupply( + "dog wif hat", + "MOCK1", + mockTokenInitialSupply, + owner + ); rewardTokens.push(token); token.approve(address(serviceManager), mockTokenInitialSupply); } @@ -133,12 +143,22 @@ contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEve function _setUpDefaultStrategiesAndMultipliers() internal virtual { // Deploy Mock Strategies IERC20 token1 = new ERC20PresetFixedSupply( - "dog wif hat", "MOCK1", mockTokenInitialSupply, address(this) + "dog wif hat", + "MOCK1", + mockTokenInitialSupply, + address(this) + ); + IERC20 token2 = new ERC20PresetFixedSupply( + "jeo boden", + "MOCK2", + mockTokenInitialSupply, + address(this) ); - IERC20 token2 = - new ERC20PresetFixedSupply("jeo boden", "MOCK2", mockTokenInitialSupply, address(this)); IERC20 token3 = new ERC20PresetFixedSupply( - "pepe wif avs", "MOCK3", mockTokenInitialSupply, address(this) + "pepe wif avs", + "MOCK3", + mockTokenInitialSupply, + address(this) ); strategyImplementation = new StrategyBase(strategyManagerMock); strategyMock1 = StrategyBase( @@ -146,7 +166,11 @@ contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEve new TransparentUpgradeableProxy( address(strategyImplementation), address(proxyAdmin), - abi.encodeWithSelector(StrategyBase.initialize.selector, token1, pauserRegistry) + abi.encodeWithSelector( + StrategyBase.initialize.selector, + token1, + pauserRegistry + ) ) ) ); @@ -155,7 +179,11 @@ contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEve new TransparentUpgradeableProxy( address(strategyImplementation), address(proxyAdmin), - abi.encodeWithSelector(StrategyBase.initialize.selector, token2, pauserRegistry) + abi.encodeWithSelector( + StrategyBase.initialize.selector, + token2, + pauserRegistry + ) ) ) ); @@ -164,7 +192,11 @@ contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEve new TransparentUpgradeableProxy( address(strategyImplementation), address(proxyAdmin), - abi.encodeWithSelector(StrategyBase.initialize.selector, token3, pauserRegistry) + abi.encodeWithSelector( + StrategyBase.initialize.selector, + token3, + pauserRegistry + ) ) ) ); @@ -179,18 +211,29 @@ contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEve strategyManagerMock.setStrategyWhitelist(strategies[2], true); defaultStrategyAndMultipliers.push( - IRewardsCoordinator.StrategyAndMultiplier(IStrategy(address(strategies[0])), 1e18) + IRewardsCoordinator.StrategyAndMultiplier( + IStrategy(address(strategies[0])), + 1e18 + ) ); defaultStrategyAndMultipliers.push( - IRewardsCoordinator.StrategyAndMultiplier(IStrategy(address(strategies[1])), 2e18) + IRewardsCoordinator.StrategyAndMultiplier( + IStrategy(address(strategies[1])), + 2e18 + ) ); defaultStrategyAndMultipliers.push( - IRewardsCoordinator.StrategyAndMultiplier(IStrategy(address(strategies[2])), 3e18) + IRewardsCoordinator.StrategyAndMultiplier( + IStrategy(address(strategies[2])), + 3e18 + ) ); } /// @dev Sort to ensure that the array is in ascending order for strategies - function _sortArrayAsc(IStrategy[] memory arr) internal pure returns (IStrategy[] memory) { + function _sortArrayAsc( + IStrategy[] memory arr + ) internal pure returns (IStrategy[] memory) { uint256 l = arr.length; for (uint256 i = 0; i < l; i++) { for (uint256 j = i + 1; j < l; j++) { @@ -204,14 +247,16 @@ contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEve return arr; } - function _maxTimestamp(uint32 timestamp1, uint32 timestamp2) internal pure returns (uint32) { + function _maxTimestamp( + uint32 timestamp1, + uint32 timestamp2 + ) internal pure returns (uint32) { return timestamp1 > timestamp2 ? timestamp1 : timestamp2; } - function testFuzz_createAVSRewardsSubmission_Revert_WhenNotOwner(address caller) - public - filterFuzzedAddressInputs(caller) - { + function testFuzz_createAVSRewardsSubmission_Revert_WhenNotOwner( + address caller + ) public filterFuzzedAddressInputs(caller) { cheats.assume(caller != rewardsInitiator); IRewardsCoordinator.RewardsSubmission[] memory rewardsSubmissions; @@ -222,13 +267,20 @@ contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEve serviceManager.createAVSRewardsSubmission(rewardsSubmissions); } - function test_createAVSRewardsSubmission_Revert_WhenERC20NotApproved() public { + function test_createAVSRewardsSubmission_Revert_WhenERC20NotApproved() + public + { IERC20 token = new ERC20PresetFixedSupply( - "dog wif hat", "MOCK1", mockTokenInitialSupply, rewardsInitiator + "dog wif hat", + "MOCK1", + mockTokenInitialSupply, + rewardsInitiator ); - IRewardsCoordinator.RewardsSubmission[] memory rewardsSubmissions = - new IRewardsCoordinator.RewardsSubmission[](1); + IRewardsCoordinator.RewardsSubmission[] + memory rewardsSubmissions = new IRewardsCoordinator.RewardsSubmission[]( + 1 + ); rewardsSubmissions[0] = IRewardsCoordinator.RewardsSubmission({ strategiesAndMultipliers: defaultStrategyAndMultipliers, token: token, @@ -249,7 +301,10 @@ contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEve ) public { // 1. Bound fuzz inputs to valid ranges and amounts IERC20 rewardToken = new ERC20PresetFixedSupply( - "dog wif hat", "MOCK1", mockTokenInitialSupply, rewardsInitiator + "dog wif hat", + "MOCK1", + mockTokenInitialSupply, + rewardsInitiator ); amount = bound(amount, 1, MAX_REWARDS_AMOUNT); duration = bound(duration, 0, MAX_REWARDS_DURATION); @@ -258,16 +313,23 @@ contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEve startTimestamp, uint256( _maxTimestamp( - GENESIS_REWARDS_TIMESTAMP, uint32(block.timestamp) - MAX_RETROACTIVE_LENGTH + GENESIS_REWARDS_TIMESTAMP, + uint32(block.timestamp) - MAX_RETROACTIVE_LENGTH ) - ) + CALCULATION_INTERVAL_SECONDS - 1, + ) + + CALCULATION_INTERVAL_SECONDS - + 1, block.timestamp + uint256(MAX_FUTURE_LENGTH) ); - startTimestamp = startTimestamp - (startTimestamp % CALCULATION_INTERVAL_SECONDS); + startTimestamp = + startTimestamp - + (startTimestamp % CALCULATION_INTERVAL_SECONDS); // 2. Create reward submission input param - IRewardsCoordinator.RewardsSubmission[] memory rewardsSubmissions = - new IRewardsCoordinator.RewardsSubmission[](1); + IRewardsCoordinator.RewardsSubmission[] + memory rewardsSubmissions = new IRewardsCoordinator.RewardsSubmission[]( + 1 + ); rewardsSubmissions[0] = IRewardsCoordinator.RewardsSubmission({ strategiesAndMultipliers: defaultStrategyAndMultipliers, token: rewardToken, @@ -281,25 +343,40 @@ contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEve rewardToken.approve(address(serviceManager), amount); // 4. call createAVSRewardsSubmission() with expected event emitted - uint256 rewardsInitiatorBalanceBefore = - rewardToken.balanceOf(address(rewardsInitiator)); - uint256 rewardsCoordinatorBalanceBefore = - rewardToken.balanceOf(address(rewardsCoordinator)); + uint256 rewardsInitiatorBalanceBefore = rewardToken.balanceOf( + address(rewardsInitiator) + ); + uint256 rewardsCoordinatorBalanceBefore = rewardToken.balanceOf( + address(rewardsCoordinator) + ); rewardToken.approve(address(rewardsCoordinator), amount); - uint256 currSubmissionNonce = rewardsCoordinator.submissionNonce(address(serviceManager)); - bytes32 avsSubmissionHash = - keccak256(abi.encode(address(serviceManager), currSubmissionNonce, rewardsSubmissions[0])); + uint256 currSubmissionNonce = rewardsCoordinator.submissionNonce( + address(serviceManager) + ); + bytes32 avsSubmissionHash = keccak256( + abi.encode( + address(serviceManager), + currSubmissionNonce, + rewardsSubmissions[0] + ) + ); cheats.expectEmit(true, true, true, true, address(rewardsCoordinator)); emit AVSRewardsSubmissionCreated( - address(serviceManager), currSubmissionNonce, avsSubmissionHash, rewardsSubmissions[0] + address(serviceManager), + currSubmissionNonce, + avsSubmissionHash, + rewardsSubmissions[0] ); serviceManager.createAVSRewardsSubmission(rewardsSubmissions); cheats.stopPrank(); assertTrue( - rewardsCoordinator.isAVSRewardsSubmissionHash(address(serviceManager), avsSubmissionHash), + rewardsCoordinator.isAVSRewardsSubmissionHash( + address(serviceManager), + avsSubmissionHash + ), "reward submission hash not submitted" ); assertEq( @@ -328,16 +405,25 @@ contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEve cheats.assume(2 <= numSubmissions && numSubmissions <= 10); cheats.prank(rewardsCoordinator.owner()); - IRewardsCoordinator.RewardsSubmission[] memory rewardsSubmissions = - new IRewardsCoordinator.RewardsSubmission[](numSubmissions); + IRewardsCoordinator.RewardsSubmission[] + memory rewardsSubmissions = new IRewardsCoordinator.RewardsSubmission[]( + numSubmissions + ); bytes32[] memory avsSubmissionHashes = new bytes32[](numSubmissions); - uint256 startSubmissionNonce = rewardsCoordinator.submissionNonce(address(serviceManager)); + uint256 startSubmissionNonce = rewardsCoordinator.submissionNonce( + address(serviceManager) + ); _deployMockRewardTokens(rewardsInitiator, numSubmissions); - uint256[] memory avsBalancesBefore = - _getBalanceForTokens(rewardTokens, rewardsInitiator); - uint256[] memory rewardsCoordinatorBalancesBefore = - _getBalanceForTokens(rewardTokens, address(rewardsCoordinator)); + uint256[] memory avsBalancesBefore = _getBalanceForTokens( + rewardTokens, + rewardsInitiator + ); + uint256[] + memory rewardsCoordinatorBalancesBefore = _getBalanceForTokens( + rewardTokens, + address(rewardsCoordinator) + ); uint256[] memory amounts = new uint256[](numSubmissions); // Create multiple rewards submissions and their expected event @@ -351,28 +437,45 @@ contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEve startTimestamp + i, uint256( _maxTimestamp( - GENESIS_REWARDS_TIMESTAMP, uint32(block.timestamp) - MAX_RETROACTIVE_LENGTH + GENESIS_REWARDS_TIMESTAMP, + uint32(block.timestamp) - MAX_RETROACTIVE_LENGTH ) - ) + CALCULATION_INTERVAL_SECONDS - 1, + ) + + CALCULATION_INTERVAL_SECONDS - + 1, block.timestamp + uint256(MAX_FUTURE_LENGTH) ); - startTimestamp = startTimestamp - (startTimestamp % CALCULATION_INTERVAL_SECONDS); + startTimestamp = + startTimestamp - + (startTimestamp % CALCULATION_INTERVAL_SECONDS); // 2. Create reward submission input param - IRewardsCoordinator.RewardsSubmission memory rewardsSubmission = IRewardsCoordinator.RewardsSubmission({ - strategiesAndMultipliers: defaultStrategyAndMultipliers, - token: rewardTokens[i], - amount: amounts[i], - startTimestamp: uint32(startTimestamp), - duration: uint32(duration) - }); + IRewardsCoordinator.RewardsSubmission + memory rewardsSubmission = IRewardsCoordinator + .RewardsSubmission({ + strategiesAndMultipliers: defaultStrategyAndMultipliers, + token: rewardTokens[i], + amount: amounts[i], + startTimestamp: uint32(startTimestamp), + duration: uint32(duration) + }); rewardsSubmissions[i] = rewardsSubmission; // 3. expected event emitted for this rewardsSubmission avsSubmissionHashes[i] = keccak256( - abi.encode(address(serviceManager), startSubmissionNonce + i, rewardsSubmissions[i]) + abi.encode( + address(serviceManager), + startSubmissionNonce + i, + rewardsSubmissions[i] + ) + ); + cheats.expectEmit( + true, + true, + true, + true, + address(rewardsCoordinator) ); - cheats.expectEmit(true, true, true, true, address(rewardsCoordinator)); emit AVSRewardsSubmissionCreated( address(serviceManager), startSubmissionNonce + i, @@ -395,7 +498,8 @@ contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEve for (uint256 i = 0; i < numSubmissions; ++i) { assertTrue( rewardsCoordinator.isAVSRewardsSubmissionHash( - address(serviceManager), avsSubmissionHashes[i] + address(serviceManager), + avsSubmissionHashes[i] ), "rewards submission hash not submitted" ); @@ -421,18 +525,26 @@ contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEve cheats.assume(2 <= numSubmissions && numSubmissions <= 10); cheats.prank(rewardsCoordinator.owner()); - IRewardsCoordinator.RewardsSubmission[] memory rewardsSubmissions = - new IRewardsCoordinator.RewardsSubmission[](numSubmissions); + IRewardsCoordinator.RewardsSubmission[] + memory rewardsSubmissions = new IRewardsCoordinator.RewardsSubmission[]( + numSubmissions + ); bytes32[] memory avsSubmissionHashes = new bytes32[](numSubmissions); - uint256 startSubmissionNonce = rewardsCoordinator.submissionNonce(address(serviceManager)); + uint256 startSubmissionNonce = rewardsCoordinator.submissionNonce( + address(serviceManager) + ); IERC20 rewardToken = new ERC20PresetFixedSupply( - "dog wif hat", "MOCK1", mockTokenInitialSupply, rewardsInitiator + "dog wif hat", + "MOCK1", + mockTokenInitialSupply, + rewardsInitiator ); cheats.prank(rewardsInitiator); rewardToken.approve(address(serviceManager), mockTokenInitialSupply); uint256 avsBalanceBefore = rewardToken.balanceOf(rewardsInitiator); - uint256 rewardsCoordinatorBalanceBefore = - rewardToken.balanceOf(address(rewardsCoordinator)); + uint256 rewardsCoordinatorBalanceBefore = rewardToken.balanceOf( + address(rewardsCoordinator) + ); uint256 totalAmount = 0; uint256[] memory amounts = new uint256[](numSubmissions); @@ -449,28 +561,45 @@ contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEve startTimestamp + i, uint256( _maxTimestamp( - GENESIS_REWARDS_TIMESTAMP, uint32(block.timestamp) - MAX_RETROACTIVE_LENGTH + GENESIS_REWARDS_TIMESTAMP, + uint32(block.timestamp) - MAX_RETROACTIVE_LENGTH ) - ) + CALCULATION_INTERVAL_SECONDS - 1, + ) + + CALCULATION_INTERVAL_SECONDS - + 1, block.timestamp + uint256(MAX_FUTURE_LENGTH) ); - startTimestamp = startTimestamp - (startTimestamp % CALCULATION_INTERVAL_SECONDS); + startTimestamp = + startTimestamp - + (startTimestamp % CALCULATION_INTERVAL_SECONDS); // 2. Create reward submission input param - IRewardsCoordinator.RewardsSubmission memory rewardsSubmission = IRewardsCoordinator.RewardsSubmission({ - strategiesAndMultipliers: defaultStrategyAndMultipliers, - token: rewardToken, - amount: amounts[i], - startTimestamp: uint32(startTimestamp), - duration: uint32(duration) - }); + IRewardsCoordinator.RewardsSubmission + memory rewardsSubmission = IRewardsCoordinator + .RewardsSubmission({ + strategiesAndMultipliers: defaultStrategyAndMultipliers, + token: rewardToken, + amount: amounts[i], + startTimestamp: uint32(startTimestamp), + duration: uint32(duration) + }); rewardsSubmissions[i] = rewardsSubmission; // 3. expected event emitted for this avs rewards submission avsSubmissionHashes[i] = keccak256( - abi.encode(address(serviceManager), startSubmissionNonce + i, rewardsSubmissions[i]) + abi.encode( + address(serviceManager), + startSubmissionNonce + i, + rewardsSubmissions[i] + ) + ); + cheats.expectEmit( + true, + true, + true, + true, + address(rewardsCoordinator) ); - cheats.expectEmit(true, true, true, true, address(rewardsCoordinator)); emit AVSRewardsSubmissionCreated( address(serviceManager), startSubmissionNonce + i, @@ -503,7 +632,8 @@ contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEve for (uint256 i = 0; i < numSubmissions; ++i) { assertTrue( rewardsCoordinator.isAVSRewardsSubmissionHash( - address(serviceManager), avsSubmissionHashes[i] + address(serviceManager), + avsSubmissionHashes[i] ), "rewards submission hash not submitted" ); @@ -511,7 +641,9 @@ contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEve } function test_setRewardsInitiator() public { - address newRewardsInitiator = address(uint160(uint256(keccak256("newRewardsInitiator")))); + address newRewardsInitiator = address( + uint160(uint256(keccak256("newRewardsInitiator"))) + ); cheats.prank(serviceManagerOwner); serviceManager.setRewardsInitiator(newRewardsInitiator); assertEq(newRewardsInitiator, serviceManager.rewardsInitiator()); @@ -519,9 +651,432 @@ contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEve function test_setRewardsInitiator_revert_notOwner() public { address caller = address(uint160(uint256(keccak256("caller")))); - address newRewardsInitiator = address(uint160(uint256(keccak256("newRewardsInitiator")))); + address newRewardsInitiator = address( + uint160(uint256(keccak256("newRewardsInitiator"))) + ); cheats.expectRevert("Ownable: caller is not the owner"); cheats.prank(caller); serviceManager.setRewardsInitiator(newRewardsInitiator); } + + function testFuzz_setClaimerFor(address claimer) public { + cheats.startPrank(serviceManagerOwner); + cheats.expectEmit(true, true, true, true, address(rewardsCoordinator)); + emit ClaimerForSet( + address(serviceManager), + rewardsCoordinator.claimerFor(address(serviceManager)), + claimer + ); + serviceManager.setClaimerFor(claimer); + assertEq( + claimer, + rewardsCoordinator.claimerFor(address(serviceManager)), + "claimerFor not set" + ); + cheats.stopPrank(); + } + + function testFuzz_setClaimerFor_revert_notOwner( + address caller, + address claimer + ) public filterFuzzedAddressInputs(caller) { + cheats.assume(caller != serviceManagerOwner); + cheats.prank(caller); + cheats.expectRevert("Ownable: caller is not the owner"); + serviceManager.setClaimerFor(claimer); + } +} + +contract ServiceManagerBase_createOperatorDirectedAVSRewardsSubmission is + ServiceManagerBase_UnitTests +{ + // used for stack too deep + struct FuzzOperatorDirectedAVSRewardsSubmission { + uint256 startTimestamp; + uint256 duration; + } + + IRewardsCoordinator.OperatorReward[] defaultOperatorRewards; + + function setUp() public virtual override { + ServiceManagerBase_UnitTests.setUp(); + + address[] memory operators = new address[](3); + operators[0] = makeAddr("operator1"); + operators[1] = makeAddr("operator2"); + operators[2] = makeAddr("operator3"); + operators = _sortAddressArrayAsc(operators); + + defaultOperatorRewards.push( + IRewardsCoordinator.OperatorReward(operators[0], 1e18) + ); + defaultOperatorRewards.push( + IRewardsCoordinator.OperatorReward(operators[1], 2e18) + ); + defaultOperatorRewards.push( + IRewardsCoordinator.OperatorReward(operators[2], 3e18) + ); + + // Set the timestamp to when Rewards v2 will realisticly go out (i.e 6 months) + cheats.warp(GENESIS_REWARDS_TIMESTAMP + 168 days); + } + + /// @dev Sort to ensure that the array is in ascending order for addresses + function _sortAddressArrayAsc( + address[] memory arr + ) internal pure returns (address[] memory) { + uint256 l = arr.length; + for (uint256 i = 0; i < l; i++) { + for (uint256 j = i + 1; j < l; j++) { + if (arr[i] > arr[j]) { + address temp = arr[i]; + arr[i] = arr[j]; + arr[j] = temp; + } + } + } + return arr; + } + + function _getTotalRewardsAmount( + IRewardsCoordinator.OperatorReward[] memory operatorRewards + ) internal pure returns (uint256) { + uint256 totalAmount = 0; + for (uint256 i = 0; i < operatorRewards.length; ++i) { + totalAmount += operatorRewards[i].amount; + } + return totalAmount; + } + + function testFuzz_createOperatorDirectedAVSRewardsSubmission_Revert_WhenNotOwner( + address caller + ) public filterFuzzedAddressInputs(caller) { + cheats.assume(caller != rewardsInitiator); + IRewardsCoordinator.OperatorDirectedRewardsSubmission[] + memory operatorDirectedRewardsSubmissions; + + cheats.prank(caller); + cheats.expectRevert( + "ServiceManagerBase.onlyRewardsInitiator: caller is not the rewards initiator" + ); + serviceManager.createOperatorDirectedAVSRewardsSubmission( + operatorDirectedRewardsSubmissions + ); + } + + function testFuzz_createOperatorDirectedAVSRewardsSubmission_Revert_WhenERC20NotApproved( + uint256 startTimestamp, + uint256 duration + ) public { + // 1. Bound fuzz inputs to valid ranges and amounts + IERC20 rewardToken = new ERC20PresetFixedSupply( + "dog wif hat", + "MOCK1", + mockTokenInitialSupply, + rewardsInitiator + ); + duration = bound(duration, 0, MAX_REWARDS_DURATION); + duration = duration - (duration % CALCULATION_INTERVAL_SECONDS); + startTimestamp = bound( + startTimestamp, + uint256( + _maxTimestamp( + GENESIS_REWARDS_TIMESTAMP, + uint32(block.timestamp) - MAX_RETROACTIVE_LENGTH + ) + ) + + CALCULATION_INTERVAL_SECONDS - + 1, + block.timestamp - duration - 1 + ); + startTimestamp = + startTimestamp - + (startTimestamp % CALCULATION_INTERVAL_SECONDS); + + // 2. Create operator directed rewards submission input param + IRewardsCoordinator.OperatorDirectedRewardsSubmission[] + memory operatorDirectedRewardsSubmissions = new IRewardsCoordinator.OperatorDirectedRewardsSubmission[]( + 1 + ); + operatorDirectedRewardsSubmissions[0] = IRewardsCoordinator + .OperatorDirectedRewardsSubmission({ + strategiesAndMultipliers: defaultStrategyAndMultipliers, + token: rewardToken, + operatorRewards: defaultOperatorRewards, + startTimestamp: uint32(startTimestamp), + duration: uint32(duration), + description: "" + }); + + // 3. Call createOperatorDirectedAVSRewardsSubmission() + cheats.prank(rewardsInitiator); + cheats.expectRevert("ERC20: insufficient allowance"); + serviceManager.createOperatorDirectedAVSRewardsSubmission( + operatorDirectedRewardsSubmissions + ); + } + + /** + * @notice test a single rewards submission asserting for the following + * - correct event emitted + * - submission nonce incrementation by 1, and rewards submission hash being set in storage. + * - rewards submission hash being set in storage + * - token balance before and after of rewards initiator and rewardsCoordinator + */ + function testFuzz_createOperatorDirectedAVSRewardsSubmission_SingleSubmission( + uint256 startTimestamp, + uint256 duration + ) public { + // 1. Bound fuzz inputs to valid ranges and amounts + IERC20 rewardToken = new ERC20PresetFixedSupply( + "dog wif hat", + "MOCK1", + mockTokenInitialSupply, + rewardsInitiator + ); + duration = bound(duration, 0, MAX_REWARDS_DURATION); + duration = duration - (duration % CALCULATION_INTERVAL_SECONDS); + startTimestamp = bound( + startTimestamp, + uint256( + _maxTimestamp( + GENESIS_REWARDS_TIMESTAMP, + uint32(block.timestamp) - MAX_RETROACTIVE_LENGTH + ) + ) + + CALCULATION_INTERVAL_SECONDS - + 1, + block.timestamp - duration - 1 + ); + startTimestamp = + startTimestamp - + (startTimestamp % CALCULATION_INTERVAL_SECONDS); + + // 2. Create operator directed rewards submission input param + IRewardsCoordinator.OperatorDirectedRewardsSubmission[] + memory operatorDirectedRewardsSubmissions = new IRewardsCoordinator.OperatorDirectedRewardsSubmission[]( + 1 + ); + operatorDirectedRewardsSubmissions[0] = IRewardsCoordinator + .OperatorDirectedRewardsSubmission({ + strategiesAndMultipliers: defaultStrategyAndMultipliers, + token: rewardToken, + operatorRewards: defaultOperatorRewards, + startTimestamp: uint32(startTimestamp), + duration: uint32(duration), + description: "" + }); + + // 3. Get total amount + uint256 amount = _getTotalRewardsAmount(defaultOperatorRewards); + + // 4. Approve serviceManager for ERC20 + cheats.startPrank(rewardsInitiator); + rewardToken.approve(address(serviceManager), amount); + + // 3. call createOperatorDirectedAVSRewardsSubmission() with expected event emitted + uint256 rewardsInitiatorBalanceBefore = rewardToken.balanceOf( + rewardsInitiator + ); + uint256 rewardsCoordinatorBalanceBefore = rewardToken.balanceOf( + address(rewardsCoordinator) + ); + uint256 currSubmissionNonce = rewardsCoordinator.submissionNonce( + address(serviceManager) + ); + bytes32 rewardsSubmissionHash = keccak256( + abi.encode( + address(serviceManager), + currSubmissionNonce, + operatorDirectedRewardsSubmissions[0] + ) + ); + cheats.expectEmit(true, true, true, true, address(rewardsCoordinator)); + emit OperatorDirectedAVSRewardsSubmissionCreated( + address(serviceManager), + address(serviceManager), + rewardsSubmissionHash, + currSubmissionNonce, + operatorDirectedRewardsSubmissions[0] + ); + serviceManager.createOperatorDirectedAVSRewardsSubmission( + operatorDirectedRewardsSubmissions + ); + cheats.stopPrank(); + + assertTrue( + rewardsCoordinator.isOperatorDirectedAVSRewardsSubmissionHash( + address(serviceManager), + rewardsSubmissionHash + ), + "rewards submission hash not submitted" + ); + assertEq( + currSubmissionNonce + 1, + rewardsCoordinator.submissionNonce(address(serviceManager)), + "submission nonce not incremented" + ); + assertEq( + rewardsInitiatorBalanceBefore - amount, + rewardToken.balanceOf(rewardsInitiator), + "rewardsInitiator balance not decremented by amount of rewards submission" + ); + assertEq( + rewardsCoordinatorBalanceBefore + amount, + rewardToken.balanceOf(address(rewardsCoordinator)), + "RewardsCoordinator balance not incremented by amount of rewards submission" + ); + } + + /** + * @notice test a multiple rewards submission asserting for the following + * - correct event emitted + * - submission nonce incrementation by 1, and rewards submission hash being set in storage. + * - rewards submission hash being set in storage + * - token balance before and after of rewards initiator and rewardsCoordinator + */ + function testFuzz_createOperatorDirectedAVSRewardsSubmission_MultipleSubmissions( + FuzzOperatorDirectedAVSRewardsSubmission memory param, + uint256 numSubmissions + ) public { + cheats.assume(2 <= numSubmissions && numSubmissions <= 10); + cheats.prank(rewardsCoordinator.owner()); + + IRewardsCoordinator.OperatorDirectedRewardsSubmission[] + memory rewardsSubmissions = new IRewardsCoordinator.OperatorDirectedRewardsSubmission[]( + numSubmissions + ); + bytes32[] memory rewardsSubmissionHashes = new bytes32[]( + numSubmissions + ); + uint256 startSubmissionNonce = rewardsCoordinator.submissionNonce( + address(serviceManager) + ); + _deployMockRewardTokens(rewardsInitiator, numSubmissions); + + uint256[] memory rewardsInitiatorBalancesBefore = _getBalanceForTokens( + rewardTokens, + rewardsInitiator + ); + uint256[] + memory rewardsCoordinatorBalancesBefore = _getBalanceForTokens( + rewardTokens, + address(rewardsCoordinator) + ); + uint256[] memory amounts = new uint256[](numSubmissions); + + // Create multiple rewards submissions and their expected event + for (uint256 i = 0; i < numSubmissions; ++i) { + // 1. Bound fuzz inputs to valid ranges and amounts using randSeed for each + amounts[i] = _getTotalRewardsAmount(defaultOperatorRewards); + param.duration = bound(param.duration, 0, MAX_REWARDS_DURATION); + param.duration = + param.duration - + (param.duration % CALCULATION_INTERVAL_SECONDS); + param.startTimestamp = bound( + param.startTimestamp + i, + uint256( + _maxTimestamp( + GENESIS_REWARDS_TIMESTAMP, + uint32(block.timestamp) - MAX_RETROACTIVE_LENGTH + ) + ) + + CALCULATION_INTERVAL_SECONDS - + 1, + block.timestamp + uint256(MAX_FUTURE_LENGTH) + ); + param.startTimestamp = + param.startTimestamp - + (param.startTimestamp % CALCULATION_INTERVAL_SECONDS); + + param.duration = bound(param.duration, 0, MAX_REWARDS_DURATION); + param.duration = + param.duration - + (param.duration % CALCULATION_INTERVAL_SECONDS); + param.startTimestamp = bound( + param.startTimestamp, + uint256( + _maxTimestamp( + GENESIS_REWARDS_TIMESTAMP, + uint32(block.timestamp) - MAX_RETROACTIVE_LENGTH + ) + ) + + CALCULATION_INTERVAL_SECONDS - + 1, + block.timestamp - param.duration - 1 + ); + param.startTimestamp = + param.startTimestamp - + (param.startTimestamp % CALCULATION_INTERVAL_SECONDS); + + // 2. Create rewards submission input param + IRewardsCoordinator.OperatorDirectedRewardsSubmission + memory rewardsSubmission = IRewardsCoordinator + .OperatorDirectedRewardsSubmission({ + strategiesAndMultipliers: defaultStrategyAndMultipliers, + token: rewardTokens[i], + operatorRewards: defaultOperatorRewards, + startTimestamp: uint32(param.startTimestamp), + duration: uint32(param.duration), + description: "" + }); + rewardsSubmissions[i] = rewardsSubmission; + + // 3. expected event emitted for this rewardsSubmission + rewardsSubmissionHashes[i] = keccak256( + abi.encode( + address(serviceManager), + startSubmissionNonce + i, + rewardsSubmissions[i] + ) + ); + cheats.expectEmit( + true, + true, + true, + true, + address(rewardsCoordinator) + ); + emit OperatorDirectedAVSRewardsSubmissionCreated( + address(serviceManager), + address(serviceManager), + rewardsSubmissionHashes[i], + startSubmissionNonce + i, + rewardsSubmissions[i] + ); + } + + // 4. call createAVSRewardsSubmission() + cheats.prank(rewardsInitiator); + serviceManager.createOperatorDirectedAVSRewardsSubmission( + rewardsSubmissions + ); + + // 5. Check for submissionNonce() and rewardsSubmissionHashes being set + assertEq( + startSubmissionNonce + numSubmissions, + rewardsCoordinator.submissionNonce(address(serviceManager)), + "submission nonce not incremented properly" + ); + + for (uint256 i = 0; i < numSubmissions; ++i) { + assertTrue( + rewardsCoordinator.isOperatorDirectedAVSRewardsSubmissionHash( + address(serviceManager), + rewardsSubmissionHashes[i] + ), + "rewards submission hash not submitted" + ); + assertEq( + rewardsInitiatorBalancesBefore[i] - amounts[i], + rewardTokens[i].balanceOf(rewardsInitiator), + "rewardsInitiator balance not decremented by amount of rewards submission" + ); + assertEq( + rewardsCoordinatorBalancesBefore[i] + amounts[i], + rewardTokens[i].balanceOf(address(rewardsCoordinator)), + "RewardsCoordinator balance not incremented by amount of rewards submission" + ); + } + } } diff --git a/test/unit/SocketRegistryUnit.t.sol b/test/unit/SocketRegistryUnit.t.sol new file mode 100644 index 00000000..6ebd6780 --- /dev/null +++ b/test/unit/SocketRegistryUnit.t.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.12; + +import {SocketRegistry} from "../../src/SocketRegistry.sol"; +import {IRegistryCoordinator} from "../../src/interfaces/IRegistryCoordinator.sol"; +import "../utils/MockAVSDeployer.sol"; + +contract SocketRegistryUnitTests is MockAVSDeployer { + + function setUp() virtual public { + _deployMockEigenLayerAndAVS(); + } + + function test_setOperatorSocket() public { + vm.startPrank(address(registryCoordinator)); + socketRegistry.setOperatorSocket(defaultOperatorId, "testSocket"); + assertEq(socketRegistry.getOperatorSocket(defaultOperatorId), "testSocket"); + } + + function test_setOperatorSocket_revert_notRegistryCoordinator() public { + vm.startPrank(address(0)); + vm.expectRevert("SocketRegistry.onlyRegistryCoordinator: caller is not the RegistryCoordinator"); + socketRegistry.setOperatorSocket(defaultOperatorId, "testSocket"); + } + + function test_migrateOperatorSockets() public { + bytes32[] memory operatorIds = new bytes32[](1); + operatorIds[0] = defaultOperatorId; + string[] memory sockets = new string[](1); + sockets[0] = "testSocket"; + + vm.startPrank(registryCoordinator.owner()); + socketRegistry.migrateOperatorSockets(operatorIds, sockets); + assertEq(socketRegistry.getOperatorSocket(defaultOperatorId), "testSocket"); + } + + function test_migrateOperatorSockets_revert_notCoordinatorOwner() public { + bytes32[] memory operatorIds = new bytes32[](1); + operatorIds[0] = defaultOperatorId; + string[] memory sockets = new string[](1); + sockets[0] = "testSocket"; + + vm.startPrank(address(0)); + vm.expectRevert("SocketRegistry.onlyCoordinatorOwner: caller is not the owner of the registryCoordinator"); + socketRegistry.migrateOperatorSockets(operatorIds, sockets); + } + +} \ No newline at end of file diff --git a/test/unit/StakeRegistryUnit.t.sol b/test/unit/StakeRegistryUnit.t.sol index bc9d46f6..52769f78 100644 --- a/test/unit/StakeRegistryUnit.t.sol +++ b/test/unit/StakeRegistryUnit.t.sol @@ -38,7 +38,7 @@ contract StakeRegistryUnitTests is MockAVSDeployer, IStakeRegistryEvents { _; } - function setUp() virtual public { + function setUp() public virtual { // Deploy contracts but with 0 quorums initialized, will initializeQuorums afterwards _deployMockEigenLayerAndAVS(0); @@ -48,40 +48,40 @@ contract StakeRegistryUnitTests is MockAVSDeployer, IStakeRegistryEvents { serviceManager, stakeRegistry, IBLSApkRegistry(blsApkRegistry), - IIndexRegistry(indexRegistry) + IIndexRegistry(indexRegistry), + socketRegistry ); stakeRegistryImplementation = new StakeRegistryHarness( - IRegistryCoordinator(address(registryCoordinator)), - delegationMock + IRegistryCoordinator(address(registryCoordinator)), delegationMock ); stakeRegistry = StakeRegistryHarness( address( new TransparentUpgradeableProxy( - address(stakeRegistryImplementation), - address(proxyAdmin), - "" + address(stakeRegistryImplementation), address(proxyAdmin), "" ) ) ); cheats.stopPrank(); // Initialize several quorums with varying minimum stakes - _initializeQuorum({ minimumStake: uint96(type(uint16).max) }); - _initializeQuorum({ minimumStake: uint96(type(uint24).max) }); - _initializeQuorum({ minimumStake: uint96(type(uint32).max) }); - _initializeQuorum({ minimumStake: uint96(type(uint64).max) }); + _initializeQuorum({minimumStake: uint96(type(uint16).max)}); + _initializeQuorum({minimumStake: uint96(type(uint24).max)}); + _initializeQuorum({minimumStake: uint96(type(uint32).max)}); + _initializeQuorum({minimumStake: uint96(type(uint64).max)}); - _initializeQuorum({ minimumStake: uint96(type(uint16).max) + 1 }); - _initializeQuorum({ minimumStake: uint96(type(uint24).max) + 1 }); - _initializeQuorum({ minimumStake: uint96(type(uint32).max) + 1 }); - _initializeQuorum({ minimumStake: uint96(type(uint64).max) + 1 }); + _initializeQuorum({minimumStake: uint96(type(uint16).max) + 1}); + _initializeQuorum({minimumStake: uint96(type(uint24).max) + 1}); + _initializeQuorum({minimumStake: uint96(type(uint32).max) + 1}); + _initializeQuorum({minimumStake: uint96(type(uint64).max) + 1}); } - /******************************************************************************* - initializers - *******************************************************************************/ + /** + * + * initializers + * + */ /** * @dev Initialize a new quorum with `minimumStake` @@ -91,7 +91,7 @@ contract StakeRegistryUnitTests is MockAVSDeployer, IStakeRegistryEvents { uint8 quorumNumber = nextQuorum; IStakeRegistry.StrategyParams[] memory strategyParams = - new IStakeRegistry.StrategyParams[](1); + new IStakeRegistry.StrategyParams[](1); strategyParams[0] = IStakeRegistry.StrategyParams( IStrategy(address(uint160(uint256(keccak256(abi.encodePacked(quorumNumber)))))), uint96(WEIGHTING_DIVISOR) @@ -107,15 +107,16 @@ contract StakeRegistryUnitTests is MockAVSDeployer, IStakeRegistryEvents { initializedQuorumBytes = initializedQuorumBitmap.bitmapToBytesArray(); } - /** + /** * @dev Initialize a new quorum with `minimumStake` and `numStrats` - * Create `numStrats` dummy strategies with multiplier of 1 for each. + * Create `numStrats` dummy strategies with multiplier of 1 for each. * Returns quorumNumber that was just initialized */ function _initializeQuorum(uint96 minimumStake, uint256 numStrats) internal returns (uint8) { uint8 quorumNumber = nextQuorum; - IStakeRegistry.StrategyParams[] memory strategyParams = new IStakeRegistry.StrategyParams[](numStrats); + IStakeRegistry.StrategyParams[] memory strategyParams = + new IStakeRegistry.StrategyParams[](numStrats); for (uint256 i = 0; i < strategyParams.length; i++) { strategyParams[i] = IStakeRegistry.StrategyParams( IStrategy(address(uint160(uint256(keccak256(abi.encodePacked(quorumNumber, i)))))), @@ -145,10 +146,11 @@ contract StakeRegistryUnitTests is MockAVSDeployer, IStakeRegistryEvents { return (operator, operatorId); } - /******************************************************************************* - test setup methods - *******************************************************************************/ - + /** + * + * test setup methods + * + */ struct RegisterSetup { address operator; bytes32 operatorId; @@ -162,20 +164,25 @@ contract StakeRegistryUnitTests is MockAVSDeployer, IStakeRegistryEvents { /// @dev Utility function set up a new operator to be registered for some quorums /// The operator's weight is set to the quorum's minimum, plus fuzzy_addtlStake (overflows are skipped) /// This function guarantees at least one quorum, and any quorums returned are initialized - function _fuzz_setupRegisterOperator(uint192 fuzzy_Bitmap, uint16 fuzzy_addtlStake) internal returns (RegisterSetup memory) { + function _fuzz_setupRegisterOperator( + uint192 fuzzy_Bitmap, + uint16 fuzzy_addtlStake + ) internal returns (RegisterSetup memory) { // Select an unused operator to register (address operator, bytes32 operatorId) = _selectNewOperator(); - + // Pick quorums to register for and get each quorum's minimum stake - ( , bytes memory quorumNumbers) = _fuzz_getQuorums(fuzzy_Bitmap); + (, bytes memory quorumNumbers) = _fuzz_getQuorums(fuzzy_Bitmap); uint96[] memory minimumStakes = _getMinimumStakes(quorumNumbers); // For each quorum, set the operator's weight as the minimum + addtlStake uint96[] memory operatorWeights = new uint96[](quorumNumbers.length); - for (uint i = 0; i < quorumNumbers.length; i++) { + for (uint256 i = 0; i < quorumNumbers.length; i++) { uint8 quorumNumber = uint8(quorumNumbers[i]); - unchecked { operatorWeights[i] = minimumStakes[i] + fuzzy_addtlStake; } + unchecked { + operatorWeights[i] = minimumStakes[i] + fuzzy_addtlStake; + } cheats.assume(operatorWeights[i] >= minimumStakes[i]); cheats.assume(operatorWeights[i] >= fuzzy_addtlStake); @@ -183,11 +190,13 @@ contract StakeRegistryUnitTests is MockAVSDeployer, IStakeRegistryEvents { } /// Get starting state - IStakeRegistry.StakeUpdate[] memory prevOperatorStakes = _getLatestStakeUpdates(operatorId, quorumNumbers); - IStakeRegistry.StakeUpdate[] memory prevTotalStakes = _getLatestTotalStakeUpdates(quorumNumbers); + IStakeRegistry.StakeUpdate[] memory prevOperatorStakes = + _getLatestStakeUpdates(operatorId, quorumNumbers); + IStakeRegistry.StakeUpdate[] memory prevTotalStakes = + _getLatestTotalStakeUpdates(quorumNumbers); // Ensure that the operator has not previously registered - for (uint i = 0; i < quorumNumbers.length; i++) { + for (uint256 i = 0; i < quorumNumbers.length; i++) { assertTrue(prevOperatorStakes[i].updateBlockNumber == 0, "operator already registered"); assertTrue(prevOperatorStakes[i].stake == 0, "operator already has stake"); } @@ -203,10 +212,14 @@ contract StakeRegistryUnitTests is MockAVSDeployer, IStakeRegistryEvents { }); } - function _fuzz_setupRegisterOperators(uint192 fuzzy_Bitmap, uint16 fuzzy_addtlStake, uint numOperators) internal returns (RegisterSetup[] memory) { + function _fuzz_setupRegisterOperators( + uint192 fuzzy_Bitmap, + uint16 fuzzy_addtlStake, + uint256 numOperators + ) internal returns (RegisterSetup[] memory) { RegisterSetup[] memory setups = new RegisterSetup[](numOperators); - for (uint i = 0; i < numOperators; i++) { + for (uint256 i = 0; i < numOperators; i++) { setups[i] = _fuzz_setupRegisterOperator(fuzzy_Bitmap, fuzzy_addtlStake); } @@ -229,21 +242,27 @@ contract StakeRegistryUnitTests is MockAVSDeployer, IStakeRegistryEvents { /// The operator's weight is set to the quorum's minimum, plus fuzzy_addtlStake (overflows are skipped) /// This function guarantees at least one quorum, and any quorums returned are initialized function _fuzz_setupDeregisterOperator( - uint192 registeredFor, - uint192 fuzzy_toRemove, + uint192 registeredFor, + uint192 fuzzy_toRemove, uint16 fuzzy_addtlStake ) internal returns (DeregisterSetup memory) { - RegisterSetup memory registerSetup = _fuzz_setupRegisterOperator(registeredFor, fuzzy_addtlStake); + RegisterSetup memory registerSetup = + _fuzz_setupRegisterOperator(registeredFor, fuzzy_addtlStake); // registerOperator cheats.prank(address(registryCoordinator)); - stakeRegistry.registerOperator(registerSetup.operator, registerSetup.operatorId, registerSetup.quorumNumbers); + stakeRegistry.registerOperator( + registerSetup.operator, registerSetup.operatorId, registerSetup.quorumNumbers + ); // Get state after registering: - IStakeRegistry.StakeUpdate[] memory operatorStakes = _getLatestStakeUpdates(registerSetup.operatorId, registerSetup.quorumNumbers); - IStakeRegistry.StakeUpdate[] memory totalStakes = _getLatestTotalStakeUpdates(registerSetup.quorumNumbers); - - (uint192 quorumsToRemoveBitmap, bytes memory quorumsToRemove) = _fuzz_getQuorums(fuzzy_toRemove); + IStakeRegistry.StakeUpdate[] memory operatorStakes = + _getLatestStakeUpdates(registerSetup.operatorId, registerSetup.quorumNumbers); + IStakeRegistry.StakeUpdate[] memory totalStakes = + _getLatestTotalStakeUpdates(registerSetup.quorumNumbers); + + (uint192 quorumsToRemoveBitmap, bytes memory quorumsToRemove) = + _fuzz_getQuorums(fuzzy_toRemove); return DeregisterSetup({ operator: registerSetup.operator, @@ -257,15 +276,16 @@ contract StakeRegistryUnitTests is MockAVSDeployer, IStakeRegistryEvents { } function _fuzz_setupDeregisterOperators( - uint192 registeredFor, - uint192 fuzzy_toRemove, - uint16 fuzzy_addtlStake, - uint numOperators + uint192 registeredFor, + uint192 fuzzy_toRemove, + uint16 fuzzy_addtlStake, + uint256 numOperators ) internal returns (DeregisterSetup[] memory) { DeregisterSetup[] memory setups = new DeregisterSetup[](numOperators); - for (uint i = 0; i < numOperators; i++) { - setups[i] = _fuzz_setupDeregisterOperator(registeredFor, fuzzy_toRemove, fuzzy_addtlStake); + for (uint256 i = 0; i < numOperators; i++) { + setups[i] = + _fuzz_setupDeregisterOperator(registeredFor, fuzzy_toRemove, fuzzy_addtlStake); } return setups; @@ -286,37 +306,52 @@ contract StakeRegistryUnitTests is MockAVSDeployer, IStakeRegistryEvents { /// After registering, and before returning, `fuzzy_Delta` is applied to the operator's weight /// to place the operator's weight above or below the minimum stake. (or unchanged!) /// The next time `updateOperatorStake` is called, this new weight will be used. - function _fuzz_setupUpdateOperatorStake(uint192 registeredFor, int8 fuzzy_Delta) internal returns (UpdateSetup memory) { + function _fuzz_setupUpdateOperatorStake( + uint192 registeredFor, + int8 fuzzy_Delta + ) internal returns (UpdateSetup memory) { RegisterSetup memory registerSetup = _fuzz_setupRegisterOperator(registeredFor, 0); // registerOperator cheats.prank(address(registryCoordinator)); - stakeRegistry.registerOperator(registerSetup.operator, registerSetup.operatorId, registerSetup.quorumNumbers); + stakeRegistry.registerOperator( + registerSetup.operator, registerSetup.operatorId, registerSetup.quorumNumbers + ); uint96[] memory minimumStakes = _getMinimumStakes(registerSetup.quorumNumbers); uint96[] memory endingWeights = new uint96[](minimumStakes.length); - for (uint i = 0; i < minimumStakes.length; i++) { + for (uint256 i = 0; i < minimumStakes.length; i++) { uint8 quorumNumber = uint8(registerSetup.quorumNumbers[i]); endingWeights[i] = _applyDelta(minimumStakes[i], int256(fuzzy_Delta)); // Sanity-check setup: if (fuzzy_Delta > 0) { - assertGt(endingWeights[i], minimumStakes[i], "_fuzz_setupUpdateOperatorStake: overflow during setup"); + assertGt( + endingWeights[i], + minimumStakes[i], + "_fuzz_setupUpdateOperatorStake: overflow during setup" + ); } else if (fuzzy_Delta < 0) { - assertLt(endingWeights[i], minimumStakes[i], "_fuzz_setupUpdateOperatorStake: underflow during setup"); + assertLt( + endingWeights[i], + minimumStakes[i], + "_fuzz_setupUpdateOperatorStake: underflow during setup" + ); } else { - assertEq(endingWeights[i], minimumStakes[i], "_fuzz_setupUpdateOperatorStake: invalid delta during setup"); + assertEq( + endingWeights[i], + minimumStakes[i], + "_fuzz_setupUpdateOperatorStake: invalid delta during setup" + ); } // Set operator weights. The next time we call `updateOperatorStake`, these new weights will be used _setOperatorWeight(registerSetup.operator, quorumNumber, endingWeights[i]); } - uint96 stakeDeltaAbs = - fuzzy_Delta < 0 ? - uint96(-int96(fuzzy_Delta)) : - uint96(int96(fuzzy_Delta)); + uint96 stakeDeltaAbs = + fuzzy_Delta < 0 ? uint96(-int96(fuzzy_Delta)) : uint96(int96(fuzzy_Delta)); return UpdateSetup({ operator: registerSetup.operator, @@ -328,19 +363,25 @@ contract StakeRegistryUnitTests is MockAVSDeployer, IStakeRegistryEvents { }); } - function _fuzz_setupUpdateOperatorStakes(uint8 numOperators, uint192 registeredFor, int8 fuzzy_Delta) internal returns (UpdateSetup[] memory) { + function _fuzz_setupUpdateOperatorStakes( + uint8 numOperators, + uint192 registeredFor, + int8 fuzzy_Delta + ) internal returns (UpdateSetup[] memory) { UpdateSetup[] memory setups = new UpdateSetup[](numOperators); - for (uint i = 0; i < numOperators; i++) { + for (uint256 i = 0; i < numOperators; i++) { setups[i] = _fuzz_setupUpdateOperatorStake(registeredFor, fuzzy_Delta); } return setups; } - /******************************************************************************* - helpful getters - *******************************************************************************/ + /** + * + * helpful getters + * + */ /// @notice Given a fuzzed bitmap input, returns a bitmap and array of quorum numbers /// that are guaranteed to be initialized. @@ -355,7 +396,7 @@ contract StakeRegistryUnitTests is MockAVSDeployer, IStakeRegistryEvents { /// @param rand is used to determine how many legitimate quorums to insert, so we can /// check this works for lists of varying lengths function _fuzz_getInvalidQuorums(bytes32 rand) internal returns (bytes memory) { - uint length = _randUint({ rand: rand, min: 1, max: initializedQuorumBytes.length + 1 }); + uint256 length = _randUint({rand: rand, min: 1, max: initializedQuorumBytes.length + 1}); bytes memory invalidQuorums = new bytes(length); // Create an invalid quorum number by incrementing the last initialized quorum @@ -364,7 +405,9 @@ contract StakeRegistryUnitTests is MockAVSDeployer, IStakeRegistryEvents { // Select real quorums up to the length, then insert an invalid quorum for (uint8 quorum = 0; quorum < length - 1; quorum++) { // sanity check test setup - assertTrue(initializedQuorumBitmap.isSet(quorum), "_fuzz_getInvalidQuorums: invalid quorum"); + assertTrue( + initializedQuorumBitmap.isSet(quorum), "_fuzz_getInvalidQuorums: invalid quorum" + ); invalidQuorums[quorum] = bytes1(quorum); } @@ -374,21 +417,24 @@ contract StakeRegistryUnitTests is MockAVSDeployer, IStakeRegistryEvents { /// @notice Returns true iff two StakeUpdates are identical function _isUnchanged( - IStakeRegistry.StakeUpdate memory prev, + IStakeRegistry.StakeUpdate memory prev, IStakeRegistry.StakeUpdate memory cur ) internal pure returns (bool) { return ( - prev.stake == cur.stake && - prev.updateBlockNumber == cur.updateBlockNumber && - prev.nextUpdateBlockNumber == cur.nextUpdateBlockNumber + prev.stake == cur.stake && prev.updateBlockNumber == cur.updateBlockNumber + && prev.nextUpdateBlockNumber == cur.nextUpdateBlockNumber ); } /// @dev Return the minimum stakes required for a list of quorums - function _getMinimumStakes(bytes memory quorumNumbers) internal view returns (uint96[] memory) { + function _getMinimumStakes(bytes memory quorumNumbers) + internal + view + returns (uint96[] memory) + { uint96[] memory minimumStakes = new uint96[](quorumNumbers.length); - - for (uint i = 0; i < quorumNumbers.length; i++) { + + for (uint256 i = 0; i < quorumNumbers.length; i++) { uint8 quorumNumber = uint8(quorumNumbers[i]); minimumStakes[i] = stakeRegistry.minimumStakeForQuorum(quorumNumber); } @@ -398,13 +444,13 @@ contract StakeRegistryUnitTests is MockAVSDeployer, IStakeRegistryEvents { /// @dev Return the most recent stake update history entries for an operator function _getLatestStakeUpdates( - bytes32 operatorId, + bytes32 operatorId, bytes memory quorumNumbers ) internal view returns (IStakeRegistry.StakeUpdate[] memory) { - IStakeRegistry.StakeUpdate[] memory stakeUpdates = + IStakeRegistry.StakeUpdate[] memory stakeUpdates = new IStakeRegistry.StakeUpdate[](quorumNumbers.length); - - for (uint i = 0; i < quorumNumbers.length; i++) { + + for (uint256 i = 0; i < quorumNumbers.length; i++) { uint8 quorumNumber = uint8(quorumNumbers[i]); stakeUpdates[i] = stakeRegistry.getLatestStakeUpdate(operatorId, quorumNumber); } @@ -413,17 +459,20 @@ contract StakeRegistryUnitTests is MockAVSDeployer, IStakeRegistryEvents { } /// @dev Return the most recent total stake update history entries - function _getLatestTotalStakeUpdates( - bytes memory quorumNumbers - ) internal view returns (IStakeRegistry.StakeUpdate[] memory) { - IStakeRegistry.StakeUpdate[] memory stakeUpdates = + function _getLatestTotalStakeUpdates(bytes memory quorumNumbers) + internal + view + returns (IStakeRegistry.StakeUpdate[] memory) + { + IStakeRegistry.StakeUpdate[] memory stakeUpdates = new IStakeRegistry.StakeUpdate[](quorumNumbers.length); - - for (uint i = 0; i < quorumNumbers.length; i++) { + + for (uint256 i = 0; i < quorumNumbers.length; i++) { uint8 quorumNumber = uint8(quorumNumbers[i]); - uint historyLength = stakeRegistry.getTotalStakeHistoryLength(quorumNumber); - stakeUpdates[i] = stakeRegistry.getTotalStakeUpdateAtIndex(quorumNumber, historyLength - 1); + uint256 historyLength = stakeRegistry.getTotalStakeHistoryLength(quorumNumber); + stakeUpdates[i] = + stakeRegistry.getTotalStakeUpdateAtIndex(quorumNumber, historyLength - 1); } return stakeUpdates; @@ -439,16 +488,19 @@ contract StakeRegistryUnitTests is MockAVSDeployer, IStakeRegistryEvents { for (uint256 i = 0; i < quorumNumbers.length; i++) { uint8 quorumNumber = uint8(quorumNumbers[i]); - operatorStakeHistoryLengths[i] = stakeRegistry.getStakeHistoryLength(operatorId, quorumNumber); + operatorStakeHistoryLengths[i] = + stakeRegistry.getStakeHistoryLength(operatorId, quorumNumber); } return operatorStakeHistoryLengths; } /// @dev Return the lengths of the total stake update history - function _getTotalStakeHistoryLengths( - bytes memory quorumNumbers - ) internal view returns (uint256[] memory) { + function _getTotalStakeHistoryLengths(bytes memory quorumNumbers) + internal + view + returns (uint256[] memory) + { uint256[] memory historyLengths = new uint256[](quorumNumbers.length); for (uint256 i = 0; i < quorumNumbers.length; i++) { @@ -461,30 +513,24 @@ contract StakeRegistryUnitTests is MockAVSDeployer, IStakeRegistryEvents { } function _calculateDelta(uint96 prev, uint96 cur) internal view returns (int256) { - return stakeRegistry.calculateDelta({ - prev: prev, - cur: cur - }); + return stakeRegistry.calculateDelta({prev: prev, cur: cur}); } function _applyDelta(uint96 value, int256 delta) internal view returns (uint96) { - return stakeRegistry.applyDelta({ - value: value, - delta: delta - }); + return stakeRegistry.applyDelta({value: value, delta: delta}); } /// @dev Uses `rand` to return a random uint, with a range given by `min` and `max` (inclusive) /// @return `min` <= result <= `max` - function _randUint(bytes32 rand, uint min, uint max) internal pure returns (uint) { + function _randUint(bytes32 rand, uint256 min, uint256 max) internal pure returns (uint256) { // hashing makes for more uniform randomness rand = keccak256(abi.encodePacked(rand)); - - uint range = max - min + 1; + + uint256 range = max - min + 1; // calculate the number of bits needed for the range - uint bitsNeeded = 0; - uint tempRange = range; + uint256 bitsNeeded = 0; + uint256 tempRange = range; while (tempRange > 0) { bitsNeeded++; tempRange >>= 1; @@ -492,8 +538,8 @@ contract StakeRegistryUnitTests is MockAVSDeployer, IStakeRegistryEvents { // create a mask for the required number of bits // and extract the value from the hash - uint mask = (1 << bitsNeeded) - 1; - uint value = uint(rand) & mask; + uint256 mask = (1 << bitsNeeded) - 1; + uint256 value = uint256(rand) & mask; // in case value is out of range, wrap around or retry while (value >= range) { @@ -506,9 +552,9 @@ contract StakeRegistryUnitTests is MockAVSDeployer, IStakeRegistryEvents { /// @dev Sort to ensure that the array is in desscending order for removeStrategies function _sortArrayDesc(uint256[] memory arr) internal pure returns (uint256[] memory) { uint256 l = arr.length; - for(uint256 i = 0; i < l; i++) { - for(uint256 j = i+1; j < l ;j++) { - if(arr[i] < arr[j]) { + for (uint256 i = 0; i < l; i++) { + for (uint256 j = i + 1; j < l; j++) { + if (arr[i] < arr[j]) { uint256 temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; @@ -521,17 +567,19 @@ contract StakeRegistryUnitTests is MockAVSDeployer, IStakeRegistryEvents { /// @notice Tests for any nonstandard/permissioned methods contract StakeRegistryUnitTests_Config is StakeRegistryUnitTests { - - /******************************************************************************* - initializeQuorum - *******************************************************************************/ - + /** + * + * initializeQuorum + * + */ function testFuzz_initializeQuorum_Revert_WhenNotRegistryCoordinator( uint8 quorumNumber, uint96 minimumStake, IStakeRegistry.StrategyParams[] memory strategyParams ) public { - cheats.expectRevert("StakeRegistry.onlyRegistryCoordinator: caller is not the RegistryCoordinator"); + cheats.expectRevert( + "StakeRegistry.onlyRegistryCoordinator: caller is not the RegistryCoordinator" + ); stakeRegistry.initializeQuorum(quorumNumber, minimumStake, strategyParams); } @@ -550,7 +598,8 @@ contract StakeRegistryUnitTests_Config is StakeRegistryUnitTests { uint96 minimumStake ) public { cheats.assume(quorumNumber >= nextQuorum); - IStakeRegistry.StrategyParams[] memory strategyParams = new IStakeRegistry.StrategyParams[](0); + IStakeRegistry.StrategyParams[] memory strategyParams = + new IStakeRegistry.StrategyParams[](0); cheats.expectRevert("StakeRegistry._addStrategyParams: no strategies provided"); cheats.prank(address(registryCoordinator)); stakeRegistry.initializeQuorum(quorumNumber, minimumStake, strategyParams); @@ -558,8 +607,7 @@ contract StakeRegistryUnitTests_Config is StakeRegistryUnitTests { strategyParams = new IStakeRegistry.StrategyParams[](MAX_WEIGHING_FUNCTION_LENGTH + 1); for (uint256 i = 0; i < strategyParams.length; i++) { strategyParams[i] = IStakeRegistry.StrategyParams( - IStrategy(address(uint160(uint256(keccak256(abi.encodePacked(i)))))), - uint96(1) + IStrategy(address(uint160(uint256(keccak256(abi.encodePacked(i)))))), uint96(1) ); } cheats.expectRevert("StakeRegistry._addStrategyParams: exceed MAX_WEIGHING_FUNCTION_LENGTH"); @@ -569,7 +617,7 @@ contract StakeRegistryUnitTests_Config is StakeRegistryUnitTests { /** * @dev Initializes a quorum with StrategyParams with fuzzed multipliers inputs and corresponding - * strategy addresses. + * strategy addresses. */ function testFuzz_initializeQuorum( uint8 quorumNumber, @@ -578,41 +626,63 @@ contract StakeRegistryUnitTests_Config is StakeRegistryUnitTests { ) public { cheats.assume(quorumNumber >= nextQuorum); cheats.assume(0 < multipliers.length && multipliers.length <= MAX_WEIGHING_FUNCTION_LENGTH); - IStakeRegistry.StrategyParams[] memory strategyParams = new IStakeRegistry.StrategyParams[](multipliers.length); + IStakeRegistry.StrategyParams[] memory strategyParams = + new IStakeRegistry.StrategyParams[](multipliers.length); for (uint256 i = 0; i < strategyParams.length; i++) { cheats.assume(multipliers[i] > 0); strategyParams[i] = IStakeRegistry.StrategyParams( - IStrategy(address(uint160(uint256(keccak256(abi.encodePacked(i)))))), - multipliers[i] + IStrategy(address(uint160(uint256(keccak256(abi.encodePacked(i)))))), multipliers[i] ); } quorumNumber = nextQuorum; cheats.prank(address(registryCoordinator)); stakeRegistry.initializeQuorum(quorumNumber, minimumStake, strategyParams); - IStakeRegistry.StakeUpdate memory initialStakeUpdate = stakeRegistry.getTotalStakeUpdateAtIndex(quorumNumber, 0); - assertEq(stakeRegistry.minimumStakeForQuorum(quorumNumber), minimumStake, "invalid minimum stake"); - assertEq(stakeRegistry.getTotalStakeHistoryLength(quorumNumber), 1, "invalid total stake history length"); + IStakeRegistry.StakeUpdate memory initialStakeUpdate = + stakeRegistry.getTotalStakeUpdateAtIndex(quorumNumber, 0); + assertEq( + stakeRegistry.minimumStakeForQuorum(quorumNumber), minimumStake, "invalid minimum stake" + ); + assertEq( + stakeRegistry.getTotalStakeHistoryLength(quorumNumber), + 1, + "invalid total stake history length" + ); assertEq(initialStakeUpdate.stake, 0, "invalid stake update"); - assertEq(initialStakeUpdate.updateBlockNumber, uint32(block.number), "invalid updateBlockNumber stake update"); - assertEq(initialStakeUpdate.nextUpdateBlockNumber, 0, "invalid nextUpdateBlockNumber stake update"); - assertEq(stakeRegistry.strategyParamsLength(quorumNumber), strategyParams.length, "invalid strategy params length"); + assertEq( + initialStakeUpdate.updateBlockNumber, + uint32(block.number), + "invalid updateBlockNumber stake update" + ); + assertEq( + initialStakeUpdate.nextUpdateBlockNumber, + 0, + "invalid nextUpdateBlockNumber stake update" + ); + assertEq( + stakeRegistry.strategyParamsLength(quorumNumber), + strategyParams.length, + "invalid strategy params length" + ); for (uint256 i = 0; i < strategyParams.length; i++) { - (IStrategy strategy , uint96 multiplier) = stakeRegistry.strategyParams(quorumNumber, i); + (IStrategy strategy, uint96 multiplier) = stakeRegistry.strategyParams(quorumNumber, i); assertEq(address(strategy), address(strategyParams[i].strategy), "invalid strategy"); assertEq(multiplier, strategyParams[i].multiplier, "invalid multiplier"); } } - /******************************************************************************* - setMinimumStakeForQuorum - *******************************************************************************/ - + /** + * + * setMinimumStakeForQuorum + * + */ function testFuzz_setMinimumStakeForQuorum_Revert_WhenNotRegistryCoordinatorOwner( uint8 quorumNumber, uint96 minimumStakeForQuorum ) public fuzzOnlyInitializedQuorums(quorumNumber) { - cheats.expectRevert("StakeRegistry.onlyCoordinatorOwner: caller is not the owner of the registryCoordinator"); + cheats.expectRevert( + "StakeRegistry.onlyCoordinatorOwner: caller is not the owner of the registryCoordinator" + ); stakeRegistry.setMinimumStakeForQuorum(quorumNumber, minimumStakeForQuorum); } @@ -626,7 +696,7 @@ contract StakeRegistryUnitTests_Config is StakeRegistryUnitTests { cheats.prank(registryCoordinatorOwner); stakeRegistry.setMinimumStakeForQuorum(quorumNumber, minimumStakeForQuorum); } - + /// @dev Fuzzes initialized quorum numbers and minimum stakes to set to function testFuzz_setMinimumStakeForQuorum( uint8 quorumNumber, @@ -634,18 +704,25 @@ contract StakeRegistryUnitTests_Config is StakeRegistryUnitTests { ) public fuzzOnlyInitializedQuorums(quorumNumber) { cheats.prank(registryCoordinatorOwner); stakeRegistry.setMinimumStakeForQuorum(quorumNumber, minimumStakeForQuorum); - assertEq(stakeRegistry.minimumStakeForQuorum(quorumNumber), minimumStakeForQuorum, "invalid minimum stake"); + assertEq( + stakeRegistry.minimumStakeForQuorum(quorumNumber), + minimumStakeForQuorum, + "invalid minimum stake" + ); } - /******************************************************************************* - addStrategies - *******************************************************************************/ - + /** + * + * addStrategies + * + */ function testFuzz_addStrategies_Revert_WhenNotRegistryCoordinatorOwner( uint8 quorumNumber, IStakeRegistry.StrategyParams[] memory strategyParams ) public fuzzOnlyInitializedQuorums(quorumNumber) { - cheats.expectRevert("StakeRegistry.onlyCoordinatorOwner: caller is not the owner of the registryCoordinator"); + cheats.expectRevert( + "StakeRegistry.onlyCoordinatorOwner: caller is not the owner of the registryCoordinator" + ); stakeRegistry.addStrategies(quorumNumber, strategyParams); } @@ -663,16 +740,12 @@ contract StakeRegistryUnitTests_Config is StakeRegistryUnitTests { function test_addStrategies_Revert_WhenDuplicateStrategies() public { uint8 quorumNumber = _initializeQuorum(uint96(type(uint16).max), 1); - IStrategy strat = IStrategy(address(uint160(uint256(keccak256(abi.encodePacked("duplicate strat")))))); - IStakeRegistry.StrategyParams[] memory strategyParams = new IStakeRegistry.StrategyParams[](2); - strategyParams[0] = IStakeRegistry.StrategyParams( - strat, - uint96(WEIGHTING_DIVISOR) - ); - strategyParams[1] = IStakeRegistry.StrategyParams( - strat, - uint96(WEIGHTING_DIVISOR) - ); + IStrategy strat = + IStrategy(address(uint160(uint256(keccak256(abi.encodePacked("duplicate strat")))))); + IStakeRegistry.StrategyParams[] memory strategyParams = + new IStakeRegistry.StrategyParams[](2); + strategyParams[0] = IStakeRegistry.StrategyParams(strat, uint96(WEIGHTING_DIVISOR)); + strategyParams[1] = IStakeRegistry.StrategyParams(strat, uint96(WEIGHTING_DIVISOR)); cheats.expectRevert("StakeRegistry._addStrategyParams: cannot add same strategy 2x"); cheats.prank(registryCoordinatorOwner); @@ -682,14 +755,15 @@ contract StakeRegistryUnitTests_Config is StakeRegistryUnitTests { function test_addStrategies_Revert_WhenZeroWeight() public { uint8 quorumNumber = _initializeQuorum(uint96(type(uint16).max), 1); - IStrategy strat = IStrategy(address(uint160(uint256(keccak256(abi.encodePacked("duplicate strat")))))); - IStakeRegistry.StrategyParams[] memory strategyParams = new IStakeRegistry.StrategyParams[](2); - strategyParams[0] = IStakeRegistry.StrategyParams( - strat, - 0 - ); + IStrategy strat = + IStrategy(address(uint160(uint256(keccak256(abi.encodePacked("duplicate strat")))))); + IStakeRegistry.StrategyParams[] memory strategyParams = + new IStakeRegistry.StrategyParams[](2); + strategyParams[0] = IStakeRegistry.StrategyParams(strat, 0); - cheats.expectRevert("StakeRegistry._addStrategyParams: cannot add strategy with zero weight"); + cheats.expectRevert( + "StakeRegistry._addStrategyParams: cannot add strategy with zero weight" + ); cheats.prank(registryCoordinatorOwner); stakeRegistry.addStrategies(quorumNumber, strategyParams); } @@ -704,18 +778,19 @@ contract StakeRegistryUnitTests_Config is StakeRegistryUnitTests { ) public fuzzOnlyInitializedQuorums(quorumNumber) { uint256 currNumStrategies = stakeRegistry.strategyParamsLength(quorumNumber); // Assume nonzero multipliers, and total added strategies length is less than MAX_WEIGHING_FUNCTION_LENGTH - cheats.assume(0 < multipliers.length && multipliers.length <= MAX_WEIGHING_FUNCTION_LENGTH - currNumStrategies); + cheats.assume( + 0 < multipliers.length + && multipliers.length <= MAX_WEIGHING_FUNCTION_LENGTH - currNumStrategies + ); for (uint256 i = 0; i < multipliers.length; i++) { cheats.assume(multipliers[i] > 0); } // Expected events emitted - IStakeRegistry.StrategyParams[] memory strategyParams = new IStakeRegistry.StrategyParams[](multipliers.length); + IStakeRegistry.StrategyParams[] memory strategyParams = + new IStakeRegistry.StrategyParams[](multipliers.length); for (uint256 i = 0; i < strategyParams.length; i++) { IStrategy strat = IStrategy(address(uint160(uint256(keccak256(abi.encodePacked(i)))))); - strategyParams[i] = IStakeRegistry.StrategyParams( - strat, - multipliers[i] - ); + strategyParams[i] = IStakeRegistry.StrategyParams(strat, multipliers[i]); cheats.expectEmit(true, true, true, true, address(stakeRegistry)); emit StrategyAddedToQuorum(quorumNumber, strat); @@ -726,22 +801,31 @@ contract StakeRegistryUnitTests_Config is StakeRegistryUnitTests { // addStrategies() call and expected assertions cheats.prank(registryCoordinatorOwner); stakeRegistry.addStrategies(quorumNumber, strategyParams); - assertEq(stakeRegistry.strategyParamsLength(quorumNumber), strategyParams.length + 1, "invalid strategy params length"); + assertEq( + stakeRegistry.strategyParamsLength(quorumNumber), + strategyParams.length + 1, + "invalid strategy params length" + ); for (uint256 i = 0; i < strategyParams.length; i++) { - (IStrategy strategy , uint96 multiplier) = stakeRegistry.strategyParams(quorumNumber, i + 1); + (IStrategy strategy, uint96 multiplier) = + stakeRegistry.strategyParams(quorumNumber, i + 1); assertEq(address(strategy), address(strategyParams[i].strategy), "invalid strategy"); assertEq(multiplier, strategyParams[i].multiplier, "invalid multiplier"); } } - /******************************************************************************* - removeStrategies - *******************************************************************************/ + /** + * + * removeStrategies + * + */ function testFuzz_removeStrategies_Revert_WhenNotRegistryCoordinatorOwner( uint8 quorumNumber, uint256[] memory indicesToRemove ) public fuzzOnlyInitializedQuorums(quorumNumber) { - cheats.expectRevert("StakeRegistry.onlyCoordinatorOwner: caller is not the owner of the registryCoordinator"); + cheats.expectRevert( + "StakeRegistry.onlyCoordinatorOwner: caller is not the owner of the registryCoordinator" + ); stakeRegistry.removeStrategies(quorumNumber, indicesToRemove); } @@ -802,7 +886,7 @@ contract StakeRegistryUnitTests_Config is StakeRegistryUnitTests { // Create array of indicesToRemove, sort desc, and assume no duplicates uint256[] memory indicesToRemove = new uint256[](numStrategiesToRemove); for (uint256 i = 0; i < numStrategiesToRemove; i++) { - indicesToRemove[i] = _randUint({ rand: bytes32(i), min: 0, max: numStrategiesToAdd - 1 }); + indicesToRemove[i] = _randUint({rand: bytes32(i), min: 0, max: numStrategiesToAdd - 1}); } indicesToRemove = _sortArrayDesc(indicesToRemove); uint256 prevIndex = indicesToRemove[0]; @@ -815,7 +899,7 @@ contract StakeRegistryUnitTests_Config is StakeRegistryUnitTests { // Expected events emitted for (uint256 i = 0; i < indicesToRemove.length; i++) { - (IStrategy strategy, ) = stakeRegistry.strategyParams(quorumNumber, indicesToRemove[i]); + (IStrategy strategy,) = stakeRegistry.strategyParams(quorumNumber, indicesToRemove[i]); cheats.expectEmit(true, true, true, true, address(stakeRegistry)); emit StrategyRemovedFromQuorum(quorumNumber, strategy); cheats.expectEmit(true, true, true, true, address(stakeRegistry)); @@ -832,16 +916,20 @@ contract StakeRegistryUnitTests_Config is StakeRegistryUnitTests { ); } - /******************************************************************************* - modifyStrategyParams - *******************************************************************************/ + /** + * + * modifyStrategyParams + * + */ function testFuzz_modifyStrategyParams_Revert_WhenNotRegistryCoordinatorOwner( uint8 quorumNumber, uint256[] calldata strategyIndices, uint96[] calldata newMultipliers ) public fuzzOnlyInitializedQuorums(quorumNumber) { - cheats.expectRevert("StakeRegistry.onlyCoordinatorOwner: caller is not the owner of the registryCoordinator"); - stakeRegistry.modifyStrategyParams(quorumNumber, strategyIndices, newMultipliers); + cheats.expectRevert( + "StakeRegistry.onlyCoordinatorOwner: caller is not the owner of the registryCoordinator" + ); + stakeRegistry.modifyStrategyParams(quorumNumber, strategyIndices, newMultipliers); } function testFuzz_modifyStrategyParams_Revert_WhenInvalidQuorum( @@ -855,10 +943,11 @@ contract StakeRegistryUnitTests_Config is StakeRegistryUnitTests { cheats.prank(registryCoordinatorOwner); stakeRegistry.modifyStrategyParams(quorumNumber, strategyIndices, newMultipliers); } - - function testFuzz_modifyStrategyParams_Revert_WhenEmptyArray( - uint8 quorumNumber - ) public fuzzOnlyInitializedQuorums(quorumNumber) { + + function testFuzz_modifyStrategyParams_Revert_WhenEmptyArray(uint8 quorumNumber) + public + fuzzOnlyInitializedQuorums(quorumNumber) + { uint256[] memory strategyIndices = new uint256[](0); uint96[] memory newMultipliers = new uint96[](0); cheats.expectRevert("StakeRegistry.modifyStrategyParams: no strategy indices provided"); @@ -875,7 +964,7 @@ contract StakeRegistryUnitTests_Config is StakeRegistryUnitTests { cheats.assume(strategyIndices.length > 0); cheats.expectRevert("StakeRegistry.modifyStrategyParams: input length mismatch"); cheats.prank(registryCoordinatorOwner); - stakeRegistry.modifyStrategyParams(quorumNumber, strategyIndices, newMultipliers); + stakeRegistry.modifyStrategyParams(quorumNumber, strategyIndices, newMultipliers); } /** @@ -893,8 +982,8 @@ contract StakeRegistryUnitTests_Config is StakeRegistryUnitTests { uint96[] memory newMultipliers = new uint96[](numStrategiesToModify); // create array of indices to modify, assume no duplicates, and create array of multipliers for each index for (uint256 i = 0; i < numStrategiesToModify; i++) { - strategyIndices[i] = _randUint({ rand: bytes32(i), min: 0, max: numStrategiesToAdd - 1 }); - newMultipliers[i] = uint96(_randUint({ rand: bytes32(i), min: 1, max: type(uint96).max })); + strategyIndices[i] = _randUint({rand: bytes32(i), min: 0, max: numStrategiesToAdd - 1}); + newMultipliers[i] = uint96(_randUint({rand: bytes32(i), min: 1, max: type(uint96).max})); // ensure no duplicate indices if (i == 0) { prevIndex = strategyIndices[0]; @@ -905,9 +994,9 @@ contract StakeRegistryUnitTests_Config is StakeRegistryUnitTests { } // Expected events emitted - uint8 quorumNumber = _initializeQuorum(0 /* minimumStake */, numStrategiesToAdd); + uint8 quorumNumber = _initializeQuorum(0, /* minimumStake */ numStrategiesToAdd); for (uint256 i = 0; i < strategyIndices.length; i++) { - (IStrategy strategy, ) = stakeRegistry.strategyParams(quorumNumber, strategyIndices[i]); + (IStrategy strategy,) = stakeRegistry.strategyParams(quorumNumber, strategyIndices[i]); cheats.expectEmit(true, true, true, true, address(stakeRegistry)); emit StrategyMultiplierUpdated(quorumNumber, strategy, newMultipliers[i]); } @@ -924,15 +1013,17 @@ contract StakeRegistryUnitTests_Config is StakeRegistryUnitTests { /// @notice Tests for StakeRegistry.registerOperator contract StakeRegistryUnitTests_Register is StakeRegistryUnitTests { - - /******************************************************************************* - registerOperator - *******************************************************************************/ - + /** + * + * registerOperator + * + */ function test_registerOperator_Revert_WhenNotRegistryCoordinator() public { (address operator, bytes32 operatorId) = _selectNewOperator(); - cheats.expectRevert("StakeRegistry.onlyRegistryCoordinator: caller is not the RegistryCoordinator"); + cheats.expectRevert( + "StakeRegistry.onlyRegistryCoordinator: caller is not the RegistryCoordinator" + ); stakeRegistry.registerOperator(operator, operatorId, initializedQuorumBytes); } @@ -942,28 +1033,31 @@ contract StakeRegistryUnitTests_Register is StakeRegistryUnitTests { // Get a list of valid quorums ending in an invalid quorum number bytes memory invalidQuorums = _fuzz_getInvalidQuorums(rand); - cheats.expectRevert("StakeRegistry.registerOperator: quorum does not exist"); + cheats.expectRevert("StakeRegistry.quorumExists: quorum does not exist"); cheats.prank(address(registryCoordinator)); stakeRegistry.registerOperator(setup.operator, setup.operatorId, invalidQuorums); } /// @dev Attempt to register for all quorums, selecting one quorum to attempt with /// insufficient stake - function testFuzz_registerOperator_Revert_WhenInsufficientStake( - uint8 failingQuorum - ) public fuzzOnlyInitializedQuorums(failingQuorum) { + function testFuzz_registerOperator_Revert_WhenInsufficientStake(uint8 failingQuorum) + public + fuzzOnlyInitializedQuorums(failingQuorum) + { (address operator, bytes32 operatorId) = _selectNewOperator(); bytes memory quorumNumbers = initializedQuorumBytes; uint96[] memory minimumStakes = _getMinimumStakes(quorumNumbers); // Set the operator's weight to the minimum stake for each quorum // ... except the failing quorum, which gets minimum stake - 1 - for (uint i = 0; i < quorumNumbers.length; i++) { + for (uint256 i = 0; i < quorumNumbers.length; i++) { uint8 quorumNumber = uint8(quorumNumbers[i]); uint96 operatorWeight; if (quorumNumber == failingQuorum) { - unchecked { operatorWeight = minimumStakes[i] - 1; } + unchecked { + operatorWeight = minimumStakes[i] - 1; + } assertTrue(operatorWeight < minimumStakes[i], "minimum stake underflow"); } else { operatorWeight = minimumStakes[i]; @@ -973,7 +1067,9 @@ contract StakeRegistryUnitTests_Register is StakeRegistryUnitTests { } // Attempt to register - cheats.expectRevert("StakeRegistry.registerOperator: Operator does not meet minimum stake requirement for quorum"); + cheats.expectRevert( + "StakeRegistry.registerOperator: Operator does not meet minimum stake requirement for quorum" + ); cheats.prank(address(registryCoordinator)); stakeRegistry.registerOperator(operator, operatorId, quorumNumbers); } @@ -993,31 +1089,57 @@ contract StakeRegistryUnitTests_Register is StakeRegistryUnitTests { /// registerOperator cheats.prank(address(registryCoordinator)); - (uint96[] memory resultingStakes, uint96[] memory totalStakes) = + (uint96[] memory resultingStakes, uint96[] memory totalStakes) = stakeRegistry.registerOperator(setup.operator, setup.operatorId, setup.quorumNumbers); /// Read ending state - IStakeRegistry.StakeUpdate[] memory newOperatorStakes = _getLatestStakeUpdates(setup.operatorId, setup.quorumNumbers); - IStakeRegistry.StakeUpdate[] memory newTotalStakes = _getLatestTotalStakeUpdates(setup.quorumNumbers); - uint256[] memory operatorStakeHistoryLengths = _getStakeHistoryLengths(setup.operatorId, setup.quorumNumbers); + IStakeRegistry.StakeUpdate[] memory newOperatorStakes = + _getLatestStakeUpdates(setup.operatorId, setup.quorumNumbers); + IStakeRegistry.StakeUpdate[] memory newTotalStakes = + _getLatestTotalStakeUpdates(setup.quorumNumbers); + uint256[] memory operatorStakeHistoryLengths = + _getStakeHistoryLengths(setup.operatorId, setup.quorumNumbers); /// Check results - assertTrue(resultingStakes.length == setup.quorumNumbers.length, "invalid return length for operator stakes"); - assertTrue(totalStakes.length == setup.quorumNumbers.length, "invalid return length for total stakes"); + assertTrue( + resultingStakes.length == setup.quorumNumbers.length, + "invalid return length for operator stakes" + ); + assertTrue( + totalStakes.length == setup.quorumNumbers.length, + "invalid return length for total stakes" + ); - for (uint i = 0; i < setup.quorumNumbers.length; i++) { + for (uint256 i = 0; i < setup.quorumNumbers.length; i++) { IStakeRegistry.StakeUpdate memory newOperatorStake = newOperatorStakes[i]; IStakeRegistry.StakeUpdate memory newTotalStake = newTotalStakes[i]; // Check return value against weights, latest state read, and minimum stake - assertEq(resultingStakes[i], setup.operatorWeights[i], "stake registry did not return correct stake"); - assertEq(resultingStakes[i], newOperatorStake.stake, "invalid latest operator stake update"); + assertEq( + resultingStakes[i], + setup.operatorWeights[i], + "stake registry did not return correct stake" + ); + assertEq( + resultingStakes[i], newOperatorStake.stake, "invalid latest operator stake update" + ); assertTrue(resultingStakes[i] != 0, "registered operator with zero stake"); - assertTrue(resultingStakes[i] >= setup.minimumStakes[i], "stake registry did not return correct stake"); - + assertTrue( + resultingStakes[i] >= setup.minimumStakes[i], + "stake registry did not return correct stake" + ); + // Check stake increase from fuzzed input - assertEq(resultingStakes[i], newOperatorStake.stake, "did not add additional stake to operator correctly"); - assertEq(resultingStakes[i], newTotalStake.stake, "did not add additional stake to total correctly"); + assertEq( + resultingStakes[i], + newOperatorStake.stake, + "did not add additional stake to operator correctly" + ); + assertEq( + resultingStakes[i], + newTotalStake.stake, + "did not add additional stake to total correctly" + ); // Check that we had an update this block assertEq(newOperatorStake.updateBlockNumber, uint32(block.number), ""); @@ -1046,36 +1168,60 @@ contract StakeRegistryUnitTests_Register is StakeRegistryUnitTests { ) public { cheats.assume(numOperators > 1 && numOperators < 20); - RegisterSetup[] memory setups = _fuzz_setupRegisterOperators(quorumBitmap, additionalStake, numOperators); + RegisterSetup[] memory setups = + _fuzz_setupRegisterOperators(quorumBitmap, additionalStake, numOperators); // Register each operator one at a time, and check results: - for (uint i = 0; i < numOperators; i++) { + for (uint256 i = 0; i < numOperators; i++) { RegisterSetup memory setup = setups[i]; cheats.prank(address(registryCoordinator)); - (uint96[] memory resultingStakes, uint96[] memory totalStakes) = - stakeRegistry.registerOperator(setup.operator, setup.operatorId, setup.quorumNumbers); - + (uint96[] memory resultingStakes, uint96[] memory totalStakes) = stakeRegistry + .registerOperator(setup.operator, setup.operatorId, setup.quorumNumbers); + /// Read ending state - IStakeRegistry.StakeUpdate[] memory newOperatorStakes = _getLatestStakeUpdates(setup.operatorId, setup.quorumNumbers); - uint256[] memory operatorStakeHistoryLengths = _getStakeHistoryLengths(setup.operatorId, setup.quorumNumbers); + IStakeRegistry.StakeUpdate[] memory newOperatorStakes = + _getLatestStakeUpdates(setup.operatorId, setup.quorumNumbers); + uint256[] memory operatorStakeHistoryLengths = + _getStakeHistoryLengths(setup.operatorId, setup.quorumNumbers); // Sum stakes in `_totalStakeAdded` to be checked later _tallyTotalStakeAdded(setup.quorumNumbers, resultingStakes); /// Check results - assertTrue(resultingStakes.length == setup.quorumNumbers.length, "invalid return length for operator stakes"); - assertTrue(totalStakes.length == setup.quorumNumbers.length, "invalid return length for total stakes"); - for (uint j = 0; j < setup.quorumNumbers.length; j++) { + assertTrue( + resultingStakes.length == setup.quorumNumbers.length, + "invalid return length for operator stakes" + ); + assertTrue( + totalStakes.length == setup.quorumNumbers.length, + "invalid return length for total stakes" + ); + for (uint256 j = 0; j < setup.quorumNumbers.length; j++) { // Check result against weights and latest state read - assertEq(resultingStakes[j], setup.operatorWeights[j], "stake registry did not return correct stake"); - assertEq(resultingStakes[j], newOperatorStakes[j].stake, "invalid latest operator stake update"); + assertEq( + resultingStakes[j], + setup.operatorWeights[j], + "stake registry did not return correct stake" + ); + assertEq( + resultingStakes[j], + newOperatorStakes[j].stake, + "invalid latest operator stake update" + ); assertTrue(resultingStakes[j] != 0, "registered operator with zero stake"); // Check result against minimum stake - assertTrue(resultingStakes[j] >= setup.minimumStakes[j], "stake registry did not return correct stake"); - + assertTrue( + resultingStakes[j] >= setup.minimumStakes[j], + "stake registry did not return correct stake" + ); + // Check stake increase from fuzzed input - assertEq(resultingStakes[j], newOperatorStakes[j].stake, "did not add additional stake to operator correctly"); + assertEq( + resultingStakes[j], + newOperatorStakes[j].stake, + "did not add additional stake to operator correctly" + ); // Check this is the first entry in the operator stake history assertEq(operatorStakeHistoryLengths[j], 1, "invalid total stake history length"); } @@ -1083,12 +1229,25 @@ contract StakeRegistryUnitTests_Register is StakeRegistryUnitTests { // Check total stake results bytes memory quorumNumbers = initializedQuorumBytes; - IStakeRegistry.StakeUpdate[] memory newTotalStakes = _getLatestTotalStakeUpdates(quorumNumbers); - for (uint i = 0; i < quorumNumbers.length; i++) { + IStakeRegistry.StakeUpdate[] memory newTotalStakes = + _getLatestTotalStakeUpdates(quorumNumbers); + for (uint256 i = 0; i < quorumNumbers.length; i++) { uint8 quorumNumber = uint8(quorumNumbers[i]); - assertEq(newTotalStakes[i].stake, _totalStakeAdded[quorumNumber], "incorrect latest total stake"); - assertEq(newTotalStakes[i].nextUpdateBlockNumber, 0, "incorrect total stake next update block"); - assertEq(newTotalStakes[i].updateBlockNumber, uint32(block.number), "incorrect total stake next update block"); + assertEq( + newTotalStakes[i].stake, + _totalStakeAdded[quorumNumber], + "incorrect latest total stake" + ); + assertEq( + newTotalStakes[i].nextUpdateBlockNumber, + 0, + "incorrect total stake next update block" + ); + assertEq( + newTotalStakes[i].updateBlockNumber, + uint32(block.number), + "incorrect total stake next update block" + ); } } @@ -1111,56 +1270,89 @@ contract StakeRegistryUnitTests_Register is StakeRegistryUnitTests { cheats.assume(operatorsPerBlock >= 1 && operatorsPerBlock <= 4); cheats.assume(totalBlocks >= 2 && totalBlocks <= 5); - uint startBlock = block.number; - for (uint i = 1; i <= totalBlocks; i++) { + uint256 startBlock = block.number; + for (uint256 i = 1; i <= totalBlocks; i++) { // Move to current block number - uint currBlock = startBlock + i; + uint256 currBlock = startBlock + i; cheats.roll(currBlock); - RegisterSetup[] memory setups = - _fuzz_setupRegisterOperators(initializedQuorumBitmap, additionalStake, operatorsPerBlock); + RegisterSetup[] memory setups = _fuzz_setupRegisterOperators( + initializedQuorumBitmap, additionalStake, operatorsPerBlock + ); // Get prior total stake updates bytes memory quorumNumbers = setups[0].quorumNumbers; - uint[] memory prevHistoryLengths = _getTotalStakeHistoryLengths(quorumNumbers); - - for (uint j = 0; j < operatorsPerBlock; j++) { + uint256[] memory prevHistoryLengths = _getTotalStakeHistoryLengths(quorumNumbers); + + for (uint256 j = 0; j < operatorsPerBlock; j++) { RegisterSetup memory setup = setups[j]; cheats.prank(address(registryCoordinator)); - (uint96[] memory resultingStakes, ) = - stakeRegistry.registerOperator(setup.operator, setup.operatorId, setup.quorumNumbers); - + (uint96[] memory resultingStakes,) = stakeRegistry.registerOperator( + setup.operator, setup.operatorId, setup.quorumNumbers + ); + // Sum stakes in `_totalStakeAdded` to be checked later _tallyTotalStakeAdded(setup.quorumNumbers, resultingStakes); } // Get new total stake updates - uint[] memory newHistoryLengths = _getTotalStakeHistoryLengths(quorumNumbers); - IStakeRegistry.StakeUpdate[] memory newTotalStakes = _getLatestTotalStakeUpdates(quorumNumbers); + uint256[] memory newHistoryLengths = _getTotalStakeHistoryLengths(quorumNumbers); + IStakeRegistry.StakeUpdate[] memory newTotalStakes = + _getLatestTotalStakeUpdates(quorumNumbers); - for (uint j = 0; j < quorumNumbers.length; j++) { + for (uint256 j = 0; j < quorumNumbers.length; j++) { uint8 quorumNumber = uint8(quorumNumbers[j]); // Check that we've added 1 to total stake history length - assertEq(prevHistoryLengths[j] + 1, newHistoryLengths[j], "total history should have a new entry"); + assertEq( + prevHistoryLengths[j] + 1, + newHistoryLengths[j], + "total history should have a new entry" + ); // Validate latest entry correctness - assertEq(newTotalStakes[j].stake, _totalStakeAdded[quorumNumber], "latest update should match total stake added"); - assertEq(newTotalStakes[j].updateBlockNumber, currBlock, "latest update should be from current block"); - assertEq(newTotalStakes[j].nextUpdateBlockNumber, 0, "latest update should not have next update block"); + assertEq( + newTotalStakes[j].stake, + _totalStakeAdded[quorumNumber], + "latest update should match total stake added" + ); + assertEq( + newTotalStakes[j].updateBlockNumber, + currBlock, + "latest update should be from current block" + ); + assertEq( + newTotalStakes[j].nextUpdateBlockNumber, + 0, + "latest update should not have next update block" + ); // Validate previous entry was updated correctly - IStakeRegistry.StakeUpdate memory prevUpdate = - stakeRegistry.getTotalStakeUpdateAtIndex(quorumNumber, prevHistoryLengths[j]-1); - assertTrue(prevUpdate.stake < newTotalStakes[j].stake, "previous update should have lower stake than latest"); - assertEq(prevUpdate.updateBlockNumber + 1, newTotalStakes[j].updateBlockNumber, "prev entry should be from last block"); - assertEq(prevUpdate.nextUpdateBlockNumber, newTotalStakes[j].updateBlockNumber, "prev entry.next should be latest.cur"); + IStakeRegistry.StakeUpdate memory prevUpdate = stakeRegistry + .getTotalStakeUpdateAtIndex(quorumNumber, prevHistoryLengths[j] - 1); + assertTrue( + prevUpdate.stake < newTotalStakes[j].stake, + "previous update should have lower stake than latest" + ); + assertEq( + prevUpdate.updateBlockNumber + 1, + newTotalStakes[j].updateBlockNumber, + "prev entry should be from last block" + ); + assertEq( + prevUpdate.nextUpdateBlockNumber, + newTotalStakes[j].updateBlockNumber, + "prev entry.next should be latest.cur" + ); } } } - function _tallyTotalStakeAdded(bytes memory quorumNumbers, uint96[] memory stakeAdded) internal { - for (uint i = 0; i < quorumNumbers.length; i++) { + function _tallyTotalStakeAdded( + bytes memory quorumNumbers, + uint96[] memory stakeAdded + ) internal { + for (uint256 i = 0; i < quorumNumbers.length; i++) { uint8 quorumNumber = uint8(quorumNumbers[i]); _totalStakeAdded[quorumNumber] += stakeAdded[i]; } @@ -1169,13 +1361,13 @@ contract StakeRegistryUnitTests_Register is StakeRegistryUnitTests { /// @notice Tests for StakeRegistry.deregisterOperator contract StakeRegistryUnitTests_Deregister is StakeRegistryUnitTests { - using BitmapUtils for *; - /******************************************************************************* - deregisterOperator - *******************************************************************************/ - + /** + * + * deregisterOperator + * + */ function test_deregisterOperator_Revert_WhenNotRegistryCoordinator() public { DeregisterSetup memory setup = _fuzz_setupDeregisterOperator({ registeredFor: initializedQuorumBitmap, @@ -1183,7 +1375,9 @@ contract StakeRegistryUnitTests_Deregister is StakeRegistryUnitTests { fuzzy_addtlStake: 0 }); - cheats.expectRevert("StakeRegistry.onlyRegistryCoordinator: caller is not the RegistryCoordinator"); + cheats.expectRevert( + "StakeRegistry.onlyRegistryCoordinator: caller is not the RegistryCoordinator" + ); stakeRegistry.deregisterOperator(setup.operatorId, setup.quorumsToRemove); } @@ -1194,11 +1388,11 @@ contract StakeRegistryUnitTests_Deregister is StakeRegistryUnitTests { fuzzy_toRemove: initializedQuorumBitmap, fuzzy_addtlStake: 0 }); - + // Get a list of valid quorums ending in an invalid quorum number bytes memory invalidQuorums = _fuzz_getInvalidQuorums(rand); - cheats.expectRevert("StakeRegistry.registerOperator: quorum does not exist"); + cheats.expectRevert("StakeRegistry.quorumExists: quorum does not exist"); cheats.prank(address(registryCoordinator)); stakeRegistry.registerOperator(setup.operator, setup.operatorId, invalidQuorums); } @@ -1224,10 +1418,12 @@ contract StakeRegistryUnitTests_Deregister is StakeRegistryUnitTests { cheats.prank(address(registryCoordinator)); stakeRegistry.deregisterOperator(setup.operatorId, setup.quorumsToRemove); - IStakeRegistry.StakeUpdate[] memory newOperatorStakes = _getLatestStakeUpdates(setup.operatorId, setup.registeredQuorumNumbers); - IStakeRegistry.StakeUpdate[] memory newTotalStakes = _getLatestTotalStakeUpdates(setup.registeredQuorumNumbers); + IStakeRegistry.StakeUpdate[] memory newOperatorStakes = + _getLatestStakeUpdates(setup.operatorId, setup.registeredQuorumNumbers); + IStakeRegistry.StakeUpdate[] memory newTotalStakes = + _getLatestTotalStakeUpdates(setup.registeredQuorumNumbers); - for (uint i = 0; i < setup.registeredQuorumNumbers.length; i++) { + for (uint256 i = 0; i < setup.registeredQuorumNumbers.length; i++) { uint8 registeredQuorum = uint8(setup.registeredQuorumNumbers[i]); IStakeRegistry.StakeUpdate memory prevOperatorStake = setup.prevOperatorStakes[i]; @@ -1242,24 +1438,49 @@ contract StakeRegistryUnitTests_Deregister is StakeRegistryUnitTests { if (deregistered) { // Check that operator's stake was removed from both operator and total assertEq(newOperatorStake.stake, 0, "failed to remove stake"); - assertEq(newTotalStake.stake + prevOperatorStake.stake, prevTotalStake.stake, "failed to remove stake from total"); - + assertEq( + newTotalStake.stake + prevOperatorStake.stake, + prevTotalStake.stake, + "failed to remove stake from total" + ); + // Check that we had an update this block - assertEq(newOperatorStake.updateBlockNumber, uint32(block.number), "operator stake has incorrect update block"); - assertEq(newOperatorStake.nextUpdateBlockNumber, 0, "operator stake has incorrect next update block"); - assertEq(newTotalStake.updateBlockNumber, uint32(block.number), "total stake has incorrect update block"); - assertEq(newTotalStake.nextUpdateBlockNumber, 0, "total stake has incorrect next update block"); + assertEq( + newOperatorStake.updateBlockNumber, + uint32(block.number), + "operator stake has incorrect update block" + ); + assertEq( + newOperatorStake.nextUpdateBlockNumber, + 0, + "operator stake has incorrect next update block" + ); + assertEq( + newTotalStake.updateBlockNumber, + uint32(block.number), + "total stake has incorrect update block" + ); + assertEq( + newTotalStake.nextUpdateBlockNumber, + 0, + "total stake has incorrect next update block" + ); } else { // Ensure no change to operator or total stakes - assertTrue(_isUnchanged(prevOperatorStake, newOperatorStake), "operator stake incorrectly updated"); - assertTrue(_isUnchanged(prevTotalStake, newTotalStake), "total stake incorrectly updated"); + assertTrue( + _isUnchanged(prevOperatorStake, newOperatorStake), + "operator stake incorrectly updated" + ); + assertTrue( + _isUnchanged(prevTotalStake, newTotalStake), "total stake incorrectly updated" + ); } } } // Track total stake removed from each quorum as we deregister operators mapping(uint8 => uint96) _totalStakeRemoved; - + /** * @dev Registers multiple operators for each initialized quorum, adding `additionalStake` * to the minimum stake for each quorum. Tests deregistering the operators for @@ -1284,21 +1505,24 @@ contract StakeRegistryUnitTests_Deregister is StakeRegistryUnitTests { bytes memory registeredQuorums = initializedQuorumBytes; uint192 quorumsToRemoveBitmap = setups[0].quorumsToRemoveBitmap; - IStakeRegistry.StakeUpdate[] memory prevTotalStakes = _getLatestTotalStakeUpdates(registeredQuorums); + IStakeRegistry.StakeUpdate[] memory prevTotalStakes = + _getLatestTotalStakeUpdates(registeredQuorums); // Deregister operators one at a time and check results - for (uint i = 0; i < numOperators; i++) { + for (uint256 i = 0; i < numOperators; i++) { DeregisterSetup memory setup = setups[i]; bytes32 operatorId = setup.operatorId; cheats.prank(address(registryCoordinator)); stakeRegistry.deregisterOperator(setup.operatorId, setup.quorumsToRemove); - IStakeRegistry.StakeUpdate[] memory newOperatorStakes = _getLatestStakeUpdates(operatorId, registeredQuorums); - IStakeRegistry.StakeUpdate[] memory newTotalStakes = _getLatestTotalStakeUpdates(registeredQuorums); + IStakeRegistry.StakeUpdate[] memory newOperatorStakes = + _getLatestStakeUpdates(operatorId, registeredQuorums); + IStakeRegistry.StakeUpdate[] memory newTotalStakes = + _getLatestTotalStakeUpdates(registeredQuorums); // Check results for each quorum - for (uint j = 0; j < registeredQuorums.length; j++) { + for (uint256 j = 0; j < registeredQuorums.length; j++) { uint8 registeredQuorum = uint8(registeredQuorums[j]); IStakeRegistry.StakeUpdate memory prevOperatorStake = setup.prevOperatorStakes[j]; @@ -1315,24 +1539,48 @@ contract StakeRegistryUnitTests_Deregister is StakeRegistryUnitTests { // Check that operator's stake was removed from both operator and total assertEq(newOperatorStake.stake, 0, "failed to remove stake"); - assertEq(newTotalStake.stake + _totalStakeRemoved[registeredQuorum], prevTotalStake.stake, "failed to remove stake from total"); - + assertEq( + newTotalStake.stake + _totalStakeRemoved[registeredQuorum], + prevTotalStake.stake, + "failed to remove stake from total" + ); + // Check that we had an update this block - assertEq(newOperatorStake.updateBlockNumber, uint32(block.number), "operator stake has incorrect update block"); - assertEq(newOperatorStake.nextUpdateBlockNumber, 0, "operator stake has incorrect next update block"); - assertEq(newTotalStake.updateBlockNumber, uint32(block.number), "total stake has incorrect update block"); - assertEq(newTotalStake.nextUpdateBlockNumber, 0, "total stake has incorrect next update block"); + assertEq( + newOperatorStake.updateBlockNumber, + uint32(block.number), + "operator stake has incorrect update block" + ); + assertEq( + newOperatorStake.nextUpdateBlockNumber, + 0, + "operator stake has incorrect next update block" + ); + assertEq( + newTotalStake.updateBlockNumber, + uint32(block.number), + "total stake has incorrect update block" + ); + assertEq( + newTotalStake.nextUpdateBlockNumber, + 0, + "total stake has incorrect next update block" + ); } else { // Ensure no change to operator stake - assertTrue(_isUnchanged(prevOperatorStake, newOperatorStake), "operator stake incorrectly updated"); + assertTrue( + _isUnchanged(prevOperatorStake, newOperatorStake), + "operator stake incorrectly updated" + ); } } } // Now that we've deregistered all the operators, check the final results // For the quorums we chose to deregister from, the total stake should be zero - IStakeRegistry.StakeUpdate[] memory finalTotalStakes = _getLatestTotalStakeUpdates(registeredQuorums); - for (uint i = 0; i < registeredQuorums.length; i++) { + IStakeRegistry.StakeUpdate[] memory finalTotalStakes = + _getLatestTotalStakeUpdates(registeredQuorums); + for (uint256 i = 0; i < registeredQuorums.length; i++) { uint8 registeredQuorum = uint8(registeredQuorums[i]); // Whether or not we deregistered operators from this quorum @@ -1340,10 +1588,21 @@ contract StakeRegistryUnitTests_Deregister is StakeRegistryUnitTests { if (deregistered) { assertEq(finalTotalStakes[i].stake, 0, "failed to remove all stake from quorum"); - assertEq(finalTotalStakes[i].updateBlockNumber, uint32(block.number), "failed to remove all stake from quorum"); - assertEq(finalTotalStakes[i].nextUpdateBlockNumber, 0, "failed to remove all stake from quorum"); + assertEq( + finalTotalStakes[i].updateBlockNumber, + uint32(block.number), + "failed to remove all stake from quorum" + ); + assertEq( + finalTotalStakes[i].nextUpdateBlockNumber, + 0, + "failed to remove all stake from quorum" + ); } else { - assertTrue(_isUnchanged(finalTotalStakes[i], prevTotalStakes[i]), "incorrectly updated total stake history for unmodified quorum"); + assertTrue( + _isUnchanged(finalTotalStakes[i], prevTotalStakes[i]), + "incorrectly updated total stake history for unmodified quorum" + ); } } } @@ -1367,8 +1626,8 @@ contract StakeRegistryUnitTests_Deregister is StakeRegistryUnitTests { cheats.assume(operatorsPerBlock >= 1 && operatorsPerBlock <= 4); cheats.assume(totalBlocks >= 2 && totalBlocks <= 5); - uint numOperators = operatorsPerBlock * totalBlocks; - uint operatorIdx; // track index in setups over test + uint256 numOperators = operatorsPerBlock * totalBlocks; + uint256 operatorIdx; // track index in setups over test // Select multiple new operators, set their weight equal to the minimum plus some additional, // then register them for all initialized quorums @@ -1382,61 +1641,91 @@ contract StakeRegistryUnitTests_Deregister is StakeRegistryUnitTests { // For all operators, we're going to register for and then deregister from all initialized quorums bytes memory registeredQuorums = initializedQuorumBytes; - IStakeRegistry.StakeUpdate[] memory prevTotalStakes = _getLatestTotalStakeUpdates(registeredQuorums); - uint startBlock = block.number; + IStakeRegistry.StakeUpdate[] memory prevTotalStakes = + _getLatestTotalStakeUpdates(registeredQuorums); + uint256 startBlock = block.number; - for (uint i = 1; i <= totalBlocks; i++) { + for (uint256 i = 1; i <= totalBlocks; i++) { // Move to current block number - uint currBlock = startBlock + i; + uint256 currBlock = startBlock + i; cheats.roll(currBlock); - uint[] memory prevHistoryLengths = _getTotalStakeHistoryLengths(registeredQuorums); + uint256[] memory prevHistoryLengths = _getTotalStakeHistoryLengths(registeredQuorums); // Within this block: deregister some operators for all quorums and add the stake removed // to `_totalStakeRemoved` for later checks - for (uint j = 0; j < operatorsPerBlock; j++) { + for (uint256 j = 0; j < operatorsPerBlock; j++) { DeregisterSetup memory setup = setups[operatorIdx]; operatorIdx++; cheats.prank(address(registryCoordinator)); stakeRegistry.deregisterOperator(setup.operatorId, setup.quorumsToRemove); - for (uint k = 0; k < registeredQuorums.length; k++) { + for (uint256 k = 0; k < registeredQuorums.length; k++) { uint8 quorumNumber = uint8(registeredQuorums[k]); _totalStakeRemoved[quorumNumber] += setup.prevOperatorStakes[k].stake; } } - uint[] memory newHistoryLengths = _getTotalStakeHistoryLengths(registeredQuorums); - IStakeRegistry.StakeUpdate[] memory newTotalStakes = _getLatestTotalStakeUpdates(registeredQuorums); + uint256[] memory newHistoryLengths = _getTotalStakeHistoryLengths(registeredQuorums); + IStakeRegistry.StakeUpdate[] memory newTotalStakes = + _getLatestTotalStakeUpdates(registeredQuorums); // Validate the sum of all updates this block: // Each quorum should have a new historical entry with the correct update block pointers // ... and each quorum's stake should have decreased by `_totalStakeRemoved[quorum]` - for (uint j = 0; j < registeredQuorums.length; j++) { + for (uint256 j = 0; j < registeredQuorums.length; j++) { uint8 quorumNumber = uint8(registeredQuorums[j]); // Check that we've added 1 to total stake history length - assertEq(prevHistoryLengths[j] + 1, newHistoryLengths[j], "total history should have a new entry"); + assertEq( + prevHistoryLengths[j] + 1, + newHistoryLengths[j], + "total history should have a new entry" + ); // Validate latest entry correctness - assertEq(newTotalStakes[j].stake + _totalStakeRemoved[quorumNumber], prevTotalStakes[j].stake, "stake not removed correctly from total stake"); - assertEq(newTotalStakes[j].updateBlockNumber, currBlock, "latest update should be from current block"); - assertEq(newTotalStakes[j].nextUpdateBlockNumber, 0, "latest update should not have next update block"); - - IStakeRegistry.StakeUpdate memory prevUpdate = - stakeRegistry.getTotalStakeUpdateAtIndex(quorumNumber, prevHistoryLengths[j]-1); + assertEq( + newTotalStakes[j].stake + _totalStakeRemoved[quorumNumber], + prevTotalStakes[j].stake, + "stake not removed correctly from total stake" + ); + assertEq( + newTotalStakes[j].updateBlockNumber, + currBlock, + "latest update should be from current block" + ); + assertEq( + newTotalStakes[j].nextUpdateBlockNumber, + 0, + "latest update should not have next update block" + ); + + IStakeRegistry.StakeUpdate memory prevUpdate = stakeRegistry + .getTotalStakeUpdateAtIndex(quorumNumber, prevHistoryLengths[j] - 1); // Validate previous entry was updated correctly - assertTrue(prevUpdate.stake > newTotalStakes[j].stake, "previous update should have higher stake than latest"); - assertEq(prevUpdate.updateBlockNumber + 1, newTotalStakes[j].updateBlockNumber, "prev entry should be from last block"); - assertEq(prevUpdate.nextUpdateBlockNumber, newTotalStakes[j].updateBlockNumber, "prev entry.next should be latest.cur"); + assertTrue( + prevUpdate.stake > newTotalStakes[j].stake, + "previous update should have higher stake than latest" + ); + assertEq( + prevUpdate.updateBlockNumber + 1, + newTotalStakes[j].updateBlockNumber, + "prev entry should be from last block" + ); + assertEq( + prevUpdate.nextUpdateBlockNumber, + newTotalStakes[j].updateBlockNumber, + "prev entry.next should be latest.cur" + ); } } // Now that we've deregistered all the operators, check the final results // Each quorum's stake should be zero - IStakeRegistry.StakeUpdate[] memory finalTotalStakes = _getLatestTotalStakeUpdates(registeredQuorums); - for (uint i = 0; i < registeredQuorums.length; i++) { + IStakeRegistry.StakeUpdate[] memory finalTotalStakes = + _getLatestTotalStakeUpdates(registeredQuorums); + for (uint256 i = 0; i < registeredQuorums.length; i++) { assertEq(finalTotalStakes[i].stake, 0, "failed to remove all stake from quorum"); } } @@ -1444,30 +1733,27 @@ contract StakeRegistryUnitTests_Deregister is StakeRegistryUnitTests { /// @notice Tests for StakeRegistry.updateOperatorStake contract StakeRegistryUnitTests_StakeUpdates is StakeRegistryUnitTests { - using BitmapUtils for *; function test_updateOperatorStake_Revert_WhenNotRegistryCoordinator() public { - UpdateSetup memory setup = _fuzz_setupUpdateOperatorStake({ - registeredFor: initializedQuorumBitmap, - fuzzy_Delta: 0 - }); + UpdateSetup memory setup = + _fuzz_setupUpdateOperatorStake({registeredFor: initializedQuorumBitmap, fuzzy_Delta: 0}); - cheats.expectRevert("StakeRegistry.onlyRegistryCoordinator: caller is not the RegistryCoordinator"); + cheats.expectRevert( + "StakeRegistry.onlyRegistryCoordinator: caller is not the RegistryCoordinator" + ); stakeRegistry.updateOperatorStake(setup.operator, setup.operatorId, setup.quorumNumbers); } function testFuzz_updateOperatorStake_Revert_WhenQuorumDoesNotExist(bytes32 rand) public { // Create a new operator registered for all quorums - UpdateSetup memory setup = _fuzz_setupUpdateOperatorStake({ - registeredFor: initializedQuorumBitmap, - fuzzy_Delta: 0 - }); - + UpdateSetup memory setup = + _fuzz_setupUpdateOperatorStake({registeredFor: initializedQuorumBitmap, fuzzy_Delta: 0}); + // Get a list of valid quorums ending in an invalid quorum number bytes memory invalidQuorums = _fuzz_getInvalidQuorums(rand); - cheats.expectRevert("StakeRegistry.updateOperatorStake: quorum does not exist"); + cheats.expectRevert("StakeRegistry.quorumExists: quorum does not exist"); cheats.prank(address(registryCoordinator)); stakeRegistry.updateOperatorStake(setup.operator, setup.operatorId, invalidQuorums); } @@ -1487,20 +1773,24 @@ contract StakeRegistryUnitTests_StakeUpdates is StakeRegistryUnitTests { }); // Get starting state - IStakeRegistry.StakeUpdate[] memory prevOperatorStakes = _getLatestStakeUpdates(setup.operatorId, setup.quorumNumbers); - IStakeRegistry.StakeUpdate[] memory prevTotalStakes = _getLatestTotalStakeUpdates(setup.quorumNumbers); + IStakeRegistry.StakeUpdate[] memory prevOperatorStakes = + _getLatestStakeUpdates(setup.operatorId, setup.quorumNumbers); + IStakeRegistry.StakeUpdate[] memory prevTotalStakes = + _getLatestTotalStakeUpdates(setup.quorumNumbers); // updateOperatorStake cheats.prank(address(registryCoordinator)); - uint192 quorumsToRemove = + uint192 quorumsToRemove = stakeRegistry.updateOperatorStake(setup.operator, setup.operatorId, setup.quorumNumbers); // Get ending state - IStakeRegistry.StakeUpdate[] memory newOperatorStakes = _getLatestStakeUpdates(setup.operatorId, setup.quorumNumbers); - IStakeRegistry.StakeUpdate[] memory newTotalStakes = _getLatestTotalStakeUpdates(setup.quorumNumbers); + IStakeRegistry.StakeUpdate[] memory newOperatorStakes = + _getLatestStakeUpdates(setup.operatorId, setup.quorumNumbers); + IStakeRegistry.StakeUpdate[] memory newTotalStakes = + _getLatestTotalStakeUpdates(setup.quorumNumbers); // Check results for each quorum - for (uint i = 0; i < setup.quorumNumbers.length; i++) { + for (uint256 i = 0; i < setup.quorumNumbers.length; i++) { uint8 quorumNumber = uint8(setup.quorumNumbers[i]); uint96 minimumStake = setup.minimumStakes[i]; @@ -1513,33 +1803,63 @@ contract StakeRegistryUnitTests_StakeUpdates is StakeRegistryUnitTests { IStakeRegistry.StakeUpdate memory newTotalStake = newTotalStakes[i]; // Sanity-check setup - operator should start with minimumStake - assertTrue(prevOperatorStake.stake == minimumStake, "operator should start with nonzero stake"); + assertTrue( + prevOperatorStake.stake == minimumStake, "operator should start with nonzero stake" + ); if (endingWeight > minimumStake) { // Check updating an operator who has added stake above the minimum: // Only updates should be stake added to operator/total stakes uint96 stakeAdded = setup.stakeDeltaAbs; - assertEq(prevOperatorStake.stake + stakeAdded, newOperatorStake.stake, "failed to add delta to operator stake"); - assertEq(prevTotalStake.stake + stakeAdded, newTotalStake.stake, "failed to add delta to total stake"); + assertEq( + prevOperatorStake.stake + stakeAdded, + newOperatorStake.stake, + "failed to add delta to operator stake" + ); + assertEq( + prevTotalStake.stake + stakeAdded, + newTotalStake.stake, + "failed to add delta to total stake" + ); // Return value should be empty since we're still above the minimum - assertTrue(quorumsToRemove.isEmpty(), "positive stake delta should not remove any quorums"); + assertTrue( + quorumsToRemove.isEmpty(), "positive stake delta should not remove any quorums" + ); } else if (endingWeight < minimumStake) { // Check updating an operator who is now below the minimum: // Stake should now be zero, regardless of stake delta uint96 stakeRemoved = minimumStake; - assertEq(prevOperatorStake.stake - stakeRemoved, newOperatorStake.stake, "failed to remove delta from operator stake"); - assertEq(prevTotalStake.stake - stakeRemoved, newTotalStake.stake, "failed to remove delta from total stake"); + assertEq( + prevOperatorStake.stake - stakeRemoved, + newOperatorStake.stake, + "failed to remove delta from operator stake" + ); + assertEq( + prevTotalStake.stake - stakeRemoved, + newTotalStake.stake, + "failed to remove delta from total stake" + ); assertEq(newOperatorStake.stake, 0, "operator stake should now be zero"); // Quorum should be added to return bitmap - assertTrue(quorumsToRemove.isSet(quorumNumber), "quorum should be in removal bitmap"); + assertTrue( + quorumsToRemove.isSet(quorumNumber), "quorum should be in removal bitmap" + ); } else { // Check that no update occurs if weight remains the same - assertTrue(_isUnchanged(prevOperatorStake, newOperatorStake), "neutral stake delta should not have changed operator stake history"); - assertTrue(_isUnchanged(prevTotalStake, newTotalStake), "neutral stake delta should not have changed total stake history"); + assertTrue( + _isUnchanged(prevOperatorStake, newOperatorStake), + "neutral stake delta should not have changed operator stake history" + ); + assertTrue( + _isUnchanged(prevTotalStake, newTotalStake), + "neutral stake delta should not have changed total stake history" + ); // Check that return value is empty - we're still at the minimum, so no quorums should be removed - assertTrue(quorumsToRemove.isEmpty(), "neutral stake delta should not remove any quorums"); + assertTrue( + quorumsToRemove.isEmpty(), "neutral stake delta should not remove any quorums" + ); } } } @@ -1568,11 +1888,12 @@ contract StakeRegistryUnitTests_StakeUpdates is StakeRegistryUnitTests { bytes memory quorumNumbers = initializedQuorumBytes; // Get initial total history state - uint[] memory initialHistoryLengths = _getTotalStakeHistoryLengths(quorumNumbers); - IStakeRegistry.StakeUpdate[] memory initialTotalStakes = _getLatestTotalStakeUpdates(quorumNumbers); + uint256[] memory initialHistoryLengths = _getTotalStakeHistoryLengths(quorumNumbers); + IStakeRegistry.StakeUpdate[] memory initialTotalStakes = + _getLatestTotalStakeUpdates(quorumNumbers); // Call `updateOperatorStake` one by one - for (uint i = 0; i < numOperators; i++) { + for (uint256 i = 0; i < numOperators; i++) { UpdateSetup memory setup = setups[i]; // updateOperatorStake @@ -1581,10 +1902,11 @@ contract StakeRegistryUnitTests_StakeUpdates is StakeRegistryUnitTests { } // Check final results for each quorum - uint[] memory finalHistoryLengths = _getTotalStakeHistoryLengths(quorumNumbers); - IStakeRegistry.StakeUpdate[] memory finalTotalStakes = _getLatestTotalStakeUpdates(quorumNumbers); + uint256[] memory finalHistoryLengths = _getTotalStakeHistoryLengths(quorumNumbers); + IStakeRegistry.StakeUpdate[] memory finalTotalStakes = + _getLatestTotalStakeUpdates(quorumNumbers); - for (uint i = 0; i < quorumNumbers.length; i++) { + for (uint256 i = 0; i < quorumNumbers.length; i++) { IStakeRegistry.StakeUpdate memory initialTotalStake = initialTotalStakes[i]; IStakeRegistry.StakeUpdate memory finalTotalStake = finalTotalStakes[i]; @@ -1593,23 +1915,42 @@ contract StakeRegistryUnitTests_StakeUpdates is StakeRegistryUnitTests { uint96 stakeDeltaAbs = setups[0].stakeDeltaAbs; // Sanity-check setup: previous total stake should be minimumStake * numOperators - assertEq(initialTotalStake.stake, minimumStake * numOperators, "quorum should start with minimum stake from all operators"); + assertEq( + initialTotalStake.stake, + minimumStake * numOperators, + "quorum should start with minimum stake from all operators" + ); // history lengths should be unchanged - assertEq(initialHistoryLengths[i], finalHistoryLengths[i], "history lengths should remain unchanged"); - + assertEq( + initialHistoryLengths[i], + finalHistoryLengths[i], + "history lengths should remain unchanged" + ); + if (endingWeight > minimumStake) { // All operators had their stake increased by stakeDelta uint96 stakeAdded = numOperators * stakeDeltaAbs; - assertEq(initialTotalStake.stake + stakeAdded, finalTotalStake.stake, "failed to add delta for all operators"); + assertEq( + initialTotalStake.stake + stakeAdded, + finalTotalStake.stake, + "failed to add delta for all operators" + ); } else if (endingWeight < minimumStake) { // All operators had their entire stake removed uint96 stakeRemoved = numOperators * minimumStake; - assertEq(initialTotalStake.stake - stakeRemoved, finalTotalStake.stake, "failed to remove delta from total stake"); + assertEq( + initialTotalStake.stake - stakeRemoved, + finalTotalStake.stake, + "failed to remove delta from total stake" + ); assertEq(finalTotalStake.stake, 0, "final total stake should be zero"); } else { // No change in stake for any operator - assertTrue(_isUnchanged(initialTotalStake, finalTotalStake), "neutral stake delta should result in no change"); + assertTrue( + _isUnchanged(initialTotalStake, finalTotalStake), + "neutral stake delta should result in no change" + ); } } } @@ -1626,7 +1967,7 @@ contract StakeRegistryUnitTests_StakeUpdates is StakeRegistryUnitTests { int8 stakeDelta ) public { cheats.assume(totalBlocks >= 2 && totalBlocks <= 8); - + uint256 startBlock = block.number; for (uint256 j = 1; j <= totalBlocks; j++) { UpdateSetup memory setup = _fuzz_setupUpdateOperatorStake({ @@ -1635,9 +1976,12 @@ contract StakeRegistryUnitTests_StakeUpdates is StakeRegistryUnitTests { }); // Get starting state - IStakeRegistry.StakeUpdate[] memory prevOperatorStakes = _getLatestStakeUpdates(setup.operatorId, setup.quorumNumbers); - IStakeRegistry.StakeUpdate[] memory prevTotalStakes = _getLatestTotalStakeUpdates(setup.quorumNumbers); - uint256[] memory prevOperatorHistoryLengths = _getStakeHistoryLengths(setup.operatorId, setup.quorumNumbers); + IStakeRegistry.StakeUpdate[] memory prevOperatorStakes = + _getLatestStakeUpdates(setup.operatorId, setup.quorumNumbers); + IStakeRegistry.StakeUpdate[] memory prevTotalStakes = + _getLatestTotalStakeUpdates(setup.quorumNumbers); + uint256[] memory prevOperatorHistoryLengths = + _getStakeHistoryLengths(setup.operatorId, setup.quorumNumbers); // Move to current block number uint256 currBlock = startBlock + j; @@ -1645,16 +1989,20 @@ contract StakeRegistryUnitTests_StakeUpdates is StakeRegistryUnitTests { // updateOperatorStake cheats.prank(address(registryCoordinator)); - uint192 quorumsToRemove = - stakeRegistry.updateOperatorStake(setup.operator, setup.operatorId, setup.quorumNumbers); + uint192 quorumsToRemove = stakeRegistry.updateOperatorStake( + setup.operator, setup.operatorId, setup.quorumNumbers + ); // Get ending state - IStakeRegistry.StakeUpdate[] memory newOperatorStakes = _getLatestStakeUpdates(setup.operatorId, setup.quorumNumbers); - IStakeRegistry.StakeUpdate[] memory newTotalStakes = _getLatestTotalStakeUpdates(setup.quorumNumbers); - uint256[] memory newOperatorHistoryLengths = _getStakeHistoryLengths(setup.operatorId, setup.quorumNumbers); + IStakeRegistry.StakeUpdate[] memory newOperatorStakes = + _getLatestStakeUpdates(setup.operatorId, setup.quorumNumbers); + IStakeRegistry.StakeUpdate[] memory newTotalStakes = + _getLatestTotalStakeUpdates(setup.quorumNumbers); + uint256[] memory newOperatorHistoryLengths = + _getStakeHistoryLengths(setup.operatorId, setup.quorumNumbers); // Check results for each quorum - for (uint i = 0; i < setup.quorumNumbers.length; i++) { + for (uint256 i = 0; i < setup.quorumNumbers.length; i++) { uint8 quorumNumber = uint8(setup.quorumNumbers[i]); uint96 minimumStake = setup.minimumStakes[i]; @@ -1667,42 +2015,95 @@ contract StakeRegistryUnitTests_StakeUpdates is StakeRegistryUnitTests { // IStakeRegistry.StakeUpdate memory newTotalStake = newTotalStakes[i]; // Sanity-check setup - operator should start with minimumStake - assertTrue(prevOperatorStake.stake == minimumStake, "operator should start with nonzero stake"); + assertTrue( + prevOperatorStake.stake == minimumStake, + "operator should start with nonzero stake" + ); if (endingWeight > minimumStake) { // Check updating an operator who has added stake above the minimum: uint96 stakeAdded = setup.stakeDeltaAbs; - assertEq(prevOperatorStake.stake + stakeAdded, newOperatorStake.stake, "failed to add delta to operator stake"); - assertEq(prevTotalStakes[i].stake + stakeAdded, newTotalStakes[i].stake, "failed to add delta to total stake"); + assertEq( + prevOperatorStake.stake + stakeAdded, + newOperatorStake.stake, + "failed to add delta to operator stake" + ); + assertEq( + prevTotalStakes[i].stake + stakeAdded, + newTotalStakes[i].stake, + "failed to add delta to total stake" + ); // Return value should be empty since we're still above the minimum - assertTrue(quorumsToRemove.isEmpty(), "positive stake delta should not remove any quorums"); - assertEq(prevOperatorHistoryLengths[i] + 1, newOperatorHistoryLengths[i], "operator should have a new pushed update"); + assertTrue( + quorumsToRemove.isEmpty(), + "positive stake delta should not remove any quorums" + ); + assertEq( + prevOperatorHistoryLengths[i] + 1, + newOperatorHistoryLengths[i], + "operator should have a new pushed update" + ); } else if (endingWeight < minimumStake) { // Check updating an operator who is now below the minimum: // Stake should now be zero, regardless of stake delta uint96 stakeRemoved = minimumStake; - assertEq(prevOperatorStake.stake - stakeRemoved, newOperatorStake.stake, "failed to remove delta from operator stake"); + assertEq( + prevOperatorStake.stake - stakeRemoved, + newOperatorStake.stake, + "failed to remove delta from operator stake" + ); // assertEq(prevTotalStake.stake - stakeRemoved, newTotalStake.stake, "failed to remove delta from total stake"); assertEq(newOperatorStake.stake, 0, "operator stake should now be zero"); // Quorum should be added to return bitmap - assertTrue(quorumsToRemove.isSet(quorumNumber), "quorum should be in removal bitmap"); + assertTrue( + quorumsToRemove.isSet(quorumNumber), "quorum should be in removal bitmap" + ); if (prevOperatorStake.stake >= minimumStake) { // Total stakes and operator history should be updated - assertEq(prevOperatorHistoryLengths[i] + 1, newOperatorHistoryLengths[i], "operator should have a new pushed update"); - assertEq(prevTotalStakes[i].stake, newTotalStakes[i].stake + prevOperatorStake.stake, "failed to remove from total stake"); + assertEq( + prevOperatorHistoryLengths[i] + 1, + newOperatorHistoryLengths[i], + "operator should have a new pushed update" + ); + assertEq( + prevTotalStakes[i].stake, + newTotalStakes[i].stake + prevOperatorStake.stake, + "failed to remove from total stake" + ); } else { // Total stakes and history should remain unchanged - assertEq(prevOperatorHistoryLengths[i], newOperatorHistoryLengths[i], "history lengths should remain unchanged"); - assertEq(prevTotalStakes[i].stake, newTotalStakes[i].stake, "total stake should remain unchanged"); + assertEq( + prevOperatorHistoryLengths[i], + newOperatorHistoryLengths[i], + "history lengths should remain unchanged" + ); + assertEq( + prevTotalStakes[i].stake, + newTotalStakes[i].stake, + "total stake should remain unchanged" + ); } } else { // Check that no update occurs if weight remains the same - assertTrue(_isUnchanged(prevOperatorStake, newOperatorStake), "neutral stake delta should not have changed operator stake history"); - assertTrue(_isUnchanged(prevTotalStakes[i], newTotalStakes[i]), "neutral stake delta should not have changed total stake history"); + assertTrue( + _isUnchanged(prevOperatorStake, newOperatorStake), + "neutral stake delta should not have changed operator stake history" + ); + assertTrue( + _isUnchanged(prevTotalStakes[i], newTotalStakes[i]), + "neutral stake delta should not have changed total stake history" + ); // Check that return value is empty - we're still at the minimum, so no quorums should be removed - assertTrue(quorumsToRemove.isEmpty(), "neutral stake delta should not remove any quorums"); - assertEq(prevOperatorHistoryLengths[i], newOperatorHistoryLengths[i], "history lengths should remain unchanged"); + assertTrue( + quorumsToRemove.isEmpty(), + "neutral stake delta should not remove any quorums" + ); + assertEq( + prevOperatorHistoryLengths[i], + newOperatorHistoryLengths[i], + "history lengths should remain unchanged" + ); } } } @@ -1715,7 +2116,7 @@ contract StakeRegistryUnitTests_weightOfOperatorForQuorum is StakeRegistryUnitTe /** * @dev Initialize a new quorum with fuzzed multipliers and corresponding shares for an operator. - * The minimum stake for the quorum is 0 so that any fuzzed input shares will register the operator + * The minimum stake for the quorum is 0 so that any fuzzed input shares will register the operator * successfully and return a value for weightOfOperatorForQuorum. Fuzz test sets the operator shares * and asserts that the summed weight of the operator is correct. */ @@ -1730,23 +2131,30 @@ contract StakeRegistryUnitTests_weightOfOperatorForQuorum is StakeRegistryUnitTe // Initialize quorum with strategies of fuzzed multipliers. // Bound multipliers and shares max values to prevent overflows - IStakeRegistry.StrategyParams[] memory strategyParams = new IStakeRegistry.StrategyParams[](3); - for (uint i = 0; i < strategyParams.length; i++) { - multipliers[i] = uint96(_randUint({rand: bytes32(uint256(multipliers[i])), min: 0, max: 1000*WEIGHTING_DIVISOR })); - shares[i] = uint96(_randUint({rand: bytes32(uint256(shares[i])), min: 0, max: 10e20 })); + IStakeRegistry.StrategyParams[] memory strategyParams = + new IStakeRegistry.StrategyParams[](3); + for (uint256 i = 0; i < strategyParams.length; i++) { + multipliers[i] = uint96( + _randUint({ + rand: bytes32(uint256(multipliers[i])), + min: 0, + max: 1000 * WEIGHTING_DIVISOR + }) + ); + shares[i] = uint96(_randUint({rand: bytes32(uint256(shares[i])), min: 0, max: 10e20})); - IStrategy strat = IStrategy(address(uint160(uint256(keccak256(abi.encodePacked("Voteweighing test", i)))))); - strategyParams[i] = IStakeRegistry.StrategyParams( - strat, - uint96(WEIGHTING_DIVISOR) + multipliers[i] + IStrategy strat = IStrategy( + address(uint160(uint256(keccak256(abi.encodePacked("Voteweighing test", i))))) ); + strategyParams[i] = + IStakeRegistry.StrategyParams(strat, uint96(WEIGHTING_DIVISOR) + multipliers[i]); } cheats.prank(address(registryCoordinator)); uint8 quorumNumber = nextQuorum; - stakeRegistry.initializeQuorum(quorumNumber, 0 /* minimumStake */, strategyParams); + stakeRegistry.initializeQuorum(quorumNumber, 0, /* minimumStake */ strategyParams); // set the operator shares - for (uint i = 0; i < strategyParams.length; i++) { + for (uint256 i = 0; i < strategyParams.length; i++) { delegationMock.setOperatorShares(operator, strategyParams[i].strategy, shares[i]); } @@ -1756,11 +2164,12 @@ contract StakeRegistryUnitTests_weightOfOperatorForQuorum is StakeRegistryUnitTe cheats.prank(address(registryCoordinator)); stakeRegistry.registerOperator(operator, defaultOperatorId, quorumNumbers); - // assert weight of the operator uint96 expectedWeight = 0; - for (uint i = 0; i < strategyParams.length; i++) { - expectedWeight += uint96(uint256(shares[i]) * uint256(strategyParams[i].multiplier) / WEIGHTING_DIVISOR); + for (uint256 i = 0; i < strategyParams.length; i++) { + expectedWeight += uint96( + uint256(shares[i]) * uint256(strategyParams[i].multiplier) / WEIGHTING_DIVISOR + ); } assertEq(stakeRegistry.weightOfOperatorForQuorum(quorumNumber, operator), expectedWeight); } @@ -1772,27 +2181,27 @@ contract StakeRegistryUnitTests_weightOfOperatorForQuorum is StakeRegistryUnitTe ) public { // 3 LST Strat multipliers, rETH, stETH, ETH uint96[] memory multipliers = new uint96[](3); - multipliers[0] = uint96(1070136092289993178); - multipliers[1] = uint96(1071364636818145808); - multipliers[2] = uint96(1000000000000000000); - - IStakeRegistry.StrategyParams[] memory strategyParams = new IStakeRegistry.StrategyParams[](3); - for (uint i = 0; i < strategyParams.length; i++) { - shares[i] = uint96(_randUint({rand: bytes32(uint256(shares[i])), min: 0, max: 1e24 })); - IStrategy strat = IStrategy(address(uint160(uint256(keccak256(abi.encodePacked("Voteweighing test", i)))))); - strategyParams[i] = IStakeRegistry.StrategyParams( - strat, - multipliers[i] + multipliers[0] = uint96(1_070_136_092_289_993_178); + multipliers[1] = uint96(1_071_364_636_818_145_808); + multipliers[2] = uint96(1_000_000_000_000_000_000); + + IStakeRegistry.StrategyParams[] memory strategyParams = + new IStakeRegistry.StrategyParams[](3); + for (uint256 i = 0; i < strategyParams.length; i++) { + shares[i] = uint96(_randUint({rand: bytes32(uint256(shares[i])), min: 0, max: 1e24})); + IStrategy strat = IStrategy( + address(uint160(uint256(keccak256(abi.encodePacked("Voteweighing test", i))))) ); + strategyParams[i] = IStakeRegistry.StrategyParams(strat, multipliers[i]); } // create a valid quorum cheats.prank(address(registryCoordinator)); uint8 quorumNumber = nextQuorum; - stakeRegistry.initializeQuorum(quorumNumber, 0 /* minimumStake */, strategyParams); + stakeRegistry.initializeQuorum(quorumNumber, 0, /* minimumStake */ strategyParams); // set the operator shares - for (uint i = 0; i < strategyParams.length; i++) { + for (uint256 i = 0; i < strategyParams.length; i++) { delegationMock.setOperatorShares(operator, strategyParams[i].strategy, shares[i]); } @@ -1802,11 +2211,12 @@ contract StakeRegistryUnitTests_weightOfOperatorForQuorum is StakeRegistryUnitTe cheats.prank(address(registryCoordinator)); stakeRegistry.registerOperator(operator, defaultOperatorId, quorumNumbers); - // assert weight of the operator uint96 expectedWeight = 0; - for (uint i = 0; i < strategyParams.length; i++) { - expectedWeight += uint96(uint256(shares[i]) * uint256(strategyParams[i].multiplier) / WEIGHTING_DIVISOR); + for (uint256 i = 0; i < strategyParams.length; i++) { + expectedWeight += uint96( + uint256(shares[i]) * uint256(strategyParams[i].multiplier) / WEIGHTING_DIVISOR + ); } assertEq(stakeRegistry.weightOfOperatorForQuorum(quorumNumber, operator), expectedWeight); } diff --git a/test/utils/MockAVSDeployer.sol b/test/utils/MockAVSDeployer.sol index 23875760..6db2781f 100644 --- a/test/utils/MockAVSDeployer.sol +++ b/test/utils/MockAVSDeployer.sol @@ -24,6 +24,7 @@ import {IStakeRegistry} from "../../src/interfaces/IStakeRegistry.sol"; import {IIndexRegistry} from "../../src/interfaces/IIndexRegistry.sol"; import {IRegistryCoordinator} from "../../src/interfaces/IRegistryCoordinator.sol"; import {IServiceManager} from "../../src/interfaces/IServiceManager.sol"; +import {SocketRegistry} from "../../src/SocketRegistry.sol"; import {StrategyManagerMock} from "eigenlayer-contracts/src/test/mocks/StrategyManagerMock.sol"; import {EigenPodManagerMock} from "eigenlayer-contracts/src/test/mocks/EigenPodManagerMock.sol"; @@ -61,6 +62,7 @@ contract MockAVSDeployer is Test { StakeRegistryHarness public stakeRegistryImplementation; IBLSApkRegistry public blsApkRegistryImplementation; IIndexRegistry public indexRegistryImplementation; + SocketRegistry public socketRegistryImplementation; ServiceManagerMock public serviceManagerImplementation; OperatorStateRetriever public operatorStateRetriever; @@ -69,6 +71,7 @@ contract MockAVSDeployer is Test { BLSApkRegistryHarness public blsApkRegistry; IIndexRegistry public indexRegistry; ServiceManagerMock public serviceManager; + SocketRegistry public socketRegistry; StrategyManagerMock public strategyManagerMock; DelegationMock public delegationMock; @@ -147,7 +150,7 @@ contract MockAVSDeployer is Test { delegationMock = new DelegationMock(); avsDirectoryMock = new AVSDirectoryMock(); - eigenPodManagerMock = new EigenPodManagerMock(); + eigenPodManagerMock = new EigenPodManagerMock(pauserRegistry); strategyManagerMock = new StrategyManagerMock(); slasherImplementation = new Slasher(strategyManagerMock, delegationMock); slasher = Slasher( @@ -198,6 +201,12 @@ contract MockAVSDeployer is Test { ) ); + socketRegistry = SocketRegistry( + address( + new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "") + ) + ); + indexRegistry = IndexRegistry( address( new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "") @@ -228,6 +237,13 @@ contract MockAVSDeployer is Test { address(stakeRegistryImplementation) ); + socketRegistryImplementation = new SocketRegistry(registryCoordinator); + + proxyAdmin.upgrade( + TransparentUpgradeableProxy(payable(address(socketRegistry))), + address(socketRegistryImplementation) + ); + blsApkRegistryImplementation = new BLSApkRegistryHarness(registryCoordinator); proxyAdmin.upgrade( @@ -279,7 +295,7 @@ contract MockAVSDeployer is Test { } registryCoordinatorImplementation = new RegistryCoordinatorHarness( - serviceManager, stakeRegistry, blsApkRegistry, indexRegistry + serviceManager, stakeRegistry, blsApkRegistry, indexRegistry, socketRegistry ); { delete operatorSetParams;