diff --git a/.gitmodules b/.gitmodules index 961c5645..43ea590c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,9 +5,6 @@ path = lib/eigenlayer-contracts url = https://github.com/Layr-labs/eigenlayer-contracts branch = dev -[submodule "lib/ds-test"] - path = lib/ds-test - url = https://github.com/dapphub/ds-test [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts url = https://github.com/Openzeppelin/openzeppelin-contracts diff --git a/audits/Dedaub - Middleware Audit - Final - Feb'24.pdf b/audits/M2 Mainnet - Dedaub - Feb 2024.pdf similarity index 100% rename from audits/Dedaub - Middleware Audit - Final - Feb'24.pdf rename to audits/M2 Mainnet - Dedaub - Feb 2024.pdf diff --git a/audits/Rewards v2 - SigmaPrime - Dec 2024.pdf b/audits/Rewards v2 - SigmaPrime - Dec 2024.pdf new file mode 100644 index 00000000..65d4c5d5 Binary files /dev/null and b/audits/Rewards v2 - SigmaPrime - Dec 2024.pdf differ diff --git a/docs/README.md b/docs/README.md index 62e9dd91..676bf182 100644 --- a/docs/README.md +++ b/docs/README.md @@ -16,7 +16,7 @@ EigenLayer AVSs ("actively validated services") are protocols that make use of E **Currently, each AVS needs to implement one thing on-chain:** registration/deregistration conditions that define how an Operator registers for/deregisters from the AVS. This repo provides building blocks to support these functions. *Eventually,* the core contracts and this repo will be extended to cover other conditions, including: -* payment conditions that define how an Operator is paid for the services it provides +* reward conditions that define how an Operator is rewarded for the services it provides * slashing conditions that define "malicious behavior" in the context of the AVS, and the punishments for this behavior *... however, the design for these conditions is still in progress.* @@ -102,7 +102,7 @@ The main thing that links an AVS to the EigenLayer core contracts is that when E These methods ensure that the Operator registering with the AVS is also registered as an Operator in EigenLayer core. In this repo, these methods are called by the `ServiceManagerBase`. -Eventually, Operator slashing and payment for services will be part of the middleware/core relationship, but these features aren't implemented yet and their design is a work in progress. +Eventually, Operator slashing and rewards for services will be part of the middleware/core relationship, but these features aren't implemented yet and their design is a work in progress. ### System Components diff --git a/docs/ServiceManagerBase.md b/docs/ServiceManagerBase.md index 781b8e03..dc1f1752 100644 --- a/docs/ServiceManagerBase.md +++ b/docs/ServiceManagerBase.md @@ -10,7 +10,7 @@ The `ServiceManagerBase` represents the AVS's address relative to EigenLayer core. When registering or deregistering an operator from an AVS, the AVS's `ServiceManagerBase` communicates this change to the core contracts, allowing the core contracts to maintain an up-to-date view on operator registration status with various AVSs. *As of M2*: -* Currently, this contract is used by the `AVSDirectory` to keep track of operator registration and deregistration. Eventually, this relationship will be expanded to allow operators to opt in to slashing and payments for services. +* Currently, this contract is used by the `AVSDirectory` to keep track of operator registration and deregistration. Eventually, this relationship will be expanded to allow operators to opt in to slashing and rewards for services. --- diff --git a/docs/experimental/AVS-Guide.md b/docs/experimental/AVS-Guide.md index 46dc9a64..b0dec05e 100644 --- a/docs/experimental/AVS-Guide.md +++ b/docs/experimental/AVS-Guide.md @@ -7,7 +7,7 @@ This document aims to describe and summarize how actively validated services (AV - enabling operators to continuously update their commitments to middlewares, and - enabling AVS to freeze operators for the purpose of slashing (the corresponding unfreeze actions are determined by the veto committee). -We are currently in the process of implementing the API for payment flow from AVSs to operators in EigenLayer. Details of this API will be added to this document in the near future. +We are currently in the process of implementing the API for rewards flow from AVSs to operators in EigenLayer. Details of this API will be added to this document in the near future. The following figure summarizes scope of this document: ![Doc Outline](../images/middleware_outline_doc.png) 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/ds-test b/lib/ds-test deleted file mode 160000 index e282159d..00000000 --- a/lib/ds-test +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e282159d5170298eb2455a6c05280ab5a73a4ef0 diff --git a/lib/eigenlayer-contracts b/lib/eigenlayer-contracts index 426f461c..ac57bc1b 160000 --- a/lib/eigenlayer-contracts +++ b/lib/eigenlayer-contracts @@ -1 +1 @@ -Subproject commit 426f461c59b4f0e16f8becdffd747075edcaded8 +Subproject commit ac57bc1b28c83d9d7143c0da19167c148c3596a3 diff --git a/lib/forge-std b/lib/forge-std index f73c73d2..bb4ceea9 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit f73c73d2018eb6a111f35e4dae7b4f27401e9421 +Subproject commit bb4ceea94d6f10eeb5b41dc2391c6c8bf8e734ef 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/BLSSignatureChecker.sol b/src/BLSSignatureChecker.sol index c79a225a..5392289c 100644 --- a/src/BLSSignatureChecker.sol +++ b/src/BLSSignatureChecker.sol @@ -17,11 +17,11 @@ import {BN254} from "./libraries/BN254.sol"; */ contract BLSSignatureChecker is IBLSSignatureChecker { using BN254 for BN254.G1Point; - + // CONSTANTS & IMMUTABLES // gas cost of multiplying 2 pairings - uint256 internal constant PAIRING_EQUALITY_CHECK_GAS = 120000; + uint256 internal constant PAIRING_EQUALITY_CHECK_GAS = 120_000; IRegistryCoordinator public immutable registryCoordinator; IStakeRegistry public immutable stakeRegistry; @@ -31,7 +31,10 @@ contract BLSSignatureChecker is IBLSSignatureChecker { bool public staleStakesForbidden; modifier onlyCoordinatorOwner() { - require(msg.sender == registryCoordinator.owner(), "BLSSignatureChecker.onlyCoordinatorOwner: caller is not the owner of the registryCoordinator"); + require( + msg.sender == registryCoordinator.owner(), + "BLSSignatureChecker.onlyCoordinatorOwner: caller is not the owner of the registryCoordinator" + ); _; } @@ -40,18 +43,16 @@ contract BLSSignatureChecker is IBLSSignatureChecker { stakeRegistry = _registryCoordinator.stakeRegistry(); blsApkRegistry = _registryCoordinator.blsApkRegistry(); delegation = stakeRegistry.delegation(); - - staleStakesForbidden = true; } /** + * /** * RegistryCoordinator owner can either enforce or not that operator stakes are staler * than the delegation.minWithdrawalDelayBlocks() window. * @param value to toggle staleStakesForbidden */ function setStaleStakesForbidden(bool value) external onlyCoordinatorOwner { - staleStakesForbidden = value; - emit StaleStakesForbiddenUpdate(value); + _setStaleStakesForbidden(value); } struct NonSignerInfo { @@ -67,15 +68,15 @@ contract BLSSignatureChecker is IBLSSignatureChecker { * The thesis of this procedure entails: * - getting the aggregated pubkey of all registered nodes at the time of pre-commit by the * disperser (represented by apk in the parameters), - * - subtracting the pubkeys of all the signers not in the quorum (nonSignerPubkeys) and storing + * - subtracting the pubkeys of all the signers not in the quorum (nonSignerPubkeys) and storing * the output in apk to get aggregated pubkey of all operators that are part of quorum. * - use this aggregated pubkey to verify the aggregated signature under BLS scheme. - * + * * @dev Before signature verification, the function verifies operator stake information. This includes ensuring that the provided `referenceBlockNumber` * is correct, i.e., ensure that the stake returned from the specified block number is recent enough and that the stake is either the most recent update * for the total stake (of the operator) or latest before the referenceBlockNumber. * @param msgHash is the hash being signed - * @dev NOTE: Be careful to ensure `msgHash` is collision-resistant! This method does not hash + * @dev NOTE: Be careful to ensure `msgHash` is collision-resistant! This method does not hash * `msgHash` in any way, so if an attacker is able to pass in an arbitrary value, they may be able * to tamper with signature verification. * @param quorumNumbers is the bytes array of quorum numbers that are being signed for @@ -85,34 +86,34 @@ contract BLSSignatureChecker is IBLSSignatureChecker { * @return signatoryRecordHash is the hash of the signatory record, which is used for fraud proofs */ function checkSignatures( - bytes32 msgHash, + bytes32 msgHash, bytes calldata quorumNumbers, - uint32 referenceBlockNumber, + uint32 referenceBlockNumber, NonSignerStakesAndSignature memory params - ) - public - view - returns ( - QuorumStakeTotals memory, - bytes32 - ) - { - require(quorumNumbers.length != 0, "BLSSignatureChecker.checkSignatures: empty quorum input"); + ) public view returns (QuorumStakeTotals memory, bytes32) { + require( + quorumNumbers.length != 0, + "BLSSignatureChecker.checkSignatures: empty quorum input" + ); require( (quorumNumbers.length == params.quorumApks.length) && - (quorumNumbers.length == params.quorumApkIndices.length) && - (quorumNumbers.length == params.totalStakeIndices.length) && - (quorumNumbers.length == params.nonSignerStakeIndices.length), + (quorumNumbers.length == params.quorumApkIndices.length) && + (quorumNumbers.length == params.totalStakeIndices.length) && + (quorumNumbers.length == params.nonSignerStakeIndices.length), "BLSSignatureChecker.checkSignatures: input quorum length mismatch" ); require( - params.nonSignerPubkeys.length == params.nonSignerQuorumBitmapIndices.length, + params.nonSignerPubkeys.length == + params.nonSignerQuorumBitmapIndices.length, "BLSSignatureChecker.checkSignatures: input nonsigner length mismatch" ); - require(referenceBlockNumber < uint32(block.number), "BLSSignatureChecker.checkSignatures: invalid reference block"); + require( + referenceBlockNumber < uint32(block.number), + "BLSSignatureChecker.checkSignatures: invalid reference block" + ); // This method needs to calculate the aggregate pubkey for all signing operators across // all signing quorums. To do that, we can query the aggregate pubkey for each quorum @@ -130,29 +131,37 @@ contract BLSSignatureChecker is IBLSSignatureChecker { stakeTotals.signedStakeForQuorum = new uint96[](quorumNumbers.length); NonSignerInfo memory nonSigners; - nonSigners.quorumBitmaps = new uint256[](params.nonSignerPubkeys.length); + nonSigners.quorumBitmaps = new uint256[]( + params.nonSignerPubkeys.length + ); nonSigners.pubkeyHashes = new bytes32[](params.nonSignerPubkeys.length); { // Get a bitmap of the quorums signing the message, and validate that // quorumNumbers contains only unique, valid quorum numbers - uint256 signingQuorumBitmap = BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, registryCoordinator.quorumCount()); + uint256 signingQuorumBitmap = BitmapUtils.orderedBytesArrayToBitmap( + quorumNumbers, + registryCoordinator.quorumCount() + ); for (uint256 j = 0; j < params.nonSignerPubkeys.length; j++) { // The nonsigner's pubkey hash doubles as their operatorId // The check below validates that these operatorIds are sorted (and therefore // free of duplicates) - nonSigners.pubkeyHashes[j] = params.nonSignerPubkeys[j].hashG1Point(); + nonSigners.pubkeyHashes[j] = params + .nonSignerPubkeys[j] + .hashG1Point(); if (j != 0) { require( - uint256(nonSigners.pubkeyHashes[j]) > uint256(nonSigners.pubkeyHashes[j - 1]), + uint256(nonSigners.pubkeyHashes[j]) > + uint256(nonSigners.pubkeyHashes[j - 1]), "BLSSignatureChecker.checkSignatures: nonSignerPubkeys not sorted" ); } // Get the quorums the nonsigner was registered for at referenceBlockNumber - nonSigners.quorumBitmaps[j] = - registryCoordinator.getQuorumBitmapAtBlockNumberByIndex({ + nonSigners.quorumBitmaps[j] = registryCoordinator + .getQuorumBitmapAtBlockNumberByIndex({ operatorId: nonSigners.pubkeyHashes[j], blockNumber: referenceBlockNumber, index: params.nonSignerQuorumBitmapIndices[j] @@ -162,10 +171,11 @@ contract BLSSignatureChecker is IBLSSignatureChecker { // of quorums they have in common with the signing quorums, because their // public key will be a part of each signing quorum's aggregate pubkey apk = apk.plus( - params.nonSignerPubkeys[j] - .scalar_mul_tiny( - BitmapUtils.countNumOnes(nonSigners.quorumBitmaps[j] & signingQuorumBitmap) + params.nonSignerPubkeys[j].scalar_mul_tiny( + BitmapUtils.countNumOnes( + nonSigners.quorumBitmaps[j] & signingQuorumBitmap ) + ) ); } } @@ -183,14 +193,20 @@ contract BLSSignatureChecker is IBLSSignatureChecker { */ { bool _staleStakesForbidden = staleStakesForbidden; - uint256 withdrawalDelayBlocks = _staleStakesForbidden ? delegation.minWithdrawalDelayBlocks() : 0; + uint256 withdrawalDelayBlocks = _staleStakesForbidden + ? delegation.minWithdrawalDelayBlocks() + : 0; for (uint256 i = 0; i < quorumNumbers.length; i++) { // If we're disallowing stale stake updates, check that each quorum's last update block // is within withdrawalDelayBlocks if (_staleStakesForbidden) { require( - registryCoordinator.quorumUpdateBlockNumber(uint8(quorumNumbers[i])) + withdrawalDelayBlocks > referenceBlockNumber, + registryCoordinator.quorumUpdateBlockNumber( + uint8(quorumNumbers[i]) + ) + + withdrawalDelayBlocks > + referenceBlockNumber, "BLSSignatureChecker.checkSignatures: StakeRegistry updates must be within withdrawalDelayBlocks window" ); } @@ -198,7 +214,7 @@ contract BLSSignatureChecker is IBLSSignatureChecker { // Validate params.quorumApks is correct for this quorum at the referenceBlockNumber, // then add it to the total apk require( - bytes24(params.quorumApks[i].hashG1Point()) == + bytes24(params.quorumApks[i].hashG1Point()) == blsApkRegistry.getApkHashAtBlockNumberAndIndex({ quorumNumber: uint8(quorumNumbers[i]), blockNumber: referenceBlockNumber, @@ -209,28 +225,36 @@ contract BLSSignatureChecker is IBLSSignatureChecker { apk = apk.plus(params.quorumApks[i]); // Get the total and starting signed stake for the quorum at referenceBlockNumber - stakeTotals.totalStakeForQuorum[i] = - stakeRegistry.getTotalStakeAtBlockNumberFromIndex({ + stakeTotals.totalStakeForQuorum[i] = stakeRegistry + .getTotalStakeAtBlockNumberFromIndex({ quorumNumber: uint8(quorumNumbers[i]), blockNumber: referenceBlockNumber, index: params.totalStakeIndices[i] }); - stakeTotals.signedStakeForQuorum[i] = stakeTotals.totalStakeForQuorum[i]; + stakeTotals.signedStakeForQuorum[i] = stakeTotals + .totalStakeForQuorum[i]; // Keep track of the nonSigners index in the quorum uint256 nonSignerForQuorumIndex = 0; - + // loop through all nonSigners, checking that they are a part of the quorum via their quorumBitmap // if so, load their stake at referenceBlockNumber and subtract it from running stake signed for (uint256 j = 0; j < params.nonSignerPubkeys.length; j++) { // if the nonSigner is a part of the quorum, subtract their stake from the running total - if (BitmapUtils.isSet(nonSigners.quorumBitmaps[j], uint8(quorumNumbers[i]))) { - stakeTotals.signedStakeForQuorum[i] -= - stakeRegistry.getStakeAtBlockNumberAndIndex({ + if ( + BitmapUtils.isSet( + nonSigners.quorumBitmaps[j], + uint8(quorumNumbers[i]) + ) + ) { + stakeTotals.signedStakeForQuorum[i] -= stakeRegistry + .getStakeAtBlockNumberAndIndex({ quorumNumber: uint8(quorumNumbers[i]), blockNumber: referenceBlockNumber, operatorId: nonSigners.pubkeyHashes[j], - index: params.nonSignerStakeIndices[i][nonSignerForQuorumIndex] + index: params.nonSignerStakeIndices[i][ + nonSignerForQuorumIndex + ] }); unchecked { ++nonSignerForQuorumIndex; @@ -241,17 +265,28 @@ contract BLSSignatureChecker is IBLSSignatureChecker { } { // verify the signature - (bool pairingSuccessful, bool signatureIsValid) = trySignatureAndApkVerification( - msgHash, - apk, - params.apkG2, - params.sigma + ( + bool pairingSuccessful, + bool signatureIsValid + ) = trySignatureAndApkVerification( + msgHash, + apk, + params.apkG2, + params.sigma + ); + require( + pairingSuccessful, + "BLSSignatureChecker.checkSignatures: pairing precompile call failed" + ); + require( + signatureIsValid, + "BLSSignatureChecker.checkSignatures: signature is invalid" ); - require(pairingSuccessful, "BLSSignatureChecker.checkSignatures: pairing precompile call failed"); - require(signatureIsValid, "BLSSignatureChecker.checkSignatures: signature is invalid"); } // set signatoryRecordHash variable used for fraudproofs - bytes32 signatoryRecordHash = keccak256(abi.encodePacked(referenceBlockNumber, nonSigners.pubkeyHashes)); + bytes32 signatoryRecordHash = keccak256( + abi.encodePacked(referenceBlockNumber, nonSigners.pubkeyHashes) + ); // return the total stakes that signed for each quorum, and a hash of the information required to prove the exact signers and stake return (stakeTotals, signatoryRecordHash); @@ -271,17 +306,36 @@ contract BLSSignatureChecker is IBLSSignatureChecker { BN254.G1Point memory apk, BN254.G2Point memory apkG2, BN254.G1Point memory sigma - ) public view returns(bool pairingSuccessful, bool siganatureIsValid) { + ) public view returns (bool pairingSuccessful, bool siganatureIsValid) { // gamma = keccak256(abi.encodePacked(msgHash, apk, apkG2, sigma)) - uint256 gamma = uint256(keccak256(abi.encodePacked(msgHash, apk.X, apk.Y, apkG2.X[0], apkG2.X[1], apkG2.Y[0], apkG2.Y[1], sigma.X, sigma.Y))) % BN254.FR_MODULUS; + uint256 gamma = uint256( + keccak256( + abi.encodePacked( + msgHash, + apk.X, + apk.Y, + apkG2.X[0], + apkG2.X[1], + apkG2.Y[0], + apkG2.Y[1], + sigma.X, + sigma.Y + ) + ) + ) % BN254.FR_MODULUS; // verify the signature (pairingSuccessful, siganatureIsValid) = BN254.safePairing( - sigma.plus(apk.scalar_mul(gamma)), - BN254.negGeneratorG2(), - BN254.hashToG1(msgHash).plus(BN254.generatorG1().scalar_mul(gamma)), - apkG2, - PAIRING_EQUALITY_CHECK_GAS - ); + sigma.plus(apk.scalar_mul(gamma)), + BN254.negGeneratorG2(), + BN254.hashToG1(msgHash).plus(BN254.generatorG1().scalar_mul(gamma)), + apkG2, + PAIRING_EQUALITY_CHECK_GAS + ); + } + + function _setStaleStakesForbidden(bool value) internal { + staleStakesForbidden = value; + emit StaleStakesForbiddenUpdate(value); } // storage gap for upgradeability diff --git a/src/EjectionManager.sol b/src/EjectionManager.sol new file mode 100644 index 00000000..ca81676b --- /dev/null +++ b/src/EjectionManager.sol @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; +import {IEjectionManager} from "./interfaces/IEjectionManager.sol"; +import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol"; +import {IStakeRegistry} from "./interfaces/IStakeRegistry.sol"; + +/** + * @title Used for automated ejection of operators from the RegistryCoordinator under a ratelimit + * @author Layr Labs, Inc. + */ +contract EjectionManager is IEjectionManager, OwnableUpgradeable{ + + /// @notice The basis point denominator for the ejectable stake percent + uint16 internal constant BIPS_DENOMINATOR = 10000; + + /// @notice the RegistryCoordinator contract that is the entry point for ejection + IRegistryCoordinator public immutable registryCoordinator; + /// @notice the StakeRegistry contract that keeps track of quorum stake + IStakeRegistry public immutable stakeRegistry; + + /// @notice Addresses permissioned to eject operators under a ratelimit + mapping(address => bool) public isEjector; + + /// @notice Keeps track of the total stake ejected for a quorum + mapping(uint8 => StakeEjection[]) public stakeEjectedForQuorum; + /// @notice Ratelimit parameters for each quorum + mapping(uint8 => QuorumEjectionParams) public quorumEjectionParams; + + constructor( + IRegistryCoordinator _registryCoordinator, + IStakeRegistry _stakeRegistry + ) { + registryCoordinator = _registryCoordinator; + stakeRegistry = _stakeRegistry; + + _disableInitializers(); + } + + /** + * @param _owner will hold the owner role + * @param _ejectors will hold the ejector role + * @param _quorumEjectionParams are the ratelimit parameters for the quorum at each index + */ + function initialize( + address _owner, + address[] memory _ejectors, + QuorumEjectionParams[] memory _quorumEjectionParams + ) external initializer { + _transferOwnership(_owner); + for(uint8 i = 0; i < _ejectors.length; i++) { + _setEjector(_ejectors[i], true); + } + for(uint8 i = 0; i < _quorumEjectionParams.length; i++) { + _setQuorumEjectionParams(i, _quorumEjectionParams[i]); + } + } + + /** + * @notice Ejects operators from the AVSs RegistryCoordinator under a ratelimit + * @param _operatorIds The ids of the operators 'j' to eject for each quorum 'i' + * @dev This function will eject as many operators as possible prioritizing operators at the lower index + * @dev The owner can eject operators without recording of stake ejection + */ + function ejectOperators(bytes32[][] memory _operatorIds) external { + require(isEjector[msg.sender] || msg.sender == owner(), "Ejector: Only owner or ejector can eject"); + + for(uint i = 0; i < _operatorIds.length; ++i) { + uint8 quorumNumber = uint8(i); + + uint256 amountEjectable = amountEjectableForQuorum(quorumNumber); + uint256 stakeForEjection; + uint32 ejectedOperators; + + bool ratelimitHit; + 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); + } + } + + //record the stake ejected if ejector and ratelimit enforced + if(isEjector[msg.sender] && stakeForEjection > 0){ + stakeEjectedForQuorum[quorumNumber].push(StakeEjection({ + timestamp: block.timestamp, + stakeEjected: stakeForEjection + })); + } + + emit QuorumEjection(ejectedOperators, ratelimitHit); + } + } + + /** + * @notice Sets the ratelimit parameters for a quorum + * @param _quorumNumber The quorum number to set the ratelimit parameters for + * @param _quorumEjectionParams The quorum ratelimit parameters to set for the given quorum + */ + function setQuorumEjectionParams(uint8 _quorumNumber, QuorumEjectionParams memory _quorumEjectionParams) external onlyOwner() { + _setQuorumEjectionParams(_quorumNumber, _quorumEjectionParams); + } + + /** + * @notice Sets the address permissioned to eject operators under a ratelimit + * @param _ejector The address to permission + * @param _status The status to set for the given address + */ + function setEjector(address _ejector, bool _status) external onlyOwner() { + _setEjector(_ejector, _status); + } + + ///@dev internal function to set the quorum ejection params + function _setQuorumEjectionParams(uint8 _quorumNumber, QuorumEjectionParams memory _quorumEjectionParams) internal { + quorumEjectionParams[_quorumNumber] = _quorumEjectionParams; + emit QuorumEjectionParamsSet(_quorumNumber, _quorumEjectionParams.rateLimitWindow, _quorumEjectionParams.ejectableStakePercent); + } + + ///@dev internal function to set the ejector + function _setEjector(address _ejector, bool _status) internal { + isEjector[_ejector] = _status; + emit EjectorUpdated(_ejector, _status); + } + + /** + * @notice Returns the amount of stake that can be ejected for a quorum at the current block.timestamp + * @param _quorumNumber The quorum number to view ejectable stake for + */ + function amountEjectableForQuorum(uint8 _quorumNumber) public view returns (uint256) { + uint256 cutoffTime = block.timestamp - quorumEjectionParams[_quorumNumber].rateLimitWindow; + uint256 totalEjectable = uint256(quorumEjectionParams[_quorumNumber].ejectableStakePercent) * uint256(stakeRegistry.getCurrentTotalStake(_quorumNumber)) / uint256(BIPS_DENOMINATOR); + uint256 totalEjected; + uint256 i; + if (stakeEjectedForQuorum[_quorumNumber].length == 0) { + return totalEjectable; + } + i = stakeEjectedForQuorum[_quorumNumber].length - 1; + + while(stakeEjectedForQuorum[_quorumNumber][i].timestamp > cutoffTime) { + totalEjected += stakeEjectedForQuorum[_quorumNumber][i].stakeEjected; + if(i == 0){ + break; + } else { + --i; + } + } + + if(totalEjected >= totalEjectable){ + return 0; + } + 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/OperatorStateRetriever.sol b/src/OperatorStateRetriever.sol index ffdc65fe..f0547a2d 100644 --- a/src/OperatorStateRetriever.sol +++ b/src/OperatorStateRetriever.sol @@ -178,4 +178,37 @@ contract OperatorStateRetriever { } return quorumBitmaps; } + + /** + * @notice This function returns the operatorIds for each of the operators in the operators array + * @param registryCoordinator is the AVS registry coordinator to fetch the operator information from + * @param operators is the array of operator address to get corresponding operatorIds for + * @dev if an operator is not registered, the operatorId will be 0 + */ + function getBatchOperatorId( + IRegistryCoordinator registryCoordinator, + address[] memory operators + ) external view returns (bytes32[] memory operatorIds) { + operatorIds = new bytes32[](operators.length); + for (uint256 i = 0; i < operators.length; ++i) { + operatorIds[i] = registryCoordinator.getOperatorId(operators[i]); + } + } + + /** + * @notice This function returns the operator addresses for each of the operators in the operatorIds array + * @param registryCoordinator is the AVS registry coordinator to fetch the operator information from + * @param operators is the array of operatorIds to get corresponding operator addresses for + * @dev if an operator is not registered, the operator address will be 0 + */ + function getBatchOperatorFromId( + IRegistryCoordinator registryCoordinator, + bytes32[] memory operatorIds + ) external view returns (address[] memory operators) { + operators = new address[](operatorIds.length); + for (uint256 i = 0; i < operatorIds.length; ++i) { + operators[i] = registryCoordinator.getOperatorFromId(operatorIds[i]); + } + } + } diff --git a/src/RegistryCoordinator.sol b/src/RegistryCoordinator.sol index 19367892..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); } /******************************************************************************* @@ -359,15 +356,28 @@ contract RegistryCoordinator is * @notice Forcibly deregisters an operator from one or more quorums * @param operator the operator to eject * @param quorumNumbers the quorum numbers to eject the operator from + * @dev possible race condition if prior to being ejected for a set of quorums the operator self deregisters from a subset */ function ejectOperator( address operator, bytes calldata quorumNumbers ) external onlyEjector { - _deregisterOperator({ - operator: operator, - quorumNumbers: quorumNumbers - }); + lastEjectionTimestamp[operator] = block.timestamp; + + OperatorInfo storage operatorInfo = _operatorInfo[operator]; + bytes32 operatorId = operatorInfo.operatorId; + uint192 quorumsToRemove = uint192(BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers, quorumCount)); + uint192 currentBitmap = _currentOperatorBitmap(operatorId); + if( + operatorInfo.status == OperatorStatus.REGISTERED && + !quorumsToRemove.isEmpty() && + quorumsToRemove.isSubsetOf(currentBitmap) + ){ + _deregisterOperator({ + operator: operator, + quorumNumbers: quorumNumbers + }); + } } /******************************************************************************* @@ -423,6 +433,16 @@ contract RegistryCoordinator is _setEjector(_ejector); } + /** + * @notice Sets the ejection cooldown, which is the time an operator must wait in + * seconds afer ejection before registering for any quorum + * @param _ejectionCooldown the new ejection cooldown in seconds + * @dev only callable by the owner + */ + function setEjectionCooldown(uint256 _ejectionCooldown) external onlyOwner { + ejectionCooldown = _ejectionCooldown; + } + /******************************************************************************* INTERNAL FUNCTIONS *******************************************************************************/ @@ -453,10 +473,13 @@ 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, "RegCoord._registerOperator: operator cannot reregister yet"); + /** * Update operator's bitmap, socket, and status. Only update operatorInfo if needed: * if we're `REGISTERED`, the operatorId and status are already correct. @@ -466,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) { @@ -479,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); } @@ -491,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 @@ -538,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" ); } @@ -565,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: @@ -576,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 @@ -649,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; @@ -678,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 @@ -760,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" ); } @@ -779,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 *******************************************************************************/ @@ -844,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 e8c42850..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 @@ -64,19 +67,26 @@ abstract contract RegistryCoordinatorStorage is IRegistryCoordinator { /// @notice the address of the entity allowed to eject operators from the AVS address public ejector; + /// @notice the last timestamp an operator was ejected + mapping(address => uint256) public lastEjectionTimestamp; + /// @notice the delay in seconds before an operator can reregister after being ejected + uint256 public ejectionCooldown; + constructor( 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 // slither-disable-next-line shadowing-state - uint256[41] private __GAP; + uint256[39] private __GAP; } diff --git a/src/ServiceManagerBase.sol b/src/ServiceManagerBase.sol index 71d262d1..65a5a152 100644 --- a/src/ServiceManagerBase.sol +++ b/src/ServiceManagerBase.sol @@ -1,28 +1,28 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.12; -import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; - -import {BitmapUtils} from "./libraries/BitmapUtils.sol"; +import {Initializable} from "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol"; import {IAVSDirectory} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; +import {IRewardsCoordinator} from "eigenlayer-contracts/src/contracts/interfaces/IRewardsCoordinator.sol"; +import {ServiceManagerBaseStorage} from "./ServiceManagerBaseStorage.sol"; import {IServiceManager} from "./interfaces/IServiceManager.sol"; import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol"; import {IStakeRegistry} from "./interfaces/IStakeRegistry.sol"; +import {BitmapUtils} from "./libraries/BitmapUtils.sol"; /** * @title Minimal implementation of a ServiceManager-type contract. * This contract can be inherited from or simply used as a point-of-reference. * @author Layr Labs, Inc. */ -abstract contract ServiceManagerBase is IServiceManager, OwnableUpgradeable { +abstract contract ServiceManagerBase is ServiceManagerBaseStorage { + using SafeERC20 for IERC20; using BitmapUtils for *; - IRegistryCoordinator internal immutable _registryCoordinator; - IStakeRegistry internal immutable _stakeRegistry; - IAVSDirectory internal immutable _avsDirectory; - /// @notice when applied to a function, only allows the RegistryCoordinator to call it modifier onlyRegistryCoordinator() { require( @@ -32,20 +32,42 @@ abstract contract ServiceManagerBase is IServiceManager, OwnableUpgradeable { _; } + /// @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 constructor( IAVSDirectory __avsDirectory, + IRewardsCoordinator __rewardsCoordinator, IRegistryCoordinator __registryCoordinator, IStakeRegistry __stakeRegistry - ) { - _avsDirectory = __avsDirectory; - _registryCoordinator = __registryCoordinator; - _stakeRegistry = __stakeRegistry; + ) + ServiceManagerBaseStorage( + __avsDirectory, + __rewardsCoordinator, + __registryCoordinator, + __stakeRegistry + ) + { _disableInitializers(); } - function __ServiceManagerBase_init(address initialOwner) internal virtual onlyInitializing { + function __ServiceManagerBase_init( + address initialOwner, + address _rewardsInitiator + ) internal virtual onlyInitializing { _transferOwnership(initialOwner); + _setRewardsInitiator(_rewardsInitiator); } /** @@ -53,10 +75,109 @@ abstract contract ServiceManagerBase is IServiceManager, OwnableUpgradeable { * @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); } + /** + * @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 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 { + for (uint256 i = 0; i < rewardsSubmissions.length; ++i) { + // transfer token to ServiceManager and approve RewardsCoordinator to transfer again + // in createAVSRewardsSubmission() call + rewardsSubmissions[i].token.safeTransferFrom( + msg.sender, + address(this), + rewardsSubmissions[i].amount + ); + rewardsSubmissions[i].token.safeIncreaseAllowance( + address(_rewardsCoordinator), + rewardsSubmissions[i].amount + ); + } + + _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 createOperatorDirectedAVSRewardsSubmission() call + operatorDirectedRewardsSubmissions[i].token.safeTransferFrom( + msg.sender, + address(this), + totalAmount + ); + operatorDirectedRewardsSubmissions[i].token.safeIncreaseAllowance( + address(_rewardsCoordinator), + totalAmount + ); + } + + _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. @@ -73,34 +194,61 @@ abstract contract ServiceManagerBase is IServiceManager, OwnableUpgradeable { * @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); } + /** + * @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; + } + /** * @notice Returns the list of strategies that the AVS supports for restaking * @dev This function is intended to be called off-chain - * @dev No guarantee is made on uniqueness of each element in the returned array. + * @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) { return new address[](0); } - + uint256 strategyCount; - for(uint256 i = 0; i < quorumCount; i++) { + for (uint256 i = 0; i < quorumCount; i++) { strategyCount += _stakeRegistry.strategyParamsLength(uint8(i)); } address[] memory restakedStrategies = new address[](strategyCount); uint256 index = 0; - for(uint256 i = 0; i < _registryCoordinator.quorumCount(); i++) { - uint256 strategyParamsLength = _stakeRegistry.strategyParamsLength(uint8(i)); + for (uint256 i = 0; i < _registryCoordinator.quorumCount(); 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++; } } @@ -111,44 +259,52 @@ abstract contract ServiceManagerBase is IServiceManager, OwnableUpgradeable { * @notice Returns the list of strategies that the operator has potentially restaked on the AVS * @param operator The address of the operator to get restaked strategies for * @dev This function is intended to be called off-chain - * @dev No guarantee is made on whether the operator has shares for a strategy in a quorum or uniqueness + * @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])); + for (uint256 i = 0; i < operatorRestakedQuorums.length; i++) { + strategyCount += _stakeRegistry.strategyParamsLength( + uint8(operatorRestakedQuorums[i]) + ); } // Get strategies for each quorum in operator bitmap address[] memory restakedStrategies = new address[](strategyCount); uint256 index = 0; - for(uint256 i = 0; i < operatorRestakedQuorums.length; i++) { + 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++; } } - return restakedStrategies; + return restakedStrategies; } /// @notice Returns the EigenLayer AVSDirectory contract. function avsDirectory() external view override returns (address) { return address(_avsDirectory); } - - // storage gap for upgradeability - // slither-disable-next-line shadowing-state - uint256[50] private __GAP; } diff --git a/src/ServiceManagerBaseStorage.sol b/src/ServiceManagerBaseStorage.sol new file mode 100644 index 00000000..e0c1d86a --- /dev/null +++ b/src/ServiceManagerBaseStorage.sol @@ -0,0 +1,53 @@ +// 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"; + +import {IAVSDirectory} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; +import {IRewardsCoordinator} from "eigenlayer-contracts/src/contracts/interfaces/IRewardsCoordinator.sol"; + +/** + * @title Storage variables for the `ServiceManagerBase` contract. + * @author Layr Labs, Inc. + * @notice This storage contract is separate from the logic to simplify the upgrade process. + */ +abstract contract ServiceManagerBaseStorage is IServiceManager, OwnableUpgradeable { + /** + * + * CONSTANTS AND IMMUTABLES + * + */ + IAVSDirectory internal immutable _avsDirectory; + IRewardsCoordinator internal immutable _rewardsCoordinator; + IRegistryCoordinator internal immutable _registryCoordinator; + IStakeRegistry internal immutable _stakeRegistry; + + /** + * + * STATE VARIABLES + * + */ + + /// @notice The address of the entity that can initiate rewards + address public rewardsInitiator; + + /// @notice Sets the (immutable) `_avsDirectory`, `_rewardsCoordinator`, `_registryCoordinator`, and `_stakeRegistry` addresses + constructor( + IAVSDirectory __avsDirectory, + IRewardsCoordinator __rewardsCoordinator, + IRegistryCoordinator __registryCoordinator, + IStakeRegistry __stakeRegistry + ) { + _avsDirectory = __avsDirectory; + _rewardsCoordinator = __rewardsCoordinator; + _registryCoordinator = __registryCoordinator; + _stakeRegistry = __stakeRegistry; + } + + // storage gap for upgradeability + uint256[49] private __GAP; +} diff --git a/src/ServiceManagerRouter.sol b/src/ServiceManagerRouter.sol index 477ed2db..e2259cfb 100644 --- a/src/ServiceManagerRouter.sol +++ b/src/ServiceManagerRouter.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.12; -import {IServiceManager} from "./interfaces/IServiceManager.sol"; +import {IServiceManagerUI} from "./interfaces/IServiceManagerUI.sol"; /** * @title Contract that proxies calls to a ServiceManager contract. @@ -20,7 +20,7 @@ contract ServiceManagerRouter { */ function getRestakeableStrategies(address serviceManager) external view returns (address[] memory) { bytes memory data = abi.encodeWithSelector( - IServiceManager.getRestakeableStrategies.selector + IServiceManagerUI.getRestakeableStrategies.selector ); return _makeCall(serviceManager, data); } @@ -32,7 +32,7 @@ contract ServiceManagerRouter { */ function getOperatorRestakedStrategies(address serviceManager, address operator) external view returns (address[] memory) { bytes memory data = abi.encodeWithSelector( - IServiceManager.getOperatorRestakedStrategies.selector, + IServiceManagerUI.getOperatorRestakedStrategies.selector, operator ); return _makeCall(serviceManager, data); diff --git a/src/SocketRegistry.sol b/src/SocketRegistry.sol new file mode 100644 index 00000000..8ede8dda --- /dev/null +++ b/src/SocketRegistry.sol @@ -0,0 +1,45 @@ +// 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 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/IECDSAStakeRegistryEventsAndErrors.sol b/src/interfaces/IECDSAStakeRegistryEventsAndErrors.sol index 48072f54..445db814 100644 --- a/src/interfaces/IECDSAStakeRegistryEventsAndErrors.sol +++ b/src/interfaces/IECDSAStakeRegistryEventsAndErrors.sol @@ -36,13 +36,20 @@ interface ECDSAStakeRegistryEventsAndErrors { /// @notice Emitted when the weight required to be an operator changes /// @param oldMinimumWeight The previous weight /// @param newMinimumWeight The updated weight - event UpdateMinimumWeight(uint256 oldMinimumWeight, uint256 newMinimumWeight); + event UpdateMinimumWeight( + uint256 oldMinimumWeight, + uint256 newMinimumWeight + ); /// @notice Emitted when the system updates an operator's weight /// @param _operator The address of the operator updated /// @param oldWeight The operator's weight before the update /// @param newWeight The operator's weight after the update - event OperatorWeightUpdated(address indexed _operator, uint256 oldWeight, uint256 newWeight); + event OperatorWeightUpdated( + address indexed _operator, + uint256 oldWeight, + uint256 newWeight + ); /// @notice Emitted when the system updates the total weight /// @param oldTotalWeight The total weight before the update @@ -52,6 +59,17 @@ interface ECDSAStakeRegistryEventsAndErrors { /// @notice Emits when setting a new threshold weight. event ThresholdWeightUpdated(uint256 _thresholdWeight); + /// @notice Emitted when an operator's signing key is updated + /// @param operator The address of the operator whose signing key was updated + /// @param updateBlock The block number at which the signing key was updated + /// @param newSigningKey The operator's signing key after the update + /// @param oldSigningKey The operator's signing key before the update + event SigningKeyUpdate( + address indexed operator, + uint256 indexed updateBlock, + address indexed newSigningKey, + address oldSigningKey + ); /// @notice Indicates when the lengths of the signers array and signatures array do not match. error LengthMismatch(); @@ -64,9 +82,12 @@ interface ECDSAStakeRegistryEventsAndErrors { /// @notice Thrown when the threshold update is greater than BPS error InvalidThreshold(); - /// @notice Thrown when missing operators in an update + /// @notice Thrown when missing operators in an update error MustUpdateAllOperators(); + /// @notice Reference blocks must be for blocks that have already been confirmed + error InvalidReferenceBlock(); + /// @notice Indicates operator weights were out of sync and the signed weight exceed the total error InvalidSignedWeight(); diff --git a/src/interfaces/IEjectionManager.sol b/src/interfaces/IEjectionManager.sol new file mode 100644 index 00000000..6649e75b --- /dev/null +++ b/src/interfaces/IEjectionManager.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +/** + * @title Interface for a contract that ejects operators from an AVSs RegistryCoordinator + * @author Layr Labs, Inc. + */ +interface IEjectionManager { + + /// @notice A quorum's ratelimit parameters + struct QuorumEjectionParams { + uint32 rateLimitWindow; // Time delta to track ejection over + uint16 ejectableStakePercent; // Max stake to be ejectable per time delta + } + + /// @notice A stake ejection event + struct StakeEjection { + uint256 timestamp; // Timestamp of the ejection + uint256 stakeEjected; // Amount of stake ejected at the timestamp + } + + ///@notice Emitted when the ejector address is set + event EjectorUpdated(address ejector, bool status); + ///@notice Emitted when the ratelimit parameters for a quorum are set + event QuorumEjectionParamsSet(uint8 quorumNumber, uint32 rateLimitWindow, uint16 ejectableStakePercent); + ///@notice Emitted when an operator is ejected + event OperatorEjected(bytes32 operatorId, uint8 quorumNumber); + ///@notice Emitted when operators are ejected for a quroum + event QuorumEjection(uint32 ejectedOperators, bool ratelimitHit); + + /** + * @notice Ejects operators from the AVSs registryCoordinator under a ratelimit + * @param _operatorIds The ids of the operators to eject for each quorum + */ + function ejectOperators(bytes32[][] memory _operatorIds) external; + + /** + * @notice Sets the ratelimit parameters for a quorum + * @param _quorumNumber The quorum number to set the ratelimit parameters for + * @param _quorumEjectionParams The quorum ratelimit parameters to set for the given quorum + */ + function setQuorumEjectionParams(uint8 _quorumNumber, QuorumEjectionParams memory _quorumEjectionParams) external; + + /** + * @notice Sets the address permissioned to eject operators under a ratelimit + * @param _ejector The address to permission + */ + function setEjector(address _ejector, bool _status) external; + + /** + * @notice Returns the amount of stake that can be ejected for a quorum at the current block.timestamp + * @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 bda03a88..c8f49166 100644 --- a/src/interfaces/IServiceManager.sol +++ b/src/interfaces/IServiceManager.sol @@ -1,53 +1,56 @@ // SPDX-License-Identifier: BUSL-1.1 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"; +import {IRewardsCoordinator} from "eigenlayer-contracts/src/contracts/interfaces/IRewardsCoordinator.sol"; +import {IServiceManagerUI} from "./IServiceManagerUI.sol"; /** * @title Minimal interface for a ServiceManager-type contract that forms the single point for an AVS to push updates to EigenLayer * @author Layr Labs, Inc. */ -interface IServiceManager { +interface IServiceManager is IServiceManagerUI { /** - * @notice Updates the metadata URI for the AVS - * @param _metadataURI is the metadata URI for the AVS + * @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 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 updateAVSMetadataURI(string memory _metadataURI) external; - - /** - * @notice Forwards a call to EigenLayer's DelegationManager 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. - */ - function registerOperatorToAVS( - address operator, - ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature + function createAVSRewardsSubmission( + IRewardsCoordinator.RewardsSubmission[] calldata rewardsSubmissions ) external; /** - * @notice Forwards a call to EigenLayer's DelegationManager contract to confirm operator deregistration from the AVS - * @param operator The address of the operator to deregister. + * @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 deregisterOperatorFromAVS(address operator) external; - - /** - * @notice Returns the list of strategies that the operator has potentially restaked on the AVS - * @param operator The address of the operator to get restaked strategies for - * @dev This function is intended to be called off-chain - * @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 createOperatorDirectedAVSRewardsSubmission( + IRewardsCoordinator.OperatorDirectedRewardsSubmission[] + calldata operatorDirectedRewardsSubmissions + ) external; /** - * @notice Returns the list of strategies that the AVS supports for restaking - * @dev This function is intended to be called off-chain - * @dev No guarantee is made on uniqueness of each element in the returned array. - * The off-chain service should do that validation separately + * @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 getRestakeableStrategies() external view returns (address[] memory); + function setClaimerFor(address claimer) external; - /// @notice Returns the EigenLayer AVSDirectory contract. - function avsDirectory() external view returns (address); + // EVENTS + event RewardsInitiatorUpdated( + address prevRewardsInitiator, + address newRewardsInitiator + ); } diff --git a/src/interfaces/IServiceManagerUI.sol b/src/interfaces/IServiceManagerUI.sol new file mode 100644 index 00000000..92cdce9c --- /dev/null +++ b/src/interfaces/IServiceManagerUI.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity >=0.5.0; + +import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol"; + +/** + * @title Minimal interface for a ServiceManager-type contract that AVS ServiceManager contracts must implement + * for eigenlabs to be able to index their data on the AVS marketplace frontend. + * @author Layr Labs, Inc. + */ +interface IServiceManagerUI { + /** + * Metadata should follow the format outlined by this example. + { + "name": "EigenLabs AVS 1", + "website": "https://www.eigenlayer.xyz/", + "description": "This is my 1st AVS", + "logo": "https://holesky-operator-metadata.s3.amazonaws.com/eigenlayer.png", + "twitter": "https://twitter.com/eigenlayer" + } + * @notice Updates the metadata URI for the AVS + * @param _metadataURI is the metadata URI for the AVS + */ + function updateAVSMetadataURI(string memory _metadataURI) external; + + /** + * @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. + */ + function registerOperatorToAVS( + address operator, + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature + ) external; + + /** + * @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; + + /** + * @notice Returns the list of strategies that the operator has potentially restaked on the AVS + * @param operator The address of the operator to get restaked strategies for + * @dev This function is intended to be called off-chain + * @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); + + /** + * @notice Returns the list of strategies that the AVS supports for restaking + * @dev This function is intended to be called off-chain + * @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); + + /// @notice Returns the EigenLayer AVSDirectory contract. + function avsDirectory() external view returns (address); +} 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 new file mode 100644 index 00000000..91738da5 --- /dev/null +++ b/src/unaudited/ECDSAServiceManagerBase.sol @@ -0,0 +1,353 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.12; + +import {OwnableUpgradeable} from "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.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"; +import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; +import {IStakeRegistry} from "../interfaces/IStakeRegistry.sol"; +import {IRewardsCoordinator} from "eigenlayer-contracts/src/contracts/interfaces/IRewardsCoordinator.sol"; +import {Quorum} from "../interfaces/IECDSAStakeRegistryEventsAndErrors.sol"; +import {ECDSAStakeRegistry} from "../unaudited/ECDSAStakeRegistry.sol"; + +abstract contract ECDSAServiceManagerBase is + IServiceManager, + OwnableUpgradeable +{ + using SafeERC20 for IERC20; + + /// @notice Address of the stake registry contract, which manages registration and stake recording. + address public immutable stakeRegistry; + + /// @notice Address of the AVS directory contract, which manages AVS-related data for registered operators. + address public immutable avsDirectory; + + /// @notice Address of the rewards coordinator contract, which handles rewards distributions. + address internal immutable rewardsCoordinator; + + /// @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` + */ + modifier onlyStakeRegistry() { + require( + msg.sender == stakeRegistry, + "ECDSAServiceManagerBase.onlyStakeRegistry: caller is not the stakeRegistry" + ); + _; + } + + /** + * @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. + * @param _stakeRegistry The address of the stake registry contract, managing registration and stake recording. + * @param _rewardsCoordinator The address of the rewards coordinator contract, handling rewards distributions. + * @param _delegationManager The address of the delegation manager contract, managing staker delegations to operators. + */ + constructor( + address _avsDirectory, + address _stakeRegistry, + address _rewardsCoordinator, + address _delegationManager + ) { + avsDirectory = _avsDirectory; + stakeRegistry = _stakeRegistry; + rewardsCoordinator = _rewardsCoordinator; + delegationManager = _delegationManager; + _disableInitializers(); + } + + /** + * @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 _rewardsInitiator + ) internal virtual onlyInitializing { + _transferOwnership(initialOwner); + _setRewardsInitiator(_rewardsInitiator); + } + + /// @inheritdoc IServiceManagerUI + function updateAVSMetadataURI( + string memory _metadataURI + ) external virtual onlyOwner { + _updateAVSMetadataURI(_metadataURI); + } + + /// @inheritdoc IServiceManager + function createAVSRewardsSubmission( + IRewardsCoordinator.RewardsSubmission[] calldata rewardsSubmissions + ) 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, + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature + ) external virtual onlyStakeRegistry { + _registerOperatorToAVS(operator, operatorSignature); + } + + /// @inheritdoc IServiceManagerUI + function deregisterOperatorFromAVS( + address operator + ) external virtual onlyStakeRegistry { + _deregisterOperatorFromAVS(operator); + } + + /// @inheritdoc IServiceManagerUI + function getRestakeableStrategies() + external + view + virtual + returns (address[] memory) + { + return _getRestakeableStrategies(); + } + + /// @inheritdoc IServiceManagerUI + function getOperatorRestakedStrategies( + address _operator + ) external view virtual returns (address[] memory) { + return _getOperatorRestakedStrategies(_operator); + } + + /** + * @notice Forwards the call to update AVS metadata URI in the AVSDirectory contract. + * @dev This internal function is a proxy to the `updateAVSMetadataURI` function of the AVSDirectory contract. + * @param _metadataURI The new metadata URI to be set. + */ + function _updateAVSMetadataURI( + string memory _metadataURI + ) internal virtual { + IAVSDirectory(avsDirectory).updateAVSMetadataURI(_metadataURI); + } + + /** + * @notice Forwards the call to register an operator in the AVSDirectory contract. + * @dev This internal function is a proxy to the `registerOperatorToAVS` function of the AVSDirectory contract. + * @param operator The address of the operator to register. + * @param operatorSignature The signature, salt, and expiry details of the operator's registration. + */ + function _registerOperatorToAVS( + address operator, + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature + ) internal virtual { + IAVSDirectory(avsDirectory).registerOperatorToAVS( + operator, + operatorSignature + ); + } + + /** + * @notice Forwards the call to deregister an operator from the AVSDirectory contract. + * @dev This internal function is a proxy to the `deregisterOperatorFromAVS` function of the AVSDirectory contract. + * @param operator The address of the operator to deregister. + */ + function _deregisterOperatorFromAVS(address operator) internal virtual { + IAVSDirectory(avsDirectory).deregisterOperatorFromAVS(operator); + } + + /** + * @notice Processes a batch of rewards submissions by transferring the specified amounts from the sender to this contract and then approving the RewardsCoordinator to use these amounts. + * @dev This function handles the transfer and approval of tokens necessary for rewards submissions. It then delegates the actual rewards logic to the RewardsCoordinator contract. + * @param rewardsSubmissions An array of `RewardsSubmission` structs, each representing rewards for a specific range. + */ + function _createAVSRewardsSubmission( + IRewardsCoordinator.RewardsSubmission[] calldata rewardsSubmissions + ) internal virtual { + for (uint256 i = 0; i < rewardsSubmissions.length; ++i) { + rewardsSubmissions[i].token.safeTransferFrom( + msg.sender, + address(this), + rewardsSubmissions[i].amount + ); + rewardsSubmissions[i].token.safeIncreaseAllowance( + rewardsCoordinator, + rewardsSubmissions[i].amount + ); + } + + 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.safeTransferFrom( + msg.sender, + address(this), + totalAmount + ); + operatorDirectedRewardsSubmissions[i].token.safeIncreaseAllowance( + rewardsCoordinator, + totalAmount + ); + } + + 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); + } + + /** + * @notice Retrieves the addresses of all strategies that are part of the current quorum. + * @dev Fetches the quorum configuration from the ECDSAStakeRegistry and extracts the strategy addresses. + * @return strategies An array of addresses representing the strategies in the current quorum. + */ + function _getRestakeableStrategies() + internal + view + virtual + returns (address[] memory) + { + Quorum memory quorum = ECDSAStakeRegistry(stakeRegistry).quorum(); + address[] memory strategies = new address[](quorum.strategies.length); + for (uint256 i = 0; i < quorum.strategies.length; i++) { + strategies[i] = address(quorum.strategies[i].strategy); + } + return strategies; + } + + /** + * @notice Retrieves the addresses of strategies where the operator has restaked. + * @dev This function fetches the quorum details from the ECDSAStakeRegistry, retrieves the operator's shares for each strategy, + * and filters out strategies with non-zero shares indicating active restaking by the operator. + * @param _operator The address of the operator whose restaked strategies are to be retrieved. + * @return restakedStrategies An array of addresses of strategies where the operator has active restakes. + */ + function _getOperatorRestakedStrategies( + address _operator + ) internal view virtual returns (address[] memory) { + Quorum memory quorum = ECDSAStakeRegistry(stakeRegistry).quorum(); + uint256 count = quorum.strategies.length; + IStrategy[] memory strategies = new IStrategy[](count); + for (uint256 i; i < count; i++) { + strategies[i] = quorum.strategies[i].strategy; + } + uint256[] memory shares = IDelegationManager(delegationManager) + .getOperatorShares(_operator, strategies); + + uint256 activeCount; + for (uint256 i; i < count; i++) { + if (shares[i] > 0) { + activeCount++; + } + } + + // 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[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[49] private __GAP; +} diff --git a/src/unaudited/ECDSAStakeRegistry.sol b/src/unaudited/ECDSAStakeRegistry.sol index 94b6c638..ab4bdbeb 100644 --- a/src/unaudited/ECDSAStakeRegistry.sol +++ b/src/unaudited/ECDSAStakeRegistry.sol @@ -43,13 +43,14 @@ contract ECDSAStakeRegistry is __ECDSAStakeRegistry_init(_serviceManager, _thresholdWeight, _quorum); } - /// @notice Registers a new operator using a provided signature + /// @notice Registers a new operator using a provided signature and signing key /// @param _operatorSignature Contains the operator's signature, salt, and expiry + /// @param _signingKey The signing key to add to the operator's history function registerOperatorWithSignature( - address _operator, - ISignatureUtils.SignatureWithSaltAndExpiry memory _operatorSignature + ISignatureUtils.SignatureWithSaltAndExpiry memory _operatorSignature, + address _signingKey ) external { - _registerOperatorWithSig(_operator, _operatorSignature); + _registerOperatorWithSig(msg.sender, _operatorSignature, _signingKey); } /// @notice Deregisters an existing operator @@ -58,7 +59,19 @@ contract ECDSAStakeRegistry is } /** - * @notice Updates the StakeRegistry's view of one or more operators' stakes adding a new entry in their history of stake checkpoints, + * @notice Updates the signing key for an operator + * @dev Only callable by the operator themselves + * @param _newSigningKey The new signing key to set for the operator + */ + function updateOperatorSigningKey(address _newSigningKey) external { + if (!_operatorRegistered[msg.sender]) { + revert OperatorNotRegistered(); + } + _updateOperatorSigningKey(msg.sender, _newSigningKey); + } + + /** + * @notice Updates the StakeRegistry's view of one or more operators' stakes adding a new entry in their history of stake checkpoints, * @dev Queries stakes from the Eigenlayer core DelegationManager contract * @param _operators A list of operator addresses to update */ @@ -68,12 +81,15 @@ contract ECDSAStakeRegistry is /** * @notice Updates the quorum configuration and the set of operators - * @dev Only callable by the contract owner. + * @dev Only callable by the contract owner. * It first updates the quorum configuration and then updates the list of operators. * @param _quorum The new quorum configuration, including strategies and their new weights * @param _operators The list of operator addresses to update stakes for */ - function updateQuorumConfig(Quorum memory _quorum, address[] memory _operators) external onlyOwner { + function updateQuorumConfig( + Quorum memory _quorum, + address[] memory _operators + ) external onlyOwner { _updateQuorumConfig(_quorum); _updateOperators(_operators); } @@ -81,17 +97,20 @@ contract ECDSAStakeRegistry is /// @notice Updates the weight an operator must have to join the operator set /// @dev Access controlled to the contract owner /// @param _newMinimumWeight The new weight an operator must have to join the operator set - function updateMinimumWeight(uint256 _newMinimumWeight, address[] memory _operators) external onlyOwner { + function updateMinimumWeight( + uint256 _newMinimumWeight, + address[] memory _operators + ) external onlyOwner { _updateMinimumWeight(_newMinimumWeight); _updateOperators(_operators); } /** * @notice Sets a new cumulative threshold weight for message validation by operator set signatures. - * @dev This function can only be invoked by the owner of the contract. It delegates the update to - * an internal function `_updateStakeThreshold`. - * @param _thresholdWeight The updated threshold weight required to validate a message. This is the - * cumulative weight that must be met or exceeded by the sum of the stakes of the signatories for + * @dev This function can only be invoked by the owner of the contract. It delegates the update to + * an internal function `_updateStakeThreshold`. + * @param _thresholdWeight The updated threshold weight required to validate a message. This is the + * cumulative weight that must be met or exceeded by the sum of the stakes of the signatories for * a message to be deemed valid. */ function updateStakeThreshold(uint256 _thresholdWeight) external onlyOwner { @@ -100,17 +119,18 @@ contract ECDSAStakeRegistry is /// @notice Verifies if the provided signature data is valid for the given data hash. /// @param _dataHash The hash of the data that was signed. - /// @param _signatureData Encoded signature data consisting of an array of signers, an array of signatures, and a reference block number. + /// @param _signatureData Encoded signature data consisting of an array of operators, an array of signatures, and a reference block number. /// @return The function selector that indicates the signature is valid according to ERC1271 standard. function isValidSignature( bytes32 _dataHash, bytes memory _signatureData ) external view returns (bytes4) { - (address[] memory signers, bytes[] memory signatures, uint32 referenceBlock) = abi.decode( - _signatureData, - (address[], bytes[], uint32) - ); - _checkSignatures(_dataHash, signers, signatures, referenceBlock); + ( + address[] memory operators, + bytes[] memory signatures, + uint32 referenceBlock + ) = abi.decode(_signatureData, (address[], bytes[], uint32)); + _checkSignatures(_dataHash, operators, signatures, referenceBlock); return IERC1271Upgradeable.isValidSignature.selector; } @@ -120,10 +140,43 @@ contract ECDSAStakeRegistry is return _quorum; } + /** + * @notice Retrieves the latest signing key for a given operator. + * @param _operator The address of the operator. + * @return The latest signing key of the operator. + */ + function getLastestOperatorSigningKey( + address _operator + ) external view returns (address) { + return address(uint160(_operatorSigningKeyHistory[_operator].latest())); + } + + /** + * @notice Retrieves the latest signing key for a given operator at a specific block number. + * @param _operator The address of the operator. + * @param _blockNumber The block number to get the operator's signing key. + * @return The signing key of the operator at the given block. + */ + function getOperatorSigningKeyAtBlock( + address _operator, + uint256 _blockNumber + ) external view returns (address) { + return + address( + uint160( + _operatorSigningKeyHistory[_operator].getAtBlock( + _blockNumber + ) + ) + ); + } + /// @notice Retrieves the last recorded weight for a given operator. /// @param _operator The address of the operator. /// @return uint256 - The latest weight of the operator. - function getLastCheckpointOperatorWeight(address _operator) external view returns (uint256) { + function getLastCheckpointOperatorWeight( + address _operator + ) external view returns (uint256) { return _operatorWeightHistory[_operator].latest(); } @@ -133,9 +186,13 @@ contract ECDSAStakeRegistry is return _totalWeightHistory.latest(); } - /// @notice Retrieves the last recorded threshold weight + /// @notice Retrieves the last recorded threshold weight /// @return uint256 - The latest threshold weight. - function getLastCheckpointThresholdWeight() external view returns (uint256) { + function getLastCheckpointThresholdWeight() + external + view + returns (uint256) + { return _thresholdWeightHistory.latest(); } @@ -168,10 +225,12 @@ contract ECDSAStakeRegistry is return _thresholdWeightHistory.getAtBlock(_blockNumber); } - function operatorRegistered(address _operator) external view returns (bool) { + function operatorRegistered( + address _operator + ) external view returns (bool) { return _operatorRegistered[_operator]; } - + /// @notice Returns the weight an operator must have to contribute to validating an AVS function minimumWeight() external view returns (uint256) { return _minimumWeight; @@ -180,18 +239,21 @@ contract ECDSAStakeRegistry is /// @notice Calculates the current weight of an operator based on their delegated stake in the strategies considered in the quorum /// @param _operator The address of the operator. /// @return uint256 - The current weight of the operator; returns 0 if below the threshold. - function getOperatorWeight(address _operator) public view returns (uint256) { + function getOperatorWeight( + address _operator + ) public view returns (uint256) { StrategyParams[] memory strategyParams = _quorum.strategies; uint256 weight; IStrategy[] memory strategies = new IStrategy[](strategyParams.length); for (uint256 i; i < strategyParams.length; i++) { - strategies[i]=strategyParams[i].strategy; - + strategies[i] = strategyParams[i].strategy; } - uint256[] memory shares = DELEGATION_MANAGER.getOperatorShares(_operator, strategies); - for (uint256 i; i= _currentSigner){ + function _validateSortedSigners( + address _lastSigner, + address _currentSigner + ) internal pure { + if (_lastSigner >= _currentSigner) { revert NotSorted(); } } @@ -440,6 +545,26 @@ contract ECDSAStakeRegistry is } } + /// @notice Retrieves the operator weight for a signer, either at the last checkpoint or a specified block. + /// @param _operator The operator to query their signing key history for + /// @param _referenceBlock The block number to query the operator's weight at, or the maximum uint32 value for the last checkpoint. + /// @return The weight of the operator. + function _getOperatorSigningKey( + address _operator, + uint32 _referenceBlock + ) internal view returns (address) { + if (_referenceBlock >= block.number) { + revert InvalidReferenceBlock(); + } + return + address( + uint160( + _operatorSigningKeyHistory[_operator].getAtBlock( + _referenceBlock + ) + ) + ); + } /// @notice Retrieves the operator weight for a signer, either at the last checkpoint or a specified block. /// @param _signer The address of the signer whose weight is returned. @@ -449,47 +574,51 @@ contract ECDSAStakeRegistry is address _signer, uint32 _referenceBlock ) internal view returns (uint256) { - if (_referenceBlock == type(uint32).max) { - return _operatorWeightHistory[_signer].latest(); - } else { - return _operatorWeightHistory[_signer].getAtBlock(_referenceBlock); + if (_referenceBlock >= block.number) { + revert InvalidReferenceBlock(); } + return _operatorWeightHistory[_signer].getAtBlock(_referenceBlock); } /// @notice Retrieve the total stake weight at a specific block or the latest if not specified. /// @dev If the `_referenceBlock` is the maximum value for uint32, the latest total weight is returned. /// @param _referenceBlock The block number to retrieve the total stake weight from. /// @return The total stake weight at the given block or the latest if the given block is the max uint32 value. - function _getTotalWeight(uint32 _referenceBlock) internal view returns (uint256) { - if (_referenceBlock == type(uint32).max) { - return _totalWeightHistory.latest(); - } else { - return _totalWeightHistory.getAtBlock(_referenceBlock); + function _getTotalWeight( + uint32 _referenceBlock + ) internal view returns (uint256) { + if (_referenceBlock >= block.number) { + revert InvalidReferenceBlock(); } + return _totalWeightHistory.getAtBlock(_referenceBlock); } /// @notice Retrieves the threshold stake for a given reference block. /// @param _referenceBlock The block number to query the threshold stake for. /// If set to the maximum uint32 value, it retrieves the latest threshold stake. /// @return The threshold stake in basis points for the reference block. - function _getThresholdStake(uint32 _referenceBlock) internal view returns (uint256) { - if (_referenceBlock == type(uint32).max) { - return _thresholdWeightHistory.latest(); - } else { - return _thresholdWeightHistory.getAtBlock(_referenceBlock); + function _getThresholdStake( + uint32 _referenceBlock + ) internal view returns (uint256) { + if (_referenceBlock >= block.number) { + revert InvalidReferenceBlock(); } + return _thresholdWeightHistory.getAtBlock(_referenceBlock); } /// @notice Validates that the cumulative stake of signed messages meets or exceeds the required threshold. /// @param _signedWeight The cumulative weight of the signers that have signed the message. /// @param _referenceBlock The block number to verify the stake threshold for - function _validateThresholdStake(uint256 _signedWeight, uint32 _referenceBlock) internal view { + function _validateThresholdStake( + uint256 _signedWeight, + uint32 _referenceBlock + ) internal view { uint256 totalWeight = _getTotalWeight(_referenceBlock); - if (_signedWeight > totalWeight){ + if (_signedWeight > totalWeight) { revert InvalidSignedWeight(); } uint256 thresholdStake = _getThresholdStake(_referenceBlock); - if (thresholdStake > _signedWeight){ + if (thresholdStake > _signedWeight) { revert InsufficientSignedStake(); } } diff --git a/src/unaudited/ECDSAStakeRegistryStorage.sol b/src/unaudited/ECDSAStakeRegistryStorage.sol index fe09b9d8..0742157a 100644 --- a/src/unaudited/ECDSAStakeRegistryStorage.sol +++ b/src/unaudited/ECDSAStakeRegistryStorage.sol @@ -5,7 +5,9 @@ import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/ import {CheckpointsUpgradeable} from "@openzeppelin-upgrades/contracts/utils/CheckpointsUpgradeable.sol"; import {ECDSAStakeRegistryEventsAndErrors, Quorum, StrategyParams} from "../interfaces/IECDSAStakeRegistryEventsAndErrors.sol"; -abstract contract ECDSAStakeRegistryStorage is ECDSAStakeRegistryEventsAndErrors { +abstract contract ECDSAStakeRegistryStorage is + ECDSAStakeRegistryEventsAndErrors +{ /// @notice Manages staking delegations through the DelegationManager interface IDelegationManager internal immutable DELEGATION_MANAGER; @@ -27,6 +29,10 @@ abstract contract ECDSAStakeRegistryStorage is ECDSAStakeRegistryEventsAndErrors /// @notice Defines the duration after which the stake's weight expires. uint256 internal _stakeExpiry; + /// @notice Maps an operator to their signing key history using checkpoints + mapping(address => CheckpointsUpgradeable.History) + internal _operatorSigningKeyHistory; + /// @notice Tracks the total stake history over time using checkpoints CheckpointsUpgradeable.History internal _totalWeightHistory; @@ -34,7 +40,8 @@ abstract contract ECDSAStakeRegistryStorage is ECDSAStakeRegistryEventsAndErrors CheckpointsUpgradeable.History internal _thresholdWeightHistory; /// @notice Maps operator addresses to their respective stake histories using checkpoints - mapping(address => CheckpointsUpgradeable.History) internal _operatorWeightHistory; + mapping(address => CheckpointsUpgradeable.History) + internal _operatorWeightHistory; /// @notice Maps an operator to their registration status mapping(address => bool) internal _operatorRegistered; @@ -47,5 +54,5 @@ abstract contract ECDSAStakeRegistryStorage is ECDSAStakeRegistryEventsAndErrors // slither-disable-next-line shadowing-state /// @dev Reserves storage slots for future upgrades // solhint-disable-next-line - uint256[42] private __gap; + uint256[40] private __gap; } diff --git a/src/unaudited/examples/ECDSAStakeRegistryPermissioned.sol b/src/unaudited/examples/ECDSAStakeRegistryPermissioned.sol index c5329ce2..ef2e691c 100644 --- a/src/unaudited/examples/ECDSAStakeRegistryPermissioned.sol +++ b/src/unaudited/examples/ECDSAStakeRegistryPermissioned.sol @@ -27,7 +27,9 @@ contract ECDSAStakeRegistryPermissioned is ECDSAStakeRegistry { /// @dev Custom error to signal that an operator is already allowlisted. error OperatorAlreadyAllowlisted(); - constructor(IDelegationManager _delegationManager) ECDSAStakeRegistry(_delegationManager) { + constructor( + IDelegationManager _delegationManager + ) ECDSAStakeRegistry(_delegationManager) { // _disableInitializers(); } @@ -63,38 +65,40 @@ contract ECDSAStakeRegistryPermissioned is ECDSAStakeRegistry { /// Doesn't register the operator into the operator set /// @param _operator The address of the operator to allowlist. function _permitOperator(address _operator) internal { - if (allowlistedOperators[_operator]){ + if (allowlistedOperators[_operator]) { revert OperatorAlreadyAllowlisted(); } allowlistedOperators[_operator] = true; emit OperatorPermitted(_operator); - } /// @dev Removes an operator from the allowlist. /// If the operator is registered, also deregisters the operator. /// @param _operator The address of the operator to be revoked. function _revokeOperator(address _operator) internal { - if (!allowlistedOperators[_operator]){ + if (!allowlistedOperators[_operator]) { revert OperatorNotAllowlisted(); } delete allowlistedOperators[_operator]; emit OperatorRevoked(_operator); - if (_operatorRegistered[_operator]){ + if (_operatorRegistered[_operator]) { _ejectOperator(_operator); } - } /// @inheritdoc ECDSAStakeRegistry function _registerOperatorWithSig( address _operator, - ISignatureUtils.SignatureWithSaltAndExpiry memory _operatorSignature + ISignatureUtils.SignatureWithSaltAndExpiry memory _operatorSignature, + address _operatorSigningKey ) internal override { - if (allowlistedOperators[_operator] != true){ + if (allowlistedOperators[_operator] != true) { revert OperatorNotAllowlisted(); } - super._registerOperatorWithSig(_operator, _operatorSignature); + super._registerOperatorWithSig( + _operator, + _operatorSignature, + _operatorSigningKey + ); } } - diff --git a/test/events/IServiceManagerBaseEvents.sol b/test/events/IServiceManagerBaseEvents.sol new file mode 100644 index 00000000..4ae9b92e --- /dev/null +++ b/test/events/IServiceManagerBaseEvents.sol @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.12; + +import {IRewardsCoordinator, IERC20} from "eigenlayer-contracts/src/contracts/interfaces/IRewardsCoordinator.sol"; + +interface IServiceManagerBaseEvents { + /// RewardsCoordinator EVENTS /// + + /// @notice emitted when an AVS creates a valid RewardsSubmission + event AVSRewardsSubmissionCreated( + address indexed avs, + uint256 indexed submissionNonce, + bytes32 indexed rewardsSubmissionHash, + IRewardsCoordinator.RewardsSubmission rewardsSubmission + ); + /// @notice emitted when a valid RewardsSubmission is created for all stakers by a valid submitter + event RewardsSubmissionForAllCreated( + address indexed submitter, + uint256 indexed submissionNonce, + bytes32 indexed rewardsSubmissionHash, + IRewardsCoordinator.RewardsSubmission rewardsSubmission + ); + /// @notice rewardsUpdater is responsible for submiting DistributionRoots, only owner can set rewardsUpdater + 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 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, + bytes32 indexed root, + uint32 indexed rewardsCalculationEndTimestamp, + uint32 activatedAt + ); + /// @notice root is one of the submitted distribution roots that was claimed against + event RewardsClaimed( + bytes32 root, + address indexed earner, + address indexed claimer, + address indexed recipient, + 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 /// + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @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 + ); +} diff --git a/test/ffi/BLSPubKeyCompendiumFFI.t.sol b/test/ffi/BLSPubKeyCompendiumFFI.t.sol index 4034749f..18d49a17 100644 --- a/test/ffi/BLSPubKeyCompendiumFFI.t.sol +++ b/test/ffi/BLSPubKeyCompendiumFFI.t.sol @@ -9,7 +9,7 @@ contract BLSApkRegistryFFITests is G2Operations { using BN254 for BN254.G1Point; using Strings for uint256; - Vm cheats = Vm(HEVM_ADDRESS); + Vm cheats = Vm(VM_ADDRESS); BLSApkRegistry blsApkRegistry; IRegistryCoordinator registryCoordinator; 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 d1d1d161..64730b17 100644 --- a/test/integration/CoreRegistration.t.sol +++ b/test/integration/CoreRegistration.t.sol @@ -6,6 +6,8 @@ import { AVSDirectory } from "eigenlayer-contracts/src/contracts/core/AVSDirecto import { IAVSDirectory } from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; import { DelegationManager } from "eigenlayer-contracts/src/contracts/core/DelegationManager.sol"; import { IDelegationManager } from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; +import { RewardsCoordinator } from "eigenlayer-contracts/src/contracts/core/RewardsCoordinator.sol"; +import { IRewardsCoordinator } from "eigenlayer-contracts/src/contracts/interfaces/IRewardsCoordinator.sol"; contract Test_CoreRegistration is MockAVSDeployer { // Contracts @@ -62,10 +64,13 @@ contract Test_CoreRegistration is MockAVSDeployer { ) ); + // Deploy Mock RewardsCoordinator + rewardsCoordinatorMock = new RewardsCoordinatorMock(); // Deploy New ServiceManager & RegistryCoordinator implementations serviceManagerImplementation = new ServiceManagerMock( avsDirectory, + rewardsCoordinatorMock, registryCoordinator, stakeRegistry ); @@ -74,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 e8d74673..d69a908b 100644 --- a/test/integration/IntegrationDeployer.t.sol +++ b/test/integration/IntegrationDeployer.t.sol @@ -16,13 +16,12 @@ import "eigenlayer-contracts/src/contracts/core/DelegationManager.sol"; import "eigenlayer-contracts/src/contracts/core/StrategyManager.sol"; import "eigenlayer-contracts/src/contracts/core/Slasher.sol"; import "eigenlayer-contracts/src/contracts/core/AVSDirectory.sol"; +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/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"; @@ -31,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"; @@ -41,22 +41,21 @@ import "eigenlayer-contracts/src/test/mocks/EmptyContract.sol"; import "test/integration/User.t.sol"; abstract contract IntegrationDeployer is Test, IUserDeployer { - using Strings for *; - Vm cheats = Vm(HEVM_ADDRESS); + Vm cheats = Vm(VM_ADDRESS); // Core contracts to deploy DelegationManager delegationManager; AVSDirectory public avsDirectory; StrategyManager strategyManager; EigenPodManager eigenPodManager; + RewardsCoordinator rewardsCoordinator; PauserRegistry pauserRegistry; Slasher slasher; IBeacon eigenPodBeacon; EigenPod pod; ETHPOSDepositMock ethPOSDeposit; - BeaconChainOracleMock beaconChainOracle; // Base strategy implementation in case we want to create more strategies later StrategyBase baseStrategyImplementation; @@ -67,6 +66,7 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { BLSApkRegistry blsApkRegistry; StakeRegistry stakeRegistry; IndexRegistry indexRegistry; + SocketRegistry socketRegistry; OperatorStateRetriever operatorStateRetriever; TimeMachine public timeMachine; @@ -81,17 +81,30 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { address eigenLayerReputedMultisig = address(this); // admin address address constant pauser = address(555); address constant unpauser = address(556); - address public registryCoordinatorOwner = address(uint160(uint256(keccak256("registryCoordinatorOwner")))); + address public registryCoordinatorOwner = + address(uint160(uint256(keccak256("registryCoordinatorOwner")))); uint256 public churnApproverPrivateKey = uint256(keccak256("churnApproverPrivateKey")); address public churnApprover = cheats.addr(churnApproverPrivateKey); address ejector = address(uint160(uint256(keccak256("ejector")))); + address rewardsUpdater = address(uint160(uint256(keccak256("rewardsUpdater")))); // Constants/Defaults - uint64 constant MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR = 32e9; - uint constant MIN_BALANCE = 1e6; - uint constant MAX_BALANCE = 5e6; - uint constant MAX_STRATEGY_COUNT = 32; // From StakeRegistry.MAX_WEIGHING_FUNCTION_LENGTH + 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 uint96 constant DEFAULT_STRATEGY_MULTIPLIER = 1e18; + // RewardsCoordinator + uint32 MAX_REWARDS_DURATION = 70 days; + uint32 MAX_RETROACTIVE_LENGTH = 84 days; + uint32 MAX_FUTURE_LENGTH = 28 days; + uint32 GENESIS_REWARDS_TIMESTAMP = 1_712_092_632; + /// @notice Delay in timestamp before a posted root can be claimed against + uint32 activationDelay = 7 days; + /// @notice intervals(epochs) are 2 weeks + uint32 calculationIntervalSeconds = 14 days; + /// @notice the commission for all operators across all avss + uint16 globalCommissionBips = 1000; function setUp() public virtual { // Deploy ProxyAdmin @@ -105,49 +118,67 @@ 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 * not yet deployed, we give these proxies an empty contract as the initial implementation, to act as if they have no code. */ delegationManager = DelegationManager( - address(new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "")) + address( + new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "") + ) ); strategyManager = StrategyManager( - address(new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "")) + address( + new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "") + ) ); slasher = Slasher( - address(new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "")) + address( + new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "") + ) ); eigenPodManager = EigenPodManager( - address(new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "")) + address( + new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "") + ) ); avsDirectory = AVSDirectory( - address(new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "")) + address( + new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "") + ) ); + // RewardsCoordinator = RewardsCoordinator( + // address(new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "")) + // ); // Deploy EigenPod Contracts pod = new EigenPod( ethPOSDeposit, eigenPodManager, - MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR + GENESIS_TIME_LOCAL ); eigenPodBeacon = new UpgradeableBeacon(address(pod)); // Second, deploy the *implementation* contracts, using the *proxy contracts* as inputs - DelegationManager delegationImplementation = new DelegationManager(strategyManager, slasher, eigenPodManager); - StrategyManager strategyManagerImplementation = new StrategyManager(delegationManager, eigenPodManager, slasher); + DelegationManager delegationImplementation = + new DelegationManager(strategyManager, slasher, eigenPodManager); + StrategyManager strategyManagerImplementation = + new StrategyManager(delegationManager, eigenPodManager, slasher); Slasher slasherImplementation = new Slasher(strategyManager, delegationManager); EigenPodManager eigenPodManagerImplementation = new EigenPodManager( - ethPOSDeposit, - eigenPodBeacon, - strategyManager, - slasher, - delegationManager + ethPOSDeposit, eigenPodBeacon, strategyManager, slasher, delegationManager ); AVSDirectory avsDirectoryImplemntation = new AVSDirectory(delegationManager); + // RewardsCoordinator rewardsCoordinatorImplementation = new RewardsCoordinator( + // delegationManager, + // IStrategyManager(address(strategyManager)), + // MAX_REWARDS_DURATION, + // MAX_RETROACTIVE_LENGTH, + // MAX_FUTURE_LENGTH, + // GENESIS_REWARDS_TIMESTAMP + // ); // Third, upgrade the proxy contracts to point to the implementations uint256 minWithdrawalDelayBlocks = 7 days / 12 seconds; @@ -161,7 +192,7 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { DelegationManager.initialize.selector, eigenLayerReputedMultisig, // initialOwner pauserRegistry, - 0 /* initialPausedStatus */, + 0, /* initialPausedStatus */ minWithdrawalDelayBlocks, initializeStrategiesToSetDelayBlocks, initializeWithdrawalDelayBlocks @@ -196,7 +227,6 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { address(eigenPodManagerImplementation), abi.encodeWithSelector( EigenPodManager.initialize.selector, - address(beaconChainOracle), eigenLayerReputedMultisig, // initialOwner pauserRegistry, 0 // initialPausedStatus @@ -213,11 +243,26 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { 0 // initialPausedStatus ) ); + // // RewardsCoordinator + // proxyAdmin.upgradeAndCall( + // TransparentUpgradeableProxy(payable(address(rewardsCoordinator))), + // address(rewardsCoordinatorImplementation), + // abi.encodeWithSelector( + // RewardsCoordinator.initialize.selector, + // eigenLayerReputedMultisig, // initialOwner + // pauserRegistry, + // 0, // initialPausedStatus + // rewardsUpdater, + // activationDelay, + // calculationIntervalSeconds, + // globalCommissionBips + // ) + // ); // Deploy and whitelist strategies baseStrategyImplementation = new StrategyBase(strategyManager); - for (uint i = 0; i < MAX_STRATEGY_COUNT; i++) { - string memory number = uint(i).toString(); + for (uint256 i = 0; i < MAX_STRATEGY_COUNT; i++) { + string memory number = uint256(i).toString(); string memory stratName = string.concat("StrategyToken", number); string memory stratSymbol = string.concat("STT", number); _newStrategyAndToken(stratName, stratSymbol, 10e50, address(this)); @@ -227,59 +272,59 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { timeMachine = new TimeMachine(); cheats.startPrank(registryCoordinatorOwner); - registryCoordinator = RegistryCoordinator(address( - new TransparentUpgradeableProxy( - address(emptyContract), - address(proxyAdmin), - "" + registryCoordinator = RegistryCoordinator( + address( + new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "") ) - )); + ); stakeRegistry = StakeRegistry( address( - new TransparentUpgradeableProxy( - address(emptyContract), - address(proxyAdmin), - "" - ) + new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "") + ) + ); + + socketRegistry = SocketRegistry( + address( + new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "") ) ); indexRegistry = IndexRegistry( address( - new TransparentUpgradeableProxy( - address(emptyContract), - address(proxyAdmin), - "" - ) + new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "") ) ); blsApkRegistry = BLSApkRegistry( address( - new TransparentUpgradeableProxy( - address(emptyContract), - address(proxyAdmin), - "" - ) + new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "") ) ); serviceManager = ServiceManagerMock( address( - new TransparentUpgradeableProxy( - address(emptyContract), - address(proxyAdmin), - "" - ) + new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "") ) ); cheats.stopPrank(); - StakeRegistry stakeRegistryImplementation = new StakeRegistry(IRegistryCoordinator(registryCoordinator), IDelegationManager(delegationManager)); - BLSApkRegistry blsApkRegistryImplementation = new BLSApkRegistry(IRegistryCoordinator(registryCoordinator)); - IndexRegistry indexRegistryImplementation = new IndexRegistry(IRegistryCoordinator(registryCoordinator)); - ServiceManagerMock serviceManagerImplementation = new ServiceManagerMock(IAVSDirectory(avsDirectory), IRegistryCoordinator(registryCoordinator), stakeRegistry); + StakeRegistry stakeRegistryImplementation = new StakeRegistry( + IRegistryCoordinator(registryCoordinator), IDelegationManager(delegationManager) + ); + BLSApkRegistry blsApkRegistryImplementation = + new BLSApkRegistry(IRegistryCoordinator(registryCoordinator)); + IndexRegistry indexRegistryImplementation = + new IndexRegistry(IRegistryCoordinator(registryCoordinator)); + ServiceManagerMock serviceManagerImplementation = new ServiceManagerMock( + IAVSDirectory(avsDirectory), + rewardsCoordinator, + IRegistryCoordinator(registryCoordinator), + stakeRegistry + ); + SocketRegistry socketRegistryImplementation = new SocketRegistry( + IRegistryCoordinator(registryCoordinator) + ); proxyAdmin.upgrade( TransparentUpgradeableProxy(payable(address(stakeRegistry))), @@ -301,9 +346,18 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { address(serviceManagerImplementation) ); - serviceManager.initialize({initialOwner: registryCoordinatorOwner}); + proxyAdmin.upgrade( + TransparentUpgradeableProxy(payable(address(socketRegistry))), + address(socketRegistryImplementation) + ); - RegistryCoordinator registryCoordinatorImplementation = new RegistryCoordinator(serviceManager, stakeRegistry, blsApkRegistry, indexRegistry); + serviceManager.initialize({ + initialOwner: registryCoordinatorOwner, + rewardsInitiator: address(msg.sender) + }); + + RegistryCoordinator registryCoordinatorImplementation = + new RegistryCoordinator(serviceManager, stakeRegistry, blsApkRegistry, indexRegistry, socketRegistry); proxyAdmin.upgradeAndCall( TransparentUpgradeableProxy(payable(address(registryCoordinator))), address(registryCoordinatorImplementation), @@ -313,7 +367,7 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { churnApprover, ejector, pauserRegistry, - 0/*initialPausedStatus*/, + 0, /*initialPausedStatus*/ new IRegistryCoordinator.OperatorSetParam[](0), new uint96[](0), new IStakeRegistry.StrategyParams[][](0) @@ -325,14 +379,22 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { /// @dev Deploy a strategy and its underlying token, push to global lists of tokens/strategies, and whitelist /// strategy in strategyManager - function _newStrategyAndToken(string memory tokenName, string memory tokenSymbol, uint initialSupply, address owner) internal { - IERC20 underlyingToken = new ERC20PresetFixedSupply(tokenName, tokenSymbol, initialSupply, owner); + function _newStrategyAndToken( + string memory tokenName, + string memory tokenSymbol, + uint256 initialSupply, + address owner + ) internal { + IERC20 underlyingToken = + new ERC20PresetFixedSupply(tokenName, tokenSymbol, initialSupply, owner); StrategyBase strategy = StrategyBase( address( new TransparentUpgradeableProxy( address(baseStrategyImplementation), address(proxyAdmin), - abi.encodeWithSelector(StrategyBase.initialize.selector, underlyingToken, pauserRegistry) + abi.encodeWithSelector( + StrategyBase.initialize.selector, underlyingToken, pauserRegistry + ) ) ) ); @@ -342,7 +404,9 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { bool[] memory thirdPartyTransfersForbiddenValues = new bool[](1); strategies[0] = strategy; cheats.prank(strategyManager.strategyWhitelister()); - strategyManager.addStrategiesToDepositWhitelist(strategies, thirdPartyTransfersForbiddenValues); + strategyManager.addStrategiesToDepositWhitelist( + strategies, thirdPartyTransfersForbiddenValues + ); // Add to allStrats allStrats.push(strategy); diff --git a/test/integration/TimeMachine.t.sol b/test/integration/TimeMachine.t.sol index 129c9892..b1df82d5 100644 --- a/test/integration/TimeMachine.t.sol +++ b/test/integration/TimeMachine.t.sol @@ -5,7 +5,7 @@ import "forge-std/Test.sol"; contract TimeMachine is Test { - Vm cheats = Vm(HEVM_ADDRESS); + Vm cheats = Vm(VM_ADDRESS); bool pastExists = false; uint lastSnapshot; diff --git a/test/integration/User.t.sol b/test/integration/User.t.sol index f809e3b4..c0da643f 100644 --- a/test/integration/User.t.sol +++ b/test/integration/User.t.sol @@ -43,7 +43,7 @@ contract User is Test { using BitmapStrings for *; using BitmapUtils for *; - Vm cheats = Vm(HEVM_ADDRESS); + Vm cheats = Vm(VM_ADDRESS); // Core contracts DelegationManager delegationManager; @@ -117,6 +117,7 @@ contract User is Test { function registerOperator(bytes calldata quorums) public createSnapshot virtual returns (bytes32) { _log("registerOperator", quorums); + vm.warp(block.timestamp + 1); registryCoordinator.registerOperator({ quorumNumbers: quorums, socket: NAME, @@ -208,6 +209,7 @@ contract User is Test { expiry: expiry }); + vm.warp(block.timestamp + 1); registryCoordinator.registerOperatorWithChurn({ quorumNumbers: allQuorums, socket: NAME, @@ -397,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/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 9d9f1b79..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,10 +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 491f0f10..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; } @@ -24,7 +27,7 @@ contract DelegationMock is IDelegationManager { mapping (address => address) public delegatedTo; function registerAsOperator(OperatorDetails calldata /*registeringOperatorDetails*/, string calldata /*metadataURI*/) external pure {} - + function updateOperatorMetadataURI(string calldata /*metadataURI*/) external pure {} function updateAVSMetadataURI(string calldata /*metadataURI*/) external pure {} @@ -65,10 +68,6 @@ contract DelegationMock is IDelegationManager { return returnValue; } - function earningsReceiver(address operator) external pure returns (address) { - return operator; - } - function delegationApprover(address operator) external pure returns (address) { return operator; } @@ -77,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) { @@ -143,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) {} @@ -168,9 +176,7 @@ contract DelegationMock is IDelegationManager { uint256[] calldata middlewareTimesIndexes, bool[] calldata receiveAsTokens ) external {} - - // function migrateQueuedWithdrawals(IStrategyManager.DeprecatedStruct_QueuedWithdrawal[] memory withdrawalsToQueue) external {} - + // onlyDelegationManager functions in StrategyManager function addShares( IStrategyManager strategyManager, @@ -200,20 +206,4 @@ contract DelegationMock is IDelegationManager { ) external { strategyManager.withdrawSharesAsTokens(recipient, strategy, shares, token); } - - function getDelegatableShares(address staker) external view returns (IStrategy[] memory, uint256[] memory) { - IStrategy[] memory strategies; - uint256[] memory shares; - return (strategies, shares); - } - - function beaconChainETHStrategy() external view returns (IStrategy) { - return IStrategy(address(0)); - } - - function setMinWithdrawalDelayBlocks(uint256 newMinWithdrawalDelayBlocks) external { - } - - function setStrategyWithdrawalDelayBlocks(IStrategy[] calldata strategies, uint256[] calldata withdrawalDelayBlocks) external { - } } 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 new file mode 100644 index 00000000..c3e36490 --- /dev/null +++ b/test/mocks/RewardsCoordinatorMock.sol @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.12; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import {IRewardsCoordinator} from "eigenlayer-contracts/src/contracts/interfaces/IRewardsCoordinator.sol"; + +contract RewardsCoordinatorMock is IRewardsCoordinator { + function rewardsUpdater() external view returns (address) {} + + function CALCULATION_INTERVAL_SECONDS() external view returns (uint32) {} + + function MAX_REWARDS_DURATION() external view returns (uint32) {} + + function MAX_RETROACTIVE_LENGTH() external view returns (uint32) {} + + function MAX_FUTURE_LENGTH() external view returns (uint32) {} + + function GENESIS_REWARDS_TIMESTAMP() external view returns (uint32) {} + + function activationDelay() external view returns (uint32) {} + + function claimerFor(address earner) external view returns (address) {} + + function cumulativeClaimed( + address claimer, + IERC20 token + ) external view returns (uint256) {} + + function defaultOperatorSplitBips() external view returns (uint16) {} + + function calculateEarnerLeafHash( + EarnerTreeMerkleLeaf 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 currRewardsCalculationEndTimestamp() + external + view + returns (uint32) + {} + + function getDistributionRootsLength() external view returns (uint256) {} + + function getDistributionRootAtIndex( + uint256 index + ) external view returns (DistributionRoot memory) {} + + 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) {} + + 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 createOperatorDirectedAVSRewardsSubmission( + address avs, + OperatorDirectedRewardsSubmission[] + calldata operatorDirectedRewardsSubmissions + ) 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 disableRoot(uint32 rootIndex) external {} + + function setClaimerFor(address claimer) external {} + + function setActivationDelay(uint32 _activationDelay) external {} + + function setDefaultOperatorSplit(uint16 split) external {} + + function setRewardsUpdater(address _rewardsUpdater) external {} + + function setRewardsForAllSubmitter( + address _submitter, + bool _newValue + ) external {} + + function setOperatorAVSSplit( + address operator, + address avs, + uint16 split + ) external {} + + function setOperatorPISplit(address operator, uint16 split) external {} +} diff --git a/test/mocks/ServiceManagerMock.sol b/test/mocks/ServiceManagerMock.sol index 1a7f0089..8af99426 100644 --- a/test/mocks/ServiceManagerMock.sol +++ b/test/mocks/ServiceManagerMock.sol @@ -6,11 +6,17 @@ import "../../src/ServiceManagerBase.sol"; contract ServiceManagerMock is ServiceManagerBase { constructor( IAVSDirectory _avsDirectory, + IRewardsCoordinator _rewardsCoordinator, IRegistryCoordinator _registryCoordinator, IStakeRegistry _stakeRegistry - ) ServiceManagerBase(_avsDirectory, _registryCoordinator, _stakeRegistry) {} + ) + ServiceManagerBase(_avsDirectory, _rewardsCoordinator, _registryCoordinator, _stakeRegistry) + {} - function initialize(address initialOwner) public virtual initializer { - __ServiceManagerBase_init(initialOwner); + function initialize( + address initialOwner, + address rewardsInitiator + ) public virtual initializer { + __ServiceManagerBase_init(initialOwner, rewardsInitiator); } } diff --git a/test/unit/BLSApkRegistryUnit.t.sol b/test/unit/BLSApkRegistryUnit.t.sol index 4b3c1f1d..5f800444 100644 --- a/test/unit/BLSApkRegistryUnit.t.sol +++ b/test/unit/BLSApkRegistryUnit.t.sol @@ -57,20 +57,26 @@ contract BLSApkRegistryUnitTests is BLSMockAVSDeployer, IBLSApkRegistryEvents { addressIsExcludedFromFuzzedInputs[defaultOperator] = true; addressIsExcludedFromFuzzedInputs[address(proxyAdmin)] = true; - pubkeyRegistrationParams.pubkeyG1 = BN254.generatorG1().scalar_mul(privKey); + pubkeyRegistrationParams.pubkeyG1 = BN254.generatorG1().scalar_mul( + privKey + ); defaultPubkey = pubkeyRegistrationParams.pubkeyG1; defaultPubkeyHash = BN254.hashG1Point(defaultPubkey); //privKey*G2 - pubkeyRegistrationParams.pubkeyG2.X[1] = - 19_101_821_850_089_705_274_637_533_855_249_918_363_070_101_489_527_618_151_493_230_256_975_900_223_847; - pubkeyRegistrationParams.pubkeyG2.X[0] = - 5_334_410_886_741_819_556_325_359_147_377_682_006_012_228_123_419_628_681_352_847_439_302_316_235_957; - pubkeyRegistrationParams.pubkeyG2.Y[1] = - 354_176_189_041_917_478_648_604_979_334_478_067_325_821_134_838_555_150_300_539_079_146_482_658_331; - pubkeyRegistrationParams.pubkeyG2.Y[0] = - 4_185_483_097_059_047_421_902_184_823_581_361_466_320_657_066_600_218_863_748_375_739_772_335_928_910; + pubkeyRegistrationParams.pubkeyG2.X[ + 1 + ] = 19_101_821_850_089_705_274_637_533_855_249_918_363_070_101_489_527_618_151_493_230_256_975_900_223_847; + pubkeyRegistrationParams.pubkeyG2.X[ + 0 + ] = 5_334_410_886_741_819_556_325_359_147_377_682_006_012_228_123_419_628_681_352_847_439_302_316_235_957; + pubkeyRegistrationParams.pubkeyG2.Y[ + 1 + ] = 354_176_189_041_917_478_648_604_979_334_478_067_325_821_134_838_555_150_300_539_079_146_482_658_331; + pubkeyRegistrationParams.pubkeyG2.Y[ + 0 + ] = 4_185_483_097_059_047_421_902_184_823_581_361_466_320_657_066_600_218_863_748_375_739_772_335_928_910; // Initialize 3 quorums _initializeQuorum(); @@ -89,7 +95,9 @@ contract BLSApkRegistryUnitTests is BLSMockAVSDeployer, IBLSApkRegistryEvents { initializedQuorums[quorumNumber] = true; // Mark quorum initialized for other tests - initializedQuorumBitmap = uint192(initializedQuorumBitmap.setBit(quorumNumber)); + initializedQuorumBitmap = uint192( + initializedQuorumBitmap.setBit(quorumNumber) + ); initializedQuorumBytes = initializedQuorumBitmap.bitmapToBytesArray(); } @@ -105,7 +113,9 @@ contract BLSApkRegistryUnitTests is BLSMockAVSDeployer, IBLSApkRegistryEvents { /// @dev initializeQuorum based on passed in bitmap of quorum numbers /// assumes that bitmap does not contain already initailized quorums and doesn't increment nextQuorum function _initializeFuzzedQuorums(uint192 bitmap) internal { - bytes memory quorumNumbers = bitmapUtilsWrapper.bitmapToBytesArray(bitmap); + bytes memory quorumNumbers = bitmapUtilsWrapper.bitmapToBytesArray( + bitmap + ); for (uint256 i = 0; i < quorumNumbers.length; i++) { uint8 quorumNumber = uint8(quorumNumbers[i]); @@ -131,7 +141,9 @@ contract BLSApkRegistryUnitTests is BLSMockAVSDeployer, IBLSApkRegistryEvents { } function _getRandBool(uint256 seed) internal view returns (bool) { - uint256 randomNumber = uint256(keccak256(abi.encodePacked(block.timestamp, seed))); + uint256 randomNumber = uint256( + keccak256(abi.encodePacked(block.timestamp, seed)) + ); return randomNumber % 2 == 0; } @@ -141,8 +153,11 @@ contract BLSApkRegistryUnitTests is BLSMockAVSDeployer, IBLSApkRegistryEvents { * */ - function _signMessage(address signer) internal view returns (BN254.G1Point memory) { - BN254.G1Point memory messageHash = registryCoordinator.pubkeyRegistrationMessageHash(signer); + function _signMessage( + address signer + ) internal view returns (BN254.G1Point memory) { + BN254.G1Point memory messageHash = registryCoordinator + .pubkeyRegistrationMessageHash(signer); return BN254.scalar_mul(messageHash, privKey); } @@ -165,19 +180,31 @@ contract BLSApkRegistryUnitTests is BLSMockAVSDeployer, IBLSApkRegistryEvents { /** * @dev registering operator with the default preset BLS key */ - function _registerDefaultBLSPubkey(address operator) internal returns (bytes32) { - pubkeyRegistrationParams.pubkeyRegistrationSignature = _signMessage(operator); - BN254.G1Point memory messageHash = - registryCoordinator.pubkeyRegistrationMessageHash(operator); + function _registerDefaultBLSPubkey( + address operator + ) internal returns (bytes32) { + pubkeyRegistrationParams.pubkeyRegistrationSignature = _signMessage( + operator + ); + BN254.G1Point memory messageHash = registryCoordinator + .pubkeyRegistrationMessageHash(operator); cheats.prank(address(registryCoordinator)); - return blsApkRegistry.registerBLSPublicKey(operator, pubkeyRegistrationParams, messageHash); + return + blsApkRegistry.registerBLSPublicKey( + operator, + pubkeyRegistrationParams, + messageHash + ); } /** * @dev register operator, assumes operator has a registered BLS public key and that quorumNumbers are valid */ - function _registerOperator(address operator, bytes memory quorumNumbers) internal { + function _registerOperator( + address operator, + bytes memory quorumNumbers + ) internal { bytes32 operatorId = blsApkRegistry.getOperatorId(operator); cheats.prank(address(registryCoordinator)); cheats.expectEmit(true, true, true, true, address(blsApkRegistry)); @@ -188,7 +215,10 @@ contract BLSApkRegistryUnitTests is BLSMockAVSDeployer, IBLSApkRegistryEvents { /** * @dev deregister operator, assumes operator has a registered BLS public key and that quorumNumbers are valid */ - function _deregisterOperator(address operator, bytes memory quorumNumbers) internal { + function _deregisterOperator( + address operator, + bytes memory quorumNumbers + ) internal { bytes32 operatorId = blsApkRegistry.getOperatorId(operator); cheats.prank(address(registryCoordinator)); cheats.expectEmit(true, true, true, true, address(blsApkRegistry)); @@ -202,8 +232,12 @@ contract BLSApkRegistryUnitTests is BLSMockAVSDeployer, IBLSApkRegistryEvents { * */ - function _getApks(bytes memory quorumNumbers) internal view returns (BN254.G1Point[] memory) { - BN254.G1Point[] memory quorumApks = new BN254.G1Point[](quorumNumbers.length); + function _getApks( + bytes memory quorumNumbers + ) internal view returns (BN254.G1Point[] memory) { + BN254.G1Point[] memory quorumApks = new BN254.G1Point[]( + quorumNumbers.length + ); for (uint8 i = 0; i < quorumNumbers.length; i++) { quorumApks[i] = blsApkRegistry.getApk(uint8(quorumNumbers[i])); } @@ -222,7 +256,9 @@ contract BLSApkRegistryUnitTests is BLSMockAVSDeployer, IBLSApkRegistryEvents { "apksBefore and quorumNumbers must be the same length" ); assertEq( - apksBefore.length, apksAfter.length, "apksBefore and apksAfter must be the same length" + apksBefore.length, + apksAfter.length, + "apksBefore and apksAfter must be the same length" ); for (uint256 i = 0; i < apksBefore.length; i++) { uint8 quorumNumber = uint8(quorumNumbers[i]); @@ -234,9 +270,11 @@ contract BLSApkRegistryUnitTests is BLSMockAVSDeployer, IBLSApkRegistryEvents { "quorum apk not updated correctly adding the operator pubkey" ); - uint32 quorumHistoryLength = blsApkRegistry.getApkHistoryLength(quorumNumber); - IBLSApkRegistry.ApkUpdate memory latestApkUpdate = - blsApkRegistry.getApkUpdateAtIndex(quorumNumber, quorumHistoryLength - 1); + uint32 quorumHistoryLength = blsApkRegistry.getApkHistoryLength( + quorumNumber + ); + IBLSApkRegistry.ApkUpdate memory latestApkUpdate = blsApkRegistry + .getApkUpdateAtIndex(quorumNumber, quorumHistoryLength - 1); assertEq( latestApkUpdate.apkHash, bytes24(BN254.hashG1Point(apkAfter)), @@ -279,7 +317,9 @@ contract BLSApkRegistryUnitTests_configAndGetters is BLSApkRegistryUnitTests { } /// @notice test for BLSApkRegistry.registerBLSPublicKey() -contract BLSApkRegistryUnitTests_registerBLSPublicKey is BLSApkRegistryUnitTests { +contract BLSApkRegistryUnitTests_registerBLSPublicKey is + BLSApkRegistryUnitTests +{ using BN254 for BN254.G1Point; function testFuzz_registerOperator_Revert_WhenNotRegistryCoordinator( @@ -287,59 +327,99 @@ contract BLSApkRegistryUnitTests_registerBLSPublicKey is BLSApkRegistryUnitTests ) public filterFuzzedAddressInputs(nonCoordinatorAddress) { cheats.assume(nonCoordinatorAddress != address(registryCoordinator)); - pubkeyRegistrationParams.pubkeyRegistrationSignature = _signMessage(defaultOperator); - BN254.G1Point memory messageHash = - registryCoordinator.pubkeyRegistrationMessageHash(defaultOperator); + pubkeyRegistrationParams.pubkeyRegistrationSignature = _signMessage( + defaultOperator + ); + BN254.G1Point memory messageHash = registryCoordinator + .pubkeyRegistrationMessageHash(defaultOperator); cheats.prank(address(nonCoordinatorAddress)); cheats.expectRevert( "BLSApkRegistry.onlyRegistryCoordinator: caller is not the registry coordinator" ); - blsApkRegistry.registerBLSPublicKey(defaultOperator, pubkeyRegistrationParams, messageHash); + blsApkRegistry.registerBLSPublicKey( + defaultOperator, + pubkeyRegistrationParams, + messageHash + ); } - function testFuzz_registerOperator_Revert_WhenZeroPubkeyHash(address operator) public { + function testFuzz_registerOperator_Revert_WhenZeroPubkeyHash( + address operator + ) public filterFuzzedAddressInputs(operator) { pubkeyRegistrationParams.pubkeyG1.X = 0; pubkeyRegistrationParams.pubkeyG1.Y = 0; - BN254.G1Point memory messageHash = - registryCoordinator.pubkeyRegistrationMessageHash(operator); + BN254.G1Point memory messageHash = registryCoordinator + .pubkeyRegistrationMessageHash(operator); cheats.prank(address(registryCoordinator)); - cheats.expectRevert("BLSApkRegistry.registerBLSPublicKey: cannot register zero pubkey"); - blsApkRegistry.registerBLSPublicKey(operator, pubkeyRegistrationParams, messageHash); + cheats.expectRevert( + "BLSApkRegistry.registerBLSPublicKey: cannot register zero pubkey" + ); + blsApkRegistry.registerBLSPublicKey( + operator, + pubkeyRegistrationParams, + messageHash + ); } - function testFuzz_registerOperator_Revert_WhenOperatorAlreadyRegistered(address operator) - public - { - pubkeyRegistrationParams.pubkeyRegistrationSignature = _signMessage(operator); - BN254.G1Point memory messageHash = - registryCoordinator.pubkeyRegistrationMessageHash(operator); + function testFuzz_registerOperator_Revert_WhenOperatorAlreadyRegistered( + address operator + ) public filterFuzzedAddressInputs(operator) { + pubkeyRegistrationParams.pubkeyRegistrationSignature = _signMessage( + operator + ); + BN254.G1Point memory messageHash = registryCoordinator + .pubkeyRegistrationMessageHash(operator); cheats.startPrank(address(registryCoordinator)); - blsApkRegistry.registerBLSPublicKey(operator, pubkeyRegistrationParams, messageHash); + blsApkRegistry.registerBLSPublicKey( + operator, + pubkeyRegistrationParams, + messageHash + ); cheats.expectRevert( "BLSApkRegistry.registerBLSPublicKey: operator already registered pubkey" ); - blsApkRegistry.registerBLSPublicKey(operator, pubkeyRegistrationParams, messageHash); + blsApkRegistry.registerBLSPublicKey( + operator, + pubkeyRegistrationParams, + messageHash + ); } function testFuzz_registerOperator_Revert_WhenPubkeyAlreadyRegistered( address operator, address operator2 - ) public { + ) + public + filterFuzzedAddressInputs(operator) + filterFuzzedAddressInputs(operator2) + { cheats.assume(operator != address(0)); cheats.assume(operator != operator2); - BN254.G1Point memory messageHash = - registryCoordinator.pubkeyRegistrationMessageHash(operator); - pubkeyRegistrationParams.pubkeyRegistrationSignature = _signMessage(operator); + BN254.G1Point memory messageHash = registryCoordinator + .pubkeyRegistrationMessageHash(operator); + pubkeyRegistrationParams.pubkeyRegistrationSignature = _signMessage( + operator + ); cheats.startPrank(address(registryCoordinator)); - blsApkRegistry.registerBLSPublicKey(operator, pubkeyRegistrationParams, messageHash); + blsApkRegistry.registerBLSPublicKey( + operator, + pubkeyRegistrationParams, + messageHash + ); - cheats.expectRevert("BLSApkRegistry.registerBLSPublicKey: public key already registered"); - blsApkRegistry.registerBLSPublicKey(operator2, pubkeyRegistrationParams, messageHash); + cheats.expectRevert( + "BLSApkRegistry.registerBLSPublicKey: public key already registered" + ); + blsApkRegistry.registerBLSPublicKey( + operator2, + pubkeyRegistrationParams, + messageHash + ); } /** @@ -349,10 +429,14 @@ contract BLSApkRegistryUnitTests_registerBLSPublicKey is BLSApkRegistryUnitTests function testFuzz_registerOperator_Revert_WhenInvalidSignature( address operator, address invalidOperator - ) public { + ) + public + filterFuzzedAddressInputs(operator) + filterFuzzedAddressInputs(invalidOperator) + { cheats.assume(invalidOperator != operator); - BN254.G1Point memory messageHash = - registryCoordinator.pubkeyRegistrationMessageHash(operator); + BN254.G1Point memory messageHash = registryCoordinator + .pubkeyRegistrationMessageHash(operator); BN254.G1Point memory invalidSignature = _signMessage(invalidOperator); pubkeyRegistrationParams.pubkeyRegistrationSignature = invalidSignature; @@ -361,56 +445,88 @@ contract BLSApkRegistryUnitTests_registerBLSPublicKey is BLSApkRegistryUnitTests cheats.expectRevert( "BLSApkRegistry.registerBLSPublicKey: either the G1 signature is wrong, or G1 and G2 private key do not match" ); - blsApkRegistry.registerBLSPublicKey(operator, pubkeyRegistrationParams, messageHash); + blsApkRegistry.registerBLSPublicKey( + operator, + pubkeyRegistrationParams, + messageHash + ); } /** * @dev operator is registering their public key but G1 and G2 private keys do not match */ - function testFuzz_registerOperator_Revert_WhenInvalidSignatureMismatchKey(address operator) - public - filterFuzzedAddressInputs(operator) - { - pubkeyRegistrationParams.pubkeyRegistrationSignature = _signMessage(operator); + function testFuzz_registerOperator_Revert_WhenInvalidSignatureMismatchKey( + address operator + ) public filterFuzzedAddressInputs(operator) { + pubkeyRegistrationParams.pubkeyRegistrationSignature = _signMessage( + operator + ); BN254.G1Point memory badPubkeyG1 = BN254.generatorG1().scalar_mul(420); // mismatch public keys pubkeyRegistrationParams.pubkeyG1 = badPubkeyG1; - BN254.G1Point memory messageHash = - registryCoordinator.pubkeyRegistrationMessageHash(operator); + BN254.G1Point memory messageHash = registryCoordinator + .pubkeyRegistrationMessageHash(operator); cheats.prank(address(registryCoordinator)); cheats.expectRevert( "BLSApkRegistry.registerBLSPublicKey: either the G1 signature is wrong, or G1 and G2 private key do not match" ); - blsApkRegistry.registerBLSPublicKey(operator, pubkeyRegistrationParams, messageHash); + blsApkRegistry.registerBLSPublicKey( + operator, + pubkeyRegistrationParams, + messageHash + ); } /** * @dev fuzz tests for different operator addresses but uses the same BLS key for each. * Checks for storage mappings being set correctly. */ - function testFuzz_registerBLSPublicKey(address operator) - public - filterFuzzedAddressInputs(operator) - { + function testFuzz_registerBLSPublicKey( + address operator + ) public filterFuzzedAddressInputs(operator) { // sign messagehash for operator with private key - pubkeyRegistrationParams.pubkeyRegistrationSignature = _signMessage(operator); - BN254.G1Point memory messageHash = - registryCoordinator.pubkeyRegistrationMessageHash(operator); + pubkeyRegistrationParams.pubkeyRegistrationSignature = _signMessage( + operator + ); + BN254.G1Point memory messageHash = registryCoordinator + .pubkeyRegistrationMessageHash(operator); cheats.prank(address(registryCoordinator)); cheats.expectEmit(true, true, true, true, address(blsApkRegistry)); emit NewPubkeyRegistration( - operator, pubkeyRegistrationParams.pubkeyG1, pubkeyRegistrationParams.pubkeyG2 + operator, + pubkeyRegistrationParams.pubkeyG1, + pubkeyRegistrationParams.pubkeyG2 + ); + blsApkRegistry.registerBLSPublicKey( + operator, + pubkeyRegistrationParams, + messageHash ); - blsApkRegistry.registerBLSPublicKey(operator, pubkeyRegistrationParams, messageHash); - (BN254.G1Point memory registeredPubkey, bytes32 registeredpkHash) = - blsApkRegistry.getRegisteredPubkey(operator); - assertEq(registeredPubkey.X, defaultPubkey.X, "registeredPubkey not set correctly"); - assertEq(registeredPubkey.Y, defaultPubkey.Y, "registeredPubkey not set correctly"); - assertEq(registeredpkHash, defaultPubkeyHash, "registeredpkHash not set correctly"); + ( + BN254.G1Point memory registeredPubkey, + bytes32 registeredpkHash + ) = blsApkRegistry.getRegisteredPubkey(operator); + assertEq( + registeredPubkey.X, + defaultPubkey.X, + "registeredPubkey not set correctly" + ); + assertEq( + registeredPubkey.Y, + defaultPubkey.Y, + "registeredPubkey not set correctly" + ); + assertEq( + registeredpkHash, + defaultPubkeyHash, + "registeredpkHash not set correctly" + ); assertEq( - blsApkRegistry.pubkeyHashToOperator(BN254.hashG1Point(defaultPubkey)), + blsApkRegistry.pubkeyHashToOperator( + BN254.hashG1Point(defaultPubkey) + ), operator, "operator address not stored correctly" ); @@ -434,12 +550,13 @@ contract BLSApkRegistryUnitTests_registerOperator is BLSApkRegistryUnitTests { blsApkRegistry.registerOperator(nonCoordinatorAddress, new bytes(0)); } - function testFuzz_registerOperator_Revert_WhenOperatorDoesNotOwnPubkey(address operator) - public - filterFuzzedAddressInputs(operator) - { + function testFuzz_registerOperator_Revert_WhenOperatorDoesNotOwnPubkey( + address operator + ) public filterFuzzedAddressInputs(operator) { cheats.prank(address(registryCoordinator)); - cheats.expectRevert("BLSApkRegistry.getRegisteredPubkey: operator is not registered"); + cheats.expectRevert( + "BLSApkRegistry.getRegisteredPubkey: operator is not registered" + ); blsApkRegistry.registerOperator(operator, new bytes(1)); } @@ -450,13 +567,19 @@ contract BLSApkRegistryUnitTests_registerOperator is BLSApkRegistryUnitTests { cheats.prank(address(registryCoordinator)); cheats.assume(quorumBitmap > initializedQuorumBitmap); // mask out quorums that are already initialized - quorumBitmap = uint192(quorumBitmap.minus(uint256(initializedQuorumBitmap))); - bytes memory quorumNumbers = bitmapUtilsWrapper.bitmapToBytesArray(quorumBitmap); + quorumBitmap = uint192( + quorumBitmap.minus(uint256(initializedQuorumBitmap)) + ); + bytes memory quorumNumbers = bitmapUtilsWrapper.bitmapToBytesArray( + quorumBitmap + ); _registerDefaultBLSPubkey(operator); cheats.prank(address(registryCoordinator)); - cheats.expectRevert("BLSApkRegistry._processQuorumApkUpdate: quorum does not exist"); + cheats.expectRevert( + "BLSApkRegistry._processQuorumApkUpdate: quorum does not exist" + ); blsApkRegistry.registerOperator(operator, quorumNumbers); } @@ -472,15 +595,26 @@ contract BLSApkRegistryUnitTests_registerOperator is BLSApkRegistryUnitTests { ) public filterFuzzedAddressInputs(operator) { // Test setup, initialize fuzzed quorums and register operator BLS pubkey cheats.assume(quorumBitmap > initializedQuorumBitmap); - uint192 initializingBitmap = uint192(quorumBitmap.minus(uint256(initializedQuorumBitmap))); + uint192 initializingBitmap = uint192( + quorumBitmap.minus(uint256(initializedQuorumBitmap)) + ); _initializeFuzzedQuorums(initializingBitmap); - bytes memory quorumNumbers = bitmapUtilsWrapper.bitmapToBytesArray(quorumBitmap); - (BN254.G1Point memory pubkey,) = _registerRandomBLSPubkey(operator, randomSeed); + bytes memory quorumNumbers = bitmapUtilsWrapper.bitmapToBytesArray( + quorumBitmap + ); + (BN254.G1Point memory pubkey, ) = _registerRandomBLSPubkey( + operator, + randomSeed + ); // get before values - BN254.G1Point[] memory quorumApksBefore = new BN254.G1Point[](quorumNumbers.length); + BN254.G1Point[] memory quorumApksBefore = new BN254.G1Point[]( + quorumNumbers.length + ); for (uint8 i = 0; i < quorumNumbers.length; i++) { - quorumApksBefore[i] = blsApkRegistry.getApk(uint8(quorumNumbers[i])); + quorumApksBefore[i] = blsApkRegistry.getApk( + uint8(quorumNumbers[i]) + ); } // registerOperator with expected OperatorAddedToQuorums event @@ -494,16 +628,20 @@ contract BLSApkRegistryUnitTests_registerOperator is BLSApkRegistryUnitTests { for (uint8 i = 0; i < quorumNumbers.length; i++) { // Check currentApk[quorumNumber] values uint8 quorumNumber = uint8(quorumNumbers[i]); - BN254.G1Point memory quorumApkAfter = blsApkRegistry.getApk(uint8(quorumNumbers[i])); + BN254.G1Point memory quorumApkAfter = blsApkRegistry.getApk( + uint8(quorumNumbers[i]) + ); assertEq( BN254.hashG1Point(quorumApkAfter), BN254.hashG1Point(quorumApksBefore[i].plus(pubkey)), "quorum apk not updated correctly adding the operator pubkey" ); // Check the latest ApkUpdate values - uint32 quorumHistoryLength = blsApkRegistry.getApkHistoryLength(quorumNumber); - IBLSApkRegistry.ApkUpdate memory latestApkUpdate = - blsApkRegistry.getApkUpdateAtIndex(quorumNumber, quorumHistoryLength - 1); + uint32 quorumHistoryLength = blsApkRegistry.getApkHistoryLength( + quorumNumber + ); + IBLSApkRegistry.ApkUpdate memory latestApkUpdate = blsApkRegistry + .getApkUpdateAtIndex(quorumNumber, quorumHistoryLength - 1); assertEq( latestApkUpdate.apkHash, bytes24(BN254.hashG1Point(quorumApkAfter)), @@ -540,12 +678,13 @@ contract BLSApkRegistryUnitTests_deregisterOperator is BLSApkRegistryUnitTests { blsApkRegistry.deregisterOperator(nonCoordinatorAddress, new bytes(0)); } - function testFuzz_deregisterOperator_Revert_WhenOperatorDoesNotOwnPubkey(address operator) - public - filterFuzzedAddressInputs(operator) - { + function testFuzz_deregisterOperator_Revert_WhenOperatorDoesNotOwnPubkey( + address operator + ) public filterFuzzedAddressInputs(operator) { cheats.prank(address(registryCoordinator)); - cheats.expectRevert("BLSApkRegistry.getRegisteredPubkey: operator is not registered"); + cheats.expectRevert( + "BLSApkRegistry.getRegisteredPubkey: operator is not registered" + ); blsApkRegistry.registerOperator(operator, new bytes(1)); } @@ -556,16 +695,22 @@ contract BLSApkRegistryUnitTests_deregisterOperator is BLSApkRegistryUnitTests { cheats.prank(address(registryCoordinator)); cheats.assume(quorumBitmap > initializedQuorumBitmap); // mask out quorums that are already initialized - quorumBitmap = uint192(quorumBitmap.minus(uint256(initializedQuorumBitmap))); - bytes memory validQuorumNumbers = - bitmapUtilsWrapper.bitmapToBytesArray(initializedQuorumBitmap); - bytes memory invalidQuorumNumbers = bitmapUtilsWrapper.bitmapToBytesArray(quorumBitmap); + quorumBitmap = uint192( + quorumBitmap.minus(uint256(initializedQuorumBitmap)) + ); + bytes memory validQuorumNumbers = bitmapUtilsWrapper.bitmapToBytesArray( + initializedQuorumBitmap + ); + bytes memory invalidQuorumNumbers = bitmapUtilsWrapper + .bitmapToBytesArray(quorumBitmap); _registerDefaultBLSPubkey(operator); _registerOperator(operator, validQuorumNumbers); cheats.prank(address(registryCoordinator)); - cheats.expectRevert("BLSApkRegistry._processQuorumApkUpdate: quorum does not exist"); + cheats.expectRevert( + "BLSApkRegistry._processQuorumApkUpdate: quorum does not exist" + ); blsApkRegistry.deregisterOperator(operator, invalidQuorumNumbers); } @@ -581,16 +726,27 @@ contract BLSApkRegistryUnitTests_deregisterOperator is BLSApkRegistryUnitTests { ) public filterFuzzedAddressInputs(operator) { // Test setup, initialize fuzzed quorums and register operator BLS pubkey cheats.assume(quorumBitmap > initializedQuorumBitmap); - uint192 initializingBitmap = uint192(quorumBitmap.minus(uint256(initializedQuorumBitmap))); + uint192 initializingBitmap = uint192( + quorumBitmap.minus(uint256(initializedQuorumBitmap)) + ); _initializeFuzzedQuorums(initializingBitmap); - bytes memory quorumNumbers = bitmapUtilsWrapper.bitmapToBytesArray(quorumBitmap); - (BN254.G1Point memory pubkey,) = _registerRandomBLSPubkey(operator, randomSeed); + bytes memory quorumNumbers = bitmapUtilsWrapper.bitmapToBytesArray( + quorumBitmap + ); + (BN254.G1Point memory pubkey, ) = _registerRandomBLSPubkey( + operator, + randomSeed + ); _registerOperator(operator, quorumNumbers); // get before values - BN254.G1Point[] memory quorumApksBefore = new BN254.G1Point[](quorumNumbers.length); + BN254.G1Point[] memory quorumApksBefore = new BN254.G1Point[]( + quorumNumbers.length + ); for (uint8 i = 0; i < quorumNumbers.length; i++) { - quorumApksBefore[i] = blsApkRegistry.getApk(uint8(quorumNumbers[i])); + quorumApksBefore[i] = blsApkRegistry.getApk( + uint8(quorumNumbers[i]) + ); } // registerOperator with expected OperatorAddedToQuorums event @@ -604,16 +760,20 @@ contract BLSApkRegistryUnitTests_deregisterOperator is BLSApkRegistryUnitTests { for (uint8 i = 0; i < quorumNumbers.length; i++) { // Check currentApk[quorumNumber] values uint8 quorumNumber = uint8(quorumNumbers[i]); - BN254.G1Point memory quorumApkAfter = blsApkRegistry.getApk(uint8(quorumNumbers[i])); + BN254.G1Point memory quorumApkAfter = blsApkRegistry.getApk( + uint8(quorumNumbers[i]) + ); assertEq( BN254.hashG1Point(quorumApkAfter), BN254.hashG1Point(quorumApksBefore[i].plus(pubkey.negate())), "quorum apk not updated correctly removing the operator pubkey" ); // Check the latest ApkUpdate values - uint32 quorumHistoryLength = blsApkRegistry.getApkHistoryLength(quorumNumber); - IBLSApkRegistry.ApkUpdate memory latestApkUpdate = - blsApkRegistry.getApkUpdateAtIndex(quorumNumber, quorumHistoryLength - 1); + uint32 quorumHistoryLength = blsApkRegistry.getApkHistoryLength( + quorumNumber + ); + IBLSApkRegistry.ApkUpdate memory latestApkUpdate = blsApkRegistry + .getApkUpdateAtIndex(quorumNumber, quorumHistoryLength - 1); assertEq( latestApkUpdate.apkHash, bytes24(BN254.hashG1Point(quorumApkAfter)), @@ -645,7 +805,10 @@ contract BLSApkRegistryUnitTests_quorumApkUpdates is BLSApkRegistryUnitTests { * @dev register/deregister up to 200 operators and check quorum apk updates * Test uses only the defaultQuorumNumber */ - function testFuzz_quorumApkUpdates(uint256 numOperators, uint256[200] memory randSeed) public { + function testFuzz_quorumApkUpdates( + uint256 numOperators, + uint256[200] memory randSeed + ) public { cheats.assume(0 < numOperators && numOperators <= 200); bytes memory quorumNumbers = new bytes(1); quorumNumbers[0] = bytes1(defaultQuorumNumber); @@ -659,11 +822,17 @@ contract BLSApkRegistryUnitTests_quorumApkUpdates is BLSApkRegistryUnitTests { // register and check quorum apk updates BN254.G1Point[] memory quorumApksBefore = _getApks(quorumNumbers); address operator = _selectNewOperator(); - (BN254.G1Point memory operatorPubkey,) = _registerRandomBLSPubkey(operator, randSeed[i]); + (BN254.G1Point memory operatorPubkey, ) = _registerRandomBLSPubkey( + operator, + randSeed[i] + ); _registerOperator(operator, quorumNumbers); BN254.G1Point[] memory quorumApksAfter = _getApks(quorumNumbers); _assertQuorumApkUpdates( - quorumNumbers, quorumApksBefore, quorumApksAfter, operatorPubkey + quorumNumbers, + quorumApksBefore, + quorumApksAfter, + operatorPubkey ); // deregister and check quorum apk updates @@ -673,7 +842,10 @@ contract BLSApkRegistryUnitTests_quorumApkUpdates is BLSApkRegistryUnitTests { _deregisterOperator(operator, quorumNumbers); quorumApksAfter = _getApks(quorumNumbers); _assertQuorumApkUpdates( - quorumNumbers, quorumApksBefore, quorumApksAfter, operatorPubkey.negate() + quorumNumbers, + quorumApksBefore, + quorumApksAfter, + operatorPubkey.negate() ); } } @@ -691,9 +863,13 @@ contract BLSApkRegistryUnitTests_quorumApkUpdates is BLSApkRegistryUnitTests { cheats.assume(0 < numOperators && numOperators <= 50); cheats.assume(quorumBitmap > initializedQuorumBitmap); // mask out quorums that are already initialized - uint192 initializingBitmap = uint192(quorumBitmap.minus(uint256(initializedQuorumBitmap))); + uint192 initializingBitmap = uint192( + quorumBitmap.minus(uint256(initializedQuorumBitmap)) + ); _initializeFuzzedQuorums(initializingBitmap); - bytes memory quorumNumbers = bitmapUtilsWrapper.bitmapToBytesArray(quorumBitmap); + bytes memory quorumNumbers = bitmapUtilsWrapper.bitmapToBytesArray( + quorumBitmap + ); /** * For each operator, randomly proceed with either registering/deregistering an operator @@ -704,11 +880,17 @@ contract BLSApkRegistryUnitTests_quorumApkUpdates is BLSApkRegistryUnitTests { // register and check quorum apk updates BN254.G1Point[] memory quorumApksBefore = _getApks(quorumNumbers); address operator = _selectNewOperator(); - (BN254.G1Point memory operatorPubkey,) = _registerRandomBLSPubkey(operator, randSeed[i]); + (BN254.G1Point memory operatorPubkey, ) = _registerRandomBLSPubkey( + operator, + randSeed[i] + ); _registerOperator(operator, quorumNumbers); BN254.G1Point[] memory quorumApksAfter = _getApks(quorumNumbers); _assertQuorumApkUpdates( - quorumNumbers, quorumApksBefore, quorumApksAfter, operatorPubkey + quorumNumbers, + quorumApksBefore, + quorumApksAfter, + operatorPubkey ); // deregister and check quorum apk updates @@ -718,7 +900,10 @@ contract BLSApkRegistryUnitTests_quorumApkUpdates is BLSApkRegistryUnitTests { _deregisterOperator(operator, quorumNumbers); quorumApksAfter = _getApks(quorumNumbers); _assertQuorumApkUpdates( - quorumNumbers, quorumApksBefore, quorumApksAfter, operatorPubkey.negate() + quorumNumbers, + quorumApksBefore, + quorumApksAfter, + operatorPubkey.negate() ); } } @@ -740,9 +925,13 @@ contract BLSApkRegistryUnitTests_quorumApkUpdates is BLSApkRegistryUnitTests { _initializeFuzzedQuorum(quorumNumber2); } - BN254.G1Point[] memory quorumApksBefore = new BN254.G1Point[](quorumNumbers.length); + BN254.G1Point[] memory quorumApksBefore = new BN254.G1Point[]( + quorumNumbers.length + ); for (uint8 i = 0; i < quorumNumbers.length; i++) { - quorumApksBefore[i] = blsApkRegistry.getApk(uint8(quorumNumbers[i])); + quorumApksBefore[i] = blsApkRegistry.getApk( + uint8(quorumNumbers[i]) + ); } // use harnessed function to directly set the pubkey, bypassing the ordinary checks @@ -753,9 +942,16 @@ contract BLSApkRegistryUnitTests_quorumApkUpdates is BLSApkRegistryUnitTests { //check quorum apk updates for (uint8 i = 0; i < quorumNumbers.length; i++) { - BN254.G1Point memory quorumApkAfter = blsApkRegistry.getApk(uint8(quorumNumbers[i])); + BN254.G1Point memory quorumApkAfter = blsApkRegistry.getApk( + uint8(quorumNumbers[i]) + ); assertEq( - BN254.hashG1Point(BN254.plus(quorumApkAfter, BN254.negate(quorumApksBefore[i]))), + BN254.hashG1Point( + BN254.plus( + quorumApkAfter, + BN254.negate(quorumApksBefore[i]) + ) + ), BN254.hashG1Point(defaultPubKey), "quorum apk not updated correctly" ); @@ -775,7 +971,9 @@ contract BLSApkRegistryUnitTests_quorumApkUpdates is BLSApkRegistryUnitTests { _registerRandomBLSPubkey(defaultOperator, randSeed); _registerOperator(defaultOperator, quorumNumbers); - BN254.G1Point memory quorumApk = blsApkRegistry.getApk(defaultQuorumNumber); + BN254.G1Point memory quorumApk = blsApkRegistry.getApk( + defaultQuorumNumber + ); BN254.G1Point memory negatedQuorumApk = BN254.negate(quorumApk); //register for one quorum with negative quorum apk @@ -800,8 +998,8 @@ contract BLSApkRegistryUnitTests_quorumApkUpdates is BLSApkRegistryUnitTests { uint256 blockGap, uint256 randSeed ) external { - numRegistrants = bound(numRegistrants, 1, 99); - blockGap = bound(blockGap, 0, 99); + numRegistrants = bound(numRegistrants, 1, 100); + blockGap = bound(blockGap, 0, 100); bytes memory quorumNumbers = new bytes(1); quorumNumbers[0] = bytes1(defaultQuorumNumber); @@ -811,18 +1009,23 @@ contract BLSApkRegistryUnitTests_quorumApkUpdates is BLSApkRegistryUnitTests { for (uint256 i = 0; i < numRegistrants; i++) { // generate operator and register them with BLS pubkey address operator = _selectNewOperator(); - (BN254.G1Point memory operatorPubkey,) = _registerRandomBLSPubkey( - operator, uint256(keccak256(abi.encodePacked(operator, randSeed))) + (BN254.G1Point memory operatorPubkey, ) = _registerRandomBLSPubkey( + operator, + uint256(keccak256(abi.encodePacked(operator, randSeed))) ); _registerOperator(operator, quorumNumbers); quorumApk = quorumApk.plus(operatorPubkey); quorumApkHash = bytes24(BN254.hashG1Point(quorumApk)); - uint256 historyLength = blsApkRegistry.getApkHistoryLength(defaultQuorumNumber); + uint256 historyLength = blsApkRegistry.getApkHistoryLength( + defaultQuorumNumber + ); assertEq( quorumApkHash, blsApkRegistry.getApkHashAtBlockNumberAndIndex( - defaultQuorumNumber, uint32(block.number + blockGap), historyLength - 1 + defaultQuorumNumber, + uint32(block.number + blockGap), + historyLength - 1 ), "incorrect quorum apk update" ); @@ -831,11 +1034,15 @@ contract BLSApkRegistryUnitTests_quorumApkUpdates is BLSApkRegistryUnitTests { _deregisterOperator(operator, quorumNumbers); quorumApk = quorumApk.plus(operatorPubkey.negate()); quorumApkHash = bytes24(BN254.hashG1Point(quorumApk)); - historyLength = blsApkRegistry.getApkHistoryLength(defaultQuorumNumber); + historyLength = blsApkRegistry.getApkHistoryLength( + defaultQuorumNumber + ); assertEq( quorumApkHash, blsApkRegistry.getApkHashAtBlockNumberAndIndex( - defaultQuorumNumber, uint32(block.number + blockGap), historyLength - 1 + defaultQuorumNumber, + uint32(block.number + blockGap), + historyLength - 1 ), "incorrect quorum apk update" ); @@ -850,12 +1057,12 @@ contract BLSApkRegistryUnitTests_quorumApkUpdates is BLSApkRegistryUnitTests { * and checking the correct revert messages are emitted for wrong blocknumber inputs */ function testFuzz_quorumApkUpdates_IncorrectBlockNumber( - uint32 numRegistrants, + uint256 numRegistrants, uint32 indexToCheck, uint32 wrongBlockNumber, uint256 randSeed ) external { - numRegistrants = uint32(bound(numRegistrants, 1, 99)); + numRegistrants = bound(numRegistrants, 1, 100); cheats.assume(indexToCheck < numRegistrants - 1); bytes memory quorumNumbers = new bytes(1); quorumNumbers[0] = bytes1(defaultQuorumNumber); @@ -865,25 +1072,34 @@ contract BLSApkRegistryUnitTests_quorumApkUpdates is BLSApkRegistryUnitTests { for (uint256 i = 0; i < numRegistrants; i++) { address operator = _selectNewOperator(); _registerRandomBLSPubkey( - operator, uint256(keccak256(abi.encodePacked(operator, randSeed))) + operator, + uint256(keccak256(abi.encodePacked(operator, randSeed))) ); _registerOperator(operator, quorumNumbers); cheats.roll(block.number + 100); } if (wrongBlockNumber < startingBlockNumber + indexToCheck * 100) { emit log_named_uint("index too recent: ", indexToCheck); - cheats.expectRevert("BLSApkRegistry._validateApkHashAtBlockNumber: index too recent"); + cheats.expectRevert( + "BLSApkRegistry._validateApkHashAtBlockNumber: index too recent" + ); blsApkRegistry.getApkHashAtBlockNumberAndIndex( - defaultQuorumNumber, wrongBlockNumber, indexToCheck + defaultQuorumNumber, + wrongBlockNumber, + indexToCheck ); } - if (wrongBlockNumber >= startingBlockNumber + (indexToCheck + 1) * 100) { + if ( + wrongBlockNumber >= startingBlockNumber + (indexToCheck + 1) * 100 + ) { emit log_named_uint("index not latest: ", indexToCheck); cheats.expectRevert( "BLSApkRegistry._validateApkHashAtBlockNumber: not latest apk update" ); blsApkRegistry.getApkHashAtBlockNumberAndIndex( - defaultQuorumNumber, wrongBlockNumber, indexToCheck + defaultQuorumNumber, + wrongBlockNumber, + indexToCheck ); } } @@ -909,7 +1125,9 @@ contract BLSApkRegistryUnitTests_quorumApkUpdates is BLSApkRegistryUnitTests { BN254.G1Point[] memory quorumApksBefore = new BN254.G1Point[](2); for (uint8 i = 0; i < quorumNumbers.length; i++) { - quorumApksBefore[i] = blsApkRegistry.getApk(uint8(quorumNumbers[i])); + quorumApksBefore[i] = blsApkRegistry.getApk( + uint8(quorumNumbers[i]) + ); } cheats.startPrank(address(registryCoordinator)); @@ -919,9 +1137,13 @@ contract BLSApkRegistryUnitTests_quorumApkUpdates is BLSApkRegistryUnitTests { BN254.G1Point memory quorumApkAfter; for (uint8 i = 0; i < quorumNumbers.length; i++) { quorumApkAfter = blsApkRegistry.getApk(uint8(quorumNumbers[i])); - BN254.G1Point memory quorumApk = blsApkRegistry.getApk(defaultQuorumNumber); + BN254.G1Point memory quorumApk = blsApkRegistry.getApk( + defaultQuorumNumber + ); assertEq( - BN254.hashG1Point(quorumApksBefore[i].plus(defaultPubKey.negate())), + BN254.hashG1Point( + quorumApksBefore[i].plus(defaultPubKey.negate()) + ), BN254.hashG1Point(quorumApkAfter), "quorum apk not updated correctly" ); 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/BitmapUtils.t.sol b/test/unit/BitmapUtils.t.sol index 9de0df75..fd51298d 100644 --- a/test/unit/BitmapUtils.t.sol +++ b/test/unit/BitmapUtils.t.sol @@ -7,7 +7,7 @@ import "../harnesses/BitmapUtilsWrapper.sol"; import "forge-std/Test.sol"; contract BitmapUtilsUnitTests is Test { - Vm cheats = Vm(HEVM_ADDRESS); + Vm cheats = Vm(VM_ADDRESS); BitmapUtilsWrapper public bitmapUtilsWrapper; 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/ECDSAStakeRegistryEqualWeightUnit.t.sol b/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol index 8775cd55..bc6337c5 100644 --- a/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol +++ b/test/unit/ECDSAStakeRegistryEqualWeightUnit.t.sol @@ -11,40 +11,80 @@ import {ECDSAStakeRegistryEqualWeight} from "../../src/unaudited/examples/ECDSAS contract EqualWeightECDSARegistry is ECDSAStakeRegistrySetup { ECDSAStakeRegistryEqualWeight internal fixedWeightRegistry; + function setUp() public virtual override { super.setUp(); - fixedWeightRegistry = new ECDSAStakeRegistryEqualWeight(IDelegationManager(address(mockDelegationManager))); + fixedWeightRegistry = new ECDSAStakeRegistryEqualWeight( + IDelegationManager(address(mockDelegationManager)) + ); IStrategy mockStrategy = IStrategy(address(0x1234)); Quorum memory quorum = Quorum({strategies: new StrategyParams[](1)}); - quorum.strategies[0] = StrategyParams({strategy: mockStrategy, multiplier: 10000}); - fixedWeightRegistry.initialize(address(mockServiceManager), 100, quorum); + quorum.strategies[0] = StrategyParams({ + strategy: mockStrategy, + multiplier: 10000 + }); + fixedWeightRegistry.initialize( + address(mockServiceManager), + 100, + quorum + ); fixedWeightRegistry.permitOperator(operator1); fixedWeightRegistry.permitOperator(operator2); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; - fixedWeightRegistry.registerOperatorWithSignature(operator1, operatorSignature); - fixedWeightRegistry.registerOperatorWithSignature(operator2, operatorSignature); + vm.prank(operator1); + fixedWeightRegistry.registerOperatorWithSignature( + operatorSignature, + operator1 + ); + vm.prank(operator2); + fixedWeightRegistry.registerOperatorWithSignature( + operatorSignature, + operator2 + ); } function test_FixedStakeUpdates() public { - assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator1), 1); - assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator2), 1); + assertEq( + fixedWeightRegistry.getLastCheckpointOperatorWeight(operator1), + 1 + ); + assertEq( + fixedWeightRegistry.getLastCheckpointOperatorWeight(operator2), + 1 + ); assertEq(fixedWeightRegistry.getLastCheckpointTotalWeight(), 2); vm.roll(block.number + 1); vm.prank(operator1); fixedWeightRegistry.deregisterOperator(); - assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator1), 0); - assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator2), 1); + assertEq( + fixedWeightRegistry.getLastCheckpointOperatorWeight(operator1), + 0 + ); + assertEq( + fixedWeightRegistry.getLastCheckpointOperatorWeight(operator2), + 1 + ); assertEq(fixedWeightRegistry.getLastCheckpointTotalWeight(), 1); vm.roll(block.number + 1); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; - fixedWeightRegistry.registerOperatorWithSignature(operator1, operatorSignature); + vm.prank(operator1); + fixedWeightRegistry.registerOperatorWithSignature( + operatorSignature, + operator1 + ); - assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator1), 1); - assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator2), 1); + assertEq( + fixedWeightRegistry.getLastCheckpointOperatorWeight(operator1), + 1 + ); + assertEq( + fixedWeightRegistry.getLastCheckpointOperatorWeight(operator2), + 1 + ); assertEq(fixedWeightRegistry.getLastCheckpointTotalWeight(), 2); vm.roll(block.number + 1); @@ -53,8 +93,14 @@ contract EqualWeightECDSARegistry is ECDSAStakeRegistrySetup { operators[1] = operator2; fixedWeightRegistry.updateOperators(operators); - assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator1), 1); - assertEq(fixedWeightRegistry.getLastCheckpointOperatorWeight(operator2), 1); + assertEq( + fixedWeightRegistry.getLastCheckpointOperatorWeight(operator1), + 1 + ); + assertEq( + fixedWeightRegistry.getLastCheckpointOperatorWeight(operator2), + 1 + ); assertEq(fixedWeightRegistry.getLastCheckpointTotalWeight(), 2); } } diff --git a/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol b/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol index 57031b29..dffb9174 100644 --- a/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol +++ b/test/unit/ECDSAStakeRegistryPermissionedUnit.t.sol @@ -11,23 +11,41 @@ import {ECDSAStakeRegistryPermissioned} from "../../src/unaudited/examples/ECDSA contract PermissionedECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { ECDSAStakeRegistryPermissioned internal permissionedRegistry; + function setUp() public virtual override { super.setUp(); - permissionedRegistry = new ECDSAStakeRegistryPermissioned(IDelegationManager(address(mockDelegationManager))); + permissionedRegistry = new ECDSAStakeRegistryPermissioned( + IDelegationManager(address(mockDelegationManager)) + ); IStrategy mockStrategy = IStrategy(address(0x1234)); Quorum memory quorum = Quorum({strategies: new StrategyParams[](1)}); - quorum.strategies[0] = StrategyParams({strategy: mockStrategy, multiplier: 10000}); - permissionedRegistry.initialize(address(mockServiceManager), 100, quorum); + quorum.strategies[0] = StrategyParams({ + strategy: mockStrategy, + multiplier: 10000 + }); + permissionedRegistry.initialize( + address(mockServiceManager), + 100, + quorum + ); permissionedRegistry.permitOperator(operator1); permissionedRegistry.permitOperator(operator2); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; - permissionedRegistry.registerOperatorWithSignature(operator1, operatorSignature); - permissionedRegistry.registerOperatorWithSignature(operator2, operatorSignature); + vm.prank(operator1); + permissionedRegistry.registerOperatorWithSignature( + operatorSignature, + operator1 + ); + vm.prank(operator2); + permissionedRegistry.registerOperatorWithSignature( + operatorSignature, + operator1 + ); } function test_RevertsWhen_NotOwner_PermitOperator() public { - address notOwner=address(0xBEEF); + address notOwner = address(0xBEEF); vm.prank(notOwner); vm.expectRevert("Ownable: caller is not the owner"); permissionedRegistry.permitOperator(operator1); @@ -39,14 +57,14 @@ contract PermissionedECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { } function test_RevertsWhen_NotOwner_RevokeOperator() public { - address notOwner=address(0xBEEF); + address notOwner = address(0xBEEF); vm.prank(notOwner); vm.expectRevert("Ownable: caller is not the owner"); permissionedRegistry.revokeOperator(operator1); } function test_When_NotOperator_RevokeOperator() public { - address notOperator=address(0xBEEF); + address notOperator = address(0xBEEF); permissionedRegistry.permitOperator(notOperator); permissionedRegistry.revokeOperator(notOperator); @@ -57,14 +75,14 @@ contract PermissionedECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { } function test_RevertsWhen_NotOwner_EjectOperator() public { - address notOwner=address(0xBEEF); + address notOwner = address(0xBEEF); vm.prank(notOwner); vm.expectRevert("Ownable: caller is not the owner"); permissionedRegistry.ejectOperator(operator1); } function test_RevertsWhen_NotOperator_EjectOperator() public { - address notOperator=address(0xBEEF); + address notOperator = address(0xBEEF); vm.expectRevert(abi.encodeWithSelector(OperatorNotRegistered.selector)); permissionedRegistry.ejectOperator(notOperator); } @@ -77,26 +95,40 @@ contract PermissionedECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { address operator3 = address(0xBEEF); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; - vm.expectRevert(abi.encodeWithSelector(ECDSAStakeRegistryPermissioned.OperatorNotAllowlisted.selector)); - permissionedRegistry.registerOperatorWithSignature(operator3, operatorSignature); - + vm.expectRevert( + abi.encodeWithSelector( + ECDSAStakeRegistryPermissioned.OperatorNotAllowlisted.selector + ) + ); + vm.prank(operator3); + permissionedRegistry.registerOperatorWithSignature( + operatorSignature, + operator3 + ); } function test_WhenAllowlisted_RegisterOperatorWithSig() public { address operator3 = address(0xBEEF); permissionedRegistry.permitOperator(operator3); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; - permissionedRegistry.registerOperatorWithSignature(operator3, operatorSignature); + vm.prank(operator3); + permissionedRegistry.registerOperatorWithSignature( + operatorSignature, + operator3 + ); } function test_DeregisterOperator() public { address operator3 = address(0xBEEF); permissionedRegistry.permitOperator(operator3); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; - permissionedRegistry.registerOperatorWithSignature(operator3, operatorSignature); + vm.prank(operator3); + permissionedRegistry.registerOperatorWithSignature( + operatorSignature, + operator3 + ); vm.prank(operator3); permissionedRegistry.deregisterOperator(); } - } diff --git a/test/unit/ECDSAStakeRegistryUnit.t.sol b/test/unit/ECDSAStakeRegistryUnit.t.sol index 7ffbb7d8..d374144d 100644 --- a/test/unit/ECDSAStakeRegistryUnit.t.sol +++ b/test/unit/ECDSAStakeRegistryUnit.t.sol @@ -10,7 +10,6 @@ import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy import {ECDSAStakeRegistry} from "../../src/unaudited/ECDSAStakeRegistry.sol"; import {ECDSAStakeRegistryEventsAndErrors, Quorum, StrategyParams} from "../../src/interfaces/IECDSAStakeRegistryEventsAndErrors.sol"; - contract MockServiceManager { // solhint-disable-next-line function deregisterOperatorFromAVS(address) external {} @@ -26,9 +25,12 @@ contract MockDelegationManager { return 1000; // Return a dummy value for simplicity } - function getOperatorShares(address, address[] memory strategies) external pure returns (uint256[] memory) { - uint256[] memory response = new uint256[](strategies.length); - for (uint256 i; i < strategies.length; i++){ + function getOperatorShares( + address, + address[] 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 @@ -38,6 +40,7 @@ contract MockDelegationManager { contract ECDSAStakeRegistrySetup is Test, ECDSAStakeRegistryEventsAndErrors { MockDelegationManager public mockDelegationManager; MockServiceManager public mockServiceManager; + ECDSAStakeRegistry public registry; address internal operator1; address internal operator2; uint256 internal operator1Pk; @@ -53,33 +56,36 @@ contract ECDSAStakeRegistrySetup is Test, ECDSAStakeRegistryEventsAndErrors { (operator2, operator2Pk) = makeAddrAndKey("Signer 2"); mockDelegationManager = new MockDelegationManager(); mockServiceManager = new MockServiceManager(); - - } -} - -contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup{ - ECDSAStakeRegistry public registry; - - function setUp() public virtual override { - super.setUp(); IStrategy mockStrategy = IStrategy(address(0x1234)); Quorum memory quorum = Quorum({strategies: new StrategyParams[](1)}); - quorum.strategies[0] = StrategyParams({strategy: mockStrategy, multiplier: 10000}); - registry = new ECDSAStakeRegistry(IDelegationManager(address(mockDelegationManager))); + quorum.strategies[0] = StrategyParams({ + strategy: mockStrategy, + multiplier: 10_000 + }); + registry = new ECDSAStakeRegistry( + IDelegationManager(address(mockDelegationManager)) + ); registry.initialize(address(mockServiceManager), 100, quorum); ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; - registry.registerOperatorWithSignature(operator1, operatorSignature); - registry.registerOperatorWithSignature(operator2, operatorSignature); - + vm.prank(operator1); + registry.registerOperatorWithSignature(operatorSignature, operator1); + vm.prank(operator2); + registry.registerOperatorWithSignature(operatorSignature, operator2); + vm.roll(block.number + 1); } +} -function test_UpdateQuorumConfig() public { +contract ECDSAStakeRegistryTest is ECDSAStakeRegistrySetup { + function test_UpdateQuorumConfig() public { IStrategy mockStrategy = IStrategy(address(420)); Quorum memory oldQuorum = registry.quorum(); Quorum memory newQuorum = Quorum({strategies: new StrategyParams[](1)}); - newQuorum.strategies[0] = StrategyParams({strategy: mockStrategy, multiplier: 10000}); - address[] memory operators = new address[](2); + newQuorum.strategies[0] = StrategyParams({ + strategy: mockStrategy, + multiplier: 10_000 + }); + address[] memory operators = new address[](2); operators[0] = operator1; operators[1] = operator2; @@ -90,25 +96,34 @@ function test_UpdateQuorumConfig() public { } function test_RevertsWhen_InvalidQuorum_UpdateQuourmConfig() public { - Quorum memory invalidQuorum = Quorum({strategies: new StrategyParams[](1)}); + Quorum memory invalidQuorum = Quorum({ + strategies: new StrategyParams[](1) + }); invalidQuorum.strategies[0] = StrategyParams({ /// TODO: Make mock strategy strategy: IStrategy(address(420)), multiplier: 5000 // This should cause the update to revert as it's not the total required }); - address[] memory operators = new address[](2); + address[] memory operators = new address[](2); operators[0] = operator1; operators[1] = operator2; - vm.expectRevert(ECDSAStakeRegistryEventsAndErrors.InvalidQuorum.selector); + vm.expectRevert( + ECDSAStakeRegistryEventsAndErrors.InvalidQuorum.selector + ); registry.updateQuorumConfig(invalidQuorum, operators); } function test_RevertsWhen_NotOwner_UpdateQuorumConfig() public { - Quorum memory validQuorum = Quorum({strategies: new StrategyParams[](1)}); - validQuorum.strategies[0] = StrategyParams({strategy: IStrategy(address(420)), multiplier: 10000}); + Quorum memory validQuorum = Quorum({ + strategies: new StrategyParams[](1) + }); + validQuorum.strategies[0] = StrategyParams({ + strategy: IStrategy(address(420)), + multiplier: 10_000 + }); - address[] memory operators = new address[](2); + address[] memory operators = new address[](2); operators[0] = operator1; operators[1] = operator2; @@ -121,7 +136,7 @@ function test_UpdateQuorumConfig() public { function test_RevertsWhen_SameQuorum_UpdateQuorumConfig() public { Quorum memory quorum = registry.quorum(); - address[] memory operators = new address[](2); + address[] memory operators = new address[](2); operators[0] = operator1; operators[1] = operator2; @@ -130,58 +145,89 @@ function test_UpdateQuorumConfig() public { } function test_RevertSWhen_Duplicate_UpdateQuorumConfig() public { - Quorum memory validQuorum = Quorum({strategies: new StrategyParams[](2)}); - validQuorum.strategies[0] = StrategyParams({strategy: IStrategy(address(420)), multiplier: 5_000}); - address[] memory operators = new address[](2); + Quorum memory invalidQuorum = Quorum({ + strategies: new StrategyParams[](2) + }); + invalidQuorum.strategies[0] = StrategyParams({ + strategy: IStrategy(address(420)), + multiplier: 5000 + }); + address[] memory operators = new address[](2); operators[0] = operator1; operators[1] = operator2; - validQuorum.strategies[1] = StrategyParams({strategy: IStrategy(address(420)), multiplier: 5_000}); + invalidQuorum.strategies[1] = StrategyParams({ + strategy: IStrategy(address(420)), + multiplier: 5000 + }); vm.expectRevert(ECDSAStakeRegistryEventsAndErrors.NotSorted.selector); - registry.updateQuorumConfig(validQuorum, operators); + registry.updateQuorumConfig(invalidQuorum, operators); } function test_RevertSWhen_NotSorted_UpdateQuorumConfig() public { - Quorum memory validQuorum = Quorum({strategies: new StrategyParams[](2)}); - validQuorum.strategies[0] = StrategyParams({strategy: IStrategy(address(420)), multiplier: 5_000}); - address[] memory operators = new address[](2); + Quorum memory invalidQuorum = Quorum({ + strategies: new StrategyParams[](2) + }); + invalidQuorum.strategies[0] = StrategyParams({ + strategy: IStrategy(address(420)), + multiplier: 5000 + }); + address[] memory operators = new address[](2); operators[0] = operator1; operators[1] = operator2; - validQuorum.strategies[1] = StrategyParams({strategy: IStrategy(address(419)), multiplier: 5_000}); + invalidQuorum.strategies[1] = StrategyParams({ + strategy: IStrategy(address(419)), + multiplier: 5000 + }); vm.expectRevert(ECDSAStakeRegistryEventsAndErrors.NotSorted.selector); - registry.updateQuorumConfig(validQuorum, operators); + registry.updateQuorumConfig(invalidQuorum, operators); } function test_RevertSWhen_OverMultiplierTotal_UpdateQuorumConfig() public { - Quorum memory validQuorum = Quorum({strategies: new StrategyParams[](1)}); - validQuorum.strategies[0] = StrategyParams({strategy: IStrategy(address(420)), multiplier: 10001}); - address[] memory operators = new address[](2); + Quorum memory invalidQuorum = Quorum({ + strategies: new StrategyParams[](1) + }); + invalidQuorum.strategies[0] = StrategyParams({ + strategy: IStrategy(address(420)), + multiplier: 10_001 + }); + address[] memory operators = new address[](2); operators[0] = operator1; operators[1] = operator2; - vm.expectRevert(ECDSAStakeRegistryEventsAndErrors.InvalidQuorum.selector); - registry.updateQuorumConfig(validQuorum,operators); + vm.expectRevert( + ECDSAStakeRegistryEventsAndErrors.InvalidQuorum.selector + ); + registry.updateQuorumConfig(invalidQuorum, operators); } function test_RegisterOperatorWithSignature() public { address operator3 = address(0x125); ISignatureUtils.SignatureWithSaltAndExpiry memory signature; - registry.registerOperatorWithSignature(operator3, signature); + vm.prank(operator3); + registry.registerOperatorWithSignature(signature, operator3); assertTrue(registry.operatorRegistered(operator3)); assertEq(registry.getLastCheckpointOperatorWeight(operator3), 1000); } - function test_RevertsWhen_AlreadyRegistered_RegisterOperatorWithSignature() public { + function test_RevertsWhen_AlreadyRegistered_RegisterOperatorWithSignature() + public + { assertEq(registry.getLastCheckpointOperatorWeight(operator1), 1000); assertEq(registry.getLastCheckpointTotalWeight(), 2000); ISignatureUtils.SignatureWithSaltAndExpiry memory signature; - vm.expectRevert(ECDSAStakeRegistryEventsAndErrors.OperatorAlreadyRegistered.selector); - registry.registerOperatorWithSignature(operator1, signature); + vm.expectRevert( + ECDSAStakeRegistryEventsAndErrors.OperatorAlreadyRegistered.selector + ); + vm.prank(operator1); + registry.registerOperatorWithSignature(signature, operator1); } - function test_RevertsWhen_SignatureIsInvalid_RegisterOperatorWithSignature() public { + function test_RevertsWhen_SignatureIsInvalid_RegisterOperatorWithSignature() + public + { bytes memory signatureData; vm.mockCall( address(mockServiceManager), @@ -212,7 +258,9 @@ function test_UpdateQuorumConfig() public { function test_RevertsWhen_NotOperator_DeregisterOperator() public { address notOperator = address(0x2); vm.prank(notOperator); - vm.expectRevert(ECDSAStakeRegistryEventsAndErrors.OperatorNotRegistered.selector); + vm.expectRevert( + ECDSAStakeRegistryEventsAndErrors.OperatorNotRegistered.selector + ); registry.deregisterOperator(); } @@ -229,7 +277,6 @@ function test_UpdateQuorumConfig() public { operators[2] = operator3; registry.updateOperators(operators); assertEq(registry.getLastCheckpointOperatorWeight(operator3), 0); - } function test_When_SingleOperator_UpdateOperators() public { @@ -237,7 +284,9 @@ function test_UpdateQuorumConfig() public { operators[0] = operator1; registry.updateOperators(operators); - uint256 updatedWeight = registry.getLastCheckpointOperatorWeight(operator1); + uint256 updatedWeight = registry.getLastCheckpointOperatorWeight( + operator1 + ); assertEq(updatedWeight, 1000); } @@ -269,8 +318,12 @@ function test_UpdateQuorumConfig() public { registry.updateOperators(operators); - uint256 updatedWeight1 = registry.getLastCheckpointOperatorWeight(operator1); - uint256 updatedWeight2 = registry.getLastCheckpointOperatorWeight(operator2); + uint256 updatedWeight1 = registry.getLastCheckpointOperatorWeight( + operator1 + ); + uint256 updatedWeight2 = registry.getLastCheckpointOperatorWeight( + operator2 + ); assertEq(updatedWeight1, 1000); assertEq(updatedWeight2, 1000); } @@ -282,7 +335,9 @@ function test_UpdateQuorumConfig() public { registry.updateOperators(operators); - uint256 updatedWeight = registry.getLastCheckpointOperatorWeight(operator1); + uint256 updatedWeight = registry.getLastCheckpointOperatorWeight( + operator1 + ); assertEq(updatedWeight, 1000); } @@ -291,8 +346,14 @@ function test_UpdateQuorumConfig() public { IStrategy mockStrategy2 = IStrategy(address(421)); Quorum memory quorum = Quorum({strategies: new StrategyParams[](2)}); - quorum.strategies[0] = StrategyParams({strategy: mockStrategy, multiplier: 5_000}); - quorum.strategies[1] = StrategyParams({strategy: mockStrategy2, multiplier: 5_000}); + quorum.strategies[0] = StrategyParams({ + strategy: mockStrategy, + multiplier: 5000 + }); + quorum.strategies[1] = StrategyParams({ + strategy: mockStrategy2, + multiplier: 5000 + }); address[] memory operators = new address[](2); operators[0] = operator1; @@ -302,20 +363,28 @@ function test_UpdateQuorumConfig() public { address[] memory strategies = new address[](2); uint256[] memory shares = new uint256[](2); - strategies[0]=address(mockStrategy); - strategies[1]=address(mockStrategy2); + strategies[0] = address(mockStrategy); + strategies[1] = address(mockStrategy2); shares[0] = 50; shares[1] = 1000; vm.mockCall( address(mockDelegationManager), - abi.encodeWithSelector(MockDelegationManager.getOperatorShares.selector, operator1, strategies), + abi.encodeWithSelector( + MockDelegationManager.getOperatorShares.selector, + operator1, + strategies + ), abi.encode(shares) ); registry.updateOperators(operators); - uint256 updatedWeight1 = registry.getLastCheckpointOperatorWeight(operator1); - uint256 updatedWeight2 = registry.getLastCheckpointOperatorWeight(operator2); + uint256 updatedWeight1 = registry.getLastCheckpointOperatorWeight( + operator1 + ); + uint256 updatedWeight2 = registry.getLastCheckpointOperatorWeight( + operator2 + ); assertEq(updatedWeight1, 525); assertEq(updatedWeight2, 1000); vm.roll(block.number + 1); @@ -327,7 +396,7 @@ function test_UpdateQuorumConfig() public { assertEq(initialMinimumWeight, 0); // Assuming initial state is 0 - address[] memory operators = new address[](2); + address[] memory operators = new address[](2); operators[0] = operator1; operators[1] = operator2; registry.updateMinimumWeight(newMinimumWeight, operators); @@ -338,7 +407,7 @@ function test_UpdateQuorumConfig() public { function test_RevertsWhen_NotOwner_UpdateMinimumWeight() public { uint256 newMinimumWeight = 5000; - address[] memory operators = new address[](2); + address[] memory operators = new address[](2); operators[0] = operator1; operators[1] = operator2; vm.prank(address(0xBEEF)); // An arbitrary non-owner address @@ -348,7 +417,7 @@ function test_UpdateQuorumConfig() public { function test_When_SameWeight_UpdateMinimumWeight() public { uint256 initialMinimumWeight = 5000; - address[] memory operators = new address[](2); + address[] memory operators = new address[](2); operators[0] = operator1; operators[1] = operator2; registry.updateMinimumWeight(initialMinimumWeight, operators); @@ -359,7 +428,7 @@ function test_UpdateQuorumConfig() public { function test_When_Weight0_UpdateMinimumWeight() public { uint256 initialMinimumWeight = 5000; - address[] memory operators = new address[](2); + address[] memory operators = new address[](2); operators[0] = operator1; operators[1] = operator2; registry.updateMinimumWeight(initialMinimumWeight, operators); @@ -371,19 +440,21 @@ function test_UpdateQuorumConfig() public { uint256 updatedMinimumWeight = registry.minimumWeight(); assertEq(updatedMinimumWeight, newMinimumWeight); } + function testUpdateThresholdStake_UpdateThresholdStake() public { - uint256 thresholdWeight = 10000000000; + uint256 thresholdWeight = 10_000_000_000; vm.prank(registry.owner()); registry.updateStakeThreshold(thresholdWeight); } function test_RevertsWhen_NotOwner_UpdateThresholdStake() public { - uint256 thresholdWeight = 10000000000; + uint256 thresholdWeight = 10_000_000_000; address notOwner = address(0x123); vm.prank(notOwner); vm.expectRevert("Ownable: caller is not the owner"); registry.updateStakeThreshold(thresholdWeight); } + function test_CheckSignatures() public { msgHash = keccak256("data"); signers = new address[](2); @@ -394,7 +465,10 @@ function test_UpdateQuorumConfig() public { (v, r, s) = vm.sign(operator2Pk, msgHash); signatures[1] = abi.encodePacked(r, s, v); - registry.isValidSignature(msgHash, abi.encode(signers, signatures, type(uint32).max)); + registry.isValidSignature( + msgHash, + abi.encode(signers, signatures, block.number - 1) + ); } function test_RevertsWhen_LengthMismatch_CheckSignatures() public { @@ -405,8 +479,13 @@ function test_UpdateQuorumConfig() public { (uint8 v, bytes32 r, bytes32 s) = vm.sign(operator1Pk, msgHash); signatures[0] = abi.encode(v, r, s); - vm.expectRevert(ECDSAStakeRegistryEventsAndErrors.LengthMismatch.selector); - registry.isValidSignature(msgHash, abi.encode(signers, signatures, type(uint32).max)); + vm.expectRevert( + ECDSAStakeRegistryEventsAndErrors.LengthMismatch.selector + ); + registry.isValidSignature( + msgHash, + abi.encode(signers, signatures, block.number - 1) + ); } function test_RevertsWhen_InvalidLength_CheckSignatures() public { @@ -414,8 +493,13 @@ function test_UpdateQuorumConfig() public { address[] memory signers = new address[](0); bytes[] memory signatures = new bytes[](0); - vm.expectRevert(ECDSAStakeRegistryEventsAndErrors.InvalidLength.selector); - registry.isValidSignature(dataHash, abi.encode(signers, signatures, type(uint32).max)); + vm.expectRevert( + ECDSAStakeRegistryEventsAndErrors.InvalidLength.selector + ); + registry.isValidSignature( + dataHash, + abi.encode(signers, signatures, block.number - 1) + ); } function test_RevertsWhen_NotSorted_CheckSignatures() public { @@ -430,14 +514,17 @@ function test_UpdateQuorumConfig() public { signatures[0] = abi.encodePacked(r, s, v); vm.expectRevert(ECDSAStakeRegistryEventsAndErrors.NotSorted.selector); - registry.isValidSignature(msgHash, abi.encode(signers, signatures, type(uint32).max)); + registry.isValidSignature( + msgHash, + abi.encode(signers, signatures, block.number - 1) + ); } function test_RevertsWhen_Duplicates_CheckSignatures() public { msgHash = keccak256("data"); signers = new address[](2); - signers[1]=operator1; - signers[0]=operator1; + signers[1] = operator1; + signers[0] = operator1; /// Duplicate assertEq(signers[0], signers[1]); @@ -448,7 +535,10 @@ function test_UpdateQuorumConfig() public { signatures[1] = abi.encodePacked(r, s, v); vm.expectRevert(ECDSAStakeRegistryEventsAndErrors.NotSorted.selector); - registry.isValidSignature(msgHash, abi.encode(signers, signatures, type(uint32).max)); + registry.isValidSignature( + msgHash, + abi.encode(signers, signatures, block.number - 1) + ); } function test_RevetsWhen_InvalidSignature_CheckSignatures() public { @@ -458,8 +548,13 @@ function test_UpdateQuorumConfig() public { bytes[] memory signatures = new bytes[](1); signatures[0] = "invalid-signature"; - vm.expectRevert(ECDSAStakeRegistryEventsAndErrors.InvalidSignature.selector); - registry.isValidSignature(dataHash, abi.encode(signers, signatures, type(uint32).max)); + vm.expectRevert( + ECDSAStakeRegistryEventsAndErrors.InvalidSignature.selector + ); + registry.isValidSignature( + dataHash, + abi.encode(signers, signatures, block.number - 1) + ); } function test_RevertsWhen_InsufficientSignedStake_CheckSignatures() public { @@ -473,19 +568,27 @@ function test_UpdateQuorumConfig() public { (v, r, s) = vm.sign(operator2Pk, msgHash); signatures[1] = abi.encodePacked(r, s, v); - uint256 thresholdWeight = 10000000000; + uint256 thresholdWeight = 10_000_000_000; vm.prank(registry.owner()); registry.updateStakeThreshold(thresholdWeight); vm.roll(block.number + 1); vm.mockCall( address(registry), - abi.encodeWithSelector(ECDSAStakeRegistry.getLastCheckpointOperatorWeight.selector, operator1), + abi.encodeWithSelector( + ECDSAStakeRegistry.getLastCheckpointOperatorWeight.selector, + operator1 + ), abi.encode(50) ); - vm.expectRevert(ECDSAStakeRegistryEventsAndErrors.InsufficientSignedStake.selector); - registry.isValidSignature(msgHash, abi.encode(signers, signatures, type(uint32).max)); + vm.expectRevert( + ECDSAStakeRegistryEventsAndErrors.InsufficientSignedStake.selector + ); + registry.isValidSignature( + msgHash, + abi.encode(signers, signatures, block.number - 1) + ); } function test_RevertsWhen_LengthMismatch_CheckSignaturesAtBlock() public { @@ -496,8 +599,13 @@ function test_UpdateQuorumConfig() public { signers[1] = operator2; bytes[] memory signatures = new bytes[](1); - vm.expectRevert(ECDSAStakeRegistryEventsAndErrors.LengthMismatch.selector); - registry.isValidSignature(dataHash, abi.encode(signers, signatures, referenceBlock)); + vm.expectRevert( + ECDSAStakeRegistryEventsAndErrors.LengthMismatch.selector + ); + registry.isValidSignature( + dataHash, + abi.encode(signers, signatures, referenceBlock) + ); } function test_RevertsWhen_InvalidLength_CheckSignaturesAtBlock() public { @@ -506,8 +614,13 @@ function test_UpdateQuorumConfig() public { address[] memory signers = new address[](0); bytes[] memory signatures = new bytes[](0); - vm.expectRevert(ECDSAStakeRegistryEventsAndErrors.InvalidLength.selector); - registry.isValidSignature(dataHash, abi.encode(signers, signatures, referenceBlock)); + vm.expectRevert( + ECDSAStakeRegistryEventsAndErrors.InvalidLength.selector + ); + registry.isValidSignature( + dataHash, + abi.encode(signers, signatures, referenceBlock) + ); } function test_RevertsWhen_NotSorted_CheckSignaturesAtBlock() public { @@ -524,10 +637,15 @@ function test_UpdateQuorumConfig() public { signatures[0] = abi.encodePacked(r, s, v); vm.expectRevert(ECDSAStakeRegistryEventsAndErrors.NotSorted.selector); - registry.isValidSignature(msgHash, abi.encode(signers, signatures, referenceBlock)); + registry.isValidSignature( + msgHash, + abi.encode(signers, signatures, referenceBlock) + ); } - function test_RevetsWhen_InsufficientSignedStake_CheckSignaturesAtBlock() public { + function test_RevetsWhen_InsufficientSignedStake_CheckSignaturesAtBlock() + public + { uint32 referenceBlock = 123; msgHash = keccak256("data"); signers = new address[](2); @@ -539,19 +657,28 @@ function test_UpdateQuorumConfig() public { (v, r, s) = vm.sign(operator2Pk, msgHash); signatures[1] = abi.encodePacked(r, s, v); - uint256 thresholdWeight = 10000000000; + uint256 thresholdWeight = 10_000_000_000; vm.prank(registry.owner()); registry.updateStakeThreshold(thresholdWeight); vm.roll(referenceBlock + 1); vm.mockCall( address(registry), - abi.encodeWithSelector(ECDSAStakeRegistry.getOperatorWeightAtBlock.selector, operator1, referenceBlock), + abi.encodeWithSelector( + ECDSAStakeRegistry.getOperatorWeightAtBlock.selector, + operator1, + referenceBlock + ), abi.encode(50) ); - vm.expectRevert(ECDSAStakeRegistryEventsAndErrors.InsufficientSignedStake.selector); - registry.isValidSignature(msgHash, abi.encode(signers, signatures, referenceBlock)); + vm.expectRevert( + ECDSAStakeRegistryEventsAndErrors.InsufficientSignedStake.selector + ); + registry.isValidSignature( + msgHash, + abi.encode(signers, signatures, referenceBlock) + ); } function test_Gas_UpdateOperators() public { @@ -564,14 +691,18 @@ function test_UpdateQuorumConfig() public { ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; address[] memory operators = new address[](30); - for (uint256 i; i< operators.length;i++) { - operators[i]=address(uint160(i)); - registry.registerOperatorWithSignature(operators[i], operatorSignature); + for (uint256 i; i < operators.length; i++) { + operators[i] = address(uint160(i)); + vm.prank(operators[i]); + registry.registerOperatorWithSignature( + operatorSignature, + operators[i] + ); } vm.resumeGasMetering(); registry.updateOperators(operators); - emit log_named_uint("Gas consumed",before - gasleft()); + emit log_named_uint("Gas consumed", before - gasleft()); } function test_Gas_CheckSignatures() public { @@ -589,23 +720,259 @@ function test_UpdateQuorumConfig() public { uint8 v; bytes32 r; bytes32 s; - for (uint256 i=1; i< operators.length+1;i++) { - operators[i-1]=address(vm.addr(i)); - registry.registerOperatorWithSignature(operators[i-1], operatorSignature); + for (uint256 i = 1; i < operators.length + 1; i++) { + operators[i - 1] = address(vm.addr(i)); + vm.prank(operators[i - 1]); + registry.registerOperatorWithSignature( + operatorSignature, + operators[i - 1] + ); (v, r, s) = vm.sign(i, msgHash); - signatures[i-1] = abi.encodePacked(r, s, v); + signatures[i - 1] = abi.encodePacked(r, s, v); } (operators, signatures) = _sort(operators, signatures); registry.updateOperators(operators); + vm.roll(block.number + 1); vm.resumeGasMetering(); - registry.isValidSignature(msgHash, abi.encode(operators, signatures, type(uint32).max)); - emit log_named_uint("Gas consumed",before - gasleft()); + registry.isValidSignature( + msgHash, + abi.encode(operators, signatures, block.number - 1) + ); + + emit log_named_uint("Gas consumed", before - gasleft()); + } + + // Define private and public keys for operator3 and signer + uint256 private operator3Pk = 3; + address private operator3 = address(vm.addr(operator3Pk)); + uint256 private signerPk = 4; + address private signer = address(vm.addr(signerPk)); + + function test_WhenUsingSigningKey_RegierOperatorWithSignature() public { + address operator = operator3; + + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; + + // Register operator with a different signing key + vm.prank(operator); + registry.registerOperatorWithSignature(operatorSignature, signer); + + // Verify that the signing key has been successfully registered for the operator + address registeredSigningKey = registry.getLastestOperatorSigningKey( + operator + ); + assertEq( + registeredSigningKey, + signer, + "The registered signing key does not match the provided signing key" + ); + } + + function test_Twice_RegierOperatorWithSignature() public { + address operator = operator3; + + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; + + // Register operator with a different signing key + vm.prank(operator); + registry.registerOperatorWithSignature(operatorSignature, signer); + + /// Register a second time + vm.prank(operator); + registry.updateOperatorSigningKey(address(420)); + + // Verify that the signing key has been successfully registered for the operator + address registeredSigningKey = registry.getLastestOperatorSigningKey( + operator + ); + + vm.roll(block.number + 1); + registeredSigningKey = registry.getOperatorSigningKeyAtBlock( + operator, + uint32(block.number - 1) + ); + assertEq( + registeredSigningKey, + address(420), + "The registered signing key does not match the provided signing key" + ); + } + + function test_WhenUsingSigningKey_CheckSignatures() public { + address operator = operator3; + + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; + + // Register operator with a different signing key + vm.prank(operator); + registry.registerOperatorWithSignature(operatorSignature, signer); + vm.roll(block.number + 1); + + // Prepare data for signature + bytes32 dataHash = keccak256("data"); + address[] memory operators = new address[](1); + operators[0] = operator; + bytes[] memory signatures = new bytes[](1); + + // Generate signature using the signing key + (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPk, dataHash); + signatures[0] = abi.encodePacked(r, s, v); + + // Check signatures using the registered signing key + registry.isValidSignature( + dataHash, + abi.encode(operators, signatures, block.number - 1) + ); } - function _sort(address[] memory operators, bytes[] memory signatures) internal pure returns (address[] memory, bytes[] memory) { - require(operators.length == signatures.length, "Operators and signatures length mismatch"); - + function test_WhenUsingSigningKey_CheckSignaturesAtBlock() public { + address operator = operator3; + address initialSigningKey = address(vm.addr(signerPk)); + address updatedSigningKey = address(vm.addr(signerPk + 1)); + + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; + + // Register operator with the initial signing key + vm.prank(operator); + registry.registerOperatorWithSignature( + operatorSignature, + initialSigningKey + ); + vm.roll(block.number + 1); + + // Prepare data for signature with initial signing key + bytes32 dataHash = keccak256("data"); + address[] memory operators = new address[](1); + operators[0] = operator; + bytes[] memory signatures = new bytes[](1); + + // Generate signature using the initial signing key + (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPk, dataHash); + signatures[0] = abi.encodePacked(r, s, v); + + // Check signatures using the initial registered signing key + registry.isValidSignature( + dataHash, + abi.encode(operators, signatures, block.number - 1) + ); + + // Increase block number + vm.roll(block.number + 10); + + // Update operator's signing key + vm.prank(operator); + registry.updateOperatorSigningKey(updatedSigningKey); + vm.roll(block.number + 1); + + // Generate signature using the updated signing key + (v, r, s) = vm.sign(signerPk + 1, dataHash); + signatures[0] = abi.encodePacked(r, s, v); + + // Check signatures using the updated registered signing key + registry.isValidSignature( + dataHash, + abi.encode(operators, signatures, block.number - 1) + ); + } + + function test_WhenUsingPriorSigningKey_CheckSignaturesAtBlock() public { + address operator = operator3; + address initialSigningKey = address(vm.addr(signerPk)); + address updatedSigningKey = address(vm.addr(signerPk + 1)); + + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature; + + // Register operator with the initial signing key + vm.prank(operator); + registry.registerOperatorWithSignature( + operatorSignature, + initialSigningKey + ); + vm.roll(block.number + 1); + + // Prepare data for signature with initial signing key + bytes32 dataHash = keccak256("data"); + address[] memory operators = new address[](1); + operators[0] = operator; + bytes[] memory signatures = new bytes[](1); + + // Generate signature using the initial signing key + (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPk, dataHash); + signatures[0] = abi.encodePacked(r, s, v); + + // Increase block number + vm.roll(block.number + 10); + + // Update operator's signing key + vm.prank(operator); + registry.updateOperatorSigningKey(updatedSigningKey); + + // Check signatures using the initial registered signing key at the previous block + registry.isValidSignature( + dataHash, + abi.encode(operators, signatures, block.number - 10) + ); + } + + function test_RevertsWhen_SigningCurrentBlock_IsValidSignature() public { + address operator = operator1; + address signingKey = address(vm.addr(signerPk)); + bytes32 dataHash = keccak256(abi.encodePacked("test data")); + uint256 currentBlock = block.number; + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPk, dataHash); + bytes memory signature = abi.encodePacked(r, s, v); + address[] memory operators = new address[](1); + operators[0] = operator; + bytes[] memory signatures = new bytes[](1); + signatures[0] = signature; + + vm.expectRevert(abi.encodeWithSignature("InvalidReferenceBlock()")); + registry.isValidSignature( + dataHash, + abi.encode(operators, signatures, currentBlock) + ); + } + + function test_RevertsWhen_SigningKeyNotValidAtBlock_IsValidSignature() + public + { + address operator = operator1; + uint256 invalidSignerPk = signerPk + 1; + address updatedSigningKey = address(vm.addr(invalidSignerPk)); + /// Different key to simulate invalid signing key + bytes32 dataHash = keccak256(abi.encodePacked("test data")); + uint256 referenceBlock = block.number; + /// Past reference block where the signer update won't be valid + vm.roll(block.number + 1); + + vm.prank(operator); + registry.updateOperatorSigningKey(address(updatedSigningKey)); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(invalidSignerPk, dataHash); + bytes memory signature = abi.encodePacked(r, s, v); + address[] memory operators = new address[](1); + operators[0] = operator; + bytes[] memory signatures = new bytes[](1); + signatures[0] = signature; + + vm.expectRevert(abi.encodeWithSignature("InvalidSignature()")); + registry.isValidSignature( + dataHash, + abi.encode(operators, signatures, referenceBlock) + ); + } + + function _sort( + address[] memory operators, + bytes[] memory signatures + ) internal pure returns (address[] memory, bytes[] memory) { + require( + operators.length == signatures.length, + "Operators and signatures length mismatch" + ); + uint256 length = operators.length; for (uint256 i = 0; i < length - 1; i++) { uint256 minIndex = i; diff --git a/test/unit/EjectionManagerUnit.t.sol b/test/unit/EjectionManagerUnit.t.sol new file mode 100644 index 00000000..6ec539fc --- /dev/null +++ b/test/unit/EjectionManagerUnit.t.sol @@ -0,0 +1,448 @@ +// SPDX-License-Identifier: BUSL-1.1 + +pragma solidity ^0.8.12; + +import {EjectionManager} from "../../src/EjectionManager.sol"; +import {IEjectionManager} from "../../src/interfaces/IEjectionManager.sol"; + +import "../utils/MockAVSDeployer.sol"; + +contract EjectionManagerUnitTests is MockAVSDeployer { + + event EjectorUpdated(address ejector, bool status); + event QuorumEjectionParamsSet(uint8 quorumNumber, uint32 rateLimitWindow, uint16 ejectableStakePercent); + event OperatorEjected(bytes32 operatorId, uint8 quorumNumber); + event FailedOperatorEjection(bytes32 operatorId, uint8 quorumNumber, bytes err); + + EjectionManager public ejectionManager; + IEjectionManager public ejectionManagerImplementation; + + IEjectionManager.QuorumEjectionParams[] public quorumEjectionParams; + + uint32 public ratelimitWindow = 1 days; + uint16 public ejectableStakePercent = 1000; + + function setUp() virtual public { + for(uint8 i = 0; i < numQuorums; i++) { + quorumEjectionParams.push(IEjectionManager.QuorumEjectionParams({ + rateLimitWindow: ratelimitWindow, + ejectableStakePercent: ejectableStakePercent + })); + } + + defaultMaxOperatorCount = 200; + _deployMockEigenLayerAndAVS(); + + ejectionManager = EjectionManager(address( + new TransparentUpgradeableProxy( + address(emptyContract), + address(proxyAdmin), + "" + ) + )); + + ejectionManagerImplementation = new EjectionManager(registryCoordinator, stakeRegistry); + + address[] memory ejectors = new address[](1); + ejectors[0] = ejector; + + cheats.prank(proxyAdminOwner); + proxyAdmin.upgradeAndCall( + TransparentUpgradeableProxy(payable(address(ejectionManager))), + address(ejectionManagerImplementation), + abi.encodeWithSelector( + EjectionManager.initialize.selector, + registryCoordinatorOwner, + ejectors, + quorumEjectionParams + ) + ); + + cheats.prank(registryCoordinatorOwner); + registryCoordinator.setEjector(address(ejectionManager)); + + cheats.warp(block.timestamp + ratelimitWindow); + } + + function testEjectOperators_OneOperatorInsideRatelimit() public { + uint8 operatorsToEject = 1; + 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)); + } + } + + assertEq(uint8(registryCoordinator.getOperatorStatus(defaultOperator)), uint8(IRegistryCoordinator.OperatorStatus.REGISTERED)); + + for(uint8 i = 0; i < numQuorums; i++) { + for(uint8 j = 0; j < operatorsToEject; j++) { + cheats.expectEmit(true, true, true, true, address(ejectionManager)); + emit OperatorEjected(operatorIds[i][j], i); + } + } + + cheats.prank(ejector); + ejectionManager.ejectOperators(operatorIds); + + assertEq(uint8(registryCoordinator.getOperatorStatus(defaultOperator)), uint8(IRegistryCoordinator.OperatorStatus.DEREGISTERED)); + } + + function testEjectOperators_MultipleOperatorInsideRatelimit() public { + uint8 operatorsToEject = 10; + uint8 numOperators = 100; + 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 < operatorsToEject; 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 < operatorsToEject; i++) { + assertEq(uint8(registryCoordinator.getOperatorStatus(_incrementAddress(defaultOperator, i))), uint8(IRegistryCoordinator.OperatorStatus.DEREGISTERED)); + } + } + + function testEjectOperators_MultipleOperatorOutsideRatelimit() 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)); + } + } + + 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; + 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 < operatorsToEject; 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 < operatorsToEject; i++) { + assertEq(uint8(registryCoordinator.getOperatorStatus(_incrementAddress(defaultOperator, i))), uint8(IRegistryCoordinator.OperatorStatus.DEREGISTERED)); + } + + cheats.warp(block.timestamp + (ratelimitWindow / 2)); + + 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, operatorsToEject + j)); + } + } + + for(uint8 i = 0; i < operatorsToEject; i++) { + assertEq(uint8(registryCoordinator.getOperatorStatus(_incrementAddress(defaultOperator, operatorsToEject + i))), uint8(IRegistryCoordinator.OperatorStatus.REGISTERED)); + } + + for(uint8 i = 0; i < numQuorums; i++) { + for(uint8 j = 0; j < operatorsToEject; 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 < operatorsToEject; i++) { + assertEq(uint8(registryCoordinator.getOperatorStatus(_incrementAddress(defaultOperator, operatorsToEject + i))), uint8(IRegistryCoordinator.OperatorStatus.DEREGISTERED)); + } + } + + function testEjectOperators_MultipleOperatorAfterRatelimitReset() public { + uint8 operatorsToEject = 10; + uint8 numOperators = 100; + uint96 stake = 1 ether; + + testEjectOperators_MultipleOperatorInsideRatelimit(); + + vm.warp(block.timestamp + 1); + + _registerOperaters(operatorsToEject, stake); + + vm.warp(block.timestamp + ratelimitWindow); + + 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 < operatorsToEject; 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 < operatorsToEject; i++) { + assertEq(uint8(registryCoordinator.getOperatorStatus(_incrementAddress(defaultOperator, i))), uint8(IRegistryCoordinator.OperatorStatus.DEREGISTERED)); + } + } + + function testEjectOperators_NoRatelimitForOwner() public { + uint8 operatorsToEject = 100; + uint8 numOperators = 100; + 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 < operatorsToEject; j++) { + cheats.expectEmit(true, true, true, true, address(ejectionManager)); + emit OperatorEjected(operatorIds[i][j], i); + } + } + + cheats.prank(registryCoordinatorOwner); + ejectionManager.ejectOperators(operatorIds); + + for(uint8 i = 0; i < operatorsToEject; i++) { + assertEq(uint8(registryCoordinator.getOperatorStatus(_incrementAddress(defaultOperator, i))), uint8(IRegistryCoordinator.OperatorStatus.DEREGISTERED)); + } + } + + function testEjectOperators_NoRevertOnMissedEjection() public { + uint8 operatorsToEject = 10; + uint8 numOperators = 100; + 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)); + } + } + + cheats.prank(defaultOperator); + registryCoordinator.deregisterOperator(BitmapUtils.bitmapToBytesArray(MAX_QUORUM_BITMAP)); + + for(uint8 i = 1; i < operatorsToEject; i++) { + assertEq(uint8(registryCoordinator.getOperatorStatus(_incrementAddress(defaultOperator, i))), uint8(IRegistryCoordinator.OperatorStatus.REGISTERED)); + } + + for(uint8 i = 0; i < numQuorums; i++) { + for(uint8 j = 1; j < operatorsToEject; 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 < operatorsToEject; i++) { + assertEq(uint8(registryCoordinator.getOperatorStatus(_incrementAddress(defaultOperator, i))), uint8(IRegistryCoordinator.OperatorStatus.DEREGISTERED)); + } + } + + function testSetQuorumEjectionParams() public { + uint8 quorumNumber = 0; + ratelimitWindow = 2 days; + ejectableStakePercent = 2000; + IEjectionManager.QuorumEjectionParams memory _quorumEjectionParams = IEjectionManager.QuorumEjectionParams({ + rateLimitWindow: ratelimitWindow, + ejectableStakePercent: ejectableStakePercent + }); + + cheats.expectEmit(true, true, true, true, address(ejectionManager)); + emit QuorumEjectionParamsSet(quorumNumber, ratelimitWindow, ejectableStakePercent); + + cheats.prank(registryCoordinatorOwner); + ejectionManager.setQuorumEjectionParams(quorumNumber, _quorumEjectionParams); + + (uint32 setRatelimitWindow, uint16 setEjectableStakePercent) = ejectionManager.quorumEjectionParams(quorumNumber); + assertEq(setRatelimitWindow, _quorumEjectionParams.rateLimitWindow); + assertEq(setEjectableStakePercent, _quorumEjectionParams.ejectableStakePercent); + } + + function testSetEjector() public { + cheats.expectEmit(true, true, true, true, address(ejectionManager)); + emit EjectorUpdated(address(0), true); + + cheats.prank(registryCoordinatorOwner); + ejectionManager.setEjector(address(0), true); + + assertEq(ejectionManager.isEjector(address(0)), true); + } + + function test_Revert_NotPermissioned() public { + bytes32[][] memory operatorIds; + cheats.expectRevert("Ejector: Only owner or ejector can eject"); + ejectionManager.ejectOperators(operatorIds); + + EjectionManager.QuorumEjectionParams memory _quorumEjectionParams; + cheats.expectRevert("Ownable: caller is not the owner"); + ejectionManager.setQuorumEjectionParams(0, _quorumEjectionParams); + + cheats.expectRevert("Ownable: caller is not the owner"); + 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))); + address operator = _incrementAddress(defaultOperator, i); + _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 b906b8be..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,10 +984,66 @@ 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); } + function test_reregisterOperator_revert_reregistrationDelay() public { + uint256 reregistrationDelay = 1 days; + cheats.warp(block.timestamp + reregistrationDelay); + cheats.prank(registryCoordinatorOwner); + registryCoordinator.setEjectionCooldown(reregistrationDelay); + + ISignatureUtils.SignatureWithSaltAndExpiry memory emptySig; + uint32 registrationBlockNumber = 100; + uint32 reregistrationBlockNumber = 200; + + bytes memory quorumNumbers = new bytes(1); + quorumNumbers[0] = bytes1(defaultQuorumNumber); + + _setOperatorWeight(defaultOperator, uint8(quorumNumbers[0]), defaultStake); + + cheats.prank(defaultOperator); + cheats.roll(registrationBlockNumber); + registryCoordinator.registerOperator(quorumNumbers, defaultSocket, pubkeyRegistrationParams, emptySig); + + cheats.prank(ejector); + registryCoordinator.ejectOperator(defaultOperator, quorumNumbers); + + cheats.prank(defaultOperator); + cheats.roll(reregistrationBlockNumber); + cheats.expectRevert("RegCoord._registerOperator: operator cannot reregister yet"); + registryCoordinator.registerOperator(quorumNumbers, defaultSocket, pubkeyRegistrationParams, emptySig); + } + + function test_reregisterOperator_reregistrationDelay() public { + uint256 reregistrationDelay = 1 days; + cheats.warp(block.timestamp + reregistrationDelay); + cheats.prank(registryCoordinatorOwner); + registryCoordinator.setEjectionCooldown(reregistrationDelay); + + ISignatureUtils.SignatureWithSaltAndExpiry memory emptySig; + uint32 registrationBlockNumber = 100; + uint32 reregistrationBlockNumber = 200; + + bytes memory quorumNumbers = new bytes(1); + quorumNumbers[0] = bytes1(defaultQuorumNumber); + + _setOperatorWeight(defaultOperator, uint8(quorumNumbers[0]), defaultStake); + + cheats.prank(defaultOperator); + cheats.roll(registrationBlockNumber); + registryCoordinator.registerOperator(quorumNumbers, defaultSocket, pubkeyRegistrationParams, emptySig); + + cheats.prank(ejector); + registryCoordinator.ejectOperator(defaultOperator, quorumNumbers); + + cheats.prank(defaultOperator); + cheats.roll(reregistrationBlockNumber); + cheats.warp(block.timestamp + reregistrationDelay + 1); + registryCoordinator.registerOperator(quorumNumbers, defaultSocket, pubkeyRegistrationParams, emptySig); + } + // note: this is not possible to test, because there is no route to getting the operator registered for nonexistent quorums // function test_deregisterOperatorExternal_revert_nonexistentQuorums() public { @@ -1157,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); } @@ -1165,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); } @@ -1186,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; @@ -1208,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; @@ -1241,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; @@ -1254,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; @@ -1267,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); } } @@ -1406,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, @@ -1438,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, @@ -1500,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, @@ -1621,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); } @@ -1633,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); } @@ -1654,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); } @@ -1682,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); } @@ -1708,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 new file mode 100644 index 00000000..a7f6464a --- /dev/null +++ b/test/unit/ServiceManagerBase.t.sol @@ -0,0 +1,1082 @@ +// SPDX-License-Identifier: BUSL-1.1 +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 {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 +{ + // RewardsCoordinator config + 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; + uint32 MAX_FUTURE_LENGTH = 28 days; + uint32 GENESIS_REWARDS_TIMESTAMP = 1_712_188_800; + uint256 MAX_REWARDS_AMOUNT = 1e38 - 1; + /// @notice Delay in timestamp before a posted root can be claimed against + uint32 activationDelay = 7 days; + /// @notice the commission for all operators across all avss + uint16 globalCommissionBips = 1000; + + // Testing Config and Mocks + address serviceManagerOwner; + address rewardsInitiator = + address(uint160(uint256(keccak256("rewardsInitiator")))); + IERC20[] rewardTokens; + uint256 mockTokenInitialSupply = 10e50; + IStrategy strategyMock1; + IStrategy strategyMock2; + IStrategy strategyMock3; + StrategyBase strategyImplementation; + IRewardsCoordinator.StrategyAndMultiplier[] defaultStrategyAndMultipliers; + + // mapping to setting fuzzed inputs + mapping(address => bool) public addressIsExcludedFromFuzzedInputs; + + modifier filterFuzzedAddressInputs(address fuzzedAddress) { + cheats.assume(!addressIsExcludedFromFuzzedInputs[fuzzedAddress]); + _; + } + + function setUp() public virtual { + _deployMockEigenLayerAndAVS(); + // Deploy rewards coordinator + rewardsCoordinatorImplementation = new RewardsCoordinator( + delegationMock, + strategyManagerMock, + CALCULATION_INTERVAL_SECONDS, + MAX_REWARDS_DURATION, + MAX_RETROACTIVE_LENGTH, + MAX_FUTURE_LENGTH, + GENESIS_REWARDS_TIMESTAMP + ); + + rewardsCoordinator = RewardsCoordinator( + address( + new TransparentUpgradeableProxy( + address(rewardsCoordinatorImplementation), + address(proxyAdmin), + abi.encodeWithSelector( + RewardsCoordinator.initialize.selector, + msg.sender, + pauserRegistry, + 0 /*initialPausedStatus*/, + rewardsUpdater, + activationDelay, + globalCommissionBips + ) + ) + ) + ); + // Deploy ServiceManager + serviceManagerImplementation = new ServiceManagerMock( + avsDirectory, + rewardsCoordinator, + registryCoordinatorImplementation, + stakeRegistryImplementation + ); + + serviceManager = ServiceManagerMock( + address( + new TransparentUpgradeableProxy( + address(serviceManagerImplementation), + address(proxyAdmin), + abi.encodeWithSelector( + ServiceManagerMock.initialize.selector, + msg.sender, + msg.sender + ) + ) + ) + ); + + serviceManagerOwner = serviceManager.owner(); + cheats.prank(serviceManagerOwner); + serviceManager.setRewardsInitiator(rewardsInitiator); + + _setUpDefaultStrategiesAndMultipliers(); + + cheats.warp(GENESIS_REWARDS_TIMESTAMP + 2 weeks); + + addressIsExcludedFromFuzzedInputs[address(pauserRegistry)] = true; + addressIsExcludedFromFuzzedInputs[address(proxyAdmin)] = true; + } + + /// @notice deploy token to owner and approve ServiceManager. Used for deploying reward tokens + 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 + ); + rewardTokens.push(token); + token.approve(address(serviceManager), mockTokenInitialSupply); + } + cheats.stopPrank(); + } + + function _getBalanceForTokens( + IERC20[] memory tokens, + address holder + ) internal view returns (uint256[] memory) { + uint256[] memory balances = new uint256[](tokens.length); + for (uint256 i = 0; i < tokens.length; ++i) { + balances[i] = tokens[i].balanceOf(holder); + } + return balances; + } + + function _setUpDefaultStrategiesAndMultipliers() internal virtual { + // Deploy Mock Strategies + IERC20 token1 = new ERC20PresetFixedSupply( + "dog wif hat", + "MOCK1", + mockTokenInitialSupply, + address(this) + ); + IERC20 token2 = new ERC20PresetFixedSupply( + "jeo boden", + "MOCK2", + mockTokenInitialSupply, + address(this) + ); + IERC20 token3 = new ERC20PresetFixedSupply( + "pepe wif avs", + "MOCK3", + mockTokenInitialSupply, + address(this) + ); + strategyImplementation = new StrategyBase(strategyManagerMock); + strategyMock1 = StrategyBase( + address( + new TransparentUpgradeableProxy( + address(strategyImplementation), + address(proxyAdmin), + abi.encodeWithSelector( + StrategyBase.initialize.selector, + token1, + pauserRegistry + ) + ) + ) + ); + strategyMock2 = StrategyBase( + address( + new TransparentUpgradeableProxy( + address(strategyImplementation), + address(proxyAdmin), + abi.encodeWithSelector( + StrategyBase.initialize.selector, + token2, + pauserRegistry + ) + ) + ) + ); + strategyMock3 = StrategyBase( + address( + new TransparentUpgradeableProxy( + address(strategyImplementation), + address(proxyAdmin), + abi.encodeWithSelector( + StrategyBase.initialize.selector, + token3, + pauserRegistry + ) + ) + ) + ); + IStrategy[] memory strategies = new IStrategy[](3); + strategies[0] = strategyMock1; + strategies[1] = strategyMock2; + strategies[2] = strategyMock3; + strategies = _sortArrayAsc(strategies); + + strategyManagerMock.setStrategyWhitelist(strategies[0], true); + strategyManagerMock.setStrategyWhitelist(strategies[1], true); + strategyManagerMock.setStrategyWhitelist(strategies[2], true); + + defaultStrategyAndMultipliers.push( + IRewardsCoordinator.StrategyAndMultiplier( + IStrategy(address(strategies[0])), + 1e18 + ) + ); + defaultStrategyAndMultipliers.push( + IRewardsCoordinator.StrategyAndMultiplier( + IStrategy(address(strategies[1])), + 2e18 + ) + ); + defaultStrategyAndMultipliers.push( + 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) { + uint256 l = arr.length; + for (uint256 i = 0; i < l; i++) { + for (uint256 j = i + 1; j < l; j++) { + if (address(arr[i]) > address(arr[j])) { + IStrategy temp = arr[i]; + arr[i] = arr[j]; + arr[j] = temp; + } + } + } + return arr; + } + + 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) { + cheats.assume(caller != rewardsInitiator); + IRewardsCoordinator.RewardsSubmission[] memory rewardsSubmissions; + + cheats.prank(caller); + cheats.expectRevert( + "ServiceManagerBase.onlyRewardsInitiator: caller is not the rewards initiator" + ); + serviceManager.createAVSRewardsSubmission(rewardsSubmissions); + } + + function test_createAVSRewardsSubmission_Revert_WhenERC20NotApproved() + public + { + IERC20 token = new ERC20PresetFixedSupply( + "dog wif hat", + "MOCK1", + mockTokenInitialSupply, + rewardsInitiator + ); + + IRewardsCoordinator.RewardsSubmission[] + memory rewardsSubmissions = new IRewardsCoordinator.RewardsSubmission[]( + 1 + ); + rewardsSubmissions[0] = IRewardsCoordinator.RewardsSubmission({ + strategiesAndMultipliers: defaultStrategyAndMultipliers, + token: token, + amount: 100, + startTimestamp: uint32(block.timestamp), + duration: uint32(1 weeks) + }); + + cheats.prank(rewardsInitiator); + cheats.expectRevert("ERC20: insufficient allowance"); + serviceManager.createAVSRewardsSubmission(rewardsSubmissions); + } + + function test_createAVSRewardsSubmission_SingleSubmission( + uint256 startTimestamp, + uint256 duration, + uint256 amount + ) public { + // 1. Bound fuzz inputs to valid ranges and amounts + IERC20 rewardToken = new ERC20PresetFixedSupply( + "dog wif hat", + "MOCK1", + mockTokenInitialSupply, + rewardsInitiator + ); + amount = bound(amount, 1, MAX_REWARDS_AMOUNT); + 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 + uint256(MAX_FUTURE_LENGTH) + ); + startTimestamp = + startTimestamp - + (startTimestamp % CALCULATION_INTERVAL_SECONDS); + + // 2. Create reward submission input param + IRewardsCoordinator.RewardsSubmission[] + memory rewardsSubmissions = new IRewardsCoordinator.RewardsSubmission[]( + 1 + ); + rewardsSubmissions[0] = IRewardsCoordinator.RewardsSubmission({ + strategiesAndMultipliers: defaultStrategyAndMultipliers, + token: rewardToken, + amount: amount, + startTimestamp: uint32(startTimestamp), + duration: uint32(duration) + }); + + // 3. Approve serviceManager for ERC20 + cheats.startPrank(rewardsInitiator); + rewardToken.approve(address(serviceManager), amount); + + // 4. call createAVSRewardsSubmission() with expected event emitted + 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] + ) + ); + + cheats.expectEmit(true, true, true, true, address(rewardsCoordinator)); + emit AVSRewardsSubmissionCreated( + address(serviceManager), + currSubmissionNonce, + avsSubmissionHash, + rewardsSubmissions[0] + ); + serviceManager.createAVSRewardsSubmission(rewardsSubmissions); + cheats.stopPrank(); + + assertTrue( + rewardsCoordinator.isAVSRewardsSubmissionHash( + address(serviceManager), + avsSubmissionHash + ), + "reward 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 reward submission" + ); + assertEq( + rewardsCoordinatorBalanceBefore + amount, + rewardToken.balanceOf(address(rewardsCoordinator)), + "RewardsCoordinator balance not incremented by amount of reward submission" + ); + } + + function test_createAVSRewardsSubmission_MultipleSubmissions( + uint256 startTimestamp, + uint256 duration, + uint256 amount, + uint256 numSubmissions + ) public { + cheats.assume(2 <= numSubmissions && numSubmissions <= 10); + cheats.prank(rewardsCoordinator.owner()); + + IRewardsCoordinator.RewardsSubmission[] + memory rewardsSubmissions = new IRewardsCoordinator.RewardsSubmission[]( + numSubmissions + ); + bytes32[] memory avsSubmissionHashes = new bytes32[](numSubmissions); + uint256 startSubmissionNonce = rewardsCoordinator.submissionNonce( + address(serviceManager) + ); + _deployMockRewardTokens(rewardsInitiator, numSubmissions); + + 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 + for (uint256 i = 0; i < numSubmissions; ++i) { + // 1. Bound fuzz inputs to valid ranges and amounts using randSeed for each + amount = bound(amount + i, 1, MAX_REWARDS_AMOUNT); + amounts[i] = amount; + duration = bound(duration + i, 0, MAX_REWARDS_DURATION); + duration = duration - (duration % CALCULATION_INTERVAL_SECONDS); + startTimestamp = bound( + startTimestamp + i, + uint256( + _maxTimestamp( + GENESIS_REWARDS_TIMESTAMP, + uint32(block.timestamp) - MAX_RETROACTIVE_LENGTH + ) + ) + + CALCULATION_INTERVAL_SECONDS - + 1, + block.timestamp + uint256(MAX_FUTURE_LENGTH) + ); + 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) + }); + rewardsSubmissions[i] = rewardsSubmission; + + // 3. expected event emitted for this rewardsSubmission + avsSubmissionHashes[i] = keccak256( + abi.encode( + address(serviceManager), + startSubmissionNonce + i, + rewardsSubmissions[i] + ) + ); + cheats.expectEmit( + true, + true, + true, + true, + address(rewardsCoordinator) + ); + emit AVSRewardsSubmissionCreated( + address(serviceManager), + startSubmissionNonce + i, + avsSubmissionHashes[i], + rewardsSubmissions[i] + ); + } + + // 4. call createAVSRewardsSubmission() + cheats.prank(rewardsInitiator); + serviceManager.createAVSRewardsSubmission(rewardsSubmissions); + + // 5. Check for submissionNonce() and avsSubmissionHashes being set + assertEq( + startSubmissionNonce + numSubmissions, + rewardsCoordinator.submissionNonce(address(serviceManager)), + "avs submission nonce not incremented properly" + ); + + for (uint256 i = 0; i < numSubmissions; ++i) { + assertTrue( + rewardsCoordinator.isAVSRewardsSubmissionHash( + address(serviceManager), + avsSubmissionHashes[i] + ), + "rewards submission hash not submitted" + ); + assertEq( + avsBalancesBefore[i] - amounts[i], + rewardTokens[i].balanceOf(rewardsInitiator), + "AVS 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" + ); + } + } + + function test_createAVSRewardsSubmission_MultipleSubmissionsSingleToken( + uint256 startTimestamp, + uint256 duration, + uint256 amount, + uint256 numSubmissions + ) public { + cheats.assume(2 <= numSubmissions && numSubmissions <= 10); + cheats.prank(rewardsCoordinator.owner()); + + IRewardsCoordinator.RewardsSubmission[] + memory rewardsSubmissions = new IRewardsCoordinator.RewardsSubmission[]( + numSubmissions + ); + bytes32[] memory avsSubmissionHashes = new bytes32[](numSubmissions); + uint256 startSubmissionNonce = rewardsCoordinator.submissionNonce( + address(serviceManager) + ); + IERC20 rewardToken = new ERC20PresetFixedSupply( + "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 totalAmount = 0; + + 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 + amount = bound(amount + i, 1, MAX_REWARDS_AMOUNT); + amounts[i] = amount; + totalAmount += amount; + duration = bound(duration + i, 0, MAX_REWARDS_DURATION); + duration = duration - (duration % CALCULATION_INTERVAL_SECONDS); + startTimestamp = bound( + startTimestamp + i, + uint256( + _maxTimestamp( + GENESIS_REWARDS_TIMESTAMP, + uint32(block.timestamp) - MAX_RETROACTIVE_LENGTH + ) + ) + + CALCULATION_INTERVAL_SECONDS - + 1, + block.timestamp + uint256(MAX_FUTURE_LENGTH) + ); + 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) + }); + rewardsSubmissions[i] = rewardsSubmission; + + // 3. expected event emitted for this avs rewards submission + avsSubmissionHashes[i] = keccak256( + abi.encode( + address(serviceManager), + startSubmissionNonce + i, + rewardsSubmissions[i] + ) + ); + cheats.expectEmit( + true, + true, + true, + true, + address(rewardsCoordinator) + ); + emit AVSRewardsSubmissionCreated( + address(serviceManager), + startSubmissionNonce + i, + avsSubmissionHashes[i], + rewardsSubmissions[i] + ); + } + + // 4. call createAVSRewardsSubmission() + cheats.prank(rewardsInitiator); + serviceManager.createAVSRewardsSubmission(rewardsSubmissions); + + // 5. Check for submissionNonce() and avsSubmissionHashes being set + assertEq( + startSubmissionNonce + numSubmissions, + rewardsCoordinator.submissionNonce(address(serviceManager)), + "avs submission nonce not incremented properly" + ); + assertEq( + avsBalanceBefore - totalAmount, + rewardToken.balanceOf(rewardsInitiator), + "AVS balance not decremented by amount of rewards submissions" + ); + assertEq( + rewardsCoordinatorBalanceBefore + totalAmount, + rewardToken.balanceOf(address(rewardsCoordinator)), + "RewardsCoordinator balance not incremented by amount of rewards submissions" + ); + + for (uint256 i = 0; i < numSubmissions; ++i) { + assertTrue( + rewardsCoordinator.isAVSRewardsSubmissionHash( + address(serviceManager), + avsSubmissionHashes[i] + ), + "rewards submission hash not submitted" + ); + } + } + + function test_setRewardsInitiator() public { + address newRewardsInitiator = address( + uint160(uint256(keccak256("newRewardsInitiator"))) + ); + cheats.prank(serviceManagerOwner); + serviceManager.setRewardsInitiator(newRewardsInitiator); + assertEq(newRewardsInitiator, serviceManager.rewardsInitiator()); + } + + function test_setRewardsInitiator_revert_notOwner() public { + address caller = address(uint160(uint256(keccak256("caller")))); + 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/ServiceManagerRouter.t.sol b/test/unit/ServiceManagerRouter.t.sol index 0064fcb1..9fc2c0f7 100644 --- a/test/unit/ServiceManagerRouter.t.sol +++ b/test/unit/ServiceManagerRouter.t.sol @@ -17,6 +17,7 @@ contract ServiceManagerRouter_UnitTests is MockAVSDeployer { // Deploy dummy serviceManager dummyServiceManager = new ServiceManagerMock( avsDirectory, + rewardsCoordinatorImplementation, registryCoordinatorImplementation, stakeRegistryImplementation ); diff --git a/test/unit/SocketRegistryUnit.t.sol b/test/unit/SocketRegistryUnit.t.sol new file mode 100644 index 00000000..af5be131 --- /dev/null +++ b/test/unit/SocketRegistryUnit.t.sol @@ -0,0 +1,27 @@ +// 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"); + } + +} \ 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 4f78f3dc..6db2781f 100644 --- a/test/utils/MockAVSDeployer.sol +++ b/test/utils/MockAVSDeployer.sol @@ -24,7 +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"; @@ -33,6 +33,10 @@ import {DelegationMock} from "../mocks/DelegationMock.sol"; import {AVSDirectory} from "eigenlayer-contracts/src/contracts/core/AVSDirectory.sol"; import {IAVSDirectory} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; +import {RewardsCoordinatorMock} from "../mocks/RewardsCoordinatorMock.sol"; + +import { RewardsCoordinator } from "eigenlayer-contracts/src/contracts/core/RewardsCoordinator.sol"; +import { IRewardsCoordinator } from "eigenlayer-contracts/src/contracts/interfaces/IRewardsCoordinator.sol"; import {BLSApkRegistryHarness} from "../harnesses/BLSApkRegistryHarness.sol"; import {EmptyContract} from "eigenlayer-contracts/src/test/mocks/EmptyContract.sol"; @@ -44,7 +48,7 @@ import "forge-std/Test.sol"; contract MockAVSDeployer is Test { using BN254 for BN254.G1Point; - Vm cheats = Vm(HEVM_ADDRESS); + Vm cheats = Vm(VM_ADDRESS); ProxyAdmin public proxyAdmin; PauserRegistry public pauserRegistry; @@ -58,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; @@ -66,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; @@ -73,12 +79,16 @@ contract MockAVSDeployer is Test { AVSDirectory public avsDirectory; AVSDirectory public avsDirectoryImplementation; AVSDirectoryMock public avsDirectoryMock; + RewardsCoordinator public rewardsCoordinator; + RewardsCoordinator public rewardsCoordinatorImplementation; + RewardsCoordinatorMock public rewardsCoordinatorMock; /// @notice StakeRegistry, Constant used as a divisor in calculating weights. uint256 public constant WEIGHTING_DIVISOR = 1e18; address public proxyAdminOwner = address(uint160(uint256(keccak256("proxyAdminOwner")))); - address public registryCoordinatorOwner = address(uint160(uint256(keccak256("registryCoordinatorOwner")))); + address public registryCoordinatorOwner = + address(uint160(uint256(keccak256("registryCoordinatorOwner")))); address public pauser = address(uint160(uint256(keccak256("pauser")))); address public unpauser = address(uint160(uint256(keccak256("unpauser")))); @@ -90,13 +100,16 @@ contract MockAVSDeployer is Test { address defaultOperator = address(uint160(uint256(keccak256("defaultOperator")))); bytes32 defaultOperatorId; - BN254.G1Point internal defaultPubKey = BN254.G1Point(18260007818883133054078754218619977578772505796600400998181738095793040006897,3432351341799135763167709827653955074218841517684851694584291831827675065899); + BN254.G1Point internal defaultPubKey = BN254.G1Point( + 18_260_007_818_883_133_054_078_754_218_619_977_578_772_505_796_600_400_998_181_738_095_793_040_006_897, + 3_432_351_341_799_135_763_167_709_827_653_955_074_218_841_517_684_851_694_584_291_831_827_675_065_899 + ); string defaultSocket = "69.69.69.69:420"; uint96 defaultStake = 1 ether; uint8 defaultQuorumNumber = 0; uint32 defaultMaxOperatorCount = 10; - uint16 defaultKickBIPsOfOperatorStake = 15000; + uint16 defaultKickBIPsOfOperatorStake = 15_000; uint16 defaultKickBIPsOfTotalStake = 150; uint8 numQuorums = 192; @@ -135,7 +148,6 @@ contract MockAVSDeployer is Test { pausers[0] = pauser; pauserRegistry = new PauserRegistry(pausers, unpauser); - delegationMock = new DelegationMock(); avsDirectoryMock = new AVSDirectoryMock(); eigenPodManagerMock = new EigenPodManagerMock(pauserRegistry); @@ -146,7 +158,12 @@ contract MockAVSDeployer is Test { new TransparentUpgradeableProxy( address(slasherImplementation), address(proxyAdmin), - abi.encodeWithSelector(Slasher.initialize.selector, msg.sender, pauserRegistry, 0/*initialPausedStatus*/) + abi.encodeWithSelector( + Slasher.initialize.selector, + msg.sender, + pauserRegistry, + 0 /*initialPausedStatus*/ + ) ) ) ); @@ -157,64 +174,54 @@ contract MockAVSDeployer is Test { new TransparentUpgradeableProxy( address(avsDirectoryImplementation), address(proxyAdmin), - abi.encodeWithSelector(AVSDirectory.initialize.selector, msg.sender, pauserRegistry, 0/*initialPausedStatus*/) + abi.encodeWithSelector( + AVSDirectory.initialize.selector, + msg.sender, + pauserRegistry, + 0 /*initialPausedStatus*/ + ) ) ) ); + rewardsCoordinatorMock = new RewardsCoordinatorMock(); - strategyManagerMock.setAddresses( - delegationMock, - eigenPodManagerMock, - slasher - ); + strategyManagerMock.setAddresses(delegationMock, eigenPodManagerMock, slasher); cheats.stopPrank(); cheats.startPrank(registryCoordinatorOwner); - registryCoordinator = RegistryCoordinatorHarness(address( - new TransparentUpgradeableProxy( - address(emptyContract), - address(proxyAdmin), - "" + registryCoordinator = RegistryCoordinatorHarness( + address( + new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "") ) - )); + ); stakeRegistry = StakeRegistryHarness( address( - new TransparentUpgradeableProxy( - address(emptyContract), - address(proxyAdmin), - "" - ) + new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "") + ) + ); + + socketRegistry = SocketRegistry( + address( + new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "") ) ); indexRegistry = IndexRegistry( address( - new TransparentUpgradeableProxy( - address(emptyContract), - address(proxyAdmin), - "" - ) + new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "") ) ); blsApkRegistry = BLSApkRegistryHarness( address( - new TransparentUpgradeableProxy( - address(emptyContract), - address(proxyAdmin), - "" - ) + new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "") ) ); serviceManager = ServiceManagerMock( address( - new TransparentUpgradeableProxy( - address(emptyContract), - address(proxyAdmin), - "" - ) + new TransparentUpgradeableProxy(address(emptyContract), address(proxyAdmin), "") ) ); @@ -222,28 +229,29 @@ contract MockAVSDeployer is Test { cheats.startPrank(proxyAdminOwner); - stakeRegistryImplementation = new StakeRegistryHarness( - IRegistryCoordinator(registryCoordinator), - delegationMock - ); + stakeRegistryImplementation = + new StakeRegistryHarness(IRegistryCoordinator(registryCoordinator), delegationMock); proxyAdmin.upgrade( TransparentUpgradeableProxy(payable(address(stakeRegistry))), address(stakeRegistryImplementation) ); - blsApkRegistryImplementation = new BLSApkRegistryHarness( - registryCoordinator + socketRegistryImplementation = new SocketRegistry(registryCoordinator); + + proxyAdmin.upgrade( + TransparentUpgradeableProxy(payable(address(socketRegistry))), + address(socketRegistryImplementation) ); + blsApkRegistryImplementation = new BLSApkRegistryHarness(registryCoordinator); + proxyAdmin.upgrade( TransparentUpgradeableProxy(payable(address(blsApkRegistry))), address(blsApkRegistryImplementation) ); - indexRegistryImplementation = new IndexRegistry( - registryCoordinator - ); + indexRegistryImplementation = new IndexRegistry(registryCoordinator); proxyAdmin.upgrade( TransparentUpgradeableProxy(payable(address(indexRegistry))), @@ -252,6 +260,7 @@ contract MockAVSDeployer is Test { serviceManagerImplementation = new ServiceManagerMock( avsDirectoryMock, + IRewardsCoordinator(address(rewardsCoordinatorMock)), registryCoordinator, stakeRegistry ); @@ -261,7 +270,10 @@ contract MockAVSDeployer is Test { address(serviceManagerImplementation) ); - serviceManager.initialize({initialOwner: registryCoordinatorOwner}); + serviceManager.initialize({ + initialOwner: registryCoordinatorOwner, + rewardsInitiator: address(proxyAdminOwner) + }); // set the public key for an operator, using harnessed function to bypass checks blsApkRegistry.setBLSPublicKey(defaultOperator, defaultPubKey); @@ -269,7 +281,7 @@ contract MockAVSDeployer is Test { // setup the dummy minimum stake for quorum uint96[] memory minimumStakeForQuorum = new uint96[](numQuorumsToAdd); for (uint256 i = 0; i < minimumStakeForQuorum.length; i++) { - minimumStakeForQuorum[i] = uint96(i+1); + minimumStakeForQuorum[i] = uint96(i + 1); } // setup the dummy quorum strategies @@ -278,26 +290,24 @@ contract MockAVSDeployer is Test { for (uint256 i = 0; i < quorumStrategiesConsideredAndMultipliers.length; i++) { quorumStrategiesConsideredAndMultipliers[i] = new IStakeRegistry.StrategyParams[](1); quorumStrategiesConsideredAndMultipliers[i][0] = IStakeRegistry.StrategyParams( - IStrategy(address(uint160(i))), - uint96(WEIGHTING_DIVISOR) + IStrategy(address(uint160(i))), uint96(WEIGHTING_DIVISOR) ); } registryCoordinatorImplementation = new RegistryCoordinatorHarness( - serviceManager, - stakeRegistry, - blsApkRegistry, - indexRegistry + serviceManager, stakeRegistry, blsApkRegistry, indexRegistry, socketRegistry ); { delete operatorSetParams; - for (uint i = 0; i < numQuorumsToAdd; i++) { + for (uint256 i = 0; i < numQuorumsToAdd; i++) { // hard code these for now - operatorSetParams.push(IRegistryCoordinator.OperatorSetParam({ - maxOperatorCount: defaultMaxOperatorCount, - kickBIPsOfOperatorStake: defaultKickBIPsOfOperatorStake, - kickBIPsOfTotalStake: defaultKickBIPsOfTotalStake - })); + operatorSetParams.push( + IRegistryCoordinator.OperatorSetParam({ + maxOperatorCount: defaultMaxOperatorCount, + kickBIPsOfOperatorStake: defaultKickBIPsOfOperatorStake, + kickBIPsOfTotalStake: defaultKickBIPsOfTotalStake + }) + ); } proxyAdmin.upgradeAndCall( @@ -309,7 +319,7 @@ contract MockAVSDeployer is Test { churnApprover, ejector, pauserRegistry, - 0/*initialPausedStatus*/, + 0, /*initialPausedStatus*/ operatorSetParams, minimumStakeForQuorum, quorumStrategiesConsideredAndMultipliers @@ -325,75 +335,101 @@ contract MockAVSDeployer is Test { /** * @notice registers operator with coordinator */ - function _registerOperatorWithCoordinator(address operator, uint256 quorumBitmap, BN254.G1Point memory pubKey) internal { + function _registerOperatorWithCoordinator( + address operator, + uint256 quorumBitmap, + BN254.G1Point memory pubKey + ) internal { _registerOperatorWithCoordinator(operator, quorumBitmap, pubKey, defaultStake); } /** * @notice registers operator with coordinator */ - function _registerOperatorWithCoordinator(address operator, uint256 quorumBitmap, BN254.G1Point memory pubKey, uint96 stake) internal { + function _registerOperatorWithCoordinator( + address operator, + uint256 quorumBitmap, + BN254.G1Point memory pubKey, + uint96 stake + ) internal { // quorumBitmap can only have 192 least significant bits quorumBitmap &= MAX_QUORUM_BITMAP; blsApkRegistry.setBLSPublicKey(operator, pubKey); bytes memory quorumNumbers = BitmapUtils.bitmapToBytesArray(quorumBitmap); - for (uint i = 0; i < quorumNumbers.length; i++) { + for (uint256 i = 0; i < quorumNumbers.length; i++) { _setOperatorWeight(operator, uint8(quorumNumbers[i]), stake); } ISignatureUtils.SignatureWithSaltAndExpiry memory emptySignatureAndExpiry; cheats.prank(operator); - registryCoordinator.registerOperator(quorumNumbers, defaultSocket, pubkeyRegistrationParams, emptySignatureAndExpiry); + registryCoordinator.registerOperator( + quorumNumbers, defaultSocket, pubkeyRegistrationParams, emptySignatureAndExpiry + ); } /** * @notice registers operator with coordinator */ - function _registerOperatorWithCoordinator(address operator, uint256 quorumBitmap, BN254.G1Point memory pubKey, uint96[] memory stakes) internal { + function _registerOperatorWithCoordinator( + address operator, + uint256 quorumBitmap, + BN254.G1Point memory pubKey, + uint96[] memory stakes + ) internal { // quorumBitmap can only have 192 least significant bits quorumBitmap &= MAX_QUORUM_BITMAP; blsApkRegistry.setBLSPublicKey(operator, pubKey); bytes memory quorumNumbers = BitmapUtils.bitmapToBytesArray(quorumBitmap); - for (uint i = 0; i < quorumNumbers.length; i++) { + for (uint256 i = 0; i < quorumNumbers.length; i++) { _setOperatorWeight(operator, uint8(quorumNumbers[i]), stakes[uint8(quorumNumbers[i])]); } ISignatureUtils.SignatureWithSaltAndExpiry memory emptySignatureAndExpiry; cheats.prank(operator); - registryCoordinator.registerOperator(quorumNumbers, defaultSocket, pubkeyRegistrationParams, emptySignatureAndExpiry); + registryCoordinator.registerOperator( + quorumNumbers, defaultSocket, pubkeyRegistrationParams, emptySignatureAndExpiry + ); } - function _registerRandomOperators(uint256 pseudoRandomNumber) internal returns(OperatorMetadata[] memory, uint256[][] memory) { + function _registerRandomOperators(uint256 pseudoRandomNumber) + internal + returns (OperatorMetadata[] memory, uint256[][] memory) + { OperatorMetadata[] memory operatorMetadatas = new OperatorMetadata[](maxOperatorsToRegister); - for (uint i = 0; i < operatorMetadatas.length; i++) { + for (uint256 i = 0; i < operatorMetadatas.length; i++) { // limit to 16 quorums so we don't run out of gas, make them all register for quorum 0 as well - operatorMetadatas[i].quorumBitmap = uint256(keccak256(abi.encodePacked("quorumBitmap", pseudoRandomNumber, i))) & (1 << maxQuorumsToRegisterFor - 1) | 1; + operatorMetadatas[i].quorumBitmap = uint256( + keccak256(abi.encodePacked("quorumBitmap", pseudoRandomNumber, i)) + ) & (1 << maxQuorumsToRegisterFor - 1) | 1; operatorMetadatas[i].operator = _incrementAddress(defaultOperator, i); - operatorMetadatas[i].pubkey = BN254.hashToG1(keccak256(abi.encodePacked("pubkey", pseudoRandomNumber, i))); + operatorMetadatas[i].pubkey = + BN254.hashToG1(keccak256(abi.encodePacked("pubkey", pseudoRandomNumber, i))); operatorMetadatas[i].operatorId = operatorMetadatas[i].pubkey.hashG1Point(); operatorMetadatas[i].stakes = new uint96[](maxQuorumsToRegisterFor); - for (uint j = 0; j < maxQuorumsToRegisterFor; j++) { - operatorMetadatas[i].stakes[j] = uint96(uint64(uint256(keccak256(abi.encodePacked("stakes", pseudoRandomNumber, i, j))))); + for (uint256 j = 0; j < maxQuorumsToRegisterFor; j++) { + operatorMetadatas[i].stakes[j] = uint96( + uint64(uint256(keccak256(abi.encodePacked("stakes", pseudoRandomNumber, i, j)))) + ); } } // get the index in quorumBitmaps of each operator in each quorum in the order they will register uint256[][] memory expectedOperatorOverallIndices = new uint256[][](numQuorums); - for (uint i = 0; i < numQuorums; i++) { + for (uint256 i = 0; i < numQuorums; i++) { uint32 numOperatorsInQuorum; // for each quorumBitmap, check if the i'th bit is set - for (uint j = 0; j < operatorMetadatas.length; j++) { + for (uint256 j = 0; j < operatorMetadatas.length; j++) { if (operatorMetadatas[j].quorumBitmap >> i & 1 == 1) { numOperatorsInQuorum++; } } expectedOperatorOverallIndices[i] = new uint256[](numOperatorsInQuorum); uint256 numOperatorCounter; - for (uint j = 0; j < operatorMetadatas.length; j++) { + for (uint256 j = 0; j < operatorMetadatas.length; j++) { if (operatorMetadatas[j].quorumBitmap >> i & 1 == 1) { expectedOperatorOverallIndices[i][numOperatorCounter] = j; numOperatorCounter++; @@ -402,10 +438,15 @@ contract MockAVSDeployer is Test { } // register operators - for (uint i = 0; i < operatorMetadatas.length; i++) { + for (uint256 i = 0; i < operatorMetadatas.length; i++) { cheats.roll(registrationBlockNumber + blocksBetweenRegistrations * i); - _registerOperatorWithCoordinator(operatorMetadatas[i].operator, operatorMetadatas[i].quorumBitmap, operatorMetadatas[i].pubkey, operatorMetadatas[i].stakes); + _registerOperatorWithCoordinator( + operatorMetadatas[i].operator, + operatorMetadatas[i].quorumBitmap, + operatorMetadatas[i].pubkey, + operatorMetadatas[i].stakes + ); } return (operatorMetadatas, expectedOperatorOverallIndices); @@ -417,7 +458,11 @@ contract MockAVSDeployer is Test { * Returns actual weight calculated set for operator shares in DelegationMock since multiplier and WEIGHTING_DIVISOR calculations * can give small rounding errors. */ - function _setOperatorWeight(address operator, uint8 quorumNumber, uint96 weight) internal returns (uint96) { + function _setOperatorWeight( + address operator, + uint8 quorumNumber, + uint96 weight + ) internal returns (uint96) { // Set StakeRegistry operator weight by setting DelegationManager operator shares (IStrategy strategy, uint96 multiplier) = stakeRegistry.strategyParams(quorumNumber, 0); uint256 actualWeight = ((uint256(weight) * WEIGHTING_DIVISOR) / uint256(multiplier)); @@ -425,21 +470,23 @@ contract MockAVSDeployer is Test { return uint96(actualWeight); } - function _incrementAddress(address start, uint256 inc) internal pure returns(address) { + function _incrementAddress(address start, uint256 inc) internal pure returns (address) { return address(uint160(uint256(uint160(start) + inc))); } - function _incrementBytes32(bytes32 start, uint256 inc) internal pure returns(bytes32) { + function _incrementBytes32(bytes32 start, uint256 inc) internal pure returns (bytes32) { return bytes32(uint256(start) + inc); } - function _signOperatorChurnApproval(address registeringOperator, bytes32 registeringOperatorId, IRegistryCoordinator.OperatorKickParam[] memory operatorKickParams, bytes32 salt, uint256 expiry) internal view returns(ISignatureUtils.SignatureWithSaltAndExpiry memory) { + function _signOperatorChurnApproval( + address registeringOperator, + bytes32 registeringOperatorId, + IRegistryCoordinator.OperatorKickParam[] memory operatorKickParams, + bytes32 salt, + uint256 expiry + ) internal view returns (ISignatureUtils.SignatureWithSaltAndExpiry memory) { bytes32 digestHash = registryCoordinator.calculateOperatorChurnApprovalDigestHash( - registeringOperator, - registeringOperatorId, - operatorKickParams, - salt, - expiry + registeringOperator, registeringOperatorId, operatorKickParams, salt, expiry ); (uint8 v, bytes32 r, bytes32 s) = vm.sign(churnApproverPrivateKey, digestHash); return ISignatureUtils.SignatureWithSaltAndExpiry({