From c5ac704c1a2e48a6e5af63839570487dd717ff01 Mon Sep 17 00:00:00 2001 From: -f Date: Tue, 16 Apr 2024 17:27:52 +0400 Subject: [PATCH 01/32] forge install: eigenlayer-middleware --- .gitmodules | 3 +++ solidity/lib/eigenlayer-middleware | 1 + 2 files changed, 4 insertions(+) create mode 160000 solidity/lib/eigenlayer-middleware diff --git a/.gitmodules b/.gitmodules index 3077b9e20f..2aa603d589 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "solidity/lib/forge-std"] path = solidity/lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "solidity/lib/eigenlayer-middleware"] + path = solidity/lib/eigenlayer-middleware + url = https://github.com/Layr-Labs/eigenlayer-middleware diff --git a/solidity/lib/eigenlayer-middleware b/solidity/lib/eigenlayer-middleware new file mode 160000 index 0000000000..5273cd18de --- /dev/null +++ b/solidity/lib/eigenlayer-middleware @@ -0,0 +1 @@ +Subproject commit 5273cd18deaddbadb63700440ea12b69627ec446 From 8873f209c315d3bb312c9896bfddeda4f8106fd0 Mon Sep 17 00:00:00 2001 From: -f Date: Mon, 22 Apr 2024 21:44:57 +0530 Subject: [PATCH 02/32] install --- .gitmodules | 3 + .../contracts/avs/HyperlaneServiceManager.sol | 94 +++++++++++++++++++ .../contracts/interfaces/avs/ITownCrier.sol | 19 ++++ solidity/lib/eigenlayer-contracts | 1 + solidity/lib/eigenlayer-middleware | 2 +- solidity/remappings.txt | 3 + 6 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 solidity/contracts/avs/HyperlaneServiceManager.sol create mode 100644 solidity/contracts/interfaces/avs/ITownCrier.sol create mode 160000 solidity/lib/eigenlayer-contracts diff --git a/.gitmodules b/.gitmodules index 2aa603d589..f07a5f3d3c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "solidity/lib/eigenlayer-middleware"] path = solidity/lib/eigenlayer-middleware url = https://github.com/Layr-Labs/eigenlayer-middleware +[submodule "solidity/lib/eigenlayer-contracts"] + path = solidity/lib/eigenlayer-contracts + url = https://github.com/Layr-Labs/eigenlayer-contracts diff --git a/solidity/contracts/avs/HyperlaneServiceManager.sol b/solidity/contracts/avs/HyperlaneServiceManager.sol new file mode 100644 index 0000000000..d1e88db3f8 --- /dev/null +++ b/solidity/contracts/avs/HyperlaneServiceManager.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +import {ISignatureUtils} from "@eigenlayer/core/interfaces/ISignatureUtils.sol"; +import {IAVSDirectory} from "@eigenlayer/core/interfaces/IAVSDirectory.sol"; + +import {IStakeRegistry} from "@eigenlayer/middleware/interfaces/IStakeRegistry.sol"; +import {IServiceManager} from "@eigenlayer/middleware/interfaces/IServiceManager.sol"; + +contract HyperlaneServiceManager is IServiceManager, OwnableUpgradeable { + IStakeRegistry internal immutable stakeRegistry; + IAVSDirectory internal immutable elAvsDirectory; + + /// @notice when applied to a function, only allows the ECDSAStakeRegistry to call it + modifier onlyStakeRegistry() { + require( + msg.sender == address(stakeRegistry), + "HyperlaneServiceManager: caller is not the stake registry" + ); + _; + } + + constructor(IAVSDirectory _avsDirectory, IStakeRegistry _stakeRegistry) { + elAvsDirectory = _avsDirectory; + stakeRegistry = _stakeRegistry; + _disableInitializers(); + } + + /** + * @notice Updates the metadata URI for the AVS + * @param _metadataURI is the metadata URI for the AVS + * @dev only callable by the owner + */ + function updateAVSMetadataURI( + string memory _metadataURI + ) public virtual onlyOwner { + elAvsDirectory.updateAVSMetadataURI(_metadataURI); + } + + /** + * @notice Forwards a call to EigenLayer's AVSDirectory contract to confirm operator registration with the AVS + * @param operator The address of the operator to register. + * @param operatorSignature The signature, salt, and expiry of the operator's signature. + */ + function registerOperatorToAVS( + address operator, + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature + ) public virtual onlyStakeRegistry { + elAvsDirectory.registerOperatorToAVS(operator, operatorSignature); + } + + /** + * @notice Forwards a call to EigenLayer's AVSDirectory contract to confirm operator deregistration from the AVS + * @param operator The address of the operator to deregister. + */ + function deregisterOperatorFromAVS( + address operator + ) public virtual onlyStakeRegistry { + elAvsDirectory.deregisterOperatorFromAVS(operator); + } + + /** + * @notice Returns the list of strategies that the AVS supports for restaking + * @dev This function is intended to be called off-chain + * @dev No guarantee is made on uniqueness of each element in the returned array. + * The off-chain service should do that validation separately + */ + function getRestakeableStrategies() + external + view + returns (address[] memory) + { + // TODO + return new address[](0); + } + + function getOperatorRestakedStrategies( + address operator + ) external view returns (address[] memory) { + // TODO + return new address[](0); + } + + /// @notice Returns the EigenLayer AVSDirectory contract. + function avsDirectory() external view override returns (address) { + return address(elAvsDirectory); + } + + // storage gap for upgradeability + // slither-disable-next-line shadowing-state + uint256[50] private __GAP; +} diff --git a/solidity/contracts/interfaces/avs/ITownCrier.sol b/solidity/contracts/interfaces/avs/ITownCrier.sol new file mode 100644 index 0000000000..c980bdd3ef --- /dev/null +++ b/solidity/contracts/interfaces/avs/ITownCrier.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +/*@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@ HYPERLANE @@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ +@@@@@@@@@ @@@@@@@@*/ + +interface ITownCrier { + function challengeDelayBlocks() external view returns (uint256); + function handleChallenge(address operator) external; +} diff --git a/solidity/lib/eigenlayer-contracts b/solidity/lib/eigenlayer-contracts new file mode 160000 index 0000000000..7229f2b426 --- /dev/null +++ b/solidity/lib/eigenlayer-contracts @@ -0,0 +1 @@ +Subproject commit 7229f2b426b6f2a24c7795b1a4687a010eac8ef2 diff --git a/solidity/lib/eigenlayer-middleware b/solidity/lib/eigenlayer-middleware index 5273cd18de..6454c05bee 160000 --- a/solidity/lib/eigenlayer-middleware +++ b/solidity/lib/eigenlayer-middleware @@ -1 +1 @@ -Subproject commit 5273cd18deaddbadb63700440ea12b69627ec446 +Subproject commit 6454c05beec77a165211f081b50905c516fc7777 diff --git a/solidity/remappings.txt b/solidity/remappings.txt index 1492dd8d72..3ba4c0f841 100644 --- a/solidity/remappings.txt +++ b/solidity/remappings.txt @@ -1,5 +1,8 @@ @openzeppelin=../node_modules/@openzeppelin +@openzeppelin/contracts-upgradeable/=../node_modules/@openzeppelin/contracts-upgradeable/ @layerzerolabs=../node_modules/@layerzerolabs @eth-optimism=../node_modules/@eth-optimism ds-test/=lib/forge-std/lib/ds-test/src/ forge-std/=lib/forge-std/src/ +@eigenlayer/core/=lib/eigenlayer-contracts/src/contracts/ +@eigenlayer/middleware/=lib/eigenlayer-middleware/src/ From 2158670b27a55b27369f0308270f8d37fbe039b8 Mon Sep 17 00:00:00 2001 From: -f Date: Mon, 22 Apr 2024 23:45:54 +0530 Subject: [PATCH 03/32] getRestakeableStrategies --- .../contracts/avs/HyperlaneServiceManager.sol | 53 ++++++++++++++++--- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/solidity/contracts/avs/HyperlaneServiceManager.sol b/solidity/contracts/avs/HyperlaneServiceManager.sol index d1e88db3f8..9377f563e3 100644 --- a/solidity/contracts/avs/HyperlaneServiceManager.sol +++ b/solidity/contracts/avs/HyperlaneServiceManager.sol @@ -6,11 +6,11 @@ import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Own import {ISignatureUtils} from "@eigenlayer/core/interfaces/ISignatureUtils.sol"; import {IAVSDirectory} from "@eigenlayer/core/interfaces/IAVSDirectory.sol"; -import {IStakeRegistry} from "@eigenlayer/middleware/interfaces/IStakeRegistry.sol"; +import {ECDSAStakeRegistry} from "@eigenlayer/middleware/unaudited/ECDSAStakeRegistry.sol"; import {IServiceManager} from "@eigenlayer/middleware/interfaces/IServiceManager.sol"; contract HyperlaneServiceManager is IServiceManager, OwnableUpgradeable { - IStakeRegistry internal immutable stakeRegistry; + ECDSAStakeRegistry internal immutable stakeRegistry; IAVSDirectory internal immutable elAvsDirectory; /// @notice when applied to a function, only allows the ECDSAStakeRegistry to call it @@ -22,12 +22,19 @@ contract HyperlaneServiceManager is IServiceManager, OwnableUpgradeable { _; } - constructor(IAVSDirectory _avsDirectory, IStakeRegistry _stakeRegistry) { + // ============ Constructor ============ + + constructor( + IAVSDirectory _avsDirectory, + ECDSAStakeRegistry _stakeRegistry + ) { elAvsDirectory = _avsDirectory; stakeRegistry = _stakeRegistry; _disableInitializers(); } + // ============ Public Functions ============ + /** * @notice Updates the metadata URI for the AVS * @param _metadataURI is the metadata URI for the AVS @@ -61,6 +68,8 @@ contract HyperlaneServiceManager is IServiceManager, OwnableUpgradeable { elAvsDirectory.deregisterOperatorFromAVS(operator); } + // ============ External Functions ============ + /** * @notice Returns the list of strategies that the AVS supports for restaking * @dev This function is intended to be called off-chain @@ -72,15 +81,21 @@ contract HyperlaneServiceManager is IServiceManager, OwnableUpgradeable { view returns (address[] memory) { - // TODO - return new address[](0); + return _getRestakeableStrategies(); } + /** + * @notice Returns the list of strategies that the operator has potentially restaked on the AVS + * @param operator The address of the operator to get restaked strategies for + * @dev This function is intended to be called off-chain + * @dev Since ECDSAStakeRegistry only supports one quorum, each operator restakes into all the AVS strategies + * @dev No guarantee is made on uniqueness of each element in the returned array. + * The off-chain service should do that validation separately + */ function getOperatorRestakedStrategies( - address operator + address /* operator */ ) external view returns (address[] memory) { - // TODO - return new address[](0); + return _getRestakeableStrategies(); } /// @notice Returns the EigenLayer AVSDirectory contract. @@ -88,6 +103,28 @@ contract HyperlaneServiceManager is IServiceManager, OwnableUpgradeable { return address(elAvsDirectory); } + // ============ Internal Function ============ + + function _getRestakeableStrategies() + internal + view + returns (address[] memory) + { + uint256 strategyCount = stakeRegistry.quorum().strategies.length; + + if (strategyCount == 0) { + return new address[](0); + } + + address[] memory restakedStrategies = new address[](strategyCount); + for (uint256 i = 0; i < strategyCount; i++) { + restakedStrategies[i] = address( + stakeRegistry.quorum().strategies[i].strategy + ); + } + return restakedStrategies; + } + // storage gap for upgradeability // slither-disable-next-line shadowing-state uint256[50] private __GAP; From 1ee6b10c85c19d1f928a1aab9137a8c454d2b625 Mon Sep 17 00:00:00 2001 From: -f Date: Tue, 23 Apr 2024 00:02:22 +0530 Subject: [PATCH 04/32] comment --- solidity/contracts/avs/HyperlaneServiceManager.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/solidity/contracts/avs/HyperlaneServiceManager.sol b/solidity/contracts/avs/HyperlaneServiceManager.sol index 9377f563e3..347f595fb6 100644 --- a/solidity/contracts/avs/HyperlaneServiceManager.sol +++ b/solidity/contracts/avs/HyperlaneServiceManager.sol @@ -86,7 +86,6 @@ contract HyperlaneServiceManager is IServiceManager, OwnableUpgradeable { /** * @notice Returns the list of strategies that the operator has potentially restaked on the AVS - * @param operator The address of the operator to get restaked strategies for * @dev This function is intended to be called off-chain * @dev Since ECDSAStakeRegistry only supports one quorum, each operator restakes into all the AVS strategies * @dev No guarantee is made on uniqueness of each element in the returned array. From 42ae9a60986c8d6ebc4d551732b01ecd05bd9f56 Mon Sep 17 00:00:00 2001 From: -f Date: Tue, 23 Apr 2024 16:32:44 +0530 Subject: [PATCH 05/32] enroll tc --- .../contracts/avs/HyperlaneServiceManager.sol | 62 +++++++++++++++++++ .../test/avs/HyperlaneServiceManager.t.sol | 15 +++++ 2 files changed, 77 insertions(+) create mode 100644 solidity/test/avs/HyperlaneServiceManager.t.sol diff --git a/solidity/contracts/avs/HyperlaneServiceManager.sol b/solidity/contracts/avs/HyperlaneServiceManager.sol index 347f595fb6..6959fdc29f 100644 --- a/solidity/contracts/avs/HyperlaneServiceManager.sol +++ b/solidity/contracts/avs/HyperlaneServiceManager.sol @@ -10,9 +10,23 @@ import {ECDSAStakeRegistry} from "@eigenlayer/middleware/unaudited/ECDSAStakeReg import {IServiceManager} from "@eigenlayer/middleware/interfaces/IServiceManager.sol"; contract HyperlaneServiceManager is IServiceManager, OwnableUpgradeable { + enum EnrollmentStatus { + ENROLLED, + PENDING_UNENROLLMENT, + UNENROLLED + } + + struct Enrollment { + EnrollmentStatus status; + uint256 unenrollmentStartBlock; + } + ECDSAStakeRegistry internal immutable stakeRegistry; IAVSDirectory internal immutable elAvsDirectory; + mapping(address => mapping(address => Enrollment)) + public enrolledTownCriers; + /// @notice when applied to a function, only allows the ECDSAStakeRegistry to call it modifier onlyStakeRegistry() { require( @@ -70,6 +84,54 @@ contract HyperlaneServiceManager is IServiceManager, OwnableUpgradeable { // ============ External Functions ============ + function enrollIntoTownCriers(ITownCrier[] memory _townCriers) external { + for (uint256 i = 0; i < _townCriers.length; i++) { + ITownCrier townCrier = _townCriers[i]; + enrolledTownCriers[msg.sender][address(townCrier)] = Enrollment( + EnrollmentStatus.ENROLLED, + 0 + ); + } + } + + function queueUnenrollmentFromTownCriers( + ITownCrier[] memory _townCriers + ) external { + for (uint256 i = 0; i < _townCriers.length; i++) { + ITownCrier townCrier = _townCriers[i]; + if ( + enrolledTownCriers[msg.sender][address(townCrier)].status != + EnrollmentStatus.UNENROLLED + ) { + enrolledTownCriers[msg.sender][address(townCrier)] = Enrollment( + EnrollmentStatus.PENDING_UNENROLLMENT, + block.number + ); + } + } + } + + function completeQueuedUnenrollmentFromTownCriers( + ITownCrier[] memory _townCriers + ) external { + for (uint256 i = 0; i < _townCriers.length; i++) { + ITownCrier townCrier = _townCriers[i]; + if ( + enrolledTownCriers[msg.sender][address(townCrier)].status == + EnrollmentStatus.PENDING_UNENROLLMENT && + block.number >= + enrolledTownCriers[msg.sender][address(townCrier)] + .unenrollmentStartBlock + + townCrier.challengeDelayBlocks() + ) { + enrolledTownCriers[msg.sender][address(townCrier)] = Enrollment( + EnrollmentStatus.UNENROLLED, + 0 + ); + } + } + } + /** * @notice Returns the list of strategies that the AVS supports for restaking * @dev This function is intended to be called off-chain diff --git a/solidity/test/avs/HyperlaneServiceManager.t.sol b/solidity/test/avs/HyperlaneServiceManager.t.sol new file mode 100644 index 0000000000..f81f5284a9 --- /dev/null +++ b/solidity/test/avs/HyperlaneServiceManager.t.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +contract HyperlaneServiceManagerTest is Test { + // register -> deregister + // register -> stake -> deregister + // register -> stake -> queue withdrawal -> deregister + // register -> stake -> queue withdrawal -> complete -> deregister + // enroll for 3 test towncriers -> unenroll + // enroll, stake/unstake -> unenroll + // enroll, + // register. enroll, unenroll partial, deregister + // register. enroll, deregister + // register, handle challenge=true, deregister +} From 325031eedeb3bed011e3cc224c8df2b48a78aa4b Mon Sep 17 00:00:00 2001 From: -f Date: Wed, 24 Apr 2024 00:34:19 +0530 Subject: [PATCH 06/32] add challenger --- .../contracts/avs/ECDSAServiceManagerBase.sol | 132 +++++++++++++++ .../contracts/avs/HyperlaneServiceManager.sol | 160 ++++-------------- .../{ITownCrier.sol => IRemoteChallenger.sol} | 2 +- solidity/remappings.txt | 4 +- solidity/test/avs/EigenlayerBase.sol | 16 ++ .../test/avs/HyperlaneServiceManager.t.sol | 107 +++++++++++- 6 files changed, 289 insertions(+), 132 deletions(-) create mode 100644 solidity/contracts/avs/ECDSAServiceManagerBase.sol rename solidity/contracts/interfaces/avs/{ITownCrier.sol => IRemoteChallenger.sol} (94%) create mode 100644 solidity/test/avs/EigenlayerBase.sol diff --git a/solidity/contracts/avs/ECDSAServiceManagerBase.sol b/solidity/contracts/avs/ECDSAServiceManagerBase.sol new file mode 100644 index 0000000000..2a9f904f8e --- /dev/null +++ b/solidity/contracts/avs/ECDSAServiceManagerBase.sol @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +import {ISignatureUtils} from "@eigenlayer/interfaces/ISignatureUtils.sol"; +import {IAVSDirectory} from "@eigenlayer/interfaces/IAVSDirectory.sol"; + +import {ECDSAStakeRegistry} from "@eigenlayer/middleware/unaudited/ECDSAStakeRegistry.sol"; +import {IServiceManager} from "@eigenlayer/middleware/interfaces/IServiceManager.sol"; + +import {IRemoteChallenger} from "../interfaces/avs/IRemoteChallenger.sol"; + +contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable { + ECDSAStakeRegistry internal immutable stakeRegistry; + IAVSDirectory internal immutable elAvsDirectory; + + /// @notice when applied to a function, only allows the ECDSAStakeRegistry to call it + modifier onlyStakeRegistry() { + require( + msg.sender == address(stakeRegistry), + "ECDSAServiceManagerBase: caller is not the stake registry" + ); + _; + } + + // ============ Constructor ============ + + constructor( + IAVSDirectory _avsDirectory, + ECDSAStakeRegistry _stakeRegistry + ) { + elAvsDirectory = _avsDirectory; + stakeRegistry = _stakeRegistry; + _disableInitializers(); + } + + // ============ Public Functions ============ + + /** + * @notice Updates the metadata URI for the AVS + * @param _metadataURI is the metadata URI for the AVS + * @dev only callable by the owner + */ + function updateAVSMetadataURI( + string memory _metadataURI + ) public virtual onlyOwner { + elAvsDirectory.updateAVSMetadataURI(_metadataURI); + } + + /** + * @notice Forwards a call to EigenLayer's AVSDirectory contract to confirm operator registration with the AVS + * @param operator The address of the operator to register. + * @param operatorSignature The signature, salt, and expiry of the operator's signature. + */ + function registerOperatorToAVS( + address operator, + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature + ) public virtual onlyStakeRegistry { + elAvsDirectory.registerOperatorToAVS(operator, operatorSignature); + } + + /** + * @notice Forwards a call to EigenLayer's AVSDirectory contract to confirm operator deregistration from the AVS + * @param operator The address of the operator to deregister. + */ + function deregisterOperatorFromAVS( + address operator + ) public virtual onlyStakeRegistry { + elAvsDirectory.deregisterOperatorFromAVS(operator); + } + + // ============ External Functions ============ + + /** + * @notice Returns the list of strategies that the AVS supports for restaking + * @dev This function is intended to be called off-chain + * @dev No guarantee is made on uniqueness of each element in the returned array. + * The off-chain service should do that validation separately + */ + function getRestakeableStrategies() + external + view + returns (address[] memory) + { + return _getRestakeableStrategies(); + } + + /** + * @notice Returns the list of strategies that the operator has potentially restaked on the AVS + * @dev This function is intended to be called off-chain + * @dev Since ECDSAStakeRegistry only supports one quorum, each operator restakes into all the AVS strategies + * @dev No guarantee is made on uniqueness of each element in the returned array. + * The off-chain service should do that validation separately + */ + function getOperatorRestakedStrategies( + address /* operator */ + ) external view returns (address[] memory) { + return _getRestakeableStrategies(); + } + + /// @notice Returns the EigenLayer AVSDirectory contract. + function avsDirectory() external view override returns (address) { + return address(elAvsDirectory); + } + + // ============ Internal Function ============ + + function _getRestakeableStrategies() + internal + view + returns (address[] memory) + { + uint256 strategyCount = stakeRegistry.quorum().strategies.length; + + if (strategyCount == 0) { + return new address[](0); + } + + address[] memory restakedStrategies = new address[](strategyCount); + for (uint256 i = 0; i < strategyCount; i++) { + restakedStrategies[i] = address( + stakeRegistry.quorum().strategies[i].strategy + ); + } + return restakedStrategies; + } + + // storage gap for upgradeability + // slither-disable-next-line shadowing-state + uint256[50] private __GAP; +} diff --git a/solidity/contracts/avs/HyperlaneServiceManager.sol b/solidity/contracts/avs/HyperlaneServiceManager.sol index 6959fdc29f..b5b2a049da 100644 --- a/solidity/contracts/avs/HyperlaneServiceManager.sol +++ b/solidity/contracts/avs/HyperlaneServiceManager.sol @@ -1,15 +1,13 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.8.0; -import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; - -import {ISignatureUtils} from "@eigenlayer/core/interfaces/ISignatureUtils.sol"; -import {IAVSDirectory} from "@eigenlayer/core/interfaces/IAVSDirectory.sol"; - +import {IAVSDirectory} from "@eigenlayer/interfaces/IAVSDirectory.sol"; import {ECDSAStakeRegistry} from "@eigenlayer/middleware/unaudited/ECDSAStakeRegistry.sol"; -import {IServiceManager} from "@eigenlayer/middleware/interfaces/IServiceManager.sol"; -contract HyperlaneServiceManager is IServiceManager, OwnableUpgradeable { +import {IRemoteChallenger} from "../interfaces/avs/IRemoteChallenger.sol"; +import {ECDSAServiceManagerBase} from "./ECDSAServiceManagerBase.sol"; + +contract HyperlaneServiceManager is ECDSAServiceManagerBase { enum EnrollmentStatus { ENROLLED, PENDING_UNENROLLMENT, @@ -21,89 +19,54 @@ contract HyperlaneServiceManager is IServiceManager, OwnableUpgradeable { uint256 unenrollmentStartBlock; } - ECDSAStakeRegistry internal immutable stakeRegistry; - IAVSDirectory internal immutable elAvsDirectory; - mapping(address => mapping(address => Enrollment)) - public enrolledTownCriers; - - /// @notice when applied to a function, only allows the ECDSAStakeRegistry to call it - modifier onlyStakeRegistry() { - require( - msg.sender == address(stakeRegistry), - "HyperlaneServiceManager: caller is not the stake registry" - ); - _; - } + public enrolledChallengers; // ============ Constructor ============ constructor( IAVSDirectory _avsDirectory, ECDSAStakeRegistry _stakeRegistry - ) { - elAvsDirectory = _avsDirectory; - stakeRegistry = _stakeRegistry; - _disableInitializers(); - } + ) ECDSAServiceManagerBase(_avsDirectory, _stakeRegistry) {} // ============ Public Functions ============ - /** - * @notice Updates the metadata URI for the AVS - * @param _metadataURI is the metadata URI for the AVS - * @dev only callable by the owner - */ - function updateAVSMetadataURI( - string memory _metadataURI - ) public virtual onlyOwner { - elAvsDirectory.updateAVSMetadataURI(_metadataURI); - } - - /** - * @notice Forwards a call to EigenLayer's AVSDirectory contract to confirm operator registration with the AVS - * @param operator The address of the operator to register. - * @param operatorSignature The signature, salt, and expiry of the operator's signature. - */ - function registerOperatorToAVS( - address operator, - ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature - ) public virtual onlyStakeRegistry { - elAvsDirectory.registerOperatorToAVS(operator, operatorSignature); - } - /** * @notice Forwards a call to EigenLayer's AVSDirectory contract to confirm operator deregistration from the AVS * @param operator The address of the operator to deregister. */ function deregisterOperatorFromAVS( address operator - ) public virtual onlyStakeRegistry { + ) public virtual override onlyStakeRegistry { elAvsDirectory.deregisterOperatorFromAVS(operator); } // ============ External Functions ============ - function enrollIntoTownCriers(ITownCrier[] memory _townCriers) external { - for (uint256 i = 0; i < _townCriers.length; i++) { - ITownCrier townCrier = _townCriers[i]; - enrolledTownCriers[msg.sender][address(townCrier)] = Enrollment( + function enrollIntoChallenger( + IRemoteChallenger[] memory _challengers + ) external { + for (uint256 i = 0; i < _challengers.length; i++) { + IRemoteChallenger challenger = _challengers[i]; + enrolledChallengers[msg.sender][address(challenger)] = Enrollment( EnrollmentStatus.ENROLLED, 0 ); } } - function queueUnenrollmentFromTownCriers( - ITownCrier[] memory _townCriers + function queueUnenrollmentFromChallenger( + IRemoteChallenger[] memory _challengers ) external { - for (uint256 i = 0; i < _townCriers.length; i++) { - ITownCrier townCrier = _townCriers[i]; + for (uint256 i = 0; i < _challengers.length; i++) { + IRemoteChallenger challenger = _challengers[i]; if ( - enrolledTownCriers[msg.sender][address(townCrier)].status != + enrolledChallengers[msg.sender][address(challenger)].status != EnrollmentStatus.UNENROLLED ) { - enrolledTownCriers[msg.sender][address(townCrier)] = Enrollment( + enrolledChallengers[msg.sender][ + address(challenger) + ] = Enrollment( EnrollmentStatus.PENDING_UNENROLLMENT, block.number ); @@ -111,82 +74,23 @@ contract HyperlaneServiceManager is IServiceManager, OwnableUpgradeable { } } - function completeQueuedUnenrollmentFromTownCriers( - ITownCrier[] memory _townCriers + function completeQueuedUnenrollmentFromChallenger( + IRemoteChallenger[] memory _challengers ) external { - for (uint256 i = 0; i < _townCriers.length; i++) { - ITownCrier townCrier = _townCriers[i]; + for (uint256 i = 0; i < _challengers.length; i++) { + IRemoteChallenger challenger = _challengers[i]; if ( - enrolledTownCriers[msg.sender][address(townCrier)].status == + enrolledChallengers[msg.sender][address(challenger)].status == EnrollmentStatus.PENDING_UNENROLLMENT && block.number >= - enrolledTownCriers[msg.sender][address(townCrier)] + enrolledChallengers[msg.sender][address(challenger)] .unenrollmentStartBlock + - townCrier.challengeDelayBlocks() + challenger.challengeDelayBlocks() ) { - enrolledTownCriers[msg.sender][address(townCrier)] = Enrollment( - EnrollmentStatus.UNENROLLED, - 0 - ); + enrolledChallengers[msg.sender][ + address(challenger) + ] = Enrollment(EnrollmentStatus.UNENROLLED, 0); } } } - - /** - * @notice Returns the list of strategies that the AVS supports for restaking - * @dev This function is intended to be called off-chain - * @dev No guarantee is made on uniqueness of each element in the returned array. - * The off-chain service should do that validation separately - */ - function getRestakeableStrategies() - external - view - returns (address[] memory) - { - return _getRestakeableStrategies(); - } - - /** - * @notice Returns the list of strategies that the operator has potentially restaked on the AVS - * @dev This function is intended to be called off-chain - * @dev Since ECDSAStakeRegistry only supports one quorum, each operator restakes into all the AVS strategies - * @dev No guarantee is made on uniqueness of each element in the returned array. - * The off-chain service should do that validation separately - */ - function getOperatorRestakedStrategies( - address /* operator */ - ) external view returns (address[] memory) { - return _getRestakeableStrategies(); - } - - /// @notice Returns the EigenLayer AVSDirectory contract. - function avsDirectory() external view override returns (address) { - return address(elAvsDirectory); - } - - // ============ Internal Function ============ - - function _getRestakeableStrategies() - internal - view - returns (address[] memory) - { - uint256 strategyCount = stakeRegistry.quorum().strategies.length; - - if (strategyCount == 0) { - return new address[](0); - } - - address[] memory restakedStrategies = new address[](strategyCount); - for (uint256 i = 0; i < strategyCount; i++) { - restakedStrategies[i] = address( - stakeRegistry.quorum().strategies[i].strategy - ); - } - return restakedStrategies; - } - - // storage gap for upgradeability - // slither-disable-next-line shadowing-state - uint256[50] private __GAP; } diff --git a/solidity/contracts/interfaces/avs/ITownCrier.sol b/solidity/contracts/interfaces/avs/IRemoteChallenger.sol similarity index 94% rename from solidity/contracts/interfaces/avs/ITownCrier.sol rename to solidity/contracts/interfaces/avs/IRemoteChallenger.sol index c980bdd3ef..0683cbe08f 100644 --- a/solidity/contracts/interfaces/avs/ITownCrier.sol +++ b/solidity/contracts/interfaces/avs/IRemoteChallenger.sol @@ -13,7 +13,7 @@ pragma solidity >=0.8.0; @@@@@@@@@ @@@@@@@@@ @@@@@@@@@ @@@@@@@@*/ -interface ITownCrier { +interface IRemoteChallenger { function challengeDelayBlocks() external view returns (uint256); function handleChallenge(address operator) external; } diff --git a/solidity/remappings.txt b/solidity/remappings.txt index 3ba4c0f841..fa66f86546 100644 --- a/solidity/remappings.txt +++ b/solidity/remappings.txt @@ -1,8 +1,10 @@ @openzeppelin=../node_modules/@openzeppelin @openzeppelin/contracts-upgradeable/=../node_modules/@openzeppelin/contracts-upgradeable/ +@openzeppelin/contracts/=lib/eigenlayer-middleware/lib/openzeppelin-contracts/contracts/ @layerzerolabs=../node_modules/@layerzerolabs @eth-optimism=../node_modules/@eth-optimism ds-test/=lib/forge-std/lib/ds-test/src/ forge-std/=lib/forge-std/src/ -@eigenlayer/core/=lib/eigenlayer-contracts/src/contracts/ +@eigenlayer/core/=lib/eigenlayer-contracts/src/contracts/core/ @eigenlayer/middleware/=lib/eigenlayer-middleware/src/ +@eigenlayer/interfaces/=lib/eigenlayer-contracts/src/contracts/interfaces/ diff --git a/solidity/test/avs/EigenlayerBase.sol b/solidity/test/avs/EigenlayerBase.sol new file mode 100644 index 0000000000..7b73f8fd69 --- /dev/null +++ b/solidity/test/avs/EigenlayerBase.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +import "forge-std/Test.sol"; + +// import {AVSDirectory} from "@eigenlayer/core/AVSDirectory.sol"; + +// contract EigenlayerBase is Test { +// AVSDirectory internal avsDirectory; +// DelegationManager internal delegationManager; + +// function _deployMockEigenlayer() internal { +// delegationManager = new +// avsDirectory = new AVSDirectory() +// } +// } diff --git a/solidity/test/avs/HyperlaneServiceManager.t.sol b/solidity/test/avs/HyperlaneServiceManager.t.sol index f81f5284a9..4c42c0eb3e 100644 --- a/solidity/test/avs/HyperlaneServiceManager.t.sol +++ b/solidity/test/avs/HyperlaneServiceManager.t.sol @@ -1,15 +1,118 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.8.0; -contract HyperlaneServiceManagerTest is Test { +import {DelegationManager} from "@eigenlayer/core/DelegationManager.sol"; +import {ISignatureUtils} from "@eigenlayer/interfaces/ISignatureUtils.sol"; +import {IAVSDirectory} from "@eigenlayer/interfaces/IAVSDirectory.sol"; + +import {MockAVSDeployer} from "eigenlayer-middleware/test/utils/MockAVSDeployer.sol"; +import {ECDSAStakeRegistry} from "@eigenlayer/middleware/unaudited/ECDSAStakeRegistry.sol"; + +import {HyperlaneServiceManager} from "../../contracts/avs/HyperlaneServiceManager.sol"; + +contract HyperlaneServiceManagerTest is MockAVSDeployer { + // TODO // register -> deregister // register -> stake -> deregister // register -> stake -> queue withdrawal -> deregister // register -> stake -> queue withdrawal -> complete -> deregister - // enroll for 3 test towncriers -> unenroll + // enroll for 3 test challengers -> unenroll // enroll, stake/unstake -> unenroll // enroll, // register. enroll, unenroll partial, deregister // register. enroll, deregister // register, handle challenge=true, deregister + + DelegationManager public delegationManager; + + HyperlaneServiceManager internal hsm; + ECDSAStakeRegistry internal ecdsaStakeRegistry; + + // Operator info + uint256 operatorPrivateKey = 0xdeadbeef; + address operator; + + bytes32 emptySalt; + uint256 maxExpiry = type(uint256).max; + + function setUp() public { + _deployMockEigenLayerAndAVS(); + + delegationManager = new DelegationManager( + strategyManagerMock, + slasher, + eigenPodManagerMock + ); + + ecdsaStakeRegistry = new ECDSAStakeRegistry(delegationManager); + + hsm = new HyperlaneServiceManager(avsDirectory, ecdsaStakeRegistry); + + // todo + // IStakeRegistry.StrategyParams[][] memory quorumStrategiesConsideredAndMultipliers = + // new IStakeRegistry.StrategyParams[][](numQuorumsToAdd); + // for (uint256 i = 0; i < quorumStrategiesConsideredAndMultipliers.length; i++) { + // quorumStrategiesConsideredAndMultipliers[i] = new IStakeRegistry.StrategyParams[](1); + // quorumStrategiesConsideredAndMultipliers[i][0] = + // IStakeRegistry.StrategyParams(IStrategy(address(uint160(i))), uint96(WEIGHTING_DIVISOR)); + // } + // Quorum memory _quorum = Quorum({strategies: quorumStrategiesConsideredAndMultipliers}); + // ecdsaStakeRegistry.initialize(address(hsm), 6667, _quorum); + } + + function test_registerOperator() public { + // act + ISignatureUtils.SignatureWithSaltAndExpiry + memory operatorSignature = _getOperatorSignature( + operatorPrivateKey, + operator, + address(hsm), + emptySalt, + maxExpiry + ); + ecdsaStakeRegistry.registerOperatorWithSignature( + operator, + operatorSignature + ); + + // assert + IAVSDirectory.OperatorAVSRegistrationStatus operatorStatus = avsDirectory + .avsOperatorStatus(address(serviceManager), operator); + assertEq( + uint8(operatorStatus), + uint8(IAVSDirectory.OperatorAVSRegistrationStatus.REGISTERED) + ); + } + + function _getOperatorSignature( + uint256 _operatorPrivateKey, + address operatorToSign, + address avs, + bytes32 salt, + uint256 expiry + ) + internal + view + returns ( + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature + ) + { + operatorSignature.salt = salt; + operatorSignature.expiry = expiry; + { + bytes32 digestHash = avsDirectory + .calculateOperatorAVSRegistrationDigestHash( + operatorToSign, + avs, + salt, + expiry + ); + (uint8 v, bytes32 r, bytes32 s) = cheats.sign( + _operatorPrivateKey, + digestHash + ); + operatorSignature.signature = abi.encodePacked(r, s, v); + } + return operatorSignature; + } } From 0488bc50affa29d3995a7bc2598635596f2ec26c Mon Sep 17 00:00:00 2001 From: -f Date: Thu, 25 Apr 2024 10:16:13 +0530 Subject: [PATCH 07/32] add enumerablemap --- .../contracts/avs/ECDSAServiceManagerBase.sol | 5 + .../contracts/avs/HyperlaneServiceManager.sol | 82 +++++++----- .../libs/EnumerableMapEnrollment.sol | 117 ++++++++++++++++++ .../test/avs/HyperlaneServiceManager.t.sol | 41 ++++-- 4 files changed, 202 insertions(+), 43 deletions(-) create mode 100644 solidity/contracts/libs/EnumerableMapEnrollment.sol diff --git a/solidity/contracts/avs/ECDSAServiceManagerBase.sol b/solidity/contracts/avs/ECDSAServiceManagerBase.sol index 2a9f904f8e..8e440e723a 100644 --- a/solidity/contracts/avs/ECDSAServiceManagerBase.sol +++ b/solidity/contracts/avs/ECDSAServiceManagerBase.sol @@ -12,6 +12,9 @@ import {IServiceManager} from "@eigenlayer/middleware/interfaces/IServiceManager import {IRemoteChallenger} from "../interfaces/avs/IRemoteChallenger.sol"; contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable { + event OperatorRegisteredToAVS(address indexed operator); + event OperatorDeregisteredToAVS(address indexed operator); + ECDSAStakeRegistry internal immutable stakeRegistry; IAVSDirectory internal immutable elAvsDirectory; @@ -58,6 +61,7 @@ contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable { ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature ) public virtual onlyStakeRegistry { elAvsDirectory.registerOperatorToAVS(operator, operatorSignature); + emit OperatorRegisteredToAVS(operator); } /** @@ -68,6 +72,7 @@ contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable { address operator ) public virtual onlyStakeRegistry { elAvsDirectory.deregisterOperatorFromAVS(operator); + emit OperatorDeregisteredToAVS(operator); } // ============ External Functions ============ diff --git a/solidity/contracts/avs/HyperlaneServiceManager.sol b/solidity/contracts/avs/HyperlaneServiceManager.sol index b5b2a049da..b9ecdf8879 100644 --- a/solidity/contracts/avs/HyperlaneServiceManager.sol +++ b/solidity/contracts/avs/HyperlaneServiceManager.sol @@ -4,23 +4,31 @@ pragma solidity >=0.8.0; import {IAVSDirectory} from "@eigenlayer/interfaces/IAVSDirectory.sol"; import {ECDSAStakeRegistry} from "@eigenlayer/middleware/unaudited/ECDSAStakeRegistry.sol"; +import {Enrollment, EnrollmentStatus, EnumerableMapEnrollment} from "../libs/EnumerableMapEnrollment.sol"; import {IRemoteChallenger} from "../interfaces/avs/IRemoteChallenger.sol"; import {ECDSAServiceManagerBase} from "./ECDSAServiceManagerBase.sol"; contract HyperlaneServiceManager is ECDSAServiceManagerBase { - enum EnrollmentStatus { - ENROLLED, - PENDING_UNENROLLMENT, - UNENROLLED - } + using EnumerableMapEnrollment for EnumerableMapEnrollment.AddressToEnrollmentMap; - struct Enrollment { - EnrollmentStatus status; - uint256 unenrollmentStartBlock; - } + event OperatorEnrolledToChallenger( + address operator, + IRemoteChallenger challenger + ); + event OperatorQueuedUnenrollmentFromChallenger( + address operator, + IRemoteChallenger challenger, + uint256 unenrollmentStartBlock, + uint256 challengeDelayBlocks + ); + event OperatorUnenrolledFromChallenger( + address operator, + IRemoteChallenger challenger, + uint256 unenrollmentEndBlock, + uint256 challengeDelayBlocks + ); - mapping(address => mapping(address => Enrollment)) - public enrolledChallengers; + mapping(address => EnumerableMapEnrollment.AddressToEnrollmentMap) enrolledChallengers; // ============ Constructor ============ @@ -48,10 +56,11 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { ) external { for (uint256 i = 0; i < _challengers.length; i++) { IRemoteChallenger challenger = _challengers[i]; - enrolledChallengers[msg.sender][address(challenger)] = Enrollment( - EnrollmentStatus.ENROLLED, - 0 + enrolledChallengers[msg.sender].set( + address(challenger), + Enrollment(EnrollmentStatus.ENROLLED, 0) ); + emit OperatorEnrolledToChallenger(msg.sender, challenger); } } @@ -60,15 +69,22 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { ) external { for (uint256 i = 0; i < _challengers.length; i++) { IRemoteChallenger challenger = _challengers[i]; - if ( - enrolledChallengers[msg.sender][address(challenger)].status != - EnrollmentStatus.UNENROLLED - ) { - enrolledChallengers[msg.sender][ - address(challenger) - ] = Enrollment( - EnrollmentStatus.PENDING_UNENROLLMENT, - block.number + (bool exists, Enrollment memory enrollment) = enrolledChallengers[ + msg.sender + ].tryGet(address(challenger)); + if (exists && enrollment.status == EnrollmentStatus.ENROLLED) { + enrolledChallengers[msg.sender].set( + address(challenger), + Enrollment( + EnrollmentStatus.PENDING_UNENROLLMENT, + uint248(block.number) + ) + ); + emit OperatorQueuedUnenrollmentFromChallenger( + msg.sender, + challenger, + block.number, + challenger.challengeDelayBlocks() ); } } @@ -79,17 +95,23 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { ) external { for (uint256 i = 0; i < _challengers.length; i++) { IRemoteChallenger challenger = _challengers[i]; + (bool exists, Enrollment memory enrollment) = enrolledChallengers[ + msg.sender + ].tryGet(address(challenger)); if ( - enrolledChallengers[msg.sender][address(challenger)].status == - EnrollmentStatus.PENDING_UNENROLLMENT && + exists && + enrollment.status == EnrollmentStatus.PENDING_UNENROLLMENT && block.number >= - enrolledChallengers[msg.sender][address(challenger)] - .unenrollmentStartBlock + + enrollment.unenrollmentStartBlock + challenger.challengeDelayBlocks() ) { - enrolledChallengers[msg.sender][ - address(challenger) - ] = Enrollment(EnrollmentStatus.UNENROLLED, 0); + enrolledChallengers[msg.sender].remove(address(challenger)); + emit OperatorUnenrolledFromChallenger( + msg.sender, + challenger, + block.number, + challenger.challengeDelayBlocks() + ); } } } diff --git a/solidity/contracts/libs/EnumerableMapEnrollment.sol b/solidity/contracts/libs/EnumerableMapEnrollment.sol new file mode 100644 index 0000000000..99cb4396e6 --- /dev/null +++ b/solidity/contracts/libs/EnumerableMapEnrollment.sol @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.11; + +// ============ External Imports ============ +import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; +import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +// ============ Internal Imports ============ +import {TypeCasts} from "./TypeCasts.sol"; + +enum EnrollmentStatus { + ENROLLED, + PENDING_UNENROLLMENT, + UNENROLLED +} + +struct Enrollment { + EnrollmentStatus status; + uint248 unenrollmentStartBlock; +} + +// extends EnumerableMap with address => bytes32 type +// modelled after https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.0/contracts/utils/structs/EnumerableMap.sol +library EnumerableMapEnrollment { + using EnumerableMap for EnumerableMap.Bytes32ToBytes32Map; + using EnumerableSet for EnumerableSet.Bytes32Set; + using TypeCasts for address; + using TypeCasts for bytes32; + + struct AddressToEnrollmentMap { + EnumerableMap.Bytes32ToBytes32Map _inner; + } + + // ============ Library Functions ============ + + function encode( + Enrollment memory enrollment + ) public pure returns (bytes32) { + return + bytes32( + abi.encodePacked( + uint8(enrollment.status), + enrollment.unenrollmentStartBlock + ) + ); + } + + function decode(bytes32 encoded) public pure returns (Enrollment memory) { + (uint8 status, uint248 unenrollmentStartBlock) = abi.decode( + abi.encodePacked(encoded), + (uint8, uint248) + ); + return Enrollment(EnrollmentStatus(status), unenrollmentStartBlock); + } + + function keys( + AddressToEnrollmentMap storage map + ) internal view returns (address[] memory _keys) { + uint256 _length = map._inner.length(); + _keys = new address[](_length); + for (uint256 i = 0; i < _length; i++) { + _keys[i] = address(uint160(uint256(map._inner._keys.at(i)))); + } + } + + function set( + AddressToEnrollmentMap storage map, + address key, + Enrollment memory value + ) internal returns (bool) { + return map._inner.set(key.addressToBytes32(), encode(value)); + } + + function get( + AddressToEnrollmentMap storage map, + address key + ) internal view returns (Enrollment memory) { + return decode(map._inner.get(key.addressToBytes32())); + } + + function tryGet( + AddressToEnrollmentMap storage map, + address key + ) internal view returns (bool, Enrollment memory) { + (bool success, bytes32 value) = map._inner.tryGet( + key.addressToBytes32() + ); + return (success, decode(value)); + } + + function remove( + AddressToEnrollmentMap storage map, + address key + ) internal returns (bool) { + return map._inner.remove(key.addressToBytes32()); + } + + function contains( + AddressToEnrollmentMap storage map, + address key + ) internal view returns (bool) { + return map._inner.contains(key.addressToBytes32()); + } + + function length( + AddressToEnrollmentMap storage map + ) internal view returns (uint256) { + return map._inner.length(); + } + + function at( + AddressToEnrollmentMap storage map, + uint256 index + ) internal view returns (uint256, Enrollment memory) { + (bytes32 key, bytes32 value) = map._inner.at(index); + return (uint256(key), decode(value)); + } +} diff --git a/solidity/test/avs/HyperlaneServiceManager.t.sol b/solidity/test/avs/HyperlaneServiceManager.t.sol index 4c42c0eb3e..18e361914e 100644 --- a/solidity/test/avs/HyperlaneServiceManager.t.sol +++ b/solidity/test/avs/HyperlaneServiceManager.t.sol @@ -1,11 +1,16 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.8.0; +import "forge-std/console.sol"; + import {DelegationManager} from "@eigenlayer/core/DelegationManager.sol"; import {ISignatureUtils} from "@eigenlayer/interfaces/ISignatureUtils.sol"; import {IAVSDirectory} from "@eigenlayer/interfaces/IAVSDirectory.sol"; +import {IDelegationManager} from "@eigenlayer/interfaces/IDelegationManager.sol"; +import {IStrategy} from "@eigenlayer/interfaces/IStrategy.sol"; import {MockAVSDeployer} from "eigenlayer-middleware/test/utils/MockAVSDeployer.sol"; +import {Quorum, StrategyParams} from "@eigenlayer/middleware/unaudited/ECDSAStakeRegistryStorage.sol"; import {ECDSAStakeRegistry} from "@eigenlayer/middleware/unaudited/ECDSAStakeRegistry.sol"; import {HyperlaneServiceManager} from "../../contracts/avs/HyperlaneServiceManager.sol"; @@ -37,7 +42,6 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { function setUp() public { _deployMockEigenLayerAndAVS(); - delegationManager = new DelegationManager( strategyManagerMock, slasher, @@ -45,22 +49,33 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { ); ecdsaStakeRegistry = new ECDSAStakeRegistry(delegationManager); - hsm = new HyperlaneServiceManager(avsDirectory, ecdsaStakeRegistry); - // todo - // IStakeRegistry.StrategyParams[][] memory quorumStrategiesConsideredAndMultipliers = - // new IStakeRegistry.StrategyParams[][](numQuorumsToAdd); - // for (uint256 i = 0; i < quorumStrategiesConsideredAndMultipliers.length; i++) { - // quorumStrategiesConsideredAndMultipliers[i] = new IStakeRegistry.StrategyParams[](1); - // quorumStrategiesConsideredAndMultipliers[i][0] = - // IStakeRegistry.StrategyParams(IStrategy(address(uint160(i))), uint96(WEIGHTING_DIVISOR)); - // } - // Quorum memory _quorum = Quorum({strategies: quorumStrategiesConsideredAndMultipliers}); - // ecdsaStakeRegistry.initialize(address(hsm), 6667, _quorum); + IStrategy mockStrategy = IStrategy(address(0x1234)); + Quorum memory quorum = Quorum({strategies: new StrategyParams[](1)}); + quorum.strategies[0] = StrategyParams({ + strategy: mockStrategy, + multiplier: 10000 + }); + ecdsaStakeRegistry.initialize(address(hsm), 6667, quorum); + + // register operator to eigenlayer + operator = cheats.addr(operatorPrivateKey); + cheats.prank(operator); + delegationManager.registerAsOperator( + IDelegationManager.OperatorDetails({ + earningsReceiver: operator, + delegationApprover: address(0), + stakerOptOutWindowBlocks: 0 + }), + "" + ); + // set operator as registered in Eigenlayer + delegationMock.setIsOperator(operator, true); } function test_registerOperator() public { + operator = cheats.addr(operatorPrivateKey); // act ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature = _getOperatorSignature( @@ -77,7 +92,7 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { // assert IAVSDirectory.OperatorAVSRegistrationStatus operatorStatus = avsDirectory - .avsOperatorStatus(address(serviceManager), operator); + .avsOperatorStatus(address(hsm), operator); assertEq( uint8(operatorStatus), uint8(IAVSDirectory.OperatorAVSRegistrationStatus.REGISTERED) From f737489d4d52a4f582965fbfa3fccad8be278384 Mon Sep 17 00:00:00 2001 From: -f Date: Thu, 25 Apr 2024 10:29:52 +0530 Subject: [PATCH 08/32] remove challengers --- .../contracts/avs/HyperlaneServiceManager.sol | 17 +++++++++++++++++ .../contracts/libs/EnumerableMapEnrollment.sol | 3 +-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/solidity/contracts/avs/HyperlaneServiceManager.sol b/solidity/contracts/avs/HyperlaneServiceManager.sol index b9ecdf8879..e205f7c6b4 100644 --- a/solidity/contracts/avs/HyperlaneServiceManager.sol +++ b/solidity/contracts/avs/HyperlaneServiceManager.sol @@ -46,6 +46,23 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { function deregisterOperatorFromAVS( address operator ) public virtual override onlyStakeRegistry { + address[] memory challengers = enrolledChallengers[operator].keys(); + for (uint256 i = 0; i < challengers.length; i++) { + IRemoteChallenger challenger = IRemoteChallenger(challengers[i]); + Enrollment memory enrollment = enrolledChallengers[operator].get( + challengers[i] + ); + require( + enrollment.status != EnrollmentStatus.ENROLLED, + string( + abi.encodePacked( + "HyperlaneServiceManager: Operator still enrolled in challenger", + challengers[i] + ) + ) + ); + enrolledChallengers[operator].remove(challengers[i]); + } elAvsDirectory.deregisterOperatorFromAVS(operator); } diff --git a/solidity/contracts/libs/EnumerableMapEnrollment.sol b/solidity/contracts/libs/EnumerableMapEnrollment.sol index 99cb4396e6..f87c27b5ed 100644 --- a/solidity/contracts/libs/EnumerableMapEnrollment.sol +++ b/solidity/contracts/libs/EnumerableMapEnrollment.sol @@ -9,8 +9,7 @@ import {TypeCasts} from "./TypeCasts.sol"; enum EnrollmentStatus { ENROLLED, - PENDING_UNENROLLMENT, - UNENROLLED + PENDING_UNENROLLMENT } struct Enrollment { From 0eaffff8a8c9d97ef523cce25ea7837c98a5eb11 Mon Sep 17 00:00:00 2001 From: -f Date: Fri, 26 Apr 2024 12:20:37 +0530 Subject: [PATCH 09/32] add unenroll tests --- .../contracts/avs/ECDSAServiceManagerBase.sol | 10 +- .../contracts/avs/HyperlaneServiceManager.sol | 53 +++- .../libs/EnumerableMapEnrollment.sol | 15 +- .../contracts/test/ERC4626/ERC4626Test.sol | 5 +- .../contracts/test/TestRemoteChallenger.sol | 12 + solidity/test/avs/EigenlayerBase.sol | 16 -- .../test/avs/HyperlaneServiceManager.t.sol | 255 +++++++++++++++++- 7 files changed, 331 insertions(+), 35 deletions(-) create mode 100644 solidity/contracts/test/TestRemoteChallenger.sol delete mode 100644 solidity/test/avs/EigenlayerBase.sol diff --git a/solidity/contracts/avs/ECDSAServiceManagerBase.sol b/solidity/contracts/avs/ECDSAServiceManagerBase.sol index 8e440e723a..d534d91ea8 100644 --- a/solidity/contracts/avs/ECDSAServiceManagerBase.sol +++ b/solidity/contracts/avs/ECDSAServiceManagerBase.sol @@ -5,6 +5,7 @@ import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Own import {ISignatureUtils} from "@eigenlayer/interfaces/ISignatureUtils.sol"; import {IAVSDirectory} from "@eigenlayer/interfaces/IAVSDirectory.sol"; +import {ISlasher} from "@eigenlayer/interfaces/ISlasher.sol"; import {ECDSAStakeRegistry} from "@eigenlayer/middleware/unaudited/ECDSAStakeRegistry.sol"; import {IServiceManager} from "@eigenlayer/middleware/interfaces/IServiceManager.sol"; @@ -17,6 +18,7 @@ contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable { ECDSAStakeRegistry internal immutable stakeRegistry; IAVSDirectory internal immutable elAvsDirectory; + ISlasher internal slasher; /// @notice when applied to a function, only allows the ECDSAStakeRegistry to call it modifier onlyStakeRegistry() { @@ -31,10 +33,12 @@ contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable { constructor( IAVSDirectory _avsDirectory, - ECDSAStakeRegistry _stakeRegistry + ECDSAStakeRegistry _stakeRegistry, + ISlasher _slasher ) { elAvsDirectory = _avsDirectory; stakeRegistry = _stakeRegistry; + slasher = _slasher; _disableInitializers(); } @@ -75,6 +79,10 @@ contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable { emit OperatorDeregisteredToAVS(operator); } + function freezeOperator(address operator) public virtual onlyOwner { + slasher.freezeOperator(operator); + } + // ============ External Functions ============ /** diff --git a/solidity/contracts/avs/HyperlaneServiceManager.sol b/solidity/contracts/avs/HyperlaneServiceManager.sol index e205f7c6b4..0f86e0a606 100644 --- a/solidity/contracts/avs/HyperlaneServiceManager.sol +++ b/solidity/contracts/avs/HyperlaneServiceManager.sol @@ -1,7 +1,10 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.8.0; +import "forge-std/console.sol"; + import {IAVSDirectory} from "@eigenlayer/interfaces/IAVSDirectory.sol"; +import {ISlasher} from "@eigenlayer/interfaces/ISlasher.sol"; import {ECDSAStakeRegistry} from "@eigenlayer/middleware/unaudited/ECDSAStakeRegistry.sol"; import {Enrollment, EnrollmentStatus, EnumerableMapEnrollment} from "../libs/EnumerableMapEnrollment.sol"; @@ -30,12 +33,22 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { mapping(address => EnumerableMapEnrollment.AddressToEnrollmentMap) enrolledChallengers; + modifier onlyEnrolledChallenger(address operator) { + (bool exists, ) = enrolledChallengers[operator].tryGet(msg.sender); + require( + exists, + "HyperlaneServiceManager: Operator not enrolled in challenger" + ); + _; + } + // ============ Constructor ============ constructor( IAVSDirectory _avsDirectory, - ECDSAStakeRegistry _stakeRegistry - ) ECDSAServiceManagerBase(_avsDirectory, _stakeRegistry) {} + ECDSAStakeRegistry _stakeRegistry, + ISlasher _slasher + ) ECDSAServiceManagerBase(_avsDirectory, _stakeRegistry, _slasher) {} // ============ Public Functions ============ @@ -46,7 +59,7 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { function deregisterOperatorFromAVS( address operator ) public virtual override onlyStakeRegistry { - address[] memory challengers = enrolledChallengers[operator].keys(); + address[] memory challengers = getOperatorChallengers(operator); for (uint256 i = 0; i < challengers.length; i++) { IRemoteChallenger challenger = IRemoteChallenger(challengers[i]); Enrollment memory enrollment = enrolledChallengers[operator].get( @@ -68,7 +81,7 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { // ============ External Functions ============ - function enrollIntoChallenger( + function enrollIntoChallengers( IRemoteChallenger[] memory _challengers ) external { for (uint256 i = 0; i < _challengers.length; i++) { @@ -81,7 +94,7 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { } } - function queueUnenrollmentFromChallenger( + function queueUnenrollmentFromChallengers( IRemoteChallenger[] memory _challengers ) external { for (uint256 i = 0; i < _challengers.length; i++) { @@ -89,6 +102,11 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { (bool exists, Enrollment memory enrollment) = enrolledChallengers[ msg.sender ].tryGet(address(challenger)); + console.log( + "queueUnenrollmentFromChallengers", + exists, + uint8(enrollment.status) + ); if (exists && enrollment.status == EnrollmentStatus.ENROLLED) { enrolledChallengers[msg.sender].set( address(challenger), @@ -107,7 +125,7 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { } } - function completeQueuedUnenrollmentFromChallenger( + function completeQueuedUnenrollmentFromChallengers( IRemoteChallenger[] memory _challengers ) external { for (uint256 i = 0; i < _challengers.length; i++) { @@ -132,4 +150,27 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { } } } + + function freezeOperator( + address operator + ) public virtual override onlyEnrolledChallenger(operator) { + slasher.freezeOperator(operator); + } + + function getEnrolledChallenger( + address _operator, + IRemoteChallenger _challenger + ) external view returns (Enrollment memory enrollment) { + address[] memory keys = enrolledChallengers[_operator].keys(); + for (uint256 i = 0; i < keys.length; i++) { + console.log("key", keys[i], address(_challenger)); + } + return enrolledChallengers[_operator].get(address(_challenger)); + } + + function getOperatorChallengers( + address _operator + ) public view returns (address[] memory) { + return enrolledChallengers[_operator].keys(); + } } diff --git a/solidity/contracts/libs/EnumerableMapEnrollment.sol b/solidity/contracts/libs/EnumerableMapEnrollment.sol index f87c27b5ed..3d75ddd6f2 100644 --- a/solidity/contracts/libs/EnumerableMapEnrollment.sol +++ b/solidity/contracts/libs/EnumerableMapEnrollment.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.6.11; +import "forge-std/console.sol"; + // ============ External Imports ============ import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; @@ -8,6 +10,7 @@ import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {TypeCasts} from "./TypeCasts.sol"; enum EnrollmentStatus { + UNENROLLED, ENROLLED, PENDING_UNENROLLMENT } @@ -43,11 +46,10 @@ library EnumerableMapEnrollment { ); } - function decode(bytes32 encoded) public pure returns (Enrollment memory) { - (uint8 status, uint248 unenrollmentStartBlock) = abi.decode( - abi.encodePacked(encoded), - (uint8, uint248) - ); + function decode(bytes32 encoded) public view returns (Enrollment memory) { + uint8 status = uint8(encoded[0]); + uint248 unenrollmentStartBlock = uint248(uint256((encoded << 8) >> 8)); + console.log("decode", status, unenrollmentStartBlock); return Enrollment(EnrollmentStatus(status), unenrollmentStartBlock); } @@ -66,6 +68,8 @@ library EnumerableMapEnrollment { address key, Enrollment memory value ) internal returns (bool) { + console.log("set", key); + console.logBytes32(encode(value)); return map._inner.set(key.addressToBytes32(), encode(value)); } @@ -73,6 +77,7 @@ library EnumerableMapEnrollment { AddressToEnrollmentMap storage map, address key ) internal view returns (Enrollment memory) { + console.logBytes32(map._inner.get(key.addressToBytes32())); return decode(map._inner.get(key.addressToBytes32())); } diff --git a/solidity/contracts/test/ERC4626/ERC4626Test.sol b/solidity/contracts/test/ERC4626/ERC4626Test.sol index 044429b539..7604fabd74 100644 --- a/solidity/contracts/test/ERC4626/ERC4626Test.sol +++ b/solidity/contracts/test/ERC4626/ERC4626Test.sol @@ -1,13 +1,14 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity >=0.8.0; + import "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "@openzeppelin/contracts/interfaces/IERC20.sol"; +import "@openzeppelin/contracts/interfaces/IERC20Metadata.sol"; contract ERC4626Test is ERC4626 { constructor( address _asset, string memory _name, string memory _symbol - ) ERC4626(IERC20(_asset)) ERC20(_name, _symbol) {} + ) ERC4626(IERC20Metadata(_asset)) ERC20(_name, _symbol) {} } diff --git a/solidity/contracts/test/TestRemoteChallenger.sol b/solidity/contracts/test/TestRemoteChallenger.sol new file mode 100644 index 0000000000..fd700e2d91 --- /dev/null +++ b/solidity/contracts/test/TestRemoteChallenger.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +import {IRemoteChallenger} from "../interfaces/avs/IRemoteChallenger.sol"; + +contract TestRemoteChallenger is IRemoteChallenger { + function challengeDelayBlocks() external view returns (uint256) { + return 50400; // one week of eth L1 blocks + } + + function handleChallenge(address operator) external {} +} diff --git a/solidity/test/avs/EigenlayerBase.sol b/solidity/test/avs/EigenlayerBase.sol deleted file mode 100644 index 7b73f8fd69..0000000000 --- a/solidity/test/avs/EigenlayerBase.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity >=0.8.0; - -import "forge-std/Test.sol"; - -// import {AVSDirectory} from "@eigenlayer/core/AVSDirectory.sol"; - -// contract EigenlayerBase is Test { -// AVSDirectory internal avsDirectory; -// DelegationManager internal delegationManager; - -// function _deployMockEigenlayer() internal { -// delegationManager = new -// avsDirectory = new AVSDirectory() -// } -// } diff --git a/solidity/test/avs/HyperlaneServiceManager.t.sol b/solidity/test/avs/HyperlaneServiceManager.t.sol index 18e361914e..b19b5685c8 100644 --- a/solidity/test/avs/HyperlaneServiceManager.t.sol +++ b/solidity/test/avs/HyperlaneServiceManager.t.sol @@ -13,7 +13,10 @@ import {MockAVSDeployer} from "eigenlayer-middleware/test/utils/MockAVSDeployer. import {Quorum, StrategyParams} from "@eigenlayer/middleware/unaudited/ECDSAStakeRegistryStorage.sol"; import {ECDSAStakeRegistry} from "@eigenlayer/middleware/unaudited/ECDSAStakeRegistry.sol"; +import {Enrollment, EnrollmentStatus} from "../../contracts/libs/EnumerableMapEnrollment.sol"; +import {IRemoteChallenger} from "../../contracts/interfaces/avs/IRemoteChallenger.sol"; import {HyperlaneServiceManager} from "../../contracts/avs/HyperlaneServiceManager.sol"; +import {TestRemoteChallenger} from "../../contracts/test/TestRemoteChallenger.sol"; contract HyperlaneServiceManagerTest is MockAVSDeployer { // TODO @@ -39,6 +42,7 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { bytes32 emptySalt; uint256 maxExpiry = type(uint256).max; + uint256 challengeDelayBlocks = 50400; // one week of eth L1 blocks function setUp() public { _deployMockEigenLayerAndAVS(); @@ -49,7 +53,11 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { ); ecdsaStakeRegistry = new ECDSAStakeRegistry(delegationManager); - hsm = new HyperlaneServiceManager(avsDirectory, ecdsaStakeRegistry); + hsm = new HyperlaneServiceManager( + avsDirectory, + ecdsaStakeRegistry, + slasher + ); IStrategy mockStrategy = IStrategy(address(0x1234)); Quorum memory quorum = Quorum({strategies: new StrategyParams[](1)}); @@ -60,8 +68,8 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { ecdsaStakeRegistry.initialize(address(hsm), 6667, quorum); // register operator to eigenlayer - operator = cheats.addr(operatorPrivateKey); - cheats.prank(operator); + operator = vm.addr(operatorPrivateKey); + vm.prank(operator); delegationManager.registerAsOperator( IDelegationManager.OperatorDetails({ earningsReceiver: operator, @@ -75,7 +83,6 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { } function test_registerOperator() public { - operator = cheats.addr(operatorPrivateKey); // act ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature = _getOperatorSignature( @@ -99,6 +106,244 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { ); } + function test_registerOperator_revert_invalidSignature() public { + // act + ISignatureUtils.SignatureWithSaltAndExpiry + memory operatorSignature = _getOperatorSignature( + operatorPrivateKey, + operator, + address(serviceManager), + emptySalt, + maxExpiry + ); + + vm.expectRevert( + "EIP1271SignatureUtils.checkSignature_EIP1271: signature not from signer" + ); + ecdsaStakeRegistry.registerOperatorWithSignature( + operator, + operatorSignature + ); + + // assert + IAVSDirectory.OperatorAVSRegistrationStatus operatorStatus = avsDirectory + .avsOperatorStatus(address(hsm), operator); + assertEq( + uint8(operatorStatus), + uint8(IAVSDirectory.OperatorAVSRegistrationStatus.UNREGISTERED) + ); + } + + function test_registerOperator_revert_expiredSignature() public { + // act + ISignatureUtils.SignatureWithSaltAndExpiry + memory operatorSignature = _getOperatorSignature( + operatorPrivateKey, + operator, + address(hsm), + emptySalt, + 0 + ); + + vm.expectRevert( + "AVSDirectory.registerOperatorToAVS: operator signature expired" + ); + ecdsaStakeRegistry.registerOperatorWithSignature( + operator, + operatorSignature + ); + + // assert + IAVSDirectory.OperatorAVSRegistrationStatus operatorStatus = avsDirectory + .avsOperatorStatus(address(hsm), operator); + assertEq( + uint8(operatorStatus), + uint8(IAVSDirectory.OperatorAVSRegistrationStatus.UNREGISTERED) + ); + } + + function test_deregisterOperator() public { + // act + _registerOperator(); + vm.prank(operator); + ecdsaStakeRegistry.deregisterOperator(); + + // assert + IAVSDirectory.OperatorAVSRegistrationStatus operatorStatus = avsDirectory + .avsOperatorStatus(address(hsm), operator); + assertEq( + uint8(operatorStatus), + uint8(IAVSDirectory.OperatorAVSRegistrationStatus.UNREGISTERED) + ); + } + + /// forge-config: default.fuzz.runs = 10 + function test_enrollIntoChallengers(uint8 numOfChallengers) public { + _registerOperator(); + IRemoteChallenger[] memory challengers = _deployChallengers( + numOfChallengers + ); + + vm.prank(operator); + hsm.enrollIntoChallengers(challengers); + + _assertChallengers(challengers, EnrollmentStatus.ENROLLED, 0); + } + + /// forge-config: default.fuzz.runs = 10 + function test_queueUnenrollmentFromChallengers_all( + uint8 numOfChallengers + ) public { + _registerOperator(); + IRemoteChallenger[] memory challengers = _deployChallengers( + numOfChallengers + ); + + vm.startPrank(operator); + hsm.enrollIntoChallengers(challengers); + _assertChallengers(challengers, EnrollmentStatus.ENROLLED, 0); + + hsm.queueUnenrollmentFromChallengers(challengers); + _assertChallengers( + challengers, + EnrollmentStatus.PENDING_UNENROLLMENT, + block.number + ); + + vm.roll(block.number + challengeDelayBlocks); + + hsm.completeQueuedUnenrollmentFromChallengers(challengers); + + // get all operator key length assert + assertEq(hsm.getOperatorChallengers(operator).length, 0); + vm.stopPrank(); + } + + /// forge-config: default.fuzz.runs = 10 + function test_queueUnenrollmentFromChallengers( + uint8 numOfChallengers, + uint8 numQueued + ) public { + vm.assume(numQueued <= numOfChallengers); + + _registerOperator(); + IRemoteChallenger[] memory challengers = _deployChallengers( + numOfChallengers + ); + IRemoteChallenger[] memory queuedChallengers = new IRemoteChallenger[]( + numQueued + ); + for (uint8 i = 0; i < numQueued; i++) { + queuedChallengers[i] = challengers[i]; + } + IRemoteChallenger[] + memory unqueuedChallengers = new IRemoteChallenger[]( + numOfChallengers - numQueued + ); + for (uint8 i = numQueued; i < numOfChallengers; i++) { + unqueuedChallengers[i - numQueued] = challengers[i]; + } + + vm.startPrank(operator); + hsm.enrollIntoChallengers(challengers); + _assertChallengers(challengers, EnrollmentStatus.ENROLLED, 0); + + hsm.queueUnenrollmentFromChallengers(queuedChallengers); + _assertChallengers( + queuedChallengers, + EnrollmentStatus.PENDING_UNENROLLMENT, + block.number + ); + _assertChallengers(unqueuedChallengers, EnrollmentStatus.ENROLLED, 0); + + vm.stopPrank(); + } + + /// forge-config: default.fuzz.runs = 10 + function test_completeQueuedUnenrollmentFromChallenger( + uint8 numOfChallengers, + uint8 numUnenrollable + ) public { + vm.assume(numUnenrollable <= numOfChallengers); + + _registerOperator(); + IRemoteChallenger[] memory challengers = _deployChallengers( + numOfChallengers + ); + IRemoteChallenger[] + memory unenrollableChallengers = new IRemoteChallenger[]( + numUnenrollable + ); + for (uint8 i = 0; i < numUnenrollable; i++) { + unenrollableChallengers[i] = challengers[i]; + } + + vm.startPrank(operator); + hsm.enrollIntoChallengers(challengers); + hsm.queueUnenrollmentFromChallengers(challengers); + + _assertChallengers( + challengers, + EnrollmentStatus.PENDING_UNENROLLMENT, + block.number + ); + + vm.roll(block.number + challengeDelayBlocks); + + hsm.completeQueuedUnenrollmentFromChallengers(unenrollableChallengers); + + assertEq( + hsm.getOperatorChallengers(operator).length, + numOfChallengers - numUnenrollable + ); + + vm.stopPrank(); + } + + function _registerOperator() internal { + ISignatureUtils.SignatureWithSaltAndExpiry + memory operatorSignature = _getOperatorSignature( + operatorPrivateKey, + operator, + address(hsm), + emptySalt, + maxExpiry + ); + + ecdsaStakeRegistry.registerOperatorWithSignature( + operator, + operatorSignature + ); + } + + function _deployChallengers( + uint8 numOfChallengers + ) internal returns (IRemoteChallenger[] memory challengers) { + challengers = new IRemoteChallenger[](numOfChallengers); + for (uint8 i = 0; i < numOfChallengers; i++) { + challengers[i] = new TestRemoteChallenger(); + } + } + + function _assertChallengers( + IRemoteChallenger[] memory _challengers, + EnrollmentStatus _expectedstatus, + uint256 _expectUnenrollmentBlock + ) internal { + for (uint256 i = 0; i < _challengers.length; i++) { + Enrollment memory enrollment = hsm.getEnrolledChallenger( + operator, + _challengers[i] + ); + assertEq(uint8(enrollment.status), uint8(_expectedstatus)); + if (_expectUnenrollmentBlock != 0) + assertEq( + enrollment.unenrollmentStartBlock, + _expectUnenrollmentBlock + ); + } + } + function _getOperatorSignature( uint256 _operatorPrivateKey, address operatorToSign, @@ -122,7 +367,7 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { salt, expiry ); - (uint8 v, bytes32 r, bytes32 s) = cheats.sign( + (uint8 v, bytes32 r, bytes32 s) = vm.sign( _operatorPrivateKey, digestHash ); From ef35202bc745247032db9833373b05c6dda5d950 Mon Sep 17 00:00:00 2001 From: -f Date: Fri, 26 Apr 2024 18:25:39 +0530 Subject: [PATCH 10/32] fin testing --- .../contracts/avs/ECDSAServiceManagerBase.sol | 6 +- .../contracts/avs/HyperlaneServiceManager.sol | 76 +++++----- .../libs/EnumerableMapEnrollment.sol | 6 +- .../contracts/test/TestRemoteChallenger.sol | 11 +- solidity/remappings.txt | 1 + .../test/avs/HyperlaneServiceManager.t.sol | 133 ++++++++++++++++-- 6 files changed, 170 insertions(+), 63 deletions(-) diff --git a/solidity/contracts/avs/ECDSAServiceManagerBase.sol b/solidity/contracts/avs/ECDSAServiceManagerBase.sol index d534d91ea8..c83e40653a 100644 --- a/solidity/contracts/avs/ECDSAServiceManagerBase.sol +++ b/solidity/contracts/avs/ECDSAServiceManagerBase.sol @@ -7,14 +7,14 @@ import {ISignatureUtils} from "@eigenlayer/interfaces/ISignatureUtils.sol"; import {IAVSDirectory} from "@eigenlayer/interfaces/IAVSDirectory.sol"; import {ISlasher} from "@eigenlayer/interfaces/ISlasher.sol"; -import {ECDSAStakeRegistry} from "@eigenlayer/middleware/unaudited/ECDSAStakeRegistry.sol"; +import {ECDSAStakeRegistry} from "@eigenlayer/ecdsa/ECDSAStakeRegistry.sol"; import {IServiceManager} from "@eigenlayer/middleware/interfaces/IServiceManager.sol"; import {IRemoteChallenger} from "../interfaces/avs/IRemoteChallenger.sol"; contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable { event OperatorRegisteredToAVS(address indexed operator); - event OperatorDeregisteredToAVS(address indexed operator); + event OperatorDeregisteredFromAVS(address indexed operator); ECDSAStakeRegistry internal immutable stakeRegistry; IAVSDirectory internal immutable elAvsDirectory; @@ -76,7 +76,7 @@ contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable { address operator ) public virtual onlyStakeRegistry { elAvsDirectory.deregisterOperatorFromAVS(operator); - emit OperatorDeregisteredToAVS(operator); + emit OperatorDeregisteredFromAVS(operator); } function freezeOperator(address operator) public virtual onlyOwner { diff --git a/solidity/contracts/avs/HyperlaneServiceManager.sol b/solidity/contracts/avs/HyperlaneServiceManager.sol index 0f86e0a606..ce1c0a604e 100644 --- a/solidity/contracts/avs/HyperlaneServiceManager.sol +++ b/solidity/contracts/avs/HyperlaneServiceManager.sol @@ -5,7 +5,7 @@ import "forge-std/console.sol"; import {IAVSDirectory} from "@eigenlayer/interfaces/IAVSDirectory.sol"; import {ISlasher} from "@eigenlayer/interfaces/ISlasher.sol"; -import {ECDSAStakeRegistry} from "@eigenlayer/middleware/unaudited/ECDSAStakeRegistry.sol"; +import {ECDSAStakeRegistry} from "@eigenlayer/ecdsa/ECDSAStakeRegistry.sol"; import {Enrollment, EnrollmentStatus, EnumerableMapEnrollment} from "../libs/EnumerableMapEnrollment.sol"; import {IRemoteChallenger} from "../interfaces/avs/IRemoteChallenger.sol"; @@ -59,24 +59,13 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { function deregisterOperatorFromAVS( address operator ) public virtual override onlyStakeRegistry { - address[] memory challengers = getOperatorChallengers(operator); - for (uint256 i = 0; i < challengers.length; i++) { - IRemoteChallenger challenger = IRemoteChallenger(challengers[i]); - Enrollment memory enrollment = enrolledChallengers[operator].get( - challengers[i] - ); - require( - enrollment.status != EnrollmentStatus.ENROLLED, - string( - abi.encodePacked( - "HyperlaneServiceManager: Operator still enrolled in challenger", - challengers[i] - ) - ) - ); - enrolledChallengers[operator].remove(challengers[i]); - } + IRemoteChallenger[] memory challengers = getOperatorChallengers( + operator + ); + completeQueuedUnenrollmentFromChallengers(challengers); + elAvsDirectory.deregisterOperatorFromAVS(operator); + emit OperatorDeregisteredFromAVS(operator); } // ============ External Functions ============ @@ -102,11 +91,6 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { (bool exists, Enrollment memory enrollment) = enrolledChallengers[ msg.sender ].tryGet(address(challenger)); - console.log( - "queueUnenrollmentFromChallengers", - exists, - uint8(enrollment.status) - ); if (exists && enrollment.status == EnrollmentStatus.ENROLLED) { enrolledChallengers[msg.sender].set( address(challenger), @@ -127,27 +111,29 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { function completeQueuedUnenrollmentFromChallengers( IRemoteChallenger[] memory _challengers - ) external { + ) public { for (uint256 i = 0; i < _challengers.length; i++) { IRemoteChallenger challenger = _challengers[i]; (bool exists, Enrollment memory enrollment) = enrolledChallengers[ msg.sender ].tryGet(address(challenger)); - if ( + + require( exists && - enrollment.status == EnrollmentStatus.PENDING_UNENROLLMENT && - block.number >= - enrollment.unenrollmentStartBlock + - challenger.challengeDelayBlocks() - ) { - enrolledChallengers[msg.sender].remove(address(challenger)); - emit OperatorUnenrolledFromChallenger( - msg.sender, - challenger, - block.number, - challenger.challengeDelayBlocks() - ); - } + enrollment.status != EnrollmentStatus.ENROLLED && + block.number >= + enrollment.unenrollmentStartBlock + + challenger.challengeDelayBlocks(), + "HyperlaneServiceManager: Invalid unenrollment" + ); + + enrolledChallengers[msg.sender].remove(address(challenger)); + emit OperatorUnenrolledFromChallenger( + msg.sender, + challenger, + block.number, + challenger.challengeDelayBlocks() + ); } } @@ -162,15 +148,19 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { IRemoteChallenger _challenger ) external view returns (Enrollment memory enrollment) { address[] memory keys = enrolledChallengers[_operator].keys(); - for (uint256 i = 0; i < keys.length; i++) { - console.log("key", keys[i], address(_challenger)); - } return enrolledChallengers[_operator].get(address(_challenger)); } function getOperatorChallengers( address _operator - ) public view returns (address[] memory) { - return enrolledChallengers[_operator].keys(); + ) public view returns (IRemoteChallenger[] memory) { + address[] memory keys = enrolledChallengers[_operator].keys(); + IRemoteChallenger[] memory challengers = new IRemoteChallenger[]( + keys.length + ); + for (uint256 i = 0; i < keys.length; i++) { + challengers[i] = IRemoteChallenger(keys[i]); + } + return challengers; } } diff --git a/solidity/contracts/libs/EnumerableMapEnrollment.sol b/solidity/contracts/libs/EnumerableMapEnrollment.sol index 3d75ddd6f2..5daf70abe7 100644 --- a/solidity/contracts/libs/EnumerableMapEnrollment.sol +++ b/solidity/contracts/libs/EnumerableMapEnrollment.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.6.11; -import "forge-std/console.sol"; +import "forge-std/Console.sol"; // ============ External Imports ============ import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; @@ -49,7 +49,6 @@ library EnumerableMapEnrollment { function decode(bytes32 encoded) public view returns (Enrollment memory) { uint8 status = uint8(encoded[0]); uint248 unenrollmentStartBlock = uint248(uint256((encoded << 8) >> 8)); - console.log("decode", status, unenrollmentStartBlock); return Enrollment(EnrollmentStatus(status), unenrollmentStartBlock); } @@ -68,8 +67,6 @@ library EnumerableMapEnrollment { address key, Enrollment memory value ) internal returns (bool) { - console.log("set", key); - console.logBytes32(encode(value)); return map._inner.set(key.addressToBytes32(), encode(value)); } @@ -77,7 +74,6 @@ library EnumerableMapEnrollment { AddressToEnrollmentMap storage map, address key ) internal view returns (Enrollment memory) { - console.logBytes32(map._inner.get(key.addressToBytes32())); return decode(map._inner.get(key.addressToBytes32())); } diff --git a/solidity/contracts/test/TestRemoteChallenger.sol b/solidity/contracts/test/TestRemoteChallenger.sol index fd700e2d91..17e55dfd43 100644 --- a/solidity/contracts/test/TestRemoteChallenger.sol +++ b/solidity/contracts/test/TestRemoteChallenger.sol @@ -2,11 +2,20 @@ pragma solidity >=0.8.0; import {IRemoteChallenger} from "../interfaces/avs/IRemoteChallenger.sol"; +import {HyperlaneServiceManager} from "../avs/HyperlaneServiceManager.sol"; contract TestRemoteChallenger is IRemoteChallenger { + HyperlaneServiceManager internal immutable hsm; + + constructor(HyperlaneServiceManager _hsm) { + hsm = _hsm; + } + function challengeDelayBlocks() external view returns (uint256) { return 50400; // one week of eth L1 blocks } - function handleChallenge(address operator) external {} + function handleChallenge(address operator) external { + hsm.freezeOperator(operator); + } } diff --git a/solidity/remappings.txt b/solidity/remappings.txt index fa66f86546..cb9d0cfd13 100644 --- a/solidity/remappings.txt +++ b/solidity/remappings.txt @@ -6,5 +6,6 @@ ds-test/=lib/forge-std/lib/ds-test/src/ forge-std/=lib/forge-std/src/ @eigenlayer/core/=lib/eigenlayer-contracts/src/contracts/core/ +@eigenlayer/ecdsa/=lib/eigenlayer-middleware/src/unaudited/ @eigenlayer/middleware/=lib/eigenlayer-middleware/src/ @eigenlayer/interfaces/=lib/eigenlayer-contracts/src/contracts/interfaces/ diff --git a/solidity/test/avs/HyperlaneServiceManager.t.sol b/solidity/test/avs/HyperlaneServiceManager.t.sol index b19b5685c8..87aa7f791f 100644 --- a/solidity/test/avs/HyperlaneServiceManager.t.sol +++ b/solidity/test/avs/HyperlaneServiceManager.t.sol @@ -8,10 +8,11 @@ import {ISignatureUtils} from "@eigenlayer/interfaces/ISignatureUtils.sol"; import {IAVSDirectory} from "@eigenlayer/interfaces/IAVSDirectory.sol"; import {IDelegationManager} from "@eigenlayer/interfaces/IDelegationManager.sol"; import {IStrategy} from "@eigenlayer/interfaces/IStrategy.sol"; +import {ISlasher} from "@eigenlayer/interfaces/ISlasher.sol"; import {MockAVSDeployer} from "eigenlayer-middleware/test/utils/MockAVSDeployer.sol"; -import {Quorum, StrategyParams} from "@eigenlayer/middleware/unaudited/ECDSAStakeRegistryStorage.sol"; -import {ECDSAStakeRegistry} from "@eigenlayer/middleware/unaudited/ECDSAStakeRegistry.sol"; +import {Quorum, StrategyParams} from "@eigenlayer/ecdsa/ECDSAStakeRegistryStorage.sol"; +import {ECDSAStakeRegistry} from "@eigenlayer/ecdsa/ECDSAStakeRegistry.sol"; import {Enrollment, EnrollmentStatus} from "../../contracts/libs/EnumerableMapEnrollment.sol"; import {IRemoteChallenger} from "../../contracts/interfaces/avs/IRemoteChallenger.sol"; @@ -178,7 +179,7 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { } /// forge-config: default.fuzz.runs = 10 - function test_enrollIntoChallengers(uint8 numOfChallengers) public { + function testFuzz_enrollIntoChallengers(uint8 numOfChallengers) public { _registerOperator(); IRemoteChallenger[] memory challengers = _deployChallengers( numOfChallengers @@ -191,7 +192,7 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { } /// forge-config: default.fuzz.runs = 10 - function test_queueUnenrollmentFromChallengers_all( + function testFuzz_queueUnenrollmentFromChallengers_all( uint8 numOfChallengers ) public { _registerOperator(); @@ -219,8 +220,7 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { vm.stopPrank(); } - /// forge-config: default.fuzz.runs = 10 - function test_queueUnenrollmentFromChallengers( + function testFuzz_queueUnenrollmentFromChallengers( uint8 numOfChallengers, uint8 numQueued ) public { @@ -259,12 +259,11 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { vm.stopPrank(); } - /// forge-config: default.fuzz.runs = 10 - function test_completeQueuedUnenrollmentFromChallenger( + function testFuzz_completeQueuedUnenrollmentFromChallenger( uint8 numOfChallengers, uint8 numUnenrollable ) public { - vm.assume(numUnenrollable <= numOfChallengers); + vm.assume(numUnenrollable > 0 && numUnenrollable <= numOfChallengers); _registerOperator(); IRemoteChallenger[] memory challengers = _deployChallengers( @@ -288,6 +287,9 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { block.number ); + vm.expectRevert(); + hsm.completeQueuedUnenrollmentFromChallengers(unenrollableChallengers); + vm.roll(block.number + challengeDelayBlocks); hsm.completeQueuedUnenrollmentFromChallengers(unenrollableChallengers); @@ -300,6 +302,114 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { vm.stopPrank(); } + function testFuzz_freezeOperator(uint8 numOfChallengers) public { + _registerOperator(); + + IRemoteChallenger[] memory challengers = _deployChallengers( + numOfChallengers + ); + + vm.prank(operator); + hsm.enrollIntoChallengers(challengers); + + for (uint256 i = 0; i < challengers.length; i++) { + vm.expectCall( + address(slasher), + abi.encodeCall(ISlasher.freezeOperator, (operator)) + ); + challengers[i].handleChallenge(operator); + } + } + + function testFuzz_freezeOperator_duringEnrollment( + uint8 numOfChallengers, + uint8 numUnenrollable + ) public { + vm.assume(numUnenrollable > 0 && numUnenrollable <= numOfChallengers); + + _registerOperator(); + IRemoteChallenger[] memory challengers = _deployChallengers( + numOfChallengers + ); + IRemoteChallenger[] + memory unenrollableChallengers = new IRemoteChallenger[]( + numUnenrollable + ); + IRemoteChallenger[] + memory otherChallengeChallengers = new IRemoteChallenger[]( + numOfChallengers - numUnenrollable + ); + for (uint8 i = 0; i < numUnenrollable; i++) { + unenrollableChallengers[i] = challengers[i]; + } + for (uint8 i = numUnenrollable; i < numOfChallengers; i++) { + otherChallengeChallengers[i - numUnenrollable] = challengers[i]; + } + + vm.startPrank(operator); + hsm.enrollIntoChallengers(challengers); + + for (uint256 i = 0; i < challengers.length; i++) { + vm.expectCall( + address(slasher), + abi.encodeCall(ISlasher.freezeOperator, (operator)) + ); + challengers[i].handleChallenge(operator); + } + + hsm.queueUnenrollmentFromChallengers(challengers); + vm.roll(block.number + challengeDelayBlocks); + hsm.completeQueuedUnenrollmentFromChallengers(unenrollableChallengers); + + for (uint256 i = 0; i < unenrollableChallengers.length; i++) { + vm.expectRevert( + "HyperlaneServiceManager: Operator not enrolled in challenger" + ); + unenrollableChallengers[i].handleChallenge(operator); + } + for (uint256 i = 0; i < otherChallengeChallengers.length; i++) { + vm.expectCall( + address(slasher), + abi.encodeCall(ISlasher.freezeOperator, (operator)) + ); + otherChallengeChallengers[i].handleChallenge(operator); + } + vm.stopPrank(); + } + + /// forge-config: default.fuzz.runs = 10 + function testFuzz_deregisterOperator_withEnrollment( + uint8 numOfChallengers + ) public { + vm.assume(numOfChallengers > 0); + + _registerOperator(); + IRemoteChallenger[] memory challengers = _deployChallengers( + numOfChallengers + ); + + vm.startPrank(operator); + hsm.enrollIntoChallengers(challengers); + _assertChallengers(challengers, EnrollmentStatus.ENROLLED, 0); + + vm.expectRevert("HyperlaneServiceManager: Invalid unenrollment"); + ecdsaStakeRegistry.deregisterOperator(); + + hsm.queueUnenrollmentFromChallengers(challengers); + + vm.expectRevert("HyperlaneServiceManager: Invalid unenrollment"); + ecdsaStakeRegistry.deregisterOperator(); + + vm.roll(block.number + challengeDelayBlocks); + + hsm.completeQueuedUnenrollmentFromChallengers(challengers); // this works + // TODO: just this doesn't work -> ecdsaStakeRegistry.deregisterOperator(); + ecdsaStakeRegistry.deregisterOperator(); + + assertEq(hsm.getOperatorChallengers(operator).length, 0); + vm.stopPrank(); + } + function _registerOperator() internal { ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature = _getOperatorSignature( @@ -321,7 +431,7 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { ) internal returns (IRemoteChallenger[] memory challengers) { challengers = new IRemoteChallenger[](numOfChallengers); for (uint8 i = 0; i < numOfChallengers; i++) { - challengers[i] = new TestRemoteChallenger(); + challengers[i] = new TestRemoteChallenger(hsm); } } @@ -336,11 +446,12 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { _challengers[i] ); assertEq(uint8(enrollment.status), uint8(_expectedstatus)); - if (_expectUnenrollmentBlock != 0) + if (_expectUnenrollmentBlock != 0) { assertEq( enrollment.unenrollmentStartBlock, _expectUnenrollmentBlock ); + } } } From a95a8c5fb2e64148f58e220a5ac0c00ba5727c53 Mon Sep 17 00:00:00 2001 From: -f Date: Fri, 26 Apr 2024 18:30:08 +0530 Subject: [PATCH 11/32] setSlasher --- solidity/contracts/avs/ECDSAServiceManagerBase.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/solidity/contracts/avs/ECDSAServiceManagerBase.sol b/solidity/contracts/avs/ECDSAServiceManagerBase.sol index c83e40653a..ba78b616bb 100644 --- a/solidity/contracts/avs/ECDSAServiceManagerBase.sol +++ b/solidity/contracts/avs/ECDSAServiceManagerBase.sol @@ -117,6 +117,10 @@ contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable { return address(elAvsDirectory); } + function setSlasher(ISlasher _slasher) external onlyOwner { + slasher = _slasher; + } + // ============ Internal Function ============ function _getRestakeableStrategies() From e348bdcb5b5f9488c2c13ec02c87b7249d1f7bd9 Mon Sep 17 00:00:00 2001 From: -f Date: Fri, 26 Apr 2024 18:33:28 +0530 Subject: [PATCH 12/32] rm console.sol --- solidity/contracts/avs/HyperlaneServiceManager.sol | 2 -- solidity/contracts/libs/EnumerableMapEnrollment.sol | 2 -- 2 files changed, 4 deletions(-) diff --git a/solidity/contracts/avs/HyperlaneServiceManager.sol b/solidity/contracts/avs/HyperlaneServiceManager.sol index ce1c0a604e..f971adb81b 100644 --- a/solidity/contracts/avs/HyperlaneServiceManager.sol +++ b/solidity/contracts/avs/HyperlaneServiceManager.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.8.0; -import "forge-std/console.sol"; - import {IAVSDirectory} from "@eigenlayer/interfaces/IAVSDirectory.sol"; import {ISlasher} from "@eigenlayer/interfaces/ISlasher.sol"; import {ECDSAStakeRegistry} from "@eigenlayer/ecdsa/ECDSAStakeRegistry.sol"; diff --git a/solidity/contracts/libs/EnumerableMapEnrollment.sol b/solidity/contracts/libs/EnumerableMapEnrollment.sol index 5daf70abe7..6f363bd699 100644 --- a/solidity/contracts/libs/EnumerableMapEnrollment.sol +++ b/solidity/contracts/libs/EnumerableMapEnrollment.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.6.11; -import "forge-std/Console.sol"; - // ============ External Imports ============ import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; From 5680d72482ba830efcab33f8e9541e2eed4b0a92 Mon Sep 17 00:00:00 2001 From: -f Date: Sat, 27 Apr 2024 23:42:34 +0530 Subject: [PATCH 13/32] fix deregister bug --- solidity/contracts/Mailbox.sol | 13 ++ .../contracts/avs/ECDSAServiceManagerBase.sol | 139 ++++++++++----- .../contracts/avs/HyperlaneServiceManager.sol | 164 +++++++++++++----- .../test/avs/HyperlaneServiceManager.t.sol | 75 +++++--- 4 files changed, 281 insertions(+), 110 deletions(-) diff --git a/solidity/contracts/Mailbox.sol b/solidity/contracts/Mailbox.sol index 338bdfc0ee..84d94507cb 100644 --- a/solidity/contracts/Mailbox.sol +++ b/solidity/contracts/Mailbox.sol @@ -1,6 +1,18 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.8.0; +/*@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@ HYPERLANE @@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ +@@@@@@@@@ @@@@@@@@*/ + // ============ Internal Imports ============ import {Versioned} from "./upgrade/Versioned.sol"; import {Indexed} from "./libs/Indexed.sol"; @@ -49,6 +61,7 @@ contract Mailbox is IMailbox, Indexed, Versioned, OwnableUpgradeable { address processor; uint48 blockNumber; } + mapping(bytes32 => Delivery) internal deliveries; // ============ Events ============ diff --git a/solidity/contracts/avs/ECDSAServiceManagerBase.sol b/solidity/contracts/avs/ECDSAServiceManagerBase.sol index ba78b616bb..76f6058b73 100644 --- a/solidity/contracts/avs/ECDSAServiceManagerBase.sol +++ b/solidity/contracts/avs/ECDSAServiceManagerBase.sol @@ -1,25 +1,58 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.8.0; -import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +/*@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@ HYPERLANE @@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ +@@@@@@@@@ @@@@@@@@*/ + +// ============ Internal Imports ============ +import {IRemoteChallenger} from "../interfaces/avs/IRemoteChallenger.sol"; import {ISignatureUtils} from "@eigenlayer/interfaces/ISignatureUtils.sol"; import {IAVSDirectory} from "@eigenlayer/interfaces/IAVSDirectory.sol"; import {ISlasher} from "@eigenlayer/interfaces/ISlasher.sol"; - import {ECDSAStakeRegistry} from "@eigenlayer/ecdsa/ECDSAStakeRegistry.sol"; import {IServiceManager} from "@eigenlayer/middleware/interfaces/IServiceManager.sol"; - -import {IRemoteChallenger} from "../interfaces/avs/IRemoteChallenger.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable { - event OperatorRegisteredToAVS(address indexed operator); - event OperatorDeregisteredFromAVS(address indexed operator); + // ============ Constants ============ + // Stake registry responsible for maintaining operator stakes ECDSAStakeRegistry internal immutable stakeRegistry; + // Eigenlayer's AVS directory for interactions between AVS and operators IAVSDirectory internal immutable elAvsDirectory; + + // ============ Public Storage ============ + + // Slasher contract responsible for slashing operators + // @dev slasher needs to be updated once slashing is implemented ISlasher internal slasher; + // ============ Events ============ + + /** + * @notice Emitted when an operator is registered to the AVS + * @param operator The address of the operator + */ + event OperatorRegisteredToAVS(address indexed operator); + + /** + * @notice Emitted when an operator is deregistered from the AVS + * @param operator The address of the operator + */ + event OperatorDeregisteredFromAVS(address indexed operator); + + // ============ Modifiers ============ + /// @notice when applied to a function, only allows the ECDSAStakeRegistry to call it modifier onlyStakeRegistry() { require( @@ -29,6 +62,16 @@ contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable { _; } + /// @notice when applied to a function, only allows the ECDSAStakeRegistry or the operator to call it + /// for completeQueuedUnenrollmentFromChallengers access control + modifier onlyStakeRegistryOrOperator(address operator) { + require( + msg.sender == address(stakeRegistry) || msg.sender == operator, + "ECDSAServiceManagerBase: caller is not the stake registry or operator" + ); + _; + } + // ============ Constructor ============ constructor( @@ -42,6 +85,48 @@ contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable { _disableInitializers(); } + // ============ External Functions ============ + + /** + * @notice Returns the list of strategies that the AVS supports for restaking + * @dev This function is intended to be called off-chain + * @dev No guarantee is made on uniqueness of each element in the returned array. + * The off-chain service should do that validation separately + */ + function getRestakeableStrategies() + external + view + returns (address[] memory) + { + return _getRestakeableStrategies(); + } + + /** + * @notice Returns the list of strategies that the operator has potentially restaked on the AVS + * @dev This function is intended to be called off-chain + * @dev Since ECDSAStakeRegistry only supports one quorum, each operator restakes into all the AVS strategies + * @dev No guarantee is made on uniqueness of each element in the returned array. + * The off-chain service should do that validation separately + */ + function getOperatorRestakedStrategies( + address /* operator */ + ) external view returns (address[] memory) { + return _getRestakeableStrategies(); + } + + /** + * @notice Sets the slasher contract responsible for slashing operators + * @param _slasher The address of the slasher contract + */ + function setSlasher(ISlasher _slasher) external onlyOwner { + slasher = _slasher; + } + + /// @notice Returns the EigenLayer AVSDirectory contract. + function avsDirectory() external view override returns (address) { + return address(elAvsDirectory); + } + // ============ Public Functions ============ /** @@ -79,46 +164,12 @@ contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable { emit OperatorDeregisteredFromAVS(operator); } - function freezeOperator(address operator) public virtual onlyOwner { - slasher.freezeOperator(operator); - } - - // ============ External Functions ============ - - /** - * @notice Returns the list of strategies that the AVS supports for restaking - * @dev This function is intended to be called off-chain - * @dev No guarantee is made on uniqueness of each element in the returned array. - * The off-chain service should do that validation separately - */ - function getRestakeableStrategies() - external - view - returns (address[] memory) - { - return _getRestakeableStrategies(); - } - /** - * @notice Returns the list of strategies that the operator has potentially restaked on the AVS - * @dev This function is intended to be called off-chain - * @dev Since ECDSAStakeRegistry only supports one quorum, each operator restakes into all the AVS strategies - * @dev No guarantee is made on uniqueness of each element in the returned array. - * The off-chain service should do that validation separately + * @notice Freezes an operator and their stake from Eigenlayer + * @param operator The address of the operator to freeze. */ - function getOperatorRestakedStrategies( - address /* operator */ - ) external view returns (address[] memory) { - return _getRestakeableStrategies(); - } - - /// @notice Returns the EigenLayer AVSDirectory contract. - function avsDirectory() external view override returns (address) { - return address(elAvsDirectory); - } - - function setSlasher(ISlasher _slasher) external onlyOwner { - slasher = _slasher; + function freezeOperator(address operator) public virtual onlyOwner { + slasher.freezeOperator(operator); } // ============ Internal Function ============ diff --git a/solidity/contracts/avs/HyperlaneServiceManager.sol b/solidity/contracts/avs/HyperlaneServiceManager.sol index f971adb81b..a61c07bcd3 100644 --- a/solidity/contracts/avs/HyperlaneServiceManager.sol +++ b/solidity/contracts/avs/HyperlaneServiceManager.sol @@ -1,36 +1,80 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.8.0; -import {IAVSDirectory} from "@eigenlayer/interfaces/IAVSDirectory.sol"; -import {ISlasher} from "@eigenlayer/interfaces/ISlasher.sol"; -import {ECDSAStakeRegistry} from "@eigenlayer/ecdsa/ECDSAStakeRegistry.sol"; - +/*@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@ HYPERLANE @@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ +@@@@@@@@@ @@@@@@@@*/ + +// ============ Internal Imports ============ import {Enrollment, EnrollmentStatus, EnumerableMapEnrollment} from "../libs/EnumerableMapEnrollment.sol"; import {IRemoteChallenger} from "../interfaces/avs/IRemoteChallenger.sol"; import {ECDSAServiceManagerBase} from "./ECDSAServiceManagerBase.sol"; +// ============ External Imports ============ +import {IAVSDirectory} from "@eigenlayer/interfaces/IAVSDirectory.sol"; +import {ISlasher} from "@eigenlayer/interfaces/ISlasher.sol"; +import {ECDSAStakeRegistry} from "@eigenlayer/ecdsa/ECDSAStakeRegistry.sol"; + contract HyperlaneServiceManager is ECDSAServiceManagerBase { + // ============ Libraries ============ + using EnumerableMapEnrollment for EnumerableMapEnrollment.AddressToEnrollmentMap; + // ============ Events ============ + + /** + * @notice Emitted when an operator is enrolled in a challenger + * @param operator The address of the operator + * @param challenger The address of the challenger + */ event OperatorEnrolledToChallenger( address operator, IRemoteChallenger challenger ); + + /** + * @notice Emitted when an operator is queued for unenrollment from a challenger + * @param operator The address of the operator + * @param challenger The address of the challenger + * @param unenrollmentStartBlock The block number at which the unenrollment was queued + * @param challengeDelayBlocks The number of blocks to wait before unenrollment is complete + */ event OperatorQueuedUnenrollmentFromChallenger( address operator, IRemoteChallenger challenger, uint256 unenrollmentStartBlock, uint256 challengeDelayBlocks ); + + /** + * @notice Emitted when an operator is unenrolled from a challenger + * @param operator The address of the operator + * @param challenger The address of the challenger + * @param unenrollmentEndBlock The block number at which the unenrollment was completed + */ event OperatorUnenrolledFromChallenger( address operator, IRemoteChallenger challenger, - uint256 unenrollmentEndBlock, - uint256 challengeDelayBlocks + uint256 unenrollmentEndBlock ); - mapping(address => EnumerableMapEnrollment.AddressToEnrollmentMap) enrolledChallengers; + // ============ Internal Storage ============ + + // Mapping of operators to challengers they are enrolled in (enumerable required for remove-all) + mapping(address => EnumerableMapEnrollment.AddressToEnrollmentMap) + internal enrolledChallengers; + // ============ Modifiers ============ + + // Only allows the challenger the operator is enrolled in to call the function modifier onlyEnrolledChallenger(address operator) { (bool exists, ) = enrolledChallengers[operator].tryGet(msg.sender); require( @@ -48,39 +92,31 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { ISlasher _slasher ) ECDSAServiceManagerBase(_avsDirectory, _stakeRegistry, _slasher) {} - // ============ Public Functions ============ + // ============ External Functions ============ /** - * @notice Forwards a call to EigenLayer's AVSDirectory contract to confirm operator deregistration from the AVS - * @param operator The address of the operator to deregister. + * @notice Enrolls as an operator into a list of challengers + * @param _challengers The list of challengers to enroll into */ - function deregisterOperatorFromAVS( - address operator - ) public virtual override onlyStakeRegistry { - IRemoteChallenger[] memory challengers = getOperatorChallengers( - operator - ); - completeQueuedUnenrollmentFromChallengers(challengers); - - elAvsDirectory.deregisterOperatorFromAVS(operator); - emit OperatorDeregisteredFromAVS(operator); - } - - // ============ External Functions ============ - function enrollIntoChallengers( IRemoteChallenger[] memory _challengers ) external { for (uint256 i = 0; i < _challengers.length; i++) { IRemoteChallenger challenger = _challengers[i]; - enrolledChallengers[msg.sender].set( - address(challenger), - Enrollment(EnrollmentStatus.ENROLLED, 0) + require( + enrolledChallengers[msg.sender].set( + address(challenger), + Enrollment(EnrollmentStatus.ENROLLED, 0) + ) ); emit OperatorEnrolledToChallenger(msg.sender, challenger); } } + /** + * @notice Queues an operator for unenrollment from a list of challengers + * @param _challengers The list of challengers to unenroll from + */ function queueUnenrollmentFromChallengers( IRemoteChallenger[] memory _challengers ) external { @@ -90,11 +126,13 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { msg.sender ].tryGet(address(challenger)); if (exists && enrollment.status == EnrollmentStatus.ENROLLED) { - enrolledChallengers[msg.sender].set( - address(challenger), - Enrollment( - EnrollmentStatus.PENDING_UNENROLLMENT, - uint248(block.number) + require( + enrolledChallengers[msg.sender].set( + address(challenger), + Enrollment( + EnrollmentStatus.PENDING_UNENROLLMENT, + uint248(block.number) + ) ) ); emit OperatorQueuedUnenrollmentFromChallenger( @@ -107,13 +145,19 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { } } + /** + * @notice Completes the unenrollment of an operator from a list of challengers + * @param operator The address of the operator + * @param _challengers The list of challengers to unenroll from + */ function completeQueuedUnenrollmentFromChallengers( + address operator, IRemoteChallenger[] memory _challengers - ) public { + ) public onlyStakeRegistryOrOperator(operator) { for (uint256 i = 0; i < _challengers.length; i++) { IRemoteChallenger challenger = _challengers[i]; (bool exists, Enrollment memory enrollment) = enrolledChallengers[ - msg.sender + operator ].tryGet(address(challenger)); require( @@ -125,22 +169,22 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { "HyperlaneServiceManager: Invalid unenrollment" ); - enrolledChallengers[msg.sender].remove(address(challenger)); + enrolledChallengers[operator].remove(address(challenger)); emit OperatorUnenrolledFromChallenger( - msg.sender, + operator, challenger, - block.number, - challenger.challengeDelayBlocks() + block.number ); } } - function freezeOperator( - address operator - ) public virtual override onlyEnrolledChallenger(operator) { - slasher.freezeOperator(operator); - } + // ============ Public Functions ============ + /** + * @notice returns the status of a challenger an operator is enrolled in + * @param _operator The address of the operator + * @param _challenger specified IRemoteChallenger contract + */ function getEnrolledChallenger( address _operator, IRemoteChallenger _challenger @@ -149,10 +193,15 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { return enrolledChallengers[_operator].get(address(_challenger)); } + /** + * @notice returns the list of challengers an operator is enrolled in + * @param _operator The address of the operator + */ function getOperatorChallengers( address _operator ) public view returns (IRemoteChallenger[] memory) { address[] memory keys = enrolledChallengers[_operator].keys(); + IRemoteChallenger[] memory challengers = new IRemoteChallenger[]( keys.length ); @@ -161,4 +210,33 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { } return challengers; } + + /** + * @notice forwards a call to the Slasher contract to freeze an operator + * @param operator The address of the operator to freeze. + * @dev only the enrolled challengers can call this function + */ + function freezeOperator( + address operator + ) public virtual override onlyEnrolledChallenger(operator) { + slasher.freezeOperator(operator); + } + + // ============ Public Functions ============ + + /** + * @notice Forwards a call to EigenLayer's AVSDirectory contract to confirm operator deregistration from the AVS + * @param operator The address of the operator to deregister. + */ + function deregisterOperatorFromAVS( + address operator + ) public virtual override onlyStakeRegistry { + IRemoteChallenger[] memory challengers = getOperatorChallengers( + operator + ); + completeQueuedUnenrollmentFromChallengers(operator, challengers); + + elAvsDirectory.deregisterOperatorFromAVS(operator); + emit OperatorDeregisteredFromAVS(operator); + } } diff --git a/solidity/test/avs/HyperlaneServiceManager.t.sol b/solidity/test/avs/HyperlaneServiceManager.t.sol index 87aa7f791f..d2b0b42215 100644 --- a/solidity/test/avs/HyperlaneServiceManager.t.sol +++ b/solidity/test/avs/HyperlaneServiceManager.t.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.8.0; -import "forge-std/console.sol"; - import {DelegationManager} from "@eigenlayer/core/DelegationManager.sol"; import {ISignatureUtils} from "@eigenlayer/interfaces/ISignatureUtils.sol"; import {IAVSDirectory} from "@eigenlayer/interfaces/IAVSDirectory.sol"; @@ -20,18 +18,6 @@ import {HyperlaneServiceManager} from "../../contracts/avs/HyperlaneServiceManag import {TestRemoteChallenger} from "../../contracts/test/TestRemoteChallenger.sol"; contract HyperlaneServiceManagerTest is MockAVSDeployer { - // TODO - // register -> deregister - // register -> stake -> deregister - // register -> stake -> queue withdrawal -> deregister - // register -> stake -> queue withdrawal -> complete -> deregister - // enroll for 3 test challengers -> unenroll - // enroll, stake/unstake -> unenroll - // enroll, - // register. enroll, unenroll partial, deregister - // register. enroll, deregister - // register, handle challenge=true, deregister - DelegationManager public delegationManager; HyperlaneServiceManager internal hsm; @@ -213,13 +199,14 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { vm.roll(block.number + challengeDelayBlocks); - hsm.completeQueuedUnenrollmentFromChallengers(challengers); + hsm.completeQueuedUnenrollmentFromChallengers(operator, challengers); // get all operator key length assert assertEq(hsm.getOperatorChallengers(operator).length, 0); vm.stopPrank(); } + /// forge-config: default.fuzz.runs = 10 function testFuzz_queueUnenrollmentFromChallengers( uint8 numOfChallengers, uint8 numQueued @@ -259,6 +246,36 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { vm.stopPrank(); } + /// forge-config: default.fuzz.runs = 10 + function testFuzz_completeQueuedUnenrollmentFromChallenger_revert_unauthorized( + uint8 numOfChallengers + ) public { + vm.assume(numOfChallengers > 0); + + _registerOperator(); + IRemoteChallenger[] memory challengers = _deployChallengers( + numOfChallengers + ); + + vm.startPrank(operator); + hsm.enrollIntoChallengers(challengers); + hsm.queueUnenrollmentFromChallengers(challengers); + vm.stopPrank(); + + vm.roll(block.number + challengeDelayBlocks); + + vm.expectRevert( + "ECDSAServiceManagerBase: caller is not the stake registry or operator" + ); + hsm.completeQueuedUnenrollmentFromChallengers(operator, challengers); + + vm.prank(address(ecdsaStakeRegistry)); + hsm.completeQueuedUnenrollmentFromChallengers(operator, challengers); + + assertEq(hsm.getOperatorChallengers(operator).length, 0); + } + + /// forge-config: default.fuzz.runs = 10 function testFuzz_completeQueuedUnenrollmentFromChallenger( uint8 numOfChallengers, uint8 numUnenrollable @@ -288,11 +305,17 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { ); vm.expectRevert(); - hsm.completeQueuedUnenrollmentFromChallengers(unenrollableChallengers); + hsm.completeQueuedUnenrollmentFromChallengers( + operator, + unenrollableChallengers + ); vm.roll(block.number + challengeDelayBlocks); - hsm.completeQueuedUnenrollmentFromChallengers(unenrollableChallengers); + hsm.completeQueuedUnenrollmentFromChallengers( + operator, + unenrollableChallengers + ); assertEq( hsm.getOperatorChallengers(operator).length, @@ -302,6 +325,7 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { vm.stopPrank(); } + /// forge-config: default.fuzz.runs = 10 function testFuzz_freezeOperator(uint8 numOfChallengers) public { _registerOperator(); @@ -321,6 +345,7 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { } } + /// forge-config: default.fuzz.runs = 10 function testFuzz_freezeOperator_duringEnrollment( uint8 numOfChallengers, uint8 numUnenrollable @@ -359,7 +384,10 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { hsm.queueUnenrollmentFromChallengers(challengers); vm.roll(block.number + challengeDelayBlocks); - hsm.completeQueuedUnenrollmentFromChallengers(unenrollableChallengers); + hsm.completeQueuedUnenrollmentFromChallengers( + operator, + unenrollableChallengers + ); for (uint256 i = 0; i < unenrollableChallengers.length; i++) { vm.expectRevert( @@ -378,9 +406,8 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { } /// forge-config: default.fuzz.runs = 10 - function testFuzz_deregisterOperator_withEnrollment( - uint8 numOfChallengers - ) public { + function testFuzz_deregisterOperator_withEnrollment() public { + uint8 numOfChallengers = 1; vm.assume(numOfChallengers > 0); _registerOperator(); @@ -402,14 +429,16 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { vm.roll(block.number + challengeDelayBlocks); - hsm.completeQueuedUnenrollmentFromChallengers(challengers); // this works - // TODO: just this doesn't work -> ecdsaStakeRegistry.deregisterOperator(); + // hsm.completeQueuedUnenrollmentFromChallengers(operator, challengers); // this works + // // TODO: just this doesn't work -> ecdsaStakeRegistry.deregisterOperator(); ecdsaStakeRegistry.deregisterOperator(); assertEq(hsm.getOperatorChallengers(operator).length, 0); vm.stopPrank(); } + // ============ Utility Functions ============ + function _registerOperator() internal { ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature = _getOperatorSignature( From b8afaaee207c4d19191ebf0ee41bfbd1a048643e Mon Sep 17 00:00:00 2001 From: -f Date: Tue, 30 Apr 2024 16:14:24 +0530 Subject: [PATCH 14/32] address yorke's comments --- solidity/contracts/Mailbox.sol | 13 ---- .../contracts/avs/ECDSAServiceManagerBase.sol | 6 +- .../contracts/avs/HyperlaneServiceManager.sol | 54 ++++++++------- .../libs/EnumerableMapEnrollment.sol | 2 + solidity/remappings.txt | 6 +- .../test/avs/HyperlaneServiceManager.t.sol | 65 +++++++++---------- 6 files changed, 66 insertions(+), 80 deletions(-) diff --git a/solidity/contracts/Mailbox.sol b/solidity/contracts/Mailbox.sol index 84d94507cb..338bdfc0ee 100644 --- a/solidity/contracts/Mailbox.sol +++ b/solidity/contracts/Mailbox.sol @@ -1,18 +1,6 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.8.0; -/*@@@@@@@ @@@@@@@@@ - @@@@@@@@@ @@@@@@@@@ - @@@@@@@@@ @@@@@@@@@ - @@@@@@@@@ @@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@ HYPERLANE @@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@ @@@@@@@@@ - @@@@@@@@@ @@@@@@@@@ - @@@@@@@@@ @@@@@@@@@ -@@@@@@@@@ @@@@@@@@*/ - // ============ Internal Imports ============ import {Versioned} from "./upgrade/Versioned.sol"; import {Indexed} from "./libs/Indexed.sol"; @@ -61,7 +49,6 @@ contract Mailbox is IMailbox, Indexed, Versioned, OwnableUpgradeable { address processor; uint48 blockNumber; } - mapping(bytes32 => Delivery) internal deliveries; // ============ Events ============ diff --git a/solidity/contracts/avs/ECDSAServiceManagerBase.sol b/solidity/contracts/avs/ECDSAServiceManagerBase.sol index 76f6058b73..31c22a3cab 100644 --- a/solidity/contracts/avs/ECDSAServiceManagerBase.sol +++ b/solidity/contracts/avs/ECDSAServiceManagerBase.sol @@ -19,8 +19,8 @@ import {IRemoteChallenger} from "../interfaces/avs/IRemoteChallenger.sol"; import {ISignatureUtils} from "@eigenlayer/interfaces/ISignatureUtils.sol"; import {IAVSDirectory} from "@eigenlayer/interfaces/IAVSDirectory.sol"; import {ISlasher} from "@eigenlayer/interfaces/ISlasher.sol"; -import {ECDSAStakeRegistry} from "@eigenlayer/ecdsa/ECDSAStakeRegistry.sol"; -import {IServiceManager} from "@eigenlayer/middleware/interfaces/IServiceManager.sol"; +import {ECDSAStakeRegistry} from "@eigenlayer-middleware/unaudited/ECDSAStakeRegistry.sol"; +import {IServiceManager} from "@eigenlayer-middleware/interfaces/IServiceManager.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable { @@ -63,7 +63,7 @@ contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable { } /// @notice when applied to a function, only allows the ECDSAStakeRegistry or the operator to call it - /// for completeQueuedUnenrollmentFromChallengers access control + /// for completeUnenrollment access control modifier onlyStakeRegistryOrOperator(address operator) { require( msg.sender == address(stakeRegistry) || msg.sender == operator, diff --git a/solidity/contracts/avs/HyperlaneServiceManager.sol b/solidity/contracts/avs/HyperlaneServiceManager.sol index a61c07bcd3..6f2e6a0110 100644 --- a/solidity/contracts/avs/HyperlaneServiceManager.sol +++ b/solidity/contracts/avs/HyperlaneServiceManager.sol @@ -13,6 +13,8 @@ pragma solidity >=0.8.0; @@@@@@@@@ @@@@@@@@@ @@@@@@@@@ @@@@@@@@*/ +import "forge-std/console.sol"; + // ============ Internal Imports ============ import {Enrollment, EnrollmentStatus, EnumerableMapEnrollment} from "../libs/EnumerableMapEnrollment.sol"; import {IRemoteChallenger} from "../interfaces/avs/IRemoteChallenger.sol"; @@ -21,7 +23,7 @@ import {ECDSAServiceManagerBase} from "./ECDSAServiceManagerBase.sol"; // ============ External Imports ============ import {IAVSDirectory} from "@eigenlayer/interfaces/IAVSDirectory.sol"; import {ISlasher} from "@eigenlayer/interfaces/ISlasher.sol"; -import {ECDSAStakeRegistry} from "@eigenlayer/ecdsa/ECDSAStakeRegistry.sol"; +import {ECDSAStakeRegistry} from "@eigenlayer-middleware/unaudited/ECDSAStakeRegistry.sol"; contract HyperlaneServiceManager is ECDSAServiceManagerBase { // ============ Libraries ============ @@ -114,34 +116,36 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { } /** - * @notice Queues an operator for unenrollment from a list of challengers + * @notice starts an operator for unenrollment from a list of challengers * @param _challengers The list of challengers to unenroll from */ - function queueUnenrollmentFromChallengers( + function startUnenrollment( IRemoteChallenger[] memory _challengers ) external { for (uint256 i = 0; i < _challengers.length; i++) { IRemoteChallenger challenger = _challengers[i]; + (bool exists, Enrollment memory enrollment) = enrolledChallengers[ msg.sender ].tryGet(address(challenger)); - if (exists && enrollment.status == EnrollmentStatus.ENROLLED) { - require( - enrolledChallengers[msg.sender].set( - address(challenger), - Enrollment( - EnrollmentStatus.PENDING_UNENROLLMENT, - uint248(block.number) - ) - ) - ); - emit OperatorQueuedUnenrollmentFromChallenger( - msg.sender, - challenger, - block.number, - challenger.challengeDelayBlocks() - ); - } + require( + exists && enrollment.status == EnrollmentStatus.ENROLLED, + "HyperlaneServiceManager: challenger isn't enrolled" + ); + + enrolledChallengers[msg.sender].set( + address(challenger), + Enrollment( + EnrollmentStatus.PENDING_UNENROLLMENT, + uint248(block.number) + ) + ); + emit OperatorQueuedUnenrollmentFromChallenger( + msg.sender, + challenger, + block.number, + challenger.challengeDelayBlocks() + ); } } @@ -150,7 +154,7 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { * @param operator The address of the operator * @param _challengers The list of challengers to unenroll from */ - function completeQueuedUnenrollmentFromChallengers( + function completeUnenrollment( address operator, IRemoteChallenger[] memory _challengers ) public onlyStakeRegistryOrOperator(operator) { @@ -162,7 +166,8 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { require( exists && - enrollment.status != EnrollmentStatus.ENROLLED && + enrollment.status == + EnrollmentStatus.PENDING_UNENROLLMENT && block.number >= enrollment.unenrollmentStartBlock + challenger.challengeDelayBlocks(), @@ -185,11 +190,10 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { * @param _operator The address of the operator * @param _challenger specified IRemoteChallenger contract */ - function getEnrolledChallenger( + function getChallengerEnrollment( address _operator, IRemoteChallenger _challenger ) external view returns (Enrollment memory enrollment) { - address[] memory keys = enrolledChallengers[_operator].keys(); return enrolledChallengers[_operator].get(address(_challenger)); } @@ -234,7 +238,7 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { IRemoteChallenger[] memory challengers = getOperatorChallengers( operator ); - completeQueuedUnenrollmentFromChallengers(operator, challengers); + completeUnenrollment(operator, challengers); elAvsDirectory.deregisterOperatorFromAVS(operator); emit OperatorDeregisteredFromAVS(operator); diff --git a/solidity/contracts/libs/EnumerableMapEnrollment.sol b/solidity/contracts/libs/EnumerableMapEnrollment.sol index 6f363bd699..f7d03d9b3f 100644 --- a/solidity/contracts/libs/EnumerableMapEnrollment.sol +++ b/solidity/contracts/libs/EnumerableMapEnrollment.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.6.11; +import "forge-std/console.sol"; + // ============ External Imports ============ import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; diff --git a/solidity/remappings.txt b/solidity/remappings.txt index cb9d0cfd13..2fed3474bf 100644 --- a/solidity/remappings.txt +++ b/solidity/remappings.txt @@ -5,7 +5,5 @@ @eth-optimism=../node_modules/@eth-optimism ds-test/=lib/forge-std/lib/ds-test/src/ forge-std/=lib/forge-std/src/ -@eigenlayer/core/=lib/eigenlayer-contracts/src/contracts/core/ -@eigenlayer/ecdsa/=lib/eigenlayer-middleware/src/unaudited/ -@eigenlayer/middleware/=lib/eigenlayer-middleware/src/ -@eigenlayer/interfaces/=lib/eigenlayer-contracts/src/contracts/interfaces/ +@eigenlayer/=lib/eigenlayer-contracts/src/contracts/ +@eigenlayer-middleware/=lib/eigenlayer-middleware/src/ diff --git a/solidity/test/avs/HyperlaneServiceManager.t.sol b/solidity/test/avs/HyperlaneServiceManager.t.sol index d2b0b42215..31e5e02be5 100644 --- a/solidity/test/avs/HyperlaneServiceManager.t.sol +++ b/solidity/test/avs/HyperlaneServiceManager.t.sol @@ -9,8 +9,8 @@ import {IStrategy} from "@eigenlayer/interfaces/IStrategy.sol"; import {ISlasher} from "@eigenlayer/interfaces/ISlasher.sol"; import {MockAVSDeployer} from "eigenlayer-middleware/test/utils/MockAVSDeployer.sol"; -import {Quorum, StrategyParams} from "@eigenlayer/ecdsa/ECDSAStakeRegistryStorage.sol"; -import {ECDSAStakeRegistry} from "@eigenlayer/ecdsa/ECDSAStakeRegistry.sol"; +import {Quorum, StrategyParams} from "@eigenlayer-middleware/unaudited/ECDSAStakeRegistryStorage.sol"; +import {ECDSAStakeRegistry} from "@eigenlayer-middleware/unaudited/ECDSAStakeRegistry.sol"; import {Enrollment, EnrollmentStatus} from "../../contracts/libs/EnumerableMapEnrollment.sol"; import {IRemoteChallenger} from "../../contracts/interfaces/avs/IRemoteChallenger.sol"; @@ -178,36 +178,40 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { } /// forge-config: default.fuzz.runs = 10 - function testFuzz_queueUnenrollmentFromChallengers_all( - uint8 numOfChallengers - ) public { + function testFuzz_startUnenrollment_revert(uint8 numOfChallengers) public { + vm.assume(numOfChallengers > 0); + _registerOperator(); IRemoteChallenger[] memory challengers = _deployChallengers( numOfChallengers ); vm.startPrank(operator); - hsm.enrollIntoChallengers(challengers); - _assertChallengers(challengers, EnrollmentStatus.ENROLLED, 0); - hsm.queueUnenrollmentFromChallengers(challengers); + vm.expectRevert("HyperlaneServiceManager: challenger isn't enrolled"); + hsm.startUnenrollment(challengers); + + hsm.enrollIntoChallengers(challengers); + hsm.startUnenrollment(challengers); _assertChallengers( challengers, EnrollmentStatus.PENDING_UNENROLLMENT, block.number ); - vm.roll(block.number + challengeDelayBlocks); - - hsm.completeQueuedUnenrollmentFromChallengers(operator, challengers); + vm.expectRevert("HyperlaneServiceManager: challenger isn't enrolled"); + hsm.startUnenrollment(challengers); + _assertChallengers( + challengers, + EnrollmentStatus.PENDING_UNENROLLMENT, + block.number + ); - // get all operator key length assert - assertEq(hsm.getOperatorChallengers(operator).length, 0); vm.stopPrank(); } /// forge-config: default.fuzz.runs = 10 - function testFuzz_queueUnenrollmentFromChallengers( + function testFuzz_startUnenrollment( uint8 numOfChallengers, uint8 numQueued ) public { @@ -235,7 +239,7 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { hsm.enrollIntoChallengers(challengers); _assertChallengers(challengers, EnrollmentStatus.ENROLLED, 0); - hsm.queueUnenrollmentFromChallengers(queuedChallengers); + hsm.startUnenrollment(queuedChallengers); _assertChallengers( queuedChallengers, EnrollmentStatus.PENDING_UNENROLLMENT, @@ -259,7 +263,7 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { vm.startPrank(operator); hsm.enrollIntoChallengers(challengers); - hsm.queueUnenrollmentFromChallengers(challengers); + hsm.startUnenrollment(challengers); vm.stopPrank(); vm.roll(block.number + challengeDelayBlocks); @@ -267,10 +271,10 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { vm.expectRevert( "ECDSAServiceManagerBase: caller is not the stake registry or operator" ); - hsm.completeQueuedUnenrollmentFromChallengers(operator, challengers); + hsm.completeUnenrollment(operator, challengers); vm.prank(address(ecdsaStakeRegistry)); - hsm.completeQueuedUnenrollmentFromChallengers(operator, challengers); + hsm.completeUnenrollment(operator, challengers); assertEq(hsm.getOperatorChallengers(operator).length, 0); } @@ -296,7 +300,7 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { vm.startPrank(operator); hsm.enrollIntoChallengers(challengers); - hsm.queueUnenrollmentFromChallengers(challengers); + hsm.startUnenrollment(challengers); _assertChallengers( challengers, @@ -305,17 +309,11 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { ); vm.expectRevert(); - hsm.completeQueuedUnenrollmentFromChallengers( - operator, - unenrollableChallengers - ); + hsm.completeUnenrollment(operator, unenrollableChallengers); vm.roll(block.number + challengeDelayBlocks); - hsm.completeQueuedUnenrollmentFromChallengers( - operator, - unenrollableChallengers - ); + hsm.completeUnenrollment(operator, unenrollableChallengers); assertEq( hsm.getOperatorChallengers(operator).length, @@ -382,12 +380,9 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { challengers[i].handleChallenge(operator); } - hsm.queueUnenrollmentFromChallengers(challengers); + hsm.startUnenrollment(challengers); vm.roll(block.number + challengeDelayBlocks); - hsm.completeQueuedUnenrollmentFromChallengers( - operator, - unenrollableChallengers - ); + hsm.completeUnenrollment(operator, unenrollableChallengers); for (uint256 i = 0; i < unenrollableChallengers.length; i++) { vm.expectRevert( @@ -422,14 +417,14 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { vm.expectRevert("HyperlaneServiceManager: Invalid unenrollment"); ecdsaStakeRegistry.deregisterOperator(); - hsm.queueUnenrollmentFromChallengers(challengers); + hsm.startUnenrollment(challengers); vm.expectRevert("HyperlaneServiceManager: Invalid unenrollment"); ecdsaStakeRegistry.deregisterOperator(); vm.roll(block.number + challengeDelayBlocks); - // hsm.completeQueuedUnenrollmentFromChallengers(operator, challengers); // this works + // hsm.completeUnenrollment(operator, challengers); // this works // // TODO: just this doesn't work -> ecdsaStakeRegistry.deregisterOperator(); ecdsaStakeRegistry.deregisterOperator(); @@ -470,7 +465,7 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { uint256 _expectUnenrollmentBlock ) internal { for (uint256 i = 0; i < _challengers.length; i++) { - Enrollment memory enrollment = hsm.getEnrolledChallenger( + Enrollment memory enrollment = hsm.getChallengerEnrollment( operator, _challengers[i] ); From d7c203210c56d48782994c53213ba90d0fbc4fa7 Mon Sep 17 00:00:00 2001 From: -f Date: Tue, 30 Apr 2024 23:00:05 +0530 Subject: [PATCH 15/32] hardhat config filtering fail --- .../contracts/avs/HyperlaneServiceManager.sol | 2 -- .../libs/EnumerableMapEnrollment.sol | 2 -- solidity/hardhat.config.cts | 27 ++++++++++++++++++- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/solidity/contracts/avs/HyperlaneServiceManager.sol b/solidity/contracts/avs/HyperlaneServiceManager.sol index 6f2e6a0110..ed9948cf01 100644 --- a/solidity/contracts/avs/HyperlaneServiceManager.sol +++ b/solidity/contracts/avs/HyperlaneServiceManager.sol @@ -13,8 +13,6 @@ pragma solidity >=0.8.0; @@@@@@@@@ @@@@@@@@@ @@@@@@@@@ @@@@@@@@*/ -import "forge-std/console.sol"; - // ============ Internal Imports ============ import {Enrollment, EnrollmentStatus, EnumerableMapEnrollment} from "../libs/EnumerableMapEnrollment.sol"; import {IRemoteChallenger} from "../interfaces/avs/IRemoteChallenger.sol"; diff --git a/solidity/contracts/libs/EnumerableMapEnrollment.sol b/solidity/contracts/libs/EnumerableMapEnrollment.sol index f7d03d9b3f..6f363bd699 100644 --- a/solidity/contracts/libs/EnumerableMapEnrollment.sol +++ b/solidity/contracts/libs/EnumerableMapEnrollment.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.6.11; -import "forge-std/console.sol"; - // ============ External Imports ============ import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; diff --git a/solidity/hardhat.config.cts b/solidity/hardhat.config.cts index 4505567e3f..ea511d8be8 100644 --- a/solidity/hardhat.config.cts +++ b/solidity/hardhat.config.cts @@ -2,14 +2,39 @@ import '@nomiclabs/hardhat-ethers'; import '@nomiclabs/hardhat-waffle'; import '@typechain/hardhat'; import 'hardhat-gas-reporter'; +import { TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS } from 'hardhat/builtin-tasks/task-names'; +import { subtask } from 'hardhat/config'; +import path from 'path'; import 'solidity-coverage'; +async function configureHardhat() { + await subtask( + TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS, + async (_, { config }, runSuper) => { + const paths = await runSuper(); + + const morePaths = paths.filter((solidityFilePath) => { + const relativePath = path.relative( + config.paths.sources, + solidityFilePath, + ); + console.log('relativePath', relativePath); + return !relativePath.includes('avs/'); + }); + console.log('morePaths', morePaths); + return morePaths; + }, + ); +} + +configureHardhat(); + /** * @type import('hardhat/config').HardhatUserConfig */ module.exports = { solidity: { - version: '0.8.19', + version: '0.8.9', settings: { optimizer: { enabled: true, From 01b194047f5ee38550954eabd7213453433a2db9 Mon Sep 17 00:00:00 2001 From: -f Date: Wed, 1 May 2024 13:03:14 +0530 Subject: [PATCH 16/32] Revert "hardhat config filtering fail" This reverts commit d7c203210c56d48782994c53213ba90d0fbc4fa7. --- .../contracts/avs/HyperlaneServiceManager.sol | 2 ++ .../libs/EnumerableMapEnrollment.sol | 2 ++ solidity/hardhat.config.cts | 27 +------------------ 3 files changed, 5 insertions(+), 26 deletions(-) diff --git a/solidity/contracts/avs/HyperlaneServiceManager.sol b/solidity/contracts/avs/HyperlaneServiceManager.sol index ed9948cf01..6f2e6a0110 100644 --- a/solidity/contracts/avs/HyperlaneServiceManager.sol +++ b/solidity/contracts/avs/HyperlaneServiceManager.sol @@ -13,6 +13,8 @@ pragma solidity >=0.8.0; @@@@@@@@@ @@@@@@@@@ @@@@@@@@@ @@@@@@@@*/ +import "forge-std/console.sol"; + // ============ Internal Imports ============ import {Enrollment, EnrollmentStatus, EnumerableMapEnrollment} from "../libs/EnumerableMapEnrollment.sol"; import {IRemoteChallenger} from "../interfaces/avs/IRemoteChallenger.sol"; diff --git a/solidity/contracts/libs/EnumerableMapEnrollment.sol b/solidity/contracts/libs/EnumerableMapEnrollment.sol index 6f363bd699..f7d03d9b3f 100644 --- a/solidity/contracts/libs/EnumerableMapEnrollment.sol +++ b/solidity/contracts/libs/EnumerableMapEnrollment.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.6.11; +import "forge-std/console.sol"; + // ============ External Imports ============ import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; diff --git a/solidity/hardhat.config.cts b/solidity/hardhat.config.cts index ea511d8be8..4505567e3f 100644 --- a/solidity/hardhat.config.cts +++ b/solidity/hardhat.config.cts @@ -2,39 +2,14 @@ import '@nomiclabs/hardhat-ethers'; import '@nomiclabs/hardhat-waffle'; import '@typechain/hardhat'; import 'hardhat-gas-reporter'; -import { TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS } from 'hardhat/builtin-tasks/task-names'; -import { subtask } from 'hardhat/config'; -import path from 'path'; import 'solidity-coverage'; -async function configureHardhat() { - await subtask( - TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS, - async (_, { config }, runSuper) => { - const paths = await runSuper(); - - const morePaths = paths.filter((solidityFilePath) => { - const relativePath = path.relative( - config.paths.sources, - solidityFilePath, - ); - console.log('relativePath', relativePath); - return !relativePath.includes('avs/'); - }); - console.log('morePaths', morePaths); - return morePaths; - }, - ); -} - -configureHardhat(); - /** * @type import('hardhat/config').HardhatUserConfig */ module.exports = { solidity: { - version: '0.8.9', + version: '0.8.19', settings: { optimizer: { enabled: true, From 317d814aa220ae4bbcb9e7032658f574073517f6 Mon Sep 17 00:00:00 2001 From: -f Date: Wed, 1 May 2024 15:36:04 +0530 Subject: [PATCH 17/32] vendor interfaces fail --- .../contracts/avs/ECDSAServiceManagerBase.sol | 14 +-- .../contracts/avs/HyperlaneServiceManager.sol | 8 +- .../interfaces/avs/IAVSDirectory.sol | 36 ++++++ .../interfaces/avs/IECDSAStakeRegistry.sol | 17 +++ .../interfaces/avs/IServiceManager.sol | 57 ++++++++++ .../interfaces/avs/ISignatureUtils.sol | 27 +++++ .../contracts/interfaces/avs/ISlasher.sol | 18 +++ .../contracts/interfaces/avs/IStrategy.sol | 107 ++++++++++++++++++ .../test/avs/HyperlaneServiceManager.t.sol | 5 +- 9 files changed, 276 insertions(+), 13 deletions(-) create mode 100644 solidity/contracts/interfaces/avs/IAVSDirectory.sol create mode 100644 solidity/contracts/interfaces/avs/IECDSAStakeRegistry.sol create mode 100644 solidity/contracts/interfaces/avs/IServiceManager.sol create mode 100644 solidity/contracts/interfaces/avs/ISignatureUtils.sol create mode 100644 solidity/contracts/interfaces/avs/ISlasher.sol create mode 100644 solidity/contracts/interfaces/avs/IStrategy.sol diff --git a/solidity/contracts/avs/ECDSAServiceManagerBase.sol b/solidity/contracts/avs/ECDSAServiceManagerBase.sol index 31c22a3cab..a48d245332 100644 --- a/solidity/contracts/avs/ECDSAServiceManagerBase.sol +++ b/solidity/contracts/avs/ECDSAServiceManagerBase.sol @@ -15,19 +15,19 @@ pragma solidity >=0.8.0; // ============ Internal Imports ============ import {IRemoteChallenger} from "../interfaces/avs/IRemoteChallenger.sol"; +import {IAVSDirectory} from "../interfaces/avs/IAVSDirectory.sol"; +import {ISignatureUtils} from "../interfaces/avs/ISignatureUtils.sol"; +import {ISlasher} from "../interfaces/avs/ISlasher.sol"; +import {IECDSAStakeRegistry} from "../interfaces/avs/IECDSAStakeRegistry.sol"; +import {IServiceManager} from "../interfaces/avs/IServiceManager.sol"; -import {ISignatureUtils} from "@eigenlayer/interfaces/ISignatureUtils.sol"; -import {IAVSDirectory} from "@eigenlayer/interfaces/IAVSDirectory.sol"; -import {ISlasher} from "@eigenlayer/interfaces/ISlasher.sol"; -import {ECDSAStakeRegistry} from "@eigenlayer-middleware/unaudited/ECDSAStakeRegistry.sol"; -import {IServiceManager} from "@eigenlayer-middleware/interfaces/IServiceManager.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable { // ============ Constants ============ // Stake registry responsible for maintaining operator stakes - ECDSAStakeRegistry internal immutable stakeRegistry; + IECDSAStakeRegistry internal immutable stakeRegistry; // Eigenlayer's AVS directory for interactions between AVS and operators IAVSDirectory internal immutable elAvsDirectory; @@ -76,7 +76,7 @@ contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable { constructor( IAVSDirectory _avsDirectory, - ECDSAStakeRegistry _stakeRegistry, + IECDSAStakeRegistry _stakeRegistry, ISlasher _slasher ) { elAvsDirectory = _avsDirectory; diff --git a/solidity/contracts/avs/HyperlaneServiceManager.sol b/solidity/contracts/avs/HyperlaneServiceManager.sol index 6f2e6a0110..f316736105 100644 --- a/solidity/contracts/avs/HyperlaneServiceManager.sol +++ b/solidity/contracts/avs/HyperlaneServiceManager.sol @@ -17,13 +17,13 @@ import "forge-std/console.sol"; // ============ Internal Imports ============ import {Enrollment, EnrollmentStatus, EnumerableMapEnrollment} from "../libs/EnumerableMapEnrollment.sol"; +import {IAVSDirectory} from "../interfaces/avs/IAVSDirectory.sol"; import {IRemoteChallenger} from "../interfaces/avs/IRemoteChallenger.sol"; import {ECDSAServiceManagerBase} from "./ECDSAServiceManagerBase.sol"; // ============ External Imports ============ -import {IAVSDirectory} from "@eigenlayer/interfaces/IAVSDirectory.sol"; -import {ISlasher} from "@eigenlayer/interfaces/ISlasher.sol"; -import {ECDSAStakeRegistry} from "@eigenlayer-middleware/unaudited/ECDSAStakeRegistry.sol"; +import {ISlasher} from "../interfaces/avs/ISlasher.sol"; +import {IECDSAStakeRegistry} from "../interfaces/avs/IECDSAStakeRegistry.sol"; contract HyperlaneServiceManager is ECDSAServiceManagerBase { // ============ Libraries ============ @@ -90,7 +90,7 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { constructor( IAVSDirectory _avsDirectory, - ECDSAStakeRegistry _stakeRegistry, + IECDSAStakeRegistry _stakeRegistry, ISlasher _slasher ) ECDSAServiceManagerBase(_avsDirectory, _stakeRegistry, _slasher) {} diff --git a/solidity/contracts/interfaces/avs/IAVSDirectory.sol b/solidity/contracts/interfaces/avs/IAVSDirectory.sol new file mode 100644 index 0000000000..a41860db33 --- /dev/null +++ b/solidity/contracts/interfaces/avs/IAVSDirectory.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity >=0.5.0; + +import "./ISignatureUtils.sol"; + +interface IAVSDirectory is ISignatureUtils { + /// @notice Enum representing the status of an operator's registration with an AVS + enum OperatorAVSRegistrationStatus { + UNREGISTERED, // Operator not registered to AVS + REGISTERED // Operator registered to AVS + } + + /** + * @notice Called by an avs to register an operator with the avs. + * @param operator The address of the operator to register. + * @param operatorSignature The signature, salt, and expiry of the operator's signature. + */ + + function registerOperatorToAVS( + address operator, + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature + ) external; + + /** + * @notice Called by an avs to deregister an operator with the avs. + * @param operator The address of the operator to deregister. + */ + function deregisterOperatorFromAVS(address operator) external; + + /** + * @notice Called by an AVS to emit an `AVSMetadataURIUpdated` event indicating the information has updated. + * @param metadataURI The URI for metadata associated with an AVS + * @dev Note that the `metadataURI` is *never stored * and is only emitted in the `AVSMetadataURIUpdated` event + */ + function updateAVSMetadataURI(string calldata metadataURI) external; +} diff --git a/solidity/contracts/interfaces/avs/IECDSAStakeRegistry.sol b/solidity/contracts/interfaces/avs/IECDSAStakeRegistry.sol new file mode 100644 index 0000000000..45bee96847 --- /dev/null +++ b/solidity/contracts/interfaces/avs/IECDSAStakeRegistry.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity >=0.5.0; + +import {IStrategy} from "./IStrategy.sol"; + +struct StrategyParams { + IStrategy strategy; // The strategy contract reference + uint96 multiplier; // The multiplier applied to the strategy +} + +struct Quorum { + StrategyParams[] strategies; // An array of strategy parameters to define the quorum +} + +interface IECDSAStakeRegistry { + function quorum() external view returns (Quorum memory); +} diff --git a/solidity/contracts/interfaces/avs/IServiceManager.sol b/solidity/contracts/interfaces/avs/IServiceManager.sol new file mode 100644 index 0000000000..cf19f0eb02 --- /dev/null +++ b/solidity/contracts/interfaces/avs/IServiceManager.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity >=0.5.0; + +import {ISignatureUtils} from "./ISignatureUtils.sol"; + +/** + * @title Minimal interface for a ServiceManager-type contract that forms the single point for an AVS to push updates to EigenLayer + * @author Layr Labs, Inc. + */ +interface IServiceManager { + /** + * @notice Updates the metadata URI for the AVS + * @param _metadataURI is the metadata URI for the AVS + */ + function updateAVSMetadataURI(string memory _metadataURI) external; + + /** + * @notice Forwards a call to EigenLayer's DelegationManager contract to confirm operator registration with the AVS + * @param operator The address of the operator to register. + * @param operatorSignature The signature, salt, and expiry of the operator's signature. + */ + function registerOperatorToAVS( + address operator, + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature + ) external; + + /** + * @notice Forwards a call to EigenLayer's DelegationManager contract to confirm operator deregistration from the AVS + * @param operator The address of the operator to deregister. + */ + function deregisterOperatorFromAVS(address operator) external; + + /** + * @notice Returns the list of strategies that the operator has potentially restaked on the AVS + * @param operator The address of the operator to get restaked strategies for + * @dev This function is intended to be called off-chain + * @dev No guarantee is made on whether the operator has shares for a strategy in a quorum or uniqueness + * of each element in the returned array. The off-chain service should do that validation separately + */ + function getOperatorRestakedStrategies( + address operator + ) external view returns (address[] memory); + + /** + * @notice Returns the list of strategies that the AVS supports for restaking + * @dev This function is intended to be called off-chain + * @dev No guarantee is made on uniqueness of each element in the returned array. + * The off-chain service should do that validation separately + */ + function getRestakeableStrategies() + external + view + returns (address[] memory); + + /// @notice Returns the EigenLayer AVSDirectory contract. + function avsDirectory() external view returns (address); +} diff --git a/solidity/contracts/interfaces/avs/ISignatureUtils.sol b/solidity/contracts/interfaces/avs/ISignatureUtils.sol new file mode 100644 index 0000000000..158b325d17 --- /dev/null +++ b/solidity/contracts/interfaces/avs/ISignatureUtils.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity >=0.5.0; + +/** + * @title The interface for common signature utilities. + * @author Layr Labs, Inc. + * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service + */ +interface ISignatureUtils { + // @notice Struct that bundles together a signature and an expiration time for the signature. Used primarily for stack management. + struct SignatureWithExpiry { + // the signature itself, formatted as a single bytes object + bytes signature; + // the expiration timestamp (UTC) of the signature + uint256 expiry; + } + + // @notice Struct that bundles together a signature, a salt for uniqueness, and an expiration time for the signature. Used primarily for stack management. + struct SignatureWithSaltAndExpiry { + // the signature itself, formatted as a single bytes object + bytes signature; + // the salt used to generate the signature + bytes32 salt; + // the expiration timestamp (UTC) of the signature + uint256 expiry; + } +} diff --git a/solidity/contracts/interfaces/avs/ISlasher.sol b/solidity/contracts/interfaces/avs/ISlasher.sol new file mode 100644 index 0000000000..ccff2786fe --- /dev/null +++ b/solidity/contracts/interfaces/avs/ISlasher.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity >=0.5.0; + +/** + * @title Interface for the primary 'slashing' contract for EigenLayer. + * @author Layr Labs, Inc. + * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service + * @notice See the `Slasher` contract itself for implementation details. + */ +interface ISlasher { + /** + * @notice Used for 'slashing' a certain operator. + * @param toBeFrozen The operator to be frozen. + * @dev Technically the operator is 'frozen' (hence the name of this function), and then subject to slashing pending a decision by a human-in-the-loop. + * @dev The operator must have previously given the caller (which should be a contract) the ability to slash them, through a call to `optIntoSlashing`. + */ + function freezeOperator(address toBeFrozen) external; +} diff --git a/solidity/contracts/interfaces/avs/IStrategy.sol b/solidity/contracts/interfaces/avs/IStrategy.sol new file mode 100644 index 0000000000..3bcc517735 --- /dev/null +++ b/solidity/contracts/interfaces/avs/IStrategy.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity >=0.5.0; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +/** + * @title Minimal interface for an `Strategy` contract. + * @author Layr Labs, Inc. + * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service + * @notice Custom `Strategy` implementations may expand extensively on this interface. + */ +interface IStrategy { + /** + * @notice Used to deposit tokens into this Strategy + * @param token is the ERC20 token being deposited + * @param amount is the amount of token being deposited + * @dev This function is only callable by the strategyManager contract. It is invoked inside of the strategyManager's + * `depositIntoStrategy` function, and individual share balances are recorded in the strategyManager as well. + * @return newShares is the number of new shares issued at the current exchange ratio. + */ + function deposit(IERC20 token, uint256 amount) external returns (uint256); + + /** + * @notice Used to withdraw tokens from this Strategy, to the `recipient`'s address + * @param recipient is the address to receive the withdrawn funds + * @param token is the ERC20 token being transferred out + * @param amountShares is the amount of shares being withdrawn + * @dev This function is only callable by the strategyManager contract. It is invoked inside of the strategyManager's + * other functions, and individual share balances are recorded in the strategyManager as well. + */ + function withdraw( + address recipient, + IERC20 token, + uint256 amountShares + ) external; + + /** + * @notice Used to convert a number of shares to the equivalent amount of underlying tokens for this strategy. + * @notice In contrast to `sharesToUnderlyingView`, this function **may** make state modifications + * @param amountShares is the amount of shares to calculate its conversion into the underlying token + * @return The amount of underlying tokens corresponding to the input `amountShares` + * @dev Implementation for these functions in particular may vary significantly for different strategies + */ + function sharesToUnderlying( + uint256 amountShares + ) external returns (uint256); + + /** + * @notice Used to convert an amount of underlying tokens to the equivalent amount of shares in this strategy. + * @notice In contrast to `underlyingToSharesView`, this function **may** make state modifications + * @param amountUnderlying is the amount of `underlyingToken` to calculate its conversion into strategy shares + * @return The amount of underlying tokens corresponding to the input `amountShares` + * @dev Implementation for these functions in particular may vary significantly for different strategies + */ + function underlyingToShares( + uint256 amountUnderlying + ) external returns (uint256); + + /** + * @notice convenience function for fetching the current underlying value of all of the `user`'s shares in + * this strategy. In contrast to `userUnderlyingView`, this function **may** make state modifications + */ + function userUnderlying(address user) external returns (uint256); + + /** + * @notice convenience function for fetching the current total shares of `user` in this strategy, by + * querying the `strategyManager` contract + */ + function shares(address user) external view returns (uint256); + + /** + * @notice Used to convert a number of shares to the equivalent amount of underlying tokens for this strategy. + * @notice In contrast to `sharesToUnderlying`, this function guarantees no state modifications + * @param amountShares is the amount of shares to calculate its conversion into the underlying token + * @return The amount of shares corresponding to the input `amountUnderlying` + * @dev Implementation for these functions in particular may vary significantly for different strategies + */ + function sharesToUnderlyingView( + uint256 amountShares + ) external view returns (uint256); + + /** + * @notice Used to convert an amount of underlying tokens to the equivalent amount of shares in this strategy. + * @notice In contrast to `underlyingToShares`, this function guarantees no state modifications + * @param amountUnderlying is the amount of `underlyingToken` to calculate its conversion into strategy shares + * @return The amount of shares corresponding to the input `amountUnderlying` + * @dev Implementation for these functions in particular may vary significantly for different strategies + */ + function underlyingToSharesView( + uint256 amountUnderlying + ) external view returns (uint256); + + /** + * @notice convenience function for fetching the current underlying value of all of the `user`'s shares in + * this strategy. In contrast to `userUnderlying`, this function guarantees no state modifications + */ + function userUnderlyingView(address user) external view returns (uint256); + + /// @notice The underlying token for shares in this Strategy + function underlyingToken() external view returns (IERC20); + + /// @notice The total number of extant shares in this Strategy + function totalShares() external view returns (uint256); + + /// @notice Returns either a brief string explaining the strategy's goal & purpose, or a link to metadata that explains in more detail. + function explanation() external view returns (string memory); +} diff --git a/solidity/test/avs/HyperlaneServiceManager.t.sol b/solidity/test/avs/HyperlaneServiceManager.t.sol index 31e5e02be5..6af4d24943 100644 --- a/solidity/test/avs/HyperlaneServiceManager.t.sol +++ b/solidity/test/avs/HyperlaneServiceManager.t.sol @@ -2,8 +2,7 @@ pragma solidity >=0.8.0; import {DelegationManager} from "@eigenlayer/core/DelegationManager.sol"; -import {ISignatureUtils} from "@eigenlayer/interfaces/ISignatureUtils.sol"; -import {IAVSDirectory} from "@eigenlayer/interfaces/IAVSDirectory.sol"; + import {IDelegationManager} from "@eigenlayer/interfaces/IDelegationManager.sol"; import {IStrategy} from "@eigenlayer/interfaces/IStrategy.sol"; import {ISlasher} from "@eigenlayer/interfaces/ISlasher.sol"; @@ -12,8 +11,10 @@ import {MockAVSDeployer} from "eigenlayer-middleware/test/utils/MockAVSDeployer. import {Quorum, StrategyParams} from "@eigenlayer-middleware/unaudited/ECDSAStakeRegistryStorage.sol"; import {ECDSAStakeRegistry} from "@eigenlayer-middleware/unaudited/ECDSAStakeRegistry.sol"; +import {ISignatureUtils} from "../../contracts/interfaces/avs/ISignatureUtils.sol"; import {Enrollment, EnrollmentStatus} from "../../contracts/libs/EnumerableMapEnrollment.sol"; import {IRemoteChallenger} from "../../contracts/interfaces/avs/IRemoteChallenger.sol"; +import {IAVSDirectory} from "../../contracts/interfaces/avs/IAVSDirectory.sol"; import {HyperlaneServiceManager} from "../../contracts/avs/HyperlaneServiceManager.sol"; import {TestRemoteChallenger} from "../../contracts/test/TestRemoteChallenger.sol"; From bf3e8df5c158fdd1bbc0e7d33b647fa8876f4b2d Mon Sep 17 00:00:00 2001 From: -f Date: Sat, 4 May 2024 15:09:46 +0530 Subject: [PATCH 18/32] vendor --- .gitmodules | 6 - .../contracts/avs/ECDSAServiceManagerBase.sol | 9 +- .../contracts/avs/HyperlaneServiceManager.sol | 9 +- .../interfaces/avs/IAVSDirectory.sol | 25 +-- .../interfaces/avs/IDelegationManager.sol | 18 ++ .../interfaces/avs/IECDSAStakeRegistry.sol | 3 + .../interfaces/avs/IServiceManager.sol | 7 +- .../interfaces/avs/ISignatureUtils.sol | 5 - .../contracts/interfaces/avs/ISlasher.sol | 12 -- .../libs/EnumerableMapEnrollment.sol | 2 +- solidity/contracts/test/TestEigenlayer.sol | 141 +++++++++++++++ .../contracts/test/TestRemoteChallenger.sol | 2 +- solidity/lib/eigenlayer-contracts | 1 - solidity/lib/eigenlayer-middleware | 1 - solidity/remappings.txt | 2 - solidity/test/avs/EigenlayerBase.sol | 19 ++ .../test/avs/HyperlaneServiceManager.t.sol | 164 ++++++++---------- 17 files changed, 280 insertions(+), 146 deletions(-) create mode 100644 solidity/contracts/interfaces/avs/IDelegationManager.sol create mode 100644 solidity/contracts/test/TestEigenlayer.sol delete mode 160000 solidity/lib/eigenlayer-contracts delete mode 160000 solidity/lib/eigenlayer-middleware create mode 100644 solidity/test/avs/EigenlayerBase.sol diff --git a/.gitmodules b/.gitmodules index f07a5f3d3c..3077b9e20f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,3 @@ [submodule "solidity/lib/forge-std"] path = solidity/lib/forge-std url = https://github.com/foundry-rs/forge-std -[submodule "solidity/lib/eigenlayer-middleware"] - path = solidity/lib/eigenlayer-middleware - url = https://github.com/Layr-Labs/eigenlayer-middleware -[submodule "solidity/lib/eigenlayer-contracts"] - path = solidity/lib/eigenlayer-contracts - url = https://github.com/Layr-Labs/eigenlayer-contracts diff --git a/solidity/contracts/avs/ECDSAServiceManagerBase.sol b/solidity/contracts/avs/ECDSAServiceManagerBase.sol index a48d245332..e3c030bc41 100644 --- a/solidity/contracts/avs/ECDSAServiceManagerBase.sol +++ b/solidity/contracts/avs/ECDSAServiceManagerBase.sol @@ -82,7 +82,14 @@ contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable { elAvsDirectory = _avsDirectory; stakeRegistry = _stakeRegistry; slasher = _slasher; - _disableInitializers(); + // _transferOwnership(msg.sender); + // _disableInitializers(); + } + + function __ServiceManagerBase_init( + address initialOwner + ) internal virtual initializer { + _transferOwnership(initialOwner); } // ============ External Functions ============ diff --git a/solidity/contracts/avs/HyperlaneServiceManager.sol b/solidity/contracts/avs/HyperlaneServiceManager.sol index f316736105..a04c6e5f46 100644 --- a/solidity/contracts/avs/HyperlaneServiceManager.sol +++ b/solidity/contracts/avs/HyperlaneServiceManager.sol @@ -92,7 +92,14 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { IAVSDirectory _avsDirectory, IECDSAStakeRegistry _stakeRegistry, ISlasher _slasher - ) ECDSAServiceManagerBase(_avsDirectory, _stakeRegistry, _slasher) {} + ) ECDSAServiceManagerBase(_avsDirectory, _stakeRegistry, _slasher) { + __ServiceManagerBase_init(msg.sender); + _disableInitializers(); + } + + function initialize() external initializer { + __ServiceManagerBase_init(msg.sender); + } // ============ External Functions ============ diff --git a/solidity/contracts/interfaces/avs/IAVSDirectory.sol b/solidity/contracts/interfaces/avs/IAVSDirectory.sol index a41860db33..24dfd59d81 100644 --- a/solidity/contracts/interfaces/avs/IAVSDirectory.sol +++ b/solidity/contracts/interfaces/avs/IAVSDirectory.sol @@ -1,36 +1,25 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity >=0.5.0; +pragma solidity >=0.8.0; import "./ISignatureUtils.sol"; +/// part of mock interfaces for vendoring necessary Eigenlayer contracts for the hyperlane AVS +/// @author Layr Labs, Inc. + interface IAVSDirectory is ISignatureUtils { - /// @notice Enum representing the status of an operator's registration with an AVS enum OperatorAVSRegistrationStatus { - UNREGISTERED, // Operator not registered to AVS - REGISTERED // Operator registered to AVS + UNREGISTERED, + REGISTERED } - /** - * @notice Called by an avs to register an operator with the avs. - * @param operator The address of the operator to register. - * @param operatorSignature The signature, salt, and expiry of the operator's signature. - */ + event AVSMetadataURIUpdated(address indexed avs, string metadataURI); function registerOperatorToAVS( address operator, ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature ) external; - /** - * @notice Called by an avs to deregister an operator with the avs. - * @param operator The address of the operator to deregister. - */ function deregisterOperatorFromAVS(address operator) external; - /** - * @notice Called by an AVS to emit an `AVSMetadataURIUpdated` event indicating the information has updated. - * @param metadataURI The URI for metadata associated with an AVS - * @dev Note that the `metadataURI` is *never stored * and is only emitted in the `AVSMetadataURIUpdated` event - */ function updateAVSMetadataURI(string calldata metadataURI) external; } diff --git a/solidity/contracts/interfaces/avs/IDelegationManager.sol b/solidity/contracts/interfaces/avs/IDelegationManager.sol new file mode 100644 index 0000000000..ee33cd2b3e --- /dev/null +++ b/solidity/contracts/interfaces/avs/IDelegationManager.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity >=0.8.0; + +/// part of mock interfaces for vendoring necessary Eigenlayer contracts for the hyperlane AVS +/// @author Layr Labs, Inc. + +interface IDelegationManager { + struct OperatorDetails { + address earningsReceiver; + address delegationApprover; + uint32 stakerOptOutWindowBlocks; + } + + function registerAsOperator( + OperatorDetails calldata registeringOperatorDetails, + string calldata metadataURI + ) external; +} diff --git a/solidity/contracts/interfaces/avs/IECDSAStakeRegistry.sol b/solidity/contracts/interfaces/avs/IECDSAStakeRegistry.sol index 45bee96847..e1b01b54bf 100644 --- a/solidity/contracts/interfaces/avs/IECDSAStakeRegistry.sol +++ b/solidity/contracts/interfaces/avs/IECDSAStakeRegistry.sol @@ -3,6 +3,9 @@ pragma solidity >=0.5.0; import {IStrategy} from "./IStrategy.sol"; +/// part of mock interfaces for vendoring necessary Eigenlayer contracts for the hyperlane AVS +/// @author Layr Labs, Inc. + struct StrategyParams { IStrategy strategy; // The strategy contract reference uint96 multiplier; // The multiplier applied to the strategy diff --git a/solidity/contracts/interfaces/avs/IServiceManager.sol b/solidity/contracts/interfaces/avs/IServiceManager.sol index cf19f0eb02..64ec580739 100644 --- a/solidity/contracts/interfaces/avs/IServiceManager.sol +++ b/solidity/contracts/interfaces/avs/IServiceManager.sol @@ -3,10 +3,9 @@ pragma solidity >=0.5.0; import {ISignatureUtils} from "./ISignatureUtils.sol"; -/** - * @title Minimal interface for a ServiceManager-type contract that forms the single point for an AVS to push updates to EigenLayer - * @author Layr Labs, Inc. - */ +/// part of mock interfaces for vendoring necessary Eigenlayer contracts for the hyperlane AVS +/// @author Layr Labs, Inc. + interface IServiceManager { /** * @notice Updates the metadata URI for the AVS diff --git a/solidity/contracts/interfaces/avs/ISignatureUtils.sol b/solidity/contracts/interfaces/avs/ISignatureUtils.sol index 158b325d17..873349f51d 100644 --- a/solidity/contracts/interfaces/avs/ISignatureUtils.sol +++ b/solidity/contracts/interfaces/avs/ISignatureUtils.sol @@ -1,11 +1,6 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity >=0.5.0; -/** - * @title The interface for common signature utilities. - * @author Layr Labs, Inc. - * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service - */ interface ISignatureUtils { // @notice Struct that bundles together a signature and an expiration time for the signature. Used primarily for stack management. struct SignatureWithExpiry { diff --git a/solidity/contracts/interfaces/avs/ISlasher.sol b/solidity/contracts/interfaces/avs/ISlasher.sol index ccff2786fe..f70329edff 100644 --- a/solidity/contracts/interfaces/avs/ISlasher.sol +++ b/solidity/contracts/interfaces/avs/ISlasher.sol @@ -1,18 +1,6 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity >=0.5.0; -/** - * @title Interface for the primary 'slashing' contract for EigenLayer. - * @author Layr Labs, Inc. - * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service - * @notice See the `Slasher` contract itself for implementation details. - */ interface ISlasher { - /** - * @notice Used for 'slashing' a certain operator. - * @param toBeFrozen The operator to be frozen. - * @dev Technically the operator is 'frozen' (hence the name of this function), and then subject to slashing pending a decision by a human-in-the-loop. - * @dev The operator must have previously given the caller (which should be a contract) the ability to slash them, through a call to `optIntoSlashing`. - */ function freezeOperator(address toBeFrozen) external; } diff --git a/solidity/contracts/libs/EnumerableMapEnrollment.sol b/solidity/contracts/libs/EnumerableMapEnrollment.sol index f7d03d9b3f..1d50a12f3c 100644 --- a/solidity/contracts/libs/EnumerableMapEnrollment.sol +++ b/solidity/contracts/libs/EnumerableMapEnrollment.sol @@ -46,7 +46,7 @@ library EnumerableMapEnrollment { ); } - function decode(bytes32 encoded) public view returns (Enrollment memory) { + function decode(bytes32 encoded) public pure returns (Enrollment memory) { uint8 status = uint8(encoded[0]); uint248 unenrollmentStartBlock = uint248(uint256((encoded << 8) >> 8)); return Enrollment(EnrollmentStatus(status), unenrollmentStartBlock); diff --git a/solidity/contracts/test/TestEigenlayer.sol b/solidity/contracts/test/TestEigenlayer.sol new file mode 100644 index 0000000000..9c00ed26b4 --- /dev/null +++ b/solidity/contracts/test/TestEigenlayer.sol @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +import {IAVSDirectory} from "../interfaces/avs/IAVSDirectory.sol"; +import {ISignatureUtils} from "../interfaces/avs/ISignatureUtils.sol"; +import {IDelegationManager} from "../interfaces/avs/IDelegationManager.sol"; +import {ISlasher} from "../interfaces/avs/ISlasher.sol"; +import {Quorum, IECDSAStakeRegistry} from "../interfaces/avs/IECDSAStakeRegistry.sol"; + +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + +contract TestAVSDirectory is IAVSDirectory { + bytes32 public constant OPERATOR_AVS_REGISTRATION_TYPEHASH = + keccak256( + "OperatorAVSRegistration(address operator,address avs,bytes32 salt,uint256 expiry)" + ); + bytes32 public constant DOMAIN_TYPEHASH = + keccak256( + "EIP712Domain(string name,uint256 chainId,address verifyingContract)" + ); + + mapping(address => mapping(address => OperatorAVSRegistrationStatus)) + public avsOperatorStatus; + + function updateAVSMetadataURI(string calldata metadataURI) external { + emit AVSMetadataURIUpdated(msg.sender, metadataURI); + } + + function registerOperatorToAVS( + address operator, + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature + ) external { + bytes32 operatorRegistrationDigestHash = calculateOperatorAVSRegistrationDigestHash({ + operator: operator, + avs: msg.sender, + salt: operatorSignature.salt, + expiry: operatorSignature.expiry + }); + require( + ECDSA.recover( + operatorRegistrationDigestHash, + operatorSignature.signature + ) == operator, + "EIP1271SignatureUtils.checkSignature_EIP1271: signature not from signer" + ); + avsOperatorStatus[msg.sender][operator] = OperatorAVSRegistrationStatus + .REGISTERED; + } + + function deregisterOperatorFromAVS(address operator) external { + avsOperatorStatus[msg.sender][operator] = OperatorAVSRegistrationStatus + .UNREGISTERED; + } + + function calculateOperatorAVSRegistrationDigestHash( + address operator, + address avs, + bytes32 salt, + uint256 expiry + ) public view returns (bytes32) { + // calculate the struct hash + bytes32 structHash = keccak256( + abi.encode( + OPERATOR_AVS_REGISTRATION_TYPEHASH, + operator, + avs, + salt, + expiry + ) + ); + // calculate the digest hash + bytes32 digestHash = keccak256( + abi.encodePacked("\x19\x01", domainSeparator(), structHash) + ); + return digestHash; + } + + function domainSeparator() public view returns (bytes32) { + return + keccak256( + abi.encode( + DOMAIN_TYPEHASH, + keccak256(bytes("EigenLayer")), + block.chainid, + address(this) + ) + ); + } +} + +contract TestDelegationManager is IDelegationManager { + mapping(address => bool) public isOperator; + + function registerAsOperator( + OperatorDetails calldata registeringOperatorDetails, + string calldata metadataURI + ) external {} + + function setIsOperator( + address operator, + bool _isOperatorReturnValue + ) external { + isOperator[operator] = _isOperatorReturnValue; + } +} + +contract TestSlasher is ISlasher { + function freezeOperator(address toBeFrozen) external {} +} + +import {IServiceManager} from "../interfaces/avs/IServiceManager.sol"; +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + +contract TestECDSAStakeRegistry is IECDSAStakeRegistry { + Quorum internal _quorum; + address internal _serviceManager; + + function initialize( + address serviceManager, + uint256, + Quorum memory + ) external { + _serviceManager = serviceManager; + } + + function quorum() external view returns (Quorum memory) {} + + function registerOperatorWithSignature( + address _operator, + ISignatureUtils.SignatureWithSaltAndExpiry memory _operatorSignature + ) external { + IServiceManager(_serviceManager).registerOperatorToAVS( + _operator, + _operatorSignature + ); + } + + function deregisterOperator() external { + IServiceManager(_serviceManager).deregisterOperatorFromAVS(msg.sender); + } +} diff --git a/solidity/contracts/test/TestRemoteChallenger.sol b/solidity/contracts/test/TestRemoteChallenger.sol index 17e55dfd43..e7fbdc220e 100644 --- a/solidity/contracts/test/TestRemoteChallenger.sol +++ b/solidity/contracts/test/TestRemoteChallenger.sol @@ -11,7 +11,7 @@ contract TestRemoteChallenger is IRemoteChallenger { hsm = _hsm; } - function challengeDelayBlocks() external view returns (uint256) { + function challengeDelayBlocks() external pure returns (uint256) { return 50400; // one week of eth L1 blocks } diff --git a/solidity/lib/eigenlayer-contracts b/solidity/lib/eigenlayer-contracts deleted file mode 160000 index 7229f2b426..0000000000 --- a/solidity/lib/eigenlayer-contracts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7229f2b426b6f2a24c7795b1a4687a010eac8ef2 diff --git a/solidity/lib/eigenlayer-middleware b/solidity/lib/eigenlayer-middleware deleted file mode 160000 index 6454c05bee..0000000000 --- a/solidity/lib/eigenlayer-middleware +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6454c05beec77a165211f081b50905c516fc7777 diff --git a/solidity/remappings.txt b/solidity/remappings.txt index 2fed3474bf..ae7bb51d69 100644 --- a/solidity/remappings.txt +++ b/solidity/remappings.txt @@ -5,5 +5,3 @@ @eth-optimism=../node_modules/@eth-optimism ds-test/=lib/forge-std/lib/ds-test/src/ forge-std/=lib/forge-std/src/ -@eigenlayer/=lib/eigenlayer-contracts/src/contracts/ -@eigenlayer-middleware/=lib/eigenlayer-middleware/src/ diff --git a/solidity/test/avs/EigenlayerBase.sol b/solidity/test/avs/EigenlayerBase.sol new file mode 100644 index 0000000000..3bbcf1b702 --- /dev/null +++ b/solidity/test/avs/EigenlayerBase.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +import "forge-std/Test.sol"; + +import {ISlasher} from "../../contracts/interfaces/avs/ISlasher.sol"; +import {TestAVSDirectory, TestDelegationManager, TestSlasher, TestDelegationManager} from "../../contracts/test/TestEigenlayer.sol"; + +contract EigenlayerBase is Test { + TestAVSDirectory internal avsDirectory; + TestDelegationManager internal delegationManager; + ISlasher internal slasher; + + function _deployMockEigenLayerAndAVS() internal { + avsDirectory = new TestAVSDirectory(); + delegationManager = new TestDelegationManager(); + slasher = new TestSlasher(); + } +} diff --git a/solidity/test/avs/HyperlaneServiceManager.t.sol b/solidity/test/avs/HyperlaneServiceManager.t.sol index 6af4d24943..64e3cb92e9 100644 --- a/solidity/test/avs/HyperlaneServiceManager.t.sol +++ b/solidity/test/avs/HyperlaneServiceManager.t.sol @@ -1,28 +1,26 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.8.0; -import {DelegationManager} from "@eigenlayer/core/DelegationManager.sol"; +import {IDelegationManager} from "../../contracts/interfaces/avs/IDelegationManager.sol"; +import {ISlasher} from "../../contracts/interfaces/avs/ISlasher.sol"; -import {IDelegationManager} from "@eigenlayer/interfaces/IDelegationManager.sol"; -import {IStrategy} from "@eigenlayer/interfaces/IStrategy.sol"; -import {ISlasher} from "@eigenlayer/interfaces/ISlasher.sol"; - -import {MockAVSDeployer} from "eigenlayer-middleware/test/utils/MockAVSDeployer.sol"; -import {Quorum, StrategyParams} from "@eigenlayer-middleware/unaudited/ECDSAStakeRegistryStorage.sol"; -import {ECDSAStakeRegistry} from "@eigenlayer-middleware/unaudited/ECDSAStakeRegistry.sol"; +import {IAVSDirectory} from "../../contracts/interfaces/avs/IAVSDirectory.sol"; +import {Quorum, StrategyParams, IECDSAStakeRegistry} from "../../contracts/interfaces/avs/IECDSAStakeRegistry.sol"; +import {TestECDSAStakeRegistry, TestDelegationManager} from "../../contracts/test/TestEigenlayer.sol"; +import {IStrategy} from "../../contracts/interfaces/avs/IStrategy.sol"; import {ISignatureUtils} from "../../contracts/interfaces/avs/ISignatureUtils.sol"; import {Enrollment, EnrollmentStatus} from "../../contracts/libs/EnumerableMapEnrollment.sol"; import {IRemoteChallenger} from "../../contracts/interfaces/avs/IRemoteChallenger.sol"; -import {IAVSDirectory} from "../../contracts/interfaces/avs/IAVSDirectory.sol"; + import {HyperlaneServiceManager} from "../../contracts/avs/HyperlaneServiceManager.sol"; import {TestRemoteChallenger} from "../../contracts/test/TestRemoteChallenger.sol"; -contract HyperlaneServiceManagerTest is MockAVSDeployer { - DelegationManager public delegationManager; +import {EigenlayerBase} from "./EigenlayerBase.sol"; - HyperlaneServiceManager internal hsm; - ECDSAStakeRegistry internal ecdsaStakeRegistry; +contract HyperlaneServiceManagerTest is EigenlayerBase { + HyperlaneServiceManager internal _hsm; + TestECDSAStakeRegistry internal _ecdsaStakeRegistry; // Operator info uint256 operatorPrivateKey = 0xdeadbeef; @@ -31,19 +29,15 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { bytes32 emptySalt; uint256 maxExpiry = type(uint256).max; uint256 challengeDelayBlocks = 50400; // one week of eth L1 blocks + address invalidServiceManager = address(0x1234); function setUp() public { _deployMockEigenLayerAndAVS(); - delegationManager = new DelegationManager( - strategyManagerMock, - slasher, - eigenPodManagerMock - ); - ecdsaStakeRegistry = new ECDSAStakeRegistry(delegationManager); - hsm = new HyperlaneServiceManager( + _ecdsaStakeRegistry = new TestECDSAStakeRegistry(); + _hsm = new HyperlaneServiceManager( avsDirectory, - ecdsaStakeRegistry, + _ecdsaStakeRegistry, slasher ); @@ -53,7 +47,7 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { strategy: mockStrategy, multiplier: 10000 }); - ecdsaStakeRegistry.initialize(address(hsm), 6667, quorum); + _ecdsaStakeRegistry.initialize(address(_hsm), 6667, quorum); // register operator to eigenlayer operator = vm.addr(operatorPrivateKey); @@ -67,7 +61,21 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { "" ); // set operator as registered in Eigenlayer - delegationMock.setIsOperator(operator, true); + delegationManager.setIsOperator(operator, true); + } + + event AVSMetadataURIUpdated(address indexed avs, string metadataURI); + + function test_updateAVSMetadataURI() public { + vm.expectEmit(true, true, true, true, address(avsDirectory)); + emit AVSMetadataURIUpdated(address(_hsm), "hyperlaneAVS"); + _hsm.updateAVSMetadataURI("hyperlaneAVS"); + } + + function test_updateAVSMetadataURI_revert_notOwnable() public { + vm.prank(address(0x1234)); + vm.expectRevert("Ownable: caller is not the owner"); + _hsm.updateAVSMetadataURI("hyperlaneAVS"); } function test_registerOperator() public { @@ -76,18 +84,18 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { memory operatorSignature = _getOperatorSignature( operatorPrivateKey, operator, - address(hsm), + address(_hsm), emptySalt, maxExpiry ); - ecdsaStakeRegistry.registerOperatorWithSignature( + _ecdsaStakeRegistry.registerOperatorWithSignature( operator, operatorSignature ); // assert IAVSDirectory.OperatorAVSRegistrationStatus operatorStatus = avsDirectory - .avsOperatorStatus(address(hsm), operator); + .avsOperatorStatus(address(_hsm), operator); assertEq( uint8(operatorStatus), uint8(IAVSDirectory.OperatorAVSRegistrationStatus.REGISTERED) @@ -100,7 +108,7 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { memory operatorSignature = _getOperatorSignature( operatorPrivateKey, operator, - address(serviceManager), + address(0x1), emptySalt, maxExpiry ); @@ -108,42 +116,14 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { vm.expectRevert( "EIP1271SignatureUtils.checkSignature_EIP1271: signature not from signer" ); - ecdsaStakeRegistry.registerOperatorWithSignature( - operator, - operatorSignature - ); - - // assert - IAVSDirectory.OperatorAVSRegistrationStatus operatorStatus = avsDirectory - .avsOperatorStatus(address(hsm), operator); - assertEq( - uint8(operatorStatus), - uint8(IAVSDirectory.OperatorAVSRegistrationStatus.UNREGISTERED) - ); - } - - function test_registerOperator_revert_expiredSignature() public { - // act - ISignatureUtils.SignatureWithSaltAndExpiry - memory operatorSignature = _getOperatorSignature( - operatorPrivateKey, - operator, - address(hsm), - emptySalt, - 0 - ); - - vm.expectRevert( - "AVSDirectory.registerOperatorToAVS: operator signature expired" - ); - ecdsaStakeRegistry.registerOperatorWithSignature( + _ecdsaStakeRegistry.registerOperatorWithSignature( operator, operatorSignature ); // assert IAVSDirectory.OperatorAVSRegistrationStatus operatorStatus = avsDirectory - .avsOperatorStatus(address(hsm), operator); + .avsOperatorStatus(address(_hsm), operator); assertEq( uint8(operatorStatus), uint8(IAVSDirectory.OperatorAVSRegistrationStatus.UNREGISTERED) @@ -154,11 +134,11 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { // act _registerOperator(); vm.prank(operator); - ecdsaStakeRegistry.deregisterOperator(); + _ecdsaStakeRegistry.deregisterOperator(); // assert IAVSDirectory.OperatorAVSRegistrationStatus operatorStatus = avsDirectory - .avsOperatorStatus(address(hsm), operator); + .avsOperatorStatus(address(_hsm), operator); assertEq( uint8(operatorStatus), uint8(IAVSDirectory.OperatorAVSRegistrationStatus.UNREGISTERED) @@ -173,7 +153,7 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { ); vm.prank(operator); - hsm.enrollIntoChallengers(challengers); + _hsm.enrollIntoChallengers(challengers); _assertChallengers(challengers, EnrollmentStatus.ENROLLED, 0); } @@ -190,10 +170,10 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { vm.startPrank(operator); vm.expectRevert("HyperlaneServiceManager: challenger isn't enrolled"); - hsm.startUnenrollment(challengers); + _hsm.startUnenrollment(challengers); - hsm.enrollIntoChallengers(challengers); - hsm.startUnenrollment(challengers); + _hsm.enrollIntoChallengers(challengers); + _hsm.startUnenrollment(challengers); _assertChallengers( challengers, EnrollmentStatus.PENDING_UNENROLLMENT, @@ -201,7 +181,7 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { ); vm.expectRevert("HyperlaneServiceManager: challenger isn't enrolled"); - hsm.startUnenrollment(challengers); + _hsm.startUnenrollment(challengers); _assertChallengers( challengers, EnrollmentStatus.PENDING_UNENROLLMENT, @@ -237,10 +217,10 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { } vm.startPrank(operator); - hsm.enrollIntoChallengers(challengers); + _hsm.enrollIntoChallengers(challengers); _assertChallengers(challengers, EnrollmentStatus.ENROLLED, 0); - hsm.startUnenrollment(queuedChallengers); + _hsm.startUnenrollment(queuedChallengers); _assertChallengers( queuedChallengers, EnrollmentStatus.PENDING_UNENROLLMENT, @@ -263,8 +243,8 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { ); vm.startPrank(operator); - hsm.enrollIntoChallengers(challengers); - hsm.startUnenrollment(challengers); + _hsm.enrollIntoChallengers(challengers); + _hsm.startUnenrollment(challengers); vm.stopPrank(); vm.roll(block.number + challengeDelayBlocks); @@ -272,12 +252,12 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { vm.expectRevert( "ECDSAServiceManagerBase: caller is not the stake registry or operator" ); - hsm.completeUnenrollment(operator, challengers); + _hsm.completeUnenrollment(operator, challengers); - vm.prank(address(ecdsaStakeRegistry)); - hsm.completeUnenrollment(operator, challengers); + vm.prank(address(_ecdsaStakeRegistry)); + _hsm.completeUnenrollment(operator, challengers); - assertEq(hsm.getOperatorChallengers(operator).length, 0); + assertEq(_hsm.getOperatorChallengers(operator).length, 0); } /// forge-config: default.fuzz.runs = 10 @@ -300,8 +280,8 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { } vm.startPrank(operator); - hsm.enrollIntoChallengers(challengers); - hsm.startUnenrollment(challengers); + _hsm.enrollIntoChallengers(challengers); + _hsm.startUnenrollment(challengers); _assertChallengers( challengers, @@ -310,14 +290,14 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { ); vm.expectRevert(); - hsm.completeUnenrollment(operator, unenrollableChallengers); + _hsm.completeUnenrollment(operator, unenrollableChallengers); vm.roll(block.number + challengeDelayBlocks); - hsm.completeUnenrollment(operator, unenrollableChallengers); + _hsm.completeUnenrollment(operator, unenrollableChallengers); assertEq( - hsm.getOperatorChallengers(operator).length, + _hsm.getOperatorChallengers(operator).length, numOfChallengers - numUnenrollable ); @@ -333,7 +313,7 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { ); vm.prank(operator); - hsm.enrollIntoChallengers(challengers); + _hsm.enrollIntoChallengers(challengers); for (uint256 i = 0; i < challengers.length; i++) { vm.expectCall( @@ -371,7 +351,7 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { } vm.startPrank(operator); - hsm.enrollIntoChallengers(challengers); + _hsm.enrollIntoChallengers(challengers); for (uint256 i = 0; i < challengers.length; i++) { vm.expectCall( @@ -381,9 +361,9 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { challengers[i].handleChallenge(operator); } - hsm.startUnenrollment(challengers); + _hsm.startUnenrollment(challengers); vm.roll(block.number + challengeDelayBlocks); - hsm.completeUnenrollment(operator, unenrollableChallengers); + _hsm.completeUnenrollment(operator, unenrollableChallengers); for (uint256 i = 0; i < unenrollableChallengers.length; i++) { vm.expectRevert( @@ -412,24 +392,22 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { ); vm.startPrank(operator); - hsm.enrollIntoChallengers(challengers); + _hsm.enrollIntoChallengers(challengers); _assertChallengers(challengers, EnrollmentStatus.ENROLLED, 0); vm.expectRevert("HyperlaneServiceManager: Invalid unenrollment"); - ecdsaStakeRegistry.deregisterOperator(); + _ecdsaStakeRegistry.deregisterOperator(); - hsm.startUnenrollment(challengers); + _hsm.startUnenrollment(challengers); vm.expectRevert("HyperlaneServiceManager: Invalid unenrollment"); - ecdsaStakeRegistry.deregisterOperator(); + _ecdsaStakeRegistry.deregisterOperator(); vm.roll(block.number + challengeDelayBlocks); - // hsm.completeUnenrollment(operator, challengers); // this works - // // TODO: just this doesn't work -> ecdsaStakeRegistry.deregisterOperator(); - ecdsaStakeRegistry.deregisterOperator(); + _ecdsaStakeRegistry.deregisterOperator(); - assertEq(hsm.getOperatorChallengers(operator).length, 0); + assertEq(_hsm.getOperatorChallengers(operator).length, 0); vm.stopPrank(); } @@ -440,12 +418,12 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { memory operatorSignature = _getOperatorSignature( operatorPrivateKey, operator, - address(hsm), + address(_hsm), emptySalt, maxExpiry ); - ecdsaStakeRegistry.registerOperatorWithSignature( + _ecdsaStakeRegistry.registerOperatorWithSignature( operator, operatorSignature ); @@ -456,7 +434,7 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { ) internal returns (IRemoteChallenger[] memory challengers) { challengers = new IRemoteChallenger[](numOfChallengers); for (uint8 i = 0; i < numOfChallengers; i++) { - challengers[i] = new TestRemoteChallenger(hsm); + challengers[i] = new TestRemoteChallenger(_hsm); } } @@ -466,7 +444,7 @@ contract HyperlaneServiceManagerTest is MockAVSDeployer { uint256 _expectUnenrollmentBlock ) internal { for (uint256 i = 0; i < _challengers.length; i++) { - Enrollment memory enrollment = hsm.getChallengerEnrollment( + Enrollment memory enrollment = _hsm.getChallengerEnrollment( operator, _challengers[i] ); From ff781c355979afe28d48c102d2f5b45c5926f0d1 Mon Sep 17 00:00:00 2001 From: -f Date: Sat, 4 May 2024 16:05:15 +0530 Subject: [PATCH 19/32] cleanup --- solidity/contracts/avs/HyperlaneServiceManager.sol | 2 -- solidity/contracts/libs/EnumerableMapEnrollment.sol | 2 -- 2 files changed, 4 deletions(-) diff --git a/solidity/contracts/avs/HyperlaneServiceManager.sol b/solidity/contracts/avs/HyperlaneServiceManager.sol index a04c6e5f46..2a8857f88f 100644 --- a/solidity/contracts/avs/HyperlaneServiceManager.sol +++ b/solidity/contracts/avs/HyperlaneServiceManager.sol @@ -13,8 +13,6 @@ pragma solidity >=0.8.0; @@@@@@@@@ @@@@@@@@@ @@@@@@@@@ @@@@@@@@*/ -import "forge-std/console.sol"; - // ============ Internal Imports ============ import {Enrollment, EnrollmentStatus, EnumerableMapEnrollment} from "../libs/EnumerableMapEnrollment.sol"; import {IAVSDirectory} from "../interfaces/avs/IAVSDirectory.sol"; diff --git a/solidity/contracts/libs/EnumerableMapEnrollment.sol b/solidity/contracts/libs/EnumerableMapEnrollment.sol index 1d50a12f3c..857f24f91a 100644 --- a/solidity/contracts/libs/EnumerableMapEnrollment.sol +++ b/solidity/contracts/libs/EnumerableMapEnrollment.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.6.11; -import "forge-std/console.sol"; - // ============ External Imports ============ import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; From 05d797931aa37cc54bde4d8ce5bbbf178a9ceb0d Mon Sep 17 00:00:00 2001 From: -f Date: Mon, 6 May 2024 16:51:48 +0530 Subject: [PATCH 20/32] using steven's SMBase --- .../contracts/avs/ECDSAServiceManagerBase.sol | 289 ++++++++++-------- .../contracts/avs/HyperlaneServiceManager.sol | 131 ++++---- .../interfaces/avs/IDelegationManager.sol | 7 + .../interfaces/avs/IPaymentCoordinator.sol | 46 +++ .../interfaces/avs/IServiceManager.sol | 70 ++--- .../interfaces/avs/IServiceManagerUI.sol | 67 ++++ .../TestAVSDirectory.sol} | 61 +--- .../test/avs/TestDelegationManager.sol | 33 ++ .../test/avs/TestECDSAStakeRegistry.sol | 36 +++ .../test/avs/TestPaymentCoordinator.sol | 20 ++ solidity/contracts/test/avs/TestSlasher.sol | 8 + solidity/remappings.txt | 1 - solidity/test/avs/EigenlayerBase.sol | 4 +- .../test/avs/HyperlaneServiceManager.t.sol | 50 +-- 14 files changed, 506 insertions(+), 317 deletions(-) create mode 100644 solidity/contracts/interfaces/avs/IPaymentCoordinator.sol create mode 100644 solidity/contracts/interfaces/avs/IServiceManagerUI.sol rename solidity/contracts/test/{TestEigenlayer.sol => avs/TestAVSDirectory.sol} (60%) create mode 100644 solidity/contracts/test/avs/TestDelegationManager.sol create mode 100644 solidity/contracts/test/avs/TestECDSAStakeRegistry.sol create mode 100644 solidity/contracts/test/avs/TestPaymentCoordinator.sol create mode 100644 solidity/contracts/test/avs/TestSlasher.sol diff --git a/solidity/contracts/avs/ECDSAServiceManagerBase.sol b/solidity/contracts/avs/ECDSAServiceManagerBase.sol index e3c030bc41..76074067e5 100644 --- a/solidity/contracts/avs/ECDSAServiceManagerBase.sol +++ b/solidity/contracts/avs/ECDSAServiceManagerBase.sol @@ -1,41 +1,47 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 +// SPDX-License-Identifier: BUSL-1.1 pragma solidity >=0.8.0; -/*@@@@@@@ @@@@@@@@@ - @@@@@@@@@ @@@@@@@@@ - @@@@@@@@@ @@@@@@@@@ - @@@@@@@@@ @@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@ HYPERLANE @@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@ @@@@@@@@@ - @@@@@@@@@ @@@@@@@@@ - @@@@@@@@@ @@@@@@@@@ -@@@@@@@@@ @@@@@@@@*/ - -// ============ Internal Imports ============ -import {IRemoteChallenger} from "../interfaces/avs/IRemoteChallenger.sol"; -import {IAVSDirectory} from "../interfaces/avs/IAVSDirectory.sol"; import {ISignatureUtils} from "../interfaces/avs/ISignatureUtils.sol"; -import {ISlasher} from "../interfaces/avs/ISlasher.sol"; -import {IECDSAStakeRegistry} from "../interfaces/avs/IECDSAStakeRegistry.sol"; +import {IAVSDirectory} from "../interfaces/avs/IAVSDirectory.sol"; + import {IServiceManager} from "../interfaces/avs/IServiceManager.sol"; +import {IServiceManagerUI} from "../interfaces/avs/IServiceManagerUI.sol"; +import {IDelegationManager} from "../interfaces/avs/IDelegationManager.sol"; +import {IStrategy} from "../interfaces/avs/IStrategy.sol"; +import {IPaymentCoordinator} from "../interfaces/avs/IPaymentCoordinator.sol"; +import {Quorum, IECDSAStakeRegistry} from "../interfaces/avs/IECDSAStakeRegistry.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable { - // ============ Constants ============ +abstract contract ECDSAServiceManagerBase is + IServiceManager, + OwnableUpgradeable +{ + /// @notice Address of the stake registry contract, which manages registration and stake recording. + address public immutable stakeRegistry; - // Stake registry responsible for maintaining operator stakes - IECDSAStakeRegistry internal immutable stakeRegistry; - // Eigenlayer's AVS directory for interactions between AVS and operators - IAVSDirectory internal immutable elAvsDirectory; + /// @notice Address of the AVS directory contract, which manages AVS-related data for registered operators. + address public immutable avsDirectory; - // ============ Public Storage ============ + /// @notice Address of the payment coordinator contract, which handles payment distributions. + address internal immutable paymentCoordinator; - // Slasher contract responsible for slashing operators - // @dev slasher needs to be updated once slashing is implemented - ISlasher internal slasher; + /// @notice Address of the delegation manager contract, which manages staker delegations to operators. + address internal immutable delegationManager; + + // ============ Modifiers ============ + + /** + * @dev Ensures that the function is only callable by the `stakeRegistry` contract. + * This is used to restrict certain registration and deregistration functionality to the `stakeRegistry` + */ + modifier onlyStakeRegistry() { + require( + msg.sender == stakeRegistry, + "ECDSAServiceManagerBase.onlyStakeRegistry: caller is not the stakeRegistry" + ); + _; + } // ============ Events ============ @@ -51,153 +57,198 @@ contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable { */ event OperatorDeregisteredFromAVS(address indexed operator); - // ============ Modifiers ============ - - /// @notice when applied to a function, only allows the ECDSAStakeRegistry to call it - modifier onlyStakeRegistry() { - require( - msg.sender == address(stakeRegistry), - "ECDSAServiceManagerBase: caller is not the stake registry" - ); - _; - } - - /// @notice when applied to a function, only allows the ECDSAStakeRegistry or the operator to call it - /// for completeUnenrollment access control - modifier onlyStakeRegistryOrOperator(address operator) { - require( - msg.sender == address(stakeRegistry) || msg.sender == operator, - "ECDSAServiceManagerBase: caller is not the stake registry or operator" - ); - _; - } - // ============ Constructor ============ + /** + * @dev Constructor for ECDSAServiceManagerBase, initializing immutable contract addresses and disabling initializers. + * @param _avsDirectory The address of the AVS directory contract, managing AVS-related data for registered operators. + * @param _stakeRegistry The address of the stake registry contract, managing registration and stake recording. + * @param _paymentCoordinator The address of the payment coordinator contract, handling payment distributions. + * @param _delegationManager The address of the delegation manager contract, managing staker delegations to operators. + */ constructor( - IAVSDirectory _avsDirectory, - IECDSAStakeRegistry _stakeRegistry, - ISlasher _slasher + address _avsDirectory, + address _stakeRegistry, + address _paymentCoordinator, + address _delegationManager ) { - elAvsDirectory = _avsDirectory; + avsDirectory = _avsDirectory; stakeRegistry = _stakeRegistry; - slasher = _slasher; - // _transferOwnership(msg.sender); - // _disableInitializers(); + paymentCoordinator = _paymentCoordinator; + delegationManager = _delegationManager; } + /** + * @dev Initializes the base service manager by transferring ownership to the initial owner. + * @param initialOwner The address to which the ownership of the contract will be transferred. + */ function __ServiceManagerBase_init( address initialOwner - ) internal virtual initializer { + ) internal virtual onlyInitializing { _transferOwnership(initialOwner); } - // ============ External Functions ============ + /// @inheritdoc IServiceManagerUI + function updateAVSMetadataURI( + string memory _metadataURI + ) external virtual onlyOwner { + _updateAVSMetadataURI(_metadataURI); + } - /** - * @notice Returns the list of strategies that the AVS supports for restaking - * @dev This function is intended to be called off-chain - * @dev No guarantee is made on uniqueness of each element in the returned array. - * The off-chain service should do that validation separately - */ + /// @inheritdoc IServiceManager + function payForRange( + IPaymentCoordinator.RangePayment[] calldata rangePayments + ) external virtual onlyOwner { + _payForRange(rangePayments); + } + + /// @inheritdoc IServiceManagerUI + function registerOperatorToAVS( + address operator, + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature + ) external virtual onlyStakeRegistry { + _registerOperatorToAVS(operator, operatorSignature); + } + + /// @inheritdoc IServiceManagerUI + function deregisterOperatorFromAVS( + address operator + ) external virtual onlyStakeRegistry { + _deregisterOperatorFromAVS(operator); + } + + /// @inheritdoc IServiceManagerUI function getRestakeableStrategies() external view + virtual returns (address[] memory) { return _getRestakeableStrategies(); } - /** - * @notice Returns the list of strategies that the operator has potentially restaked on the AVS - * @dev This function is intended to be called off-chain - * @dev Since ECDSAStakeRegistry only supports one quorum, each operator restakes into all the AVS strategies - * @dev No guarantee is made on uniqueness of each element in the returned array. - * The off-chain service should do that validation separately - */ + /// @inheritdoc IServiceManagerUI function getOperatorRestakedStrategies( - address /* operator */ - ) external view returns (address[] memory) { - return _getRestakeableStrategies(); + address _operator + ) external view virtual returns (address[] memory) { + return _getOperatorRestakedStrategies(_operator); } /** - * @notice Sets the slasher contract responsible for slashing operators - * @param _slasher The address of the slasher contract + * @notice Forwards the call to update AVS metadata URI in the AVSDirectory contract. + * @dev This internal function is a proxy to the `updateAVSMetadataURI` function of the AVSDirectory contract. + * @param _metadataURI The new metadata URI to be set. */ - function setSlasher(ISlasher _slasher) external onlyOwner { - slasher = _slasher; - } - - /// @notice Returns the EigenLayer AVSDirectory contract. - function avsDirectory() external view override returns (address) { - return address(elAvsDirectory); - } - - // ============ Public Functions ============ - - /** - * @notice Updates the metadata URI for the AVS - * @param _metadataURI is the metadata URI for the AVS - * @dev only callable by the owner - */ - function updateAVSMetadataURI( + function _updateAVSMetadataURI( string memory _metadataURI - ) public virtual onlyOwner { - elAvsDirectory.updateAVSMetadataURI(_metadataURI); + ) internal virtual { + IAVSDirectory(avsDirectory).updateAVSMetadataURI(_metadataURI); } /** - * @notice Forwards a call to EigenLayer's AVSDirectory contract to confirm operator registration with the AVS + * @notice Forwards the call to register an operator in the AVSDirectory contract. + * @dev This internal function is a proxy to the `registerOperatorToAVS` function of the AVSDirectory contract. * @param operator The address of the operator to register. - * @param operatorSignature The signature, salt, and expiry of the operator's signature. + * @param operatorSignature The signature, salt, and expiry details of the operator's registration. */ - function registerOperatorToAVS( + function _registerOperatorToAVS( address operator, ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature - ) public virtual onlyStakeRegistry { - elAvsDirectory.registerOperatorToAVS(operator, operatorSignature); + ) internal virtual { + IAVSDirectory(avsDirectory).registerOperatorToAVS( + operator, + operatorSignature + ); emit OperatorRegisteredToAVS(operator); } /** - * @notice Forwards a call to EigenLayer's AVSDirectory contract to confirm operator deregistration from the AVS + * @notice Forwards the call to deregister an operator from the AVSDirectory contract. + * @dev This internal function is a proxy to the `deregisterOperatorFromAVS` function of the AVSDirectory contract. * @param operator The address of the operator to deregister. */ - function deregisterOperatorFromAVS( - address operator - ) public virtual onlyStakeRegistry { - elAvsDirectory.deregisterOperatorFromAVS(operator); + function _deregisterOperatorFromAVS(address operator) internal virtual { + IAVSDirectory(avsDirectory).deregisterOperatorFromAVS(operator); emit OperatorDeregisteredFromAVS(operator); } /** - * @notice Freezes an operator and their stake from Eigenlayer - * @param operator The address of the operator to freeze. + * @notice Processes a batch of range payments by transferring the specified amounts from the sender to this contract and then approving the PaymentCoordinator to use these amounts. + * @dev This function handles the transfer and approval of tokens necessary for range payments. It then delegates the actual payment logic to the PaymentCoordinator contract. + * @param rangePayments An array of `RangePayment` structs, each representing a payment for a specific range. */ - function freezeOperator(address operator) public virtual onlyOwner { - slasher.freezeOperator(operator); - } + function _payForRange( + IPaymentCoordinator.RangePayment[] calldata rangePayments + ) internal virtual { + for (uint256 i = 0; i < rangePayments.length; ++i) { + rangePayments[i].token.transferFrom( + msg.sender, + address(this), + rangePayments[i].amount + ); + rangePayments[i].token.approve( + paymentCoordinator, + rangePayments[i].amount + ); + } - // ============ Internal Function ============ + IPaymentCoordinator(paymentCoordinator).payForRange(rangePayments); + } + /** + * @notice Retrieves the addresses of all strategies that are part of the current quorum. + * @dev Fetches the quorum configuration from the ECDSAStakeRegistry and extracts the strategy addresses. + * @return strategies An array of addresses representing the strategies in the current quorum. + */ function _getRestakeableStrategies() internal view + virtual returns (address[] memory) { - uint256 strategyCount = stakeRegistry.quorum().strategies.length; + Quorum memory quorum = IECDSAStakeRegistry(stakeRegistry).quorum(); + address[] memory strategies = new address[](quorum.strategies.length); + for (uint256 i = 0; i < quorum.strategies.length; i++) { + strategies[i] = address(quorum.strategies[i].strategy); + } + return strategies; + } - if (strategyCount == 0) { - return new address[](0); + /** + * @notice Retrieves the addresses of strategies where the operator has restaked. + * @dev This function fetches the quorum details from the ECDSAStakeRegistry, retrieves the operator's shares for each strategy, + * and filters out strategies with non-zero shares indicating active restaking by the operator. + * @param _operator The address of the operator whose restaked strategies are to be retrieved. + * @return restakedStrategies An array of addresses of strategies where the operator has active restakes. + */ + function _getOperatorRestakedStrategies( + address _operator + ) internal view virtual returns (address[] memory) { + Quorum memory quorum = IECDSAStakeRegistry(stakeRegistry).quorum(); + uint256 count = quorum.strategies.length; + IStrategy[] memory strategies = new IStrategy[](count); + for (uint256 i; i < count; i++) { + strategies[i] = quorum.strategies[i].strategy; } + uint256[] memory shares = IDelegationManager(delegationManager) + .getOperatorShares(_operator, strategies); - address[] memory restakedStrategies = new address[](strategyCount); - for (uint256 i = 0; i < strategyCount; i++) { - restakedStrategies[i] = address( - stakeRegistry.quorum().strategies[i].strategy - ); + address[] memory activeStrategies = new address[](count); + uint256 activeCount; + for (uint256 i; i < count; i++) { + if (shares[i] > 0) { + activeCount++; + } + } + + // Resize the array to fit only the active strategies + address[] memory restakedStrategies = new address[](activeCount); + for (uint256 j = 0; j < count; j++) { + if (shares[j] > 0) { + restakedStrategies[j] = activeStrategies[j]; + } } + return restakedStrategies; } diff --git a/solidity/contracts/avs/HyperlaneServiceManager.sol b/solidity/contracts/avs/HyperlaneServiceManager.sol index 2a8857f88f..e955185b64 100644 --- a/solidity/contracts/avs/HyperlaneServiceManager.sol +++ b/solidity/contracts/avs/HyperlaneServiceManager.sol @@ -28,6 +28,12 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { using EnumerableMapEnrollment for EnumerableMapEnrollment.AddressToEnrollmentMap; + // ============ Public Storage ============ + + // Slasher contract responsible for slashing operators + // @dev slasher needs to be updated once slashing is implemented + ISlasher internal slasher; + // ============ Events ============ /** @@ -87,15 +93,19 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { // ============ Constructor ============ constructor( - IAVSDirectory _avsDirectory, - IECDSAStakeRegistry _stakeRegistry, - ISlasher _slasher - ) ECDSAServiceManagerBase(_avsDirectory, _stakeRegistry, _slasher) { - __ServiceManagerBase_init(msg.sender); - _disableInitializers(); - } - - function initialize() external initializer { + address _avsDirectory, + address _stakeRegistry, + address _paymentCoordinator, + address _delegationManager + ) + ECDSAServiceManagerBase( + _avsDirectory, + _stakeRegistry, + _paymentCoordinator, + _delegationManager + ) + initializer + { __ServiceManagerBase_init(msg.sender); } @@ -156,39 +166,21 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { /** * @notice Completes the unenrollment of an operator from a list of challengers - * @param operator The address of the operator * @param _challengers The list of challengers to unenroll from */ function completeUnenrollment( - address operator, IRemoteChallenger[] memory _challengers - ) public onlyStakeRegistryOrOperator(operator) { - for (uint256 i = 0; i < _challengers.length; i++) { - IRemoteChallenger challenger = _challengers[i]; - (bool exists, Enrollment memory enrollment) = enrolledChallengers[ - operator - ].tryGet(address(challenger)); - - require( - exists && - enrollment.status == - EnrollmentStatus.PENDING_UNENROLLMENT && - block.number >= - enrollment.unenrollmentStartBlock + - challenger.challengeDelayBlocks(), - "HyperlaneServiceManager: Invalid unenrollment" - ); - - enrolledChallengers[operator].remove(address(challenger)); - emit OperatorUnenrolledFromChallenger( - operator, - challenger, - block.number - ); - } + ) external { + _completeUnenrollment(msg.sender, _challengers); } - // ============ Public Functions ============ + /** + * @notice Sets the slasher contract responsible for slashing operators + * @param _slasher The address of the slasher contract + */ + function setSlasher(ISlasher _slasher) external onlyOwner { + slasher = _slasher; + } /** * @notice returns the status of a challenger an operator is enrolled in @@ -202,6 +194,19 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { return enrolledChallengers[_operator].get(address(_challenger)); } + /** + * @notice forwards a call to the Slasher contract to freeze an operator + * @param operator The address of the operator to freeze. + * @dev only the enrolled challengers can call this function + */ + function freezeOperator( + address operator + ) external virtual onlyEnrolledChallenger(operator) { + slasher.freezeOperator(operator); + } + + // ============ Public Functions ============ + /** * @notice returns the list of challengers an operator is enrolled in * @param _operator The address of the operator @@ -220,32 +225,52 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { return challengers; } + // ============ Internal Functions ============ + /** - * @notice forwards a call to the Slasher contract to freeze an operator - * @param operator The address of the operator to freeze. - * @dev only the enrolled challengers can call this function + * @notice Completes the unenrollment of an operator from a list of challengers + * @param operator The address of the operator + * @param _challengers The list of challengers to unenroll from */ - function freezeOperator( - address operator - ) public virtual override onlyEnrolledChallenger(operator) { - slasher.freezeOperator(operator); - } + function _completeUnenrollment( + address operator, + IRemoteChallenger[] memory _challengers + ) internal { + for (uint256 i = 0; i < _challengers.length; i++) { + IRemoteChallenger challenger = _challengers[i]; + (bool exists, Enrollment memory enrollment) = enrolledChallengers[ + operator + ].tryGet(address(challenger)); - // ============ Public Functions ============ + require( + exists && + enrollment.status == + EnrollmentStatus.PENDING_UNENROLLMENT && + block.number >= + enrollment.unenrollmentStartBlock + + challenger.challengeDelayBlocks(), + "HyperlaneServiceManager: Invalid unenrollment" + ); - /** - * @notice Forwards a call to EigenLayer's AVSDirectory contract to confirm operator deregistration from the AVS - * @param operator The address of the operator to deregister. - */ - function deregisterOperatorFromAVS( + enrolledChallengers[operator].remove(address(challenger)); + emit OperatorUnenrolledFromChallenger( + operator, + challenger, + block.number + ); + } + } + + /// @inheritdoc ECDSAServiceManagerBase + function _deregisterOperatorFromAVS( address operator - ) public virtual override onlyStakeRegistry { + ) internal virtual override { IRemoteChallenger[] memory challengers = getOperatorChallengers( operator ); - completeUnenrollment(operator, challengers); + _completeUnenrollment(operator, challengers); - elAvsDirectory.deregisterOperatorFromAVS(operator); + IAVSDirectory(avsDirectory).deregisterOperatorFromAVS(operator); emit OperatorDeregisteredFromAVS(operator); } } diff --git a/solidity/contracts/interfaces/avs/IDelegationManager.sol b/solidity/contracts/interfaces/avs/IDelegationManager.sol index ee33cd2b3e..b75785a260 100644 --- a/solidity/contracts/interfaces/avs/IDelegationManager.sol +++ b/solidity/contracts/interfaces/avs/IDelegationManager.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity >=0.8.0; +import {IStrategy} from "./IStrategy.sol"; + /// part of mock interfaces for vendoring necessary Eigenlayer contracts for the hyperlane AVS /// @author Layr Labs, Inc. @@ -15,4 +17,9 @@ interface IDelegationManager { OperatorDetails calldata registeringOperatorDetails, string calldata metadataURI ) external; + + function getOperatorShares( + address operator, + IStrategy[] memory strategies + ) external view returns (uint256[] memory); } diff --git a/solidity/contracts/interfaces/avs/IPaymentCoordinator.sol b/solidity/contracts/interfaces/avs/IPaymentCoordinator.sol new file mode 100644 index 0000000000..818b84dc73 --- /dev/null +++ b/solidity/contracts/interfaces/avs/IPaymentCoordinator.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity >=0.8.0; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "./IStrategy.sol"; + +/** + * @title Interface for the `IPaymentCoordinator` contract. + * @author Layr Labs, Inc. + * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service + * @notice Allows AVSs to make "Range Payments", which get distributed amongst the AVSs' confirmed + * Operators and the Stakers delegated to those Operators. + * Calculations are performed based on the completed Range Payments, with the results posted in + * a Merkle root against which Stakers & Operators can make claims. + */ +interface IPaymentCoordinator { + /// STRUCTS /// + struct StrategyAndMultiplier { + IStrategy strategy; + // weight used to compare shares in multiple strategies against one another + uint96 multiplier; + } + + struct RangePayment { + // Strategies & relative weights of shares in the strategies + StrategyAndMultiplier[] strategiesAndMultipliers; + IERC20 token; + uint256 amount; + uint64 startTimestamp; + uint64 duration; + } + + /// EXTERNAL FUNCTIONS /// + + /** + * @notice Creates a new range payment on behalf of an AVS, to be split amongst the + * set of stakers delegated to operators who are registered to the `avs` + * @param rangePayments The range payments being created + * @dev Expected to be called by the ServiceManager of the AVS on behalf of which the payment is being made + * @dev The duration of the `rangePayment` cannot exceed `MAX_PAYMENT_DURATION` + * @dev The tokens are sent to the `claimingManager` contract + * @dev This function will revert if the `rangePayment` is malformed, + * e.g. if the `strategies` and `weights` arrays are of non-equal lengths + */ + function payForRange(RangePayment[] calldata rangePayments) external; +} diff --git a/solidity/contracts/interfaces/avs/IServiceManager.sol b/solidity/contracts/interfaces/avs/IServiceManager.sol index 64ec580739..2393fc6706 100644 --- a/solidity/contracts/interfaces/avs/IServiceManager.sol +++ b/solidity/contracts/interfaces/avs/IServiceManager.sol @@ -1,56 +1,28 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity >=0.5.0; +pragma solidity >=0.8.0; -import {ISignatureUtils} from "./ISignatureUtils.sol"; - -/// part of mock interfaces for vendoring necessary Eigenlayer contracts for the hyperlane AVS -/// @author Layr Labs, Inc. - -interface IServiceManager { - /** - * @notice Updates the metadata URI for the AVS - * @param _metadataURI is the metadata URI for the AVS - */ - function updateAVSMetadataURI(string memory _metadataURI) external; +import {IPaymentCoordinator} from "./IPaymentCoordinator.sol"; +import {IServiceManagerUI} from "./IServiceManagerUI.sol"; +/** + * @title Minimal interface for a ServiceManager-type contract that forms the single point for an AVS to push updates to EigenLayer + * @author Layr Labs, Inc. + */ +interface IServiceManager is IServiceManagerUI { /** - * @notice Forwards a call to EigenLayer's DelegationManager contract to confirm operator registration with the AVS - * @param operator The address of the operator to register. - * @param operatorSignature The signature, salt, and expiry of the operator's signature. + * @notice Creates a new range payment on behalf of an AVS, to be split amongst the + * set of stakers delegated to operators who are registered to the `avs`. + * Note that the owner calling this function must have approved the tokens to be transferred to the ServiceManager + * and of course has the required balances. + * @param rangePayments The range payments being created + * @dev Expected to be called by the ServiceManager of the AVS on behalf of which the payment is being made + * @dev The duration of the `rangePayment` cannot exceed `paymentCoordinator.MAX_PAYMENT_DURATION()` + * @dev The tokens are sent to the `PaymentCoordinator` contract + * @dev Strategies must be in ascending order of addresses to check for duplicates + * @dev This function will revert if the `rangePayment` is malformed, + * e.g. if the `strategies` and `weights` arrays are of non-equal lengths */ - function registerOperatorToAVS( - address operator, - ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature + function payForRange( + IPaymentCoordinator.RangePayment[] calldata rangePayments ) external; - - /** - * @notice Forwards a call to EigenLayer's DelegationManager contract to confirm operator deregistration from the AVS - * @param operator The address of the operator to deregister. - */ - function deregisterOperatorFromAVS(address operator) external; - - /** - * @notice Returns the list of strategies that the operator has potentially restaked on the AVS - * @param operator The address of the operator to get restaked strategies for - * @dev This function is intended to be called off-chain - * @dev No guarantee is made on whether the operator has shares for a strategy in a quorum or uniqueness - * of each element in the returned array. The off-chain service should do that validation separately - */ - function getOperatorRestakedStrategies( - address operator - ) external view returns (address[] memory); - - /** - * @notice Returns the list of strategies that the AVS supports for restaking - * @dev This function is intended to be called off-chain - * @dev No guarantee is made on uniqueness of each element in the returned array. - * The off-chain service should do that validation separately - */ - function getRestakeableStrategies() - external - view - returns (address[] memory); - - /// @notice Returns the EigenLayer AVSDirectory contract. - function avsDirectory() external view returns (address); } diff --git a/solidity/contracts/interfaces/avs/IServiceManagerUI.sol b/solidity/contracts/interfaces/avs/IServiceManagerUI.sol new file mode 100644 index 0000000000..8df18a409c --- /dev/null +++ b/solidity/contracts/interfaces/avs/IServiceManagerUI.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity >=0.8.0; + +import {ISignatureUtils} from "./ISignatureUtils.sol"; +import {IDelegationManager} from "./IDelegationManager.sol"; + +/** + * @title Minimal interface for a ServiceManager-type contract that AVS ServiceManager contracts must implement + * for eigenlabs to be able to index their data on the AVS marketplace frontend. + * @author Layr Labs, Inc. + */ +interface IServiceManagerUI { + /** + * Metadata should follow the format outlined by this example. + * { + * "name": "EigenLabs AVS 1", + * "website": "https://www.eigenlayer.xyz/", + * "description": "This is my 1st AVS", + * "logo": "https://holesky-operator-metadata.s3.amazonaws.com/eigenlayer.png", + * "twitter": "https://twitter.com/eigenlayer" + * } + * @notice Updates the metadata URI for the AVS + * @param _metadataURI is the metadata URI for the AVS + */ + function updateAVSMetadataURI(string memory _metadataURI) external; + + /** + * @notice Forwards a call to EigenLayer's DelegationManager contract to confirm operator registration with the AVS + * @param operator The address of the operator to register. + * @param operatorSignature The signature, salt, and expiry of the operator's signature. + */ + function registerOperatorToAVS( + address operator, + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature + ) external; + + /** + * @notice Forwards a call to EigenLayer's DelegationManager contract to confirm operator deregistration from the AVS + * @param operator The address of the operator to deregister. + */ + function deregisterOperatorFromAVS(address operator) external; + + /** + * @notice Returns the list of strategies that the operator has potentially restaked on the AVS + * @param operator The address of the operator to get restaked strategies for + * @dev This function is intended to be called off-chain + * @dev No guarantee is made on whether the operator has shares for a strategy in a quorum or uniqueness + * of each element in the returned array. The off-chain service should do that validation separately + */ + function getOperatorRestakedStrategies( + address operator + ) external view returns (address[] memory); + + /** + * @notice Returns the list of strategies that the AVS supports for restaking + * @dev This function is intended to be called off-chain + * @dev No guarantee is made on uniqueness of each element in the returned array. + * The off-chain service should do that validation separately + */ + function getRestakeableStrategies() + external + view + returns (address[] memory); + + /// @notice Returns the EigenLayer AVSDirectory contract. + function avsDirectory() external view returns (address); +} diff --git a/solidity/contracts/test/TestEigenlayer.sol b/solidity/contracts/test/avs/TestAVSDirectory.sol similarity index 60% rename from solidity/contracts/test/TestEigenlayer.sol rename to solidity/contracts/test/avs/TestAVSDirectory.sol index 9c00ed26b4..2e22ae9048 100644 --- a/solidity/contracts/test/TestEigenlayer.sol +++ b/solidity/contracts/test/avs/TestAVSDirectory.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.8.0; -import {IAVSDirectory} from "../interfaces/avs/IAVSDirectory.sol"; -import {ISignatureUtils} from "../interfaces/avs/ISignatureUtils.sol"; -import {IDelegationManager} from "../interfaces/avs/IDelegationManager.sol"; -import {ISlasher} from "../interfaces/avs/ISlasher.sol"; -import {Quorum, IECDSAStakeRegistry} from "../interfaces/avs/IECDSAStakeRegistry.sol"; - +import {IAVSDirectory} from "../../interfaces/avs/IAVSDirectory.sol"; +import {ISignatureUtils} from "../../interfaces/avs/ISignatureUtils.sol"; +import {ISlasher} from "../../interfaces/avs/ISlasher.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; contract TestAVSDirectory is IAVSDirectory { @@ -87,55 +84,3 @@ contract TestAVSDirectory is IAVSDirectory { ); } } - -contract TestDelegationManager is IDelegationManager { - mapping(address => bool) public isOperator; - - function registerAsOperator( - OperatorDetails calldata registeringOperatorDetails, - string calldata metadataURI - ) external {} - - function setIsOperator( - address operator, - bool _isOperatorReturnValue - ) external { - isOperator[operator] = _isOperatorReturnValue; - } -} - -contract TestSlasher is ISlasher { - function freezeOperator(address toBeFrozen) external {} -} - -import {IServiceManager} from "../interfaces/avs/IServiceManager.sol"; -import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; - -contract TestECDSAStakeRegistry is IECDSAStakeRegistry { - Quorum internal _quorum; - address internal _serviceManager; - - function initialize( - address serviceManager, - uint256, - Quorum memory - ) external { - _serviceManager = serviceManager; - } - - function quorum() external view returns (Quorum memory) {} - - function registerOperatorWithSignature( - address _operator, - ISignatureUtils.SignatureWithSaltAndExpiry memory _operatorSignature - ) external { - IServiceManager(_serviceManager).registerOperatorToAVS( - _operator, - _operatorSignature - ); - } - - function deregisterOperator() external { - IServiceManager(_serviceManager).deregisterOperatorFromAVS(msg.sender); - } -} diff --git a/solidity/contracts/test/avs/TestDelegationManager.sol b/solidity/contracts/test/avs/TestDelegationManager.sol new file mode 100644 index 0000000000..cbd50fbc6c --- /dev/null +++ b/solidity/contracts/test/avs/TestDelegationManager.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +import {IDelegationManager} from "../../interfaces/avs/IDelegationManager.sol"; +import {IStrategy} from "../../interfaces/avs/IStrategy.sol"; + +contract TestDelegationManager is IDelegationManager { + mapping(address => bool) public isOperator; + mapping(address => mapping(IStrategy => uint256)) public operatorShares; + + function registerAsOperator( + OperatorDetails calldata registeringOperatorDetails, + string calldata metadataURI + ) external {} + + function setIsOperator( + address operator, + bool _isOperatorReturnValue + ) external { + isOperator[operator] = _isOperatorReturnValue; + } + + function getOperatorShares( + address operator, + IStrategy[] memory strategies + ) public view returns (uint256[] memory) { + uint256[] memory shares = new uint256[](strategies.length); + for (uint256 i = 0; i < strategies.length; ++i) { + shares[i] = operatorShares[operator][strategies[i]]; + } + return shares; + } +} diff --git a/solidity/contracts/test/avs/TestECDSAStakeRegistry.sol b/solidity/contracts/test/avs/TestECDSAStakeRegistry.sol new file mode 100644 index 0000000000..7ee80ef566 --- /dev/null +++ b/solidity/contracts/test/avs/TestECDSAStakeRegistry.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +import {ISignatureUtils} from "../../interfaces/avs/ISignatureUtils.sol"; +import {Quorum, IECDSAStakeRegistry} from "../../interfaces/avs/IECDSAStakeRegistry.sol"; +import {IServiceManager} from "../../interfaces/avs/IServiceManager.sol"; +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + +contract TestECDSAStakeRegistry is IECDSAStakeRegistry { + Quorum internal _quorum; + address internal _serviceManager; + + function initialize( + address serviceManager, + uint256, + Quorum memory + ) external { + _serviceManager = serviceManager; + } + + function quorum() external view returns (Quorum memory) {} + + function registerOperatorWithSignature( + address _operator, + ISignatureUtils.SignatureWithSaltAndExpiry memory _operatorSignature + ) external { + IServiceManager(_serviceManager).registerOperatorToAVS( + _operator, + _operatorSignature + ); + } + + function deregisterOperator() external { + IServiceManager(_serviceManager).deregisterOperatorFromAVS(msg.sender); + } +} diff --git a/solidity/contracts/test/avs/TestPaymentCoordinator.sol b/solidity/contracts/test/avs/TestPaymentCoordinator.sol new file mode 100644 index 0000000000..e86c6a0b99 --- /dev/null +++ b/solidity/contracts/test/avs/TestPaymentCoordinator.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +import {IPaymentCoordinator} from "../../interfaces/avs/IPaymentCoordinator.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +contract TestPaymentCoordinator is IPaymentCoordinator { + using SafeERC20 for IERC20; + + function payForRange(RangePayment[] calldata rangePayments) external { + for (uint256 i = 0; i < rangePayments.length; i++) { + rangePayments[i].token.safeTransferFrom( + msg.sender, + address(this), + rangePayments[i].amount + ); + } + } +} diff --git a/solidity/contracts/test/avs/TestSlasher.sol b/solidity/contracts/test/avs/TestSlasher.sol new file mode 100644 index 0000000000..ee9f56039f --- /dev/null +++ b/solidity/contracts/test/avs/TestSlasher.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +import {ISlasher} from "../../interfaces/avs/ISlasher.sol"; + +contract TestSlasher is ISlasher { + function freezeOperator(address toBeFrozen) external {} +} diff --git a/solidity/remappings.txt b/solidity/remappings.txt index ae7bb51d69..02eba29477 100644 --- a/solidity/remappings.txt +++ b/solidity/remappings.txt @@ -1,6 +1,5 @@ @openzeppelin=../node_modules/@openzeppelin @openzeppelin/contracts-upgradeable/=../node_modules/@openzeppelin/contracts-upgradeable/ -@openzeppelin/contracts/=lib/eigenlayer-middleware/lib/openzeppelin-contracts/contracts/ @layerzerolabs=../node_modules/@layerzerolabs @eth-optimism=../node_modules/@eth-optimism ds-test/=lib/forge-std/lib/ds-test/src/ diff --git a/solidity/test/avs/EigenlayerBase.sol b/solidity/test/avs/EigenlayerBase.sol index 3bbcf1b702..c89345c9e3 100644 --- a/solidity/test/avs/EigenlayerBase.sol +++ b/solidity/test/avs/EigenlayerBase.sol @@ -4,7 +4,9 @@ pragma solidity >=0.8.0; import "forge-std/Test.sol"; import {ISlasher} from "../../contracts/interfaces/avs/ISlasher.sol"; -import {TestAVSDirectory, TestDelegationManager, TestSlasher, TestDelegationManager} from "../../contracts/test/TestEigenlayer.sol"; +import {TestAVSDirectory} from "../../contracts/test/avs/TestAVSDirectory.sol"; +import {TestDelegationManager} from "../../contracts/test/avs/TestDelegationManager.sol"; +import {TestSlasher} from "../../contracts/test/avs/TestSlasher.sol"; contract EigenlayerBase is Test { TestAVSDirectory internal avsDirectory; diff --git a/solidity/test/avs/HyperlaneServiceManager.t.sol b/solidity/test/avs/HyperlaneServiceManager.t.sol index 64e3cb92e9..5f79667c2d 100644 --- a/solidity/test/avs/HyperlaneServiceManager.t.sol +++ b/solidity/test/avs/HyperlaneServiceManager.t.sol @@ -6,7 +6,9 @@ import {ISlasher} from "../../contracts/interfaces/avs/ISlasher.sol"; import {IAVSDirectory} from "../../contracts/interfaces/avs/IAVSDirectory.sol"; import {Quorum, StrategyParams, IECDSAStakeRegistry} from "../../contracts/interfaces/avs/IECDSAStakeRegistry.sol"; -import {TestECDSAStakeRegistry, TestDelegationManager} from "../../contracts/test/TestEigenlayer.sol"; +import {TestDelegationManager} from "../../contracts/test/avs/TestDelegationManager.sol"; +import {TestECDSAStakeRegistry} from "../../contracts/test/avs/TestECDSAStakeRegistry.sol"; +import {TestPaymentCoordinator} from "../../contracts/test/avs/TestPaymentCoordinator.sol"; import {IStrategy} from "../../contracts/interfaces/avs/IStrategy.sol"; import {ISignatureUtils} from "../../contracts/interfaces/avs/ISignatureUtils.sol"; @@ -21,6 +23,7 @@ import {EigenlayerBase} from "./EigenlayerBase.sol"; contract HyperlaneServiceManagerTest is EigenlayerBase { HyperlaneServiceManager internal _hsm; TestECDSAStakeRegistry internal _ecdsaStakeRegistry; + TestPaymentCoordinator internal _paymentCoordinator; // Operator info uint256 operatorPrivateKey = 0xdeadbeef; @@ -35,11 +38,15 @@ contract HyperlaneServiceManagerTest is EigenlayerBase { _deployMockEigenLayerAndAVS(); _ecdsaStakeRegistry = new TestECDSAStakeRegistry(); + _paymentCoordinator = new TestPaymentCoordinator(); + _hsm = new HyperlaneServiceManager( - avsDirectory, - _ecdsaStakeRegistry, - slasher + address(avsDirectory), + address(_ecdsaStakeRegistry), + address(_paymentCoordinator), + address(delegationManager) ); + _hsm.setSlasher(slasher); IStrategy mockStrategy = IStrategy(address(0x1234)); Quorum memory quorum = Quorum({strategies: new StrategyParams[](1)}); @@ -231,35 +238,6 @@ contract HyperlaneServiceManagerTest is EigenlayerBase { vm.stopPrank(); } - /// forge-config: default.fuzz.runs = 10 - function testFuzz_completeQueuedUnenrollmentFromChallenger_revert_unauthorized( - uint8 numOfChallengers - ) public { - vm.assume(numOfChallengers > 0); - - _registerOperator(); - IRemoteChallenger[] memory challengers = _deployChallengers( - numOfChallengers - ); - - vm.startPrank(operator); - _hsm.enrollIntoChallengers(challengers); - _hsm.startUnenrollment(challengers); - vm.stopPrank(); - - vm.roll(block.number + challengeDelayBlocks); - - vm.expectRevert( - "ECDSAServiceManagerBase: caller is not the stake registry or operator" - ); - _hsm.completeUnenrollment(operator, challengers); - - vm.prank(address(_ecdsaStakeRegistry)); - _hsm.completeUnenrollment(operator, challengers); - - assertEq(_hsm.getOperatorChallengers(operator).length, 0); - } - /// forge-config: default.fuzz.runs = 10 function testFuzz_completeQueuedUnenrollmentFromChallenger( uint8 numOfChallengers, @@ -290,11 +268,11 @@ contract HyperlaneServiceManagerTest is EigenlayerBase { ); vm.expectRevert(); - _hsm.completeUnenrollment(operator, unenrollableChallengers); + _hsm.completeUnenrollment(unenrollableChallengers); vm.roll(block.number + challengeDelayBlocks); - _hsm.completeUnenrollment(operator, unenrollableChallengers); + _hsm.completeUnenrollment(unenrollableChallengers); assertEq( _hsm.getOperatorChallengers(operator).length, @@ -363,7 +341,7 @@ contract HyperlaneServiceManagerTest is EigenlayerBase { _hsm.startUnenrollment(challengers); vm.roll(block.number + challengeDelayBlocks); - _hsm.completeUnenrollment(operator, unenrollableChallengers); + _hsm.completeUnenrollment(unenrollableChallengers); for (uint256 i = 0; i < unenrollableChallengers.length; i++) { vm.expectRevert( From df4a172821036cf8b4b1f0d8578f0d7b2010e866 Mon Sep 17 00:00:00 2001 From: -f Date: Mon, 6 May 2024 17:13:22 +0530 Subject: [PATCH 21/32] rm remapping upgrade --- solidity/remappings.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/solidity/remappings.txt b/solidity/remappings.txt index 02eba29477..1492dd8d72 100644 --- a/solidity/remappings.txt +++ b/solidity/remappings.txt @@ -1,5 +1,4 @@ @openzeppelin=../node_modules/@openzeppelin -@openzeppelin/contracts-upgradeable/=../node_modules/@openzeppelin/contracts-upgradeable/ @layerzerolabs=../node_modules/@layerzerolabs @eth-optimism=../node_modules/@eth-optimism ds-test/=lib/forge-std/lib/ds-test/src/ From f0450454e1c5e1a91d8c5752e38c9d9b9a2629e1 Mon Sep 17 00:00:00 2001 From: -f Date: Mon, 6 May 2024 19:07:01 +0530 Subject: [PATCH 22/32] adding el core addresses --- .../contracts/avs/ECDSAServiceManagerBase.sol | 7 +- solidity/contracts/avs/ECDSAStakeRegistry.sol | 535 ++++++++++++++++++ .../avs/ECDSAStakeRegistryStorage.sol | 54 ++ .../contracts/avs/HyperlaneServiceManager.sol | 3 - .../interfaces/avs/IECDSAStakeRegistry.sol | 20 - .../IECDSAStakeRegistryEventsAndErrors.sol | 100 ++++ .../test/avs/TestECDSAStakeRegistry.sol | 36 -- solidity/foundry.toml | 1 + solidity/script/DeployAVS.s.sol | 59 ++ solidity/script/eigenlayer_addresses.json | 14 + .../test/avs/HyperlaneServiceManager.t.sol | 8 +- 11 files changed, 771 insertions(+), 66 deletions(-) create mode 100644 solidity/contracts/avs/ECDSAStakeRegistry.sol create mode 100644 solidity/contracts/avs/ECDSAStakeRegistryStorage.sol delete mode 100644 solidity/contracts/interfaces/avs/IECDSAStakeRegistry.sol create mode 100644 solidity/contracts/interfaces/avs/IECDSAStakeRegistryEventsAndErrors.sol delete mode 100644 solidity/contracts/test/avs/TestECDSAStakeRegistry.sol create mode 100644 solidity/script/DeployAVS.s.sol create mode 100644 solidity/script/eigenlayer_addresses.json diff --git a/solidity/contracts/avs/ECDSAServiceManagerBase.sol b/solidity/contracts/avs/ECDSAServiceManagerBase.sol index 76074067e5..bd37af234b 100644 --- a/solidity/contracts/avs/ECDSAServiceManagerBase.sol +++ b/solidity/contracts/avs/ECDSAServiceManagerBase.sol @@ -9,7 +9,8 @@ import {IServiceManagerUI} from "../interfaces/avs/IServiceManagerUI.sol"; import {IDelegationManager} from "../interfaces/avs/IDelegationManager.sol"; import {IStrategy} from "../interfaces/avs/IStrategy.sol"; import {IPaymentCoordinator} from "../interfaces/avs/IPaymentCoordinator.sol"; -import {Quorum, IECDSAStakeRegistry} from "../interfaces/avs/IECDSAStakeRegistry.sol"; +import {Quorum} from "../interfaces/avs/IECDSAStakeRegistryEventsAndErrors.sol"; +import {ECDSAStakeRegistry} from "./ECDSAStakeRegistry.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; @@ -206,7 +207,7 @@ abstract contract ECDSAServiceManagerBase is virtual returns (address[] memory) { - Quorum memory quorum = IECDSAStakeRegistry(stakeRegistry).quorum(); + Quorum memory quorum = ECDSAStakeRegistry(stakeRegistry).quorum(); address[] memory strategies = new address[](quorum.strategies.length); for (uint256 i = 0; i < quorum.strategies.length; i++) { strategies[i] = address(quorum.strategies[i].strategy); @@ -224,7 +225,7 @@ abstract contract ECDSAServiceManagerBase is function _getOperatorRestakedStrategies( address _operator ) internal view virtual returns (address[] memory) { - Quorum memory quorum = IECDSAStakeRegistry(stakeRegistry).quorum(); + Quorum memory quorum = ECDSAStakeRegistry(stakeRegistry).quorum(); uint256 count = quorum.strategies.length; IStrategy[] memory strategies = new IStrategy[](count); for (uint256 i; i < count; i++) { diff --git a/solidity/contracts/avs/ECDSAStakeRegistry.sol b/solidity/contracts/avs/ECDSAStakeRegistry.sol new file mode 100644 index 0000000000..c5c915c5d3 --- /dev/null +++ b/solidity/contracts/avs/ECDSAStakeRegistry.sol @@ -0,0 +1,535 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import {ECDSAStakeRegistryStorage, Quorum, StrategyParams} from "./ECDSAStakeRegistryStorage.sol"; +import {IStrategy} from "../interfaces/avs/IStrategy.sol"; +import {IDelegationManager} from "../interfaces/avs/IDelegationManager.sol"; +import {ISignatureUtils} from "../interfaces/avs/ISignatureUtils.sol"; +import {IServiceManager} from "../interfaces/avs/IServiceManager.sol"; + +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {CheckpointsUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/CheckpointsUpgradeable.sol"; +import {SignatureCheckerUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/SignatureCheckerUpgradeable.sol"; +import {IERC1271Upgradeable} from "@openzeppelin/contracts-upgradeable/interfaces/IERC1271Upgradeable.sol"; + +/// @title ECDSA Stake Registry +/// @dev THIS CONTRACT IS NOT AUDITED +/// @notice Manages operator registration and quorum updates for an AVS using ECDSA signatures. +contract ECDSAStakeRegistry is + IERC1271Upgradeable, + OwnableUpgradeable, + ECDSAStakeRegistryStorage +{ + using SignatureCheckerUpgradeable for address; + using CheckpointsUpgradeable for CheckpointsUpgradeable.History; + + /// @dev Constructor to create ECDSAStakeRegistry. + /// @param _delegationManager Address of the DelegationManager contract that this registry interacts with. + constructor( + IDelegationManager _delegationManager + ) ECDSAStakeRegistryStorage(_delegationManager) { + // _disableInitializers(); + } + + /// @notice Initializes the contract with the given parameters. + /// @param _serviceManager The address of the service manager. + /// @param _thresholdWeight The threshold weight in basis points. + /// @param _quorum The quorum struct containing the details of the quorum thresholds. + function initialize( + address _serviceManager, + uint256 _thresholdWeight, + Quorum memory _quorum + ) external initializer { + __ECDSAStakeRegistry_init(_serviceManager, _thresholdWeight, _quorum); + } + + /// @notice Registers a new operator using a provided signature + /// @param _operatorSignature Contains the operator's signature, salt, and expiry + function registerOperatorWithSignature( + address _operator, + ISignatureUtils.SignatureWithSaltAndExpiry memory _operatorSignature + ) external { + _registerOperatorWithSig(_operator, _operatorSignature); + } + + /// @notice Deregisters an existing operator + function deregisterOperator() external { + _deregisterOperator(msg.sender); + } + + /** + * @notice Updates the StakeRegistry's view of one or more operators' stakes adding a new entry in their history of stake checkpoints, + * @dev Queries stakes from the Eigenlayer core DelegationManager contract + * @param _operators A list of operator addresses to update + */ + function updateOperators(address[] memory _operators) external { + _updateOperators(_operators); + } + + /** + * @notice Updates the quorum configuration and the set of operators + * @dev Only callable by the contract owner. + * It first updates the quorum configuration and then updates the list of operators. + * @param _quorum The new quorum configuration, including strategies and their new weights + * @param _operators The list of operator addresses to update stakes for + */ + function updateQuorumConfig( + Quorum memory _quorum, + address[] memory _operators + ) external onlyOwner { + _updateQuorumConfig(_quorum); + _updateOperators(_operators); + } + + /// @notice Updates the weight an operator must have to join the operator set + /// @dev Access controlled to the contract owner + /// @param _newMinimumWeight The new weight an operator must have to join the operator set + function updateMinimumWeight( + uint256 _newMinimumWeight, + address[] memory _operators + ) external onlyOwner { + _updateMinimumWeight(_newMinimumWeight); + _updateOperators(_operators); + } + + /** + * @notice Sets a new cumulative threshold weight for message validation by operator set signatures. + * @dev This function can only be invoked by the owner of the contract. It delegates the update to + * an internal function `_updateStakeThreshold`. + * @param _thresholdWeight The updated threshold weight required to validate a message. This is the + * cumulative weight that must be met or exceeded by the sum of the stakes of the signatories for + * a message to be deemed valid. + */ + function updateStakeThreshold(uint256 _thresholdWeight) external onlyOwner { + _updateStakeThreshold(_thresholdWeight); + } + + /// @notice Verifies if the provided signature data is valid for the given data hash. + /// @param _dataHash The hash of the data that was signed. + /// @param _signatureData Encoded signature data consisting of an array of signers, an array of signatures, and a reference block number. + /// @return The function selector that indicates the signature is valid according to ERC1271 standard. + function isValidSignature( + bytes32 _dataHash, + bytes memory _signatureData + ) external view returns (bytes4) { + ( + address[] memory signers, + bytes[] memory signatures, + uint32 referenceBlock + ) = abi.decode(_signatureData, (address[], bytes[], uint32)); + _checkSignatures(_dataHash, signers, signatures, referenceBlock); + return IERC1271Upgradeable.isValidSignature.selector; + } + + /// @notice Retrieves the current stake quorum details. + /// @return Quorum - The current quorum of strategies and weights + function quorum() external view returns (Quorum memory) { + return _quorum; + } + + /// @notice Retrieves the last recorded weight for a given operator. + /// @param _operator The address of the operator. + /// @return uint256 - The latest weight of the operator. + function getLastCheckpointOperatorWeight( + address _operator + ) external view returns (uint256) { + return _operatorWeightHistory[_operator].latest(); + } + + /// @notice Retrieves the last recorded total weight across all operators. + /// @return uint256 - The latest total weight. + function getLastCheckpointTotalWeight() external view returns (uint256) { + return _totalWeightHistory.latest(); + } + + /// @notice Retrieves the last recorded threshold weight + /// @return uint256 - The latest threshold weight. + function getLastCheckpointThresholdWeight() + external + view + returns (uint256) + { + return _thresholdWeightHistory.latest(); + } + + /// @notice Retrieves the operator's weight at a specific block number. + /// @param _operator The address of the operator. + /// @param _blockNumber The block number to get the operator weight for the quorum + /// @return uint256 - The weight of the operator at the given block. + function getOperatorWeightAtBlock( + address _operator, + uint32 _blockNumber + ) external view returns (uint256) { + return _operatorWeightHistory[_operator].getAtBlock(_blockNumber); + } + + /// @notice Retrieves the total weight at a specific block number. + /// @param _blockNumber The block number to get the total weight for the quorum + /// @return uint256 - The total weight at the given block. + function getLastCheckpointTotalWeightAtBlock( + uint32 _blockNumber + ) external view returns (uint256) { + return _totalWeightHistory.getAtBlock(_blockNumber); + } + + /// @notice Retrieves the threshold weight at a specific block number. + /// @param _blockNumber The block number to get the threshold weight for the quorum + /// @return uint256 - The threshold weight the given block. + function getLastCheckpointThresholdWeightAtBlock( + uint32 _blockNumber + ) external view returns (uint256) { + return _thresholdWeightHistory.getAtBlock(_blockNumber); + } + + function operatorRegistered( + address _operator + ) external view returns (bool) { + return _operatorRegistered[_operator]; + } + + /// @notice Returns the weight an operator must have to contribute to validating an AVS + function minimumWeight() external view returns (uint256) { + return _minimumWeight; + } + + /// @notice Calculates the current weight of an operator based on their delegated stake in the strategies considered in the quorum + /// @param _operator The address of the operator. + /// @return uint256 - The current weight of the operator; returns 0 if below the threshold. + function getOperatorWeight( + address _operator + ) public view returns (uint256) { + StrategyParams[] memory strategyParams = _quorum.strategies; + uint256 weight; + IStrategy[] memory strategies = new IStrategy[](strategyParams.length); + for (uint256 i; i < strategyParams.length; i++) { + strategies[i] = strategyParams[i].strategy; + } + uint256[] memory shares = DELEGATION_MANAGER.getOperatorShares( + _operator, + strategies + ); + for (uint256 i; i < strategyParams.length; i++) { + weight += shares[i] * strategyParams[i].multiplier; + } + weight = weight / BPS; + + if (weight >= _minimumWeight) { + return weight; + } else { + return 0; + } + } + + /// @notice Initializes state for the StakeRegistry + /// @param _serviceManagerAddr The AVS' ServiceManager contract's address + function __ECDSAStakeRegistry_init( + address _serviceManagerAddr, + uint256 _thresholdWeight, + Quorum memory _quorum + ) internal onlyInitializing { + _serviceManager = _serviceManagerAddr; + _updateStakeThreshold(_thresholdWeight); + _updateQuorumConfig(_quorum); + __Ownable_init(); + } + + /// @notice Updates the set of operators for the first quorum. + /// @param operatorsPerQuorum An array of operator address arrays, one for each quorum. + /// @dev This interface maintains compatibility with avs-sync which handles multiquorums while this registry has a single quorum + function updateOperatorsForQuorum( + address[][] memory operatorsPerQuorum, + bytes memory + ) external { + _updateAllOperators(operatorsPerQuorum[0]); + } + + /// @dev Updates the list of operators if the provided list has the correct number of operators. + /// Reverts if the provided list of operators does not match the expected total count of operators. + /// @param _operators The list of operator addresses to update. + function _updateAllOperators(address[] memory _operators) internal { + if (_operators.length != _totalOperators) { + revert MustUpdateAllOperators(); + } + _updateOperators(_operators); + } + + /// @dev Updates the weights for a given list of operator addresses. + /// When passing an operator that isn't registered, then 0 is added to their history + /// @param _operators An array of addresses for which to update the weights. + function _updateOperators(address[] memory _operators) internal { + int256 delta; + for (uint256 i; i < _operators.length; i++) { + delta += _updateOperatorWeight(_operators[i]); + } + _updateTotalWeight(delta); + } + + /// @dev Updates the stake threshold weight and records the history. + /// @param _thresholdWeight The new threshold weight to set and record in the history. + function _updateStakeThreshold(uint256 _thresholdWeight) internal { + _thresholdWeightHistory.push(_thresholdWeight); + emit ThresholdWeightUpdated(_thresholdWeight); + } + + /// @dev Updates the weight an operator must have to join the operator set + /// @param _newMinimumWeight The new weight an operator must have to join the operator set + function _updateMinimumWeight(uint256 _newMinimumWeight) internal { + uint256 oldMinimumWeight = _minimumWeight; + _minimumWeight = _newMinimumWeight; + emit MinimumWeightUpdated(oldMinimumWeight, _newMinimumWeight); + } + + /// @notice Updates the quorum configuration + /// @dev Replaces the current quorum configuration with `_newQuorum` if valid. + /// Reverts with `InvalidQuorum` if the new quorum configuration is not valid. + /// Emits `QuorumUpdated` event with the old and new quorum configurations. + /// @param _newQuorum The new quorum configuration to set. + function _updateQuorumConfig(Quorum memory _newQuorum) internal { + if (!_isValidQuorum(_newQuorum)) { + revert InvalidQuorum(); + } + Quorum memory oldQuorum = _quorum; + delete _quorum; + for (uint256 i; i < _newQuorum.strategies.length; i++) { + _quorum.strategies.push(_newQuorum.strategies[i]); + } + emit QuorumUpdated(oldQuorum, _newQuorum); + } + + /// @dev Internal function to deregister an operator + /// @param _operator The operator's address to deregister + function _deregisterOperator(address _operator) internal { + if (!_operatorRegistered[_operator]) { + revert OperatorNotRegistered(); + } + _totalOperators--; + delete _operatorRegistered[_operator]; + int256 delta = _updateOperatorWeight(_operator); + _updateTotalWeight(delta); + IServiceManager(_serviceManager).deregisterOperatorFromAVS(_operator); + emit OperatorDeregistered(_operator, address(_serviceManager)); + } + + /// @dev registers an operator through a provided signature + /// @param _operatorSignature Contains the operator's signature, salt, and expiry + function _registerOperatorWithSig( + address _operator, + ISignatureUtils.SignatureWithSaltAndExpiry memory _operatorSignature + ) internal virtual { + if (_operatorRegistered[_operator]) { + revert OperatorAlreadyRegistered(); + } + _totalOperators++; + _operatorRegistered[_operator] = true; + int256 delta = _updateOperatorWeight(_operator); + _updateTotalWeight(delta); + IServiceManager(_serviceManager).registerOperatorToAVS( + _operator, + _operatorSignature + ); + emit OperatorRegistered(_operator, _serviceManager); + } + + /// @notice Updates the weight of an operator and returns the previous and current weights. + /// @param _operator The address of the operator to update the weight of. + function _updateOperatorWeight( + address _operator + ) internal virtual returns (int256) { + int256 delta; + uint256 newWeight; + uint256 oldWeight = _operatorWeightHistory[_operator].latest(); + if (!_operatorRegistered[_operator]) { + delta -= int256(oldWeight); + if (delta == 0) { + return delta; + } + _operatorWeightHistory[_operator].push(0); + } else { + newWeight = getOperatorWeight(_operator); + delta = int256(newWeight) - int256(oldWeight); + if (delta == 0) { + return delta; + } + _operatorWeightHistory[_operator].push(newWeight); + } + emit OperatorWeightUpdated(_operator, oldWeight, newWeight); + return delta; + } + + /// @dev Internal function to update the total weight of the stake + /// @param delta The change in stake applied last total weight + /// @return oldTotalWeight The weight before the update + /// @return newTotalWeight The updated weight after applying the delta + function _updateTotalWeight( + int256 delta + ) internal returns (uint256 oldTotalWeight, uint256 newTotalWeight) { + oldTotalWeight = _totalWeightHistory.latest(); + int256 newWeight = int256(oldTotalWeight) + delta; + newTotalWeight = uint256(newWeight); + _totalWeightHistory.push(newTotalWeight); + emit TotalWeightUpdated(oldTotalWeight, newTotalWeight); + } + + /** + * @dev Verifies that a specified quorum configuration is valid. A valid quorum has: + * 1. Weights that sum to exactly 10,000 basis points, ensuring proportional representation. + * 2. Unique strategies without duplicates to maintain quorum integrity. + * @param _quorum The quorum configuration to be validated. + * @return bool True if the quorum configuration is valid, otherwise false. + */ + function _isValidQuorum( + Quorum memory _quorum + ) internal pure returns (bool) { + StrategyParams[] memory strategies = _quorum.strategies; + address lastStrategy; + address currentStrategy; + uint256 totalMultiplier; + for (uint256 i; i < strategies.length; i++) { + currentStrategy = address(strategies[i].strategy); + if (lastStrategy >= currentStrategy) revert NotSorted(); + lastStrategy = currentStrategy; + totalMultiplier += strategies[i].multiplier; + } + if (totalMultiplier != BPS) { + return false; + } else { + return true; + } + } + + /** + * @notice Common logic to verify a batch of ECDSA signatures against a hash, using either last stake weight or at a specific block. + * @param _dataHash The hash of the data the signers endorsed. + * @param _signers A collection of addresses that endorsed the data hash. + * @param _signatures A collection of signatures matching the signers. + * @param _referenceBlock The block number for evaluating stake weight; use max uint32 for latest weight. + */ + function _checkSignatures( + bytes32 _dataHash, + address[] memory _signers, + bytes[] memory _signatures, + uint32 _referenceBlock + ) internal view { + uint256 signersLength = _signers.length; + address lastSigner; + uint256 signedWeight; + + _validateSignaturesLength(signersLength, _signatures.length); + for (uint256 i; i < signersLength; i++) { + address currentSigner = _signers[i]; + + _validateSortedSigners(lastSigner, currentSigner); + _validateSignature(currentSigner, _dataHash, _signatures[i]); + + lastSigner = currentSigner; + uint256 operatorWeight = _getOperatorWeight( + currentSigner, + _referenceBlock + ); + signedWeight += operatorWeight; + } + + _validateThresholdStake(signedWeight, _referenceBlock); + } + + /// @notice Validates that the number of signers equals the number of signatures, and neither is zero. + /// @param _signersLength The number of signers. + /// @param _signaturesLength The number of signatures. + function _validateSignaturesLength( + uint256 _signersLength, + uint256 _signaturesLength + ) internal pure { + if (_signersLength != _signaturesLength) { + revert LengthMismatch(); + } + if (_signersLength == 0) { + revert InvalidLength(); + } + } + + /// @notice Ensures that signers are sorted in ascending order by address. + /// @param _lastSigner The address of the last signer. + /// @param _currentSigner The address of the current signer. + function _validateSortedSigners( + address _lastSigner, + address _currentSigner + ) internal pure { + if (_lastSigner >= _currentSigner) { + revert NotSorted(); + } + } + + /// @notice Validates a given signature against the signer's address and data hash. + /// @param _signer The address of the signer to validate. + /// @param _dataHash The hash of the data that is signed. + /// @param _signature The signature to validate. + function _validateSignature( + address _signer, + bytes32 _dataHash, + bytes memory _signature + ) internal view { + if (!_signer.isValidSignatureNow(_dataHash, _signature)) { + revert InvalidSignature(); + } + } + + /// @notice Retrieves the operator weight for a signer, either at the last checkpoint or a specified block. + /// @param _signer The address of the signer whose weight is returned. + /// @param _referenceBlock The block number to query the operator's weight at, or the maximum uint32 value for the last checkpoint. + /// @return The weight of the operator. + function _getOperatorWeight( + address _signer, + uint32 _referenceBlock + ) internal view returns (uint256) { + if (_referenceBlock == type(uint32).max) { + return _operatorWeightHistory[_signer].latest(); + } else { + return _operatorWeightHistory[_signer].getAtBlock(_referenceBlock); + } + } + + /// @notice Retrieve the total stake weight at a specific block or the latest if not specified. + /// @dev If the `_referenceBlock` is the maximum value for uint32, the latest total weight is returned. + /// @param _referenceBlock The block number to retrieve the total stake weight from. + /// @return The total stake weight at the given block or the latest if the given block is the max uint32 value. + function _getTotalWeight( + uint32 _referenceBlock + ) internal view returns (uint256) { + if (_referenceBlock == type(uint32).max) { + return _totalWeightHistory.latest(); + } else { + return _totalWeightHistory.getAtBlock(_referenceBlock); + } + } + + /// @notice Retrieves the threshold stake for a given reference block. + /// @param _referenceBlock The block number to query the threshold stake for. + /// If set to the maximum uint32 value, it retrieves the latest threshold stake. + /// @return The threshold stake in basis points for the reference block. + function _getThresholdStake( + uint32 _referenceBlock + ) internal view returns (uint256) { + if (_referenceBlock == type(uint32).max) { + return _thresholdWeightHistory.latest(); + } else { + return _thresholdWeightHistory.getAtBlock(_referenceBlock); + } + } + + /// @notice Validates that the cumulative stake of signed messages meets or exceeds the required threshold. + /// @param _signedWeight The cumulative weight of the signers that have signed the message. + /// @param _referenceBlock The block number to verify the stake threshold for + function _validateThresholdStake( + uint256 _signedWeight, + uint32 _referenceBlock + ) internal view { + uint256 totalWeight = _getTotalWeight(_referenceBlock); + if (_signedWeight > totalWeight) { + revert InvalidSignedWeight(); + } + uint256 thresholdStake = _getThresholdStake(_referenceBlock); + if (thresholdStake > _signedWeight) { + revert InsufficientSignedStake(); + } + } +} diff --git a/solidity/contracts/avs/ECDSAStakeRegistryStorage.sol b/solidity/contracts/avs/ECDSAStakeRegistryStorage.sol new file mode 100644 index 0000000000..7665aea5d3 --- /dev/null +++ b/solidity/contracts/avs/ECDSAStakeRegistryStorage.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import {IDelegationManager} from "../interfaces/avs/IDelegationManager.sol"; +import {CheckpointsUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/CheckpointsUpgradeable.sol"; +import {IECDSAStakeRegistryEventsAndErrors, Quorum, StrategyParams} from "../interfaces/avs/IECDSAStakeRegistryEventsAndErrors.sol"; + +abstract contract ECDSAStakeRegistryStorage is + IECDSAStakeRegistryEventsAndErrors +{ + /// @notice Manages staking delegations through the DelegationManager interface + IDelegationManager internal immutable DELEGATION_MANAGER; + + /// @dev The total amount of multipliers to weigh stakes + uint256 internal constant BPS = 10_000; + + /// @notice The size of the current operator set + uint256 internal _totalOperators; + + /// @notice Stores the current quorum configuration + Quorum internal _quorum; + + /// @notice Specifies the weight required to become an operator + uint256 internal _minimumWeight; + + /// @notice Holds the address of the service manager + address internal _serviceManager; + + /// @notice Defines the duration after which the stake's weight expires. + uint256 internal _stakeExpiry; + + /// @notice Tracks the total stake history over time using checkpoints + CheckpointsUpgradeable.History internal _totalWeightHistory; + + /// @notice Tracks the threshold bps history using checkpoints + CheckpointsUpgradeable.History internal _thresholdWeightHistory; + + /// @notice Maps operator addresses to their respective stake histories using checkpoints + mapping(address => CheckpointsUpgradeable.History) + internal _operatorWeightHistory; + + /// @notice Maps an operator to their registration status + mapping(address => bool) internal _operatorRegistered; + + /// @param _delegationManager Connects this registry with the DelegationManager + constructor(IDelegationManager _delegationManager) { + DELEGATION_MANAGER = _delegationManager; + } + + // slither-disable-next-line shadowing-state + /// @dev Reserves storage slots for future upgrades + // solhint-disable-next-line + uint256[42] private __gap; +} diff --git a/solidity/contracts/avs/HyperlaneServiceManager.sol b/solidity/contracts/avs/HyperlaneServiceManager.sol index e955185b64..c02fe54e3c 100644 --- a/solidity/contracts/avs/HyperlaneServiceManager.sol +++ b/solidity/contracts/avs/HyperlaneServiceManager.sol @@ -18,10 +18,7 @@ import {Enrollment, EnrollmentStatus, EnumerableMapEnrollment} from "../libs/Enu import {IAVSDirectory} from "../interfaces/avs/IAVSDirectory.sol"; import {IRemoteChallenger} from "../interfaces/avs/IRemoteChallenger.sol"; import {ECDSAServiceManagerBase} from "./ECDSAServiceManagerBase.sol"; - -// ============ External Imports ============ import {ISlasher} from "../interfaces/avs/ISlasher.sol"; -import {IECDSAStakeRegistry} from "../interfaces/avs/IECDSAStakeRegistry.sol"; contract HyperlaneServiceManager is ECDSAServiceManagerBase { // ============ Libraries ============ diff --git a/solidity/contracts/interfaces/avs/IECDSAStakeRegistry.sol b/solidity/contracts/interfaces/avs/IECDSAStakeRegistry.sol deleted file mode 100644 index e1b01b54bf..0000000000 --- a/solidity/contracts/interfaces/avs/IECDSAStakeRegistry.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity >=0.5.0; - -import {IStrategy} from "./IStrategy.sol"; - -/// part of mock interfaces for vendoring necessary Eigenlayer contracts for the hyperlane AVS -/// @author Layr Labs, Inc. - -struct StrategyParams { - IStrategy strategy; // The strategy contract reference - uint96 multiplier; // The multiplier applied to the strategy -} - -struct Quorum { - StrategyParams[] strategies; // An array of strategy parameters to define the quorum -} - -interface IECDSAStakeRegistry { - function quorum() external view returns (Quorum memory); -} diff --git a/solidity/contracts/interfaces/avs/IECDSAStakeRegistryEventsAndErrors.sol b/solidity/contracts/interfaces/avs/IECDSAStakeRegistryEventsAndErrors.sol new file mode 100644 index 0000000000..ce2126201e --- /dev/null +++ b/solidity/contracts/interfaces/avs/IECDSAStakeRegistryEventsAndErrors.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +/// part of mock interfaces for vendoring necessary Eigenlayer contracts for the hyperlane AVS +/// @author Layr Labs, Inc. + +import {IStrategy} from "./IStrategy.sol"; + +struct StrategyParams { + IStrategy strategy; // The strategy contract reference + uint96 multiplier; // The multiplier applied to the strategy +} + +struct Quorum { + StrategyParams[] strategies; // An array of strategy parameters to define the quorum +} + +interface IECDSAStakeRegistryEventsAndErrors { + /// @notice Emitted when the system registers an operator + /// @param _operator The address of the registered operator + /// @param _avs The address of the associated AVS + event OperatorRegistered(address indexed _operator, address indexed _avs); + + /// @notice Emitted when the system deregisters an operator + /// @param _operator The address of the deregistered operator + /// @param _avs The address of the associated AVS + event OperatorDeregistered(address indexed _operator, address indexed _avs); + + /// @notice Emitted when the system updates the quorum + /// @param _old The previous quorum configuration + /// @param _new The new quorum configuration + event QuorumUpdated(Quorum _old, Quorum _new); + + /// @notice Emitted when the weight to join the operator set updates + /// @param _old The previous minimum weight + /// @param _new The new minimumWeight + event MinimumWeightUpdated(uint256 _old, uint256 _new); + + /// @notice Emitted when the weight required to be an operator changes + /// @param oldMinimumWeight The previous weight + /// @param newMinimumWeight The updated weight + event UpdateMinimumWeight( + uint256 oldMinimumWeight, + uint256 newMinimumWeight + ); + + /// @notice Emitted when the system updates an operator's weight + /// @param _operator The address of the operator updated + /// @param oldWeight The operator's weight before the update + /// @param newWeight The operator's weight after the update + event OperatorWeightUpdated( + address indexed _operator, + uint256 oldWeight, + uint256 newWeight + ); + + /// @notice Emitted when the system updates the total weight + /// @param oldTotalWeight The total weight before the update + /// @param newTotalWeight The total weight after the update + event TotalWeightUpdated(uint256 oldTotalWeight, uint256 newTotalWeight); + + /// @notice Emits when setting a new threshold weight. + event ThresholdWeightUpdated(uint256 _thresholdWeight); + + /// @notice Indicates when the lengths of the signers array and signatures array do not match. + error LengthMismatch(); + + /// @notice Indicates encountering an invalid length for the signers or signatures array. + error InvalidLength(); + + /// @notice Indicates encountering an invalid signature. + error InvalidSignature(); + + /// @notice Thrown when the threshold update is greater than BPS + error InvalidThreshold(); + + /// @notice Thrown when missing operators in an update + error MustUpdateAllOperators(); + + /// @notice Indicates operator weights were out of sync and the signed weight exceed the total + error InvalidSignedWeight(); + + /// @notice Indicates the total signed stake fails to meet the required threshold. + error InsufficientSignedStake(); + + /// @notice Indicates an individual signer's weight fails to meet the required threshold. + error InsufficientWeight(); + + /// @notice Indicates the quorum is invalid + error InvalidQuorum(); + + /// @notice Indicates the system finds a list of items unsorted + error NotSorted(); + + /// @notice Thrown when registering an already registered operator + error OperatorAlreadyRegistered(); + + /// @notice Thrown when de-registering or updating the stake for an unregisted operator + error OperatorNotRegistered(); +} diff --git a/solidity/contracts/test/avs/TestECDSAStakeRegistry.sol b/solidity/contracts/test/avs/TestECDSAStakeRegistry.sol deleted file mode 100644 index 7ee80ef566..0000000000 --- a/solidity/contracts/test/avs/TestECDSAStakeRegistry.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity >=0.8.0; - -import {ISignatureUtils} from "../../interfaces/avs/ISignatureUtils.sol"; -import {Quorum, IECDSAStakeRegistry} from "../../interfaces/avs/IECDSAStakeRegistry.sol"; -import {IServiceManager} from "../../interfaces/avs/IServiceManager.sol"; -import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; - -contract TestECDSAStakeRegistry is IECDSAStakeRegistry { - Quorum internal _quorum; - address internal _serviceManager; - - function initialize( - address serviceManager, - uint256, - Quorum memory - ) external { - _serviceManager = serviceManager; - } - - function quorum() external view returns (Quorum memory) {} - - function registerOperatorWithSignature( - address _operator, - ISignatureUtils.SignatureWithSaltAndExpiry memory _operatorSignature - ) external { - IServiceManager(_serviceManager).registerOperatorToAVS( - _operator, - _operatorSignature - ); - } - - function deregisterOperator() external { - IServiceManager(_serviceManager).deregisterOperatorFromAVS(msg.sender); - } -} diff --git a/solidity/foundry.toml b/solidity/foundry.toml index 5f3db3949e..c1e4bc20f1 100644 --- a/solidity/foundry.toml +++ b/solidity/foundry.toml @@ -1,5 +1,6 @@ [profile.default] src = 'contracts' +script = 'script' out = 'out' libs = ['node_modules', 'lib'] test = 'test' diff --git a/solidity/script/DeployAVS.s.sol b/solidity/script/DeployAVS.s.sol new file mode 100644 index 0000000000..62be410f8a --- /dev/null +++ b/solidity/script/DeployAVS.s.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +import "forge-std/Script.sol"; + +import {IDelegationManager} from "../contracts/interfaces/avs/IDelegationManager.sol"; +import {ECDSAStakeRegistry} from "../contracts/avs/ECDSAStakeRegistry.sol"; +import {Quorum} from "../contracts/interfaces/avs/IECDSAStakeRegistryEventsAndErrors.sol"; +import {HyperlaneServiceManager} from "../contracts/avs/HyperlaneServiceManager.sol"; + +contract DeployAVS is Script { + address public avsDirectory; + IDelegationManager public delegationManager; + address public paymentCoordinator; + + Quorum quorum; + uint256 thresholdWeight = 6667; + + function loadEigenlayerAddresses(string targetEnv) external { + string memory root = vm.projectRoot(); + string memory path = string.concat( + root, + "solidity/script/avs/eigenlayer_addresses.json" + ); + string json = vm.readFile(path); + + avsDirectory = json.readAddress( + string(abi.encodePacked(".", targetEnv, ".avsDirectory")) + ); + delegationManager = IDelegationManager( + json.readAddress( + string(abi.encodePacked(".", targetEnv, ".delegationManager")) + ) + ); + paymentCoordinator = json.readAddress( + string(abi.encodePacked(".", targetEnv, ".paymentCoordinator")) + ); + } + + function run() external { + vm.startBroadcast(); + + loadEigenlayerAddresses(); + + ECDSAStakeRegistry stakeRegistry = new ECDSAStakeRegistry( + delegationManager + ); + HyperlaneServiceManager hsm = new HyperlaneServiceManager( + avsDirectory, + address(stakeRegistry), + paymentCoordinator, + address(delegationManager) + ); + + stakeRegistry.initialize(address(hsm), thresholdWeight, quorum); + + vm.stopBroadcast(); + } +} diff --git a/solidity/script/eigenlayer_addresses.json b/solidity/script/eigenlayer_addresses.json new file mode 100644 index 0000000000..5764804ff8 --- /dev/null +++ b/solidity/script/eigenlayer_addresses.json @@ -0,0 +1,14 @@ +{ + "mainnet": { + "delegationManager": "0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A", + "avsDirectory": "0x135DDa560e946695d6f155dACaFC6f1F25C1F5AF", + "paymentCoordinator": "", + "strategies": [ + { + "name": "cbETH", + "address": "0x54945180dB7943c0ed0FEE7EdaB2Bd24620256bc" + } + ] + }, + "holesky": {} +} diff --git a/solidity/test/avs/HyperlaneServiceManager.t.sol b/solidity/test/avs/HyperlaneServiceManager.t.sol index 5f79667c2d..4230341cba 100644 --- a/solidity/test/avs/HyperlaneServiceManager.t.sol +++ b/solidity/test/avs/HyperlaneServiceManager.t.sol @@ -5,9 +5,9 @@ import {IDelegationManager} from "../../contracts/interfaces/avs/IDelegationMana import {ISlasher} from "../../contracts/interfaces/avs/ISlasher.sol"; import {IAVSDirectory} from "../../contracts/interfaces/avs/IAVSDirectory.sol"; -import {Quorum, StrategyParams, IECDSAStakeRegistry} from "../../contracts/interfaces/avs/IECDSAStakeRegistry.sol"; +import {Quorum, StrategyParams} from "../../contracts/interfaces/avs/IECDSAStakeRegistryEventsAndErrors.sol"; import {TestDelegationManager} from "../../contracts/test/avs/TestDelegationManager.sol"; -import {TestECDSAStakeRegistry} from "../../contracts/test/avs/TestECDSAStakeRegistry.sol"; +import {ECDSAStakeRegistry} from "../../contracts/avs/ECDSAStakeRegistry.sol"; import {TestPaymentCoordinator} from "../../contracts/test/avs/TestPaymentCoordinator.sol"; import {IStrategy} from "../../contracts/interfaces/avs/IStrategy.sol"; @@ -22,7 +22,7 @@ import {EigenlayerBase} from "./EigenlayerBase.sol"; contract HyperlaneServiceManagerTest is EigenlayerBase { HyperlaneServiceManager internal _hsm; - TestECDSAStakeRegistry internal _ecdsaStakeRegistry; + ECDSAStakeRegistry internal _ecdsaStakeRegistry; TestPaymentCoordinator internal _paymentCoordinator; // Operator info @@ -37,7 +37,7 @@ contract HyperlaneServiceManagerTest is EigenlayerBase { function setUp() public { _deployMockEigenLayerAndAVS(); - _ecdsaStakeRegistry = new TestECDSAStakeRegistry(); + _ecdsaStakeRegistry = new ECDSAStakeRegistry(delegationManager); _paymentCoordinator = new TestPaymentCoordinator(); _hsm = new HyperlaneServiceManager( From 2ef337c6c66629ce935948e19502bc71ad1b3d40 Mon Sep 17 00:00:00 2001 From: -f Date: Tue, 7 May 2024 13:15:23 +0530 Subject: [PATCH 23/32] reading strategies from json --- .gitattributes | 2 + solidity/foundry.toml | 1 + solidity/script/DeployAVS.s.sol | 59 ---------- solidity/script/avs/DeployAVS.s.sol | 105 ++++++++++++++++++ solidity/script/avs/eigenlayer_addresses.json | 40 +++++++ solidity/script/eigenlayer_addresses.json | 14 --- 6 files changed, 148 insertions(+), 73 deletions(-) delete mode 100644 solidity/script/DeployAVS.s.sol create mode 100644 solidity/script/avs/DeployAVS.s.sol create mode 100644 solidity/script/avs/eigenlayer_addresses.json delete mode 100644 solidity/script/eigenlayer_addresses.json diff --git a/.gitattributes b/.gitattributes index e0732994f9..d061a18bd0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,4 @@ typescript/sdk/src/cw-types/*.types.ts linguist-generated=true rust/chains/hyperlane-ethereum/abis/*.abi.json linguist-generated=true +solidity/contracts/interfaces/avs/*.sol linguist-vendored=true +solidity/contracts/avs/ECDSA*.sol linguist-vendored=true diff --git a/solidity/foundry.toml b/solidity/foundry.toml index c1e4bc20f1..254b79030b 100644 --- a/solidity/foundry.toml +++ b/solidity/foundry.toml @@ -10,6 +10,7 @@ solc_version = '0.8.22' evm_version= 'paris' optimizer = true optimizer_runs = 999_999 +fs_permissions = [{ access = "read-write", path = "./"}] [profile.ci] verbosity = 4 diff --git a/solidity/script/DeployAVS.s.sol b/solidity/script/DeployAVS.s.sol deleted file mode 100644 index 62be410f8a..0000000000 --- a/solidity/script/DeployAVS.s.sol +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity >=0.8.0; - -import "forge-std/Script.sol"; - -import {IDelegationManager} from "../contracts/interfaces/avs/IDelegationManager.sol"; -import {ECDSAStakeRegistry} from "../contracts/avs/ECDSAStakeRegistry.sol"; -import {Quorum} from "../contracts/interfaces/avs/IECDSAStakeRegistryEventsAndErrors.sol"; -import {HyperlaneServiceManager} from "../contracts/avs/HyperlaneServiceManager.sol"; - -contract DeployAVS is Script { - address public avsDirectory; - IDelegationManager public delegationManager; - address public paymentCoordinator; - - Quorum quorum; - uint256 thresholdWeight = 6667; - - function loadEigenlayerAddresses(string targetEnv) external { - string memory root = vm.projectRoot(); - string memory path = string.concat( - root, - "solidity/script/avs/eigenlayer_addresses.json" - ); - string json = vm.readFile(path); - - avsDirectory = json.readAddress( - string(abi.encodePacked(".", targetEnv, ".avsDirectory")) - ); - delegationManager = IDelegationManager( - json.readAddress( - string(abi.encodePacked(".", targetEnv, ".delegationManager")) - ) - ); - paymentCoordinator = json.readAddress( - string(abi.encodePacked(".", targetEnv, ".paymentCoordinator")) - ); - } - - function run() external { - vm.startBroadcast(); - - loadEigenlayerAddresses(); - - ECDSAStakeRegistry stakeRegistry = new ECDSAStakeRegistry( - delegationManager - ); - HyperlaneServiceManager hsm = new HyperlaneServiceManager( - avsDirectory, - address(stakeRegistry), - paymentCoordinator, - address(delegationManager) - ); - - stakeRegistry.initialize(address(hsm), thresholdWeight, quorum); - - vm.stopBroadcast(); - } -} diff --git a/solidity/script/avs/DeployAVS.s.sol b/solidity/script/avs/DeployAVS.s.sol new file mode 100644 index 0000000000..f17c1c5123 --- /dev/null +++ b/solidity/script/avs/DeployAVS.s.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +import "forge-std/Script.sol"; + +import {IStrategy} from "../../contracts/interfaces/avs/IStrategy.sol"; +import {IDelegationManager} from "../../contracts/interfaces/avs/IDelegationManager.sol"; +import {ECDSAStakeRegistry} from "../../contracts/avs/ECDSAStakeRegistry.sol"; +import {Quorum, StrategyParams} from "../../contracts/interfaces/avs/IECDSAStakeRegistryEventsAndErrors.sol"; +import {HyperlaneServiceManager} from "../../contracts/avs/HyperlaneServiceManager.sol"; + +import {TestPaymentCoordinator} from "../../contracts/test/avs/TestPaymentCoordinator.sol"; + +contract DeployAVS is Script { + using stdJson for string; + + struct StrategyInfo { + string name; + address strategy; + } + + uint256 deployerPrivateKey; + + address public avsDirectory; + IDelegationManager public delegationManager; + address public paymentCoordinator; + + Quorum quorum; + uint256 thresholdWeight = 6667; + + function _loadEigenlayerAddresses(string memory targetEnv) internal { + string memory root = vm.projectRoot(); + string memory path = string.concat( + root, + "/script/avs/eigenlayer_addresses.json" + ); + string memory json = vm.readFile(path); + + avsDirectory = json.readAddress( + string(abi.encodePacked(".", targetEnv, ".avsDirectory")) + ); + console.log("AVS directory address: ", avsDirectory); + delegationManager = IDelegationManager( + json.readAddress( + string(abi.encodePacked(".", targetEnv, ".delegationManager")) + ) + ); + // paymentCoordinator = json.readAddress(string(abi.encodePacked(".", targetEnv, ".paymentCoordinator"))); + paymentCoordinator = address(new TestPaymentCoordinator()); + + StrategyInfo[] memory strategies = abi.decode( + vm.parseJson( + json, + string(abi.encodePacked(".", targetEnv, ".strategies")) + ), + (StrategyInfo[]) + ); + + StrategyParams memory strategyParam; + + uint96 totalMultipliers = 10_000; + uint96 multiplier; + + uint96 strategyCount = uint96(strategies.length); + for (uint96 i = 0; i < strategyCount; i++) { + // the multipliers need to add up to 10,000, so we divide the total by the number of strategies for the first n-1 strategies + // and then the last strategy gets the remainder + if (i < strategies.length - 1) { + multiplier = totalMultipliers / uint96(strategyCount); + } else { + multiplier = + totalMultipliers - + multiplier * + uint96(strategyCount - 1); + } + strategyParam = StrategyParams({ + strategy: IStrategy(strategies[i].strategy), + multiplier: multiplier + }); + quorum.strategies.push(strategyParam); + } + } + + function run() external { + deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); + + _loadEigenlayerAddresses("holesky"); + + vm.startBroadcast(deployerPrivateKey); + + ECDSAStakeRegistry stakeRegistry = new ECDSAStakeRegistry( + delegationManager + ); + HyperlaneServiceManager hsm = new HyperlaneServiceManager( + avsDirectory, + address(stakeRegistry), + paymentCoordinator, + address(delegationManager) + ); + + stakeRegistry.initialize(address(hsm), thresholdWeight, quorum); + + vm.stopBroadcast(); + } +} diff --git a/solidity/script/avs/eigenlayer_addresses.json b/solidity/script/avs/eigenlayer_addresses.json new file mode 100644 index 0000000000..9a246e4a56 --- /dev/null +++ b/solidity/script/avs/eigenlayer_addresses.json @@ -0,0 +1,40 @@ +{ + "mainnet": { + "delegationManager": "0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A", + "avsDirectory": "0x135DDa560e946695d6f155dACaFC6f1F25C1F5AF", + "paymentCoordinator": "", + "strategies": [ + { + "name": "cbETH", + "strategy": "0x54945180dB7943c0ed0FEE7EdaB2Bd24620256bc" + }, + { + "name": "stETH", + "strategy": "0x93c4b944D05dfe6df7645A86cd2206016c51564D" + }, + { + "name": "Beacon Chain ETH", + "strategy": "0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0" + } + ] + }, + "holesky": { + "delegationManager": "0xA44151489861Fe9e3055d95adC98FbD462B948e7", + "avsDirectory": "0x055733000064333CaDDbC92763c58BF0192fFeBf", + "paymentCoordinator": "", + "strategies": [ + { + "name": "cbETH", + "strategy": "0x70EB4D3c164a6B4A5f908D4FBb5a9cAfFb66bAB6" + }, + { + "name": "stETH", + "strategy": "0x7D704507b76571a51d9caE8AdDAbBFd0ba0e63d3" + }, + { + "name": "Beacon Chain ETH", + "strategy": "0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0" + } + ] + } +} diff --git a/solidity/script/eigenlayer_addresses.json b/solidity/script/eigenlayer_addresses.json deleted file mode 100644 index 5764804ff8..0000000000 --- a/solidity/script/eigenlayer_addresses.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "mainnet": { - "delegationManager": "0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A", - "avsDirectory": "0x135DDa560e946695d6f155dACaFC6f1F25C1F5AF", - "paymentCoordinator": "", - "strategies": [ - { - "name": "cbETH", - "address": "0x54945180dB7943c0ed0FEE7EdaB2Bd24620256bc" - } - ] - }, - "holesky": {} -} From b50e0b25a1ac181ac8be8d86b0eec76b4fe95aca Mon Sep 17 00:00:00 2001 From: -f Date: Tue, 7 May 2024 14:22:06 +0530 Subject: [PATCH 24/32] add test for check man unenrollment --- .../test/avs/TestHyperlaneServiceManager.sol | 30 +++++++++++++++++++ .../test/avs/HyperlaneServiceManager.t.sol | 18 +++++++++-- 2 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 solidity/contracts/test/avs/TestHyperlaneServiceManager.sol diff --git a/solidity/contracts/test/avs/TestHyperlaneServiceManager.sol b/solidity/contracts/test/avs/TestHyperlaneServiceManager.sol new file mode 100644 index 0000000000..3a0bafcb99 --- /dev/null +++ b/solidity/contracts/test/avs/TestHyperlaneServiceManager.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +import {Enrollment, EnrollmentStatus, EnumerableMapEnrollment} from "../../libs/EnumerableMapEnrollment.sol"; +import {HyperlaneServiceManager} from "../../avs/HyperlaneServiceManager.sol"; + +contract TestHyperlaneServiceManager is HyperlaneServiceManager { + using EnumerableMapEnrollment for EnumerableMapEnrollment.AddressToEnrollmentMap; + + constructor( + address _avsDirectory, + address _stakeRegistry, + address _paymentCoordinator, + address _delegationManager + ) + HyperlaneServiceManager( + _avsDirectory, + _stakeRegistry, + _paymentCoordinator, + _delegationManager + ) + {} + + function mockSetUnenrolled(address operator, address challenger) external { + enrolledChallengers[operator].set( + address(challenger), + Enrollment(EnrollmentStatus.UNENROLLED, 0) + ); + } +} diff --git a/solidity/test/avs/HyperlaneServiceManager.t.sol b/solidity/test/avs/HyperlaneServiceManager.t.sol index 4230341cba..55061616fa 100644 --- a/solidity/test/avs/HyperlaneServiceManager.t.sol +++ b/solidity/test/avs/HyperlaneServiceManager.t.sol @@ -16,12 +16,13 @@ import {Enrollment, EnrollmentStatus} from "../../contracts/libs/EnumerableMapEn import {IRemoteChallenger} from "../../contracts/interfaces/avs/IRemoteChallenger.sol"; import {HyperlaneServiceManager} from "../../contracts/avs/HyperlaneServiceManager.sol"; +import {TestHyperlaneServiceManager} from "../../contracts/test/avs/TestHyperlaneServiceManager.sol"; import {TestRemoteChallenger} from "../../contracts/test/TestRemoteChallenger.sol"; import {EigenlayerBase} from "./EigenlayerBase.sol"; contract HyperlaneServiceManagerTest is EigenlayerBase { - HyperlaneServiceManager internal _hsm; + TestHyperlaneServiceManager internal _hsm; ECDSAStakeRegistry internal _ecdsaStakeRegistry; TestPaymentCoordinator internal _paymentCoordinator; @@ -40,7 +41,7 @@ contract HyperlaneServiceManagerTest is EigenlayerBase { _ecdsaStakeRegistry = new ECDSAStakeRegistry(delegationManager); _paymentCoordinator = new TestPaymentCoordinator(); - _hsm = new HyperlaneServiceManager( + _hsm = new TestHyperlaneServiceManager( address(avsDirectory), address(_ecdsaStakeRegistry), address(_paymentCoordinator), @@ -165,6 +166,19 @@ contract HyperlaneServiceManagerTest is EigenlayerBase { _assertChallengers(challengers, EnrollmentStatus.ENROLLED, 0); } + // to check if the exists in tryGet is working for when we set the enrollment to UNENROLLED + function test_checkUnenrolled() public { + _registerOperator(); + IRemoteChallenger[] memory challengers = _deployChallengers(1); + + vm.prank(operator); + _hsm.enrollIntoChallengers(challengers); + _assertChallengers(challengers, EnrollmentStatus.ENROLLED, 0); + + _hsm.mockSetUnenrolled(operator, address(challengers[0])); + _assertChallengers(challengers, EnrollmentStatus.UNENROLLED, 0); + } + /// forge-config: default.fuzz.runs = 10 function testFuzz_startUnenrollment_revert(uint8 numOfChallengers) public { vm.assume(numOfChallengers > 0); From 828f4e9846909f33cd6e5fbe4b40205c33790ded Mon Sep 17 00:00:00 2001 From: -f Date: Tue, 7 May 2024 14:50:33 +0530 Subject: [PATCH 25/32] unenroll with address[] --- .../contracts/avs/HyperlaneServiceManager.sol | 24 +++++-------------- .../contracts/test/ERC4626/ERC4626Test.sol | 5 ++-- .../test/avs/HyperlaneServiceManager.t.sol | 22 ++++++++--------- 3 files changed, 19 insertions(+), 32 deletions(-) diff --git a/solidity/contracts/avs/HyperlaneServiceManager.sol b/solidity/contracts/avs/HyperlaneServiceManager.sol index c02fe54e3c..1e11247d49 100644 --- a/solidity/contracts/avs/HyperlaneServiceManager.sol +++ b/solidity/contracts/avs/HyperlaneServiceManager.sol @@ -165,9 +165,7 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { * @notice Completes the unenrollment of an operator from a list of challengers * @param _challengers The list of challengers to unenroll from */ - function completeUnenrollment( - IRemoteChallenger[] memory _challengers - ) external { + function completeUnenrollment(address[] memory _challengers) external { _completeUnenrollment(msg.sender, _challengers); } @@ -210,16 +208,8 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { */ function getOperatorChallengers( address _operator - ) public view returns (IRemoteChallenger[] memory) { - address[] memory keys = enrolledChallengers[_operator].keys(); - - IRemoteChallenger[] memory challengers = new IRemoteChallenger[]( - keys.length - ); - for (uint256 i = 0; i < keys.length; i++) { - challengers[i] = IRemoteChallenger(keys[i]); - } - return challengers; + ) public view returns (address[] memory) { + return enrolledChallengers[_operator].keys(); } // ============ Internal Functions ============ @@ -231,10 +221,10 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { */ function _completeUnenrollment( address operator, - IRemoteChallenger[] memory _challengers + address[] memory _challengers ) internal { for (uint256 i = 0; i < _challengers.length; i++) { - IRemoteChallenger challenger = _challengers[i]; + IRemoteChallenger challenger = IRemoteChallenger(_challengers[i]); (bool exists, Enrollment memory enrollment) = enrolledChallengers[ operator ].tryGet(address(challenger)); @@ -262,9 +252,7 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { function _deregisterOperatorFromAVS( address operator ) internal virtual override { - IRemoteChallenger[] memory challengers = getOperatorChallengers( - operator - ); + address[] memory challengers = getOperatorChallengers(operator); _completeUnenrollment(operator, challengers); IAVSDirectory(avsDirectory).deregisterOperatorFromAVS(operator); diff --git a/solidity/contracts/test/ERC4626/ERC4626Test.sol b/solidity/contracts/test/ERC4626/ERC4626Test.sol index 7604fabd74..044429b539 100644 --- a/solidity/contracts/test/ERC4626/ERC4626Test.sol +++ b/solidity/contracts/test/ERC4626/ERC4626Test.sol @@ -1,14 +1,13 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity >=0.8.0; - import "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "@openzeppelin/contracts/interfaces/IERC20Metadata.sol"; +import "@openzeppelin/contracts/interfaces/IERC20.sol"; contract ERC4626Test is ERC4626 { constructor( address _asset, string memory _name, string memory _symbol - ) ERC4626(IERC20Metadata(_asset)) ERC20(_name, _symbol) {} + ) ERC4626(IERC20(_asset)) ERC20(_name, _symbol) {} } diff --git a/solidity/test/avs/HyperlaneServiceManager.t.sol b/solidity/test/avs/HyperlaneServiceManager.t.sol index 55061616fa..3328895032 100644 --- a/solidity/test/avs/HyperlaneServiceManager.t.sol +++ b/solidity/test/avs/HyperlaneServiceManager.t.sol @@ -263,12 +263,11 @@ contract HyperlaneServiceManagerTest is EigenlayerBase { IRemoteChallenger[] memory challengers = _deployChallengers( numOfChallengers ); - IRemoteChallenger[] - memory unenrollableChallengers = new IRemoteChallenger[]( - numUnenrollable - ); + address[] memory unenrollableChallengers = new address[]( + numUnenrollable + ); for (uint8 i = 0; i < numUnenrollable; i++) { - unenrollableChallengers[i] = challengers[i]; + unenrollableChallengers[i] = address(challengers[i]); } vm.startPrank(operator); @@ -327,16 +326,15 @@ contract HyperlaneServiceManagerTest is EigenlayerBase { IRemoteChallenger[] memory challengers = _deployChallengers( numOfChallengers ); - IRemoteChallenger[] - memory unenrollableChallengers = new IRemoteChallenger[]( - numUnenrollable - ); + address[] memory unenrollableChallengers = new address[]( + numUnenrollable + ); IRemoteChallenger[] memory otherChallengeChallengers = new IRemoteChallenger[]( numOfChallengers - numUnenrollable ); for (uint8 i = 0; i < numUnenrollable; i++) { - unenrollableChallengers[i] = challengers[i]; + unenrollableChallengers[i] = address(challengers[i]); } for (uint8 i = numUnenrollable; i < numOfChallengers; i++) { otherChallengeChallengers[i - numUnenrollable] = challengers[i]; @@ -361,7 +359,9 @@ contract HyperlaneServiceManagerTest is EigenlayerBase { vm.expectRevert( "HyperlaneServiceManager: Operator not enrolled in challenger" ); - unenrollableChallengers[i].handleChallenge(operator); + IRemoteChallenger(unenrollableChallengers[i]).handleChallenge( + operator + ); } for (uint256 i = 0; i < otherChallengeChallengers.length; i++) { vm.expectCall( From 7112431989b06ed9fc6fcf051f825601759e3853 Mon Sep 17 00:00:00 2001 From: -f Date: Tue, 7 May 2024 15:06:34 +0530 Subject: [PATCH 26/32] function for single challenger enroll/unenroll --- .../contracts/avs/HyperlaneServiceManager.sol | 136 +++++++++++------- 1 file changed, 84 insertions(+), 52 deletions(-) diff --git a/solidity/contracts/avs/HyperlaneServiceManager.sol b/solidity/contracts/avs/HyperlaneServiceManager.sol index 1e11247d49..973445ad88 100644 --- a/solidity/contracts/avs/HyperlaneServiceManager.sol +++ b/solidity/contracts/avs/HyperlaneServiceManager.sol @@ -116,14 +116,7 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { IRemoteChallenger[] memory _challengers ) external { for (uint256 i = 0; i < _challengers.length; i++) { - IRemoteChallenger challenger = _challengers[i]; - require( - enrolledChallengers[msg.sender].set( - address(challenger), - Enrollment(EnrollmentStatus.ENROLLED, 0) - ) - ); - emit OperatorEnrolledToChallenger(msg.sender, challenger); + enrollIntoChallenger(_challengers[i]); } } @@ -135,29 +128,7 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { IRemoteChallenger[] memory _challengers ) external { for (uint256 i = 0; i < _challengers.length; i++) { - IRemoteChallenger challenger = _challengers[i]; - - (bool exists, Enrollment memory enrollment) = enrolledChallengers[ - msg.sender - ].tryGet(address(challenger)); - require( - exists && enrollment.status == EnrollmentStatus.ENROLLED, - "HyperlaneServiceManager: challenger isn't enrolled" - ); - - enrolledChallengers[msg.sender].set( - address(challenger), - Enrollment( - EnrollmentStatus.PENDING_UNENROLLMENT, - uint248(block.number) - ) - ); - emit OperatorQueuedUnenrollmentFromChallenger( - msg.sender, - challenger, - block.number, - challenger.challengeDelayBlocks() - ); + startUnenrollment(_challengers[i]); } } @@ -212,6 +183,56 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { return enrolledChallengers[_operator].keys(); } + /** + * @notice Enrolls as an operator into a single challenger + * @param challenger The challenger to enroll into + */ + function enrollIntoChallenger(IRemoteChallenger challenger) public { + require( + enrolledChallengers[msg.sender].set( + address(challenger), + Enrollment(EnrollmentStatus.ENROLLED, 0) + ) + ); + emit OperatorEnrolledToChallenger(msg.sender, challenger); + } + + /** + * @notice starts an operator for unenrollment from a challenger + * @param challenger The challenger to unenroll from + */ + function startUnenrollment(IRemoteChallenger challenger) public { + (bool exists, Enrollment memory enrollment) = enrolledChallengers[ + msg.sender + ].tryGet(address(challenger)); + require( + exists && enrollment.status == EnrollmentStatus.ENROLLED, + "HyperlaneServiceManager: challenger isn't enrolled" + ); + + enrolledChallengers[msg.sender].set( + address(challenger), + Enrollment( + EnrollmentStatus.PENDING_UNENROLLMENT, + uint248(block.number) + ) + ); + emit OperatorQueuedUnenrollmentFromChallenger( + msg.sender, + challenger, + block.number, + challenger.challengeDelayBlocks() + ); + } + + /** + * @notice Completes the unenrollment of an operator from a challenger + * @param challenger The challenger to unenroll from + */ + function completeUnenrollment(address challenger) public { + _completeUnenrollment(msg.sender, challenger); + } + // ============ Internal Functions ============ /** @@ -224,30 +245,41 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { address[] memory _challengers ) internal { for (uint256 i = 0; i < _challengers.length; i++) { - IRemoteChallenger challenger = IRemoteChallenger(_challengers[i]); - (bool exists, Enrollment memory enrollment) = enrolledChallengers[ - operator - ].tryGet(address(challenger)); - - require( - exists && - enrollment.status == - EnrollmentStatus.PENDING_UNENROLLMENT && - block.number >= - enrollment.unenrollmentStartBlock + - challenger.challengeDelayBlocks(), - "HyperlaneServiceManager: Invalid unenrollment" - ); - - enrolledChallengers[operator].remove(address(challenger)); - emit OperatorUnenrolledFromChallenger( - operator, - challenger, - block.number - ); + _completeUnenrollment(operator, _challengers[i]); } } + /** + * @notice Completes the unenrollment of an operator from a challenger + * @param operator The address of the operator + * @param _challenger The challenger to unenroll from + */ + function _completeUnenrollment( + address operator, + address _challenger + ) internal { + IRemoteChallenger challenger = IRemoteChallenger(_challenger); + (bool exists, Enrollment memory enrollment) = enrolledChallengers[ + operator + ].tryGet(address(challenger)); + + require( + exists && + enrollment.status == EnrollmentStatus.PENDING_UNENROLLMENT && + block.number >= + enrollment.unenrollmentStartBlock + + challenger.challengeDelayBlocks(), + "HyperlaneServiceManager: Invalid unenrollment" + ); + + enrolledChallengers[operator].remove(address(challenger)); + emit OperatorUnenrolledFromChallenger( + operator, + challenger, + block.number + ); + } + /// @inheritdoc ECDSAServiceManagerBase function _deregisterOperatorFromAVS( address operator From 4cf5b0203e1be3a9225034f12e4d5b35a97bf5fb Mon Sep 17 00:00:00 2001 From: -f Date: Tue, 7 May 2024 15:13:32 +0530 Subject: [PATCH 27/32] separate vendored folder --- .../contracts/avs/ECDSAServiceManagerBase.sol | 18 +++++++++--------- solidity/contracts/avs/ECDSAStakeRegistry.sol | 8 ++++---- .../avs/ECDSAStakeRegistryStorage.sol | 4 ++-- .../contracts/avs/HyperlaneServiceManager.sol | 4 ++-- .../avs/{ => vendored}/IAVSDirectory.sol | 0 .../avs/{ => vendored}/IDelegationManager.sol | 0 .../IECDSAStakeRegistryEventsAndErrors.sol | 0 .../avs/{ => vendored}/IPaymentCoordinator.sol | 0 .../avs/{ => vendored}/IServiceManager.sol | 0 .../avs/{ => vendored}/IServiceManagerUI.sol | 0 .../avs/{ => vendored}/ISignatureUtils.sol | 0 .../interfaces/avs/{ => vendored}/ISlasher.sol | 0 .../avs/{ => vendored}/IStrategy.sol | 0 .../contracts/test/avs/TestAVSDirectory.sol | 6 +++--- .../test/avs/TestDelegationManager.sol | 4 ++-- .../test/avs/TestPaymentCoordinator.sol | 2 +- solidity/contracts/test/avs/TestSlasher.sol | 2 +- solidity/script/avs/DeployAVS.s.sol | 6 +++--- solidity/test/avs/EigenlayerBase.sol | 2 +- .../test/avs/HyperlaneServiceManager.t.sol | 12 ++++++------ 20 files changed, 34 insertions(+), 34 deletions(-) rename solidity/contracts/interfaces/avs/{ => vendored}/IAVSDirectory.sol (100%) rename solidity/contracts/interfaces/avs/{ => vendored}/IDelegationManager.sol (100%) rename solidity/contracts/interfaces/avs/{ => vendored}/IECDSAStakeRegistryEventsAndErrors.sol (100%) rename solidity/contracts/interfaces/avs/{ => vendored}/IPaymentCoordinator.sol (100%) rename solidity/contracts/interfaces/avs/{ => vendored}/IServiceManager.sol (100%) rename solidity/contracts/interfaces/avs/{ => vendored}/IServiceManagerUI.sol (100%) rename solidity/contracts/interfaces/avs/{ => vendored}/ISignatureUtils.sol (100%) rename solidity/contracts/interfaces/avs/{ => vendored}/ISlasher.sol (100%) rename solidity/contracts/interfaces/avs/{ => vendored}/IStrategy.sol (100%) diff --git a/solidity/contracts/avs/ECDSAServiceManagerBase.sol b/solidity/contracts/avs/ECDSAServiceManagerBase.sol index bd37af234b..44743a9edc 100644 --- a/solidity/contracts/avs/ECDSAServiceManagerBase.sol +++ b/solidity/contracts/avs/ECDSAServiceManagerBase.sol @@ -1,15 +1,15 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity >=0.8.0; -import {ISignatureUtils} from "../interfaces/avs/ISignatureUtils.sol"; -import {IAVSDirectory} from "../interfaces/avs/IAVSDirectory.sol"; - -import {IServiceManager} from "../interfaces/avs/IServiceManager.sol"; -import {IServiceManagerUI} from "../interfaces/avs/IServiceManagerUI.sol"; -import {IDelegationManager} from "../interfaces/avs/IDelegationManager.sol"; -import {IStrategy} from "../interfaces/avs/IStrategy.sol"; -import {IPaymentCoordinator} from "../interfaces/avs/IPaymentCoordinator.sol"; -import {Quorum} from "../interfaces/avs/IECDSAStakeRegistryEventsAndErrors.sol"; +import {ISignatureUtils} from "../interfaces/avs/vendored/ISignatureUtils.sol"; +import {IAVSDirectory} from "../interfaces/avs/vendored/IAVSDirectory.sol"; + +import {IServiceManager} from "../interfaces/avs/vendored/IServiceManager.sol"; +import {IServiceManagerUI} from "../interfaces/avs/vendored/IServiceManagerUI.sol"; +import {IDelegationManager} from "../interfaces/avs/vendored/IDelegationManager.sol"; +import {IStrategy} from "../interfaces/avs/vendored/IStrategy.sol"; +import {IPaymentCoordinator} from "../interfaces/avs/vendored/IPaymentCoordinator.sol"; +import {Quorum} from "../interfaces/avs/vendored/IECDSAStakeRegistryEventsAndErrors.sol"; import {ECDSAStakeRegistry} from "./ECDSAStakeRegistry.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; diff --git a/solidity/contracts/avs/ECDSAStakeRegistry.sol b/solidity/contracts/avs/ECDSAStakeRegistry.sol index c5c915c5d3..417fc58afd 100644 --- a/solidity/contracts/avs/ECDSAStakeRegistry.sol +++ b/solidity/contracts/avs/ECDSAStakeRegistry.sol @@ -2,10 +2,10 @@ pragma solidity ^0.8.12; import {ECDSAStakeRegistryStorage, Quorum, StrategyParams} from "./ECDSAStakeRegistryStorage.sol"; -import {IStrategy} from "../interfaces/avs/IStrategy.sol"; -import {IDelegationManager} from "../interfaces/avs/IDelegationManager.sol"; -import {ISignatureUtils} from "../interfaces/avs/ISignatureUtils.sol"; -import {IServiceManager} from "../interfaces/avs/IServiceManager.sol"; +import {IStrategy} from "../interfaces/avs/vendored/IStrategy.sol"; +import {IDelegationManager} from "../interfaces/avs/vendored/IDelegationManager.sol"; +import {ISignatureUtils} from "../interfaces/avs/vendored/ISignatureUtils.sol"; +import {IServiceManager} from "../interfaces/avs/vendored/IServiceManager.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {CheckpointsUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/CheckpointsUpgradeable.sol"; diff --git a/solidity/contracts/avs/ECDSAStakeRegistryStorage.sol b/solidity/contracts/avs/ECDSAStakeRegistryStorage.sol index 7665aea5d3..00af27ad91 100644 --- a/solidity/contracts/avs/ECDSAStakeRegistryStorage.sol +++ b/solidity/contracts/avs/ECDSAStakeRegistryStorage.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.12; -import {IDelegationManager} from "../interfaces/avs/IDelegationManager.sol"; +import {IDelegationManager} from "../interfaces/avs/vendored/IDelegationManager.sol"; import {CheckpointsUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/CheckpointsUpgradeable.sol"; -import {IECDSAStakeRegistryEventsAndErrors, Quorum, StrategyParams} from "../interfaces/avs/IECDSAStakeRegistryEventsAndErrors.sol"; +import {IECDSAStakeRegistryEventsAndErrors, Quorum, StrategyParams} from "../interfaces/avs/vendored/IECDSAStakeRegistryEventsAndErrors.sol"; abstract contract ECDSAStakeRegistryStorage is IECDSAStakeRegistryEventsAndErrors diff --git a/solidity/contracts/avs/HyperlaneServiceManager.sol b/solidity/contracts/avs/HyperlaneServiceManager.sol index 973445ad88..242fd33c85 100644 --- a/solidity/contracts/avs/HyperlaneServiceManager.sol +++ b/solidity/contracts/avs/HyperlaneServiceManager.sol @@ -15,10 +15,10 @@ pragma solidity >=0.8.0; // ============ Internal Imports ============ import {Enrollment, EnrollmentStatus, EnumerableMapEnrollment} from "../libs/EnumerableMapEnrollment.sol"; -import {IAVSDirectory} from "../interfaces/avs/IAVSDirectory.sol"; +import {IAVSDirectory} from "../interfaces/avs/vendored/IAVSDirectory.sol"; import {IRemoteChallenger} from "../interfaces/avs/IRemoteChallenger.sol"; import {ECDSAServiceManagerBase} from "./ECDSAServiceManagerBase.sol"; -import {ISlasher} from "../interfaces/avs/ISlasher.sol"; +import {ISlasher} from "../interfaces/avs/vendored/ISlasher.sol"; contract HyperlaneServiceManager is ECDSAServiceManagerBase { // ============ Libraries ============ diff --git a/solidity/contracts/interfaces/avs/IAVSDirectory.sol b/solidity/contracts/interfaces/avs/vendored/IAVSDirectory.sol similarity index 100% rename from solidity/contracts/interfaces/avs/IAVSDirectory.sol rename to solidity/contracts/interfaces/avs/vendored/IAVSDirectory.sol diff --git a/solidity/contracts/interfaces/avs/IDelegationManager.sol b/solidity/contracts/interfaces/avs/vendored/IDelegationManager.sol similarity index 100% rename from solidity/contracts/interfaces/avs/IDelegationManager.sol rename to solidity/contracts/interfaces/avs/vendored/IDelegationManager.sol diff --git a/solidity/contracts/interfaces/avs/IECDSAStakeRegistryEventsAndErrors.sol b/solidity/contracts/interfaces/avs/vendored/IECDSAStakeRegistryEventsAndErrors.sol similarity index 100% rename from solidity/contracts/interfaces/avs/IECDSAStakeRegistryEventsAndErrors.sol rename to solidity/contracts/interfaces/avs/vendored/IECDSAStakeRegistryEventsAndErrors.sol diff --git a/solidity/contracts/interfaces/avs/IPaymentCoordinator.sol b/solidity/contracts/interfaces/avs/vendored/IPaymentCoordinator.sol similarity index 100% rename from solidity/contracts/interfaces/avs/IPaymentCoordinator.sol rename to solidity/contracts/interfaces/avs/vendored/IPaymentCoordinator.sol diff --git a/solidity/contracts/interfaces/avs/IServiceManager.sol b/solidity/contracts/interfaces/avs/vendored/IServiceManager.sol similarity index 100% rename from solidity/contracts/interfaces/avs/IServiceManager.sol rename to solidity/contracts/interfaces/avs/vendored/IServiceManager.sol diff --git a/solidity/contracts/interfaces/avs/IServiceManagerUI.sol b/solidity/contracts/interfaces/avs/vendored/IServiceManagerUI.sol similarity index 100% rename from solidity/contracts/interfaces/avs/IServiceManagerUI.sol rename to solidity/contracts/interfaces/avs/vendored/IServiceManagerUI.sol diff --git a/solidity/contracts/interfaces/avs/ISignatureUtils.sol b/solidity/contracts/interfaces/avs/vendored/ISignatureUtils.sol similarity index 100% rename from solidity/contracts/interfaces/avs/ISignatureUtils.sol rename to solidity/contracts/interfaces/avs/vendored/ISignatureUtils.sol diff --git a/solidity/contracts/interfaces/avs/ISlasher.sol b/solidity/contracts/interfaces/avs/vendored/ISlasher.sol similarity index 100% rename from solidity/contracts/interfaces/avs/ISlasher.sol rename to solidity/contracts/interfaces/avs/vendored/ISlasher.sol diff --git a/solidity/contracts/interfaces/avs/IStrategy.sol b/solidity/contracts/interfaces/avs/vendored/IStrategy.sol similarity index 100% rename from solidity/contracts/interfaces/avs/IStrategy.sol rename to solidity/contracts/interfaces/avs/vendored/IStrategy.sol diff --git a/solidity/contracts/test/avs/TestAVSDirectory.sol b/solidity/contracts/test/avs/TestAVSDirectory.sol index 2e22ae9048..bd4a70a2f2 100644 --- a/solidity/contracts/test/avs/TestAVSDirectory.sol +++ b/solidity/contracts/test/avs/TestAVSDirectory.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.8.0; -import {IAVSDirectory} from "../../interfaces/avs/IAVSDirectory.sol"; -import {ISignatureUtils} from "../../interfaces/avs/ISignatureUtils.sol"; -import {ISlasher} from "../../interfaces/avs/ISlasher.sol"; +import {IAVSDirectory} from "../../interfaces/avs/vendored/IAVSDirectory.sol"; +import {ISignatureUtils} from "../../interfaces/avs/vendored/ISignatureUtils.sol"; +import {ISlasher} from "../../interfaces/avs/vendored/ISlasher.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; contract TestAVSDirectory is IAVSDirectory { diff --git a/solidity/contracts/test/avs/TestDelegationManager.sol b/solidity/contracts/test/avs/TestDelegationManager.sol index cbd50fbc6c..cfe4ea2ce6 100644 --- a/solidity/contracts/test/avs/TestDelegationManager.sol +++ b/solidity/contracts/test/avs/TestDelegationManager.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.8.0; -import {IDelegationManager} from "../../interfaces/avs/IDelegationManager.sol"; -import {IStrategy} from "../../interfaces/avs/IStrategy.sol"; +import {IDelegationManager} from "../../interfaces/avs/vendored/IDelegationManager.sol"; +import {IStrategy} from "../../interfaces/avs/vendored/IStrategy.sol"; contract TestDelegationManager is IDelegationManager { mapping(address => bool) public isOperator; diff --git a/solidity/contracts/test/avs/TestPaymentCoordinator.sol b/solidity/contracts/test/avs/TestPaymentCoordinator.sol index e86c6a0b99..ac11efe516 100644 --- a/solidity/contracts/test/avs/TestPaymentCoordinator.sol +++ b/solidity/contracts/test/avs/TestPaymentCoordinator.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.8.0; -import {IPaymentCoordinator} from "../../interfaces/avs/IPaymentCoordinator.sol"; +import {IPaymentCoordinator} from "../../interfaces/avs/vendored/IPaymentCoordinator.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; diff --git a/solidity/contracts/test/avs/TestSlasher.sol b/solidity/contracts/test/avs/TestSlasher.sol index ee9f56039f..6f4d3eb1ed 100644 --- a/solidity/contracts/test/avs/TestSlasher.sol +++ b/solidity/contracts/test/avs/TestSlasher.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.8.0; -import {ISlasher} from "../../interfaces/avs/ISlasher.sol"; +import {ISlasher} from "../../interfaces/avs/vendored/ISlasher.sol"; contract TestSlasher is ISlasher { function freezeOperator(address toBeFrozen) external {} diff --git a/solidity/script/avs/DeployAVS.s.sol b/solidity/script/avs/DeployAVS.s.sol index f17c1c5123..6b42294601 100644 --- a/solidity/script/avs/DeployAVS.s.sol +++ b/solidity/script/avs/DeployAVS.s.sol @@ -3,10 +3,10 @@ pragma solidity >=0.8.0; import "forge-std/Script.sol"; -import {IStrategy} from "../../contracts/interfaces/avs/IStrategy.sol"; -import {IDelegationManager} from "../../contracts/interfaces/avs/IDelegationManager.sol"; +import {IStrategy} from "../../contracts/interfaces/avs/vendored/IStrategy.sol"; +import {IDelegationManager} from "../../contracts/interfaces/avs/vendored/IDelegationManager.sol"; import {ECDSAStakeRegistry} from "../../contracts/avs/ECDSAStakeRegistry.sol"; -import {Quorum, StrategyParams} from "../../contracts/interfaces/avs/IECDSAStakeRegistryEventsAndErrors.sol"; +import {Quorum, StrategyParams} from "../../contracts/interfaces/avs/vendored/IECDSAStakeRegistryEventsAndErrors.sol"; import {HyperlaneServiceManager} from "../../contracts/avs/HyperlaneServiceManager.sol"; import {TestPaymentCoordinator} from "../../contracts/test/avs/TestPaymentCoordinator.sol"; diff --git a/solidity/test/avs/EigenlayerBase.sol b/solidity/test/avs/EigenlayerBase.sol index c89345c9e3..60adf3a9f9 100644 --- a/solidity/test/avs/EigenlayerBase.sol +++ b/solidity/test/avs/EigenlayerBase.sol @@ -3,7 +3,7 @@ pragma solidity >=0.8.0; import "forge-std/Test.sol"; -import {ISlasher} from "../../contracts/interfaces/avs/ISlasher.sol"; +import {ISlasher} from "../../contracts/interfaces/avs/vendored/ISlasher.sol"; import {TestAVSDirectory} from "../../contracts/test/avs/TestAVSDirectory.sol"; import {TestDelegationManager} from "../../contracts/test/avs/TestDelegationManager.sol"; import {TestSlasher} from "../../contracts/test/avs/TestSlasher.sol"; diff --git a/solidity/test/avs/HyperlaneServiceManager.t.sol b/solidity/test/avs/HyperlaneServiceManager.t.sol index 3328895032..3e5cb0681d 100644 --- a/solidity/test/avs/HyperlaneServiceManager.t.sol +++ b/solidity/test/avs/HyperlaneServiceManager.t.sol @@ -1,17 +1,17 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.8.0; -import {IDelegationManager} from "../../contracts/interfaces/avs/IDelegationManager.sol"; -import {ISlasher} from "../../contracts/interfaces/avs/ISlasher.sol"; +import {IDelegationManager} from "../../contracts/interfaces/avs/vendored/IDelegationManager.sol"; +import {ISlasher} from "../../contracts/interfaces/avs/vendored/ISlasher.sol"; -import {IAVSDirectory} from "../../contracts/interfaces/avs/IAVSDirectory.sol"; -import {Quorum, StrategyParams} from "../../contracts/interfaces/avs/IECDSAStakeRegistryEventsAndErrors.sol"; +import {IAVSDirectory} from "../../contracts/interfaces/avs/vendored/IAVSDirectory.sol"; +import {Quorum, StrategyParams} from "../../contracts/interfaces/avs/vendored/IECDSAStakeRegistryEventsAndErrors.sol"; import {TestDelegationManager} from "../../contracts/test/avs/TestDelegationManager.sol"; import {ECDSAStakeRegistry} from "../../contracts/avs/ECDSAStakeRegistry.sol"; import {TestPaymentCoordinator} from "../../contracts/test/avs/TestPaymentCoordinator.sol"; -import {IStrategy} from "../../contracts/interfaces/avs/IStrategy.sol"; -import {ISignatureUtils} from "../../contracts/interfaces/avs/ISignatureUtils.sol"; +import {IStrategy} from "../../contracts/interfaces/avs/vendored/IStrategy.sol"; +import {ISignatureUtils} from "../../contracts/interfaces/avs/vendored/ISignatureUtils.sol"; import {Enrollment, EnrollmentStatus} from "../../contracts/libs/EnumerableMapEnrollment.sol"; import {IRemoteChallenger} from "../../contracts/interfaces/avs/IRemoteChallenger.sol"; From 7f3540bfa1feb031336695c9f01a0c812609bd7f Mon Sep 17 00:00:00 2001 From: -f Date: Tue, 7 May 2024 20:47:50 +0530 Subject: [PATCH 28/32] add proxy deployment to forge --- .../contracts/avs/HyperlaneServiceManager.sol | 10 ++- solidity/script/avs/DeployAVS.s.sol | 63 ++++++++++++++----- solidity/script/avs/eigenlayer_addresses.json | 4 +- .../test/avs/HyperlaneServiceManager.t.sol | 1 + 4 files changed, 57 insertions(+), 21 deletions(-) diff --git a/solidity/contracts/avs/HyperlaneServiceManager.sol b/solidity/contracts/avs/HyperlaneServiceManager.sol index 242fd33c85..49372ec299 100644 --- a/solidity/contracts/avs/HyperlaneServiceManager.sol +++ b/solidity/contracts/avs/HyperlaneServiceManager.sol @@ -101,9 +101,13 @@ contract HyperlaneServiceManager is ECDSAServiceManagerBase { _paymentCoordinator, _delegationManager ) - initializer - { - __ServiceManagerBase_init(msg.sender); + {} + + /** + * @notice Initializes the HyperlaneServiceManager contract with the owner address + */ + function initialize(address _owner) public initializer { + __ServiceManagerBase_init(_owner); } // ============ External Functions ============ diff --git a/solidity/script/avs/DeployAVS.s.sol b/solidity/script/avs/DeployAVS.s.sol index 6b42294601..c0f66ceeb8 100644 --- a/solidity/script/avs/DeployAVS.s.sol +++ b/solidity/script/avs/DeployAVS.s.sol @@ -4,7 +4,12 @@ pragma solidity >=0.8.0; import "forge-std/Script.sol"; import {IStrategy} from "../../contracts/interfaces/avs/vendored/IStrategy.sol"; +import {IAVSDirectory} from "../../contracts/interfaces/avs/vendored/IAVSDirectory.sol"; +import {IPaymentCoordinator} from "../../contracts/interfaces/avs/vendored/IPaymentCoordinator.sol"; import {IDelegationManager} from "../../contracts/interfaces/avs/vendored/IDelegationManager.sol"; + +import {ProxyAdmin} from "../../contracts/upgrade/ProxyAdmin.sol"; +import {TransparentUpgradeableProxy} from "../../contracts/upgrade/TransparentUpgradeableProxy.sol"; import {ECDSAStakeRegistry} from "../../contracts/avs/ECDSAStakeRegistry.sol"; import {Quorum, StrategyParams} from "../../contracts/interfaces/avs/vendored/IECDSAStakeRegistryEventsAndErrors.sol"; import {HyperlaneServiceManager} from "../../contracts/avs/HyperlaneServiceManager.sol"; @@ -21,9 +26,10 @@ contract DeployAVS is Script { uint256 deployerPrivateKey; - address public avsDirectory; + ProxyAdmin public proxyAdmin; + IAVSDirectory public avsDirectory; + IPaymentCoordinator public paymentCoordinator; IDelegationManager public delegationManager; - address public paymentCoordinator; Quorum quorum; uint256 thresholdWeight = 6667; @@ -36,17 +42,23 @@ contract DeployAVS is Script { ); string memory json = vm.readFile(path); - avsDirectory = json.readAddress( - string(abi.encodePacked(".", targetEnv, ".avsDirectory")) + proxyAdmin = ProxyAdmin( + json.readAddress( + string(abi.encodePacked(".", targetEnv, ".proxyAdmin")) + ) + ); + avsDirectory = IAVSDirectory( + json.readAddress( + string(abi.encodePacked(".", targetEnv, ".avsDirectory")) + ) ); - console.log("AVS directory address: ", avsDirectory); delegationManager = IDelegationManager( json.readAddress( string(abi.encodePacked(".", targetEnv, ".delegationManager")) ) ); - // paymentCoordinator = json.readAddress(string(abi.encodePacked(".", targetEnv, ".paymentCoordinator"))); - paymentCoordinator = address(new TestPaymentCoordinator()); + // paymentCoordinator = IPaymentCoordinator(json.readAddress(string(abi.encodePacked(".", targetEnv, ".paymentCoordinator")))); + paymentCoordinator = new TestPaymentCoordinator(); // temporary until Eigenlayer deploys the real one StrategyInfo[] memory strategies = abi.decode( vm.parseJson( @@ -81,24 +93,41 @@ contract DeployAVS is Script { } } - function run() external { + function run(string memory network) external { deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); - _loadEigenlayerAddresses("holesky"); + _loadEigenlayerAddresses(network); vm.startBroadcast(deployerPrivateKey); - ECDSAStakeRegistry stakeRegistry = new ECDSAStakeRegistry( + ECDSAStakeRegistry stakeRegistryImpl = new ECDSAStakeRegistry( delegationManager ); - HyperlaneServiceManager hsm = new HyperlaneServiceManager( - avsDirectory, - address(stakeRegistry), - paymentCoordinator, - address(delegationManager) + HyperlaneServiceManager strategyManagerImpl = new HyperlaneServiceManager( + address(avsDirectory), + address(stakeRegistryImpl), + address(paymentCoordinator), + address(delegationManager) + ); + + TransparentUpgradeableProxy hsmProxy = new TransparentUpgradeableProxy( + address(strategyManagerImpl), + address(proxyAdmin), + abi.encodeWithSelector( + HyperlaneServiceManager.initialize.selector, + msg.sender + ) ); - - stakeRegistry.initialize(address(hsm), thresholdWeight, quorum); + TransparentUpgradeableProxy stakeRegistryProxy = new TransparentUpgradeableProxy( + address(stakeRegistryImpl), + address(proxyAdmin), + abi.encodeWithSelector( + ECDSAStakeRegistry.initialize.selector, + address(hsmProxy), + thresholdWeight, + quorum + ) + ); vm.stopBroadcast(); } diff --git a/solidity/script/avs/eigenlayer_addresses.json b/solidity/script/avs/eigenlayer_addresses.json index 9a246e4a56..fa970ed19e 100644 --- a/solidity/script/avs/eigenlayer_addresses.json +++ b/solidity/script/avs/eigenlayer_addresses.json @@ -1,5 +1,6 @@ { - "mainnet": { + "ethereum": { + "proxyAdmin": "0x75EE15Ee1B4A75Fa3e2fDF5DF3253c25599cc659", "delegationManager": "0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A", "avsDirectory": "0x135DDa560e946695d6f155dACaFC6f1F25C1F5AF", "paymentCoordinator": "", @@ -19,6 +20,7 @@ ] }, "holesky": { + "proxyAdmin": "", "delegationManager": "0xA44151489861Fe9e3055d95adC98FbD462B948e7", "avsDirectory": "0x055733000064333CaDDbC92763c58BF0192fFeBf", "paymentCoordinator": "", diff --git a/solidity/test/avs/HyperlaneServiceManager.t.sol b/solidity/test/avs/HyperlaneServiceManager.t.sol index 3e5cb0681d..75ecf471fc 100644 --- a/solidity/test/avs/HyperlaneServiceManager.t.sol +++ b/solidity/test/avs/HyperlaneServiceManager.t.sol @@ -47,6 +47,7 @@ contract HyperlaneServiceManagerTest is EigenlayerBase { address(_paymentCoordinator), address(delegationManager) ); + _hsm.initialize(address(this)); _hsm.setSlasher(slasher); IStrategy mockStrategy = IStrategy(address(0x1234)); From 23aa15529a22408b9baef54bff151b484f249937 Mon Sep 17 00:00:00 2001 From: -f Date: Tue, 7 May 2024 21:07:41 +0530 Subject: [PATCH 29/32] add global fuzz.runs config --- solidity/foundry.toml | 5 +++++ solidity/test/avs/HyperlaneServiceManager.t.sol | 7 ------- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/solidity/foundry.toml b/solidity/foundry.toml index 254b79030b..33127dbd58 100644 --- a/solidity/foundry.toml +++ b/solidity/foundry.toml @@ -18,3 +18,8 @@ verbosity = 4 [rpc_endpoints] mainnet = "https://eth.merkle.io" optimism = "https://mainnet.optimism.io " + + +[fuzz] +runs = 50 +dictionary_weight = 80 \ No newline at end of file diff --git a/solidity/test/avs/HyperlaneServiceManager.t.sol b/solidity/test/avs/HyperlaneServiceManager.t.sol index 75ecf471fc..4ea9ce8f1e 100644 --- a/solidity/test/avs/HyperlaneServiceManager.t.sol +++ b/solidity/test/avs/HyperlaneServiceManager.t.sol @@ -154,7 +154,6 @@ contract HyperlaneServiceManagerTest is EigenlayerBase { ); } - /// forge-config: default.fuzz.runs = 10 function testFuzz_enrollIntoChallengers(uint8 numOfChallengers) public { _registerOperator(); IRemoteChallenger[] memory challengers = _deployChallengers( @@ -180,7 +179,6 @@ contract HyperlaneServiceManagerTest is EigenlayerBase { _assertChallengers(challengers, EnrollmentStatus.UNENROLLED, 0); } - /// forge-config: default.fuzz.runs = 10 function testFuzz_startUnenrollment_revert(uint8 numOfChallengers) public { vm.assume(numOfChallengers > 0); @@ -213,7 +211,6 @@ contract HyperlaneServiceManagerTest is EigenlayerBase { vm.stopPrank(); } - /// forge-config: default.fuzz.runs = 10 function testFuzz_startUnenrollment( uint8 numOfChallengers, uint8 numQueued @@ -253,7 +250,6 @@ contract HyperlaneServiceManagerTest is EigenlayerBase { vm.stopPrank(); } - /// forge-config: default.fuzz.runs = 10 function testFuzz_completeQueuedUnenrollmentFromChallenger( uint8 numOfChallengers, uint8 numUnenrollable @@ -296,7 +292,6 @@ contract HyperlaneServiceManagerTest is EigenlayerBase { vm.stopPrank(); } - /// forge-config: default.fuzz.runs = 10 function testFuzz_freezeOperator(uint8 numOfChallengers) public { _registerOperator(); @@ -316,7 +311,6 @@ contract HyperlaneServiceManagerTest is EigenlayerBase { } } - /// forge-config: default.fuzz.runs = 10 function testFuzz_freezeOperator_duringEnrollment( uint8 numOfChallengers, uint8 numUnenrollable @@ -374,7 +368,6 @@ contract HyperlaneServiceManagerTest is EigenlayerBase { vm.stopPrank(); } - /// forge-config: default.fuzz.runs = 10 function testFuzz_deregisterOperator_withEnrollment() public { uint8 numOfChallengers = 1; vm.assume(numOfChallengers > 0); From e11ed99cf7b8f8117ac437e92e4182bc2d8b6435 Mon Sep 17 00:00:00 2001 From: -f Date: Tue, 7 May 2024 21:23:46 +0530 Subject: [PATCH 30/32] update spelling --- .../avs/vendored/IECDSAStakeRegistryEventsAndErrors.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solidity/contracts/interfaces/avs/vendored/IECDSAStakeRegistryEventsAndErrors.sol b/solidity/contracts/interfaces/avs/vendored/IECDSAStakeRegistryEventsAndErrors.sol index ce2126201e..ccdf93ca63 100644 --- a/solidity/contracts/interfaces/avs/vendored/IECDSAStakeRegistryEventsAndErrors.sol +++ b/solidity/contracts/interfaces/avs/vendored/IECDSAStakeRegistryEventsAndErrors.sol @@ -95,6 +95,6 @@ interface IECDSAStakeRegistryEventsAndErrors { /// @notice Thrown when registering an already registered operator error OperatorAlreadyRegistered(); - /// @notice Thrown when de-registering or updating the stake for an unregisted operator + /// @notice Thrown when de-registering or updating the stake for an unregistered operator error OperatorNotRegistered(); } From e62daa455b41146edc736614f4afdcd06cdba918 Mon Sep 17 00:00:00 2001 From: -f Date: Wed, 8 May 2024 02:01:17 +0530 Subject: [PATCH 31/32] add incomp attribution to LL --- solidity/contracts/avs/ECDSAServiceManagerBase.sol | 1 + solidity/contracts/avs/ECDSAStakeRegistry.sol | 1 + .../contracts/avs/ECDSAStakeRegistryStorage.sol | 1 + solidity/contracts/avs/HyperlaneServiceManager.sol | 2 +- .../contracts/interfaces/avs/IRemoteChallenger.sol | 5 +++++ .../interfaces/avs/vendored/IAVSDirectory.sol | 1 - .../interfaces/avs/vendored/IDelegationManager.sol | 13 ++++++++++--- .../vendored/IECDSAStakeRegistryEventsAndErrors.sol | 5 ++--- .../interfaces/avs/vendored/ISignatureUtils.sol | 5 +++++ .../contracts/interfaces/avs/vendored/ISlasher.sol | 5 +++++ solidity/contracts/test/avs/TestAVSDirectory.sol | 1 + 11 files changed, 32 insertions(+), 8 deletions(-) diff --git a/solidity/contracts/avs/ECDSAServiceManagerBase.sol b/solidity/contracts/avs/ECDSAServiceManagerBase.sol index 44743a9edc..5f6c994739 100644 --- a/solidity/contracts/avs/ECDSAServiceManagerBase.sol +++ b/solidity/contracts/avs/ECDSAServiceManagerBase.sol @@ -14,6 +14,7 @@ import {ECDSAStakeRegistry} from "./ECDSAStakeRegistry.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +/// @author Layr Labs, Inc. abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable diff --git a/solidity/contracts/avs/ECDSAStakeRegistry.sol b/solidity/contracts/avs/ECDSAStakeRegistry.sol index 417fc58afd..0a4e32a011 100644 --- a/solidity/contracts/avs/ECDSAStakeRegistry.sol +++ b/solidity/contracts/avs/ECDSAStakeRegistry.sol @@ -13,6 +13,7 @@ import {SignatureCheckerUpgradeable} from "@openzeppelin/contracts-upgradeable/u import {IERC1271Upgradeable} from "@openzeppelin/contracts-upgradeable/interfaces/IERC1271Upgradeable.sol"; /// @title ECDSA Stake Registry +/// @author Layr Labs, Inc. /// @dev THIS CONTRACT IS NOT AUDITED /// @notice Manages operator registration and quorum updates for an AVS using ECDSA signatures. contract ECDSAStakeRegistry is diff --git a/solidity/contracts/avs/ECDSAStakeRegistryStorage.sol b/solidity/contracts/avs/ECDSAStakeRegistryStorage.sol index 00af27ad91..9e59fd8f63 100644 --- a/solidity/contracts/avs/ECDSAStakeRegistryStorage.sol +++ b/solidity/contracts/avs/ECDSAStakeRegistryStorage.sol @@ -5,6 +5,7 @@ import {IDelegationManager} from "../interfaces/avs/vendored/IDelegationManager. import {CheckpointsUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/CheckpointsUpgradeable.sol"; import {IECDSAStakeRegistryEventsAndErrors, Quorum, StrategyParams} from "../interfaces/avs/vendored/IECDSAStakeRegistryEventsAndErrors.sol"; +/// @author Layr Labs, Inc. abstract contract ECDSAStakeRegistryStorage is IECDSAStakeRegistryEventsAndErrors { diff --git a/solidity/contracts/avs/HyperlaneServiceManager.sol b/solidity/contracts/avs/HyperlaneServiceManager.sol index 49372ec299..b663695ddb 100644 --- a/solidity/contracts/avs/HyperlaneServiceManager.sol +++ b/solidity/contracts/avs/HyperlaneServiceManager.sol @@ -17,8 +17,8 @@ pragma solidity >=0.8.0; import {Enrollment, EnrollmentStatus, EnumerableMapEnrollment} from "../libs/EnumerableMapEnrollment.sol"; import {IAVSDirectory} from "../interfaces/avs/vendored/IAVSDirectory.sol"; import {IRemoteChallenger} from "../interfaces/avs/IRemoteChallenger.sol"; -import {ECDSAServiceManagerBase} from "./ECDSAServiceManagerBase.sol"; import {ISlasher} from "../interfaces/avs/vendored/ISlasher.sol"; +import {ECDSAServiceManagerBase} from "./ECDSAServiceManagerBase.sol"; contract HyperlaneServiceManager is ECDSAServiceManagerBase { // ============ Libraries ============ diff --git a/solidity/contracts/interfaces/avs/IRemoteChallenger.sol b/solidity/contracts/interfaces/avs/IRemoteChallenger.sol index 0683cbe08f..0b8ce6bc53 100644 --- a/solidity/contracts/interfaces/avs/IRemoteChallenger.sol +++ b/solidity/contracts/interfaces/avs/IRemoteChallenger.sol @@ -14,6 +14,11 @@ pragma solidity >=0.8.0; @@@@@@@@@ @@@@@@@@*/ interface IRemoteChallenger { + /// @notice Returns the number of blocks that must be mined before a challenge can be handled + /// @return The number of blocks that must be mined before a challenge can be handled function challengeDelayBlocks() external view returns (uint256); + + /// @notice Handles a challenge for an operator + /// @param operator The address of the operator function handleChallenge(address operator) external; } diff --git a/solidity/contracts/interfaces/avs/vendored/IAVSDirectory.sol b/solidity/contracts/interfaces/avs/vendored/IAVSDirectory.sol index 24dfd59d81..d8003a656f 100644 --- a/solidity/contracts/interfaces/avs/vendored/IAVSDirectory.sol +++ b/solidity/contracts/interfaces/avs/vendored/IAVSDirectory.sol @@ -5,7 +5,6 @@ import "./ISignatureUtils.sol"; /// part of mock interfaces for vendoring necessary Eigenlayer contracts for the hyperlane AVS /// @author Layr Labs, Inc. - interface IAVSDirectory is ISignatureUtils { enum OperatorAVSRegistrationStatus { UNREGISTERED, diff --git a/solidity/contracts/interfaces/avs/vendored/IDelegationManager.sol b/solidity/contracts/interfaces/avs/vendored/IDelegationManager.sol index b75785a260..8af9f453a3 100644 --- a/solidity/contracts/interfaces/avs/vendored/IDelegationManager.sol +++ b/solidity/contracts/interfaces/avs/vendored/IDelegationManager.sol @@ -3,9 +3,16 @@ pragma solidity >=0.8.0; import {IStrategy} from "./IStrategy.sol"; -/// part of mock interfaces for vendoring necessary Eigenlayer contracts for the hyperlane AVS -/// @author Layr Labs, Inc. - +/** + * @title DelegationManager + * @author Layr Labs, Inc. + * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service + * @notice This is the contract for delegation in EigenLayer. The main functionalities of this contract are + * - enabling anyone to register as an operator in EigenLayer + * - allowing operators to specify parameters related to stakers who delegate to them + * - enabling any staker to delegate its stake to the operator of its choice (a given staker can only delegate to a single operator at a time) + * - enabling a staker to undelegate its assets from the operator it is delegated to (performed as part of the withdrawal process, initiated through the StrategyManager) + */ interface IDelegationManager { struct OperatorDetails { address earningsReceiver; diff --git a/solidity/contracts/interfaces/avs/vendored/IECDSAStakeRegistryEventsAndErrors.sol b/solidity/contracts/interfaces/avs/vendored/IECDSAStakeRegistryEventsAndErrors.sol index ccdf93ca63..021d34db40 100644 --- a/solidity/contracts/interfaces/avs/vendored/IECDSAStakeRegistryEventsAndErrors.sol +++ b/solidity/contracts/interfaces/avs/vendored/IECDSAStakeRegistryEventsAndErrors.sol @@ -1,9 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.12; -/// part of mock interfaces for vendoring necessary Eigenlayer contracts for the hyperlane AVS -/// @author Layr Labs, Inc. - import {IStrategy} from "./IStrategy.sol"; struct StrategyParams { @@ -15,6 +12,8 @@ struct Quorum { StrategyParams[] strategies; // An array of strategy parameters to define the quorum } +/// part of mock interfaces for vendoring necessary Eigenlayer contracts for the hyperlane AVS +/// @author Layr Labs, Inc. interface IECDSAStakeRegistryEventsAndErrors { /// @notice Emitted when the system registers an operator /// @param _operator The address of the registered operator diff --git a/solidity/contracts/interfaces/avs/vendored/ISignatureUtils.sol b/solidity/contracts/interfaces/avs/vendored/ISignatureUtils.sol index 873349f51d..158b325d17 100644 --- a/solidity/contracts/interfaces/avs/vendored/ISignatureUtils.sol +++ b/solidity/contracts/interfaces/avs/vendored/ISignatureUtils.sol @@ -1,6 +1,11 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity >=0.5.0; +/** + * @title The interface for common signature utilities. + * @author Layr Labs, Inc. + * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service + */ interface ISignatureUtils { // @notice Struct that bundles together a signature and an expiration time for the signature. Used primarily for stack management. struct SignatureWithExpiry { diff --git a/solidity/contracts/interfaces/avs/vendored/ISlasher.sol b/solidity/contracts/interfaces/avs/vendored/ISlasher.sol index f70329edff..577a36eb47 100644 --- a/solidity/contracts/interfaces/avs/vendored/ISlasher.sol +++ b/solidity/contracts/interfaces/avs/vendored/ISlasher.sol @@ -1,6 +1,11 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity >=0.5.0; +/** + * @title Interface for the primary 'slashing' contract for EigenLayer. + * @author Layr Labs, Inc. + * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service + */ interface ISlasher { function freezeOperator(address toBeFrozen) external; } diff --git a/solidity/contracts/test/avs/TestAVSDirectory.sol b/solidity/contracts/test/avs/TestAVSDirectory.sol index bd4a70a2f2..ad028d6a82 100644 --- a/solidity/contracts/test/avs/TestAVSDirectory.sol +++ b/solidity/contracts/test/avs/TestAVSDirectory.sol @@ -4,6 +4,7 @@ pragma solidity >=0.8.0; import {IAVSDirectory} from "../../interfaces/avs/vendored/IAVSDirectory.sol"; import {ISignatureUtils} from "../../interfaces/avs/vendored/ISignatureUtils.sol"; import {ISlasher} from "../../interfaces/avs/vendored/ISlasher.sol"; + import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; contract TestAVSDirectory is IAVSDirectory { From b2e164350840efedd55c862544a11e6e840fef7f Mon Sep 17 00:00:00 2001 From: -f Date: Thu, 9 May 2024 22:15:57 +0530 Subject: [PATCH 32/32] deploy proxyAdmind --- .../contracts/avs/ECDSAServiceManagerBase.sol | 19 +++++-- solidity/script/avs/DeployAVS.s.sol | 49 ++++++++++++------- solidity/script/avs/eigenlayer_addresses.json | 2 - 3 files changed, 48 insertions(+), 22 deletions(-) diff --git a/solidity/contracts/avs/ECDSAServiceManagerBase.sol b/solidity/contracts/avs/ECDSAServiceManagerBase.sol index 5f6c994739..7fdb67fbf4 100644 --- a/solidity/contracts/avs/ECDSAServiceManagerBase.sol +++ b/solidity/contracts/avs/ECDSAServiceManagerBase.sol @@ -25,12 +25,14 @@ abstract contract ECDSAServiceManagerBase is /// @notice Address of the AVS directory contract, which manages AVS-related data for registered operators. address public immutable avsDirectory; - /// @notice Address of the payment coordinator contract, which handles payment distributions. - address internal immutable paymentCoordinator; - /// @notice Address of the delegation manager contract, which manages staker delegations to operators. address internal immutable delegationManager; + // ============ Public Storage ============ + + /// @notice Address of the payment coordinator contract, which handles payment distributions. Will be set once live on Eigenlayer. + address internal paymentCoordinator; + // ============ Modifiers ============ /** @@ -136,6 +138,17 @@ abstract contract ECDSAServiceManagerBase is return _getOperatorRestakedStrategies(_operator); } + /** + * @notice Sets the address of the payment coordinator contract. + * @dev This function is only callable by the contract owner. + * @param _paymentCoordinator The address of the payment coordinator contract. + */ + function setPaymentCoordinator( + address _paymentCoordinator + ) external virtual onlyOwner { + paymentCoordinator = _paymentCoordinator; + } + /** * @notice Forwards the call to update AVS metadata URI in the AVSDirectory contract. * @dev This internal function is a proxy to the `updateAVSMetadataURI` function of the AVSDirectory contract. diff --git a/solidity/script/avs/DeployAVS.s.sol b/solidity/script/avs/DeployAVS.s.sol index c0f66ceeb8..f2fce2d1d8 100644 --- a/solidity/script/avs/DeployAVS.s.sol +++ b/solidity/script/avs/DeployAVS.s.sol @@ -42,11 +42,6 @@ contract DeployAVS is Script { ); string memory json = vm.readFile(path); - proxyAdmin = ProxyAdmin( - json.readAddress( - string(abi.encodePacked(".", targetEnv, ".proxyAdmin")) - ) - ); avsDirectory = IAVSDirectory( json.readAddress( string(abi.encodePacked(".", targetEnv, ".avsDirectory")) @@ -77,7 +72,7 @@ contract DeployAVS is Script { for (uint96 i = 0; i < strategyCount; i++) { // the multipliers need to add up to 10,000, so we divide the total by the number of strategies for the first n-1 strategies // and then the last strategy gets the remainder - if (i < strategies.length - 1) { + if (i < strategyCount - 1) { multiplier = totalMultipliers / uint96(strategyCount); } else { multiplier = @@ -100,12 +95,20 @@ contract DeployAVS is Script { vm.startBroadcast(deployerPrivateKey); + proxyAdmin = new ProxyAdmin(); + ECDSAStakeRegistry stakeRegistryImpl = new ECDSAStakeRegistry( delegationManager ); + TransparentUpgradeableProxy stakeRegistryProxy = new TransparentUpgradeableProxy( + address(stakeRegistryImpl), + address(proxyAdmin), + "" + ); + HyperlaneServiceManager strategyManagerImpl = new HyperlaneServiceManager( address(avsDirectory), - address(stakeRegistryImpl), + address(stakeRegistryProxy), address(paymentCoordinator), address(delegationManager) ); @@ -118,16 +121,28 @@ contract DeployAVS is Script { msg.sender ) ); - TransparentUpgradeableProxy stakeRegistryProxy = new TransparentUpgradeableProxy( - address(stakeRegistryImpl), - address(proxyAdmin), - abi.encodeWithSelector( - ECDSAStakeRegistry.initialize.selector, - address(hsmProxy), - thresholdWeight, - quorum - ) - ); + + // Initialize the ECDSAStakeRegistry once we have the HyperlaneServiceManager proxy + (bool success, ) = address(stakeRegistryProxy).call( + abi.encodeWithSelector( + ECDSAStakeRegistry.initialize.selector, + address(hsmProxy), + thresholdWeight, + quorum + ) + ); + require(success, "Failed to initialize ECDSAStakeRegistry"); + + console.log( + "ECDSAStakeRegistry Implementation: ", + address(stakeRegistryImpl) + ); + console.log( + "HyperlaneServiceManager Implementation: ", + address(strategyManagerImpl) + ); + console.log("StakeRegistry Proxy: ", address(stakeRegistryProxy)); + console.log("HyperlaneServiceManager Proxy: ", address(hsmProxy)); vm.stopBroadcast(); } diff --git a/solidity/script/avs/eigenlayer_addresses.json b/solidity/script/avs/eigenlayer_addresses.json index fa970ed19e..d8890a77be 100644 --- a/solidity/script/avs/eigenlayer_addresses.json +++ b/solidity/script/avs/eigenlayer_addresses.json @@ -1,6 +1,5 @@ { "ethereum": { - "proxyAdmin": "0x75EE15Ee1B4A75Fa3e2fDF5DF3253c25599cc659", "delegationManager": "0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A", "avsDirectory": "0x135DDa560e946695d6f155dACaFC6f1F25C1F5AF", "paymentCoordinator": "", @@ -20,7 +19,6 @@ ] }, "holesky": { - "proxyAdmin": "", "delegationManager": "0xA44151489861Fe9e3055d95adC98FbD462B948e7", "avsDirectory": "0x055733000064333CaDDbC92763c58BF0192fFeBf", "paymentCoordinator": "",