From 071f3ec1d416103331b3b87ae4956ca6a93a846d Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 1 Dec 2025 12:24:39 -0500 Subject: [PATCH 1/4] feat: nonsigner view and operator index --- foundry.lock | 2 +- foundry.toml | 2 +- lib/eigenlayer-contracts | 2 +- src/RegistryCoordinator.sol | 8 +- src/SlashingRegistryCoordinator.sol | 22 +- src/SlashingRegistryCoordinatorStorage.sol | 4 + src/interfaces/IBN254TableCalculator.sol | 37 ++ .../ISlashingRegistryCoordinator.sol | 5 +- .../BN254TableCalculatorBase.sol | 188 +++++-- test/fork/EigenDA.t.sol | 11 +- test/fork/End2End.t.sol | 4 +- test/integration/IntegrationConfig.t.sol | 6 +- test/integration/IntegrationDeployer.t.sol | 70 +-- test/mocks/AllocationManagerMock.sol | 52 ++ test/mocks/KeyRegistrarMock.sol | 23 + test/mocks/PermissionControllerMock.sol | 4 +- test/unit/InstantSlasher.t.sol | 23 +- test/unit/RegistryCoordinatorUnit.t.sol | 12 +- test/unit/ServiceManagerBase.t.sol | 13 +- .../SlashingRegistryCoordinatorUnit.t.sol | 33 +- test/unit/Utils.sol | 2 +- test/unit/VetoableSlasher.t.sol | 10 +- .../AVSRegistrarAsIdentifierUnit.t.sol | 13 +- .../BN254TableCalculatorBaseUnit.t.sol | 464 ++++++++++++++++++ .../BN254TableCalculatorUnit.t.sol | 1 + test/unit/middlewareV2/MockDeployer.sol | 2 +- test/utils/CoreDeployLib.sol | 35 +- test/utils/MockAVSDeployer.sol | 37 +- 28 files changed, 906 insertions(+), 179 deletions(-) diff --git a/foundry.lock b/foundry.lock index e503ae3ca..31cfd8694 100644 --- a/foundry.lock +++ b/foundry.lock @@ -1,6 +1,6 @@ { "lib/eigenlayer-contracts": { - "rev": "31aade2fc3bf6e2c0160cc2e7c7be1a6017296e5" + "rev": "b9c1a6118534d19dafec502849e557ea68dff494" }, "lib/forge-std": { "rev": "77041d2ce690e692d6e03cc812b57d1ddaa4d505" diff --git a/foundry.toml b/foundry.toml index 9d48c79ef..7751ee1c1 100644 --- a/foundry.toml +++ b/foundry.toml @@ -24,7 +24,7 @@ ] # Specifies the exact version of Solidity to use, overriding auto-detection. - solc_version = '0.8.27' + solc_version = '0.8.29' # If enabled, treats Solidity compiler warnings as errors, preventing artifact generation if warnings are present. deny_warnings = false # If set to true, changes compilation pipeline to go through the new IR optimizer. diff --git a/lib/eigenlayer-contracts b/lib/eigenlayer-contracts index 31aade2fc..b9c1a6118 160000 --- a/lib/eigenlayer-contracts +++ b/lib/eigenlayer-contracts @@ -1 +1 @@ -Subproject commit 31aade2fc3bf6e2c0160cc2e7c7be1a6017296e5 +Subproject commit b9c1a6118534d19dafec502849e557ea68dff494 diff --git a/src/RegistryCoordinator.sol b/src/RegistryCoordinator.sol index 2bf5a5f45..bc8033364 100644 --- a/src/RegistryCoordinator.sol +++ b/src/RegistryCoordinator.sol @@ -289,13 +289,7 @@ contract RegistryCoordinator is SlashingRegistryCoordinator, RegistryCoordinator * @notice Returns the version of the contract * @return The version string */ - function version() - public - view - virtual - override(ISemVerMixin, SemVerMixin) - returns (string memory) - { + function version() public view virtual override(SemVerMixin) returns (string memory) { return "v0.0.1"; } } diff --git a/src/SlashingRegistryCoordinator.sol b/src/SlashingRegistryCoordinator.sol index 1b4ad47f7..01c334abb 100644 --- a/src/SlashingRegistryCoordinator.sol +++ b/src/SlashingRegistryCoordinator.sol @@ -125,7 +125,8 @@ contract SlashingRegistryCoordinator is minimumStake, strategyParams, IStakeRegistryTypes.StakeType.TOTAL_DELEGATED, - 0 + 0, + DELEGATED_STAKE_SLASHER ); } @@ -134,14 +135,16 @@ contract SlashingRegistryCoordinator is OperatorSetParam memory operatorSetParams, uint96 minimumStake, IStakeRegistryTypes.StrategyParams[] memory strategyParams, - uint32 lookAheadPeriod + uint32 lookAheadPeriod, + address slasher ) external virtual onlyOwner { _createQuorum( operatorSetParams, minimumStake, strategyParams, IStakeRegistryTypes.StakeType.TOTAL_SLASHABLE, - lookAheadPeriod + lookAheadPeriod, + slasher ); } @@ -780,13 +783,15 @@ contract SlashingRegistryCoordinator is * registered * @param strategyParams a list of strategies and multipliers used by the StakeRegistry to * calculate an operator's stake weight for the quorum + * @param slasher the address of the slasher to use for the quorum (operatorSet) */ function _createQuorum( OperatorSetParam memory operatorSetParams, uint96 minimumStake, IStakeRegistryTypes.StrategyParams[] memory strategyParams, IStakeRegistryTypes.StakeType stakeType, - uint32 lookAheadPeriod + uint32 lookAheadPeriod, + address slasher ) internal { // The previous quorum count is the new quorum's number, // this is because quorum numbers begin from index 0. @@ -803,8 +808,8 @@ contract SlashingRegistryCoordinator is _setOperatorSetParams(quorumNumber, operatorSetParams); // Create array of CreateSetParams for the new quorum - IAllocationManagerTypes.CreateSetParams[] memory createSetParams = - new IAllocationManagerTypes.CreateSetParams[](1); + IAllocationManagerTypes.CreateSetParamsV2[] memory createSetParams = + new IAllocationManagerTypes.CreateSetParamsV2[](1); // Extract strategies from strategyParams IStrategy[] memory strategies = new IStrategy[](strategyParams.length); @@ -813,9 +818,10 @@ contract SlashingRegistryCoordinator is } // Initialize CreateSetParams with quorumNumber as operatorSetId - createSetParams[0] = IAllocationManagerTypes.CreateSetParams({ + createSetParams[0] = IAllocationManagerTypes.CreateSetParamsV2({ operatorSetId: quorumNumber, - strategies: strategies + strategies: strategies, + slasher: slasher }); allocationManager.createOperatorSets({avs: avs, params: createSetParams}); diff --git a/src/SlashingRegistryCoordinatorStorage.sol b/src/SlashingRegistryCoordinatorStorage.sol index f843aa18e..6a9042afd 100644 --- a/src/SlashingRegistryCoordinatorStorage.sol +++ b/src/SlashingRegistryCoordinatorStorage.sol @@ -45,6 +45,10 @@ abstract contract SlashingRegistryCoordinatorStorage is ISlashingRegistryCoordin /// @notice the Index Registry contract that will keep track of operators' indexes IIndexRegistry public immutable indexRegistry; + /// @notice For delegated stake quorums, the address that is set to slash + /// @dev This address is set to the burn address as 0 addresses are not valid slasher addresses + address public constant DELEGATED_STAKE_SLASHER = 0x00000000000000000000000000000000000E16E4; + /// EigenLayer contracts /// @notice the AllocationManager that tracks OperatorSets and Slashing in EigenLayer IAllocationManager public immutable allocationManager; diff --git a/src/interfaces/IBN254TableCalculator.sol b/src/interfaces/IBN254TableCalculator.sol index 938fcc639..52a7fcdf7 100644 --- a/src/interfaces/IBN254TableCalculator.sol +++ b/src/interfaces/IBN254TableCalculator.sol @@ -2,10 +2,13 @@ pragma solidity >=0.5.0; import {OperatorSet} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol"; +import {BN254} from "eigenlayer-contracts/src/contracts/libraries/BN254.sol"; import { IOperatorTableCalculator, IOperatorTableCalculatorTypes } from "eigenlayer-contracts/src/contracts/interfaces/IOperatorTableCalculator.sol"; +import {IBN254CertificateVerifierTypes} from + "eigenlayer-contracts/src/contracts/interfaces/IBN254CertificateVerifier.sol"; interface IBN254TableCalculator is IOperatorTableCalculator, IOperatorTableCalculatorTypes { /** @@ -28,4 +31,38 @@ interface IBN254TableCalculator is IOperatorTableCalculator, IOperatorTableCalcu function getOperatorInfos( OperatorSet calldata operatorSet ) external view returns (BN254OperatorInfo[] memory operatorInfos); + + /** + * @notice Returns the 0-based index of an operator within the operator table built by `calculateOperatorTable` + * @param operatorSet The operator set context used to build the table + * @param operator The operator address whose index is requested + * @return found True if the operator is included in the table (registered), false otherwise + * @return index The 0-based index within the table when `found` is true; zero when `found` is false + * @dev The operator table is formed by iterating results from `getOperatorSetWeights(operatorSet)` and including + * only operators that are registered in `keyRegistrar` for the given `operatorSet`, preserving order. + * This function deterministically reconstructs that inclusion order to locate the operator's index. + */ + function getOperatorIndex( + OperatorSet calldata operatorSet, + address operator + ) external view returns (bool found, uint32 index); + + /** + * @notice Returns non-signer witnesses and aggregate non-signer BN254 G1 public key for a given set of signing operators + * @param operatorSet The operator set context + * @param signingOperators The list of operators that signed (addresses) + * @return nonSignerWitnesses The witnesses for operators that did not sign + * @return nonSignerApk The aggregate BN254 G1 public key of the non-signers + * @dev Reconstructs the operator info merkle tree deterministically to produce proofs and indices. + */ + function getNonSignerWitnessesAndApk( + OperatorSet calldata operatorSet, + address[] calldata signingOperators + ) + external + view + returns ( + IBN254CertificateVerifierTypes.BN254OperatorInfoWitness[] memory nonSignerWitnesses, + BN254.G1Point memory nonSignerApk + ); } diff --git a/src/interfaces/ISlashingRegistryCoordinator.sol b/src/interfaces/ISlashingRegistryCoordinator.sol index 6ffd1de92..08e90a608 100644 --- a/src/interfaces/ISlashingRegistryCoordinator.sol +++ b/src/interfaces/ISlashingRegistryCoordinator.sol @@ -407,13 +407,16 @@ interface ISlashingRegistryCoordinator is * @param strategyParams A list of strategies and multipliers used by the StakeRegistry to calculate * an operator's stake weight for the quorum. * @param lookAheadPeriod The number of blocks to look ahead when calculating slashable stake. + * @param slasher The address of the slasher to use for the operatorSet (quorum) in EigenLayer core * @dev Can only be called when operator sets are enabled. + * @dev The lookahead period is set to 0 and slasher is set to DELEGATED_STAKE_SLASHER for total delegated stake quorums */ function createSlashableStakeQuorum( OperatorSetParam memory operatorSetParams, uint96 minimumStake, IStakeRegistryTypes.StrategyParams[] memory strategyParams, - uint32 lookAheadPeriod + uint32 lookAheadPeriod, + address slasher ) external; /** diff --git a/src/middlewareV2/tableCalculator/BN254TableCalculatorBase.sol b/src/middlewareV2/tableCalculator/BN254TableCalculatorBase.sol index 0d42e58e7..2b6a3f72f 100644 --- a/src/middlewareV2/tableCalculator/BN254TableCalculatorBase.sol +++ b/src/middlewareV2/tableCalculator/BN254TableCalculatorBase.sol @@ -7,6 +7,8 @@ import {IOperatorTableCalculator} from import {IKeyRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IKeyRegistrar.sol"; import {Merkle} from "eigenlayer-contracts/src/contracts/libraries/Merkle.sol"; import {BN254} from "eigenlayer-contracts/src/contracts/libraries/BN254.sol"; +import {IBN254CertificateVerifierTypes} from + "eigenlayer-contracts/src/contracts/interfaces/IBN254CertificateVerifier.sol"; import {LeafCalculatorMixin} from "eigenlayer-contracts/src/contracts/mixins/LeafCalculatorMixin.sol"; import {IBN254TableCalculator} from "../../interfaces/IBN254TableCalculator.sol"; @@ -98,6 +100,87 @@ abstract contract BN254TableCalculatorBase is IBN254TableCalculator, LeafCalcula return operatorInfos; } + /// @inheritdoc IBN254TableCalculator + function getOperatorIndex( + OperatorSet calldata operatorSet, + address operator + ) external view virtual returns (bool found, uint32 index) { + (,, address[] memory registeredOperators) = _buildRegisteredOperatorData(operatorSet); + + uint32 includedIndex = 0; + for (uint256 i = 0; i < registeredOperators.length; i++) { + if (registeredOperators[i] == operator) { + return (true, includedIndex); + } + includedIndex++; + } + + return (false, 0); + } + + /// @inheritdoc IBN254TableCalculator + function getNonSignerWitnessesAndApk( + OperatorSet calldata operatorSet, + address[] calldata signingOperators + ) + external + view + returns ( + IBN254CertificateVerifierTypes.BN254OperatorInfoWitness[] memory nonSignerWitnesses, + BN254.G1Point memory nonSignerApk + ) + { + // Early return if no operators with weights + ( + bytes32[] memory operatorInfoLeaves, + BN254OperatorInfo[] memory registeredOperatorInfos, + address[] memory registeredOperators + ) = _buildRegisteredOperatorData(operatorSet); + + if (registeredOperatorInfos.length == 0) { + return ( + new IBN254CertificateVerifierTypes.BN254OperatorInfoWitness[](0), + BN254.G1Point(0, 0) + ); + } + + // Prepare outputs with max length then resize + nonSignerWitnesses = new IBN254CertificateVerifierTypes.BN254OperatorInfoWitness[]( + registeredOperators.length + ); + nonSignerApk = BN254.G1Point(0, 0); + + uint256 nonSignerCount = 0; + for (uint256 idx = 0; idx < registeredOperators.length; idx++) { + bool signerFound = false; + for (uint256 k = 0; k < signingOperators.length; k++) { + if (signingOperators[k] == registeredOperators[idx]) { + signerFound = true; + break; + } + } + if (signerFound) continue; + + BN254OperatorInfo memory opInfo = registeredOperatorInfos[idx]; + nonSignerApk = nonSignerApk.plus(opInfo.pubkey); + + nonSignerWitnesses[nonSignerCount] = IBN254CertificateVerifierTypes + .BN254OperatorInfoWitness({ + operatorIndex: uint32(idx), + operatorInfoProof: Merkle.getProofKeccak(operatorInfoLeaves, idx), + operatorInfo: opInfo + }); + nonSignerCount++; + } + + // Resize witnesses to actual count + assembly { + mstore(nonSignerWitnesses, nonSignerCount) + } + + return (nonSignerWitnesses, nonSignerApk); + } + /** * @notice Abstract function to get the operator weights for a given operatorSet * @param operatorSet The operatorSet to get the weights for @@ -115,6 +198,59 @@ abstract contract BN254TableCalculatorBase is IBN254TableCalculator, LeafCalcula OperatorSet calldata operatorSet ) internal view virtual returns (address[] memory operators, uint256[][] memory weights); + /** + * @dev Helper that constructs the included operators, infos, and Merkle leaves for registered operators only. + * It preserves deterministic ordering implied by `_getOperatorWeights` output while filtering out + * unregistered operators. + */ + function _buildRegisteredOperatorData( + OperatorSet calldata operatorSet + ) + internal + view + returns ( + bytes32[] memory operatorInfoLeaves, + BN254OperatorInfo[] memory registeredOperatorInfos, + address[] memory registeredOperators + ) + { + (address[] memory operators, uint256[][] memory weights) = _getOperatorWeights(operatorSet); + + // If there are no weights, return empty + if (weights.length == 0) { + return (new bytes32[](0), new BN254OperatorInfo[](0), new address[](0)); + } + + operatorInfoLeaves = new bytes32[](operators.length); + registeredOperatorInfos = new BN254OperatorInfo[](operators.length); + registeredOperators = new address[](operators.length); + + uint256 includedCount = 0; + for (uint256 i = 0; i < operators.length; i++) { + if (!keyRegistrar.isRegistered(operatorSet, operators[i])) { + continue; + } + + (BN254.G1Point memory g1Point,) = keyRegistrar.getBN254Key(operatorSet, operators[i]); + BN254OperatorInfo memory info = + BN254OperatorInfo({pubkey: g1Point, weights: weights[i]}); + + registeredOperatorInfos[includedCount] = info; + registeredOperators[includedCount] = operators[i]; + operatorInfoLeaves[includedCount] = calculateOperatorInfoLeaf(info); + includedCount++; + } + + // Shrink arrays to included count + assembly { + mstore(operatorInfoLeaves, includedCount) + mstore(registeredOperatorInfos, includedCount) + mstore(registeredOperators, includedCount) + } + + return (operatorInfoLeaves, registeredOperatorInfos, registeredOperators); + } + /** * @notice Calculates the operator table for a given operatorSet, also calculates the aggregate pubkey for the operatorSet * @param operatorSet The operatorSet to calculate the operator table for @@ -130,7 +266,6 @@ abstract contract BN254TableCalculatorBase is IBN254TableCalculator, LeafCalcula function _calculateOperatorTable( OperatorSet calldata operatorSet ) internal view returns (BN254OperatorSetInfo memory operatorSetInfo) { - // Get the weights for all operators in the operatorSet (address[] memory operators, uint256[][] memory weights) = _getOperatorWeights(operatorSet); // If there are no weights, return an empty operator set info @@ -143,39 +278,11 @@ abstract contract BN254TableCalculatorBase is IBN254TableCalculator, LeafCalcula }); } - // Initialize arrays - uint256 subArrayLength = weights[0].length; - uint256[] memory totalWeights = new uint256[](subArrayLength); - bytes32[] memory operatorInfoLeaves = new bytes32[](operators.length); - BN254.G1Point memory aggregatePubkey; - uint256 operatorCount = 0; + (bytes32[] memory operatorInfoLeaves, BN254OperatorInfo[] memory registeredOperatorInfos,) = + _buildRegisteredOperatorData(operatorSet); - for (uint256 i = 0; i < operators.length; i++) { - // Skip if the operator has not registered their key - if (!keyRegistrar.isRegistered(operatorSet, operators[i])) { - continue; - } - - // Read the weights for the operator and encode them into the operatorInfoLeaves - // for all weights, add them to the total weights. The ith index returns the weights array for the ith operator - for (uint256 j = 0; j < subArrayLength; j++) { - totalWeights[j] += weights[i][j]; - } - (BN254.G1Point memory g1Point,) = keyRegistrar.getBN254Key(operatorSet, operators[i]); - - // Use `LeafCalculatorMixin` to calculate the leaf hash for the operator info - operatorInfoLeaves[operatorCount] = - calculateOperatorInfoLeaf(BN254OperatorInfo({pubkey: g1Point, weights: weights[i]})); - - // Add the operator's G1 point to the aggregate pubkey - aggregatePubkey = aggregatePubkey.plus(g1Point); - - // Increment the operator count - operatorCount++; - } - - // If there are no operators, return an empty operator set info - if (operatorCount == 0) { + // If there are no included operators, return an empty operator set info + if (registeredOperatorInfos.length == 0) { return BN254OperatorSetInfo({ operatorInfoTreeRoot: bytes32(0), numOperators: 0, @@ -184,16 +291,23 @@ abstract contract BN254TableCalculatorBase is IBN254TableCalculator, LeafCalcula }); } - // Resize the operatorInfoLeaves array to the number of operators and merkleize - assembly { - mstore(operatorInfoLeaves, operatorCount) + uint256 subArrayLength = registeredOperatorInfos[0].weights.length; + uint256[] memory totalWeights = new uint256[](subArrayLength); + BN254.G1Point memory aggregatePubkey; + + for (uint256 i = 0; i < registeredOperatorInfos.length; i++) { + BN254OperatorInfo memory info = registeredOperatorInfos[i]; + for (uint256 j = 0; j < subArrayLength; j++) { + totalWeights[j] += info.weights[j]; + } + aggregatePubkey = aggregatePubkey.plus(info.pubkey); } bytes32 operatorInfoTreeRoot = operatorInfoLeaves.merkleizeKeccak(); return BN254OperatorSetInfo({ operatorInfoTreeRoot: operatorInfoTreeRoot, - numOperators: operatorCount, + numOperators: registeredOperatorInfos.length, aggregatePubkey: aggregatePubkey, totalWeights: totalWeights }); diff --git a/test/fork/EigenDA.t.sol b/test/fork/EigenDA.t.sol index a2acae7da..9ccbabadf 100644 --- a/test/fork/EigenDA.t.sol +++ b/test/fork/EigenDA.t.sol @@ -19,8 +19,6 @@ import {IIndexRegistry} from "../../src/interfaces/IIndexRegistry.sol"; import {ISlashingRegistryCoordinator} from "../../src/interfaces/ISlashingRegistryCoordinator.sol"; import {ISocketRegistry} from "../../src/interfaces/ISocketRegistry.sol"; import {IPauserRegistry} from "eigenlayer-contracts/src/contracts/interfaces/IPauserRegistry.sol"; -import {IAllocationManager} from - "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; import {IAVSDirectory} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; @@ -35,6 +33,7 @@ import { OperatorSet, IAllocationManagerTypes } from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; +import {AllocationManager} from "eigenlayer-contracts/src/contracts/core/AllocationManager.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { ISignatureUtilsMixin, @@ -689,7 +688,7 @@ contract EigenDATest is Test { vm.startPrank(serviceManagerOwner); registryCoordinator.createSlashableStakeQuorum( - operatorSetParam, minimumStake, strategyParams, lookAheadPeriod + operatorSetParam, minimumStake, strategyParams, lookAheadPeriod, serviceManagerOwner ); vm.stopPrank(); } @@ -789,16 +788,16 @@ contract EigenDATest is Test { serviceManager.setAppointee( address(registryCoordinator), allocationManagerAddr, - IAllocationManager.createOperatorSets.selector + bytes4(keccak256("createOperatorSets(address,(uint32,address[],address)[])")) ); serviceManager.setAppointee( serviceManagerOwner, allocationManagerAddr, - IAllocationManager.updateAVSMetadataURI.selector + AllocationManager.updateAVSMetadataURI.selector ); serviceManager.setAppointee( - serviceManagerOwner, allocationManagerAddr, IAllocationManager.setAVSRegistrar.selector + serviceManagerOwner, allocationManagerAddr, AllocationManager.setAVSRegistrar.selector ); console.log("Appointees set for required permissions"); diff --git a/test/fork/End2End.t.sol b/test/fork/End2End.t.sol index b75471cdc..5624eb3ef 100644 --- a/test/fork/End2End.t.sol +++ b/test/fork/End2End.t.sol @@ -206,7 +206,7 @@ contract End2EndForkTest is Test { address(middlewareDeployment.serviceManager), address(middlewareDeployment.registryCoordinator), coreDeployment.allocationManager, - AllocationManager.createOperatorSets.selector + bytes4(keccak256("createOperatorSets(address,(uint32,address[],address)[])")) ); vm.stopPrank(); } @@ -367,7 +367,7 @@ contract End2EndForkTest is Test { }); RegistryCoordinator(middlewareDeployment.registryCoordinator).createSlashableStakeQuorum( - operatorSetParams, 100, strategyParams, 10 + operatorSetParams, 100, strategyParams, 10, address(middlewareDeployment.serviceManager) ); vm.stopPrank(); diff --git a/test/integration/IntegrationConfig.t.sol b/test/integration/IntegrationConfig.t.sol index f1ff0a894..b0a9376ed 100644 --- a/test/integration/IntegrationConfig.t.sol +++ b/test/integration/IntegrationConfig.t.sol @@ -204,7 +204,8 @@ contract IntegrationConfig is IntegrationDeployer, G2Operations, Constants { operatorSetParams: operatorSet, minimumStake: minimumStake, strategyParams: strategyParams, - lookAheadPeriod: 0 + lookAheadPeriod: 0, + slasher: registryCoordinatorOwner }); } else if (quorumType == BOTH) { // randomly choose one of the two @@ -222,7 +223,8 @@ contract IntegrationConfig is IntegrationDeployer, G2Operations, Constants { operatorSetParams: operatorSet, minimumStake: minimumStake, strategyParams: strategyParams, - lookAheadPeriod: 0 + lookAheadPeriod: 0, + slasher: registryCoordinatorOwner }); } } diff --git a/test/integration/IntegrationDeployer.t.sol b/test/integration/IntegrationDeployer.t.sol index b695633ad..551440945 100644 --- a/test/integration/IntegrationDeployer.t.sol +++ b/test/integration/IntegrationDeployer.t.sol @@ -18,6 +18,7 @@ import "eigenlayer-contracts/src/contracts/core/DelegationManager.sol"; import "eigenlayer-contracts/src/contracts/core/StrategyManager.sol"; import "eigenlayer-contracts/src/contracts/core/AVSDirectory.sol"; import "eigenlayer-contracts/src/contracts/core/RewardsCoordinator.sol"; +import "eigenlayer-contracts/src/contracts/core/AllocationManagerView.sol"; import "eigenlayer-contracts/src/contracts/core/AllocationManager.sol"; import "eigenlayer-contracts/src/contracts/strategies/StrategyBase.sol"; import "eigenlayer-contracts/src/contracts/pods/EigenPodManager.sol"; @@ -133,6 +134,11 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { /// @notice the commission for all operators across all avss uint16 globalCommissionBips = 1000; + /// @notice The deallocation delay for the AllocationManager + uint32 DEALLOCATION_DELAY = 7 days; + /// @notice The allocation configuration delay for the AllocationManager + uint32 ALLOCATION_CONFIGURATION_DELAY = 1 days; + function setUp() public virtual { // Deploy ProxyAdmin proxyAdmin = new ProxyAdmin(); @@ -190,11 +196,11 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { ); // Deploy EigenPod Contracts - pod = new EigenPod(ethPOSDeposit, eigenPodManager, "v0.0.1"); + pod = new EigenPod(ethPOSDeposit, eigenPodManager); eigenPodBeacon = new UpgradeableBeacon(address(pod)); - PermissionController permissionControllerImplementation = new PermissionController("v0.0.1"); + PermissionController permissionControllerImplementation = new PermissionController(); // Second, deploy the *implementation* contracts, using the *proxy contracts* as inputs DelegationManager delegationImplementation = new DelegationManager( @@ -208,9 +214,8 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { ); StrategyManager strategyManagerImplementation = new StrategyManager(allocationManager, delegationManager, pauserRegistry, "v0.0.1"); - EigenPodManager eigenPodManagerImplementation = new EigenPodManager( - ethPOSDeposit, eigenPodBeacon, delegationManager, pauserRegistry, "v0.0.1" - ); + EigenPodManager eigenPodManagerImplementation = + new EigenPodManager(ethPOSDeposit, eigenPodBeacon, delegationManager, pauserRegistry); AVSDirectory avsDirectoryImplementation = new AVSDirectory(delegationManager, pauserRegistry, "v0.0.1"); @@ -225,23 +230,28 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { MAX_REWARDS_DURATION: MAX_REWARDS_DURATION, MAX_RETROACTIVE_LENGTH: MAX_RETROACTIVE_LENGTH, MAX_FUTURE_LENGTH: MAX_FUTURE_LENGTH, - GENESIS_REWARDS_TIMESTAMP: GENESIS_REWARDS_TIMESTAMP, - version: "v0.0.1" + GENESIS_REWARDS_TIMESTAMP: GENESIS_REWARDS_TIMESTAMP }) ); - IStrategy eigenStrategy = - IStrategy(new EigenStrategy(strategyManager, pauserRegistry, "v0.0.1")); + IStrategy eigenStrategy = IStrategy(new EigenStrategy(strategyManager, pauserRegistry)); - AllocationManager allocationManagerImplementation = new AllocationManager( - delegationManager, - eigenStrategy, - pauserRegistry, - permissionController, - uint32(7 days), // DEALLOCATION_DELAY - uint32(1 days), // ALLOCATION_CONFIGURATION_DELAY - "v0.0.1" // Added config parameter - ); + AllocationManagerView allocationManagerView = new AllocationManagerView({ + _delegation: delegationManager, + _eigenStrategy: eigenStrategy, + _DEALLOCATION_DELAY: DEALLOCATION_DELAY, + _ALLOCATION_CONFIGURATION_DELAY: ALLOCATION_CONFIGURATION_DELAY + }); + + AllocationManager allocationManagerImplementation = new AllocationManager({ + _allocationManagerView: allocationManagerView, + _delegation: delegationManager, + _eigenStrategy: eigenStrategy, + _pauserRegistry: pauserRegistry, + _permissionController: permissionController, + _DEALLOCATION_DELAY: DEALLOCATION_DELAY, + _ALLOCATION_CONFIGURATION_DELAY: ALLOCATION_CONFIGURATION_DELAY + }); // Third, upgrade the proxy contracts to point to the implementations uint256 minWithdrawalDelayBlocks = 7 days / 12 seconds; @@ -316,7 +326,7 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { ); // Deploy and whitelist strategies - baseStrategyImplementation = new StrategyBase(strategyManager, pauserRegistry, "v0.0.1"); + baseStrategyImplementation = new StrategyBase(strategyManager, pauserRegistry); for (uint256 i = 0; i < MAX_STRATEGY_COUNT; i++) { string memory number = uint256(i).toString(); string memory stratName = string.concat("StrategyToken", number); @@ -486,38 +496,38 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { serviceManager.setAppointee({ appointee: serviceManager.owner(), target: address(allocationManager), - selector: IAllocationManager.setAVSRegistrar.selector + selector: AllocationManager.setAVSRegistrar.selector }); // 2. set AVS metadata serviceManager.setAppointee({ appointee: serviceManager.owner(), target: address(allocationManager), - selector: IAllocationManager.updateAVSMetadataURI.selector + selector: AllocationManager.updateAVSMetadataURI.selector }); // 3. create operator sets serviceManager.setAppointee({ appointee: address(registryCoordinator), target: address(allocationManager), - selector: IAllocationManager.createOperatorSets.selector + selector: bytes4(keccak256("createOperatorSets(address,(uint32,address[],address)[])")) }); // 4. deregister operator from operator sets serviceManager.setAppointee({ appointee: address(registryCoordinator), target: address(allocationManager), - selector: IAllocationManager.deregisterFromOperatorSets.selector + selector: AllocationManager.deregisterFromOperatorSets.selector }); // 5. add strategies to operator sets serviceManager.setAppointee({ appointee: address(registryCoordinator), target: address(stakeRegistry), - selector: IAllocationManager.addStrategiesToOperatorSet.selector + selector: AllocationManager.addStrategiesToOperatorSet.selector }); // 6. remove strategies from operator sets serviceManager.setAppointee({ appointee: address(registryCoordinator), target: address(stakeRegistry), - selector: IAllocationManager.removeStrategiesFromOperatorSet.selector + selector: AllocationManager.removeStrategiesFromOperatorSet.selector }); cheats.stopPrank(); _setOperatorSetsEnabled(false); @@ -530,31 +540,31 @@ abstract contract IntegrationDeployer is Test, IUserDeployer { account: avsAccountIdentifier, appointee: address(avsAccountIdentifier), target: address(allocationManager), - selector: IAllocationManager.setAVSRegistrar.selector + selector: AllocationManager.setAVSRegistrar.selector }); permissionController.setAppointee({ account: avsAccountIdentifier, appointee: address(slashingRegistryCoordinator), target: address(allocationManager), - selector: IAllocationManager.createOperatorSets.selector + selector: bytes4(keccak256("createOperatorSets(address,(uint32,address[],address)[])")) }); permissionController.setAppointee({ account: avsAccountIdentifier, appointee: address(slashingRegistryCoordinator), target: address(allocationManager), - selector: IAllocationManager.deregisterFromOperatorSets.selector + selector: AllocationManager.deregisterFromOperatorSets.selector }); permissionController.setAppointee({ account: avsAccountIdentifier, appointee: address(stakeRegistry), target: address(allocationManager), - selector: IAllocationManager.addStrategiesToOperatorSet.selector + selector: AllocationManager.addStrategiesToOperatorSet.selector }); permissionController.setAppointee({ account: avsAccountIdentifier, appointee: address(stakeRegistry), target: address(allocationManager), - selector: IAllocationManager.removeStrategiesFromOperatorSet.selector + selector: AllocationManager.removeStrategiesFromOperatorSet.selector }); // set AVS Registrar to slashingRegistryCoordinator allocationManager.setAVSRegistrar( diff --git a/test/mocks/AllocationManagerMock.sol b/test/mocks/AllocationManagerMock.sol index 4844bfb9e..51808edc9 100644 --- a/test/mocks/AllocationManagerMock.sol +++ b/test/mocks/AllocationManagerMock.sol @@ -9,6 +9,8 @@ import {IAVSRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IAVSR import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; import {IPauserRegistry} from "eigenlayer-contracts/src/contracts/interfaces/IPauserRegistry.sol"; import {ISemVerMixin} from "eigenlayer-contracts/src/contracts/interfaces/ISemVerMixin.sol"; +import {IDelegationManager} from + "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; contract AllocationManagerIntermediate is IAllocationManager { mapping(address avs => address avsRegistrar) internal _avsRegistrar; @@ -58,12 +60,23 @@ contract AllocationManagerIntermediate is IAllocationManager { function createOperatorSets(address avs, CreateSetParams[] calldata params) external virtual {} + function createOperatorSets( + address avs, + CreateSetParamsV2[] calldata params + ) external virtual {} + function createRedistributingOperatorSets( address avs, CreateSetParams[] calldata params, address[] calldata redistributionRecipients ) external virtual {} + function createRedistributingOperatorSets( + address avs, + CreateSetParamsV2[] calldata params, + address[] calldata redistributionRecipients + ) external virtual {} + function addStrategiesToOperatorSet( address avs, uint32 operatorSetId, @@ -218,6 +231,45 @@ contract AllocationManagerIntermediate is IAllocationManager { function isRedistributingOperatorSet( OperatorSet memory operatorSet ) external pure virtual returns (bool) {} + + function ALLOCATION_CONFIGURATION_DELAY() external pure virtual returns (uint32) {} + + function delegation() external pure virtual returns (IDelegationManager) {} + + function eigenStrategy() external pure virtual returns (IStrategy) {} + + function getPendingSlasher( + OperatorSet memory operatorSet + ) external view virtual returns (address pendingSlasher, uint32 effectBlock) {} + + function migrateSlashers( + OperatorSet[] memory operatorSets + ) external virtual {} + + function updateSlasher(OperatorSet memory operatorSet, address slasher) external virtual {} + + function getSlasher( + OperatorSet memory operatorSet + ) external view virtual returns (address slasher) {} + + // Pause functions + function pause( + uint256 newPausedStatus + ) external virtual {} + + function unpause( + uint256 newPausedStatus + ) external virtual {} + + function paused() external view virtual returns (uint256) {} + + function paused( + uint8 index + ) external view virtual returns (bool) {} + + function pauseAll() external virtual {} + + function pauserRegistry() external view virtual returns (IPauserRegistry) {} } contract AllocationManagerMock is AllocationManagerIntermediate { diff --git a/test/mocks/KeyRegistrarMock.sol b/test/mocks/KeyRegistrarMock.sol index f60228682..f6d399f97 100644 --- a/test/mocks/KeyRegistrarMock.sol +++ b/test/mocks/KeyRegistrarMock.sol @@ -32,6 +32,29 @@ contract KeyRegistrarMock is IKeyRegistrar { function configureOperatorSet(OperatorSet memory operatorSet, CurveType curveType) external {} + function configureOperatorSetWithMinDelay( + OperatorSet memory operatorSet, + CurveType curveType, + uint64 minDelaySeconds + ) external {} + + function finalizeScheduledRotation( + address operator, + OperatorSet memory operatorSet + ) external returns (bool) {} + + function rotateKey( + address operator, + OperatorSet memory operatorSet, + bytes calldata pubkey, + bytes calldata signature + ) external {} + + function setMinKeyRotationDelay( + OperatorSet memory operatorSet, + uint64 minDelaySeconds + ) external {} + function registerKey( address operator, OperatorSet memory operatorSet, diff --git a/test/mocks/PermissionControllerMock.sol b/test/mocks/PermissionControllerMock.sol index 6a6cc05e2..d66731844 100644 --- a/test/mocks/PermissionControllerMock.sol +++ b/test/mocks/PermissionControllerMock.sol @@ -50,7 +50,7 @@ contract PermissionControllerIntermediate is IPermissionController { address caller, address target, bytes4 selector - ) external virtual returns (bool) {} + ) external view virtual returns (bool) {} function getAppointeePermissions( address account, @@ -90,7 +90,7 @@ contract PermissionControllerMock is PermissionControllerIntermediate { address caller, address target, bytes4 selector - ) external override returns (bool) { + ) external view override returns (bool) { if (account == caller) return true; return _canCall[account][caller][target][selector]; } diff --git a/test/unit/InstantSlasher.t.sol b/test/unit/InstantSlasher.t.sol index 95436ccfb..bc2e77147 100644 --- a/test/unit/InstantSlasher.t.sol +++ b/test/unit/InstantSlasher.t.sol @@ -189,12 +189,6 @@ contract InstantSlasherTest is Test { vm.stopPrank(); vm.startPrank(serviceManager); - PermissionController(coreDeployment.permissionController).setAppointee( - address(serviceManager), - address(instantSlasher), - coreDeployment.allocationManager, - AllocationManager.slashOperator.selector - ); slashingRegistryCoordinator = SlashingRegistryCoordinator(middlewareDeployments.slashingRegistryCoordinator); @@ -205,14 +199,7 @@ contract InstantSlasherTest is Test { address(serviceManager), address(slashingRegistryCoordinator), coreDeployment.allocationManager, - AllocationManager.createOperatorSets.selector - ); - - PermissionController(coreDeployment.permissionController).setAppointee( - address(serviceManager), - address(instantSlasher), - coreDeployment.allocationManager, - AllocationManager.slashOperator.selector + bytes4(keccak256("createOperatorSets(address,(uint32,address[],address)[])")) ); PermissionController(coreDeployment.permissionController).setAppointee( @@ -255,7 +242,7 @@ contract InstantSlasherTest is Test { serviceManager, "fake-avs-metadata" ); slashingRegistryCoordinator.createSlashableStakeQuorum( - operatorSetParams, 1 ether, strategyParams, 0 + operatorSetParams, 1 ether, strategyParams, 0, address(instantSlasher) ); vm.stopPrank(); @@ -276,6 +263,8 @@ contract InstantSlasherTest is Test { } function test_fulfillSlashingRequest() public { + // Roll block number so we don't underflow in the DM when calculating `prevQueuedScaledShares` + vm.roll(block.number + ALLOCATION_CONFIGURATION_DELAY + 1); vm.startPrank(operatorWallet.key.addr); IDelegationManager(coreDeployment.delegationManager).registerAsOperator( address(0), 1, "metadata" @@ -297,9 +286,7 @@ contract InstantSlasherTest is Test { (bool isSet,) = IAllocationManager(coreDeployment.allocationManager).getAllocationDelay( operatorWallet.key.addr ); - assertFalse(isSet, "Operator allocation delay not set"); - - vm.roll(block.number + ALLOCATION_CONFIGURATION_DELAY + 1); + assertTrue(isSet, "Operator allocation delay set"); IStrategy[] memory allocStrategies = new IStrategy[](1); allocStrategies[0] = mockStrategy; diff --git a/test/unit/RegistryCoordinatorUnit.t.sol b/test/unit/RegistryCoordinatorUnit.t.sol index 9334d27ae..a911672b5 100644 --- a/test/unit/RegistryCoordinatorUnit.t.sol +++ b/test/unit/RegistryCoordinatorUnit.t.sol @@ -2417,7 +2417,11 @@ contract RegistryCoordinatorUnitTests_BeforeMigration is RegistryCoordinatorUnit // Attempt to create quorum with slashable stake type before enabling operator sets cheats.prank(registryCoordinatorOwner); registryCoordinator.createSlashableStakeQuorum( - operatorSetParams, minimumStake, strategyParams, lookAheadPeriod + operatorSetParams, + minimumStake, + strategyParams, + lookAheadPeriod, + registryCoordinatorOwner ); assertEq(registryCoordinator.quorumCount(), 1, "New quorum 0 should be created"); assertFalse(registryCoordinator.isM2Quorum(0), "Quorum created should not be an M2 quorum"); @@ -2523,7 +2527,11 @@ contract RegistryCoordinatorUnitTests_AfterMigration is RegistryCoordinatorUnitT // Create slashable stake quorum cheats.prank(registryCoordinatorOwner); registryCoordinator.createSlashableStakeQuorum( - operatorSetParams, minimumStake, strategyParams, lookAheadPeriod + operatorSetParams, + minimumStake, + strategyParams, + lookAheadPeriod, + registryCoordinatorOwner ); } diff --git a/test/unit/ServiceManagerBase.t.sol b/test/unit/ServiceManagerBase.t.sol index df5711173..492d2e579 100644 --- a/test/unit/ServiceManagerBase.t.sol +++ b/test/unit/ServiceManagerBase.t.sol @@ -18,7 +18,8 @@ import {IServiceManagerErrors} from "../../src/interfaces/IServiceManager.sol"; import { IAllocationManagerTypes, - IAllocationManager + IAllocationManager, + IAllocationManagerActions } from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; import "../utils/MockAVSDeployer.sol"; @@ -77,8 +78,7 @@ contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEve MAX_REWARDS_DURATION: MAX_REWARDS_DURATION, MAX_RETROACTIVE_LENGTH: MAX_RETROACTIVE_LENGTH, MAX_FUTURE_LENGTH: MAX_FUTURE_LENGTH, - GENESIS_REWARDS_TIMESTAMP: GENESIS_REWARDS_TIMESTAMP, - version: "v0.0.1" + GENESIS_REWARDS_TIMESTAMP: GENESIS_REWARDS_TIMESTAMP }) ); @@ -168,9 +168,8 @@ contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEve IERC20 token3 = new ERC20PresetFixedSupply( "pepe wif avs", "MOCK3", mockTokenInitialSupply, address(this) ); - strategyImplementation = new StrategyBase( - IStrategyManager(address(strategyManagerMock)), pauserRegistry, "v0.0.1" - ); + strategyImplementation = + new StrategyBase(IStrategyManager(address(strategyManagerMock)), pauserRegistry); strategyMock1 = StrategyBase( address( new TransparentUpgradeableProxy( @@ -935,7 +934,7 @@ contract ServiceManagerBase_UnitTests is MockAVSDeployer, IServiceManagerBaseEve cheats.expectCall( address(allocationManagerMock), - abi.encodeCall(IAllocationManager.deregisterFromOperatorSets, (expectedParams)) + abi.encodeCall(AllocationManager.deregisterFromOperatorSets, (expectedParams)) ); // Call should only work from registryCoordinator diff --git a/test/unit/SlashingRegistryCoordinatorUnit.t.sol b/test/unit/SlashingRegistryCoordinatorUnit.t.sol index cfbc0ad73..2ef94c835 100644 --- a/test/unit/SlashingRegistryCoordinatorUnit.t.sol +++ b/test/unit/SlashingRegistryCoordinatorUnit.t.sol @@ -6,7 +6,8 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {InstantSlasher} from "../../src/slashers/InstantSlasher.sol"; import { IAllocationManager, - IAllocationManagerTypes + IAllocationManagerTypes, + IAllocationManagerActions } from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; import {OperatorSetLib} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol"; @@ -278,7 +279,7 @@ contract SlashingRegistryCoordinatorUnitTestSetup is address(serviceManager), address(slashingRegistryCoordinator), coreDeployment.allocationManager, - AllocationManager.createOperatorSets.selector + bytes4(keccak256("createOperatorSets(address,(uint32,address[],address)[])")) ); PermissionController(coreDeployment.permissionController).setAppointee( @@ -669,7 +670,7 @@ contract SlashingRegistryCoordinator_CreateSlashableStakeQuorum is vm.prank(proxyAdminOwner); slashingRegistryCoordinator.createSlashableStakeQuorum( - operatorSetParams, minimumStake, getStrategyParams(), lookAheadPeriod + operatorSetParams, minimumStake, getStrategyParams(), lookAheadPeriod, proxyAdminOwner ); assertEq(slashingRegistryCoordinator.quorumCount(), initialQuorumCount + 1); @@ -691,7 +692,7 @@ contract SlashingRegistryCoordinator_CreateSlashableStakeQuorum is vm.prank(proxyAdminOwner); slashingRegistryCoordinator.createSlashableStakeQuorum( - operatorSetParams, minimumStake, strategyParams, lookAheadPeriod + operatorSetParams, minimumStake, strategyParams, lookAheadPeriod, proxyAdminOwner ); assertEq(slashingRegistryCoordinator.quorumCount(), quorumNumber + 1); @@ -707,7 +708,7 @@ contract SlashingRegistryCoordinator_CreateSlashableStakeQuorum is vm.prank(address(0xdead)); slashingRegistryCoordinator.createSlashableStakeQuorum( - operatorSetParams, minimumStake, getStrategyParams(), lookAheadPeriod + operatorSetParams, minimumStake, getStrategyParams(), lookAheadPeriod, proxyAdminOwner ); } @@ -718,13 +719,17 @@ contract SlashingRegistryCoordinator_CreateSlashableStakeQuorum is // So we need to create 191 more for (uint8 i = 0; i < 191; i++) { slashingRegistryCoordinator.createSlashableStakeQuorum( - operatorSetParams, minimumStake, getStrategyParams(), lookAheadPeriod + operatorSetParams, + minimumStake, + getStrategyParams(), + lookAheadPeriod, + proxyAdminOwner ); } vm.expectRevert(MaxQuorumsReached.selector); slashingRegistryCoordinator.createSlashableStakeQuorum( - operatorSetParams, minimumStake, getStrategyParams(), lookAheadPeriod + operatorSetParams, minimumStake, getStrategyParams(), lookAheadPeriod, proxyAdminOwner ); vm.stopPrank(); @@ -739,7 +744,11 @@ contract SlashingRegistryCoordinator_CreateSlashableStakeQuorum is vm.prank(proxyAdminOwner); vm.expectRevert(LookAheadPeriodTooLong.selector); slashingRegistryCoordinator.createSlashableStakeQuorum( - operatorSetParams, minimumStake, getStrategyParams(), tooLongLookAheadPeriod + operatorSetParams, + minimumStake, + getStrategyParams(), + tooLongLookAheadPeriod, + proxyAdminOwner ); } } @@ -1345,7 +1354,7 @@ contract SlashingRegistryCoordinator_EjectOperator is SlashingRegistryCoordinato address(serviceManager), address(slashingRegistryCoordinator), address(coreDeployment.allocationManager), - IAllocationManager.deregisterFromOperatorSets.selector + AllocationManager.deregisterFromOperatorSets.selector ); registerOperatorInSlashingRegistryCoordinator(testOperator, "socket:8545", operatorSetIds); @@ -1586,7 +1595,7 @@ contract SlashingRegistryCoordinator_RegisterWithChurn is address(serviceManager), address(slashingRegistryCoordinator), address(coreDeployment.allocationManager), - IAllocationManager.deregisterFromOperatorSets.selector + AllocationManager.deregisterFromOperatorSets.selector ); } @@ -2058,7 +2067,7 @@ contract SlashingRegistryCoordinator_UpdateOperators is SlashingRegistryCoordina address(serviceManager), address(slashingRegistryCoordinator), address(coreDeployment.allocationManager), - IAllocationManager.deregisterFromOperatorSets.selector + AllocationManager.deregisterFromOperatorSets.selector ); } @@ -2269,7 +2278,7 @@ contract SlashingRegistryCoordinator_UpdateOperatorsForQuorum is address(serviceManager), address(slashingRegistryCoordinator), address(coreDeployment.allocationManager), - IAllocationManager.deregisterFromOperatorSets.selector + AllocationManager.deregisterFromOperatorSets.selector ); } diff --git a/test/unit/Utils.sol b/test/unit/Utils.sol index 89f274e8c..463d8a3bc 100644 --- a/test/unit/Utils.sol +++ b/test/unit/Utils.sol @@ -12,7 +12,7 @@ contract Utils { IPauserRegistry pauserRegistry, address admin ) public returns (StrategyBase) { - StrategyBase newStrategy = new StrategyBase(strategyManager, pauserRegistry, "v0.0.1"); + StrategyBase newStrategy = new StrategyBase(strategyManager, pauserRegistry); newStrategy = StrategyBase( address(new TransparentUpgradeableProxy(address(newStrategy), address(admin), "")) ); diff --git a/test/unit/VetoableSlasher.t.sol b/test/unit/VetoableSlasher.t.sol index 889ab796f..c06616b39 100644 --- a/test/unit/VetoableSlasher.t.sol +++ b/test/unit/VetoableSlasher.t.sol @@ -223,18 +223,12 @@ contract VetoableSlasherTest is Test { vm.stopPrank(); vm.startPrank(serviceManager); - PermissionController(coreDeployment.permissionController).setAppointee( - address(serviceManager), - address(vetoableSlasher), - coreDeployment.allocationManager, - AllocationManager.slashOperator.selector - ); PermissionController(coreDeployment.permissionController).setAppointee( address(serviceManager), address(slashingRegistryCoordinator), coreDeployment.allocationManager, - AllocationManager.createOperatorSets.selector + bytes4(keccak256("createOperatorSets(address,(uint32,address[],address)[])")) ); PermissionController(coreDeployment.permissionController).setAppointee( @@ -277,7 +271,7 @@ contract VetoableSlasherTest is Test { serviceManager, "fake-avs-metadata" ); slashingRegistryCoordinator.createSlashableStakeQuorum( - operatorSetParams, 1 ether, strategyParams, 0 + operatorSetParams, 1 ether, strategyParams, 0, address(vetoableSlasher) ); vm.stopPrank(); diff --git a/test/unit/middlewareV2/AVSRegistrarAsIdentifierUnit.t.sol b/test/unit/middlewareV2/AVSRegistrarAsIdentifierUnit.t.sol index 879a1a3f6..19e02cfd2 100644 --- a/test/unit/middlewareV2/AVSRegistrarAsIdentifierUnit.t.sol +++ b/test/unit/middlewareV2/AVSRegistrarAsIdentifierUnit.t.sol @@ -13,6 +13,7 @@ import {IAllocationManager} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {AllocationManager} from "eigenlayer-contracts/src/contracts/core/AllocationManager.sol"; contract AVSRegistrarAsIdentifierUnitTests is AVSRegistrarBase { AVSRegistrarAsIdentifier public avsRegistrarAsIdentifier; @@ -86,7 +87,7 @@ contract AVSRegistrarAsIdentifierUnitTests_initialize is AVSRegistrarAsIdentifie vm.mockCall( address(allocationManagerMock), abi.encodeWithSelector( - IAllocationManager.updateAVSMetadataURI.selector, + AllocationManager.updateAVSMetadataURI.selector, address(avsRegistrarAsIdentifier), METADATA_URI ), @@ -95,7 +96,7 @@ contract AVSRegistrarAsIdentifierUnitTests_initialize is AVSRegistrarAsIdentifie vm.mockCall( address(allocationManagerMock), abi.encodeWithSelector( - IAllocationManager.setAVSRegistrar.selector, + AllocationManager.setAVSRegistrar.selector, address(avsRegistrarAsIdentifier), avsRegistrarAsIdentifier ), @@ -117,7 +118,7 @@ contract AVSRegistrarAsIdentifierUnitTests_initialize is AVSRegistrarAsIdentifie vm.expectCall( address(allocationManagerMock), abi.encodeWithSelector( - IAllocationManager.updateAVSMetadataURI.selector, + AllocationManager.updateAVSMetadataURI.selector, address(avsRegistrarAsIdentifier), METADATA_URI ) @@ -125,7 +126,7 @@ contract AVSRegistrarAsIdentifierUnitTests_initialize is AVSRegistrarAsIdentifie vm.expectCall( address(allocationManagerMock), abi.encodeWithSelector( - IAllocationManager.setAVSRegistrar.selector, + AllocationManager.setAVSRegistrar.selector, address(avsRegistrarAsIdentifier), avsRegistrarAsIdentifier ) @@ -147,12 +148,12 @@ contract AVSRegistrarAsIdentifierUnitTests_initialize is AVSRegistrarAsIdentifie // Initialize first vm.mockCall( address(allocationManagerMock), - abi.encodeWithSelector(IAllocationManager.updateAVSMetadataURI.selector), + abi.encodeWithSelector(AllocationManager.updateAVSMetadataURI.selector), "" ); vm.mockCall( address(allocationManagerMock), - abi.encodeWithSelector(IAllocationManager.setAVSRegistrar.selector), + abi.encodeWithSelector(AllocationManager.setAVSRegistrar.selector), "" ); vm.mockCall( diff --git a/test/unit/middlewareV2/BN254TableCalculatorBaseUnit.t.sol b/test/unit/middlewareV2/BN254TableCalculatorBaseUnit.t.sol index 29b836126..e799215cc 100644 --- a/test/unit/middlewareV2/BN254TableCalculatorBaseUnit.t.sol +++ b/test/unit/middlewareV2/BN254TableCalculatorBaseUnit.t.sol @@ -11,6 +11,8 @@ import { BN254, IOperatorTableCalculatorTypes } from "eigenlayer-contracts/src/contracts/interfaces/IOperatorTableCalculator.sol"; +import {IBN254CertificateVerifierTypes} from + "eigenlayer-contracts/src/contracts/interfaces/IBN254CertificateVerifier.sol"; import {IBN254TableCalculator} from "../../../src/interfaces/IBN254TableCalculator.sol"; import { OperatorSet, @@ -838,3 +840,465 @@ contract BN254TableCalculatorBaseUnitTests_getOperatorInfos is BN254TableCalcula } } } + +/** + * @title BN254TableCalculatorBaseUnitTests_getOperatorIndex + * @notice Unit tests for BN254TableCalculatorBase.getOperatorIndex + */ +contract BN254TableCalculatorBaseUnitTests_getOperatorIndex is BN254TableCalculatorBaseUnitTests { + function test_getOperatorIndex_allRegistered() public { + // Register all operators + _registerOperatorKey( + operator1, defaultOperatorSet, bn254G1Key1, bn254G2Key1, BN254_PRIV_KEY_1 + ); + _registerOperatorKey( + operator2, defaultOperatorSet, bn254G1Key2, bn254G2Key2, BN254_PRIV_KEY_2 + ); + _registerOperatorKey( + operator3, defaultOperatorSet, bn254G1Key3, bn254G2Key3, BN254_PRIV_KEY_3 + ); + + // Provide weights for all + address[] memory operators = new address[](3); + operators[0] = operator1; + operators[1] = operator2; + operators[2] = operator3; + + uint256[][] memory weights = new uint256[][](3); + weights[0] = _createSingleWeightArray(100)[0]; + weights[1] = _createSingleWeightArray(200)[0]; + weights[2] = _createSingleWeightArray(300)[0]; + + calculator.setMockOperatorWeights(defaultOperatorSet, operators, weights); + + (bool found1, uint32 idx1) = calculator.getOperatorIndex(defaultOperatorSet, operator1); + (bool found2, uint32 idx2) = calculator.getOperatorIndex(defaultOperatorSet, operator2); + (bool found3, uint32 idx3) = calculator.getOperatorIndex(defaultOperatorSet, operator3); + + assertTrue(found1, "operator1 should be found"); + assertTrue(found2, "operator2 should be found"); + assertTrue(found3, "operator3 should be found"); + assertEq(uint256(idx1), 0, "operator1 index"); + assertEq(uint256(idx2), 1, "operator2 index"); + assertEq(uint256(idx3), 2, "operator3 index"); + } + + function test_getOperatorIndex_unregisteredExcluded() public { + // Register operator1 and operator3 only + _registerOperatorKey( + operator1, defaultOperatorSet, bn254G1Key1, bn254G2Key1, BN254_PRIV_KEY_1 + ); + _registerOperatorKey( + operator3, defaultOperatorSet, bn254G1Key3, bn254G2Key3, BN254_PRIV_KEY_3 + ); + + // Ordered operators with weights + address[] memory operators = new address[](3); + operators[0] = operator1; // registered -> index 0 + operators[1] = operator2; // not registered -> excluded + operators[2] = operator3; // registered -> index 1 + + uint256[][] memory weights = new uint256[][](3); + weights[0] = _createSingleWeightArray(100)[0]; + weights[1] = _createSingleWeightArray(200)[0]; + weights[2] = _createSingleWeightArray(300)[0]; + + calculator.setMockOperatorWeights(defaultOperatorSet, operators, weights); + + (bool f1, uint32 i1) = calculator.getOperatorIndex(defaultOperatorSet, operator1); + (bool f2, uint32 i2) = calculator.getOperatorIndex(defaultOperatorSet, operator2); + (bool f3, uint32 i3) = calculator.getOperatorIndex(defaultOperatorSet, operator3); + + assertTrue(f1, "operator1 should be found"); + assertEq(uint256(i1), 0, "operator1 index should be 0"); + + assertTrue(!f2, "operator2 should not be found"); + assertEq(uint256(i2), 0, "operator2 index irrelevant when not found"); + + assertTrue(f3, "operator3 should be found"); + assertEq(uint256(i3), 1, "operator3 index should be 1"); + } + + function test_getOperatorIndex_emptySet() public { + address[] memory operators = new address[](0); + uint256[][] memory weights = new uint256[][](0); + calculator.setMockOperatorWeights(defaultOperatorSet, operators, weights); + + (bool found, uint32 idx) = calculator.getOperatorIndex(defaultOperatorSet, operator1); + assertTrue(!found, "no operator should be found"); + assertEq(uint256(idx), 0, "index should be 0 when not found"); + } + + function test_getOperatorIndex_noRegistrations() public { + // Operators and weights exist but none registered -> none found + address[] memory operators = new address[](2); + operators[0] = operator1; + operators[1] = operator2; + + uint256[][] memory weights = new uint256[][](2); + weights[0] = _createSingleWeightArray(100)[0]; + weights[1] = _createSingleWeightArray(200)[0]; + + calculator.setMockOperatorWeights(defaultOperatorSet, operators, weights); + + (bool f1, uint32 i1) = calculator.getOperatorIndex(defaultOperatorSet, operator1); + (bool f2, uint32 i2) = calculator.getOperatorIndex(defaultOperatorSet, operator2); + + assertTrue(!f1, "operator1 should not be found"); + assertTrue(!f2, "operator2 should not be found"); + assertEq(uint256(i1), 0, "operator1 index irrelevant when not found"); + assertEq(uint256(i2), 0, "operator2 index irrelevant when not found"); + } +} + +/** + * @title BN254TableCalculatorBaseUnitTests_getNonSignerWitnessesAndApk + * @notice Unit tests for BN254TableCalculatorBase.getNonSignerWitnessesAndApk + * @dev Uses the base harness with mocked weights and real KeyRegistrar registrations + */ +contract BN254TableCalculatorBaseUnitTests_getNonSignerWitnessesAndApk is + BN254TableCalculatorBaseUnitTests +{ + function test_emptySigningOperators_allNonSigners_base() public { + // Operators and weights + address[] memory operators = new address[](2); + operators[0] = operator1; + operators[1] = operator2; + uint256[][] memory weights = new uint256[][](2); + weights[0] = _createSingleWeightArray(100)[0]; + weights[1] = _createSingleWeightArray(200)[0]; + calculator.setMockOperatorWeights(defaultOperatorSet, operators, weights); + + // Register both + _registerOperatorKey( + operator1, defaultOperatorSet, bn254G1Key1, bn254G2Key1, BN254_PRIV_KEY_1 + ); + _registerOperatorKey( + operator2, defaultOperatorSet, bn254G1Key2, bn254G2Key2, BN254_PRIV_KEY_2 + ); + + BN254OperatorSetInfo memory info = calculator.calculateOperatorTable(defaultOperatorSet); + + address[] memory signing = new address[](0); + ( + IBN254CertificateVerifierTypes.BN254OperatorInfoWitness[] memory witnesses, + BN254.G1Point memory nonSignerApk + ) = calculator.getNonSignerWitnessesAndApk(defaultOperatorSet, signing); + + assertEq(witnesses.length, 2, "all operators should be non-signers"); + assertEq(uint256(witnesses[0].operatorIndex), 0); + assertEq(uint256(witnesses[1].operatorIndex), 1); + for (uint256 i = 0; i < witnesses.length; i++) { + bytes32 leaf = calculateOperatorInfoLeaf(witnesses[i].operatorInfo); + bool ok = Merkle.verifyInclusionKeccak( + witnesses[i].operatorInfoProof, + info.operatorInfoTreeRoot, + leaf, + witnesses[i].operatorIndex + ); + assertTrue(ok); + } + BN254.G1Point memory expected = BN254.plus(bn254G1Key1, bn254G1Key2); + assertEq(nonSignerApk.X, expected.X); + assertEq(nonSignerApk.Y, expected.Y); + } + + function test_signingOperatorsWithDuplicatesAndUnknowns_base() public { + // Three operators + address[] memory operators = new address[](3); + operators[0] = operator1; + operators[1] = operator2; + operators[2] = operator3; + uint256[][] memory weights = new uint256[][](3); + weights[0] = _createSingleWeightArray(100)[0]; + weights[1] = _createSingleWeightArray(200)[0]; + weights[2] = _createSingleWeightArray(300)[0]; + calculator.setMockOperatorWeights(defaultOperatorSet, operators, weights); + + // Register all + _registerOperatorKey( + operator1, defaultOperatorSet, bn254G1Key1, bn254G2Key1, BN254_PRIV_KEY_1 + ); + _registerOperatorKey( + operator2, defaultOperatorSet, bn254G1Key2, bn254G2Key2, BN254_PRIV_KEY_2 + ); + _registerOperatorKey( + operator3, defaultOperatorSet, bn254G1Key3, bn254G2Key3, BN254_PRIV_KEY_3 + ); + + BN254OperatorSetInfo memory info = calculator.calculateOperatorTable(defaultOperatorSet); + + // Signing list with duplicate and unknown + address[] memory signing = new address[](3); + signing[0] = operator1; + signing[1] = operator1; + signing[2] = address(0xdead); + + ( + IBN254CertificateVerifierTypes.BN254OperatorInfoWitness[] memory witnesses, + BN254.G1Point memory nonSignerApk + ) = calculator.getNonSignerWitnessesAndApk(defaultOperatorSet, signing); + + // Expect operator2 and operator3 as non-signers + assertEq(witnesses.length, 2); + assertEq(uint256(witnesses[0].operatorIndex), 1); + assertEq(uint256(witnesses[1].operatorIndex), 2); + for (uint256 i = 0; i < witnesses.length; i++) { + bytes32 leaf = calculateOperatorInfoLeaf(witnesses[i].operatorInfo); + bool ok = Merkle.verifyInclusionKeccak( + witnesses[i].operatorInfoProof, + info.operatorInfoTreeRoot, + leaf, + witnesses[i].operatorIndex + ); + assertTrue(ok); + } + BN254.G1Point memory expected = BN254.plus(bn254G1Key2, bn254G1Key3); + assertEq(nonSignerApk.X, expected.X); + assertEq(nonSignerApk.Y, expected.Y); + } + + function testFuzz_nonSignerSelection_base( + uint8 maskRaw + ) public { + // Use up to 3 operators (keys already prepared) + uint8 n = 3; + address[] memory operators = new address[](n); + operators[0] = operator1; + operators[1] = operator2; + operators[2] = operator3; + uint256[][] memory weights = new uint256[][](n); + for (uint256 i = 0; i < n; i++) { + weights[i] = _createSingleWeightArray(100 + i)[0]; + } + calculator.setMockOperatorWeights(defaultOperatorSet, operators, weights); + + // Register all + _registerOperatorKey( + operator1, defaultOperatorSet, bn254G1Key1, bn254G2Key1, BN254_PRIV_KEY_1 + ); + _registerOperatorKey( + operator2, defaultOperatorSet, bn254G1Key2, bn254G2Key2, BN254_PRIV_KEY_2 + ); + _registerOperatorKey( + operator3, defaultOperatorSet, bn254G1Key3, bn254G2Key3, BN254_PRIV_KEY_3 + ); + + BN254OperatorSetInfo memory info = calculator.calculateOperatorTable(defaultOperatorSet); + + // Build signing subset by mask + uint8 mask = uint8(bound(maskRaw, 0, (1 << n) - 1)); + uint8 countSigners = 0; + for (uint8 i = 0; i < n; i++) { + if ((mask & (1 << i)) != 0) countSigners++; + } + address[] memory signing = new address[](countSigners); + uint8 s = 0; + for (uint8 i2 = 0; i2 < n; i2++) { + if ((mask & (1 << i2)) != 0) { + signing[s++] = operators[i2]; + } + } + + ( + IBN254CertificateVerifierTypes.BN254OperatorInfoWitness[] memory witnesses, + BN254.G1Point memory nonSignerApk + ) = calculator.getNonSignerWitnessesAndApk(defaultOperatorSet, signing); + + // Expected count and APK + uint256 expectedCount = n - countSigners; + assertEq(witnesses.length, expectedCount, "non-signer count mismatch"); + + // Check ordering strictly increasing and proofs + uint32 prev = 0; + for (uint256 i3 = 0; i3 < witnesses.length; i3++) { + if (i3 > 0) { + assertTrue(witnesses[i3].operatorIndex > prev, "indices must increase"); + } + prev = witnesses[i3].operatorIndex; + bytes32 leaf = calculateOperatorInfoLeaf(witnesses[i3].operatorInfo); + bool ok = Merkle.verifyInclusionKeccak( + witnesses[i3].operatorInfoProof, + info.operatorInfoTreeRoot, + leaf, + witnesses[i3].operatorIndex + ); + assertTrue(ok); + } + + // Compute expected APK + BN254.G1Point memory expectedApk; + for (uint8 i4 = 0; i4 < n; i4++) { + if ((mask & (1 << i4)) == 0) { + if (i4 == 0) expectedApk = BN254.plus(expectedApk, bn254G1Key1); + if (i4 == 1) expectedApk = BN254.plus(expectedApk, bn254G1Key2); + if (i4 == 2) expectedApk = BN254.plus(expectedApk, bn254G1Key3); + } + } + assertEq(nonSignerApk.X, expectedApk.X); + assertEq(nonSignerApk.Y, expectedApk.Y); + } + + function test_singleNonSigner_producesWitnessAndApk_base() public { + // Set operators and weights to include both operators + address[] memory operators = new address[](2); + operators[0] = operator1; + operators[1] = operator2; + uint256[][] memory weights = new uint256[][](2); + weights[0] = _createSingleWeightArray(100)[0]; + weights[1] = _createSingleWeightArray(200)[0]; + calculator.setMockOperatorWeights(defaultOperatorSet, operators, weights); + + // Register both operators + _registerOperatorKey( + operator1, defaultOperatorSet, bn254G1Key1, bn254G2Key1, BN254_PRIV_KEY_1 + ); + _registerOperatorKey( + operator2, defaultOperatorSet, bn254G1Key2, bn254G2Key2, BN254_PRIV_KEY_2 + ); + + BN254OperatorSetInfo memory info = calculator.calculateOperatorTable(defaultOperatorSet); + + address[] memory signing = new address[](1); + signing[0] = operator1; + + ( + IBN254CertificateVerifierTypes.BN254OperatorInfoWitness[] memory witnesses, + BN254.G1Point memory nonSignerApk + ) = calculator.getNonSignerWitnessesAndApk(defaultOperatorSet, signing); + + assertEq(witnesses.length, 1); + assertEq(uint256(witnesses[0].operatorIndex), 1); + bytes32 leaf = calculateOperatorInfoLeaf(witnesses[0].operatorInfo); + bool ok = Merkle.verifyInclusionKeccak( + witnesses[0].operatorInfoProof, + info.operatorInfoTreeRoot, + leaf, + witnesses[0].operatorIndex + ); + assertTrue(ok); + assertEq(nonSignerApk.X, bn254G1Key2.X); + assertEq(nonSignerApk.Y, bn254G1Key2.Y); + } + + function test_multipleNonSigners_sortedIndicesAndValidProofs_base() public { + // Include three operators + address[] memory operators = new address[](3); + operators[0] = operator1; + operators[1] = operator2; + operators[2] = operator3; + uint256[][] memory weights = new uint256[][](3); + weights[0] = _createSingleWeightArray(100)[0]; + weights[1] = _createSingleWeightArray(101)[0]; + weights[2] = _createSingleWeightArray(102)[0]; + calculator.setMockOperatorWeights(defaultOperatorSet, operators, weights); + + // Register all + _registerOperatorKey( + operator1, defaultOperatorSet, bn254G1Key1, bn254G2Key1, BN254_PRIV_KEY_1 + ); + _registerOperatorKey( + operator2, defaultOperatorSet, bn254G1Key2, bn254G2Key2, BN254_PRIV_KEY_2 + ); + _registerOperatorKey( + operator3, defaultOperatorSet, bn254G1Key3, bn254G2Key3, BN254_PRIV_KEY_3 + ); + + BN254OperatorSetInfo memory info = calculator.calculateOperatorTable(defaultOperatorSet); + + address[] memory signing = new address[](1); + signing[0] = operator1; + ( + IBN254CertificateVerifierTypes.BN254OperatorInfoWitness[] memory witnesses, + BN254.G1Point memory nonSignerApk + ) = calculator.getNonSignerWitnessesAndApk(defaultOperatorSet, signing); + + assertEq(witnesses.length, 2); + assertEq(uint256(witnesses[0].operatorIndex), 1); + assertEq(uint256(witnesses[1].operatorIndex), 2); + for (uint256 i = 0; i < witnesses.length; i++) { + bytes32 leaf = calculateOperatorInfoLeaf(witnesses[i].operatorInfo); + bool ok = Merkle.verifyInclusionKeccak( + witnesses[i].operatorInfoProof, + info.operatorInfoTreeRoot, + leaf, + witnesses[i].operatorIndex + ); + assertTrue(ok); + } + BN254.G1Point memory expected = BN254.plus(bn254G1Key2, bn254G1Key3); + assertEq(nonSignerApk.X, expected.X); + assertEq(nonSignerApk.Y, expected.Y); + } + + function test_allSigners_returnsEmpty_base() public { + // Two operators, both sign + address[] memory operators = new address[](2); + operators[0] = operator1; + operators[1] = operator2; + uint256[][] memory weights = new uint256[][](2); + weights[0] = _createSingleWeightArray(100)[0]; + weights[1] = _createSingleWeightArray(200)[0]; + calculator.setMockOperatorWeights(defaultOperatorSet, operators, weights); + + _registerOperatorKey( + operator1, defaultOperatorSet, bn254G1Key1, bn254G2Key1, BN254_PRIV_KEY_1 + ); + _registerOperatorKey( + operator2, defaultOperatorSet, bn254G1Key2, bn254G2Key2, BN254_PRIV_KEY_2 + ); + + address[] memory signing = new address[](2); + signing[0] = operator1; + signing[1] = operator2; + ( + IBN254CertificateVerifierTypes.BN254OperatorInfoWitness[] memory witnesses, + BN254.G1Point memory nonSignerApk + ) = calculator.getNonSignerWitnessesAndApk(defaultOperatorSet, signing); + assertEq(witnesses.length, 0); + assertEq(nonSignerApk.X, 0); + assertEq(nonSignerApk.Y, 0); + } + + function test_unregisteredExcludedFromTree_base() public { + // Three operators, second unregistered + address[] memory operators = new address[](3); + operators[0] = operator1; + operators[1] = operator2; // will remain unregistered + operators[2] = operator3; + uint256[][] memory weights = new uint256[][](3); + weights[0] = _createSingleWeightArray(100)[0]; + weights[1] = _createSingleWeightArray(200)[0]; + weights[2] = _createSingleWeightArray(300)[0]; + calculator.setMockOperatorWeights(defaultOperatorSet, operators, weights); + + _registerOperatorKey( + operator1, defaultOperatorSet, bn254G1Key1, bn254G2Key1, BN254_PRIV_KEY_1 + ); + _registerOperatorKey( + operator3, defaultOperatorSet, bn254G1Key3, bn254G2Key3, BN254_PRIV_KEY_3 + ); + + BN254OperatorSetInfo memory info = calculator.calculateOperatorTable(defaultOperatorSet); + assertEq(info.numOperators, 2); + + address[] memory signing = new address[](1); + signing[0] = operator1; + ( + IBN254CertificateVerifierTypes.BN254OperatorInfoWitness[] memory witnesses, + BN254.G1Point memory nonSignerApk + ) = calculator.getNonSignerWitnessesAndApk(defaultOperatorSet, signing); + assertEq(witnesses.length, 1); + assertEq(uint256(witnesses[0].operatorIndex), 1); + bytes32 leaf = calculateOperatorInfoLeaf(witnesses[0].operatorInfo); + bool ok = Merkle.verifyInclusionKeccak( + witnesses[0].operatorInfoProof, + info.operatorInfoTreeRoot, + leaf, + witnesses[0].operatorIndex + ); + assertTrue(ok); + assertEq(nonSignerApk.X, bn254G1Key3.X); + assertEq(nonSignerApk.Y, bn254G1Key3.Y); + } +} diff --git a/test/unit/middlewareV2/BN254TableCalculatorUnit.t.sol b/test/unit/middlewareV2/BN254TableCalculatorUnit.t.sol index 889941cc5..e286e4a10 100644 --- a/test/unit/middlewareV2/BN254TableCalculatorUnit.t.sol +++ b/test/unit/middlewareV2/BN254TableCalculatorUnit.t.sol @@ -43,6 +43,7 @@ contract BN254TableCalculatorHarness is BN254TableCalculator { * @notice Base contract for all BN254TableCalculator unit tests */ contract BN254TableCalculatorUnitTests is MockEigenLayerDeployer, IOperatorTableCalculatorTypes { + using BN254 for BN254.G1Point; using OperatorSetLib for OperatorSet; // Test contracts diff --git a/test/unit/middlewareV2/MockDeployer.sol b/test/unit/middlewareV2/MockDeployer.sol index ba73cb71e..8cb02176a 100644 --- a/test/unit/middlewareV2/MockDeployer.sol +++ b/test/unit/middlewareV2/MockDeployer.sol @@ -60,7 +60,7 @@ abstract contract MockEigenLayerDeployer is Test { keyRegistrarMock = new KeyRegistrarMock(); // Deploy the actual PermissionController & KeyRegistrar implementations - permissionControllerImplementation = new PermissionController("9.9.9"); + permissionControllerImplementation = new PermissionController(); permissionController = PermissionController( address( new TransparentUpgradeableProxy( diff --git a/test/utils/CoreDeployLib.sol b/test/utils/CoreDeployLib.sol index 357e1a969..53ec6591d 100644 --- a/test/utils/CoreDeployLib.sol +++ b/test/utils/CoreDeployLib.sol @@ -6,6 +6,8 @@ import {stdJson} from "forge-std/StdJson.sol"; import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {AllocationManagerView} from + "eigenlayer-contracts/src/contracts/core/AllocationManagerView.sol"; import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; import {DelegationManager} from "eigenlayer-contracts/src/contracts/core/DelegationManager.sol"; import {StrategyManager} from "eigenlayer-contracts/src/contracts/core/StrategyManager.sol"; @@ -27,6 +29,8 @@ import { import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; import {IBeacon} from "@openzeppelin/contracts/proxy/beacon/IBeacon.sol"; +import {IAllocationManagerView} from + "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol"; import {IStrategyManager} from "eigenlayer-contracts/src/contracts/interfaces/IStrategyManager.sol"; import {IEigenPodManager} from "eigenlayer-contracts/src/contracts/interfaces/IEigenPodManager.sol"; import {IAVSDirectory} from "eigenlayer-contracts/src/contracts/interfaces/IAVSDirectory.sol"; @@ -165,7 +169,7 @@ library CoreDeployLib { DeploymentConfigData memory config ) internal { // Deploy core implementations - address permissionControllerImpl = address(new PermissionController("1.0.0")); + address permissionControllerImpl = address(new PermissionController()); address strategyManagerImpl = address( new StrategyManager( @@ -176,15 +180,24 @@ library CoreDeployLib { ) ); + address allocationManagerView = address( + new AllocationManagerView( + IDelegationManager(deployments.delegationManager), + IStrategy(deployments.eigenStrategy), + config.allocationManager.deallocationDelay, + config.allocationManager.allocationConfigurationDelay + ) + ); + address allocationManagerImpl = address( new AllocationManager( + IAllocationManagerView(allocationManagerView), IDelegationManager(deployments.delegationManager), IStrategy(deployments.eigenStrategy), IPauserRegistry(deployments.pauserRegistry), IPermissionController(deployments.permissionController), config.allocationManager.deallocationDelay, - config.allocationManager.allocationConfigurationDelay, - "1.0.0" + config.allocationManager.allocationConfigurationDelay ) ); @@ -259,9 +272,7 @@ library CoreDeployLib { address eigenPodImpl = address( new EigenPod( - IETHPOSDeposit(ethPOSDeposit), - IEigenPodManager(deployments.eigenPodManager), - "1.0.0" + IETHPOSDeposit(ethPOSDeposit), IEigenPodManager(deployments.eigenPodManager) ) ); @@ -273,8 +284,7 @@ library CoreDeployLib { IETHPOSDeposit(ethPOSDeposit), IBeacon(deployments.eigenPodBeacon), IDelegationManager(deployments.delegationManager), - IPauserRegistry(deployments.pauserRegistry), - "1.0.0" + IPauserRegistry(deployments.pauserRegistry) ) ); @@ -294,8 +304,7 @@ library CoreDeployLib { address baseStrategyImpl = address( new StrategyBase( IStrategyManager(deployments.strategyManager), - IPauserRegistry(deployments.pauserRegistry), - "1.0.0" + IPauserRegistry(deployments.pauserRegistry) ) ); @@ -304,8 +313,7 @@ library CoreDeployLib { address strategyFactoryImpl = address( new StrategyFactory( IStrategyManager(deployments.strategyManager), - IPauserRegistry(deployments.pauserRegistry), - "1.0.0" + IPauserRegistry(deployments.pauserRegistry) ) ); @@ -338,8 +346,7 @@ library CoreDeployLib { MAX_REWARDS_DURATION: config.rewardsCoordinator.maxRewardsDuration, MAX_RETROACTIVE_LENGTH: config.rewardsCoordinator.maxRetroactiveLength, MAX_FUTURE_LENGTH: config.rewardsCoordinator.maxFutureLength, - GENESIS_REWARDS_TIMESTAMP: config.rewardsCoordinator.genesisRewardsTimestamp, - version: "1.0.0" + GENESIS_REWARDS_TIMESTAMP: config.rewardsCoordinator.genesisRewardsTimestamp }) ) ); diff --git a/test/utils/MockAVSDeployer.sol b/test/utils/MockAVSDeployer.sol index d786de1d1..114502e18 100644 --- a/test/utils/MockAVSDeployer.sol +++ b/test/utils/MockAVSDeployer.sol @@ -55,6 +55,8 @@ import {PermissionController} from import {AllocationManager} from "eigenlayer-contracts/src/contracts/core/AllocationManager.sol"; import {IRewardsCoordinator} from "eigenlayer-contracts/src/contracts/interfaces/IRewardsCoordinator.sol"; +import {AllocationManagerView} from + "eigenlayer-contracts/src/contracts/core/AllocationManagerView.sol"; import {BLSApkRegistryHarness} from "../harnesses/BLSApkRegistryHarness.sol"; import {EmptyContract} from "eigenlayer-contracts/src/test/mocks/EmptyContract.sol"; @@ -96,6 +98,7 @@ contract MockAVSDeployer is Test { AVSDirectory public avsDirectoryImplementation; AVSDirectoryMock public avsDirectoryMock; AllocationManagerMock public allocationManagerMock; + AllocationManagerView public allocationManagerView; AllocationManager public allocationManager; AllocationManager public allocationManagerImplementation; RewardsCoordinator public rewardsCoordinator; @@ -152,6 +155,11 @@ contract MockAVSDeployer is Test { uint256 MAX_QUORUM_BITMAP = type(uint192).max; + /// @notice The deallocation delay for the AllocationManager + uint32 public constant DEALLOCATION_DELAY = 7 days; + /// @notice The allocation configuration delay for the AllocationManager + uint32 public constant ALLOCATION_CONFIGURATION_DELAY = 1 days; + function _deployMockEigenLayerAndAVS() internal { _deployMockEigenLayerAndAVS(numQuorums); } @@ -276,20 +284,25 @@ contract MockAVSDeployer is Test { ); IStrategy eigenStrategy = IStrategy( - new EigenStrategy( - IStrategyManager(address(strategyManagerMock)), pauserRegistry, "v0.0.1" - ) + new EigenStrategy(IStrategyManager(address(strategyManagerMock)), pauserRegistry) ); - allocationManagerImplementation = new AllocationManager( - delegationMock, - eigenStrategy, - pauserRegistry, - permissionControllerMock, - uint32(7 days), // DEALLOCATION_DELAY - uint32(1 days), // ALLOCATION_CONFIGURATION_DELAY - "v0.0.1" // Added config parameter - ); + allocationManagerView = new AllocationManagerView({ + _delegation: delegationMock, + _eigenStrategy: eigenStrategy, + _DEALLOCATION_DELAY: DEALLOCATION_DELAY, + _ALLOCATION_CONFIGURATION_DELAY: ALLOCATION_CONFIGURATION_DELAY + }); + + allocationManagerImplementation = new AllocationManager({ + _allocationManagerView: allocationManagerView, + _delegation: delegationMock, + _eigenStrategy: eigenStrategy, + _pauserRegistry: pauserRegistry, + _permissionController: permissionControllerMock, + _DEALLOCATION_DELAY: DEALLOCATION_DELAY, + _ALLOCATION_CONFIGURATION_DELAY: ALLOCATION_CONFIGURATION_DELAY + }); proxyAdmin.upgrade( ITransparentUpgradeableProxy(payable(address(allocationManager))), address(allocationManagerImplementation) From 9ae71e3a3fe07028e152b5c31df71170caf9f611 Mon Sep 17 00:00:00 2001 From: Yash Patil <40046473+ypatil12@users.noreply.github.com> Date: Mon, 1 Dec 2025 14:50:46 -0500 Subject: [PATCH 2/4] feat: update registry coordinator for new createOperatorSets (#548) **Motivation:** As part of https://github.com/Layr-Labs/eigenlayer-contracts/pull/1645, we are adding a new `createOperatorSets` function that passes in the slasher address upon creation of an operatorSet. Although the *old* `createOperatorSets` function is not being deprecated, we are updating the `RegistryCoordinator` in order to be **only compatible the new function**. **Modifications:** - Update `createSlashableStakeQuorum` to take in a `slasher` address - Update all tests - Update `solc` version in `foundry.toml` to `0.8.29`, matching Core **Result:** Compatible with updated `AllocationManager` interface --- .github/workflows/storage-report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/storage-report.yml b/.github/workflows/storage-report.yml index fa196822d..ddd22a2e8 100644 --- a/.github/workflows/storage-report.yml +++ b/.github/workflows/storage-report.yml @@ -22,7 +22,7 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 with: - version: v1.3.5 + version: nightly - name: "Generate and prepare the storage reports for current branch" run: | From 7e64cd837b4717e4ec56e7ea14815e03f11c6fc8 Mon Sep 17 00:00:00 2001 From: Yash Patil <40046473+ypatil12@users.noreply.github.com> Date: Tue, 2 Dec 2025 18:43:07 -0500 Subject: [PATCH 3/4] docs: v2.0.0 release notes; feat: rev contract submodule (#550) Release notes for v2.0.0. Also, rev version for core submodule. --- CHANGELOG/CHANGELOG-2.0.0.md | 20 ++++++++++++++++++++ foundry.lock | 2 +- lib/eigenlayer-contracts | 2 +- test/mocks/PermissionControllerMock.sol | 4 ++-- 4 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 CHANGELOG/CHANGELOG-2.0.0.md diff --git a/CHANGELOG/CHANGELOG-2.0.0.md b/CHANGELOG/CHANGELOG-2.0.0.md new file mode 100644 index 000000000..e02270437 --- /dev/null +++ b/CHANGELOG/CHANGELOG-2.0.0.md @@ -0,0 +1,20 @@ +# v2.0.0 UX Improvements + +This release brings 2 UX improvements to the middleware repo. We increment the major release due to the new `createSlashableStakeQuorum` interface. + +🚀 New Features + +- NonSigner View Function for Bn254 Table Calculator: Constructs nonsigner witness onchain by passing in a list of signers + +⛔ Breaking Changes +- Update `createSlashableStakeQuorum` to take in a slasher address + +🔧 Improvements +- Update `foundry.toml` solc to 0.8.29 + +## Changelog + + +- feat: update registry coordinator for new createOperatorSets [PR #548](https://github.com/layr-labs/eigenlayer-middleware/pull/548) +- feat: nonsigner view and operator index [PR #545](https://github.com/Layr-Labs/eigenlayer-middleware/pull/542) +- chore: update readMe for middlewarev2 deployment [PR #539](https://github.com/layr-labs/eigenlayer-middleware/pull/539) \ No newline at end of file diff --git a/foundry.lock b/foundry.lock index 31cfd8694..d05471236 100644 --- a/foundry.lock +++ b/foundry.lock @@ -1,6 +1,6 @@ { "lib/eigenlayer-contracts": { - "rev": "b9c1a6118534d19dafec502849e557ea68dff494" + "rev": "1564be7b5ac19af78ef58cd3b153bd88ef9fcad4" }, "lib/forge-std": { "rev": "77041d2ce690e692d6e03cc812b57d1ddaa4d505" diff --git a/lib/eigenlayer-contracts b/lib/eigenlayer-contracts index b9c1a6118..1564be7b5 160000 --- a/lib/eigenlayer-contracts +++ b/lib/eigenlayer-contracts @@ -1 +1 @@ -Subproject commit b9c1a6118534d19dafec502849e557ea68dff494 +Subproject commit 1564be7b5ac19af78ef58cd3b153bd88ef9fcad4 diff --git a/test/mocks/PermissionControllerMock.sol b/test/mocks/PermissionControllerMock.sol index d66731844..b42327346 100644 --- a/test/mocks/PermissionControllerMock.sol +++ b/test/mocks/PermissionControllerMock.sol @@ -55,13 +55,13 @@ contract PermissionControllerIntermediate is IPermissionController { function getAppointeePermissions( address account, address appointee - ) external virtual returns (address[] memory, bytes4[] memory) {} + ) external view virtual returns (address[] memory, bytes4[] memory) {} function getAppointees( address account, address target, bytes4 selector - ) external virtual returns (address[] memory) {} + ) external view virtual returns (address[] memory) {} /** * @notice Returns the version of the contract From 4d10bb42714c12845e1911a6bb98b07b58269adb Mon Sep 17 00:00:00 2001 From: Yash Patil <40046473+ypatil12@users.noreply.github.com> Date: Wed, 3 Dec 2025 14:03:18 -0500 Subject: [PATCH 4/4] docs: slashable stake quorum (#551) **Motivation:** Natspec and docs were missing changes to the `createTotalDelegatedStakeQuorum` and `createTotalSlashableStakeQuorum`. **Modifications:** Update docs and natspec. **Result:** Up to date docs. --------- Co-authored-by: Nadir Akhtar <9601075+nadir-akhtar@users.noreply.github.com> --- docs/SlashingRegistryCoordinator.md | 75 ++++++++++++------- .../ISlashingRegistryCoordinator.sol | 2 +- 2 files changed, 50 insertions(+), 27 deletions(-) diff --git a/docs/SlashingRegistryCoordinator.md b/docs/SlashingRegistryCoordinator.md index 672779ec7..3f2dc86e6 100644 --- a/docs/SlashingRegistryCoordinator.md +++ b/docs/SlashingRegistryCoordinator.md @@ -66,20 +66,31 @@ struct OperatorSetParam { #### `createTotalDelegatedStakeQuorum` ```solidity +/** + * @notice Creates a new quorum that tracks total delegated stake for operators. + * @param operatorSetParams Configures the quorum's max operator count and churn parameters. + * @param minimumStake Sets the minimum stake required for an operator to register or remain registered. + * @param strategyParams A list of strategies and multipliers used by the StakeRegistry to calculate + * an operator's stake weight for the quorum. + * @dev For m2 AVS this function has the same behavior as createQuorum before. + * @dev For migrated AVS that enable operator sets this will create a quorum that measures total delegated stake for operator set. + * @dev The slasher is set to DELEGATED_STAKE_SLASHER for total delegated stake quorums. This address cannot slash an operatorSet. + */ function createTotalDelegatedStakeQuorum( OperatorSetParam memory operatorSetParams, uint96 minimumStake, IStakeRegistryTypes.StrategyParams[] memory strategyParams -) - external +) external; ``` This function creates a new quorum that tracks the total delegated stake for operators. The quorum is initialized with the provided parameters and integrated with the underlying registry contracts. +Note that this function *does not* allow for stake to be slashed. To create a quorum with slashable delegated stake, see [`createSlashableStakeQuorum`](#createslashablestakequorum). + *Effects:* * Increments the `quorumCount` by 1 * Sets the operator set parameters for the new quorum -* Creates an operator set in the `AllocationManager` +* Creates an operator set in the `AllocationManager` with a slasher address that is the the `DELEGATED_STAKE_SLASHER` * Initializes the quorum in all registry contracts: * `StakeRegistry`: Sets minimum stake and strategy parameters * `IndexRegistry`: Prepares the quorum for tracking operator indices @@ -93,19 +104,31 @@ This function creates a new quorum that tracks the total delegated stake for ope #### `createSlashableStakeQuorum` ```solidity +/** + * @notice Creates a new quorum that tracks slashable stake for operators. + * @param operatorSetParams Configures the quorum's max operator count and churn parameters. + * @param minimumStake Sets the minimum stake required for an operator to register or remain registered. + * @param strategyParams A list of strategies and multipliers used by the StakeRegistry to calculate + * an operator's stake weight for the quorum. + * @param lookAheadPeriod The number of blocks to look ahead when calculating slashable stake. + * @param slasher The address of the slasher to use for the operatorSet (quorum) in EigenLayer core + * @dev Can only be called when operator sets are enabled. + */ function createSlashableStakeQuorum( OperatorSetParam memory operatorSetParams, uint96 minimumStake, IStakeRegistryTypes.StrategyParams[] memory strategyParams, - uint32 lookAheadPeriod -) - external + uint32 lookAheadPeriod, + address slasher +) external; ``` This function creates a new quorum that specifically tracks slashable stake for operators. This type of quorum provides slashing enforcement through the `AllocationManager`. *Effects:* -* Same as `createTotalDelegatedStakeQuorum`, but initializes the quorum with slashable stake type +* Same as `createTotalDelegatedStakeQuorum`, but + - initializes the quorum with slashable stake type + - Sets the `slasher` to an address that is controlled by the AVS * Additionally configures the `lookAheadPeriod` for slashable stake calculation *Requirements:* @@ -117,7 +140,7 @@ This function creates a new quorum that specifically tracks slashable stake for function setOperatorSetParams( uint8 quorumNumber, OperatorSetParam memory operatorSetParams -) +) external ``` @@ -151,7 +174,7 @@ function registerOperator( address avs, uint32[] calldata operatorSetIds, bytes calldata data -) +) external onlyAllocationManager onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) @@ -184,7 +207,7 @@ function deregisterOperator( address operator, address avs, uint32[] calldata operatorSetIds -) +) external onlyAllocationManager onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) @@ -210,7 +233,7 @@ This function is called by the `AllocationManager` when an operator wants to der ```solidity function updateSocket( string memory socket -) +) external ``` @@ -229,7 +252,7 @@ This function allows a registered operator to update their socket information. function ejectOperator( address operator, bytes memory quorumNumbers -) +) external onlyEjector ``` @@ -261,7 +284,7 @@ The `SlashingRegistryCoordinator` manages operator stakes through the `StakeRegi function updateOperatorsForQuorum( address[][] memory operatorsPerQuorum, bytes calldata quorumNumbers -) +) external ``` @@ -308,18 +331,18 @@ The contract implements two helper functions to calculate these thresholds: function _individualKickThreshold( uint96 operatorStake, OperatorSetParam memory setParams -) - internal - pure +) + internal + pure ``` ```solidity function _totalKickThreshold( uint96 totalStake, OperatorSetParam memory setParams -) - internal - pure +) + internal + pure ``` #### Churn Approval @@ -340,7 +363,7 @@ function calculateOperatorChurnApprovalDigestHash( OperatorKickParam[] memory operatorKickParams, bytes32 salt, uint256 expiry -) +) public ``` @@ -349,7 +372,7 @@ function calculateOperatorChurnApprovalDigestHash( ```solidity function setChurnApprover( address _churnApprover -) +) external ``` @@ -377,11 +400,11 @@ The `SlashingRegistryCoordinator` integrates with `AllocationManager`, and is id ```solidity function setAVS( address _avs -) +) external ``` -This function sets the AVS address for the AVS (this identitiy is used for UAM integration). Note: updating this will break existing operator sets, this value should only be set once. +This function sets the AVS address for the AVS (this identitiy is used for UAM integration). Note: updating this will break existing operator sets, this value should only be set once. This value should be the address of the `ServiceManager` contract. *Effects:* @@ -395,7 +418,7 @@ This value should be the address of the `ServiceManager` contract. ```solidity function supportsAVS( address _avs -) +) public ``` @@ -420,7 +443,7 @@ These functions allow the contract owner to configure various parameters and rol ```solidity function setEjector( address _ejector -) +) external ``` @@ -438,7 +461,7 @@ This function updates the address that is authorized to forcibly eject operators ```solidity function setEjectionCooldown( uint256 _ejectionCooldown -) +) external ``` diff --git a/src/interfaces/ISlashingRegistryCoordinator.sol b/src/interfaces/ISlashingRegistryCoordinator.sol index 08e90a608..2c450ca8e 100644 --- a/src/interfaces/ISlashingRegistryCoordinator.sol +++ b/src/interfaces/ISlashingRegistryCoordinator.sol @@ -393,6 +393,7 @@ interface ISlashingRegistryCoordinator is * an operator's stake weight for the quorum. * @dev For m2 AVS this function has the same behavior as createQuorum before. * @dev For migrated AVS that enable operator sets this will create a quorum that measures total delegated stake for operator set. + * @dev The slasher is set to DELEGATED_STAKE_SLASHER for total delegated stake quorums. This address cannot slash an operatorSet. */ function createTotalDelegatedStakeQuorum( OperatorSetParam memory operatorSetParams, @@ -409,7 +410,6 @@ interface ISlashingRegistryCoordinator is * @param lookAheadPeriod The number of blocks to look ahead when calculating slashable stake. * @param slasher The address of the slasher to use for the operatorSet (quorum) in EigenLayer core * @dev Can only be called when operator sets are enabled. - * @dev The lookahead period is set to 0 and slasher is set to DELEGATED_STAKE_SLASHER for total delegated stake quorums */ function createSlashableStakeQuorum( OperatorSetParam memory operatorSetParams,