From d0615dd8f7acb280cb341fe54390b0362aec5636 Mon Sep 17 00:00:00 2001 From: Yash Patil <40046473+ypatil12@users.noreply.github.com> Date: Mon, 11 Dec 2023 13:29:32 -0500 Subject: [PATCH 1/6] update: update submodule --- lib/eigenlayer-contracts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/eigenlayer-contracts b/lib/eigenlayer-contracts index b91a743b..1da8330d 160000 --- a/lib/eigenlayer-contracts +++ b/lib/eigenlayer-contracts @@ -1 +1 @@ -Subproject commit b91a743b9099762ef190ef855eac059df04e9dcf +Subproject commit 1da8330d389f6f3d267957f458935da4c8df3b0a From 791d22507eef3dec720a63b31e90259b06832243 Mon Sep 17 00:00:00 2001 From: Yash Patil <40046473+ypatil12@users.noreply.github.com> Date: Mon, 11 Dec 2023 16:38:50 -0500 Subject: [PATCH 2/6] feat: add avs registration to middleware --- lib/eigenlayer-contracts | 2 +- src/RegistryCoordinator.sol | 40 ++++++++--- src/interfaces/IRegistryCoordinator.sol | 2 +- test/harnesses/RegistryCoordinatorHarness.sol | 3 +- test/unit/RegistryCoordinatorUnit.t.sol | 69 +++++++++++++------ test/unit/StakeRegistryUnit.t.sol | 1 + test/utils/MockAVSDeployer.sol | 9 ++- 7 files changed, 90 insertions(+), 36 deletions(-) diff --git a/lib/eigenlayer-contracts b/lib/eigenlayer-contracts index 1da8330d..a7bb3d8c 160000 --- a/lib/eigenlayer-contracts +++ b/lib/eigenlayer-contracts @@ -1 +1 @@ -Subproject commit 1da8330d389f6f3d267957f458935da4c8df3b0a +Subproject commit a7bb3d8ca4374c5cbb95324916cb44727e0ecadc diff --git a/src/RegistryCoordinator.sol b/src/RegistryCoordinator.sol index e074a203..91fcc6fe 100644 --- a/src/RegistryCoordinator.sol +++ b/src/RegistryCoordinator.sol @@ -9,6 +9,7 @@ import {EIP1271SignatureUtils} from "eigenlayer-contracts/src/contracts/librarie import {IPauserRegistry} from "eigenlayer-contracts/src/contracts/interfaces/IPauserRegistry.sol"; import {Pausable} from "eigenlayer-contracts/src/contracts/permissions/Pausable.sol"; import {ISlasher} from "eigenlayer-contracts/src/contracts/interfaces/ISlasher.sol"; +import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; import {IRegistryCoordinator} from "src/interfaces/IRegistryCoordinator.sol"; import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol"; @@ -62,6 +63,8 @@ contract RegistryCoordinator is IStakeRegistry public immutable stakeRegistry; /// @notice the Index Registry contract that will keep track of operators' indexes IIndexRegistry public immutable indexRegistry; + /// @notice The Delegation Manager contract to record operator avs relationships + IDelegationManager public immutable delegationManager; /// @notice the current number of quorums supported by the registry coordinator uint8 public quorumCount; @@ -98,11 +101,13 @@ contract RegistryCoordinator is } constructor( + IDelegationManager _delegationManager, ISlasher _slasher, IStakeRegistry _stakeRegistry, IBLSApkRegistry _blsApkRegistry, IIndexRegistry _indexRegistry ) EIP712("AVSRegistryCoordinator", "v0.0.1") { + delegationManager = _delegationManager; slasher = _slasher; stakeRegistry = _stakeRegistry; blsApkRegistry = _blsApkRegistry; @@ -151,10 +156,13 @@ contract RegistryCoordinator is * @notice Registers msg.sender as an operator for one or more quorums. If any quorum reaches its maximum * operator capacity, this method will fail. * @param quorumNumbers is an ordered byte array containing the quorum numbers being registered for + * @param socket is the socket of the operator + * @param operatorSignature is the signature of the operator used by the AVS to register the operator in the delegation manager */ function registerOperator( bytes calldata quorumNumbers, - string calldata socket + string calldata socket, + SignatureWithSaltAndExpiry memory operatorSignature ) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) { bytes32 operatorId = blsApkRegistry.getOperatorId(msg.sender); @@ -163,7 +171,8 @@ contract RegistryCoordinator is operator: msg.sender, operatorId: operatorId, quorumNumbers: quorumNumbers, - socket: socket + socket: socket, + operatorSignature: operatorSignature }); for (uint256 i = 0; i < quorumNumbers.length; i++) { @@ -189,12 +198,14 @@ contract RegistryCoordinator is * @param operatorKickParams are used to determine which operator is removed to maintain quorum capacity as the * operator registers for quorums. * @param churnApproverSignature is the signature of the churnApprover on the operator kick params + * @param operatorSignature is the signature of the operator used by the AVS to register the operator in the delegation manager */ function registerOperatorWithChurn( bytes calldata quorumNumbers, string calldata socket, OperatorKickParam[] calldata operatorKickParams, - SignatureWithSaltAndExpiry memory churnApproverSignature + SignatureWithSaltAndExpiry memory churnApproverSignature, + SignatureWithSaltAndExpiry memory operatorSignature ) external onlyWhenNotPaused(PAUSED_REGISTER_OPERATOR) { require(operatorKickParams.length == quorumNumbers.length, "RegistryCoordinator.registerOperatorWithChurn: input length mismatch"); @@ -212,10 +223,10 @@ contract RegistryCoordinator is operator: msg.sender, operatorId: operatorId, quorumNumbers: quorumNumbers, - socket: socket + socket: socket, + operatorSignature: operatorSignature }); - uint256 kickIndex = 0; for (uint256 i = 0; i < quorumNumbers.length; i++) { uint8 quorumNumber = uint8(quorumNumbers[i]); @@ -236,7 +247,6 @@ contract RegistryCoordinator is }); _deregisterOperator(operatorKickParams[i].operator, quorumNumbers[i:i+1]); - kickIndex++; } } } @@ -404,6 +414,15 @@ contract RegistryCoordinator is _setEjector(_ejector); } + /** + * @notice Sets the metadata URI for the AVS + * @param _metadataURI is the metadata URI for the AVS + * @dev only callable by the service manager owner + */ + function setMetadataURI(string memory _metadataURI) external onlyServiceManagerOwner { + delegationManager.updateAVSMetadataURI(_metadataURI); + } + /******************************************************************************* INTERNAL FUNCTIONS *******************************************************************************/ @@ -422,7 +441,8 @@ contract RegistryCoordinator is address operator, bytes32 operatorId, bytes calldata quorumNumbers, - string memory socket + string memory socket, + SignatureWithSaltAndExpiry memory operatorSignature ) internal virtual returns (RegisterResults memory) { /** * Get bitmap of quorums to register for and operator's current bitmap. Validate that: @@ -454,6 +474,9 @@ contract RegistryCoordinator is status: OperatorStatus.REGISTERED }); + // Register the operator with the delegation manager + delegationManager.registerOperatorToAVS(operator, operatorSignature); + emit OperatorRegistered(operator, operatorId); } @@ -533,9 +556,10 @@ contract RegistryCoordinator is newBitmap: newBitmap }); - // If the operator is no longer registered for any quorums, update their status + // If the operator is no longer registered for any quorums, update their status and deregister from delegationManager if (newBitmap.isEmpty()) { operatorInfo.status = OperatorStatus.DEREGISTERED; + // delegationManager.deregisterOperatorFromAVS(operator); emit OperatorDeregistered(operator, operatorId); } diff --git a/src/interfaces/IRegistryCoordinator.sol b/src/interfaces/IRegistryCoordinator.sol index ccbd8a86..02b72a5e 100644 --- a/src/interfaces/IRegistryCoordinator.sol +++ b/src/interfaces/IRegistryCoordinator.sol @@ -26,7 +26,7 @@ interface IRegistryCoordinator { /// @notice emitted when all the operators for a quorum are updated at once event QuorumBlockNumberUpdated(uint8 indexed quorumNumber, uint256 blocknumber); - + // DATA STRUCTURES enum OperatorStatus { diff --git a/test/harnesses/RegistryCoordinatorHarness.sol b/test/harnesses/RegistryCoordinatorHarness.sol index eff9bead..246ea705 100644 --- a/test/harnesses/RegistryCoordinatorHarness.sol +++ b/test/harnesses/RegistryCoordinatorHarness.sol @@ -6,11 +6,12 @@ import "src/RegistryCoordinator.sol"; // wrapper around the RegistryCoordinator contract that exposes the internal functions for unit testing. contract RegistryCoordinatorHarness is RegistryCoordinator { constructor( + IDelegationManager _delegationManager, ISlasher _slasher, IStakeRegistry _stakeRegistry, IBLSApkRegistry _blsApkRegistry, IIndexRegistry _indexRegistry - ) RegistryCoordinator(_slasher, _stakeRegistry, _blsApkRegistry, _indexRegistry) { + ) RegistryCoordinator(_delegationManager, _slasher, _stakeRegistry, _blsApkRegistry, _indexRegistry) { _transferOwnership(msg.sender); } diff --git a/test/unit/RegistryCoordinatorUnit.t.sol b/test/unit/RegistryCoordinatorUnit.t.sol index 4360a7f1..3f20e23d 100644 --- a/test/unit/RegistryCoordinatorUnit.t.sol +++ b/test/unit/RegistryCoordinatorUnit.t.sol @@ -116,41 +116,50 @@ contract RegistryCoordinatorUnit is MockAVSDeployer { function testRegisterOperatorWithCoordinator_WhenPaused_Reverts() public { bytes memory emptyQuorumNumbers = new bytes(0); + ISignatureUtils.SignatureWithSaltAndExpiry memory emptySig; + // pause registerOperator cheats.prank(pauser); registryCoordinator.pause(2 ** PAUSED_REGISTER_OPERATOR); cheats.startPrank(defaultOperator); cheats.expectRevert(bytes("Pausable: index is paused")); - registryCoordinator.registerOperator(emptyQuorumNumbers, defaultSocket); + registryCoordinator.registerOperator(emptyQuorumNumbers, defaultSocket, emptySig); } function testRegisterOperatorWithCoordinator_EmptyQuorumNumbers_Reverts() public { bytes memory emptyQuorumNumbers = new bytes(0); + ISignatureUtils.SignatureWithSaltAndExpiry memory emptySig; + cheats.expectRevert("RegistryCoordinator._registerOperator: bitmap cannot be 0"); cheats.prank(defaultOperator); - registryCoordinator.registerOperator(emptyQuorumNumbers, defaultSocket); + registryCoordinator.registerOperator(emptyQuorumNumbers, defaultSocket, emptySig); } function testRegisterOperatorWithCoordinator_QuorumNumbersTooLarge_Reverts() public { bytes memory quorumNumbersTooLarge = new bytes(1); + ISignatureUtils.SignatureWithSaltAndExpiry memory emptySig; + quorumNumbersTooLarge[0] = 0xC0; cheats.expectRevert("BitmapUtils.orderedBytesArrayToBitmap: bitmap exceeds max value"); cheats.prank(defaultOperator); - registryCoordinator.registerOperator(quorumNumbersTooLarge, defaultSocket); + registryCoordinator.registerOperator(quorumNumbersTooLarge, defaultSocket, emptySig); } function testRegisterOperatorWithCoordinator_QuorumNotCreated_Reverts() public { _deployMockEigenLayerAndAVS(10); bytes memory quorumNumbersNotCreated = new bytes(1); + ISignatureUtils.SignatureWithSaltAndExpiry memory emptySig; + quorumNumbersNotCreated[0] = 0x0B; cheats.prank(defaultOperator); cheats.expectRevert("BitmapUtils.orderedBytesArrayToBitmap: bitmap exceeds max value"); - registryCoordinator.registerOperator(quorumNumbersNotCreated, defaultSocket); + registryCoordinator.registerOperator(quorumNumbersNotCreated, defaultSocket, emptySig); } function testRegisterOperatorWithCoordinatorForSingleQuorum_Valid() public { bytes memory quorumNumbers = new bytes(1); + ISignatureUtils.SignatureWithSaltAndExpiry memory emptySig; quorumNumbers[0] = bytes1(defaultQuorumNumber); stakeRegistry.setOperatorWeight(uint8(quorumNumbers[0]), defaultOperator, defaultStake); @@ -166,7 +175,7 @@ contract RegistryCoordinatorUnit is MockAVSDeployer { uint256 gasBefore = gasleft(); cheats.prank(defaultOperator); - registryCoordinator.registerOperator(quorumNumbers, defaultSocket); + registryCoordinator.registerOperator(quorumNumbers, defaultSocket, emptySig); uint256 gasAfter = gasleft(); emit log_named_uint("gasUsed", gasBefore - gasAfter); @@ -193,6 +202,7 @@ contract RegistryCoordinatorUnit is MockAVSDeployer { function testRegisterOperatorWithCoordinatorForFuzzedQuorums_Valid(uint256 quorumBitmap) public { quorumBitmap = quorumBitmap & MAX_QUORUM_BITMAP; + ISignatureUtils.SignatureWithSaltAndExpiry memory emptySig; cheats.assume(quorumBitmap != 0); bytes memory quorumNumbers = BitmapUtils.bitmapToBytesArray(quorumBitmap); @@ -218,7 +228,7 @@ contract RegistryCoordinatorUnit is MockAVSDeployer { uint256 gasBefore = gasleft(); cheats.prank(defaultOperator); - registryCoordinator.registerOperator(quorumNumbers, defaultSocket); + registryCoordinator.registerOperator(quorumNumbers, defaultSocket, emptySig); uint256 gasAfter = gasleft(); emit log_named_uint("gasUsed", gasBefore - gasAfter); emit log_named_uint("numQuorums", quorumNumbers.length); @@ -245,6 +255,7 @@ contract RegistryCoordinatorUnit is MockAVSDeployer { function testRegisterOperatorWithCoordinator_RegisteredOperatorForNewQuorums_Valid() public { uint256 registrationBlockNumber = block.number + 100; uint256 nextRegistrationBlockNumber = registrationBlockNumber + 100; + ISignatureUtils.SignatureWithSaltAndExpiry memory emptySig; bytes memory quorumNumbers = new bytes(1); quorumNumbers[0] = bytes1(defaultQuorumNumber); @@ -252,7 +263,7 @@ contract RegistryCoordinatorUnit is MockAVSDeployer { stakeRegistry.setOperatorWeight(uint8(quorumNumbers[0]), defaultOperator, defaultStake); cheats.prank(defaultOperator); cheats.roll(registrationBlockNumber); - registryCoordinator.registerOperator(quorumNumbers, defaultSocket); + registryCoordinator.registerOperator(quorumNumbers, defaultSocket, emptySig); bytes memory newQuorumNumbers = new bytes(1); newQuorumNumbers[0] = bytes1(defaultQuorumNumber+1); @@ -268,7 +279,7 @@ contract RegistryCoordinatorUnit is MockAVSDeployer { emit QuorumIndexUpdate(defaultOperatorId, uint8(newQuorumNumbers[0]), 0); cheats.roll(nextRegistrationBlockNumber); cheats.prank(defaultOperator); - registryCoordinator.registerOperator(newQuorumNumbers, defaultSocket); + registryCoordinator.registerOperator(newQuorumNumbers, defaultSocket, emptySig); uint256 quorumBitmap = BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers) | BitmapUtils.orderedBytesArrayToBitmap(newQuorumNumbers); @@ -302,6 +313,7 @@ contract RegistryCoordinatorUnit is MockAVSDeployer { function testRegisterOperatorWithCoordinator_OverFilledQuorum_Reverts(uint256 pseudoRandomNumber) public { uint32 numOperators = defaultMaxOperatorCount; uint32 registrationBlockNumber = 200; + ISignatureUtils.SignatureWithSaltAndExpiry memory emptySig; bytes memory quorumNumbers = new bytes(1); quorumNumbers[0] = bytes1(defaultQuorumNumber); @@ -326,12 +338,13 @@ contract RegistryCoordinatorUnit is MockAVSDeployer { cheats.prank(operatorToRegister); cheats.expectRevert("RegistryCoordinator.registerOperator: operator count exceeds maximum"); - registryCoordinator.registerOperator(quorumNumbers, defaultSocket); + registryCoordinator.registerOperator(quorumNumbers, defaultSocket, emptySig); } function testRegisterOperatorWithCoordinator_RegisteredOperatorForSameQuorums_Reverts() public { uint256 registrationBlockNumber = block.number + 100; uint256 nextRegistrationBlockNumber = registrationBlockNumber + 100; + ISignatureUtils.SignatureWithSaltAndExpiry memory emptySig; bytes memory quorumNumbers = new bytes(1); quorumNumbers[0] = bytes1(defaultQuorumNumber); @@ -339,12 +352,12 @@ contract RegistryCoordinatorUnit is MockAVSDeployer { stakeRegistry.setOperatorWeight(uint8(quorumNumbers[0]), defaultOperator, defaultStake); cheats.prank(defaultOperator); cheats.roll(registrationBlockNumber); - registryCoordinator.registerOperator(quorumNumbers, defaultSocket); + registryCoordinator.registerOperator(quorumNumbers, defaultSocket, emptySig); cheats.prank(defaultOperator); cheats.roll(nextRegistrationBlockNumber); cheats.expectRevert("RegistryCoordinator._registerOperator: operator already registered for some quorums being registered for"); - registryCoordinator.registerOperator(quorumNumbers, defaultSocket); + registryCoordinator.registerOperator(quorumNumbers, defaultSocket, emptySig); } function testDeregisterOperatorWithCoordinator_WhenPaused_Reverts() public { @@ -389,6 +402,7 @@ contract RegistryCoordinatorUnit is MockAVSDeployer { } function testDeregisterOperatorWithCoordinatorForSingleQuorumAndSingleOperator_Valid() public { + ISignatureUtils.SignatureWithSaltAndExpiry memory emptySig; uint32 registrationBlockNumber = 100; uint32 deregistrationBlockNumber = 200; @@ -401,7 +415,7 @@ contract RegistryCoordinatorUnit is MockAVSDeployer { cheats.roll(registrationBlockNumber); - registryCoordinator.registerOperator(quorumNumbers, defaultSocket); + registryCoordinator.registerOperator(quorumNumbers, defaultSocket, emptySig); uint256 quorumBitmap = BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers); @@ -436,6 +450,7 @@ contract RegistryCoordinatorUnit is MockAVSDeployer { } function testDeregisterOperatorWithCoordinatorForFuzzedQuorumAndSingleOperator_Valid(uint256 quorumBitmap) public { + ISignatureUtils.SignatureWithSaltAndExpiry memory emptySig; uint32 registrationBlockNumber = 100; uint32 deregistrationBlockNumber = 200; @@ -451,7 +466,7 @@ contract RegistryCoordinatorUnit is MockAVSDeployer { cheats.roll(registrationBlockNumber); - registryCoordinator.registerOperator(quorumNumbers, defaultSocket); + registryCoordinator.registerOperator(quorumNumbers, defaultSocket, emptySig); cheats.expectEmit(true, true, true, true, address(blsApkRegistry)); emit OperatorRemovedFromQuorums(defaultOperator, quorumNumbers); @@ -564,6 +579,7 @@ contract RegistryCoordinatorUnit is MockAVSDeployer { function testReregisterOperatorWithCoordinator_Valid() public { testDeregisterOperatorWithCoordinatorForSingleQuorumAndSingleOperator_Valid(); + ISignatureUtils.SignatureWithSaltAndExpiry memory emptySig; uint32 reregistrationBlockNumber = 201; bytes memory quorumNumbers = new bytes(1); @@ -578,7 +594,7 @@ contract RegistryCoordinatorUnit is MockAVSDeployer { registryCoordinator.getQuorumBitmapUpdateByIndex(defaultOperatorId, 0); // re-register the operator - registryCoordinator.registerOperator(quorumNumbers, defaultSocket); + registryCoordinator.registerOperator(quorumNumbers, defaultSocket, emptySig); // check success of registration uint256 quorumBitmap = BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers); @@ -676,6 +692,7 @@ contract RegistryCoordinatorUnit is MockAVSDeployer { emit QuorumIndexUpdate(operatorToRegisterId, defaultQuorumNumber, numOperators - 1); { + ISignatureUtils.SignatureWithSaltAndExpiry memory emptyAVSRegSig; ISignatureUtils.SignatureWithSaltAndExpiry memory signatureWithExpiry = _signOperatorChurnApproval(operatorToRegisterId, operatorKickParams, defaultSalt, block.timestamp + 10); cheats.prank(operatorToRegister); uint256 gasBefore = gasleft(); @@ -683,7 +700,8 @@ contract RegistryCoordinatorUnit is MockAVSDeployer { quorumNumbers, defaultSocket, operatorKickParams, - signatureWithExpiry + signatureWithExpiry, + emptyAVSRegSig ); uint256 gasAfter = gasleft(); emit log_named_uint("gasUsed", gasBefore - gasAfter); @@ -716,6 +734,7 @@ contract RegistryCoordinatorUnit is MockAVSDeployer { function testRegisterOperatorWithCoordinatorWithKicks_LessThanKickBIPsOfOperatorStake_Reverts(uint256 pseudoRandomNumber) public { bytes memory quorumNumbers = new bytes(1); quorumNumbers[0] = bytes1(defaultQuorumNumber); + ISignatureUtils.SignatureWithSaltAndExpiry memory emptyAVSRegSig; ( address operatorToRegister, @@ -730,12 +749,13 @@ contract RegistryCoordinatorUnit is MockAVSDeployer { ISignatureUtils.SignatureWithSaltAndExpiry memory signatureWithExpiry = _signOperatorChurnApproval(operatorToRegisterId, operatorKickParams, defaultSalt, block.timestamp + 10); cheats.prank(operatorToRegister); cheats.expectRevert("RegistryCoordinator._validateChurn: incoming operator has insufficient stake for churn"); - registryCoordinator.registerOperatorWithChurn(quorumNumbers, defaultSocket, operatorKickParams, signatureWithExpiry); + registryCoordinator.registerOperatorWithChurn(quorumNumbers, defaultSocket, operatorKickParams, signatureWithExpiry, emptyAVSRegSig); } function testRegisterOperatorWithCoordinatorWithKicks_LessThanKickBIPsOfTotalStake_Reverts(uint256 pseudoRandomNumber) public { bytes memory quorumNumbers = new bytes(1); quorumNumbers[0] = bytes1(defaultQuorumNumber); + ISignatureUtils.SignatureWithSaltAndExpiry memory emptyAVSRegSig; uint96 operatorToKickStake = defaultMaxOperatorCount * defaultStake; ( @@ -753,12 +773,13 @@ contract RegistryCoordinatorUnit is MockAVSDeployer { ISignatureUtils.SignatureWithSaltAndExpiry memory signatureWithExpiry = _signOperatorChurnApproval(operatorToRegisterId, operatorKickParams, defaultSalt, block.timestamp + 10); cheats.prank(operatorToRegister); cheats.expectRevert("RegistryCoordinator._validateChurn: cannot kick operator with more than kickBIPsOfTotalStake"); - registryCoordinator.registerOperatorWithChurn(quorumNumbers, defaultSocket, operatorKickParams, signatureWithExpiry); + registryCoordinator.registerOperatorWithChurn(quorumNumbers, defaultSocket, operatorKickParams, signatureWithExpiry, emptyAVSRegSig); } function testRegisterOperatorWithCoordinatorWithKicks_InvalidSignatures_Reverts(uint256 pseudoRandomNumber) public { bytes memory quorumNumbers = new bytes(1); quorumNumbers[0] = bytes1(defaultQuorumNumber); + ISignatureUtils.SignatureWithSaltAndExpiry memory emptyAVSRegSig; ( address operatorToRegister, @@ -776,12 +797,13 @@ contract RegistryCoordinatorUnit is MockAVSDeployer { signatureWithSaltAndExpiry.salt = defaultSalt; cheats.prank(operatorToRegister); cheats.expectRevert("ECDSA: invalid signature"); - registryCoordinator.registerOperatorWithChurn(quorumNumbers, defaultSocket, operatorKickParams, signatureWithSaltAndExpiry); + registryCoordinator.registerOperatorWithChurn(quorumNumbers, defaultSocket, operatorKickParams, signatureWithSaltAndExpiry, emptyAVSRegSig); } function testRegisterOperatorWithCoordinatorWithKicks_ExpiredSignatures_Reverts(uint256 pseudoRandomNumber) public { bytes memory quorumNumbers = new bytes(1); quorumNumbers[0] = bytes1(defaultQuorumNumber); + ISignatureUtils.SignatureWithSaltAndExpiry memory emptyAVSRegSig; ( address operatorToRegister, @@ -797,18 +819,19 @@ contract RegistryCoordinatorUnit is MockAVSDeployer { ISignatureUtils.SignatureWithSaltAndExpiry memory signatureWithSaltAndExpiry = _signOperatorChurnApproval(operatorToRegisterId, operatorKickParams, defaultSalt, block.timestamp - 1); cheats.prank(operatorToRegister); cheats.expectRevert("RegistryCoordinator._verifyChurnApproverSignature: churnApprover signature expired"); - registryCoordinator.registerOperatorWithChurn(quorumNumbers, defaultSocket, operatorKickParams, signatureWithSaltAndExpiry); + registryCoordinator.registerOperatorWithChurn(quorumNumbers, defaultSocket, operatorKickParams, signatureWithSaltAndExpiry, emptyAVSRegSig); } function testEjectOperatorFromCoordinator_AllQuorums_Valid() public { // register operator with default stake with default quorum number bytes memory quorumNumbers = new bytes(1); quorumNumbers[0] = bytes1(defaultQuorumNumber); + ISignatureUtils.SignatureWithSaltAndExpiry memory emptySig; stakeRegistry.setOperatorWeight(uint8(quorumNumbers[0]), defaultOperator, defaultStake); cheats.prank(defaultOperator); - registryCoordinator.registerOperator(quorumNumbers, defaultSocket); + registryCoordinator.registerOperator(quorumNumbers, defaultSocket, emptySig); cheats.expectEmit(true, true, true, true, address(blsApkRegistry)); emit OperatorRemovedFromQuorums(defaultOperator, quorumNumbers); @@ -837,13 +860,14 @@ contract RegistryCoordinatorUnit is MockAVSDeployer { bytes memory quorumNumbers = new bytes(2); quorumNumbers[0] = bytes1(defaultQuorumNumber); quorumNumbers[1] = bytes1(defaultQuorumNumber + 1); + ISignatureUtils.SignatureWithSaltAndExpiry memory emptySig; for (uint i = 0; i < quorumNumbers.length; i++) { stakeRegistry.setOperatorWeight(uint8(quorumNumbers[i]), defaultOperator, defaultStake); } cheats.prank(defaultOperator); - registryCoordinator.registerOperator(quorumNumbers, defaultSocket); + registryCoordinator.registerOperator(quorumNumbers, defaultSocket, emptySig); // eject from only first quorum bytes memory quorumNumbersToEject = new bytes(1); @@ -876,11 +900,12 @@ contract RegistryCoordinatorUnit is MockAVSDeployer { function testEjectOperatorFromCoordinator_NotEjector_Reverts() public { bytes memory quorumNumbers = new bytes(1); quorumNumbers[0] = bytes1(defaultQuorumNumber); + ISignatureUtils.SignatureWithSaltAndExpiry memory emptySig; stakeRegistry.setOperatorWeight(uint8(quorumNumbers[0]), defaultOperator, defaultStake); cheats.prank(defaultOperator); - registryCoordinator.registerOperator(quorumNumbers, defaultSocket); + registryCoordinator.registerOperator(quorumNumbers, defaultSocket, emptySig); cheats.expectRevert("RegistryCoordinator.onlyEjector: caller is not the ejector"); cheats.prank(defaultOperator); diff --git a/test/unit/StakeRegistryUnit.t.sol b/test/unit/StakeRegistryUnit.t.sol index 5a56772e..c0ae719b 100644 --- a/test/unit/StakeRegistryUnit.t.sol +++ b/test/unit/StakeRegistryUnit.t.sol @@ -103,6 +103,7 @@ contract StakeRegistryUnitTests is Test { cheats.startPrank(registryCoordinatorOwner); registryCoordinator = new RegistryCoordinatorHarness( + delegationMock, slasher, stakeRegistry, IBLSApkRegistry(apkRegistry), diff --git a/test/utils/MockAVSDeployer.sol b/test/utils/MockAVSDeployer.sol index 89e0fcd0..45ea8c4c 100644 --- a/test/utils/MockAVSDeployer.sol +++ b/test/utils/MockAVSDeployer.sol @@ -77,7 +77,7 @@ contract MockAVSDeployer is Test { bytes32 defaultSalt = bytes32(uint256(keccak256("defaultSalt"))); address ejector = address(uint160(uint256(keccak256("ejector")))); - + address defaultOperator = address(uint160(uint256(keccak256("defaultOperator")))); bytes32 defaultOperatorId; BN254.G1Point internal defaultPubKey = BN254.G1Point(18260007818883133054078754218619977578772505796600400998181738095793040006897,3432351341799135763167709827653955074218841517684851694584291831827675065899); @@ -237,6 +237,7 @@ contract MockAVSDeployer is Test { } registryCoordinatorImplementation = new RegistryCoordinatorHarness( + delegationMock, slasher, stakeRegistry, blsApkRegistry, @@ -296,8 +297,9 @@ contract MockAVSDeployer is Test { stakeRegistry.setOperatorWeight(uint8(quorumNumbers[i]), operator, stake); } + ISignatureUtils.SignatureWithSaltAndExpiry memory emptySignatureAndExpiry; cheats.prank(operator); - registryCoordinator.registerOperator(quorumNumbers, defaultSocket); + registryCoordinator.registerOperator(quorumNumbers, defaultSocket, emptySignatureAndExpiry); } /** @@ -314,8 +316,9 @@ contract MockAVSDeployer is Test { stakeRegistry.setOperatorWeight(uint8(quorumNumbers[i]), operator, stakes[uint8(quorumNumbers[i])]); } + ISignatureUtils.SignatureWithSaltAndExpiry memory emptySignatureAndExpiry; cheats.prank(operator); - registryCoordinator.registerOperator(quorumNumbers, defaultSocket); + registryCoordinator.registerOperator(quorumNumbers, defaultSocket, emptySignatureAndExpiry); } function _registerRandomOperators(uint256 pseudoRandomNumber) internal returns(OperatorMetadata[] memory, uint256[][] memory) { From d874013fe798be2100b8a1d50b344cb40d10ddd1 Mon Sep 17 00:00:00 2001 From: Yash Patil <40046473+ypatil12@users.noreply.github.com> Date: Mon, 11 Dec 2023 18:15:50 -0500 Subject: [PATCH 3/6] test: add AVS registration tests --- src/RegistryCoordinator.sol | 2 +- src/interfaces/IIndexRegistry.sol | 4 +- src/interfaces/IStakeRegistry.sol | 2 +- test/integration/CoreRegistration.t.sol | 176 ++++++++++++++++++++++++ 4 files changed, 180 insertions(+), 4 deletions(-) create mode 100644 test/integration/CoreRegistration.t.sol diff --git a/src/RegistryCoordinator.sol b/src/RegistryCoordinator.sol index 91fcc6fe..d0434eb2 100644 --- a/src/RegistryCoordinator.sol +++ b/src/RegistryCoordinator.sol @@ -559,7 +559,7 @@ contract RegistryCoordinator is // If the operator is no longer registered for any quorums, update their status and deregister from delegationManager if (newBitmap.isEmpty()) { operatorInfo.status = OperatorStatus.DEREGISTERED; - // delegationManager.deregisterOperatorFromAVS(operator); + delegationManager.deregisterOperatorFromAVS(operator); emit OperatorDeregistered(operator, operatorId); } diff --git a/src/interfaces/IIndexRegistry.sol b/src/interfaces/IIndexRegistry.sol index 1765802d..5a0b1eb4 100644 --- a/src/interfaces/IIndexRegistry.sol +++ b/src/interfaces/IIndexRegistry.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity =0.8.12; -import {IRegistry} from "./IRegistry.sol"; +// import {IRegistry} from "./IRegistry.sol"; /** * @title Interface for a `Registry`-type contract that keeps track of an ordered list of operators for up to 256 quorums. * @author Layr Labs, Inc. */ -interface IIndexRegistry is IRegistry { +interface IIndexRegistry { // EVENTS // emitted when an operator's index in the orderd operator list for the quorum with number `quorumNumber` is updated diff --git a/src/interfaces/IStakeRegistry.sol b/src/interfaces/IStakeRegistry.sol index 51196a2f..95739eb2 100644 --- a/src/interfaces/IStakeRegistry.sol +++ b/src/interfaces/IStakeRegistry.sol @@ -10,7 +10,7 @@ import {IRegistry} from "./IRegistry.sol"; * @title Interface for a `Registry` that keeps track of stakes of operators for up to 256 quorums. * @author Layr Labs, Inc. */ -interface IStakeRegistry is IRegistry { +interface IStakeRegistry { // DATA STRUCTURES diff --git a/test/integration/CoreRegistration.t.sol b/test/integration/CoreRegistration.t.sol new file mode 100644 index 00000000..2e4c2130 --- /dev/null +++ b/test/integration/CoreRegistration.t.sol @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.12; + +import "test/utils/MockAVSDeployer.sol"; +import { DelegationManager } from "eigenlayer-contracts/src/contracts/core/DelegationManager.sol"; +import { IDelegationManager } from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; + +contract Test_CoreRegistration is MockAVSDeployer { + // Contracts + DelegationManager public delegationManager; + + // Operator info + uint256 operatorPrivateKey = 420; + address operator; + + // Dummy vals used across tests + bytes32 emptySalt; + uint256 maxExpiry = type(uint256).max; + string emptyStringForMetadataURI; + + function setUp() public { + _deployMockEigenLayerAndAVS(); + + // Deploy New DelegationManager + DelegationManager delegationManagerImplementation = new DelegationManager(strategyManagerMock, slasher, eigenPodManagerMock); + delegationManager = DelegationManager( + address( + new TransparentUpgradeableProxy( + address(delegationManagerImplementation), + address(proxyAdmin), + abi.encodeWithSelector( + DelegationManager.initialize.selector, + address(this), + pauserRegistry, + 0, // 0 is initialPausedStatus + 50400 // Initial withdrawal delay blocks + ) + ) + ) + ); + + // Deploy New RegistryCoordinator + registryCoordinatorImplementation = new RegistryCoordinatorHarness( + delegationManager, + slasher, + serviceManagerMock, + stakeRegistry, + blsApkRegistry, + indexRegistry + ); + + // Upgrade Registry Coordinator + cheats.prank(proxyAdminOwner); + proxyAdmin.upgrade( + TransparentUpgradeableProxy(payable(address(registryCoordinator))), + address(registryCoordinatorImplementation) + ); + + // Set operator address + operator = cheats.addr(operatorPrivateKey); + pubkeyCompendium.setBLSPublicKey(operator, defaultPubKey); + + // Register operator to EigenLayer + cheats.prank(operator); + delegationManager.registerAsOperator( + IDelegationManager.OperatorDetails({ + earningsReceiver: operator, + delegationApprover: address(0), + stakerOptOutWindowBlocks: 0 + }), + emptyStringForMetadataURI + ); + + // Set operator weight in single quorum + bytes memory quorumNumbers = BitmapUtils.bitmapToBytesArray(MAX_QUORUM_BITMAP); + for (uint i = 0; i < quorumNumbers.length; i++) { + stakeRegistry.setOperatorWeight(uint8(quorumNumbers[i]), operator, defaultStake); + } + } + + function test_registerOperator_coreStateChanges() public { + bytes memory quorumNumbers = new bytes(1); + + // Get operator signature + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature = _getOperatorSignature( + operatorPrivateKey, + operator, + address(registryCoordinator), + emptySalt, + maxExpiry + ); + + // Register operator + cheats.prank(operator); + registryCoordinator.registerOperator(quorumNumbers, defaultSocket, operatorSignature); + + // Check operator is registered + IDelegationManager.OperatorAVSRegistrationStatus operatorStatus = delegationManager.avsOperatorStatus(address(registryCoordinator), operator); + assertEq(uint8(operatorStatus), uint8(IDelegationManager.OperatorAVSRegistrationStatus.REGISTERED)); + } + + function test_deregisterOperator_coreStateChanges() public { + // Register operator + bytes memory quorumNumbers = new bytes(1); + _registerOperator(quorumNumbers); + + // Deregister Operator + cheats.prank(operator); + registryCoordinator.deregisterOperator(quorumNumbers); + + // Check operator is deregistered + IDelegationManager.OperatorAVSRegistrationStatus operatorStatus = delegationManager.avsOperatorStatus(address(registryCoordinator), operator); + assertEq(uint8(operatorStatus), uint8(IDelegationManager.OperatorAVSRegistrationStatus.UNREGISTERED)); + } + + function test_deregisterOperator_notGloballyDeregistered() public { + // Register operator with all quorums + bytes memory quorumNumbers = BitmapUtils.bitmapToBytesArray(MAX_QUORUM_BITMAP); + emit log_named_bytes("quorumNumbers", quorumNumbers); + _registerOperator(quorumNumbers); + + // Deregister Operator with single quorum + quorumNumbers = new bytes(1); + cheats.prank(operator); + registryCoordinator.deregisterOperator(quorumNumbers); + + // Check operator is still registered + IDelegationManager.OperatorAVSRegistrationStatus operatorStatus = delegationManager.avsOperatorStatus(address(registryCoordinator), operator); + assertEq(uint8(operatorStatus), uint8(IDelegationManager.OperatorAVSRegistrationStatus.REGISTERED)); + } + + function test_setMetadataURI_fail_notServiceManagerOwner(address invalidCaller) public { + cheats.prank(operator); + cheats.expectRevert("RegistryCoordinator.onlyServiceManagerOwner: caller is not the service manager owner"); + registryCoordinator.setMetadataURI("Test MetadataURI"); + } + + function test_setMetadataURI() public { + cheats.prank(serviceManagerOwner); + registryCoordinator.setMetadataURI("Test MetadataURI"); + } + + // Utils + function _registerOperator(bytes memory quorumNumbers) internal { + // Get operator signature + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature = _getOperatorSignature( + operatorPrivateKey, + operator, + address(registryCoordinator), + emptySalt, + maxExpiry + ); + + // Register operator + cheats.prank(operator); + registryCoordinator.registerOperator(quorumNumbers, defaultSocket, operatorSignature); + } + + function _getOperatorSignature( + uint256 _operatorPrivateKey, + address operator, + address avs, + bytes32 salt, + uint256 expiry + ) internal view returns (ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature) { + operatorSignature.salt = salt; + operatorSignature.expiry = expiry; + { + bytes32 digestHash = delegationManager.calculateOperatorAVSRegistrationDigestHash(operator, avs, salt, expiry); + (uint8 v, bytes32 r, bytes32 s) = cheats.sign(_operatorPrivateKey, digestHash); + operatorSignature.signature = abi.encodePacked(r, s, v); + } + return operatorSignature; + } + +} \ No newline at end of file From 6e6ffe7b6e03100abd606717e07342525c93ceb5 Mon Sep 17 00:00:00 2001 From: Yash Patil <40046473+ypatil12@users.noreply.github.com> Date: Mon, 11 Dec 2023 18:24:02 -0500 Subject: [PATCH 4/6] fix: re-add commented out interfaces --- src/interfaces/IIndexRegistry.sol | 4 ++-- src/interfaces/IStakeRegistry.sol | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/interfaces/IIndexRegistry.sol b/src/interfaces/IIndexRegistry.sol index 5a0b1eb4..f9981899 100644 --- a/src/interfaces/IIndexRegistry.sol +++ b/src/interfaces/IIndexRegistry.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity =0.8.12; -// import {IRegistry} from "./IRegistry.sol"; +import {IRegistry} from "./IRegistry.sol"; /** * @title Interface for a `Registry`-type contract that keeps track of an ordered list of operators for up to 256 quorums. * @author Layr Labs, Inc. */ -interface IIndexRegistry { +interface IIndexRegistry is IIndexRegistry { // EVENTS // emitted when an operator's index in the orderd operator list for the quorum with number `quorumNumber` is updated diff --git a/src/interfaces/IStakeRegistry.sol b/src/interfaces/IStakeRegistry.sol index 95739eb2..51196a2f 100644 --- a/src/interfaces/IStakeRegistry.sol +++ b/src/interfaces/IStakeRegistry.sol @@ -10,7 +10,7 @@ import {IRegistry} from "./IRegistry.sol"; * @title Interface for a `Registry` that keeps track of stakes of operators for up to 256 quorums. * @author Layr Labs, Inc. */ -interface IStakeRegistry { +interface IStakeRegistry is IRegistry { // DATA STRUCTURES From a688046823536862c543a0e3c55e860c4a14151e Mon Sep 17 00:00:00 2001 From: Yash Patil <40046473+ypatil12@users.noreply.github.com> Date: Mon, 11 Dec 2023 18:26:47 -0500 Subject: [PATCH 5/6] fix: correct interface naming --- src/interfaces/IIndexRegistry.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interfaces/IIndexRegistry.sol b/src/interfaces/IIndexRegistry.sol index f9981899..1765802d 100644 --- a/src/interfaces/IIndexRegistry.sol +++ b/src/interfaces/IIndexRegistry.sol @@ -7,7 +7,7 @@ import {IRegistry} from "./IRegistry.sol"; * @title Interface for a `Registry`-type contract that keeps track of an ordered list of operators for up to 256 quorums. * @author Layr Labs, Inc. */ -interface IIndexRegistry is IIndexRegistry { +interface IIndexRegistry is IRegistry { // EVENTS // emitted when an operator's index in the orderd operator list for the quorum with number `quorumNumber` is updated From 98cbb0bcccec7d6926a47a37e28e619bb065a88f Mon Sep 17 00:00:00 2001 From: Yash Patil <40046473+ypatil12@users.noreply.github.com> Date: Tue, 12 Dec 2023 10:58:27 -0500 Subject: [PATCH 6/6] refactor: serviceManager -> owner --- src/RegistryCoordinator.sol | 2 +- test/integration/CoreRegistration.t.sol | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/RegistryCoordinator.sol b/src/RegistryCoordinator.sol index d0434eb2..714285bb 100644 --- a/src/RegistryCoordinator.sol +++ b/src/RegistryCoordinator.sol @@ -419,7 +419,7 @@ contract RegistryCoordinator is * @param _metadataURI is the metadata URI for the AVS * @dev only callable by the service manager owner */ - function setMetadataURI(string memory _metadataURI) external onlyServiceManagerOwner { + function setMetadataURI(string memory _metadataURI) external onlyOwner { delegationManager.updateAVSMetadataURI(_metadataURI); } diff --git a/test/integration/CoreRegistration.t.sol b/test/integration/CoreRegistration.t.sol index 2e4c2130..1df04dfc 100644 --- a/test/integration/CoreRegistration.t.sol +++ b/test/integration/CoreRegistration.t.sol @@ -43,7 +43,6 @@ contract Test_CoreRegistration is MockAVSDeployer { registryCoordinatorImplementation = new RegistryCoordinatorHarness( delegationManager, slasher, - serviceManagerMock, stakeRegistry, blsApkRegistry, indexRegistry @@ -129,14 +128,14 @@ contract Test_CoreRegistration is MockAVSDeployer { assertEq(uint8(operatorStatus), uint8(IDelegationManager.OperatorAVSRegistrationStatus.REGISTERED)); } - function test_setMetadataURI_fail_notServiceManagerOwner(address invalidCaller) public { + function test_setMetadataURI_fail_notServiceManagerOwner() public { cheats.prank(operator); - cheats.expectRevert("RegistryCoordinator.onlyServiceManagerOwner: caller is not the service manager owner"); + cheats.expectRevert("Ownable: caller is not the owner"); registryCoordinator.setMetadataURI("Test MetadataURI"); } function test_setMetadataURI() public { - cheats.prank(serviceManagerOwner); + cheats.prank(registryCoordinatorOwner); registryCoordinator.setMetadataURI("Test MetadataURI"); } @@ -158,7 +157,7 @@ contract Test_CoreRegistration is MockAVSDeployer { function _getOperatorSignature( uint256 _operatorPrivateKey, - address operator, + address operatorToSign, address avs, bytes32 salt, uint256 expiry @@ -166,7 +165,7 @@ contract Test_CoreRegistration is MockAVSDeployer { operatorSignature.salt = salt; operatorSignature.expiry = expiry; { - bytes32 digestHash = delegationManager.calculateOperatorAVSRegistrationDigestHash(operator, avs, salt, expiry); + bytes32 digestHash = delegationManager.calculateOperatorAVSRegistrationDigestHash(operatorToSign, avs, salt, expiry); (uint8 v, bytes32 r, bytes32 s) = cheats.sign(_operatorPrivateKey, digestHash); operatorSignature.signature = abi.encodePacked(r, s, v); }