From ddb36ffc7a8e79a8d7ea53e4e26cc9881c06d149 Mon Sep 17 00:00:00 2001 From: pandabadger <33740825+pandabadger@users.noreply.github.com> Date: Tue, 24 Oct 2023 23:51:33 +0100 Subject: [PATCH 01/24] Update DelegationManager.md --- docs/core/DelegationManager.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/core/DelegationManager.md b/docs/core/DelegationManager.md index 3354775d37..3c3bac3acf 100644 --- a/docs/core/DelegationManager.md +++ b/docs/core/DelegationManager.md @@ -375,7 +375,7 @@ Called by the `EigenPodManager` when a Staker's shares decrease. This method is * `EigenPod.verifyAndProcessWithdrawals` *Effects*: If the Staker in question is delegated to an Operator, the Operator's delegated balance for the `strategy` is decreased by `shares` -* This method is a no-op if the Staker is not delegated an an Operator. +* This method is a no-op if the Staker is not delegated as an Operator. *Requirements*: * Caller MUST be either the `StrategyManager` or `EigenPodManager` (although the `StrategyManager` doesn't use this method) @@ -401,4 +401,4 @@ Allows the `owner` to update the number of blocks that must pass before a withdr *Requirements*: * Caller MUST be the `owner` -* `_withdrawalDelayBlocks` MUST NOT be greater than `MAX_WITHDRAWAL_DELAY_BLOCKS` (50400) \ No newline at end of file +* `_withdrawalDelayBlocks` MUST NOT be greater than `MAX_WITHDRAWAL_DELAY_BLOCKS` (50400) From b214d28a85a00088504862588ceaedff4226d66d Mon Sep 17 00:00:00 2001 From: pandabadger <33740825+pandabadger@users.noreply.github.com> Date: Tue, 24 Oct 2023 23:56:48 +0100 Subject: [PATCH 02/24] Update EigenPod.md --- docs/core/EigenPod.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/core/EigenPod.md b/docs/core/EigenPod.md index 6fda750327..f595617f26 100644 --- a/docs/core/EigenPod.md +++ b/docs/core/EigenPod.md @@ -6,7 +6,7 @@ The EigenPods subprotocol is a protocol within the broader EigenLayer protocol t The purpose of this document is to detail the EigenPods subprotocol's functionality since it is complex and not well documented besides the (not so) "self documenting code". In addition, there are some proposed revisions that are to be considered. -Ultimately, the protocol's values are security, functionality, and simplicity. Security means no loss of user funds, no unforseen scenarios for AVSs, and more. Functionality means that the protocol is acheiving its goals with respected to its expected function to the best of its ability (see [functionality goals](#Functionality-Goals) for more). Simplicity means ease of understanding for stakers and AVSs, ease of integration for AVSs, and more confidence in the protocol's security. +Ultimately, the protocol's values are security, functionality, and simplicity. Security means no loss of user funds, no unforeseen scenarios for AVSs, and more. Functionality means that the protocol is achieving its goals with respect to its expected function to the best of its ability (see [functionality goals](#Functionality-Goals) for more). Simplicity means ease of understanding for stakers and AVSs, ease of integration for AVSs, and more confidence in the protocol's security. ## Challenges The reasons native restaking is more complex than token restaking are @@ -32,7 +32,7 @@ This will either be implemented by Succinct Labs or eventually by Ethereum nativ ### Hysteresis -We want to understimate validator balances on EigenLayer to be tolerant to small slashing events that occur on the beacon chain. +We want to underestimate validator balances on EigenLayer to be tolerant to small slashing events that occur on the beacon chain. We underestimate validator stake on EigenLayer through the following equation: $\text{eigenlayerBalance} = \text{min}(32, \text{floor}(x-C))$ where $C$ is some offset which we can assume is equal to 0.75. Since a validator's effective balance on the beacon chain can at most 0.25 ETH more than its actual balance on the beacon chain, we subtract 0.75 and floor it for simplicity. @@ -40,7 +40,7 @@ We underestimate validator stake on EigenLayer through the following equation: $ Any user that wants to participate in native restaking first deploys an EigenPod contract by calling `createPod()` on the EigenPodManager. This deploys an EigenPod contract which is a BeaconProxy in the Beacon Proxy pattern. The user is called the *pod owner* of the EigenPod they deploy. -### Repointing Withdrawan Credentials: BLS to Execution Changes and Deposits +### Repointing Withdrawal Credentials: BLS to Execution Changes and Deposits The precise method by which native restaking occurs is a user repointing their validator's withdrawal credentials. Once an EigenPod is created, a user can make deposits for new validators or switch the withdrawal credentials of their existing validators to their EigenPod address. This makes it so that all funds that are ever withdrawn from the beacon chain on behalf of their validators will be sent to their EigenPod. Since the EigenPod is a contract that runs code as part of the EigenLayer protocol, we engineer it in such a way that the funds that can/will flow through it are restaked on EigenLayer. @@ -58,7 +58,7 @@ We will take a brief aside to explain a simpler part of the protocol, partial wi Note that partial withdrawals can be withdrawn immediately from the system because they are not staked on EigenLayer, unlike the base validator stake that is restaked on EigenLayer -Currently, users can submit partial withdrawal proofs one at a time, at a cost of around 30-40k gas per proof. This is highly inefficient as partial withdrawals are often nominal amounts for which an expensive proof transaction each time is not feasible for our users. The solution is to use succinct zk proving solution generate a single proof for multiple withdrawals, which can be verified for a fixed cost of around 300k gas. This system and associated integration are currently under development. +Currently, users can submit partial withdrawal proofs one at a time, at a cost of around 30-40k gas per proof. This is highly inefficient as partial withdrawals are often nominal amounts for which an expensive proof transaction each time is not feasible for our users. The solution is to use a succinct zk proving solution to generate a single proof for multiple withdrawals, which can be verified for a fixed cost of around 300k gas. This system and associated integration are currently under development. ### Proofs of Validator Balance Updates @@ -75,5 +75,5 @@ Full withdrawals occur when a validator completely exits and withdraws from the If the amount of the withdrawal was greater than what the validator has reestaked on EigenLayer (which most often will be), then the excess is immediately sent to the DelayedWithdrawalRouter to be sent to the pod owner, as this excess balance is not restaked. -Once the "restaked balance" of the pod is incremented, the pod owner is able to queue withdrawals for up to the "restaked balance", decrementing the "restaked balance" by the withdrawal amount. When the withdrawal is completed the pod simply sends a payment to the pod owner for the queued funds. Note that if the withdrawer chooses to recieve the withdrawal as shares, the StrategyManager will increase the "restaked balance" by the withdrawal amount. +Once the "restaked balance" of the pod is incremented, the pod owner is able to queue withdrawals for up to the "restaked balance", decrementing the "restaked balance" by the withdrawal amount. When the withdrawal is completed the pod simply sends a payment to the pod owner for the queued funds. Note that if the withdrawer chooses to receive the withdrawal as shares, the StrategyManager will increase the "restaked balance" by the withdrawal amount. From 62333c1eb4f64a4b31c67d5b395e4d414de03c97 Mon Sep 17 00:00:00 2001 From: pandabadger <33740825+pandabadger@users.noreply.github.com> Date: Wed, 25 Oct 2023 00:01:29 +0100 Subject: [PATCH 03/24] Update BeaconChainProofs.md --- docs/core/proofs/BeaconChainProofs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/core/proofs/BeaconChainProofs.md b/docs/core/proofs/BeaconChainProofs.md index 73a0d8334b..866446cc40 100644 --- a/docs/core/proofs/BeaconChainProofs.md +++ b/docs/core/proofs/BeaconChainProofs.md @@ -50,7 +50,7 @@ function verifyStateRootAgainstLatestBlockRoot( bytes calldata stateRootProof ) internal ``` -Verifies the proof of a beacon state root against the oracle provded block root. Every [beacon block](https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#beaconblock) in the beacon state contains the state root corresponding with that block. Thus to prove anything against a state root, we must first prove the state root against the corresponding oracle block root. +Verifies the proof of a beacon state root against the oracle provided block root. Every [beacon block](https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#beaconblock) in the beacon state contains the state root corresponding with that block. Thus to prove anything against a state root, we must first prove the state root against the corresponding oracle block root. ![Verify State Root Proof Structure](../../images/staterootproof.png) From 812cb1fd96e3f09faaad5774ad5c4bd3d2e0eab3 Mon Sep 17 00:00:00 2001 From: ChaoticWalrus <93558947+ChaoticWalrus@users.noreply.github.com> Date: Thu, 26 Oct 2023 09:11:57 -0700 Subject: [PATCH 04/24] chore: have DelegationManager use stub interface instead of full interface Should help to resolve some of the import hell that we are experiencing. --- src/contracts/core/DelegationManager.sol | 2 +- .../core/DelegationManagerStorage.sol | 2 +- .../interfaces/IDelegationManager.sol | 6 +- .../interfaces/IStakeRegistryStub.sol | 13 ++ src/test/mocks/DelegationManagerMock.sol | 4 +- src/test/mocks/StakeRegistryMock.sol | 151 ------------------ src/test/mocks/StakeRegistryStub.sol | 4 +- src/test/unit/DelegationUnit.t.sol | 8 +- 8 files changed, 27 insertions(+), 163 deletions(-) create mode 100644 src/contracts/interfaces/IStakeRegistryStub.sol delete mode 100644 src/test/mocks/StakeRegistryMock.sol diff --git a/src/contracts/core/DelegationManager.sol b/src/contracts/core/DelegationManager.sol index 2ed222cd3a..ecb594e145 100644 --- a/src/contracts/core/DelegationManager.sol +++ b/src/contracts/core/DelegationManager.sol @@ -85,7 +85,7 @@ contract DelegationManager is Initializable, OwnableUpgradeable, Pausable, Deleg * @param _stakeRegistry is the address of the StakeRegistry contract to call for stake updates when operator shares are changed * @dev Only callable once */ - function setStakeRegistry(IStakeRegistry _stakeRegistry) external onlyOwner { + function setStakeRegistry(IStakeRegistryStub _stakeRegistry) external onlyOwner { require(address(stakeRegistry) == address(0), "DelegationManager.setStakeRegistry: stakeRegistry already set"); require(address(_stakeRegistry) != address(0), "DelegationManager.setStakeRegistry: stakeRegistry cannot be zero address"); stakeRegistry = _stakeRegistry; diff --git a/src/contracts/core/DelegationManagerStorage.sol b/src/contracts/core/DelegationManagerStorage.sol index e05b778425..789db18f0b 100644 --- a/src/contracts/core/DelegationManagerStorage.sol +++ b/src/contracts/core/DelegationManagerStorage.sol @@ -90,7 +90,7 @@ abstract contract DelegationManagerStorage is IDelegationManager { mapping(address => uint256) public cumulativeWithdrawalsQueued; /// @notice the address of the StakeRegistry contract to call for stake updates when operator shares are changed - IStakeRegistry public stakeRegistry; + IStakeRegistryStub public stakeRegistry; constructor(IStrategyManager _strategyManager, ISlasher _slasher, IEigenPodManager _eigenPodManager) { strategyManager = _strategyManager; diff --git a/src/contracts/interfaces/IDelegationManager.sol b/src/contracts/interfaces/IDelegationManager.sol index 94049af3f0..528b62e504 100644 --- a/src/contracts/interfaces/IDelegationManager.sol +++ b/src/contracts/interfaces/IDelegationManager.sol @@ -3,7 +3,7 @@ pragma solidity >=0.5.0; import "./IStrategy.sol"; import "./ISignatureUtils.sol"; -import "./IStakeRegistry.sol"; +import "./IStakeRegistryStub.sol"; import "./IStrategyManager.sol"; /** @@ -71,7 +71,7 @@ interface IDelegationManager is ISignatureUtils { } /// @notice Emitted when the StakeRegistry is set - event StakeRegistrySet(IStakeRegistry stakeRegistry); + event StakeRegistrySet(IStakeRegistryStub stakeRegistry); /** * Struct type used to specify an existing queued withdrawal. Rather than storing the entire struct, only a hash is stored. @@ -311,7 +311,7 @@ interface IDelegationManager is ISignatureUtils { ) external; /// @notice the address of the StakeRegistry contract to call for stake updates when operator shares are changed - function stakeRegistry() external view returns (IStakeRegistry); + function stakeRegistry() external view returns (IStakeRegistryStub); /** * @notice returns the address of the operator that `staker` is delegated to. diff --git a/src/contracts/interfaces/IStakeRegistryStub.sol b/src/contracts/interfaces/IStakeRegistryStub.sol new file mode 100644 index 0000000000..ad64a6785a --- /dev/null +++ b/src/contracts/interfaces/IStakeRegistryStub.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity >=0.5.0; + +import "./IStakeRegistryStub.sol"; + +// @notice Stub interface to avoid circular-ish inheritance, where core contracts rely on middleware interfaces +interface IStakeRegistryStub { + /** + * @notice Used for updating information on deposits of nodes. + * @param operators are the addresses of the operators whose stake information is getting updated + */ + function updateStakes(address[] memory operators) external; +} diff --git a/src/test/mocks/DelegationManagerMock.sol b/src/test/mocks/DelegationManagerMock.sol index df852bb98f..75427229e7 100644 --- a/src/test/mocks/DelegationManagerMock.sol +++ b/src/test/mocks/DelegationManagerMock.sol @@ -9,9 +9,9 @@ import "../../contracts/interfaces/IStrategyManager.sol"; contract DelegationManagerMock is IDelegationManager, Test { mapping(address => bool) public isOperator; mapping(address => mapping(IStrategy => uint256)) public operatorShares; - IStakeRegistry public stakeRegistry; + IStakeRegistryStub public stakeRegistry; - function setStakeRegistry(IStakeRegistry _stakeRegistry) external {} + function setStakeRegistry(IStakeRegistryStub _stakeRegistry) external {} function setIsOperator(address operator, bool _isOperatorReturnValue) external { isOperator[operator] = _isOperatorReturnValue; diff --git a/src/test/mocks/StakeRegistryMock.sol b/src/test/mocks/StakeRegistryMock.sol deleted file mode 100644 index bf9c371df9..0000000000 --- a/src/test/mocks/StakeRegistryMock.sol +++ /dev/null @@ -1,151 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.12; - -import "../../contracts/interfaces/IStakeRegistry.sol"; - -/** - * @title Interface for a `Registry` that keeps track of stakes of operators for up to 256 quorums. - * @author Layr Labs, Inc. - */ -contract StakeRegistryMock is IStakeRegistry { - - function registryCoordinator() external view returns (IRegistryCoordinator) {} - - /** - * @notice Registers the `operator` with `operatorId` for the specified `quorumNumbers`. - * @param operator The address of the operator to register. - * @param operatorId The id of the operator to register. - * @param quorumNumbers The quorum numbers the operator is registering for, where each byte is an 8 bit integer quorumNumber. - * @dev access restricted to the RegistryCoordinator - * @dev Preconditions (these are assumed, not validated in this contract): - * 1) `quorumNumbers` has no duplicates - * 2) `quorumNumbers.length` != 0 - * 3) `quorumNumbers` is ordered in ascending order - * 4) the operator is not already registered - */ - function registerOperator(address operator, bytes32 operatorId, bytes memory quorumNumbers) external {} - - /** - * @notice Deregisters the operator with `operatorId` for the specified `quorumNumbers`. - * @param operatorId The id of the operator to deregister. - * @param quorumNumbers The quorum numbers the operator is deregistering from, where each byte is an 8 bit integer quorumNumber. - * @dev access restricted to the RegistryCoordinator - * @dev Preconditions (these are assumed, not validated in this contract): - * 1) `quorumNumbers` has no duplicates - * 2) `quorumNumbers.length` != 0 - * 3) `quorumNumbers` is ordered in ascending order - * 4) the operator is not already deregistered - * 5) `quorumNumbers` is a subset of the quorumNumbers that the operator is registered for - */ - function deregisterOperator(bytes32 operatorId, bytes memory quorumNumbers) external {} - - /// @notice In order to register for a quorum i, an operator must have at least `minimumStakeForQuorum[i]` - function minimumStakeForQuorum(uint256 quorumNumber) external view returns (uint96) {} - - /** - * @notice Returns the entire `operatorIdToStakeHistory[operatorId][quorumNumber]` array. - * @param operatorId The id of the operator of interest. - * @param quorumNumber The quorum number to get the stake for. - */ - function getOperatorIdToStakeHistory(bytes32 operatorId, uint8 quorumNumber) external view returns (OperatorStakeUpdate[] memory) {} - - function getLengthOfTotalStakeHistoryForQuorum(uint8 quorumNumber) external view returns (uint256) {} - - /** - * @notice Returns the `index`-th entry in the dynamic array of total stake, `totalStakeHistory` for quorum `quorumNumber`. - * @param quorumNumber The quorum number to get the stake for. - * @param index Array index for lookup, within the dynamic array `totalStakeHistory[quorumNumber]`. - */ - function getTotalStakeUpdateForQuorumFromIndex(uint8 quorumNumber, uint256 index) external view returns (OperatorStakeUpdate memory) {} - - /// @notice Returns the indices of the operator stakes for the provided `quorumNumber` at the given `blockNumber` - function getStakeUpdateIndexForOperatorIdForQuorumAtBlockNumber(bytes32 operatorId, uint8 quorumNumber, uint32 blockNumber) - external - view - returns (uint32) {} - - /// @notice Returns the indices of the total stakes for the provided `quorumNumbers` at the given `blockNumber` - function getTotalStakeIndicesByQuorumNumbersAtBlockNumber(uint32 blockNumber, bytes calldata quorumNumbers) external view returns(uint32[] memory) {} - - /** - * @notice Returns the `index`-th entry in the `operatorIdToStakeHistory[operatorId][quorumNumber]` array. - * @param quorumNumber The quorum number to get the stake for. - * @param operatorId The id of the operator of interest. - * @param index Array index for lookup, within the dynamic array `operatorIdToStakeHistory[operatorId][quorumNumber]`. - * @dev Function will revert if `index` is out-of-bounds. - */ - function getStakeUpdateForQuorumFromOperatorIdAndIndex(uint8 quorumNumber, bytes32 operatorId, uint256 index) - external - view - returns (OperatorStakeUpdate memory) {} - - /** - * @notice Returns the most recent stake weight for the `operatorId` for a certain quorum - * @dev Function returns an OperatorStakeUpdate struct with **every entry equal to 0** in the event that the operator has no stake history - */ - function getMostRecentStakeUpdateByOperatorId(bytes32 operatorId, uint8 quorumNumber) external view returns (OperatorStakeUpdate memory) {} - - /** - * @notice Returns the stake weight corresponding to `operatorId` for quorum `quorumNumber`, at the - * `index`-th entry in the `operatorIdToStakeHistory[operatorId][quorumNumber]` array if the entry - * corresponds to the operator's stake at `blockNumber`. Reverts otherwise. - * @param quorumNumber The quorum number to get the stake for. - * @param operatorId The id of the operator of interest. - * @param index Array index for lookup, within the dynamic array `operatorIdToStakeHistory[operatorId][quorumNumber]`. - * @param blockNumber Block number to make sure the stake is from. - * @dev Function will revert if `index` is out-of-bounds. - * @dev used the BLSSignatureChecker to get past stakes of signing operators - */ - function getStakeForQuorumAtBlockNumberFromOperatorIdAndIndex(uint8 quorumNumber, uint32 blockNumber, bytes32 operatorId, uint256 index) - external - view - returns (uint96) {} - - /** - * @notice Returns the total stake weight for quorum `quorumNumber`, at the `index`-th entry in the - * `totalStakeHistory[quorumNumber]` array if the entry corresponds to the total stake at `blockNumber`. - * Reverts otherwise. - * @param quorumNumber The quorum number to get the stake for. - * @param index Array index for lookup, within the dynamic array `totalStakeHistory[quorumNumber]`. - * @param blockNumber Block number to make sure the stake is from. - * @dev Function will revert if `index` is out-of-bounds. - * @dev used the BLSSignatureChecker to get past stakes of signing operators - */ - function getTotalStakeAtBlockNumberFromIndex(uint8 quorumNumber, uint32 blockNumber, uint256 index) external view returns (uint96) {} - - /** - * @notice Returns the most recent stake weight for the `operatorId` for quorum `quorumNumber` - * @dev Function returns weight of **0** in the event that the operator has no stake history - */ - function getCurrentOperatorStakeForQuorum(bytes32 operatorId, uint8 quorumNumber) external view returns (uint96) {} - - /// @notice Returns the stake of the operator for the provided `quorumNumber` at the given `blockNumber` - function getStakeForOperatorIdForQuorumAtBlockNumber(bytes32 operatorId, uint8 quorumNumber, uint32 blockNumber) - external - view - returns (uint96){} - - /** - * @notice Returns the stake weight from the latest entry in `_totalStakeHistory` for quorum `quorumNumber`. - * @dev Will revert if `_totalStakeHistory[quorumNumber]` is empty. - */ - function getCurrentTotalStakeForQuorum(uint8 quorumNumber) external view returns (uint96) {} - - /** - * @notice Used for updating information on deposits of nodes. - * @param operators are the addresses of the operators whose stake information is getting updated - */ - function updateStakes(address[] memory operators) external { - for (uint256 i = 0; i < operators.length; i++) { - emit StakeUpdate( - bytes32(uint256(keccak256(abi.encodePacked(operators[i], "operatorId")))), - uint8(uint256(keccak256(abi.encodePacked(operators[i], i, "quorumNumber")))), - uint96(uint256(keccak256(abi.encodePacked(operators[i], i, "stake")))) - ); - } - } - - function getMockOperatorId(address operator) external pure returns(bytes32) { - return bytes32(uint256(keccak256(abi.encodePacked(operator, "operatorId")))); - } -} \ No newline at end of file diff --git a/src/test/mocks/StakeRegistryStub.sol b/src/test/mocks/StakeRegistryStub.sol index 003adce8c6..1e0c3de622 100644 --- a/src/test/mocks/StakeRegistryStub.sol +++ b/src/test/mocks/StakeRegistryStub.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity =0.8.12; -contract StakeRegistryStub { +import "../../contracts/interfaces/IStakeRegistryStub.sol"; + +contract StakeRegistryStub is IStakeRegistryStub { function updateStakes(address[] memory) external {} } diff --git a/src/test/unit/DelegationUnit.t.sol b/src/test/unit/DelegationUnit.t.sol index 432a5d08b5..6ce25902f4 100644 --- a/src/test/unit/DelegationUnit.t.sol +++ b/src/test/unit/DelegationUnit.t.sol @@ -9,7 +9,7 @@ import "forge-std/Test.sol"; import "../mocks/StrategyManagerMock.sol"; import "../mocks/SlasherMock.sol"; import "../mocks/EigenPodManagerMock.sol"; -import "../mocks/StakeRegistryMock.sol"; +import "../mocks/StakeRegistryStub.sol"; import "../EigenLayerTestHelper.t.sol"; import "../mocks/ERC20Mock.sol"; import "../mocks/Reenterer.sol"; @@ -29,7 +29,7 @@ contract DelegationUnitTests is EigenLayerTestHelper { StrategyBase strategyMock3; IERC20 mockToken; EigenPodManagerMock eigenPodManagerMock; - StakeRegistryMock stakeRegistryMock; + StakeRegistryStub stakeRegistryMock; Reenterer public reenterer; @@ -61,7 +61,7 @@ contract DelegationUnitTests is EigenLayerTestHelper { uint8 internal constant PAUSED_EXIT_WITHDRAWAL_QUEUE = 2; /// @notice Emitted when the StakeRegistry is set - event StakeRegistrySet(IStakeRegistry stakeRegistry); + event StakeRegistrySet(IStakeRegistryStub stakeRegistry); // @notice Emitted when a new operator registers in EigenLayer and provides their OperatorDetails. event OperatorRegistered(address indexed operator, IDelegationManager.OperatorDetails operatorDetails); @@ -171,7 +171,7 @@ contract DelegationUnitTests is EigenLayerTestHelper { ) ); - stakeRegistryMock = new StakeRegistryMock(); + stakeRegistryMock = new StakeRegistryStub(); cheats.expectEmit(true, true, true, true, address(delegationManager)); emit StakeRegistrySet(stakeRegistryMock); From 97a2ddff40b99a94ff79e7fdfc0bd2783dc8338e Mon Sep 17 00:00:00 2001 From: ChaoticWalrus <93558947+ChaoticWalrus@users.noreply.github.com> Date: Thu, 26 Oct 2023 09:58:36 -0700 Subject: [PATCH 05/24] remove middleware interfaces from this repo this commit also removes a ton of detritus from existing tests, which appears to have functionally been doing absolutely nothing. --- script/whitelist/Whitelister.sol | 162 ------------------ .../interfaces/IBLSPublicKeyCompendium.sol | 43 ----- src/contracts/interfaces/IBLSRegistry.sol | 70 -------- src/contracts/interfaces/IDelayedService.sol | 17 -- src/contracts/interfaces/IIndexRegistry.sol | 93 ---------- src/contracts/interfaces/IQuorumRegistry.sol | 155 ----------------- src/contracts/interfaces/IRegistry.sol | 15 -- .../interfaces/IRegistryCoordinator.sol | 97 ----------- src/contracts/interfaces/IServiceManager.sol | 39 ----- src/contracts/interfaces/IStakeRegistry.sol | 160 ----------------- src/contracts/interfaces/IVoteWeigher.sol | 96 ----------- src/test/Delegation.t.sol | 51 ------ src/test/EigenLayerDeployer.t.sol | 1 - src/test/EigenPod.t.sol | 1 - src/test/Withdrawals.t.sol | 91 +--------- src/test/mocks/MiddlewareRegistryMock.sol | 52 ------ src/test/mocks/RegistryCoordinatorMock.sol | 47 ----- src/test/mocks/ServiceManagerMock.sol | 48 ------ src/test/mocks/StrategyManagerMock.sol | 1 - src/test/unit/DelegationUnit.t.sol | 29 ---- 20 files changed, 4 insertions(+), 1264 deletions(-) delete mode 100644 script/whitelist/Whitelister.sol delete mode 100644 src/contracts/interfaces/IBLSPublicKeyCompendium.sol delete mode 100644 src/contracts/interfaces/IBLSRegistry.sol delete mode 100644 src/contracts/interfaces/IDelayedService.sol delete mode 100644 src/contracts/interfaces/IIndexRegistry.sol delete mode 100644 src/contracts/interfaces/IQuorumRegistry.sol delete mode 100644 src/contracts/interfaces/IRegistry.sol delete mode 100644 src/contracts/interfaces/IRegistryCoordinator.sol delete mode 100644 src/contracts/interfaces/IServiceManager.sol delete mode 100644 src/contracts/interfaces/IStakeRegistry.sol delete mode 100644 src/contracts/interfaces/IVoteWeigher.sol delete mode 100644 src/test/mocks/MiddlewareRegistryMock.sol delete mode 100644 src/test/mocks/RegistryCoordinatorMock.sol delete mode 100644 src/test/mocks/ServiceManagerMock.sol diff --git a/script/whitelist/Whitelister.sol b/script/whitelist/Whitelister.sol deleted file mode 100644 index 1dfa8400b4..0000000000 --- a/script/whitelist/Whitelister.sol +++ /dev/null @@ -1,162 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.12; - -import "../../src/contracts/interfaces/IStrategyManager.sol"; -import "../../src/contracts/interfaces/IStrategy.sol"; -import "../../src/contracts/interfaces/IDelegationManager.sol"; -import "../../src/contracts/interfaces/IBLSRegistry.sol"; -import "../../src/contracts/interfaces/IWhitelister.sol"; -import "./Staker.sol"; - - -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "./ERC20PresetMinterPauser.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/utils/Create2.sol"; - - -contract Whitelister is IWhitelister, Ownable { - //address constant strategyManager = 0x0000000000000000000000000000000000000000; - //TODO: change before deploy - IStrategyManager immutable strategyManager; - ERC20PresetMinterPauser immutable stakeToken; - IStrategy immutable stakeStrategy; - IDelegationManager delegation; - - IBLSRegistry immutable registry; - - uint256 public constant DEFAULT_AMOUNT = 100e18; - - //TODO: Deploy ERC20PresetMinterPauser and a corresponding StrategyBase for it - //TODO: Transfer ownership of Whitelister to multisig after deployment - //TODO: Give mint/admin/pauser permssions of whitelistToken to Whitelister and multisig after deployment - //TODO: Give up mint/admin/pauser permssions of whitelistToken for deployer - constructor( - IStrategyManager _strategyManager, - IDelegationManager _delegation, - ERC20PresetMinterPauser _token, - IStrategy _strategy, - IBLSRegistry _registry - ) { - strategyManager = _strategyManager; - delegation = _delegation; - stakeToken = _token; - stakeStrategy = _strategy; - - registry = _registry; - } - - function whitelist(address operator) public onlyOwner { - // mint the staker the tokens - stakeToken.mint(getStaker(operator), DEFAULT_AMOUNT); - // deploy the staker - Create2.deploy( - 0, - bytes32(uint256(uint160(operator))), - abi.encodePacked( - type(Staker).creationCode, - abi.encode( - stakeStrategy, - strategyManager, - delegation, - stakeToken, - DEFAULT_AMOUNT, - operator - ) - ) - ); - - // add operator to whitelist - address[] memory operators = new address[](1); - operators[0] = operator; - registry.addToOperatorWhitelist(operators); - } - - function getStaker(address operator) public view returns (address) { - return - Create2.computeAddress( - bytes32(uint256(uint160(operator))), //salt - keccak256( - abi.encodePacked( - type(Staker).creationCode, - abi.encode( - stakeStrategy, - strategyManager, - delegation, - stakeToken, - DEFAULT_AMOUNT, - operator - ) - ) - ) - ); - } - - function depositIntoStrategy( - address staker, - IStrategy strategy, - IERC20 token, - uint256 amount - ) public onlyOwner returns (bytes memory) { - - bytes memory data = abi.encodeWithSelector( - IStrategyManager.depositIntoStrategy.selector, - strategy, - token, - amount - ); - - return Staker(staker).callAddress(address(strategyManager), data); - } - - function queueWithdrawal( - address staker, - IDelegationManager.QueuedWithdrawalParams[] calldata queuedWithdrawalParams - ) public onlyOwner returns (bytes memory) { - bytes memory data = abi.encodeWithSelector( - IDelegationManager.queueWithdrawals.selector, - queuedWithdrawalParams - ); - return Staker(staker).callAddress(address(delegation), data); - } - - function completeQueuedWithdrawal( - address staker, - IDelegationManager.Withdrawal calldata queuedWithdrawal, - IERC20[] calldata tokens, - uint256 middlewareTimesIndex, - bool receiveAsTokens - ) public onlyOwner returns (bytes memory) { - bytes memory data = abi.encodeWithSelector( - IDelegationManager.completeQueuedWithdrawal.selector, - queuedWithdrawal, - tokens, - middlewareTimesIndex, - receiveAsTokens - ); - - return Staker(staker).callAddress(address(delegation), data); - } - - function transfer( - address staker, - address token, - address to, - uint256 amount - ) public onlyOwner returns (bytes memory) { - bytes memory data = abi.encodeWithSelector(IERC20.transfer.selector, to, amount); - - return Staker(staker).callAddress(token, data); - } - - function callAddress( - address to, - bytes memory data - ) public onlyOwner payable returns (bytes memory) { - (bool ok, bytes memory res) = payable(to).call{value: msg.value}(data); - if (!ok) { - revert(string(res)); - } - return res; - } -} \ No newline at end of file diff --git a/src/contracts/interfaces/IBLSPublicKeyCompendium.sol b/src/contracts/interfaces/IBLSPublicKeyCompendium.sol deleted file mode 100644 index 032528ecf6..0000000000 --- a/src/contracts/interfaces/IBLSPublicKeyCompendium.sol +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity >=0.5.0; - -import "../libraries/BN254.sol"; - -/** - * @title Minimal interface for the `BLSPublicKeyCompendium` contract. - * @author Layr Labs, Inc. - * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service - */ -interface IBLSPublicKeyCompendium { - - // EVENTS - /// @notice Emitted when `operator` registers with the public key `pk`. - event NewPubkeyRegistration(address indexed operator, BN254.G1Point pubkeyG1, BN254.G2Point pubkeyG2); - - /** - * @notice mapping from operator address to pubkey hash. - * Returns *zero* if the `operator` has never registered, and otherwise returns the hash of the public key of the operator. - */ - function operatorToPubkeyHash(address operator) external view returns (bytes32); - - /** - * @notice mapping from pubkey hash to operator address. - * Returns *zero* if no operator has ever registered the public key corresponding to `pubkeyHash`, - * and otherwise returns the (unique) registered operator who owns the BLS public key that is the preimage of `pubkeyHash`. - */ - function pubkeyHashToOperator(bytes32 pubkeyHash) external view returns (address); - - /** - * @notice Called by an operator to register themselves as the owner of a BLS public key and reveal their G1 and G2 public key. - * @param signedMessageHash is the registration message hash signed by the private key of the operator - * @param pubkeyG1 is the corresponding G1 public key of the operator - * @param pubkeyG2 is the corresponding G2 public key of the operator - */ - function registerBLSPublicKey(BN254.G1Point memory signedMessageHash, BN254.G1Point memory pubkeyG1, BN254.G2Point memory pubkeyG2) external; - - /** - * @notice Returns the message hash that an operator must sign to register their BLS public key. - * @param operator is the address of the operator registering their BLS public key - */ - function getMessageHash(address operator) external view returns (BN254.G1Point memory); -} diff --git a/src/contracts/interfaces/IBLSRegistry.sol b/src/contracts/interfaces/IBLSRegistry.sol deleted file mode 100644 index 2ad7a4f550..0000000000 --- a/src/contracts/interfaces/IBLSRegistry.sol +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity >=0.5.0; - -import "./IQuorumRegistry.sol"; -import "../libraries/BN254.sol"; - - -/** - * @title Minimal interface extension to `IQuorumRegistry`. - * @author Layr Labs, Inc. - * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service - * @notice Adds BLS-specific functions to the base interface. - */ -interface IBLSRegistry is IQuorumRegistry { - /// @notice Data structure used to track the history of the Aggregate Public Key of all operators - struct ApkUpdate { - // keccak256(apk_x0, apk_x1, apk_y0, apk_y1) - bytes32 apkHash; - // block number at which the update occurred - uint32 blockNumber; - } - - // EVENTS - /** - * @notice Emitted upon the registration of a new operator for the middleware - * @param operator Address of the new operator - * @param pkHash The keccak256 hash of the operator's public key - * @param pk The operator's public key itself - * @param apkHashIndex The index of the latest (i.e. the new) APK update - * @param apkHash The keccak256 hash of the new Aggregate Public Key - */ - event Registration( - address indexed operator, - bytes32 pkHash, - BN254.G1Point pk, - uint32 apkHashIndex, - bytes32 apkHash, - string socket - ); - - /// @notice Emitted when the `operatorWhitelister` role is transferred. - event OperatorWhitelisterTransferred(address previousAddress, address newAddress); - - /** - * @notice get hash of a historical aggregated public key corresponding to a given index; - * called by checkSignatures in BLSSignatureChecker.sol. - */ - function getCorrectApkHash(uint256 index, uint32 blockNumber) external returns (bytes32); - - /// @notice returns the `ApkUpdate` struct at `index` in the list of APK updates - function apkUpdates(uint256 index) external view returns (ApkUpdate memory); - - /// @notice returns the APK hash that resulted from the `index`th APK update - function apkHashes(uint256 index) external view returns (bytes32); - - /// @notice returns the block number at which the `index`th APK update occurred - function apkUpdateBlockNumbers(uint256 index) external view returns (uint32); - - function operatorWhitelister() external view returns (address); - - function operatorWhitelistEnabled() external view returns (bool); - - function whitelisted(address) external view returns (bool); - - function setOperatorWhitelistStatus(bool _operatorWhitelistEnabled) external; - - function addToOperatorWhitelist(address[] calldata) external; - - function removeFromWhitelist(address[] calldata operators) external; -} diff --git a/src/contracts/interfaces/IDelayedService.sol b/src/contracts/interfaces/IDelayedService.sol deleted file mode 100644 index bccacd69bd..0000000000 --- a/src/contracts/interfaces/IDelayedService.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity >=0.5.0; - -/** - * @title Interface for a middleware / service that may look at past stake amounts. - * @author Layr Labs, Inc. - * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service - * @notice Specifically, this interface is designed for services that consult stake amounts up to `BLOCK_STALE_MEASURE` - * blocks in the past. This may be necessary due to, e.g., network processing & communication delays, or to avoid race conditions - * that could be present with coordinating aggregate operator signatures while service operators are registering & de-registering. - * @dev To clarify edge cases, the middleware can look `BLOCK_STALE_MEASURE` blocks into the past, i.e. it may trust stakes from the interval - * [block.number - BLOCK_STALE_MEASURE, block.number] (specifically, *inclusive* of the block that is `BLOCK_STALE_MEASURE` before the current one) - */ -interface IDelayedService { - /// @notice The maximum amount of blocks in the past that the service will consider stake amounts to still be 'valid'. - function BLOCK_STALE_MEASURE() external view returns (uint32); -} diff --git a/src/contracts/interfaces/IIndexRegistry.sol b/src/contracts/interfaces/IIndexRegistry.sol deleted file mode 100644 index 30a95998c2..0000000000 --- a/src/contracts/interfaces/IIndexRegistry.sol +++ /dev/null @@ -1,93 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.12; - -import "./IRegistry.sol"; - -/** - * @title Interface for a `Registry`-type contract that keeps track of an ordered list of operators for up to 256 quorums. - * @author Layr Labs, Inc. - */ -interface IIndexRegistry is IRegistry { - // EVENTS - - // emitted when an operator's index in the orderd operator list for the quorum with number `quorumNumber` is updated - event QuorumIndexUpdate(bytes32 indexed operatorId, uint8 quorumNumber, uint32 newIndex); - - // DATA STRUCTURES - - // struct used to give definitive ordering to operators at each blockNumber. - // NOTE: this struct is slightly abused for also storing the total number of operators for each quorum over time - struct OperatorIndexUpdate { - // blockNumber number from which `index` was the operators index - // the operator's index or the total number of operators at a `blockNumber` is the first entry such that `blockNumber >= entry.fromBlockNumber` - uint32 fromBlockNumber; - // index of the operator in array of operators, or the total number of operators if in the 'totalOperatorsHistory' - // index = type(uint32).max = OPERATOR_DEREGISTERED_INDEX implies the operator was deregistered - uint32 index; - } - - /** - * @notice Registers the operator with the specified `operatorId` for the quorums specified by `quorumNumbers`. - * @param operatorId is the id of the operator that is being registered - * @param quorumNumbers is the quorum numbers the operator is registered for - * @return numOperatorsPerQuorum is a list of the number of operators (including the registering operator) in each of the quorums the operator is registered for - * @dev access restricted to the RegistryCoordinator - * @dev Preconditions (these are assumed, not validated in this contract): - * 1) `quorumNumbers` has no duplicates - * 2) `quorumNumbers.length` != 0 - * 3) `quorumNumbers` is ordered in ascending order - * 4) the operator is not already registered - */ - function registerOperator(bytes32 operatorId, bytes calldata quorumNumbers) external returns(uint32[] memory); - - /** - * @notice Deregisters the operator with the specified `operatorId` for the quorums specified by `quorumNumbers`. - * @param operatorId is the id of the operator that is being deregistered - * @param quorumNumbers is the quorum numbers the operator is deregistered for - * @param operatorIdsToSwap is the list of operatorIds that have the largest indexes in each of the `quorumNumbers` - * they will be swapped with the operator's current index when the operator is removed from the list - * @dev access restricted to the RegistryCoordinator - * @dev Preconditions (these are assumed, not validated in this contract): - * 1) `quorumNumbers` has no duplicates - * 2) `quorumNumbers.length` != 0 - * 3) `quorumNumbers` is ordered in ascending order - * 4) the operator is not already deregistered - * 5) `quorumNumbers` is a subset of the quorumNumbers that the operator is registered for - */ - function deregisterOperator(bytes32 operatorId, bytes calldata quorumNumbers, bytes32[] memory operatorIdsToSwap) external; - - /// @notice Returns the length of the globalOperatorList - function getGlobalOperatorListLength() external view returns (uint256); - - /// @notice Returns the _operatorIdToIndexHistory entry for the specified `operatorId` and `quorumNumber` at the specified `index` - function getOperatorIndexUpdateOfOperatorIdForQuorumAtIndex(bytes32 operatorId, uint8 quorumNumber, uint32 index) external view returns (OperatorIndexUpdate memory); - - /// @notice Returns the _totalOperatorsHistory entry for the specified `quorumNumber` at the specified `index` - function getTotalOperatorsUpdateForQuorumAtIndex(uint8 quorumNumber, uint32 index) external view returns (OperatorIndexUpdate memory); - - /** - * @notice Looks up the `operator`'s index for `quorumNumber` at the specified `blockNumber` using the `index`. - * @param operatorId is the id of the operator for which the index is desired - * @param quorumNumber is the quorum number for which the operator index is desired - * @param blockNumber is the block number at which the index of the operator is desired - * @param index Used to specify the entry within the dynamic array `operatorIdToIndexHistory[operatorId]` to - * read data from - * @dev Function will revert in the event that the specified `index` input does not identify the appropriate entry in the - * array `operatorIdToIndexHistory[operatorId][quorumNumber]` to pull the info from. - */ - function getOperatorIndexForQuorumAtBlockNumberByIndex(bytes32 operatorId, uint8 quorumNumber, uint32 blockNumber, uint32 index) external view returns (uint32); - - /** - * @notice Looks up the number of total operators for `quorumNumber` at the specified `blockNumber`. - * @param quorumNumber is the quorum number for which the total number of operators is desired - * @param blockNumber is the block number at which the total number of operators is desired - * @param index is the index of the entry in the dynamic array `totalOperatorsHistory[quorumNumber]` to read data from - */ - function getTotalOperatorsForQuorumAtBlockNumberByIndex(uint8 quorumNumber, uint32 blockNumber, uint32 index) external view returns (uint32); - - /// @notice Returns the current number of operators of this service for `quorumNumber`. - function totalOperatorsForQuorum(uint8 quorumNumber) external view returns (uint32); - - /// @notice Returns an ordered list of operators of the services for the given `quorumNumber` at the given `blockNumber` - function getOperatorListForQuorumAtBlockNumber(uint8 quorumNumber, uint32 blockNumber) external view returns (bytes32[] memory); -} \ No newline at end of file diff --git a/src/contracts/interfaces/IQuorumRegistry.sol b/src/contracts/interfaces/IQuorumRegistry.sol deleted file mode 100644 index d59dfcf169..0000000000 --- a/src/contracts/interfaces/IQuorumRegistry.sol +++ /dev/null @@ -1,155 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity >=0.5.0; - -import "./IRegistry.sol"; - -/** - * @title Interface for a `Registry`-type contract that uses either 1 or 2 quorums. - * @author Layr Labs, Inc. - * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service - * @notice This contract does not currently support n-quorums where n >= 3. - * Note in particular the presence of only `firstQuorumStake` and `secondQuorumStake` in the `OperatorStake` struct. - */ -interface IQuorumRegistry is IRegistry { - // DATA STRUCTURES - enum Status { - // default is inactive - INACTIVE, - ACTIVE - } - - /** - * @notice Data structure for storing info on operators to be used for: - * - sending data by the sequencer - * - payment and associated challenges - */ - struct Operator { - // hash of pubkey of the operator - bytes32 pubkeyHash; - // start taskNumber from which the operator has been registered - uint32 fromTaskNumber; - // indicates whether the operator is actively registered for serving the middleware or not - Status status; - } - - // struct used to give definitive ordering to operators at each blockNumber - struct OperatorIndex { - // blockNumber number at which operator index changed - // note that the operator's index is different *for this block number*, i.e. the *new* index is *inclusive* of this value - uint32 toBlockNumber; - // index of the operator in array of operators, or the total number of operators if in the 'totalOperatorsHistory' - uint32 index; - } - - /// @notice struct used to store the stakes of an individual operator or the sum of all operators' stakes, for storage - struct OperatorStake { - // the block number at which the stake amounts were updated and stored - uint32 updateBlockNumber; - // the block number at which the *next update* occurred. - /// @notice This entry has the value **0** until another update takes place. - uint32 nextUpdateBlockNumber; - // stake weight for the first quorum - uint96 firstQuorumStake; - // stake weight for the second quorum. Will always be zero in the event that only one quorum is used - uint96 secondQuorumStake; - } - - function getLengthOfTotalStakeHistory() external view returns (uint256); - - /** - * @notice Returns the `index`-th entry in the dynamic array of total stake, `totalStakeHistory`. - * @dev Function will revert in the event that `index` is out-of-bounds. - */ - function getTotalStakeFromIndex(uint256 index) external view returns (OperatorStake memory); - - /// @notice Returns the stored pubkeyHash for the specified `operator`. - function getOperatorPubkeyHash(address operator) external view returns (bytes32); - - /// @notice Returns task number from when `operator` has been registered. - function getFromTaskNumberForOperator(address operator) external view returns (uint32); - - /** - * @notice Returns the stake weight corresponding to `pubkeyHash`, at the - * `index`-th entry in the `pubkeyHashToStakeHistory[pubkeyHash]` array. - * @param pubkeyHash Hash of the public key of the operator of interest. - * @param index Array index for lookup, within the dynamic array `pubkeyHashToStakeHistory[pubkeyHash]`. - * @dev Function will revert if `index` is out-of-bounds. - */ - function getStakeFromPubkeyHashAndIndex( - bytes32 pubkeyHash, - uint256 index - ) external view returns (OperatorStake memory); - - /** - * @notice Checks that the `operator` was active at the `blockNumber`, using the specified `stakeHistoryIndex` as proof. - * @param operator is the operator of interest - * @param blockNumber is the block number of interest - * @param stakeHistoryIndex specifies an index in `pubkeyHashToStakeHistory[pubkeyHash]`, where `pubkeyHash` is looked up - * in `registry[operator].pubkeyHash` - * @return 'true' if it is successfully proven that the `operator` was active at the `blockNumber`, and 'false' otherwise - * @dev In order for this function to return 'true', the inputs must satisfy all of the following list: - * 1) `pubkeyHashToStakeHistory[pubkeyHash][index].updateBlockNumber <= blockNumber` - * 2) `pubkeyHashToStakeHistory[pubkeyHash][index].nextUpdateBlockNumber` must be either `0` (signifying no next update) or - * is must be strictly greater than `blockNumber` - * 3) `pubkeyHashToStakeHistory[pubkeyHash][index].firstQuorumStake > 0` - * or `pubkeyHashToStakeHistory[pubkeyHash][index].secondQuorumStake > 0`, i.e. the operator had nonzero stake - * @dev Note that a return value of 'false' does not guarantee that the `operator` was inactive at `blockNumber`, since a - * bad `stakeHistoryIndex` can be supplied in order to obtain a response of 'false'. - */ - function checkOperatorActiveAtBlockNumber( - address operator, - uint256 blockNumber, - uint256 stakeHistoryIndex - ) external view returns (bool); - - /** - * @notice Checks that the `operator` was inactive at the `blockNumber`, using the specified `stakeHistoryIndex` as proof. - * @param operator is the operator of interest - * @param blockNumber is the block number of interest - * @param stakeHistoryIndex specifies an index in `pubkeyHashToStakeHistory[pubkeyHash]`, where `pubkeyHash` is looked up - * in `registry[operator].pubkeyHash` - * @return 'true' if it is successfully proven that the `operator` was inactive at the `blockNumber`, and 'false' otherwise - * @dev In order for this function to return 'true', the inputs must satisfy all of the following list: - * 1) `pubkeyHashToStakeHistory[pubkeyHash][index].updateBlockNumber <= blockNumber` - * 2) `pubkeyHashToStakeHistory[pubkeyHash][index].nextUpdateBlockNumber` must be either `0` (signifying no next update) or - * is must be strictly greater than `blockNumber` - * 3) `pubkeyHashToStakeHistory[pubkeyHash][index].firstQuorumStake > 0` - * or `pubkeyHashToStakeHistory[pubkeyHash][index].secondQuorumStake > 0`, i.e. the operator had nonzero stake - * @dev Note that a return value of 'false' does not guarantee that the `operator` was active at `blockNumber`, since a - * bad `stakeHistoryIndex` can be supplied in order to obtain a response of 'false'. - */ - function checkOperatorInactiveAtBlockNumber( - address operator, - uint256 blockNumber, - uint256 stakeHistoryIndex - ) external view returns (bool); - - /** - * @notice Looks up the `operator`'s index in the dynamic array `operatorList` at the specified `blockNumber`. - * @param index Used to specify the entry within the dynamic array `pubkeyHashToIndexHistory[pubkeyHash]` to - * read data from, where `pubkeyHash` is looked up from `operator`'s registration info - * @param blockNumber Is the desired block number at which we wish to query the operator's position in the `operatorList` array - * @dev Function will revert in the event that the specified `index` input does not identify the appropriate entry in the - * array `pubkeyHashToIndexHistory[pubkeyHash]` to pull the info from. - */ - function getOperatorIndex(address operator, uint32 blockNumber, uint32 index) external view returns (uint32); - - /** - * @notice Looks up the number of total operators at the specified `blockNumber`. - * @param index Input used to specify the entry within the dynamic array `totalOperatorsHistory` to read data from. - * @dev This function will revert if the provided `index` is out of bounds. - */ - function getTotalOperators(uint32 blockNumber, uint32 index) external view returns (uint32); - - /// @notice Returns the current number of operators of this service. - function numOperators() external view returns (uint32); - - /** - * @notice Returns the most recent stake weights for the `operator` - * @dev Function returns weights of **0** in the event that the operator has no stake history - */ - function operatorStakes(address operator) external view returns (uint96, uint96); - - /// @notice Returns the stake amounts from the latest entry in `totalStakeHistory`. - function totalStake() external view returns (uint96, uint96); -} diff --git a/src/contracts/interfaces/IRegistry.sol b/src/contracts/interfaces/IRegistry.sol deleted file mode 100644 index 49e7d5456e..0000000000 --- a/src/contracts/interfaces/IRegistry.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity >=0.5.0; - -import "./IRegistryCoordinator.sol"; - -/** - * @title Minimal interface for a `Registry`-type contract. - * @author Layr Labs, Inc. - * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service - * @notice Functions related to the registration process itself have been intentionally excluded - * because their function signatures may vary significantly. - */ -interface IRegistry { - function registryCoordinator() external view returns (IRegistryCoordinator); -} diff --git a/src/contracts/interfaces/IRegistryCoordinator.sol b/src/contracts/interfaces/IRegistryCoordinator.sol deleted file mode 100644 index 3ee5b1a9fd..0000000000 --- a/src/contracts/interfaces/IRegistryCoordinator.sol +++ /dev/null @@ -1,97 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.12; - -/** - * @title Interface for a contract that coordinates between various registries for an AVS. - * @author Layr Labs, Inc. - */ -interface IRegistryCoordinator { - // EVENTS - /// Emits when an operator is registered - event OperatorRegistered(address indexed operator, bytes32 indexed operatorId); - - /// Emits when an operator is deregistered - event OperatorDeregistered(address indexed operator, bytes32 indexed operatorId); - - // DATA STRUCTURES - enum OperatorStatus - { - // default is NEVER_REGISTERED - NEVER_REGISTERED, - REGISTERED, - DEREGISTERED - } - - // STRUCTS - - /** - * @notice Data structure for storing info on operators - */ - struct Operator { - // the id of the operator, which is likely the keccak256 hash of the operator's public key if using BLSRegsitry - bytes32 operatorId; - // indicates whether the operator is actively registered for serving the middleware or not - OperatorStatus status; - } - - /** - * @notice Data structure for storing info on quorum bitmap updates where the `quorumBitmap` is the bitmap of the - * quorums the operator is registered for starting at (inclusive)`updateBlockNumber` and ending at (exclusive) `nextUpdateBlockNumber` - * @dev nextUpdateBlockNumber is initialized to 0 for the latest update - */ - struct QuorumBitmapUpdate { - uint32 updateBlockNumber; - uint32 nextUpdateBlockNumber; - uint192 quorumBitmap; - } - - /// @notice Returns the operator struct for the given `operator` - function getOperator(address operator) external view returns (Operator memory); - - /// @notice Returns the operatorId for the given `operator` - function getOperatorId(address operator) external view returns (bytes32); - - /// @notice Returns the operator address for the given `operatorId` - function getOperatorFromId(bytes32 operatorId) external view returns (address operator); - - /// @notice Returns the status for the given `operator` - function getOperatorStatus(address operator) external view returns (IRegistryCoordinator.OperatorStatus); - - /// @notice Returns the indices of the quorumBitmaps for the provided `operatorIds` at the given `blockNumber` - function getQuorumBitmapIndicesByOperatorIdsAtBlockNumber(uint32 blockNumber, bytes32[] memory operatorIds) external view returns (uint32[] memory); - - /** - * @notice Returns the quorum bitmap for the given `operatorId` at the given `blockNumber` via the `index` - * @dev reverts if `index` is incorrect - */ - function getQuorumBitmapByOperatorIdAtBlockNumberByIndex(bytes32 operatorId, uint32 blockNumber, uint256 index) external view returns (uint192); - - /// @notice Returns the `index`th entry in the operator with `operatorId`'s bitmap history - function getQuorumBitmapUpdateByOperatorIdByIndex(bytes32 operatorId, uint256 index) external view returns (QuorumBitmapUpdate memory); - - /// @notice Returns the current quorum bitmap for the given `operatorId` - function getCurrentQuorumBitmapByOperatorId(bytes32 operatorId) external view returns (uint192); - - /// @notice Returns the length of the quorum bitmap history for the given `operatorId` - function getQuorumBitmapUpdateByOperatorIdLength(bytes32 operatorId) external view returns (uint256); - - /// @notice Returns the registry at the desired index - function registries(uint256) external view returns (address); - - /// @notice Returns the number of registries - function numRegistries() external view returns (uint256); - - /** - * @notice Registers msg.sender as an operator with the middleware - * @param quorumNumbers are the bytes representing the quorum numbers that the operator is registering for - * @param registrationData is the data that is decoded to get the operator's registration information - */ - function registerOperatorWithCoordinator(bytes memory quorumNumbers, bytes calldata registrationData) external; - - /** - * @notice Deregisters the msg.sender as an operator from the middleware - * @param quorumNumbers are the bytes representing the quorum numbers that the operator is registered for - * @param deregistrationData is the the data that is decoded to get the operator's deregistration information - */ - function deregisterOperatorWithCoordinator(bytes calldata quorumNumbers, bytes calldata deregistrationData) external; -} \ No newline at end of file diff --git a/src/contracts/interfaces/IServiceManager.sol b/src/contracts/interfaces/IServiceManager.sol deleted file mode 100644 index d137a42a40..0000000000 --- a/src/contracts/interfaces/IServiceManager.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity >=0.5.0; - -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "./ISlasher.sol"; - -/** - * @title Interface for a `ServiceManager`-type contract. - * @author Layr Labs, Inc. - * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service - */ -interface IServiceManager { - - // ServiceManager proxies to the slasher - function slasher() external view returns (ISlasher); - - /// @notice Returns the current 'taskNumber' for the middleware - function taskNumber() external view returns (uint32); - - /// @notice function that causes the ServiceManager to freeze the operator on EigenLayer, through a call to the Slasher contract - /// @dev this function should contain slashing logic, to make sure operators are not needlessly being slashed - function freezeOperator(address operator) external; - - /// @notice proxy call to the slasher, recording an initial stake update (on operator registration) - function recordFirstStakeUpdate(address operator, uint32 serveUntilBlock) external; - - /// @notice proxy call to the slasher, recording a stake update - function recordStakeUpdate(address operator, uint32 updateBlock, uint32 serveUntilBlock, uint256 prevElement) external; - - /// @notice proxy call to the slasher, recording a final stake update (on operator deregistration) - function recordLastStakeUpdateAndRevokeSlashingAbility(address operator, uint32 serveUntilBlock) external; - - /// @notice Returns the latest block until which operators must serve (could be in the past or future). - /// @dev this should be called and the response passed to the recordStakeUpdate functionss' serveUntilBlock parameter - function latestServeUntilBlock() external view returns (uint32); - - /// @notice required since the registry contract will call this function to permission its upgrades to be done by the same owner as the service manager - function owner() external view returns (address); -} diff --git a/src/contracts/interfaces/IStakeRegistry.sol b/src/contracts/interfaces/IStakeRegistry.sol deleted file mode 100644 index 078eb5709d..0000000000 --- a/src/contracts/interfaces/IStakeRegistry.sol +++ /dev/null @@ -1,160 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.12; - -import "./IRegistry.sol"; - -/** - * @title Interface for a `Registry` that keeps track of stakes of operators for up to 256 quorums. - * @author Layr Labs, Inc. - */ -interface IStakeRegistry is IRegistry { - // EVENTS - /// @notice emitted whenever the stake of `operator` is updated - event StakeUpdate( - bytes32 indexed operatorId, - uint8 quorumNumber, - uint96 stake - ); - - // DATA STRUCTURES - - /// @notice struct used to store the stakes of an individual operator or the sum of all operators' stakes, for storage - struct OperatorStakeUpdate { - // the block number at which the stake amounts were updated and stored - uint32 updateBlockNumber; - // the block number at which the *next update* occurred. - /// @notice This entry has the value **0** until another update takes place. - uint32 nextUpdateBlockNumber; - // stake weight for the quorum - uint96 stake; - } - - // EVENTS - event MinimumStakeForQuorumUpdated(uint8 indexed quorumNumber, uint96 minimumStake); - - /** - * @notice Registers the `operator` with `operatorId` for the specified `quorumNumbers`. - * @param operator The address of the operator to register. - * @param operatorId The id of the operator to register. - * @param quorumNumbers The quorum numbers the operator is registering for, where each byte is an 8 bit integer quorumNumber. - * @dev access restricted to the RegistryCoordinator - * @dev Preconditions (these are assumed, not validated in this contract): - * 1) `quorumNumbers` has no duplicates - * 2) `quorumNumbers.length` != 0 - * 3) `quorumNumbers` is ordered in ascending order - * 4) the operator is not already registered - */ - function registerOperator(address operator, bytes32 operatorId, bytes memory quorumNumbers) external; - - /** - * @notice Deregisters the operator with `operatorId` for the specified `quorumNumbers`. - * @param operatorId The id of the operator to deregister. - * @param quorumNumbers The quorum numbers the operator is deregistering from, where each byte is an 8 bit integer quorumNumber. - * @dev access restricted to the RegistryCoordinator - * @dev Preconditions (these are assumed, not validated in this contract): - * 1) `quorumNumbers` has no duplicates - * 2) `quorumNumbers.length` != 0 - * 3) `quorumNumbers` is ordered in ascending order - * 4) the operator is not already deregistered - * 5) `quorumNumbers` is a subset of the quorumNumbers that the operator is registered for - */ - function deregisterOperator(bytes32 operatorId, bytes memory quorumNumbers) external; - - /// @notice In order to register for a quorum i, an operator must have at least `minimumStakeForQuorum[i]` - function minimumStakeForQuorum(uint256 quorumNumber) external view returns (uint96); - - /** - * @notice Returns the entire `operatorIdToStakeHistory[operatorId][quorumNumber]` array. - * @param operatorId The id of the operator of interest. - * @param quorumNumber The quorum number to get the stake for. - */ - function getOperatorIdToStakeHistory(bytes32 operatorId, uint8 quorumNumber) external view returns (OperatorStakeUpdate[] memory); - - function getLengthOfTotalStakeHistoryForQuorum(uint8 quorumNumber) external view returns (uint256); - - /** - * @notice Returns the `index`-th entry in the dynamic array of total stake, `totalStakeHistory` for quorum `quorumNumber`. - * @param quorumNumber The quorum number to get the stake for. - * @param index Array index for lookup, within the dynamic array `totalStakeHistory[quorumNumber]`. - */ - function getTotalStakeUpdateForQuorumFromIndex(uint8 quorumNumber, uint256 index) external view returns (OperatorStakeUpdate memory); - - /// @notice Returns the indices of the operator stakes for the provided `quorumNumber` at the given `blockNumber` - function getStakeUpdateIndexForOperatorIdForQuorumAtBlockNumber(bytes32 operatorId, uint8 quorumNumber, uint32 blockNumber) - external - view - returns (uint32); - - /// @notice Returns the indices of the total stakes for the provided `quorumNumbers` at the given `blockNumber` - function getTotalStakeIndicesByQuorumNumbersAtBlockNumber(uint32 blockNumber, bytes calldata quorumNumbers) external view returns(uint32[] memory) ; - - /** - * @notice Returns the `index`-th entry in the `operatorIdToStakeHistory[operatorId][quorumNumber]` array. - * @param quorumNumber The quorum number to get the stake for. - * @param operatorId The id of the operator of interest. - * @param index Array index for lookup, within the dynamic array `operatorIdToStakeHistory[operatorId][quorumNumber]`. - * @dev Function will revert if `index` is out-of-bounds. - */ - function getStakeUpdateForQuorumFromOperatorIdAndIndex(uint8 quorumNumber, bytes32 operatorId, uint256 index) - external - view - returns (OperatorStakeUpdate memory); - - /** - * @notice Returns the most recent stake weight for the `operatorId` for a certain quorum - * @dev Function returns an OperatorStakeUpdate struct with **every entry equal to 0** in the event that the operator has no stake history - */ - function getMostRecentStakeUpdateByOperatorId(bytes32 operatorId, uint8 quorumNumber) external view returns (OperatorStakeUpdate memory); - - /** - * @notice Returns the stake weight corresponding to `operatorId` for quorum `quorumNumber`, at the - * `index`-th entry in the `operatorIdToStakeHistory[operatorId][quorumNumber]` array if the entry - * corresponds to the operator's stake at `blockNumber`. Reverts otherwise. - * @param quorumNumber The quorum number to get the stake for. - * @param operatorId The id of the operator of interest. - * @param index Array index for lookup, within the dynamic array `operatorIdToStakeHistory[operatorId][quorumNumber]`. - * @param blockNumber Block number to make sure the stake is from. - * @dev Function will revert if `index` is out-of-bounds. - * @dev used the BLSSignatureChecker to get past stakes of signing operators - */ - function getStakeForQuorumAtBlockNumberFromOperatorIdAndIndex(uint8 quorumNumber, uint32 blockNumber, bytes32 operatorId, uint256 index) - external - view - returns (uint96); - - /** - * @notice Returns the total stake weight for quorum `quorumNumber`, at the `index`-th entry in the - * `totalStakeHistory[quorumNumber]` array if the entry corresponds to the total stake at `blockNumber`. - * Reverts otherwise. - * @param quorumNumber The quorum number to get the stake for. - * @param index Array index for lookup, within the dynamic array `totalStakeHistory[quorumNumber]`. - * @param blockNumber Block number to make sure the stake is from. - * @dev Function will revert if `index` is out-of-bounds. - * @dev used the BLSSignatureChecker to get past stakes of signing operators - */ - function getTotalStakeAtBlockNumberFromIndex(uint8 quorumNumber, uint32 blockNumber, uint256 index) external view returns (uint96); - - /** - * @notice Returns the most recent stake weight for the `operatorId` for quorum `quorumNumber` - * @dev Function returns weight of **0** in the event that the operator has no stake history - */ - function getCurrentOperatorStakeForQuorum(bytes32 operatorId, uint8 quorumNumber) external view returns (uint96); - - /// @notice Returns the stake of the operator for the provided `quorumNumber` at the given `blockNumber` - function getStakeForOperatorIdForQuorumAtBlockNumber(bytes32 operatorId, uint8 quorumNumber, uint32 blockNumber) - external - view - returns (uint96); - - /** - * @notice Returns the stake weight from the latest entry in `_totalStakeHistory` for quorum `quorumNumber`. - * @dev Will revert if `_totalStakeHistory[quorumNumber]` is empty. - */ - function getCurrentTotalStakeForQuorum(uint8 quorumNumber) external view returns (uint96); - - /** - * @notice Used for updating information on deposits of nodes. - * @param operators are the addresses of the operators whose stake information is getting updated - */ - function updateStakes(address[] memory operators) external; -} \ No newline at end of file diff --git a/src/contracts/interfaces/IVoteWeigher.sol b/src/contracts/interfaces/IVoteWeigher.sol deleted file mode 100644 index 26d145b52c..0000000000 --- a/src/contracts/interfaces/IVoteWeigher.sol +++ /dev/null @@ -1,96 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity >=0.5.0; - -import "../interfaces/IStrategyManager.sol"; -import "../interfaces/IServiceManager.sol"; -import "../interfaces/ISlasher.sol"; -import "../interfaces/IDelegationManager.sol"; - -/** - * @title Interface for a `VoteWeigher`-type contract. - * @author Layr Labs, Inc. - * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service - * @notice Note that `NUMBER_OF_QUORUMS` is expected to remain constant, as suggested by its uppercase formatting. - */ -interface IVoteWeigher { - /// @notice emitted when a new quorum is created - event QuorumCreated(uint8 indexed quorumNumber); - /// @notice emitted when `strategy` has been added to the array at `strategiesConsideredAndMultipliers[quorumNumber]` - event StrategyAddedToQuorum(uint8 indexed quorumNumber, IStrategy strategy); - /// @notice emitted when `strategy` has removed from the array at `strategiesConsideredAndMultipliers[quorumNumber]` - event StrategyRemovedFromQuorum(uint8 indexed quorumNumber, IStrategy strategy); - /// @notice emitted when `strategy` has its `multiplier` updated in the array at `strategiesConsideredAndMultipliers[quorumNumber]` - event StrategyMultiplierUpdated(uint8 indexed quorumNumber, IStrategy strategy, uint256 multiplier); - - /** - * @notice In weighing a particular strategy, the amount of underlying asset for that strategy is - * multiplied by its multiplier, then divided by WEIGHTING_DIVISOR - */ - struct StrategyAndWeightingMultiplier { - IStrategy strategy; - uint96 multiplier; - } - - /// @notice Constant used as a divisor in calculating weights. - function WEIGHTING_DIVISOR() external pure returns (uint256); - - /// @notice Returns the EigenLayer strategy manager contract. - function strategyManager() external view returns (IStrategyManager); - - /// @notice Returns the EigenLayer slasher contract. - function slasher() external view returns (ISlasher); - - /// @notice Returns the EigenLayer delegation manager contract. - function delegation() external view returns (IDelegationManager); - - /// @notice Returns the AVS service manager contract. - function serviceManager() external view returns (IServiceManager); - - /** - * @notice This function computes the total weight of the @param operator in the quorum @param quorumNumber. - * @dev reverts in the case that `quorumNumber` is greater than or equal to `quorumCount` - */ - function weightOfOperatorForQuorum(uint8 quorumNumber, address operator) external view returns (uint96); - - /// @notice Number of quorums that are being used by the middleware. - function quorumCount() external view returns (uint16); - - /// @notice Returns the strategy and weight multiplier for the `index`'th strategy in the quorum `quorumNumber` - function strategyAndWeightingMultiplierForQuorumByIndex( - uint8 quorumNumber, - uint256 index - ) external view returns (StrategyAndWeightingMultiplier memory); - - /// @notice Create a new quorum and add the strategies and their associated weights to the quorum. - function createQuorum(StrategyAndWeightingMultiplier[] memory _strategiesConsideredAndMultipliers) external; - - /// @notice Adds new strategies and the associated multipliers to the @param quorumNumber. - function addStrategiesConsideredAndMultipliers( - uint8 quorumNumber, - StrategyAndWeightingMultiplier[] memory _newStrategiesConsideredAndMultipliers - ) external; - - /** - * @notice This function is used for removing strategies and their associated weights from the - * mapping strategiesConsideredAndMultipliers for a specific @param quorumNumber. - * @dev higher indices should be *first* in the list of @param indicesToRemove, since otherwise - * the removal of lower index entries will cause a shift in the indices of the other strategiesToRemove - */ - function removeStrategiesConsideredAndMultipliers(uint8 quorumNumber, uint256[] calldata indicesToRemove) external; - - /** - * @notice This function is used for modifying the weights of strategies that are already in the - * mapping strategiesConsideredAndMultipliers for a specific - * @param quorumNumber is the quorum number to change the strategy for - * @param strategyIndices are the indices of the strategies to change - * @param newMultipliers are the new multipliers for the strategies - */ - function modifyStrategyWeights( - uint8 quorumNumber, - uint256[] calldata strategyIndices, - uint96[] calldata newMultipliers - ) external; - - /// @notice Returns the length of the dynamic array stored in `strategiesConsideredAndMultipliers[quorumNumber]`. - function strategiesConsideredAndMultipliersLength(uint8 quorumNumber) external view returns (uint256); -} diff --git a/src/test/Delegation.t.sol b/src/test/Delegation.t.sol index 14cb1c059c..dc75fe5d9c 100644 --- a/src/test/Delegation.t.sol +++ b/src/test/Delegation.t.sol @@ -6,15 +6,12 @@ import "src/contracts/interfaces/ISignatureUtils.sol"; import "../test/EigenLayerTestHelper.t.sol"; -import "./mocks/ServiceManagerMock.sol"; - contract DelegationTests is EigenLayerTestHelper { uint256 public PRIVATE_KEY = 420; uint32 serveUntil = 100; address public registryCoordinator = address(uint160(uint256(keccak256("registryCoordinator")))); - ServiceManagerMock public serviceManager; StakeRegistryStub public stakeRegistry; uint8 defaultQuorumNumber = 0; bytes32 defaultOperatorId = bytes32(uint256(0)); @@ -32,52 +29,7 @@ contract DelegationTests is EigenLayerTestHelper { } function initializeMiddlewares() public { - serviceManager = new ServiceManagerMock(slasher); - stakeRegistry = new StakeRegistryStub(); - - { - uint96 multiplier = 1e18; - uint8 _NUMBER_OF_QUORUMS = 2; - // uint256[] memory _quorumBips = new uint256[](_NUMBER_OF_QUORUMS); - // // split 60% ETH quorum, 40% EIGEN quorum - // _quorumBips[0] = 6000; - // _quorumBips[1] = 4000; - // IVoteWeigher.StrategyAndWeightingMultiplier[] memory ethStratsAndMultipliers = - // new IVoteWeigher.StrategyAndWeightingMultiplier[](1); - // ethStratsAndMultipliers[0].strategy = wethStrat; - // ethStratsAndMultipliers[0].multiplier = multiplier; - // IVoteWeigher.StrategyAndWeightingMultiplier[] memory eigenStratsAndMultipliers = - // new IVoteWeigher.StrategyAndWeightingMultiplier[](1); - // eigenStratsAndMultipliers[0].strategy = eigenStrat; - // eigenStratsAndMultipliers[0].multiplier = multiplier; - - cheats.startPrank(eigenLayerProxyAdmin.owner()); - - // setup the dummy minimum stake for quorum - uint96[] memory minimumStakeForQuorum = new uint96[](_NUMBER_OF_QUORUMS); - for (uint256 i = 0; i < minimumStakeForQuorum.length; i++) { - minimumStakeForQuorum[i] = uint96(i + 1); - } - - // setup the dummy quorum strategies - IVoteWeigher.StrategyAndWeightingMultiplier[][] - memory quorumStrategiesConsideredAndMultipliers = new IVoteWeigher.StrategyAndWeightingMultiplier[][]( - 2 - ); - quorumStrategiesConsideredAndMultipliers[0] = new IVoteWeigher.StrategyAndWeightingMultiplier[](1); - quorumStrategiesConsideredAndMultipliers[0][0] = IVoteWeigher.StrategyAndWeightingMultiplier( - wethStrat, - multiplier - ); - quorumStrategiesConsideredAndMultipliers[1] = new IVoteWeigher.StrategyAndWeightingMultiplier[](1); - quorumStrategiesConsideredAndMultipliers[1][0] = IVoteWeigher.StrategyAndWeightingMultiplier( - eigenStrat, - multiplier - ); - - cheats.stopPrank(); - } } /// @notice testing if an operator can register to themselves. @@ -525,9 +477,6 @@ contract DelegationTests is EigenLayerTestHelper { _testRegisterAsOperator(sender, operatorDetails); cheats.startPrank(sender); - //whitelist the serviceManager to slash the operator - slasher.optIntoSlashing(address(serviceManager)); - cheats.stopPrank(); } diff --git a/src/test/EigenLayerDeployer.t.sol b/src/test/EigenLayerDeployer.t.sol index 6f78cbafa0..71566030a3 100644 --- a/src/test/EigenLayerDeployer.t.sol +++ b/src/test/EigenLayerDeployer.t.sol @@ -12,7 +12,6 @@ import "../contracts/core/DelegationManager.sol"; import "../contracts/interfaces/IETHPOSDeposit.sol"; import "../contracts/interfaces/IBeaconChainOracle.sol"; -import "../contracts/interfaces/IVoteWeigher.sol"; import "../contracts/core/StrategyManager.sol"; import "../contracts/strategies/StrategyBase.sol"; diff --git a/src/test/EigenPod.t.sol b/src/test/EigenPod.t.sol index 54851f1846..e82669f843 100644 --- a/src/test/EigenPod.t.sol +++ b/src/test/EigenPod.t.sol @@ -5,7 +5,6 @@ import "../contracts/interfaces/IEigenPod.sol"; import "../contracts/pods/DelayedWithdrawalRouter.sol"; import "./utils/ProofParsing.sol"; import "./EigenLayerDeployer.t.sol"; -import "./mocks/MiddlewareRegistryMock.sol"; import "../contracts/libraries/BeaconChainProofs.sol"; import "./mocks/BeaconChainOracleMock.sol"; import "./harnesses/EigenPodHarness.sol"; diff --git a/src/test/Withdrawals.t.sol b/src/test/Withdrawals.t.sol index 73f2dc1069..5ed2cdd7f1 100644 --- a/src/test/Withdrawals.t.sol +++ b/src/test/Withdrawals.t.sol @@ -3,9 +3,6 @@ pragma solidity =0.8.12; import "../test/EigenLayerTestHelper.t.sol"; -import "./mocks/MiddlewareRegistryMock.sol"; -import "./mocks/ServiceManagerMock.sol"; - import "./mocks/StakeRegistryStub.sol"; contract WithdrawalTests is EigenLayerTestHelper { @@ -17,17 +14,8 @@ contract WithdrawalTests is EigenLayerTestHelper { uint96 nonce; } - address public registryCoordinator = address(uint160(uint256(keccak256("registryCoordinator")))); - ServiceManagerMock public serviceManager; - StakeRegistryStub public stakeRegistry; - - MiddlewareRegistryMock public generalReg1; - ServiceManagerMock public generalServiceManager1; - - MiddlewareRegistryMock public generalReg2; - ServiceManagerMock public generalServiceManager2; - bytes32 defaultOperatorId = bytes32(uint256(0)); + StakeRegistryStub public stakeRegistry; function setUp() public virtual override { EigenLayerDeployer.setUp(); @@ -36,62 +24,7 @@ contract WithdrawalTests is EigenLayerTestHelper { } function initializeMiddlewares() public { - serviceManager = new ServiceManagerMock(slasher); - stakeRegistry = new StakeRegistryStub(); - - { - uint96 multiplier = 1e18; - uint8 _NUMBER_OF_QUORUMS = 2; - // uint256[] memory _quorumBips = new uint256[](_NUMBER_OF_QUORUMS); - // // split 60% ETH quorum, 40% EIGEN quorum - // _quorumBips[0] = 6000; - // _quorumBips[1] = 4000; - // IVoteWeigher.StrategyAndWeightingMultiplier[] memory ethStratsAndMultipliers = - // new IVoteWeigher.StrategyAndWeightingMultiplier[](1); - // ethStratsAndMultipliers[0].strategy = wethStrat; - // ethStratsAndMultipliers[0].multiplier = multiplier; - // IVoteWeigher.StrategyAndWeightingMultiplier[] memory eigenStratsAndMultipliers = - // new IVoteWeigher.StrategyAndWeightingMultiplier[](1); - // eigenStratsAndMultipliers[0].strategy = eigenStrat; - // eigenStratsAndMultipliers[0].multiplier = multiplier; - - cheats.startPrank(eigenLayerProxyAdmin.owner()); - - // setup the dummy minimum stake for quorum - uint96[] memory minimumStakeForQuorum = new uint96[](_NUMBER_OF_QUORUMS); - for (uint256 i = 0; i < minimumStakeForQuorum.length; i++) { - minimumStakeForQuorum[i] = uint96(i + 1); - } - - // setup the dummy quorum strategies - IVoteWeigher.StrategyAndWeightingMultiplier[][] - memory quorumStrategiesConsideredAndMultipliers = new IVoteWeigher.StrategyAndWeightingMultiplier[][]( - 2 - ); - quorumStrategiesConsideredAndMultipliers[0] = new IVoteWeigher.StrategyAndWeightingMultiplier[](1); - quorumStrategiesConsideredAndMultipliers[0][0] = IVoteWeigher.StrategyAndWeightingMultiplier( - wethStrat, - multiplier - ); - quorumStrategiesConsideredAndMultipliers[1] = new IVoteWeigher.StrategyAndWeightingMultiplier[](1); - quorumStrategiesConsideredAndMultipliers[1][0] = IVoteWeigher.StrategyAndWeightingMultiplier( - eigenStrat, - multiplier - ); - - cheats.stopPrank(); - } - } - - function initializeGeneralMiddlewares() public { - generalServiceManager1 = new ServiceManagerMock(slasher); - - generalReg1 = new MiddlewareRegistryMock(generalServiceManager1, strategyManager); - - generalServiceManager2 = new ServiceManagerMock(slasher); - - generalReg2 = new MiddlewareRegistryMock(generalServiceManager2, strategyManager); } //This function helps with stack too deep issues with "testWithdrawal" test @@ -108,7 +41,7 @@ contract WithdrawalTests is EigenLayerTestHelper { cheats.assume(ethAmount >= 1 && ethAmount <= 1e18); cheats.assume(eigenAmount >= 1 && eigenAmount <= 1e18); - initializeGeneralMiddlewares(); + initializeMiddlewares(); if (RANDAO) { _testWithdrawalAndDeregistration(operator, depositor, withdrawer, ethAmount, eigenAmount, withdrawAsTokens); @@ -130,12 +63,6 @@ contract WithdrawalTests is EigenLayerTestHelper { ) internal { _initiateDelegation(operator, depositor, ethAmount, eigenAmount); - cheats.startPrank(operator); - slasher.optIntoSlashing(address(generalServiceManager1)); - cheats.stopPrank(); - - generalReg1.registerOperator(operator, uint32(block.timestamp) + 3 days); - address delegatedTo = delegation.delegatedTo(depositor); // packed data structure to deal with stack-too-deep issues @@ -180,7 +107,6 @@ contract WithdrawalTests is EigenLayerTestHelper { cheats.warp(uint32(block.timestamp) + 2 days); cheats.roll(uint32(block.timestamp) + 2 days); - generalReg1.deregisterOperator(operator); { //warp past the serve until time, which is 3 days from the beginning. THis puts us at 4 days past that point cheats.warp(uint32(block.timestamp) + 4 days); @@ -228,21 +154,14 @@ contract WithdrawalTests is EigenLayerTestHelper { ) public { _initiateDelegation(operator, depositor, ethAmount, eigenAmount); - cheats.startPrank(operator); - slasher.optIntoSlashing(address(generalServiceManager1)); - slasher.optIntoSlashing(address(generalServiceManager2)); - cheats.stopPrank(); - // emit log_named_uint("Linked list element 1", uint256(uint160(address(generalServiceManager1)))); // emit log_named_uint("Linked list element 2", uint256(uint160(address(generalServiceManager2)))); // emit log("________________________________________________________________"); - generalReg1.registerOperator(operator, uint32(block.timestamp) + 5 days); // emit log_named_uint("Middleware 1 Update Block", uint32(block.number)); cheats.warp(uint32(block.timestamp) + 1 days); cheats.roll(uint32(block.number) + 1); - generalReg2.registerOperator(operator, uint32(block.timestamp) + 5 days); // emit log_named_uint("Middleware 2 Update Block", uint32(block.number)); address delegatedTo = delegation.delegatedTo(depositor); @@ -289,14 +208,12 @@ contract WithdrawalTests is EigenLayerTestHelper { cheats.warp(uint32(block.timestamp) + 2 days); cheats.roll(uint32(block.number) + 2); - uint256 prevElement = uint256(uint160(address(generalServiceManager2))); - generalReg1.propagateStakeUpdate(operator, uint32(block.number), prevElement); + // uint256 prevElement = uint256(uint160(address(generalServiceManager2))); cheats.warp(uint32(block.timestamp) + 1 days); cheats.roll(uint32(block.number) + 1); - prevElement = uint256(uint160(address(generalServiceManager1))); - generalReg2.propagateStakeUpdate(operator, uint32(block.number), prevElement); + // prevElement = uint256(uint160(address(generalServiceManager1))); { //warp past the serve until time, which is 3 days from the beginning. THis puts us at 4 days past that point diff --git a/src/test/mocks/MiddlewareRegistryMock.sol b/src/test/mocks/MiddlewareRegistryMock.sol deleted file mode 100644 index 788dc56ddd..0000000000 --- a/src/test/mocks/MiddlewareRegistryMock.sol +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.12; - -import "../../contracts/interfaces/IServiceManager.sol"; -import "../../contracts/interfaces/IStrategyManager.sol"; -import "../../contracts/interfaces/ISlasher.sol"; - -import "forge-std/Test.sol"; - - - - -contract MiddlewareRegistryMock is DSTest{ - IServiceManager public serviceManager; - IStrategyManager public strategyManager; - ISlasher public slasher; - - - constructor( - IServiceManager _serviceManager, - IStrategyManager _strategyManager - ) { - serviceManager = _serviceManager; - strategyManager = _strategyManager; - slasher = _strategyManager.slasher(); - } - - function registerOperator(address operator, uint32 serveUntil) public { - require(slasher.canSlash(operator, address(serviceManager)), "Not opted into slashing"); - serviceManager.recordFirstStakeUpdate(operator, serveUntil); - - } - - function deregisterOperator(address operator) public { - uint32 latestServeUntilBlock = serviceManager.latestServeUntilBlock(); - serviceManager.recordLastStakeUpdateAndRevokeSlashingAbility(operator, latestServeUntilBlock); - } - - function propagateStakeUpdate(address operator, uint32 blockNumber, uint256 prevElement) external { - uint32 latestServeUntilBlock = serviceManager.latestServeUntilBlock(); - serviceManager.recordStakeUpdate(operator, blockNumber, latestServeUntilBlock, prevElement); - } - - function isActiveOperator(address operator) external pure returns (bool) { - if (operator != address(0)) { - return true; - } else { - return false; - } - } - -} \ No newline at end of file diff --git a/src/test/mocks/RegistryCoordinatorMock.sol b/src/test/mocks/RegistryCoordinatorMock.sol deleted file mode 100644 index 6e4bed688b..0000000000 --- a/src/test/mocks/RegistryCoordinatorMock.sol +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.12; - - -import "../../contracts/interfaces/IRegistryCoordinator.sol"; - - -contract RegistryCoordinatorMock is IRegistryCoordinator { - /// @notice Returns the bitmap of the quorums the operator is registered for. - function operatorIdToQuorumBitmap(bytes32 pubkeyHash) external view returns (uint256){} - - function getOperator(address operator) external view returns (Operator memory){} - - /// @notice Returns the stored id for the specified `operator`. - function getOperatorId(address operator) external view returns (bytes32){} - - /// @notice Returns the operator address for the given `operatorId` - function getOperatorFromId(bytes32 operatorId) external view returns (address) {} - - /// @notice Returns the status for the given `operator` - function getOperatorStatus(address operator) external view returns (IRegistryCoordinator.OperatorStatus){} - - /// @notice Returns task number from when `operator` has been registered. - function getFromTaskNumberForOperator(address operator) external view returns (uint32){} - - function getQuorumBitmapIndicesByOperatorIdsAtBlockNumber(uint32 blockNumber, bytes32[] memory operatorIds) external view returns (uint32[] memory){} - - /// @notice Returns the quorum bitmap for the given `operatorId` at the given `blockNumber` via the `index` - function getQuorumBitmapByOperatorIdAtBlockNumberByIndex(bytes32 operatorId, uint32 blockNumber, uint256 index) external view returns (uint192) {} - - /// @notice Returns the `index`th entry in the operator with `operatorId`'s bitmap history - function getQuorumBitmapUpdateByOperatorIdByIndex(bytes32 operatorId, uint256 index) external view returns (QuorumBitmapUpdate memory) {} - - /// @notice Returns the current quorum bitmap for the given `operatorId` - function getCurrentQuorumBitmapByOperatorId(bytes32 operatorId) external view returns (uint192) {} - - /// @notice Returns the length of the quorum bitmap history for the given `operatorId` - function getQuorumBitmapUpdateByOperatorIdLength(bytes32 operatorId) external view returns (uint256) {} - - function numRegistries() external view returns (uint256){} - - function registries(uint256) external view returns (address){} - - function registerOperatorWithCoordinator(bytes memory quorumNumbers, bytes calldata) external {} - - function deregisterOperatorWithCoordinator(bytes calldata quorumNumbers, bytes calldata) external {} -} diff --git a/src/test/mocks/ServiceManagerMock.sol b/src/test/mocks/ServiceManagerMock.sol deleted file mode 100644 index caa9c978a6..0000000000 --- a/src/test/mocks/ServiceManagerMock.sol +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity =0.8.12; - -import "forge-std/Test.sol"; -import "../../contracts/interfaces/IServiceManager.sol"; -import "../../contracts/interfaces/ISlasher.sol"; - -import "forge-std/Test.sol"; - -contract ServiceManagerMock is IServiceManager, DSTest { - address public owner; - ISlasher public slasher; - - constructor(ISlasher _slasher) { - owner = msg.sender; - slasher = _slasher; - - } - - /// @notice Returns the current 'taskNumber' for the middleware - function taskNumber() external pure returns (uint32) { - return 0; - } - - /// @notice Permissioned function that causes the ServiceManager to freeze the operator on EigenLayer, through a call to the Slasher contract - function freezeOperator(address operator) external { - slasher.freezeOperator(operator); - } - - /// @notice Permissioned function to have the ServiceManager forward a call to the slasher, recording an initial stake update (on operator registration) - function recordFirstStakeUpdate(address operator, uint32 serveUntil) external pure {} - - /// @notice Permissioned function to have the ServiceManager forward a call to the slasher, recording a stake update - function recordStakeUpdate(address operator, uint32 updateBlock, uint32 serveUntilBlock, uint256 prevElement) external pure {} - - /// @notice Permissioned function to have the ServiceManager forward a call to the slasher, recording a final stake update (on operator deregistration) - function recordLastStakeUpdateAndRevokeSlashingAbility(address operator, uint32 serveUntil) external pure {} - - /// @notice Token used for placing guarantee on challenges & payment commits - function paymentChallengeToken() external pure returns (IERC20) { - return IERC20(address(0)); - } - - /// @notice Returns the `latestServeUntilBlock` until which operators must serve. - function latestServeUntilBlock() external pure returns (uint32) { - return type(uint32).max; - } -} \ No newline at end of file diff --git a/src/test/mocks/StrategyManagerMock.sol b/src/test/mocks/StrategyManagerMock.sol index 4596b5c846..168ea9a741 100644 --- a/src/test/mocks/StrategyManagerMock.sol +++ b/src/test/mocks/StrategyManagerMock.sol @@ -7,7 +7,6 @@ import "@openzeppelin-upgrades/contracts/security/ReentrancyGuardUpgradeable.sol import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "../../contracts/permissions/Pausable.sol"; import "../../contracts/core/StrategyManagerStorage.sol"; -import "../../contracts/interfaces/IServiceManager.sol"; import "../../contracts/interfaces/IEigenPodManager.sol"; import "../../contracts/interfaces/IDelegationManager.sol"; diff --git a/src/test/unit/DelegationUnit.t.sol b/src/test/unit/DelegationUnit.t.sol index 6ce25902f4..990e763f28 100644 --- a/src/test/unit/DelegationUnit.t.sol +++ b/src/test/unit/DelegationUnit.t.sol @@ -86,17 +86,6 @@ contract DelegationUnitTests is EigenLayerTestHelper { // @notice Emitted when @param staker undelegates from @param operator. event StakerUndelegated(address indexed staker, address indexed operator); - - - // STAKE REGISTRY EVENT - /// @notice emitted whenever the stake of `operator` is updated - event StakeUpdate( - bytes32 indexed operatorId, - uint8 quorumNumber, - uint96 stake - ); - // @notice Emitted when @param staker is undelegated via a call not originating from the staker themself - event StakerForceUndelegated(address indexed staker, address indexed operator); /** * @notice Emitted when a new withdrawal is queued. @@ -244,9 +233,6 @@ contract DelegationUnitTests is EigenLayerTestHelper { emit OperatorDetailsModified(operator, operatorDetails); cheats.expectEmit(true, true, true, true, address(delegationManager)); emit StakerDelegated(operator, operator); - // don't check any parameters other than event type - cheats.expectEmit(false, false, false, false, address(stakeRegistryMock)); - emit StakeUpdate(bytes32(0), 0, 0); cheats.expectEmit(true, true, true, true, address(delegationManager)); emit OperatorRegistered(operator, operatorDetails); cheats.expectEmit(true, true, true, true, address(delegationManager)); @@ -463,9 +449,6 @@ contract DelegationUnitTests is EigenLayerTestHelper { emit StakerDelegated(staker, _operator); cheats.expectEmit(true, true, true, true, address(delegationManager)); emit OperatorSharesIncreased(_operator, staker, strategyMock, 1); - // don't check any parameters other than event type - cheats.expectEmit(false, false, false, false, address(stakeRegistryMock)); - emit StakeUpdate(bytes32(0), 0, 0); delegationManager.delegateTo(_operator, approverSignatureAndExpiry, salt); cheats.stopPrank(); @@ -1055,9 +1038,6 @@ contract DelegationUnitTests is EigenLayerTestHelper { cheats.startPrank(staker); cheats.expectEmit(true, true, true, true, address(delegationManager)); emit StakerUndelegated(staker, delegationManager.delegatedTo(staker)); - // don't check any parameters other than event type - cheats.expectEmit(false, false, false, false, address(stakeRegistryMock)); - emit StakeUpdate(bytes32(0), 0, 0); delegationManager.undelegate(staker); cheats.stopPrank(); @@ -1117,9 +1097,6 @@ contract DelegationUnitTests is EigenLayerTestHelper { if(delegationManager.isDelegated(staker)) { cheats.expectEmit(true, true, true, true, address(delegationManager)); emit OperatorSharesIncreased(operator, staker, strategy, shares); - // don't check any parameters other than event type - cheats.expectEmit(false, false, false, false, address(stakeRegistryMock)); - emit StakeUpdate(bytes32(0), 0, 0); } cheats.startPrank(address(strategyManagerMock)); @@ -1190,9 +1167,6 @@ contract DelegationUnitTests is EigenLayerTestHelper { for (uint256 i = 0; i < strategies.length; ++i) { cheats.expectEmit(true, true, true, true, address(delegationManager)); emit OperatorSharesDecreased(operatorToDecreaseSharesOf, staker, strategies[i], sharesInputArray[i]); - // don't check any parameters other than event type - cheats.expectEmit(false, false, false, false, address(stakeRegistryMock)); - emit StakeUpdate(bytes32(0), 0, 0); delegationManager.decreaseDelegatedShares(staker, strategies[i], sharesInputArray[i]); } } @@ -1339,9 +1313,6 @@ contract DelegationUnitTests is EigenLayerTestHelper { cheats.startPrank(caller); // check that the correct calldata is forwarded by looking for an event emitted by the StrategyManagerMock contract if (strategyManagerMock.stakerStrategyListLength(staker) != 0) { - // don't check any parameters other than event type - cheats.expectEmit(false, false, false, false, address(stakeRegistryMock)); - emit StakeUpdate(bytes32(0), 0, 0); cheats.expectEmit(true, true, true, true, address(strategyManagerMock)); emit ForceTotalWithdrawalCalled(staker); } From 01a0b2aa7bbef11134d8f9a25eca8ba694877bd6 Mon Sep 17 00:00:00 2001 From: sidu28 <60323455+Sidu28@users.noreply.github.com> Date: Thu, 26 Oct 2023 10:23:11 -0700 Subject: [PATCH 06/24] init --- src/contracts/pods/EigenPod.sol | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src/contracts/pods/EigenPod.sol b/src/contracts/pods/EigenPod.sol index b70474167f..6552cacd0f 100644 --- a/src/contracts/pods/EigenPod.sol +++ b/src/contracts/pods/EigenPod.sol @@ -483,7 +483,7 @@ contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, Eigen validatorInfo.status = VALIDATOR_STATUS.ACTIVE; validatorInfo.validatorIndex = validatorIndex; validatorInfo.mostRecentBalanceUpdateTimestamp = oracleTimestamp; - validatorInfo.restakedBalanceGwei = _calculateRestakedBalanceGwei(validatorEffectiveBalanceGwei); + validatorInfo.restakedBalanceGwei = validatorEffectiveBalanceGwei; _validatorPubkeyHashToInfo[validatorPubkeyHash] = validatorInfo; emit ValidatorRestaked(validatorIndex); @@ -545,7 +545,7 @@ contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, Eigen // Done with proofs! Now update the validator's balance and send to the EigenPodManager if needed uint64 currentRestakedBalanceGwei = validatorInfo.restakedBalanceGwei; - uint64 newRestakedBalanceGwei = _calculateRestakedBalanceGwei(validatorBalance); + uint64 newRestakedBalanceGwei = validatorBalance; // Update validator balance and timestamp, and save to state: validatorInfo.restakedBalanceGwei = newRestakedBalanceGwei; @@ -743,20 +743,6 @@ contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, Eigen delayedWithdrawalRouter.createDelayedWithdrawal{value: amountWei}(podOwner, recipient); } - function _calculateRestakedBalanceGwei(uint64 amountGwei) internal view returns (uint64) { - if (amountGwei <= RESTAKED_BALANCE_OFFSET_GWEI) { - return 0; - } - /** - * calculates the "floor" of amountGwei - RESTAKED_BALANCE_OFFSET_GWEI. By using integer division - * (dividing by GWEI_TO_WEI = 1e9) and then multiplying by 1e9, we effectively "round down" amountGwei to - * the nearest ETH, effectively calculating the floor of amountGwei. - */ - // slither-disable-next-line divide-before-multiply - uint64 effectiveBalanceGwei = uint64(((amountGwei - RESTAKED_BALANCE_OFFSET_GWEI) / GWEI_TO_WEI) * GWEI_TO_WEI); - return uint64(MathUpgradeable.min(MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR, effectiveBalanceGwei)); - } - function _podWithdrawalCredentials() internal view returns (bytes memory) { return abi.encodePacked(bytes1(uint8(1)), bytes11(0), address(this)); } From 1bcd39d647a117fa508135d062e0c528321fbcd6 Mon Sep 17 00:00:00 2001 From: sidu28 <60323455+Sidu28@users.noreply.github.com> Date: Thu, 26 Oct 2023 10:25:16 -0700 Subject: [PATCH 07/24] change 31 to 32 --- src/test/EigenPod.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/EigenPod.t.sol b/src/test/EigenPod.t.sol index 54851f1846..52aedc5b79 100644 --- a/src/test/EigenPod.t.sol +++ b/src/test/EigenPod.t.sol @@ -56,7 +56,7 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants { bytes32[] validatorFields; uint32 WITHDRAWAL_DELAY_BLOCKS = 7 days / 12 seconds; - uint64 MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR = 31e9; + uint64 MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR = 32e9; uint64 RESTAKED_BALANCE_OFFSET_GWEI = 75e7; uint64 internal constant GOERLI_GENESIS_TIME = 1616508000; uint64 internal constant SECONDS_PER_SLOT = 12; From f25820edb4cda72cac16dd456a979dfe59aad606 Mon Sep 17 00:00:00 2001 From: sidu28 <60323455+Sidu28@users.noreply.github.com> Date: Thu, 26 Oct 2023 10:30:50 -0700 Subject: [PATCH 08/24] all tests working --- src/test/EigenPod.t.sol | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/src/test/EigenPod.t.sol b/src/test/EigenPod.t.sol index 52aedc5b79..10215ed42a 100644 --- a/src/test/EigenPod.t.sol +++ b/src/test/EigenPod.t.sol @@ -471,7 +471,7 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants { withdrawalFields[BeaconChainProofs.WITHDRAWAL_VALIDATOR_AMOUNT_INDEX] ); uint64 leftOverBalanceWEI = uint64( - withdrawalAmountGwei - _calculateRestakedBalanceGwei(pod.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR()) + withdrawalAmountGwei - (pod.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR()) ) * uint64(GWEI_TO_WEI); cheats.deal(address(pod), leftOverBalanceWEI); { @@ -708,7 +708,7 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants { int256 beaconChainETHShares = eigenPodManager.podOwnerShares(podOwner); require( - beaconChainETHShares == int256(_calculateRestakedBalanceGwei(newValidatorBalance) * GWEI_TO_WEI), + beaconChainETHShares == int256((newValidatorBalance) * GWEI_TO_WEI), "eigenPodManager shares not updated correctly" ); } @@ -886,7 +886,7 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants { assertTrue( eigenPodManager.podOwnerShares(podOwner) == - int256(_calculateRestakedBalanceGwei(newValidatorBalance) * GWEI_TO_WEI), + int256((newValidatorBalance) * GWEI_TO_WEI), "hysterisis not working" ); assertTrue( @@ -931,7 +931,7 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants { assertTrue( eigenPodManager.podOwnerShares(podOwner) == - int256(_calculateRestakedBalanceGwei(newValidatorBalance) * GWEI_TO_WEI), + int256((newValidatorBalance) * GWEI_TO_WEI), "hysterisis not working" ); assertTrue( @@ -1786,7 +1786,7 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants { uint256 withdrawableRestakedExecutionLayerGweiBefore = pod.withdrawableRestakedExecutionLayerGwei(); - uint256 shareAmount = _calculateRestakedBalanceGwei(pod.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR()) * GWEI_TO_WEI; + uint256 shareAmount = (pod.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR()) * GWEI_TO_WEI; _verifyEigenPodBalanceSharesInvariant(podOwner, pod, validatorPubkeyHash); _testQueueWithdrawal(podOwner, shareAmount); _verifyEigenPodBalanceSharesInvariant(podOwner, pod, validatorPubkeyHash); @@ -2009,7 +2009,7 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants { int256 beaconChainETHSharesAfter = eigenPodManager.podOwnerShares(_podOwner); uint256 valBalance = Endian.fromLittleEndianUint64(getValidatorFields()[2]); - uint256 effectiveBalance = uint256(_calculateRestakedBalanceGwei(uint64(valBalance))) * + uint256 effectiveBalance = uint256((uint64(valBalance))) * GWEI_TO_WEI; require( (beaconChainETHSharesAfter - beaconChainETHSharesBefore) == int256(effectiveBalance), @@ -2101,23 +2101,10 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants { function testEffectiveRestakedBalance() public { uint64 amountGwei = 29134000000; - uint64 effectiveBalance = _calculateRestakedBalanceGwei(amountGwei); + uint64 effectiveBalance = (amountGwei); emit log_named_uint("effectiveBalance", effectiveBalance); } - function _calculateRestakedBalanceGwei(uint64 amountGwei) internal view returns (uint64) { - if (amountGwei <= RESTAKED_BALANCE_OFFSET_GWEI) { - return 0; - } - /** - * calculates the "floor" of amountGwei - RESTAKED_BALANCE_OFFSET_GWEI. By using integer division - * (dividing by GWEI_TO_WEI = 1e9) and then multiplying by 1e9, we effectively "round down" amountGwei to - * the nearest ETH, effectively calculating the floor of amountGwei. - */ - uint64 effectiveBalanceGwei = uint64(((amountGwei - RESTAKED_BALANCE_OFFSET_GWEI) / GWEI_TO_WEI) * GWEI_TO_WEI); - return uint64(MathUpgradeable.min(MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR, effectiveBalanceGwei)); - } - function _computeTimestampAtSlot(uint64 slot) internal pure returns (uint64) { return uint64(GOERLI_GENESIS_TIME + slot * SECONDS_PER_SLOT); } From a92e58ed00e4052a701f3bf04a27f873d645fa61 Mon Sep 17 00:00:00 2001 From: sidu28 <60323455+Sidu28@users.noreply.github.com> Date: Thu, 26 Oct 2023 10:34:28 -0700 Subject: [PATCH 09/24] close out tests --- script/M1_Deploy.s.sol | 2 +- script/configs/AVSContractsDeploy.json | 2 +- script/configs/M1_deploy_devnet.config.json | 2 +- script/configs/M1_deploy_mainnet.config.json | 2 +- script/middleware/DeployOpenEigenLayer.s.sol | 2 +- script/milestone/M2Deploy.s.sol | 2 +- script/milestone/M2Deploy.sol | 2 +- script/testing/M2_Deploy_From_Scratch.s.sol | 4 ++-- script/testing/M2_deploy_from_scratch.anvil.config.json | 2 +- script/testing/M2_deploy_from_scratch.mainnet.config.json | 2 +- src/test/EigenLayerDeployer.t.sol | 2 +- src/test/EigenPod.t.sol | 2 +- src/test/unit/EigenPodManagerUnit.t.sol | 2 +- src/test/unit/StrategyManagerUnit.t.sol | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/script/M1_Deploy.s.sol b/script/M1_Deploy.s.sol index ac689a29a3..ac266f1597 100644 --- a/script/M1_Deploy.s.sol +++ b/script/M1_Deploy.s.sol @@ -541,7 +541,7 @@ contract Deployer_M1 is Script, Test { // "strategyManager: withdrawalDelayBlocks initialized incorrectly"); // require(delayedWithdrawalRouter.withdrawalDelayBlocks() == 7 days / 12 seconds, // "delayedWithdrawalRouter: withdrawalDelayBlocks initialized incorrectly"); - // uint256 REQUIRED_BALANCE_WEI = 31 ether; + // uint256 REQUIRED_BALANCE_WEI = 32 ether; require( strategyManager.strategyWhitelister() == operationsMultisig, diff --git a/script/configs/AVSContractsDeploy.json b/script/configs/AVSContractsDeploy.json index 2704a008c4..e6e052c0eb 100644 --- a/script/configs/AVSContractsDeploy.json +++ b/script/configs/AVSContractsDeploy.json @@ -10,7 +10,7 @@ "eigenPod": { "PARTIAL_WITHDRAWAL_FRAUD_PROOF_PERIOD_BLOCKS": 50400, - "REQUIRED_BALANCE_WEI": "31000000000000000000" + "REQUIRED_BALANCE_WEI": "32000000000000000000" }, "eigenPodManager": { diff --git a/script/configs/M1_deploy_devnet.config.json b/script/configs/M1_deploy_devnet.config.json index 980dae7827..6da5379e15 100644 --- a/script/configs/M1_deploy_devnet.config.json +++ b/script/configs/M1_deploy_devnet.config.json @@ -26,7 +26,7 @@ "eigenPod": { "PARTIAL_WITHDRAWAL_FRAUD_PROOF_PERIOD_BLOCKS": 50400, - "REQUIRED_BALANCE_WEI": "31000000000000000000" + "REQUIRED_BALANCE_WEI": "32000000000000000000" }, "eigenPodManager": { diff --git a/script/configs/M1_deploy_mainnet.config.json b/script/configs/M1_deploy_mainnet.config.json index eefe11d7f2..e5b43e804b 100644 --- a/script/configs/M1_deploy_mainnet.config.json +++ b/script/configs/M1_deploy_mainnet.config.json @@ -37,7 +37,7 @@ "eigenPod": { "PARTIAL_WITHDRAWAL_FRAUD_PROOF_PERIOD_BLOCKS": 50400, - "REQUIRED_BALANCE_WEI": "31000000000000000000" + "REQUIRED_BALANCE_WEI": "32000000000000000000" }, "eigenPodManager": { diff --git a/script/middleware/DeployOpenEigenLayer.s.sol b/script/middleware/DeployOpenEigenLayer.s.sol index a793c98a77..9fc8eb7495 100644 --- a/script/middleware/DeployOpenEigenLayer.s.sol +++ b/script/middleware/DeployOpenEigenLayer.s.sol @@ -116,7 +116,7 @@ contract DeployOpenEigenLayer is Script, Test { delayedWithdrawalRouter, eigenPodManager, // uint64(MAX_VALIDATOR_BALANCE_GWEI), - uint64(31 gwei), + uint64(32 gwei), // uint64(EFFECTIVE_RESTAKED_BALANCE_OFFSET_GWEI) uint64(0.75 gwei), 1000 // temp genesis time diff --git a/script/milestone/M2Deploy.s.sol b/script/milestone/M2Deploy.s.sol index 2f8e15fd7d..531bd2d3d9 100644 --- a/script/milestone/M2Deploy.s.sol +++ b/script/milestone/M2Deploy.s.sol @@ -189,7 +189,7 @@ contract M2Deploy is Script, Test { _ethPOS: ethPOS, _delayedWithdrawalRouter: delayedWithdrawalRouter, _eigenPodManager: eigenPodManager, - _MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR: 31 gwei, + _MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR: 32 gwei, _RESTAKED_BALANCE_OFFSET_GWEI: 0.5 gwei, _GENESIS_TIME: 1616508000 }); diff --git a/script/milestone/M2Deploy.sol b/script/milestone/M2Deploy.sol index 0227a8b8f5..a3aaa81a4f 100644 --- a/script/milestone/M2Deploy.sol +++ b/script/milestone/M2Deploy.sol @@ -83,7 +83,7 @@ contract M2Deploy is Script, Test { _ethPOS: ethPOS, _delayedWithdrawalRouter: delayedWithdrawalRouter, _eigenPodManager: eigenPodManager, - _MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR: 31 gwei, + _MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR: 32 gwei, _RESTAKED_BALANCE_OFFSET_GWEI: 0.5 gwei, _GENESIS_TIME: 1616508000 }); diff --git a/script/testing/M2_Deploy_From_Scratch.s.sol b/script/testing/M2_Deploy_From_Scratch.s.sol index 8bb1d312e4..77df8b4749 100644 --- a/script/testing/M2_Deploy_From_Scratch.s.sol +++ b/script/testing/M2_Deploy_From_Scratch.s.sol @@ -540,9 +540,9 @@ contract Deployer_M2 is Script, Test { // "strategyManager: withdrawalDelayBlocks initialized incorrectly"); // require(delayedWithdrawalRouter.withdrawalDelayBlocks() == 7 days / 12 seconds, // "delayedWithdrawalRouter: withdrawalDelayBlocks initialized incorrectly"); - // uint256 MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR = 31 ether; + // uint256 MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR = 32 ether; require( - eigenPodImplementation.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR() == 31 gwei, + eigenPodImplementation.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR() == 32 gwei, "eigenPod: MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR initialized incorrectly" ); diff --git a/script/testing/M2_deploy_from_scratch.anvil.config.json b/script/testing/M2_deploy_from_scratch.anvil.config.json index 639a0825bf..80c007aedb 100644 --- a/script/testing/M2_deploy_from_scratch.anvil.config.json +++ b/script/testing/M2_deploy_from_scratch.anvil.config.json @@ -12,7 +12,7 @@ }, "eigenPod": { "PARTIAL_WITHDRAWAL_FRAUD_PROOF_PERIOD_BLOCKS": 1, - "MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR": "31000000000", + "MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR": "32000000000", "RESTAKED_BALANCE_OFFSET_GWEI": "750000000" }, "eigenPodManager": { diff --git a/script/testing/M2_deploy_from_scratch.mainnet.config.json b/script/testing/M2_deploy_from_scratch.mainnet.config.json index d3619b4640..7981fc0577 100644 --- a/script/testing/M2_deploy_from_scratch.mainnet.config.json +++ b/script/testing/M2_deploy_from_scratch.mainnet.config.json @@ -35,7 +35,7 @@ "eigenPod": { "PARTIAL_WITHDRAWAL_FRAUD_PROOF_PERIOD_BLOCKS": 50400, - "MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR": "31000000000", + "MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR": "32000000000", "RESTAKED_BALANCE_OFFSET_GWEI": "750000000" }, diff --git a/src/test/EigenLayerDeployer.t.sol b/src/test/EigenLayerDeployer.t.sol index 6f78cbafa0..f8fada4fd2 100644 --- a/src/test/EigenLayerDeployer.t.sol +++ b/src/test/EigenLayerDeployer.t.sol @@ -75,7 +75,7 @@ contract EigenLayerDeployer is Operators { uint256 nonce = 69; uint256 public gasLimit = 750000; uint32 PARTIAL_WITHDRAWAL_FRAUD_PROOF_PERIOD_BLOCKS = 7 days / 12 seconds; - uint256 REQUIRED_BALANCE_WEI = 31 ether; + uint256 REQUIRED_BALANCE_WEI = 32 ether; uint64 MAX_PARTIAL_WTIHDRAWAL_AMOUNT_GWEI = 1 ether / 1e9; uint64 MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR = 32e9; uint64 EFFECTIVE_RESTAKED_BALANCE_OFFSET = 75e7; diff --git a/src/test/EigenPod.t.sol b/src/test/EigenPod.t.sol index 10215ed42a..f80487493e 100644 --- a/src/test/EigenPod.t.sol +++ b/src/test/EigenPod.t.sol @@ -1773,7 +1773,7 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants { // ./solidityProofGen "ValidatorFieldsProof" 302913 true "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "withdrawal_credential_proof_302913.json" setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json"); setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json"); _testDeployAndVerifyNewEigenPod(podOwner, signature, depositDataRoot); - uint256 shareAmount = 31e18; + uint256 shareAmount = 32e18; // expect revert from underflow cheats.expectRevert(); _testQueueWithdrawal(podOwner, shareAmount); diff --git a/src/test/unit/EigenPodManagerUnit.t.sol b/src/test/unit/EigenPodManagerUnit.t.sol index e6afc6a72d..be69df4843 100644 --- a/src/test/unit/EigenPodManagerUnit.t.sol +++ b/src/test/unit/EigenPodManagerUnit.t.sol @@ -22,7 +22,7 @@ contract EigenPodManagerUnitTests is Test, EigenPodPausingConstants { Vm cheats = Vm(HEVM_ADDRESS); - uint256 public REQUIRED_BALANCE_WEI = 31 ether; + uint256 public REQUIRED_BALANCE_WEI = 32 ether; ProxyAdmin public proxyAdmin; PauserRegistry public pauserRegistry; diff --git a/src/test/unit/StrategyManagerUnit.t.sol b/src/test/unit/StrategyManagerUnit.t.sol index 4f95c6c408..93acdbd853 100644 --- a/src/test/unit/StrategyManagerUnit.t.sol +++ b/src/test/unit/StrategyManagerUnit.t.sol @@ -23,7 +23,7 @@ import "./Utils.sol"; contract StrategyManagerUnitTests is Test, Utils { Vm cheats = Vm(HEVM_ADDRESS); - uint256 public REQUIRED_BALANCE_WEI = 31 ether; + uint256 public REQUIRED_BALANCE_WEI = 32 ether; ProxyAdmin public proxyAdmin; PauserRegistry public pauserRegistry; From 8030c73ac901b58826d098f0fee2495b4146859c Mon Sep 17 00:00:00 2001 From: sidu28 <60323455+Sidu28@users.noreply.github.com> Date: Thu, 26 Oct 2023 10:43:04 -0700 Subject: [PATCH 10/24] cleanup --- src/test/EigenPod.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/EigenPod.t.sol b/src/test/EigenPod.t.sol index f80487493e..0d5dff6211 100644 --- a/src/test/EigenPod.t.sol +++ b/src/test/EigenPod.t.sol @@ -2009,7 +2009,7 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants { int256 beaconChainETHSharesAfter = eigenPodManager.podOwnerShares(_podOwner); uint256 valBalance = Endian.fromLittleEndianUint64(getValidatorFields()[2]); - uint256 effectiveBalance = uint256((uint64(valBalance))) * + uint256 effectiveBalance = valBalance * GWEI_TO_WEI; require( (beaconChainETHSharesAfter - beaconChainETHSharesBefore) == int256(effectiveBalance), From 8bf9d301f803d97a731753354e3585e2e540f8c9 Mon Sep 17 00:00:00 2001 From: sidu28 <60323455+Sidu28@users.noreply.github.com> Date: Thu, 26 Oct 2023 10:51:19 -0700 Subject: [PATCH 11/24] fixed balance update code --- src/contracts/pods/EigenPod.sol | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/contracts/pods/EigenPod.sol b/src/contracts/pods/EigenPod.sol index 6552cacd0f..483566815f 100644 --- a/src/contracts/pods/EigenPod.sol +++ b/src/contracts/pods/EigenPod.sol @@ -545,7 +545,12 @@ contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, Eigen // Done with proofs! Now update the validator's balance and send to the EigenPodManager if needed uint64 currentRestakedBalanceGwei = validatorInfo.restakedBalanceGwei; - uint64 newRestakedBalanceGwei = validatorBalance; + uint64 newRestakedBalanceGwei; + if (validatorBalance > MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR) { + newRestakedBalanceGwei = MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR; + } else { + newRestakedBalanceGwei = validatorBalance; + } // Update validator balance and timestamp, and save to state: validatorInfo.restakedBalanceGwei = newRestakedBalanceGwei; From e2f3867be87bb989eb6a5813832e40ee6b6eff1f Mon Sep 17 00:00:00 2001 From: sidu28 <60323455+Sidu28@users.noreply.github.com> Date: Thu, 26 Oct 2023 11:29:00 -0700 Subject: [PATCH 12/24] rectified test --- docs/core/EigenPod.md | 6 ------ script/M1_Deploy.s.sol | 6 ------ script/M1_deploy.config.json | 3 +-- script/middleware/DeployOpenEigenLayer.s.sol | 2 -- script/milestone/M2Deploy.s.sol | 1 - script/milestone/M2Deploy.sol | 1 - script/testing/M2_Deploy_From_Scratch.s.sol | 3 --- .../testing/M2_deploy_from_scratch.anvil.config.json | 3 +-- .../M2_deploy_from_scratch.mainnet.config.json | 4 +--- script/upgrade/GoerliUpgrade1.s.sol | 1 - src/contracts/pods/EigenPod.sol | 12 +++--------- src/test/DepositWithdraw.t.sol | 2 +- src/test/EigenLayerDeployer.t.sol | 3 --- src/test/EigenPod.t.sol | 5 +---- src/test/harnesses/EigenPodHarness.sol | 2 -- 15 files changed, 8 insertions(+), 46 deletions(-) diff --git a/docs/core/EigenPod.md b/docs/core/EigenPod.md index 6fda750327..790e499b58 100644 --- a/docs/core/EigenPod.md +++ b/docs/core/EigenPod.md @@ -30,12 +30,6 @@ Since the execution layer cannot yet synchronously read arbitrary state from the This will either be implemented by Succinct Labs or eventually by Ethereum natively in EIP-4788 ([read here](https://eips.ethereum.org/EIPS/eip-4788)). The interface will have requests be for a block number and responses be the block roots corresponding to the provided block numbers. -### Hysteresis - -We want to understimate validator balances on EigenLayer to be tolerant to small slashing events that occur on the beacon chain. - -We underestimate validator stake on EigenLayer through the following equation: $\text{eigenlayerBalance} = \text{min}(32, \text{floor}(x-C))$ where $C$ is some offset which we can assume is equal to 0.75. Since a validator's effective balance on the beacon chain can at most 0.25 ETH more than its actual balance on the beacon chain, we subtract 0.75 and floor it for simplicity. - ### Creating EigenPods Any user that wants to participate in native restaking first deploys an EigenPod contract by calling `createPod()` on the EigenPodManager. This deploys an EigenPod contract which is a BeaconProxy in the Beacon Proxy pattern. The user is called the *pod owner* of the EigenPod they deploy. diff --git a/script/M1_Deploy.s.sol b/script/M1_Deploy.s.sol index ac266f1597..a1aebb11d2 100644 --- a/script/M1_Deploy.s.sol +++ b/script/M1_Deploy.s.sol @@ -77,7 +77,6 @@ contract Deployer_M1 is Script, Test { // IMMUTABLES TO SET uint256 REQUIRED_BALANCE_WEI; uint256 MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR; - uint256 EFFECTIVE_RESTAKED_BALANCE_OFFSET_GWEI; uint64 GOERLI_GENESIS_TIME = 1616508000; // OTHER DEPLOYMENT PARAMETERS @@ -123,10 +122,6 @@ contract Deployer_M1 is Script, Test { config_data, ".eigenPod.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR" ); - EFFECTIVE_RESTAKED_BALANCE_OFFSET_GWEI = stdJson.readUint( - config_data, - ".eigenPod.EFFECTIVE_RESTAKED_BALANCE_OFFSET_GWEI" - ); // tokens to deploy strategies for StrategyConfig[] memory strategyConfigs; @@ -189,7 +184,6 @@ contract Deployer_M1 is Script, Test { delayedWithdrawalRouter, eigenPodManager, uint64(MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR), - uint64(EFFECTIVE_RESTAKED_BALANCE_OFFSET_GWEI), GOERLI_GENESIS_TIME ); diff --git a/script/M1_deploy.config.json b/script/M1_deploy.config.json index 3829ad7b2f..0086100919 100644 --- a/script/M1_deploy.config.json +++ b/script/M1_deploy.config.json @@ -38,8 +38,7 @@ { "PARTIAL_WITHDRAWAL_FRAUD_PROOF_PERIOD_BLOCKS": 50400, "REQUIRED_BALANCE_WEI": "31000000000000000000", - "MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR": "31000000000", - "EFFECTIVE_RESTAKED_BALANCE_OFFSET_GWEI": "750000000" + "MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR": "31000000000" }, "eigenPodManager": { diff --git a/script/middleware/DeployOpenEigenLayer.s.sol b/script/middleware/DeployOpenEigenLayer.s.sol index 9fc8eb7495..23eb5cdb84 100644 --- a/script/middleware/DeployOpenEigenLayer.s.sol +++ b/script/middleware/DeployOpenEigenLayer.s.sol @@ -117,8 +117,6 @@ contract DeployOpenEigenLayer is Script, Test { eigenPodManager, // uint64(MAX_VALIDATOR_BALANCE_GWEI), uint64(32 gwei), - // uint64(EFFECTIVE_RESTAKED_BALANCE_OFFSET_GWEI) - uint64(0.75 gwei), 1000 // temp genesis time ); diff --git a/script/milestone/M2Deploy.s.sol b/script/milestone/M2Deploy.s.sol index 531bd2d3d9..b49c10db29 100644 --- a/script/milestone/M2Deploy.s.sol +++ b/script/milestone/M2Deploy.s.sol @@ -190,7 +190,6 @@ contract M2Deploy is Script, Test { _delayedWithdrawalRouter: delayedWithdrawalRouter, _eigenPodManager: eigenPodManager, _MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR: 32 gwei, - _RESTAKED_BALANCE_OFFSET_GWEI: 0.5 gwei, _GENESIS_TIME: 1616508000 }); diff --git a/script/milestone/M2Deploy.sol b/script/milestone/M2Deploy.sol index a3aaa81a4f..b149bd406d 100644 --- a/script/milestone/M2Deploy.sol +++ b/script/milestone/M2Deploy.sol @@ -84,7 +84,6 @@ contract M2Deploy is Script, Test { _delayedWithdrawalRouter: delayedWithdrawalRouter, _eigenPodManager: eigenPodManager, _MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR: 32 gwei, - _RESTAKED_BALANCE_OFFSET_GWEI: 0.5 gwei, _GENESIS_TIME: 1616508000 }); diff --git a/script/testing/M2_Deploy_From_Scratch.s.sol b/script/testing/M2_Deploy_From_Scratch.s.sol index 77df8b4749..6830999598 100644 --- a/script/testing/M2_Deploy_From_Scratch.s.sol +++ b/script/testing/M2_Deploy_From_Scratch.s.sol @@ -76,7 +76,6 @@ contract Deployer_M2 is Script, Test { // IMMUTABLES TO SET uint64 MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR; - uint64 RESTAKED_BALANCE_OFFSET_GWEI; uint64 GOERLI_GENESIS_TIME = 1616508000; // OTHER DEPLOYMENT PARAMETERS @@ -121,7 +120,6 @@ contract Deployer_M2 is Script, Test { MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR = uint64( stdJson.readUint(config_data, ".eigenPod.MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR") ); - RESTAKED_BALANCE_OFFSET_GWEI = uint64(stdJson.readUint(config_data, ".eigenPod.RESTAKED_BALANCE_OFFSET_GWEI")); // tokens to deploy strategies for StrategyConfig[] memory strategyConfigs; @@ -184,7 +182,6 @@ contract Deployer_M2 is Script, Test { delayedWithdrawalRouter, eigenPodManager, MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR, - RESTAKED_BALANCE_OFFSET_GWEI, GOERLI_GENESIS_TIME ); diff --git a/script/testing/M2_deploy_from_scratch.anvil.config.json b/script/testing/M2_deploy_from_scratch.anvil.config.json index 80c007aedb..8f074e030c 100644 --- a/script/testing/M2_deploy_from_scratch.anvil.config.json +++ b/script/testing/M2_deploy_from_scratch.anvil.config.json @@ -12,8 +12,7 @@ }, "eigenPod": { "PARTIAL_WITHDRAWAL_FRAUD_PROOF_PERIOD_BLOCKS": 1, - "MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR": "32000000000", - "RESTAKED_BALANCE_OFFSET_GWEI": "750000000" + "MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR": "32000000000" }, "eigenPodManager": { "max_pods": 0, diff --git a/script/testing/M2_deploy_from_scratch.mainnet.config.json b/script/testing/M2_deploy_from_scratch.mainnet.config.json index 7981fc0577..e61ac9055c 100644 --- a/script/testing/M2_deploy_from_scratch.mainnet.config.json +++ b/script/testing/M2_deploy_from_scratch.mainnet.config.json @@ -35,9 +35,7 @@ "eigenPod": { "PARTIAL_WITHDRAWAL_FRAUD_PROOF_PERIOD_BLOCKS": 50400, - "MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR": "32000000000", - "RESTAKED_BALANCE_OFFSET_GWEI": "750000000" - + "MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR": "32000000000" }, "eigenPodManager": { diff --git a/script/upgrade/GoerliUpgrade1.s.sol b/script/upgrade/GoerliUpgrade1.s.sol index 9459c7786e..5d645a25b1 100644 --- a/script/upgrade/GoerliUpgrade1.s.sol +++ b/script/upgrade/GoerliUpgrade1.s.sol @@ -73,7 +73,6 @@ contract GoerliUpgrade1 is Script, Test { delayedWithdrawalRouter, eigenPodManager, 32e9, - 75e7, 1616508000 ) ); diff --git a/src/contracts/pods/EigenPod.sol b/src/contracts/pods/EigenPod.sol index 483566815f..a5535d9582 100644 --- a/src/contracts/pods/EigenPod.sol +++ b/src/contracts/pods/EigenPod.sol @@ -20,6 +20,8 @@ import "../interfaces/IPausable.sol"; import "./EigenPodPausingConstants.sol"; +import "forge-std/Test.sol"; + /** * @title The implementation contract used for restaking beacon chain ETH on EigenLayer * @author Layr Labs, Inc. @@ -34,7 +36,7 @@ import "./EigenPodPausingConstants.sol"; * @dev Note that all beacon chain balances are stored as gwei within the beacon chain datastructures. We choose * to account balances in terms of gwei in the EigenPod contract and convert to wei when making calls to other contracts */ -contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, EigenPodPausingConstants { +contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, EigenPodPausingConstants, Test { using BytesLib for bytes; using SafeERC20 for IERC20; using BeaconChainProofs for *; @@ -58,12 +60,6 @@ contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, Eigen ///@notice The maximum amount of ETH, in gwei, a validator can have restaked in the eigenlayer uint64 public immutable MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR; - /** - * @notice The value used in our effective restaked balance calculation, to set the - * amount by which to underestimate the validator's effective balance. - */ - uint64 public immutable RESTAKED_BALANCE_OFFSET_GWEI; - /// @notice This is the genesis time of the beacon state, to help us calculate conversions between slot and timestamp uint64 public immutable GENESIS_TIME; @@ -144,14 +140,12 @@ contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, Eigen IDelayedWithdrawalRouter _delayedWithdrawalRouter, IEigenPodManager _eigenPodManager, uint64 _MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR, - uint64 _RESTAKED_BALANCE_OFFSET_GWEI, uint64 _GENESIS_TIME ) { ethPOS = _ethPOS; delayedWithdrawalRouter = _delayedWithdrawalRouter; eigenPodManager = _eigenPodManager; MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR = _MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR; - RESTAKED_BALANCE_OFFSET_GWEI = _RESTAKED_BALANCE_OFFSET_GWEI; GENESIS_TIME = _GENESIS_TIME; _disableInitializers(); } diff --git a/src/test/DepositWithdraw.t.sol b/src/test/DepositWithdraw.t.sol index e6a1fc3a4e..238792e3fa 100644 --- a/src/test/DepositWithdraw.t.sol +++ b/src/test/DepositWithdraw.t.sol @@ -483,7 +483,7 @@ contract DepositWithdrawTests is EigenLayerTestHelper { ); ethPOSDeposit = new ETHPOSDepositMock(); - pod = new EigenPod(ethPOSDeposit, delayedWithdrawalRouter, eigenPodManager, MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR, EFFECTIVE_RESTAKED_BALANCE_OFFSET, GOERLI_GENESIS_TIME); + pod = new EigenPod(ethPOSDeposit, delayedWithdrawalRouter, eigenPodManager, MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR, GOERLI_GENESIS_TIME); eigenPodBeacon = new UpgradeableBeacon(address(pod)); diff --git a/src/test/EigenLayerDeployer.t.sol b/src/test/EigenLayerDeployer.t.sol index f8fada4fd2..88f68d28da 100644 --- a/src/test/EigenLayerDeployer.t.sol +++ b/src/test/EigenLayerDeployer.t.sol @@ -78,7 +78,6 @@ contract EigenLayerDeployer is Operators { uint256 REQUIRED_BALANCE_WEI = 32 ether; uint64 MAX_PARTIAL_WTIHDRAWAL_AMOUNT_GWEI = 1 ether / 1e9; uint64 MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR = 32e9; - uint64 EFFECTIVE_RESTAKED_BALANCE_OFFSET = 75e7; uint64 GOERLI_GENESIS_TIME = 1616508000; address pauser; @@ -170,7 +169,6 @@ contract EigenLayerDeployer is Operators { delayedWithdrawalRouter, eigenPodManager, MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR, - EFFECTIVE_RESTAKED_BALANCE_OFFSET, GOERLI_GENESIS_TIME ); @@ -245,7 +243,6 @@ contract EigenLayerDeployer is Operators { delayedWithdrawalRouter, eigenPodManager, MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR, - EFFECTIVE_RESTAKED_BALANCE_OFFSET, GOERLI_GENESIS_TIME ); diff --git a/src/test/EigenPod.t.sol b/src/test/EigenPod.t.sol index 0d5dff6211..2b2d1ca4d0 100644 --- a/src/test/EigenPod.t.sol +++ b/src/test/EigenPod.t.sol @@ -158,7 +158,6 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants { delayedWithdrawalRouter, IEigenPodManager(podManagerAddress), MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR, - RESTAKED_BALANCE_OFFSET_GWEI, GOERLI_GENESIS_TIME ); @@ -883,10 +882,9 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants { uint64 newValidatorBalance = BeaconChainProofs.getBalanceAtIndex(getBalanceRoot(), uint40(getValidatorIndex())); int256 shareDiff = beaconChainETHBefore - eigenPodManager.podOwnerShares(podOwner); - assertTrue( eigenPodManager.podOwnerShares(podOwner) == - int256((newValidatorBalance) * GWEI_TO_WEI), + int256((MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR) * GWEI_TO_WEI), "hysterisis not working" ); assertTrue( @@ -2115,7 +2113,6 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants { delayedWithdrawalRouter, IEigenPodManager(podManagerAddress), MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR, - RESTAKED_BALANCE_OFFSET_GWEI, GOERLI_GENESIS_TIME ); } diff --git a/src/test/harnesses/EigenPodHarness.sol b/src/test/harnesses/EigenPodHarness.sol index 507757c90b..1c341448b1 100644 --- a/src/test/harnesses/EigenPodHarness.sol +++ b/src/test/harnesses/EigenPodHarness.sol @@ -10,14 +10,12 @@ contract EPInternalFunctions is EigenPod { IDelayedWithdrawalRouter _delayedWithdrawalRouter, IEigenPodManager _eigenPodManager, uint64 _MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR, - uint64 _RESTAKED_BALANCE_OFFSET_GWEI, uint64 _GENESIS_TIME ) EigenPod( _ethPOS, _delayedWithdrawalRouter, _eigenPodManager, _MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR, - _RESTAKED_BALANCE_OFFSET_GWEI, _GENESIS_TIME ) {} From e2bc5f6caeac2072510b07f0df40845d7d8133c1 Mon Sep 17 00:00:00 2001 From: sidu28 <60323455+Sidu28@users.noreply.github.com> Date: Thu, 26 Oct 2023 11:33:15 -0700 Subject: [PATCH 13/24] removed test import --- src/contracts/pods/EigenPod.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/contracts/pods/EigenPod.sol b/src/contracts/pods/EigenPod.sol index a5535d9582..0f27a7bac0 100644 --- a/src/contracts/pods/EigenPod.sol +++ b/src/contracts/pods/EigenPod.sol @@ -20,8 +20,6 @@ import "../interfaces/IPausable.sol"; import "./EigenPodPausingConstants.sol"; -import "forge-std/Test.sol"; - /** * @title The implementation contract used for restaking beacon chain ETH on EigenLayer * @author Layr Labs, Inc. @@ -36,7 +34,7 @@ import "forge-std/Test.sol"; * @dev Note that all beacon chain balances are stored as gwei within the beacon chain datastructures. We choose * to account balances in terms of gwei in the EigenPod contract and convert to wei when making calls to other contracts */ -contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, EigenPodPausingConstants, Test { +contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, EigenPodPausingConstants { using BytesLib for bytes; using SafeERC20 for IERC20; using BeaconChainProofs for *; From c19f650d9de903cd00982db0bba2acda9606fe17 Mon Sep 17 00:00:00 2001 From: sidu28 <60323455+Sidu28@users.noreply.github.com> Date: Thu, 26 Oct 2023 11:40:23 -0700 Subject: [PATCH 14/24] fixed --- src/test/EigenPod.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/EigenPod.t.sol b/src/test/EigenPod.t.sol index 2b2d1ca4d0..6719a567ca 100644 --- a/src/test/EigenPod.t.sol +++ b/src/test/EigenPod.t.sol @@ -707,7 +707,7 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants { int256 beaconChainETHShares = eigenPodManager.podOwnerShares(podOwner); require( - beaconChainETHShares == int256((newValidatorBalance) * GWEI_TO_WEI), + beaconChainETHShares == int256((MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR) * GWEI_TO_WEI), "eigenPodManager shares not updated correctly" ); } @@ -929,7 +929,7 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants { assertTrue( eigenPodManager.podOwnerShares(podOwner) == - int256((newValidatorBalance) * GWEI_TO_WEI), + int256((MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR) * GWEI_TO_WEI), "hysterisis not working" ); assertTrue( From f27e50e496a10c5fd982118489427ace3896de43 Mon Sep 17 00:00:00 2001 From: sidu28 <60323455+Sidu28@users.noreply.github.com> Date: Thu, 26 Oct 2023 12:11:13 -0700 Subject: [PATCH 15/24] removed other references to hysterisis --- docs/core/EigenPodManager.md | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/docs/core/EigenPodManager.md b/docs/core/EigenPodManager.md index df34a0fb76..c081cfc1a7 100644 --- a/docs/core/EigenPodManager.md +++ b/docs/core/EigenPodManager.md @@ -46,7 +46,7 @@ The functions of the `EigenPodManager` and `EigenPod` contracts are tightly link * `validatorStatus`: (`INACTIVE`, `ACTIVE`, `WITHDRAWN`) * `validatorIndex`: A `uint40` that is unique for each validator making a successful deposit via the deposit contract * `mostRecentBalanceUpdateTimestamp`: A timestamp that represents the most recent successful proof of the validator's effective balance - * `restakedBalanceGwei`: Calculated against the validator's proven effective balance using `_calculateRestakedBalanceGwei` (see definitions below) + * `restakedBalanceGwei`: set to the validator's balance. * `withdrawableRestakedExecutionLayerGwei`: When a Staker proves that a validator has exited from the beacon chain, the withdrawal amount is added to this variable. When completing a withdrawal of beacon chain ETH, the withdrawal amount is subtracted from this variable. See also: * [`DelegationManager`: "Undelegating and Withdrawing"](./DelegationManager.md#undelegating-and-withdrawing) * [`EigenPodManager`: "Withdrawal Processing"](#withdrawal-processing) @@ -58,14 +58,6 @@ The functions of the `EigenPodManager` and `EigenPod` contracts are tightly link * Pod Owners can delegate their `EigenPodManager` shares to Operators (via `DelegationManager`). * These shares correspond to the amount of provably-restaked beacon chain ETH held by the Pod Owner via their `EigenPod`. * `EigenPod`: - * `_calculateRestakedBalanceGwei(uint64 effectiveBalance) -> (uint64)`: - * This method is used by an `EigenPod` to calculate a "pessimistic" view of a validator's effective balance to avoid the need for repeated balance updates when small balance fluctuations occur. - * The calculation subtracts an offset (`RESTAKED_BALANCE_OFFSET_GWEI`) from the validator's proven balance, and round down to the nearest ETH - * Related: `uint64 RESTAKED_BALANCE_OFFSET_GWEI` - * As of M2, this is 0.75 ETH (in Gwei) - * Related: `uint64 MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR` - * As of M2, this is 31 ETH (in Gwei) - * This is the maximum amount of restaked ETH a single validator can be credited with in EigenLayer * `_podWithdrawalCredentials() -> (bytes memory)`: * Gives `abi.encodePacked(bytes1(uint8(1)), bytes11(0), address(EigenPod))` * These are the `0x01` withdrawal credentials of the `EigenPod`, used as a validator's withdrawal credentials on the beacon chain. @@ -192,7 +184,7 @@ For each validator the Pod Owner wants to verify, the Pod Owner must supply: * `VALIDATOR_STATUS` moves from `INACTIVE` to `ACTIVE` * `validatorIndex` is recorded * `mostRecentBalanceUpdateTimestamp` is set to the `oracleTimestamp` used to fetch the beacon block root - * `restakedBalanceGwei` is set to `_calculateRestakedBalanceGwei(effectiveBalance)` + * `restakedBalanceGwei` is set to the validator's balance * See [`EigenPodManager.recordBeaconChainETHBalanceUpdate`](#eigenpodmanagerrecordbeaconchainethbalanceupdate) *Requirements*: From 014b6779739b48efb22950ae21bcc6db50e62fd5 Mon Sep 17 00:00:00 2001 From: sidu28 <60323455+Sidu28@users.noreply.github.com> Date: Thu, 26 Oct 2023 13:10:31 -0700 Subject: [PATCH 16/24] made updates consistent --- src/contracts/pods/EigenPod.sol | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/contracts/pods/EigenPod.sol b/src/contracts/pods/EigenPod.sol index 0f27a7bac0..f563279231 100644 --- a/src/contracts/pods/EigenPod.sol +++ b/src/contracts/pods/EigenPod.sol @@ -475,7 +475,12 @@ contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, Eigen validatorInfo.status = VALIDATOR_STATUS.ACTIVE; validatorInfo.validatorIndex = validatorIndex; validatorInfo.mostRecentBalanceUpdateTimestamp = oracleTimestamp; - validatorInfo.restakedBalanceGwei = validatorEffectiveBalanceGwei; + + if (validatorEffectiveBalanceGwei > MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR) { + validatorInfo.restakedBalanceGwei = MAX_RESTAKED_BALANCE_GWEI_PER_VALIDATOR; + } else { + validatorInfo.restakedBalanceGwei = validatorEffectiveBalanceGwei; + } _validatorPubkeyHashToInfo[validatorPubkeyHash] = validatorInfo; emit ValidatorRestaked(validatorIndex); From 76701072594c9f5a8e8995b8e5bbcf8fbf5295ec Mon Sep 17 00:00:00 2001 From: pandabadger <33740825+pandabadger@users.noreply.github.com> Date: Thu, 26 Oct 2023 22:44:34 +0100 Subject: [PATCH 17/24] Update DelegationManager.md --- docs/core/DelegationManager.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/core/DelegationManager.md b/docs/core/DelegationManager.md index 3c3bac3acf..b61bb2103a 100644 --- a/docs/core/DelegationManager.md +++ b/docs/core/DelegationManager.md @@ -375,7 +375,7 @@ Called by the `EigenPodManager` when a Staker's shares decrease. This method is * `EigenPod.verifyAndProcessWithdrawals` *Effects*: If the Staker in question is delegated to an Operator, the Operator's delegated balance for the `strategy` is decreased by `shares` -* This method is a no-op if the Staker is not delegated as an Operator. +* This method is a no-op if the Staker is not delegated to an Operator. *Requirements*: * Caller MUST be either the `StrategyManager` or `EigenPodManager` (although the `StrategyManager` doesn't use this method) From b4c0514e24fc373823d76260ab735c8b7465f930 Mon Sep 17 00:00:00 2001 From: ChaoticWalrus <93558947+ChaoticWalrus@users.noreply.github.com> Date: Thu, 26 Oct 2023 14:44:36 -0700 Subject: [PATCH 18/24] chore: remove never-used storage at end of storage layout and increase __gap size to compensate This storage is at the end of the used slots in the storage layout, and was never used either on testnet or mainnet. Therefore, it should be able to be safety deleted without consequence. This commit also increases the size of the __gap variable to compensate for the removed storage. --- src/contracts/pods/EigenPodManagerStorage.sol | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/contracts/pods/EigenPodManagerStorage.sol b/src/contracts/pods/EigenPodManagerStorage.sol index 75b7365e61..b893a08164 100644 --- a/src/contracts/pods/EigenPodManagerStorage.sol +++ b/src/contracts/pods/EigenPodManagerStorage.sol @@ -64,12 +64,6 @@ abstract contract EigenPodManagerStorage is IEigenPodManager { */ mapping(address => int256) public podOwnerShares; - /// @notice Mapping: podOwner => cumulative number of queued withdrawals of beaconchainETH they have ever initiated. only increments (doesn't decrement) - mapping(address => uint256) public cumulativeWithdrawalsQueued; - - /// @notice Mapping: hash of withdrawal inputs, aka 'withdrawalRoot' => whether the withdrawal is pending - mapping(bytes32 => bool) public withdrawalRootPending; - constructor( IETHPOSDeposit _ethPOS, IBeacon _eigenPodBeacon, @@ -89,5 +83,5 @@ abstract contract EigenPodManagerStorage is IEigenPodManager { * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[43] private __gap; + uint256[45] private __gap; } From 3aad4e4044b6c9676cceec8de4201bcdd8194442 Mon Sep 17 00:00:00 2001 From: pandabadger <33740825+pandabadger@users.noreply.github.com> Date: Thu, 26 Oct 2023 22:47:42 +0100 Subject: [PATCH 19/24] Update EigenPod.md --- docs/core/EigenPod.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/core/EigenPod.md b/docs/core/EigenPod.md index f595617f26..096b5f698d 100644 --- a/docs/core/EigenPod.md +++ b/docs/core/EigenPod.md @@ -58,7 +58,7 @@ We will take a brief aside to explain a simpler part of the protocol, partial wi Note that partial withdrawals can be withdrawn immediately from the system because they are not staked on EigenLayer, unlike the base validator stake that is restaked on EigenLayer -Currently, users can submit partial withdrawal proofs one at a time, at a cost of around 30-40k gas per proof. This is highly inefficient as partial withdrawals are often nominal amounts for which an expensive proof transaction each time is not feasible for our users. The solution is to use a succinct zk proving solution to generate a single proof for multiple withdrawals, which can be verified for a fixed cost of around 300k gas. This system and associated integration are currently under development. +Currently, users can submit partial withdrawal proofs one at a time, at a cost of around 30-40k gas per proof. This is highly inefficient as partial withdrawals are often nominal amounts for which an expensive proof transaction each time is not feasible for our users. The solution is to use succinct zk proving solution to generate a single proof for multiple withdrawals, which can be verified for a fixed cost of around 300k gas. This system and associated integration are currently under development. ### Proofs of Validator Balance Updates From cd6166fb0ca0e5636c04dd826d6d4dfb028cfc6e Mon Sep 17 00:00:00 2001 From: sidu28 <60323455+Sidu28@users.noreply.github.com> Date: Thu, 26 Oct 2023 14:55:08 -0700 Subject: [PATCH 20/24] clarified as effective not actual balance --- docs/core/EigenPodManager.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/core/EigenPodManager.md b/docs/core/EigenPodManager.md index c081cfc1a7..fe5ea172af 100644 --- a/docs/core/EigenPodManager.md +++ b/docs/core/EigenPodManager.md @@ -184,7 +184,7 @@ For each validator the Pod Owner wants to verify, the Pod Owner must supply: * `VALIDATOR_STATUS` moves from `INACTIVE` to `ACTIVE` * `validatorIndex` is recorded * `mostRecentBalanceUpdateTimestamp` is set to the `oracleTimestamp` used to fetch the beacon block root - * `restakedBalanceGwei` is set to the validator's balance + * `restakedBalanceGwei` is set to the validator's effective balance * See [`EigenPodManager.recordBeaconChainETHBalanceUpdate`](#eigenpodmanagerrecordbeaconchainethbalanceupdate) *Requirements*: From 5a0015ec2fb8280547a0ef65c2a4bbe335b17c5a Mon Sep 17 00:00:00 2001 From: sidu28 <60323455+Sidu28@users.noreply.github.com> Date: Thu, 26 Oct 2023 14:57:35 -0700 Subject: [PATCH 21/24] removed inline comment --- src/contracts/pods/EigenPod.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/contracts/pods/EigenPod.sol b/src/contracts/pods/EigenPod.sol index f563279231..4b7d56d464 100644 --- a/src/contracts/pods/EigenPod.sol +++ b/src/contracts/pods/EigenPod.sol @@ -458,8 +458,7 @@ contract EigenPod is IEigenPod, Initializable, ReentrancyGuardUpgradeable, Eigen * balance, always a multiple of 1 ETH, will only lower to the next multiple of 1 ETH if the current balance is less * than 0.25 ETH below their current effective balance. For example, if the effective balance is 31ETH, it only falls to * 30ETH when the true balance falls below 30.75ETH. Thus in the worst case, the effective balance is overestimating the - * actual validator balance by 0.25 ETH. In EigenLayer, we calculate our own "restaked balance" which is a further pessimistic - * view of the validator's effective balance. + * actual validator balance by 0.25 ETH. */ uint64 validatorEffectiveBalanceGwei = validatorFields.getEffectiveBalanceGwei(); From 6b73cd8707cf8dae159a778bda10c41b6e5bb83c Mon Sep 17 00:00:00 2001 From: sidu28 <60323455+Sidu28@users.noreply.github.com> Date: Thu, 26 Oct 2023 22:12:27 -0700 Subject: [PATCH 22/24] changed proof gen repo commands --- src/test/EigenPod.t.sol | 25 +++++----- .../withdrawal_credential_proof_302913.json | 46 ++++++++++--------- 2 files changed, 38 insertions(+), 33 deletions(-) diff --git a/src/test/EigenPod.t.sol b/src/test/EigenPod.t.sol index e82669f843..3106cd85fd 100644 --- a/src/test/EigenPod.t.sol +++ b/src/test/EigenPod.t.sol @@ -295,7 +295,7 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants { } function testDeployEigenPodWithoutActivateRestaking() public { - // ./solidityProofGen "ValidatorFieldsProof" 302913 true "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "withdrawal_credential_proof_302913.json" + // ./solidityProofGen -newBalance=32000115173 "ValidatorFieldsProof" 302913 true "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "withdrawal_credential_proof_302913.json" setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json"); IEigenPod newPod = eigenPodManager.getPod(podOwner); @@ -330,7 +330,7 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants { } function testDeployEigenPodTooSoon() public { - // ./solidityProofGen "ValidatorFieldsProof" 302913 true "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "withdrawal_credential_proof_302913.json" + // ./solidityProofGen -newBalance=32000115173 "ValidatorFieldsProof" 302913 true "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "withdrawal_credential_proof_302913.json" setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json"); IEigenPod newPod = eigenPodManager.getPod(podOwner); @@ -441,7 +441,7 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants { /// @notice This test is to ensure the full withdrawal flow works function testFullWithdrawalFlow() public returns (IEigenPod) { //this call is to ensure that validator 302913 has proven their withdrawalcreds - // ./solidityProofGen "ValidatorFieldsProof" 302913 true "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "withdrawal_credential_proof_302913.json" setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json"); + // ./solidityProofGen -newBalance=32000115173 "ValidatorFieldsProof" 302913 true "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "withdrawal_credential_proof_302913.json" setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json"); _testDeployAndVerifyNewEigenPod(podOwner, signature, depositDataRoot); IEigenPod newPod = eigenPodManager.getPod(podOwner); @@ -532,7 +532,7 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants { /// @notice This test is to ensure that the partial withdrawal flow works correctly function testPartialWithdrawalFlow() public returns (IEigenPod) { //this call is to ensure that validator 61068 has proven their withdrawalcreds - // ./solidityProofGen "ValidatorFieldsProof" 302913 true "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "withdrawal_credential_proof_302913.json" setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json"); + // ./solidityProofGen -newBalance=32000115173 "ValidatorFieldsProof" 302913 true "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "withdrawal_credential_proof_302913.json" setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json"); _testDeployAndVerifyNewEigenPod(podOwner, signature, depositDataRoot); IEigenPod newPod = eigenPodManager.getPod(podOwner); @@ -685,7 +685,7 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants { } function testDeployAndVerifyNewEigenPod() public returns (IEigenPod) { - // ./solidityProofGen "ValidatorFieldsProof" 302913 true "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "withdrawal_credential_proof_302913.json" setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json"); + // ./solidityProofGen -newBalance=32000115173 "ValidatorFieldsProof" 302913 true "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "withdrawal_credential_proof_302913.json" setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json"); return _testDeployAndVerifyNewEigenPod(podOwner, signature, depositDataRoot); } @@ -714,7 +714,7 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants { //test deploying an eigen pod with mismatched withdrawal credentials between the proof and the actual pod's address function testDeployNewEigenPodWithWrongWithdrawalCreds(address wrongWithdrawalAddress) public { - // ./solidityProofGen "ValidatorFieldsProof" 302913 true "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "withdrawal_credential_proof_302913.json" setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json"); + // ./solidityProofGen -newBalance=32000115173 "ValidatorFieldsProof" 302913 true "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "withdrawal_credential_proof_302913.json" setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json"); cheats.startPrank(podOwner); eigenPodManager.stake{value: stakeAmount}(pubkey, signature, depositDataRoot); @@ -760,6 +760,7 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants { //ensures that a validator proving WC after they have exited the beacon chain is allowed to //prove their WC and process a withdrawal function testProveWithdrawalCredentialsAfterValidatorExit() public { + // ./solidityProofGen -newBalance=0 "ValidatorFieldsProof" 302913 true "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "withdrawal_credential_proof_302913_exited.json" setJSON("./src/test/test-data/withdrawal_credential_proof_302913_exited.json"); IEigenPod newPod = _testDeployAndVerifyNewEigenPod(podOwner, signature, depositDataRoot); //./solidityProofGen "WithdrawalFieldsProof" 302913 146 8092 true false "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6397852.json" "data/withdrawal_proof_goerli/goerli_block_header_6397852.json" "data/withdrawal_proof_goerli/goerli_block_6397852.json" "fullWithdrawalProof_Latest.json" false @@ -772,7 +773,7 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants { function testVerifyWithdrawalCredsFromNonPodOwnerAddress(address nonPodOwnerAddress) public { // nonPodOwnerAddress must be different from podOwner cheats.assume(nonPodOwnerAddress != podOwner); - // ./solidityProofGen "ValidatorFieldsProof" 302913 true "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "withdrawal_credential_proof_302913.json" setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json"); + // ./solidityProofGen -newBalance=32000115173 "ValidatorFieldsProof" 302913 true "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "withdrawal_credential_proof_302913.json" setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json"); cheats.startPrank(podOwner); eigenPodManager.stake{value: stakeAmount}(pubkey, signature, depositDataRoot); @@ -805,7 +806,7 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants { //test that when withdrawal credentials are verified more than once, it reverts function testDeployNewEigenPodWithActiveValidator() public { - // ./solidityProofGen "ValidatorFieldsProof" 302913 true "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "withdrawal_credential_proof_302913.json" setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json"); + // ./solidityProofGen -newBalance=32000115173 "ValidatorFieldsProof" 302913 true "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "withdrawal_credential_proof_302913.json" setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json"); IEigenPod pod = _testDeployAndVerifyNewEigenPod(podOwner, signature, depositDataRoot); @@ -842,7 +843,7 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants { // // validator status should be marked as ACTIVE function testProveSingleWithdrawalCredential() public { - // ./solidityProofGen "ValidatorFieldsProof" 302913 true "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "withdrawal_credential_proof_302913.json" setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json"); + // ./solidityProofGen -newBalance=32000115173 "ValidatorFieldsProof" 302913 true "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "withdrawal_credential_proof_302913.json" setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json"); IEigenPod pod = _testDeployAndVerifyNewEigenPod(podOwner, signature, depositDataRoot); bytes32 validatorPubkeyHash = getValidatorPubkeyHash(); @@ -1046,7 +1047,7 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants { */ function testVerifyCorrectWithdrawalCredentialsRevertsWhenPaused() external { - // ./solidityProofGen "ValidatorFieldsProof" 302913 true "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "withdrawal_credential_proof_302913.json" setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json"); + // ./solidityProofGen -newBalance=32000115173 "ValidatorFieldsProof" 302913 true "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "withdrawal_credential_proof_302913.json" setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json"); bytes32 newBeaconStateRoot = getBeaconStateRoot(); BeaconChainOracleMock(address(beaconChainOracle)).setOracleBlockRootAtTimestamp(newBeaconStateRoot); @@ -1218,7 +1219,7 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants { } function testWithdrawBeforeRestakingAfterRestaking() public { - // ./solidityProofGen "ValidatorFieldsProof" 302913 true "data/withdrawal_proof_goerli/goerli_slot_6399999.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "withdrawal_credential_proof_510257.json" + // ./solidityProofGen -newBalance=32000115173 "ValidatorFieldsProof" 302913 true "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "withdrawal_credential_proof_302913.json" setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json"); IEigenPod pod = _testDeployAndVerifyNewEigenPod(podOwner, signature, depositDataRoot); @@ -1769,7 +1770,7 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants { /* TODO: reimplement similar tests function testQueueBeaconChainETHWithdrawalWithoutProvingFullWithdrawal() external { - // ./solidityProofGen "ValidatorFieldsProof" 302913 true "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "withdrawal_credential_proof_302913.json" setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json"); + // ./solidityProofGen -newBalance=32000115173 "ValidatorFieldsProof" 302913 true "data/withdrawal_proof_goerli/goerli_block_header_6399998.json" "data/withdrawal_proof_goerli/goerli_slot_6399998.json" "withdrawal_credential_proof_302913.json" setJSON("./src/test/test-data/withdrawal_credential_proof_302913.json"); _testDeployAndVerifyNewEigenPod(podOwner, signature, depositDataRoot); uint256 shareAmount = 31e18; diff --git a/src/test/test-data/withdrawal_credential_proof_302913.json b/src/test/test-data/withdrawal_credential_proof_302913.json index 7bda1451c3..b35ae433bd 100644 --- a/src/test/test-data/withdrawal_credential_proof_302913.json +++ b/src/test/test-data/withdrawal_credential_proof_302913.json @@ -1,9 +1,8 @@ { "validatorIndex": 302913, - "beaconStateRoot": "0x4fa780c32b4cf43ade769a51d738d889858c23197e4567ad537270220ab923e0", - "slotRoot": "0xfea7610000000000000000000000000000000000000000000000000000000000", - "balanceRoot": "0x6cba5d73070000009ab05d730700000008bd5d7307000000239e5d7307000000", - "latestBlockHeaderRoot": "0xdc501e6d6439fb13ee2020b4013374be51e35c58d611115a96856cbf71edaf73", + "beaconStateRoot": "0x4678e21381463879490925e8911492b890c0ce7ba347cce57fd8d4e0bb27f04a", + "balanceRoot": "0x6cba5d7307000000e5015b730700000008bd5d7307000000239e5d7307000000", + "latestBlockHeaderRoot": "0x859816e76b0944c3110a31f26acc301718122e1acfe8fb161df9c0e684515593", "ValidatorBalanceProof": [ "0x26805d73070000002aa55d7307000000218e5d730700000056d35d7307000000", "0x24846a54724508d8394a9e30f819bc19fe6727745be4f19e1e3924b985b4b8e9", @@ -43,22 +42,12 @@ "0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c", "0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327", "0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74", - "0x846b080000000000000000000000000000000000000000000000000000000000" - ], - "ValidatorFields": [ - "0xe36689b7b39ee895a754ba878afac2aa5d83349143a8b23d371823dd9ed3435d", - "0x01000000000000000000000049c486e3f4303bc11c02f952fe5b08d0ab22d443", - "0x0040597307000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0xea65010000000000000000000000000000000000000000000000000000000000", - "0xf265010000000000000000000000000000000000000000000000000000000000", - "0xffffffffffffffff000000000000000000000000000000000000000000000000", - "0xffffffffffffffff000000000000000000000000000000000000000000000000" - ], - "StateRootAgainstLatestBlockHeaderProof": [ - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b", - "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71" + "0x846b080000000000000000000000000000000000000000000000000000000000", + "0x7d46d32db9a97974916a2114aeffc11fc031a954b2110742267054a438055c1a", + "0x81e1234eb27473f180f890c98019ecb7655a3e58105c7182d8daf726d75aaf8d", + "0xa158c9a519bf3c8d2296ebb6a2df54283c7014e7b84524da0574c95fe600a1fd", + "0x4715bf9a259680cd06827b30bddb27ad445506e9edeb72a3eab904d94dea816b", + "0x5ba049ff558dd0ff1eadf2cef346aac41b7059433f21925b345f1024af18057d" ], "WithdrawalCredentialProof": [ "0x9e06c3582190fe488eac3f9f6c95622742f9afe3e038b39d2ca97ba6d5d0de4e", @@ -104,8 +93,23 @@ "0x846b080000000000000000000000000000000000000000000000000000000000", "0x5a6c050000000000000000000000000000000000000000000000000000000000", "0x47314ebc95c63f3fd909af6442ed250d823b2ee8a6e48d8167d4dfdab96c8b5e", - "0xce3d1b002aa817e3718132b7ffe6cea677f81b1b3b690b8052732d8e1a70d06b", + "0x3f918c09ea95d520538a81f767de0f1be3f30fc9d599805c68048ef5c23a85e2", "0x4715bf9a259680cd06827b30bddb27ad445506e9edeb72a3eab904d94dea816b", "0x5ba049ff558dd0ff1eadf2cef346aac41b7059433f21925b345f1024af18057d" + ], + "ValidatorFields": [ + "0xe36689b7b39ee895a754ba878afac2aa5d83349143a8b23d371823dd9ed3435d", + "0x01000000000000000000000049c486e3f4303bc11c02f952fe5b08d0ab22d443", + "0xe5015b7307000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xea65010000000000000000000000000000000000000000000000000000000000", + "0xf265010000000000000000000000000000000000000000000000000000000000", + "0xffffffffffffffff000000000000000000000000000000000000000000000000", + "0xffffffffffffffff000000000000000000000000000000000000000000000000" + ], + "StateRootAgainstLatestBlockHeaderProof": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b", + "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71" ] } \ No newline at end of file From 75a5e556e83f471cba2f164b3a40e31f94f89bc4 Mon Sep 17 00:00:00 2001 From: Gajesh Naik Date: Fri, 27 Oct 2023 20:06:52 +0530 Subject: [PATCH 23/24] made the changes to createPod() --- src/contracts/interfaces/IEigenPodManager.sol | 3 ++- src/contracts/pods/EigenPodManager.sol | 7 +++++-- src/test/EigenPod.t.sol | 8 ++++++++ src/test/mocks/EigenPodManagerMock.sol | 2 +- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/contracts/interfaces/IEigenPodManager.sol b/src/contracts/interfaces/IEigenPodManager.sol index ad6e71ddb0..2ed9063c0e 100644 --- a/src/contracts/interfaces/IEigenPodManager.sol +++ b/src/contracts/interfaces/IEigenPodManager.sol @@ -42,8 +42,9 @@ interface IEigenPodManager is IPausable { /** * @notice Creates an EigenPod for the sender. * @dev Function will revert if the `msg.sender` already has an EigenPod. + * @dev Returns EigenPod address */ - function createPod() external; + function createPod() external returns (address); /** * @notice Stakes for a new beacon chain validator on the sender's EigenPod. diff --git a/src/contracts/pods/EigenPodManager.sol b/src/contracts/pods/EigenPodManager.sol index d53d5af25e..efc5b9080b 100644 --- a/src/contracts/pods/EigenPodManager.sol +++ b/src/contracts/pods/EigenPodManager.sol @@ -70,11 +70,14 @@ contract EigenPodManager is /** * @notice Creates an EigenPod for the sender. * @dev Function will revert if the `msg.sender` already has an EigenPod. + * @dev Returns EigenPod address */ - function createPod() external { + function createPod() external returns (address) { require(!hasPod(msg.sender), "EigenPodManager.createPod: Sender already has a pod"); // deploy a pod if the sender doesn't have one already - _deployPod(); + IEigenPod pod = _deployPod(); + + return address(pod); } /** diff --git a/src/test/EigenPod.t.sol b/src/test/EigenPod.t.sol index ec1ccb6546..8b436d2c61 100644 --- a/src/test/EigenPod.t.sol +++ b/src/test/EigenPod.t.sol @@ -994,6 +994,14 @@ contract EigenPodTests is ProofParsing, EigenPodPausingConstants { cheats.stopPrank(); } + function testCreatePodIfItReturnsPodAddress() external { + cheats.startPrank(podOwner); + address podAddress = eigenPodManager.createPod(); + cheats.stopPrank(); + IEigenPod pod = eigenPodManager.getPod(podOwner); + require(podAddress == address(pod), "invalid pod address"); + } + function testStakeOnEigenPodFromNonPodManagerAddress(address nonPodManager) external fuzzedAddress(nonPodManager) { cheats.assume(nonPodManager != address(eigenPodManager)); diff --git a/src/test/mocks/EigenPodManagerMock.sol b/src/test/mocks/EigenPodManagerMock.sol index b6e99b59de..3f6d69f7a1 100644 --- a/src/test/mocks/EigenPodManagerMock.sol +++ b/src/test/mocks/EigenPodManagerMock.sol @@ -11,7 +11,7 @@ contract EigenPodManagerMock is IEigenPodManager, Test { function slasher() external view returns(ISlasher) {} - function createPod() external pure {} + function createPod() external returns(address) {} function stake(bytes calldata /*pubkey*/, bytes calldata /*signature*/, bytes32 /*depositDataRoot*/) external payable {} From c13eb6e341cccca071a3cd3747dfeac41ac94da6 Mon Sep 17 00:00:00 2001 From: Yash Patil <40046473+ypatil12@users.noreply.github.com> Date: Fri, 27 Oct 2023 10:58:27 -0400 Subject: [PATCH 24/24] EigenPodManager Unit Test Refactor (#290) * skeleton refactor * finished EPM unit tests * fix tree diagram typos * fix _checkPodDeployed function * split max pod revert tests * fuzz removeShares tests * update initializePodWithShares to not use stdStorage * add error messages on asserts * fix tree file name * add share adjustment tests * create temp file for pod and pod manager unit tests * remove unused constant in EPM unit test --- src/contracts/pods/EigenPodManager.sol | 6 +- src/test/EigenPod.t.sol | 137 ++++ src/test/events/IEigenPodManagerEvents.sol | 13 + src/test/harnesses/EigenPodManagerWrapper.sol | 20 + src/test/tree/EigenPodManagerUnit.tree | 93 +++ src/test/unit/EigenPod-PodManagerUnit.t.sol | 53 ++ src/test/unit/EigenPodManagerUnit.t.sol | 750 ++++++++++++------ src/test/utils/EigenLayerUnitTestSetup.sol | 22 + 8 files changed, 838 insertions(+), 256 deletions(-) create mode 100644 src/test/events/IEigenPodManagerEvents.sol create mode 100644 src/test/harnesses/EigenPodManagerWrapper.sol create mode 100644 src/test/tree/EigenPodManagerUnit.tree create mode 100644 src/test/unit/EigenPod-PodManagerUnit.t.sol create mode 100644 src/test/utils/EigenLayerUnitTestSetup.sol diff --git a/src/contracts/pods/EigenPodManager.sol b/src/contracts/pods/EigenPodManager.sol index d53d5af25e..f98faa7086 100644 --- a/src/contracts/pods/EigenPodManager.sol +++ b/src/contracts/pods/EigenPodManager.sol @@ -142,13 +142,13 @@ contract EigenPodManager is * result in the `podOwner` incurring a "share deficit". This behavior prevents a Staker from queuing a withdrawal which improperly removes excessive * shares from the operator to whom the staker is delegated. * @dev Reverts if `shares` is not a whole Gwei amount + * @dev The delegation manager validates that the podOwner is not address(0) */ function removeShares( address podOwner, uint256 shares ) external onlyDelegationManager { - require(podOwner != address(0), "EigenPodManager.removeShares: podOwner cannot be zero address"); - require(int256(shares) >= 0, "EigenPodManager.removeShares: shares amount is negative"); + require(int256(shares) >= 0, "EigenPodManager.removeShares: shares cannot be negative"); require(shares % GWEI_TO_WEI == 0, "EigenPodManager.removeShares: shares must be a whole Gwei amount"); int256 updatedPodOwnerShares = podOwnerShares[podOwner] - int256(shares); require(updatedPodOwnerShares >= 0, "EigenPodManager.removeShares: cannot result in pod owner having negative shares"); @@ -180,6 +180,8 @@ contract EigenPodManager is * @notice Used by the DelegationManager to complete a withdrawal, sending tokens to some destination address * @dev Prioritizes decreasing the podOwner's share deficit, if they have one * @dev Reverts if `shares` is not a whole Gwei amount + * @dev This function assumes that `removeShares` has already been called by the delegationManager, hence why + * we do not need to update the podOwnerShares if `currentPodOwnerShares` is positive */ function withdrawSharesAsTokens( address podOwner, diff --git a/src/test/EigenPod.t.sol b/src/test/EigenPod.t.sol index ec1ccb6546..00c49fb6bc 100644 --- a/src/test/EigenPod.t.sol +++ b/src/test/EigenPod.t.sol @@ -2126,3 +2126,140 @@ contract Relayer is Test { BeaconChainProofs.verifyWithdrawal(beaconStateRoot, withdrawalFields, proofs); } } + + +//TODO: Integration Tests from old EPM unit tests: + // queues a withdrawal of "beacon chain ETH shares" from this address to itself + // fuzzed input amountGwei is sized-down, since it must be in GWEI and gets sized-up to be WEI +// TODO: reimplement similar test + // function testQueueWithdrawalBeaconChainETHToSelf(uint128 amountGwei) + // public returns (IEigenPodManager.BeaconChainQueuedWithdrawal memory, bytes32 /*withdrawalRoot*/) + // { + // // scale fuzzed amount up to be a whole amount of GWEI + // uint256 amount = uint256(amountGwei) * 1e9; + // address staker = address(this); + // address withdrawer = staker; + + // testRestakeBeaconChainETHSuccessfully(staker, amount); + + // (IEigenPodManager.BeaconChainQueuedWithdrawal memory queuedWithdrawal, bytes32 withdrawalRoot) = + // _createQueuedWithdrawal(staker, amount, withdrawer); + + // return (queuedWithdrawal, withdrawalRoot); + // } +// TODO: reimplement similar test + // function testQueueWithdrawalBeaconChainETHToDifferentAddress(address withdrawer, uint128 amountGwei) + // public + // filterFuzzedAddressInputs(withdrawer) + // returns (IEigenPodManager.BeaconChainQueuedWithdrawal memory, bytes32 /*withdrawalRoot*/) + // { + // // scale fuzzed amount up to be a whole amount of GWEI + // uint256 amount = uint256(amountGwei) * 1e9; + // address staker = address(this); + + // testRestakeBeaconChainETHSuccessfully(staker, amount); + + // (IEigenPodManager.BeaconChainQueuedWithdrawal memory queuedWithdrawal, bytes32 withdrawalRoot) = + // _createQueuedWithdrawal(staker, amount, withdrawer); + + // return (queuedWithdrawal, withdrawalRoot); + // } +// TODO: reimplement similar test + + // function testQueueWithdrawalBeaconChainETHFailsNonWholeAmountGwei(uint256 nonWholeAmount) external { + // // this also filters out the zero case, which will revert separately + // cheats.assume(nonWholeAmount % GWEI_TO_WEI != 0); + // cheats.expectRevert(bytes("EigenPodManager._queueWithdrawal: cannot queue a withdrawal of Beacon Chain ETH for an non-whole amount of gwei")); + // eigenPodManager.queueWithdrawal(nonWholeAmount, address(this)); + // } + + // function testQueueWithdrawalBeaconChainETHFailsZeroAmount() external { + // cheats.expectRevert(bytes("EigenPodManager._queueWithdrawal: amount must be greater than zero")); + // eigenPodManager.queueWithdrawal(0, address(this)); + // } + +// TODO: reimplement similar test + // function testCompleteQueuedWithdrawal() external { + // address staker = address(this); + // uint256 withdrawalAmount = 1e18; + + // // withdrawalAmount is converted to GWEI here + // (IEigenPodManager.BeaconChainQueuedWithdrawal memory queuedWithdrawal, bytes32 withdrawalRoot) = + // testQueueWithdrawalBeaconChainETHToSelf(uint128(withdrawalAmount / 1e9)); + + // IEigenPod eigenPod = eigenPodManager.getPod(staker); + // uint256 eigenPodBalanceBefore = address(eigenPod).balance; + + // uint256 middlewareTimesIndex = 0; + + // // actually complete the withdrawal + // cheats.startPrank(staker); + // cheats.expectEmit(true, true, true, true, address(eigenPodManager)); + // emit BeaconChainETHWithdrawalCompleted( + // queuedWithdrawal.podOwner, + // queuedWithdrawal.shares, + // queuedWithdrawal.nonce, + // queuedWithdrawal.delegatedAddress, + // queuedWithdrawal.withdrawer, + // withdrawalRoot + // ); + // eigenPodManager.completeQueuedWithdrawal(queuedWithdrawal, middlewareTimesIndex); + // cheats.stopPrank(); + + // // TODO: make EigenPodMock do something so we can verify that it gets called appropriately? + // uint256 eigenPodBalanceAfter = address(eigenPod).balance; + + // // verify that the withdrawal root does bit exist after queuing + // require(!eigenPodManager.withdrawalRootPending(withdrawalRoot), "withdrawalRootPendingBefore is true!"); + // } + +// TODO: reimplement similar test + // // creates a queued withdrawal of "beacon chain ETH shares", from `staker`, of `amountWei`, "to" the `withdrawer` + // function _createQueuedWithdrawal(address staker, uint256 amountWei, address withdrawer) + // internal + // returns (IEigenPodManager.BeaconChainQueuedWithdrawal memory queuedWithdrawal, bytes32 withdrawalRoot) + // { + // // create the struct, for reference / to return + // queuedWithdrawal = IEigenPodManager.BeaconChainQueuedWithdrawal({ + // shares: amountWei, + // podOwner: staker, + // nonce: eigenPodManager.cumulativeWithdrawalsQueued(staker), + // startBlock: uint32(block.number), + // delegatedTo: delegationManagerMock.delegatedTo(staker), + // withdrawer: withdrawer + // }); + + // // verify that the withdrawal root does not exist before queuing + // require(!eigenPodManager.withdrawalRootPending(withdrawalRoot), "withdrawalRootPendingBefore is true!"); + + // // get staker nonce and shares before queuing + // uint256 nonceBefore = eigenPodManager.cumulativeWithdrawalsQueued(staker); + // int256 sharesBefore = eigenPodManager.podOwnerShares(staker); + + // // actually create the queued withdrawal, and check for event emission + // cheats.startPrank(staker); + + // cheats.expectEmit(true, true, true, true, address(eigenPodManager)); + // emit BeaconChainETHWithdrawalQueued( + // queuedWithdrawal.podOwner, + // queuedWithdrawal.shares, + // queuedWithdrawal.nonce, + // queuedWithdrawal.delegatedAddress, + // queuedWithdrawal.withdrawer, + // eigenPodManager.calculateWithdrawalRoot(queuedWithdrawal) + // ); + // withdrawalRoot = eigenPodManager.queueWithdrawal(amountWei, withdrawer); + // cheats.stopPrank(); + + // // verify that the withdrawal root does exist after queuing + // require(eigenPodManager.withdrawalRootPending(withdrawalRoot), "withdrawalRootPendingBefore is false!"); + + // // verify that staker nonce incremented correctly and shares decremented correctly + // uint256 nonceAfter = eigenPodManager.cumulativeWithdrawalsQueued(staker); + // int256 sharesAfter = eigenPodManager.podOwnerShares(staker); + // require(nonceAfter == nonceBefore + 1, "nonce did not increment correctly on queuing withdrawal"); + // require(sharesAfter + amountWei == sharesBefore, "shares did not decrement correctly on queuing withdrawal"); + + // return (queuedWithdrawal, withdrawalRoot); + // } + diff --git a/src/test/events/IEigenPodManagerEvents.sol b/src/test/events/IEigenPodManagerEvents.sol new file mode 100644 index 0000000000..9b2eb73868 --- /dev/null +++ b/src/test/events/IEigenPodManagerEvents.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.12; + +interface IEigenPodManagerEvents { + /// @notice Emitted to notify the update of the beaconChainOracle address + event BeaconOracleUpdated(address indexed newOracleAddress); + + /// @notice Emitted to notify the deployment of an EigenPod + event PodDeployed(address indexed eigenPod, address indexed podOwner); + + /// @notice Emitted when `maxPods` value is updated from `previousValue` to `newValue` + event MaxPodsUpdated(uint256 previousValue, uint256 newValue); +} \ No newline at end of file diff --git a/src/test/harnesses/EigenPodManagerWrapper.sol b/src/test/harnesses/EigenPodManagerWrapper.sol new file mode 100644 index 0000000000..4c1f961215 --- /dev/null +++ b/src/test/harnesses/EigenPodManagerWrapper.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.12; + +import "../../contracts/pods/EigenPodManager.sol"; + +///@notice This contract exposed the internal `_calculateChangeInDelegatableShares` function for testing +contract EigenPodManagerWrapper is EigenPodManager { + + constructor( + IETHPOSDeposit _ethPOS, + IBeacon _eigenPodBeacon, + IStrategyManager _strategyManager, + ISlasher _slasher, + IDelegationManager _delegationManager + ) EigenPodManager(_ethPOS, _eigenPodBeacon, _strategyManager, _slasher, _delegationManager) {} + + function calculateChangeInDelegatableShares(int256 sharesBefore, int256 sharesAfter) external pure returns (int256) { + return _calculateChangeInDelegatableShares(sharesBefore, sharesAfter); + } +} \ No newline at end of file diff --git a/src/test/tree/EigenPodManagerUnit.tree b/src/test/tree/EigenPodManagerUnit.tree new file mode 100644 index 0000000000..f0803b3271 --- /dev/null +++ b/src/test/tree/EigenPodManagerUnit.tree @@ -0,0 +1,93 @@ +├── EigenPodManager Tree (*** denotes that integrationt tests are needed to validate path) +├── when contract is deployed and initialized +│ └── it should properly set storage +├── when initialize called again +│ └── it should revert +├── when createPod called +│ ├── given the user has already created a pod +│ │ └── it should revert +│ ├── given that the max number of pods has been deployed +│ │ └── it should revert +│ └── given the user has not created a pod +│ └── it should deploy a pod +├── when stake is called +│ ├── given the user has not created a pod +│ │ └── it should deploy a pod +│ └── given the user has already created a pod +│ └── it should call stake on the eigenPod +├── when setMaxPods is called +│ ├── given the user is not the pauser +│ │ └── it should revert +│ └── given the user is the pauser +│ └── it should set the max pods +├── when updateBeaconChainOracle is called +│ ├── given the user is not the owner +│ │ └── it should revert +│ └── given the user is the owner +│ └── it should set the beacon chain oracle +├── when addShares is called +│ ├── given that the caller is not the delegationManager +│ │ └── it should revert +│ ├── given that the podOwner address is 0 +│ │ └── it should revert +│ ├── given that the shares amount is negative +│ │ └── it should revert +│ ├── given that the shares is not a whole gwei amount +│ │ └── it should revert +│ └── given that all of the above conditions are satisfied +│ └── it should update the podOwnerShares +├── when removeShares is called +│ ├── given that the caller is not the delegationManager +│ │ └── it should revert +│ ├── given that the shares amount is negative +│ │ └── it should revert +│ ├── given that the shares is not a whole gwei amount +│ │ └── it should revert +│ ├── given that removing shares results in the pod owner having negative shares +│ │ └── it should revert +│ └── given that all of the above conditions are satisfied +│ └── it should update the podOwnerShares +├── when withdrawSharesAsTokens is called +│ ├── given that the podOwner is address 0 +│ │ └── it should revert +│ ├── given that the destination is address 0 +│ │ └── it should revert +│ ├── given that the shares amount is negative +│ │ └── it should revert +│ ├── given that the shares is not a whole gwei amount +│ │ └── it should revert +│ ├── given that the current podOwner shares are negative +│ │ ├── given that the shares to withdraw are larger in magnitude than the shares of the podOwner +│ │ │ └── it should set the podOwnerShares to 0 and decrement shares to withdraw by the share deficit +│ │ └── given that the shares to withdraw are smaller in magnitude than shares of the podOwner +│ │ └── it should increment the podOwner shares by the shares to withdraw +│ └── given that the pod owner shares are positive +│ └── it should withdraw restaked ETH from the eigenPod +├── when shares are adjusted +│ ├── given that sharesBefore is negative or 0 +│ │ ├── given that sharesAfter is negative or zero +│ │ │ └── the change in delegateable shares should be 0 +│ │ └── given that sharesAfter is positive +│ │ └── the change in delegateable shares should be positive +│ └── given that sharesBefore is positive +│ ├── given that sharesAfter is negative or zero +│ │ └── the change in delegateable shares is negative sharesBefore +│ └── given that sharesAfter is positive +│ └── the change in delegateable shares is the difference between sharesAfter and sharesBefore +└── when recordBeaconChainETHBalanceUpdate is called + ├── given that the podOwner's eigenPod is not the caller + │ └── it should revert + ├── given that the podOwner is a zero address + │ └── it should revert + ├── given that sharesDelta is not a whole gwei amount + │ ├── it should revert + │ └── given that the shares delta is valid + │ └── it should update the podOwnerShares + ├── given that the change in delegateable shares is positive *** + │ └── it should increase delegated shares on the delegationManager + ├── given that the change in delegateable shares is negative *** + │ └── it should decrease delegated shares on the delegationManager + ├── given that the change in delegateable shares is 0 *** + │ └── it should only update the podOwnerShares + └── given that the function is reentered *** + └── it should revert \ No newline at end of file diff --git a/src/test/unit/EigenPod-PodManagerUnit.t.sol b/src/test/unit/EigenPod-PodManagerUnit.t.sol new file mode 100644 index 0000000000..5a0cea727f --- /dev/null +++ b/src/test/unit/EigenPod-PodManagerUnit.t.sol @@ -0,0 +1,53 @@ +///@notice Placeholder for future unit tests that combine interaction between the EigenPod & EigenPodManager + +// TODO: salvage / re-implement a check for reentrancy guard on functions, as possible + // function testRecordBeaconChainETHBalanceUpdateFailsWhenReentering() public { + // uint256 amount = 1e18; + // uint256 amount2 = 2e18; + // address staker = address(this); + // uint256 beaconChainETHStrategyIndex = 0; + + // _beaconChainReentrancyTestsSetup(); + + // testRestakeBeaconChainETHSuccessfully(staker, amount); + + // address targetToUse = address(strategyManager); + // uint256 msgValueToUse = 0; + + // int256 amountDelta = int256(amount2 - amount); + // // reference: function recordBeaconChainETHBalanceUpdate(address podOwner, uint256 beaconChainETHStrategyIndex, uint256 sharesDelta, bool isNegative) + // bytes memory calldataToUse = abi.encodeWithSelector(StrategyManager.recordBeaconChainETHBalanceUpdate.selector, staker, beaconChainETHStrategyIndex, amountDelta); + // reenterer.prepare(targetToUse, msgValueToUse, calldataToUse, bytes("ReentrancyGuard: reentrant call")); + + // cheats.startPrank(address(reenterer)); + // eigenPodManager.recordBeaconChainETHBalanceUpdate(staker, amountDelta); + // cheats.stopPrank(); + // } + + // function _beaconChainReentrancyTestsSetup() internal { + // // prepare EigenPodManager with StrategyManager and Delegation replaced with a Reenterer contract + // reenterer = new Reenterer(); + // eigenPodManagerImplementation = new EigenPodManager( + // ethPOSMock, + // eigenPodBeacon, + // IStrategyManager(address(reenterer)), + // slasherMock, + // IDelegationManager(address(reenterer)) + // ); + // eigenPodManager = EigenPodManager( + // address( + // new TransparentUpgradeableProxy( + // address(eigenPodManagerImplementation), + // address(proxyAdmin), + // abi.encodeWithSelector( + // EigenPodManager.initialize.selector, + // type(uint256).max /*maxPods*/, + // IBeaconChainOracle(address(0)) /*beaconChainOracle*/, + // initialOwner, + // pauserRegistry, + // 0 /*initialPausedStatus*/ + // ) + // ) + // ) + // ); + // } diff --git a/src/test/unit/EigenPodManagerUnit.t.sol b/src/test/unit/EigenPodManagerUnit.t.sol index be69df4843..d0d91780ad 100644 --- a/src/test/unit/EigenPodManagerUnit.t.sol +++ b/src/test/unit/EigenPodManagerUnit.t.sol @@ -1,98 +1,66 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity =0.8.12; -import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; -import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; - import "forge-std/Test.sol"; import "../../contracts/pods/EigenPodManager.sol"; import "../../contracts/pods/EigenPodPausingConstants.sol"; import "../../contracts/permissions/PauserRegistry.sol"; + +import "../events/IEigenPodManagerEvents.sol"; +import "../utils/EigenLayerUnitTestSetup.sol"; +import "../harnesses/EigenPodManagerWrapper.sol"; import "../mocks/DelegationManagerMock.sol"; import "../mocks/SlasherMock.sol"; import "../mocks/StrategyManagerMock.sol"; import "../mocks/EigenPodMock.sol"; import "../mocks/ETHDepositMock.sol"; -import "../mocks/Reenterer.sol"; -import "../mocks/Reverter.sol"; - -contract EigenPodManagerUnitTests is Test, EigenPodPausingConstants { - Vm cheats = Vm(HEVM_ADDRESS); +contract EigenPodManagerUnitTests is EigenLayerUnitTestSetup { + // Contracts Under Test: EigenPodManager + EigenPodManager public eigenPodManagerImplementation; + EigenPodManager public eigenPodManager; - uint256 public REQUIRED_BALANCE_WEI = 32 ether; + using stdStorage for StdStorage; + // Proxy Admin & Pauser Registry ProxyAdmin public proxyAdmin; PauserRegistry public pauserRegistry; - EigenPodManager public eigenPodManagerImplementation; - EigenPodManager public eigenPodManager; - + // Mocks StrategyManagerMock public strategyManagerMock; DelegationManagerMock public delegationManagerMock; SlasherMock public slasherMock; IETHPOSDeposit public ethPOSMock; - IEigenPod public eigenPodImplementation; - IBeacon public eigenPodBeacon; - + IEigenPod public eigenPodMockImplementation; + IBeacon public eigenPodBeacon; // Proxy for eigenPodMockImplementation IStrategy public beaconChainETHStrategy; - - Reenterer public reenterer; - - uint256 GWEI_TO_WEI = 1e9; - - address public pauser = address(555); - address public unpauser = address(999); - - address initialOwner = address(this); - - mapping(address => bool) public addressIsExcludedFromFuzzedInputs; - - modifier filterFuzzedAddressInputs(address fuzzedAddress) { - cheats.assume(!addressIsExcludedFromFuzzedInputs[fuzzedAddress]); - _; - } - - /// @notice Emitted to notify the update of the beaconChainOracle address - event BeaconOracleUpdated(address indexed newOracleAddress); - - /// @notice Emitted to notify the deployment of an EigenPod - event PodDeployed(address indexed eigenPod, address indexed podOwner); - - /// @notice Emitted to notify a deposit of beacon chain ETH recorded in the strategy manager - event BeaconChainETHDeposited(address indexed podOwner, uint256 amount); - - /// @notice Emitted when `maxPods` value is updated from `previousValue` to `newValue` - event MaxPodsUpdated(uint256 previousValue, uint256 newValue); - - /// @notice Emitted when a withdrawal of beacon chain ETH is queued - event BeaconChainETHWithdrawalQueued(address indexed podOwner, uint256 shares, uint96 nonce, address delegatedAddress, address withdrawer, bytes32 withdrawalRoot); - /// @notice Emitted when a withdrawal of beacon chain ETH is completed - event BeaconChainETHWithdrawalCompleted(address indexed podOwner, uint256 shares, uint96 nonce, address delegatedAddress, address withdrawer, bytes32 withdrawalRoot); - - // @notice Emitted when `podOwner` enters the "undelegation limbo" mode - event UndelegationLimboEntered(address indexed podOwner); - - // @notice Emitted when `podOwner` exits the "undelegation limbo" mode - event UndelegationLimboExited(address indexed podOwner); + // Constants + uint256 public constant GWEI_TO_WEI = 1e9; + address public defaultStaker = address(this); + IEigenPod public defaultPod; + address public initialOwner = address(this); function setUp() virtual public { + // Deploy ProxyAdmin proxyAdmin = new ProxyAdmin(); + // Initialize PauserRegistry address[] memory pausers = new address[](1); pausers[0] = pauser; pauserRegistry = new PauserRegistry(pausers, unpauser); + // Deploy Mocks slasherMock = new SlasherMock(); delegationManagerMock = new DelegationManagerMock(); strategyManagerMock = new StrategyManagerMock(); ethPOSMock = new ETHPOSDepositMock(); - eigenPodImplementation = new EigenPodMock(); - eigenPodBeacon = new UpgradeableBeacon(address(eigenPodImplementation)); + eigenPodMockImplementation = new EigenPodMock(); + eigenPodBeacon = new UpgradeableBeacon(address(eigenPodMockImplementation)); + // Deploy EPM Implementation & Proxy eigenPodManagerImplementation = new EigenPodManager( ethPOSMock, eigenPodBeacon, @@ -117,220 +85,494 @@ contract EigenPodManagerUnitTests is Test, EigenPodPausingConstants { ) ); + // Set beaconChainETHStrategy beaconChainETHStrategy = eigenPodManager.beaconChainETHStrategy(); - // excude the zero address, the proxyAdmin and the eigenPodManager itself from fuzzed inputs + // Set defaultPod + defaultPod = eigenPodManager.getPod(defaultStaker); + + // Exclude the zero address, the proxyAdmin and the eigenPodManager itself from fuzzed inputs addressIsExcludedFromFuzzedInputs[address(0)] = true; addressIsExcludedFromFuzzedInputs[address(proxyAdmin)] = true; addressIsExcludedFromFuzzedInputs[address(eigenPodManager)] = true; } - function testRecordBeaconChainETHBalanceUpdateFailsWhenNotCalledByEigenPod(address improperCaller) public filterFuzzedAddressInputs(improperCaller) { - address staker = address(this); - IEigenPod eigenPod = _deployEigenPodForStaker(staker); - cheats.assume(improperCaller != address(eigenPod)); - - cheats.expectRevert(bytes("EigenPodManager.onlyEigenPod: not a pod")); - cheats.startPrank(address(improperCaller)); - eigenPodManager.recordBeaconChainETHBalanceUpdate(staker, int256(0)); - cheats.stopPrank(); - } - -// TODO: salvage / re-implement a check for reentrancy guard on functions, as possible - // function testRecordBeaconChainETHBalanceUpdateFailsWhenReentering() public { - // uint256 amount = 1e18; - // uint256 amount2 = 2e18; - // address staker = address(this); - // uint256 beaconChainETHStrategyIndex = 0; - - // _beaconChainReentrancyTestsSetup(); - - // testRestakeBeaconChainETHSuccessfully(staker, amount); - - // address targetToUse = address(strategyManager); - // uint256 msgValueToUse = 0; - - // int256 amountDelta = int256(amount2 - amount); - // // reference: function recordBeaconChainETHBalanceUpdate(address podOwner, uint256 beaconChainETHStrategyIndex, uint256 sharesDelta, bool isNegative) - // bytes memory calldataToUse = abi.encodeWithSelector(StrategyManager.recordBeaconChainETHBalanceUpdate.selector, staker, beaconChainETHStrategyIndex, amountDelta); - // reenterer.prepare(targetToUse, msgValueToUse, calldataToUse, bytes("ReentrancyGuard: reentrant call")); - - // cheats.startPrank(address(reenterer)); - // eigenPodManager.recordBeaconChainETHBalanceUpdate(staker, amountDelta); - // cheats.stopPrank(); - // } - - // queues a withdrawal of "beacon chain ETH shares" from this address to itself - // fuzzed input amountGwei is sized-down, since it must be in GWEI and gets sized-up to be WEI -// TODO: reimplement similar test - // function testQueueWithdrawalBeaconChainETHToSelf(uint128 amountGwei) - // public returns (IEigenPodManager.BeaconChainQueuedWithdrawal memory, bytes32 /*withdrawalRoot*/) - // { - // // scale fuzzed amount up to be a whole amount of GWEI - // uint256 amount = uint256(amountGwei) * 1e9; - // address staker = address(this); - // address withdrawer = staker; - - // testRestakeBeaconChainETHSuccessfully(staker, amount); - - // (IEigenPodManager.BeaconChainQueuedWithdrawal memory queuedWithdrawal, bytes32 withdrawalRoot) = - // _createQueuedWithdrawal(staker, amount, withdrawer); - - // return (queuedWithdrawal, withdrawalRoot); - // } -// TODO: reimplement similar test - // function testQueueWithdrawalBeaconChainETHToDifferentAddress(address withdrawer, uint128 amountGwei) - // public - // filterFuzzedAddressInputs(withdrawer) - // returns (IEigenPodManager.BeaconChainQueuedWithdrawal memory, bytes32 /*withdrawalRoot*/) - // { - // // scale fuzzed amount up to be a whole amount of GWEI - // uint256 amount = uint256(amountGwei) * 1e9; - // address staker = address(this); - - // testRestakeBeaconChainETHSuccessfully(staker, amount); - - // (IEigenPodManager.BeaconChainQueuedWithdrawal memory queuedWithdrawal, bytes32 withdrawalRoot) = - // _createQueuedWithdrawal(staker, amount, withdrawer); - - // return (queuedWithdrawal, withdrawalRoot); - // } -// TODO: reimplement similar test - - // function testQueueWithdrawalBeaconChainETHFailsNonWholeAmountGwei(uint256 nonWholeAmount) external { - // // this also filters out the zero case, which will revert separately - // cheats.assume(nonWholeAmount % GWEI_TO_WEI != 0); - // cheats.expectRevert(bytes("EigenPodManager._queueWithdrawal: cannot queue a withdrawal of Beacon Chain ETH for an non-whole amount of gwei")); - // eigenPodManager.queueWithdrawal(nonWholeAmount, address(this)); - // } - - // function testQueueWithdrawalBeaconChainETHFailsZeroAmount() external { - // cheats.expectRevert(bytes("EigenPodManager._queueWithdrawal: amount must be greater than zero")); - // eigenPodManager.queueWithdrawal(0, address(this)); - // } - -// TODO: reimplement similar test - // function testCompleteQueuedWithdrawal() external { - // address staker = address(this); - // uint256 withdrawalAmount = 1e18; - - // // withdrawalAmount is converted to GWEI here - // (IEigenPodManager.BeaconChainQueuedWithdrawal memory queuedWithdrawal, bytes32 withdrawalRoot) = - // testQueueWithdrawalBeaconChainETHToSelf(uint128(withdrawalAmount / 1e9)); - - // IEigenPod eigenPod = eigenPodManager.getPod(staker); - // uint256 eigenPodBalanceBefore = address(eigenPod).balance; - - // uint256 middlewareTimesIndex = 0; - - // // actually complete the withdrawal - // cheats.startPrank(staker); - // cheats.expectEmit(true, true, true, true, address(eigenPodManager)); - // emit BeaconChainETHWithdrawalCompleted( - // queuedWithdrawal.podOwner, - // queuedWithdrawal.shares, - // queuedWithdrawal.nonce, - // queuedWithdrawal.delegatedAddress, - // queuedWithdrawal.withdrawer, - // withdrawalRoot - // ); - // eigenPodManager.completeQueuedWithdrawal(queuedWithdrawal, middlewareTimesIndex); - // cheats.stopPrank(); - - // // TODO: make EigenPodMock do something so we can verify that it gets called appropriately? - // uint256 eigenPodBalanceAfter = address(eigenPod).balance; - - // // verify that the withdrawal root does bit exist after queuing - // require(!eigenPodManager.withdrawalRootPending(withdrawalRoot), "withdrawalRootPendingBefore is true!"); - // } - - // INTERNAL / HELPER FUNCTIONS - // deploy an EigenPod for the staker and check the emitted event - function _deployEigenPodForStaker(address staker) internal returns (IEigenPod deployedPod) { + /******************************************************************************* + Helper Functions/Modifiers + *******************************************************************************/ + + function _initializePodWithShares(address podOwner, int256 shares) internal { + // Deploy pod + IEigenPod deployedPod = _deployAndReturnEigenPodForStaker(podOwner); + + // Set shares + cheats.prank(address(deployedPod)); + eigenPodManager.recordBeaconChainETHBalanceUpdate(podOwner, shares); + } + + + modifier deployPodForStaker(address staker) { + _deployAndReturnEigenPodForStaker(staker); + _; + } + + function _deployAndReturnEigenPodForStaker(address staker) internal returns (IEigenPod deployedPod) { deployedPod = eigenPodManager.getPod(staker); - cheats.startPrank(staker); - cheats.expectEmit(true, true, true, true, address(eigenPodManager)); - emit PodDeployed(address(deployedPod), staker); + cheats.prank(staker); eigenPodManager.createPod(); - cheats.stopPrank(); return deployedPod; } -// TODO: reimplement similar test - // // creates a queued withdrawal of "beacon chain ETH shares", from `staker`, of `amountWei`, "to" the `withdrawer` - // function _createQueuedWithdrawal(address staker, uint256 amountWei, address withdrawer) - // internal - // returns (IEigenPodManager.BeaconChainQueuedWithdrawal memory queuedWithdrawal, bytes32 withdrawalRoot) - // { - // // create the struct, for reference / to return - // queuedWithdrawal = IEigenPodManager.BeaconChainQueuedWithdrawal({ - // shares: amountWei, - // podOwner: staker, - // nonce: eigenPodManager.cumulativeWithdrawalsQueued(staker), - // startBlock: uint32(block.number), - // delegatedTo: delegationManagerMock.delegatedTo(staker), - // withdrawer: withdrawer - // }); - - // // verify that the withdrawal root does not exist before queuing - // require(!eigenPodManager.withdrawalRootPending(withdrawalRoot), "withdrawalRootPendingBefore is true!"); - - // // get staker nonce and shares before queuing - // uint256 nonceBefore = eigenPodManager.cumulativeWithdrawalsQueued(staker); - // int256 sharesBefore = eigenPodManager.podOwnerShares(staker); - - // // actually create the queued withdrawal, and check for event emission - // cheats.startPrank(staker); + function _checkPodDeployed(address staker, address expectedPod, uint256 numPodsBefore) internal { + assertEq(address(eigenPodManager.ownerToPod(staker)), expectedPod, "Expected pod not deployed"); + assertEq(eigenPodManager.numPods(), numPodsBefore + 1, "Num pods not incremented"); + } +} + +contract EigenPodManagerUnitTests_Initialization_Setters is EigenPodManagerUnitTests, IEigenPodManagerEvents { + + /******************************************************************************* + Initialization Tests + *******************************************************************************/ + + function test_initialization() public { + // Check max pods, beacon chain, owner, and pauser + assertEq(eigenPodManager.maxPods(), type(uint256).max, "Initialization: max pods incorrect"); + assertEq(address(eigenPodManager.beaconChainOracle()), address(IBeaconChainOracle(address(0))), "Initialization: beacon chain oracle incorrect"); + assertEq(eigenPodManager.owner(), initialOwner, "Initialization: owner incorrect"); + assertEq(address(eigenPodManager.pauserRegistry()), address(pauserRegistry), "Initialization: pauser registry incorrect"); + assertEq(eigenPodManager.paused(), 0, "Initialization: paused value not 0"); + + // Check storage variables + assertEq(address(eigenPodManager.ethPOS()), address(ethPOSMock), "Initialization: ethPOS incorrect"); + assertEq(address(eigenPodManager.eigenPodBeacon()), address(eigenPodBeacon), "Initialization: eigenPodBeacon incorrect"); + assertEq(address(eigenPodManager.strategyManager()), address(strategyManagerMock), "Initialization: strategyManager incorrect"); + assertEq(address(eigenPodManager.slasher()), address(slasherMock), "Initialization: slasher incorrect"); + assertEq(address(eigenPodManager.delegationManager()), address(delegationManagerMock), "Initialization: delegationManager incorrect"); + } + + function test_initialize_revert_alreadyInitialized() public { + cheats.expectRevert("Initializable: contract is already initialized"); + eigenPodManager.initialize(type(uint256).max /*maxPods*/, + IBeaconChainOracle(address(0)) /*beaconChainOracle*/, + initialOwner, + pauserRegistry, + 0 /*initialPausedStatus*/); + } + + function testFuzz_setMaxPods_revert_notUnpauser(address notUnpauser) public { + cheats.assume(notUnpauser != unpauser); + cheats.prank(notUnpauser); + cheats.expectRevert("msg.sender is not permissioned as unpauser"); + eigenPodManager.setMaxPods(0); + } + + /******************************************************************************* + Setters + *******************************************************************************/ + + function test_setMaxPods() public { + // Set max pods + uint256 newMaxPods = 0; + cheats.expectEmit(true, true, true, true); + emit MaxPodsUpdated(eigenPodManager.maxPods(), newMaxPods); + cheats.prank(unpauser); + eigenPodManager.setMaxPods(newMaxPods); + + // Check storage update + assertEq(eigenPodManager.maxPods(), newMaxPods, "Max pods not updated"); + } + + function testFuzz_updateBeaconChainOracle_revert_notOwner(address notOwner) public { + cheats.assume(notOwner != initialOwner); + cheats.prank(notOwner); + cheats.expectRevert("Ownable: caller is not the owner"); + eigenPodManager.updateBeaconChainOracle(IBeaconChainOracle(address(1))); + } + + function test_updateBeaconChainOracle() public { + // Set new beacon chain oracle + IBeaconChainOracle newBeaconChainOracle = IBeaconChainOracle(address(1)); + cheats.prank(initialOwner); + cheats.expectEmit(true, true, true, true); + emit BeaconOracleUpdated(address(newBeaconChainOracle)); + eigenPodManager.updateBeaconChainOracle(newBeaconChainOracle); + + // Check storage update + assertEq(address(eigenPodManager.beaconChainOracle()), address(newBeaconChainOracle), "Beacon chain oracle not updated"); + } +} + +contract EigenPodManagerUnitTests_CreationTests is EigenPodManagerUnitTests, IEigenPodManagerEvents { + + function test_createPod() public { + // Get expected pod address and pods before + IEigenPod expectedPod = eigenPodManager.getPod(defaultStaker); + uint256 numPodsBefore = eigenPodManager.numPods(); + + // Create pod + cheats.expectEmit(true, true, true, true); + emit PodDeployed(address(expectedPod), defaultStaker); + eigenPodManager.createPod(); + + // Check pod deployed + _checkPodDeployed(defaultStaker, address(defaultPod), numPodsBefore); + } + + function test_createPod_revert_alreadyCreated() public deployPodForStaker(defaultStaker) { + cheats.expectRevert("EigenPodManager.createPod: Sender already has a pod"); + eigenPodManager.createPod(); + } + + function test_createPod_revert_maxPodsUint256() public { + // Write numPods into storage. Num pods is at slot 153 + bytes32 slot = bytes32(uint256(153)); + bytes32 value = bytes32(eigenPodManager.maxPods()); + cheats.store(address(eigenPodManager), slot, value); + + // Expect revert on pod creation + cheats.expectRevert(); // Arithmetic overflow/underflow + eigenPodManager.createPod(); + } + + function test_createPod_revert_maxPodsNontUint256() public { + // Set max pods to a small value - 0 + cheats.prank(unpauser); + eigenPodManager.setMaxPods(0); + + // Expect revert on pod creation + cheats.expectRevert("EigenPodManager._deployPod: pod limit reached"); + eigenPodManager.createPod(); + } +} + +contract EigenPodManagerUnitTests_StakeTests is EigenPodManagerUnitTests { + + function test_stake_podAlreadyDeployed() deployPodForStaker(defaultStaker) public { + // Declare dummy variables + bytes memory pubkey = bytes("pubkey"); + bytes memory sig = bytes("sig"); + bytes32 depositDataRoot = bytes32("depositDataRoot"); + + // Stake + eigenPodManager.stake{value: 32 ether}(pubkey, sig, depositDataRoot); + + // Expect pod has 32 ether + assertEq(address(defaultPod).balance, 32 ether, "ETH not staked in EigenPod"); + } + + function test_stake_newPodDeployed() public { + // Declare dummy variables + bytes memory pubkey = bytes("pubkey"); + bytes memory sig = bytes("sig"); + bytes32 depositDataRoot = bytes32("depositDataRoot"); + + // Stake + eigenPodManager.stake{value: 32 ether}(pubkey, sig, depositDataRoot); + + // Check pod deployed + _checkPodDeployed(defaultStaker, address(defaultPod), 0); // staker, defaultPod, numPodsBefore + + // Expect pod has 32 ether + assertEq(address(defaultPod).balance, 32 ether, "ETH not staked in EigenPod"); + } +} + +contract EigenPodManagerUnitTests_ShareUpdateTests is EigenPodManagerUnitTests { + + /******************************************************************************* + Add Shares Tests + *******************************************************************************/ + + function testFuzz_addShares_revert_notDelegationManager(address notDelegationManager) public { + cheats.assume(notDelegationManager != address(delegationManagerMock)); + cheats.prank(notDelegationManager); + cheats.expectRevert("EigenPodManager.onlyDelegationManager: not the DelegationManager"); + eigenPodManager.addShares(defaultStaker, 0); + } - // cheats.expectEmit(true, true, true, true, address(eigenPodManager)); - // emit BeaconChainETHWithdrawalQueued( - // queuedWithdrawal.podOwner, - // queuedWithdrawal.shares, - // queuedWithdrawal.nonce, - // queuedWithdrawal.delegatedAddress, - // queuedWithdrawal.withdrawer, - // eigenPodManager.calculateWithdrawalRoot(queuedWithdrawal) - // ); - // withdrawalRoot = eigenPodManager.queueWithdrawal(amountWei, withdrawer); - // cheats.stopPrank(); - - // // verify that the withdrawal root does exist after queuing - // require(eigenPodManager.withdrawalRootPending(withdrawalRoot), "withdrawalRootPendingBefore is false!"); - - // // verify that staker nonce incremented correctly and shares decremented correctly - // uint256 nonceAfter = eigenPodManager.cumulativeWithdrawalsQueued(staker); - // int256 sharesAfter = eigenPodManager.podOwnerShares(staker); - // require(nonceAfter == nonceBefore + 1, "nonce did not increment correctly on queuing withdrawal"); - // require(sharesAfter + amountWei == sharesBefore, "shares did not decrement correctly on queuing withdrawal"); - - // return (queuedWithdrawal, withdrawalRoot); - // } - - function _beaconChainReentrancyTestsSetup() internal { - // prepare EigenPodManager with StrategyManager and Delegation replaced with a Reenterer contract - reenterer = new Reenterer(); - eigenPodManagerImplementation = new EigenPodManager( + function test_addShares_revert_podOwnerZeroAddress() public { + cheats.prank(address(delegationManagerMock)); + cheats.expectRevert("EigenPodManager.addShares: podOwner cannot be zero address"); + eigenPodManager.addShares(address(0), 0); + } + + function testFuzz_addShares_revert_sharesNegative(int256 shares) public { + cheats.assume(shares < 0); + cheats.prank(address(delegationManagerMock)); + cheats.expectRevert("EigenPodManager.addShares: shares cannot be negative"); + eigenPodManager.addShares(defaultStaker, uint256(shares)); + } + + function testFuzz_addShares_revert_sharesNotWholeGwei(uint256 shares) public { + cheats.assume(int256(shares) >= 0); + cheats.assume(shares % GWEI_TO_WEI != 0); + cheats.prank(address(delegationManagerMock)); + cheats.expectRevert("EigenPodManager.addShares: shares must be a whole Gwei amount"); + eigenPodManager.addShares(defaultStaker, shares); + } + + function testFuzz_addShares(uint256 shares) public { + // Fuzz inputs + cheats.assume(defaultStaker != address(0)); + cheats.assume(shares % GWEI_TO_WEI == 0); + cheats.assume(int256(shares) >= 0); + + // Add shares + cheats.prank(address(delegationManagerMock)); + eigenPodManager.addShares(defaultStaker, shares); + + // Check storage update + assertEq(eigenPodManager.podOwnerShares(defaultStaker), int256(shares), "Incorrect number of shares added"); + } + + /******************************************************************************* + Remove Shares Tests + ******************************************************************************/ + + function testFuzz_removeShares_revert_notDelegationManager(address notDelegationManager) public { + cheats.assume(notDelegationManager != address(delegationManagerMock)); + cheats.prank(notDelegationManager); + cheats.expectRevert("EigenPodManager.onlyDelegationManager: not the DelegationManager"); + eigenPodManager.removeShares(defaultStaker, 0); + } + + function testFuzz_removeShares_revert_sharesNegative(int256 shares) public { + cheats.assume(shares < 0); + cheats.prank(address(delegationManagerMock)); + cheats.expectRevert("EigenPodManager.removeShares: shares cannot be negative"); + eigenPodManager.removeShares(defaultStaker, uint256(shares)); + } + + function testFuzz_removeShares_revert_sharesNotWholeGwei(uint256 shares) public { + cheats.assume(int256(shares) >= 0); + cheats.assume(shares % GWEI_TO_WEI != 0); + cheats.prank(address(delegationManagerMock)); + cheats.expectRevert("EigenPodManager.removeShares: shares must be a whole Gwei amount"); + eigenPodManager.removeShares(defaultStaker, shares); + } + + function testFuzz_removeShares_revert_tooManySharesRemoved(uint224 sharesToAdd, uint224 sharesToRemove) public { + // Constrain inputs + cheats.assume(sharesToRemove > sharesToAdd); + uint256 sharesAdded = sharesToAdd * GWEI_TO_WEI; + uint256 sharesRemoved = sharesToRemove * GWEI_TO_WEI; + + // Initialize pod with shares + _initializePodWithShares(defaultStaker, int256(sharesAdded)); + + // Remove shares + cheats.prank(address(delegationManagerMock)); + cheats.expectRevert("EigenPodManager.removeShares: cannot result in pod owner having negative shares"); + eigenPodManager.removeShares(defaultStaker, sharesRemoved); + } + + function testFuzz_removeShares(uint224 sharesToAdd, uint224 sharesToRemove) public { + // Constain inputs + cheats.assume(sharesToRemove <= sharesToAdd); + uint256 sharesAdded = sharesToAdd * GWEI_TO_WEI; + uint256 sharesRemoved = sharesToRemove * GWEI_TO_WEI; + + // Initialize pod with shares + _initializePodWithShares(defaultStaker, int256(sharesAdded)); + + // Remove shares + cheats.prank(address(delegationManagerMock)); + eigenPodManager.removeShares(defaultStaker, sharesRemoved); + + // Check storage + assertEq(eigenPodManager.podOwnerShares(defaultStaker), int256(sharesAdded - sharesRemoved), "Incorrect number of shares removed"); + } + + function testFuzz_removeShares_zeroShares(address podOwner, uint256 shares) public { + // Constrain inputs + cheats.assume(podOwner != address(0)); + cheats.assume(shares % GWEI_TO_WEI == 0); + + // Initialize pod with shares + _initializePodWithShares(podOwner, int256(shares)); + + // Remove shares + cheats.prank(address(delegationManagerMock)); + eigenPodManager.removeShares(podOwner, shares); + + // Check storage update + assertEq(eigenPodManager.podOwnerShares(podOwner), 0, "Shares not reset to zero"); + } + + /******************************************************************************* + WithdrawSharesAsTokens Tests + ******************************************************************************/ + + function test_withdrawSharesAsTokens_revert_podOwnerZeroAddress() public { + cheats.prank(address(delegationManagerMock)); + cheats.expectRevert("EigenPodManager.withdrawSharesAsTokens: podOwner cannot be zero address"); + eigenPodManager.withdrawSharesAsTokens(address(0), address(0), 0); + } + + function test_withdrawSharesAsTokens_revert_destinationZeroAddress() public { + cheats.prank(address(delegationManagerMock)); + cheats.expectRevert("EigenPodManager.withdrawSharesAsTokens: destination cannot be zero address"); + eigenPodManager.withdrawSharesAsTokens(defaultStaker, address(0), 0); + } + + function testFuzz_withdrawSharesAsTokens_revert_sharesNegative(int256 shares) public { + cheats.assume(shares < 0); + cheats.prank(address(delegationManagerMock)); + cheats.expectRevert("EigenPodManager.withdrawSharesAsTokens: shares cannot be negative"); + eigenPodManager.withdrawSharesAsTokens(defaultStaker, defaultStaker, uint256(shares)); + } + + function testFuzz_withdrawSharesAsTokens_revert_sharesNotWholeGwei(uint256 shares) public { + cheats.assume(int256(shares) >= 0); + cheats.assume(shares % GWEI_TO_WEI != 0); + + cheats.prank(address(delegationManagerMock)); + cheats.expectRevert("EigenPodManager.withdrawSharesAsTokens: shares must be a whole Gwei amount"); + eigenPodManager.withdrawSharesAsTokens(defaultStaker, defaultStaker, shares); + } + + /** + * @notice The `withdrawSharesAsTokens` is called in the `completeQueuedWithdrawal` function from the + * delegationManager. When a withdrawal is queued in the delegationManager, `removeShares is called` + */ + function test_withdrawSharesAsTokens_reduceEntireDeficit() public { + // Shares to initialize & withdraw + int256 sharesBeginning = -100e18; + uint256 sharesToWithdraw = 101e18; + + // Deploy Pod And initialize with negative shares + _initializePodWithShares(defaultStaker, sharesBeginning); + + // Withdraw shares + cheats.prank(address(delegationManagerMock)); + eigenPodManager.withdrawSharesAsTokens(defaultStaker, defaultStaker, sharesToWithdraw); + + // Check storage update + assertEq(eigenPodManager.podOwnerShares(defaultStaker), int256(0), "Shares not reduced to 0"); + } + + function test_withdrawSharesAsTokens_partialDefecitReduction() public { + // Shares to initialize & withdraw + int256 sharesBeginning = -100e18; + uint256 sharesToWithdraw = 50e18; + + // Deploy Pod And initialize with negative shares + _initializePodWithShares(defaultStaker, sharesBeginning); + + // Withdraw shares + cheats.prank(address(delegationManagerMock)); + eigenPodManager.withdrawSharesAsTokens(defaultStaker, defaultStaker, sharesToWithdraw); + + // Check storage update + int256 expectedShares = sharesBeginning + int256(sharesToWithdraw); + assertEq(eigenPodManager.podOwnerShares(defaultStaker), expectedShares, "Shares not reduced to expected amount"); + } + + function test_withdrawSharesAsTokens_withdrawPositive() public { + // Shares to initialize & withdraw + int256 sharesBeginning = 100e18; + uint256 sharesToWithdraw = 50e18; + + // Deploy Pod And initialize with negative shares + _initializePodWithShares(defaultStaker, sharesBeginning); + + // Withdraw shares + cheats.prank(address(delegationManagerMock)); + eigenPodManager.withdrawSharesAsTokens(defaultStaker, defaultStaker, sharesToWithdraw); + + // Check storage remains the same + assertEq(eigenPodManager.podOwnerShares(defaultStaker), sharesBeginning, "Shares should not be adjusted"); + } +} + +contract EigenPodManagerUnitTests_BeaconChainETHBalanceUpdateTests is EigenPodManagerUnitTests { + + function testFuzz_recordBalanceUpdate_revert_notPod(address invalidCaller) public deployPodForStaker(defaultStaker) { + cheats.assume(invalidCaller != defaultStaker); + cheats.prank(invalidCaller); + cheats.expectRevert("EigenPodManager.onlyEigenPod: not a pod"); + eigenPodManager.recordBeaconChainETHBalanceUpdate(defaultStaker, 0); + } + + function test_recordBalanceUpdate_revert_zeroAddress() public { + IEigenPod zeroAddressPod = _deployAndReturnEigenPodForStaker(address(0)); + cheats.prank(address(zeroAddressPod)); + cheats.expectRevert("EigenPodManager.recordBeaconChainETHBalanceUpdate: podOwner cannot be zero address"); + eigenPodManager.recordBeaconChainETHBalanceUpdate(address(0), 0); + } + + function testFuzz_recordBalanceUpdate_revert_nonWholeGweiAmount(int256 sharesDelta) public deployPodForStaker(defaultStaker) { + cheats.assume(sharesDelta % int256(GWEI_TO_WEI) != 0); + cheats.prank(address(defaultPod)); + cheats.expectRevert("EigenPodManager.recordBeaconChainETHBalanceUpdate: sharesDelta must be a whole Gwei amount"); + eigenPodManager.recordBeaconChainETHBalanceUpdate(defaultStaker, sharesDelta); + } + + function testFuzz_recordBalanceUpdateX(int224 sharesBefore, int224 sharesDelta) public { + // Constrain inputs + int256 scaledSharesBefore = sharesBefore * int256(GWEI_TO_WEI); + int256 scaledSharesDelta = sharesDelta * int256(GWEI_TO_WEI); + + // Initialize shares + _initializePodWithShares(defaultStaker, scaledSharesBefore); + + // Update balance + cheats.prank(address(defaultPod)); + eigenPodManager.recordBeaconChainETHBalanceUpdate(defaultStaker, scaledSharesDelta); + + // Check storage + assertEq(eigenPodManager.podOwnerShares(defaultStaker), scaledSharesBefore + scaledSharesDelta, "Shares not updated correctly"); + } +} + +contract EigenPodManagerUnitTests_ShareAdjustmentCalculationTests is EigenPodManagerUnitTests { + // Wrapper contract that exposes the internal `_calculateChangeInDelegatableShares` function + EigenPodManagerWrapper public eigenPodManagerWrapper; + + function setUp() virtual override public { + super.setUp(); + + // Upgrade eigenPodManager to wrapper + eigenPodManagerWrapper = new EigenPodManagerWrapper( ethPOSMock, eigenPodBeacon, - IStrategyManager(address(reenterer)), + strategyManagerMock, slasherMock, - IDelegationManager(address(reenterer)) - ); - eigenPodManager = EigenPodManager( - address( - new TransparentUpgradeableProxy( - address(eigenPodManagerImplementation), - address(proxyAdmin), - abi.encodeWithSelector( - EigenPodManager.initialize.selector, - type(uint256).max /*maxPods*/, - IBeaconChainOracle(address(0)) /*beaconChainOracle*/, - initialOwner, - pauserRegistry, - 0 /*initialPausedStatus*/ - ) - ) - ) + delegationManagerMock ); + proxyAdmin.upgrade(TransparentUpgradeableProxy(payable(address(eigenPodManager))), address(eigenPodManagerWrapper)); + } + + function testFuzz_shareAdjustment_negativeToNegative(int256 sharesBefore, int256 sharesAfter) public { + cheats.assume(sharesBefore <= 0); + cheats.assume(sharesAfter <= 0); + + int256 sharesDelta = eigenPodManagerWrapper.calculateChangeInDelegatableShares(sharesBefore, sharesAfter); + assertEq(sharesDelta, 0, "Shares delta must be 0"); + } + + function testFuzz_shareAdjustment_negativeToPositive(int256 sharesBefore, int256 sharesAfter) public { + cheats.assume(sharesBefore <= 0); + cheats.assume(sharesAfter > 0); + + int256 sharesDelta = eigenPodManagerWrapper.calculateChangeInDelegatableShares(sharesBefore, sharesAfter); + assertEq(sharesDelta, sharesAfter, "Shares delta must be equal to sharesAfter"); + } + + function testFuzz_shareAdjustment_positiveToNegative(int256 sharesBefore, int256 sharesAfter) public { + cheats.assume(sharesBefore > 0); + cheats.assume(sharesAfter <= 0); + + int256 sharesDelta = eigenPodManagerWrapper.calculateChangeInDelegatableShares(sharesBefore, sharesAfter); + assertEq(sharesDelta, -sharesBefore, "Shares delta must be equal to the negative of sharesBefore"); + } + + function testFuzz_shareAdjustment_positiveToPositive(int256 sharesBefore, int256 sharesAfter) public { + cheats.assume(sharesBefore > 0); + cheats.assume(sharesAfter > 0); + + int256 sharesDelta = eigenPodManagerWrapper.calculateChangeInDelegatableShares(sharesBefore, sharesAfter); + assertEq(sharesDelta, sharesAfter - sharesBefore, "Shares delta must be equal to the difference between sharesAfter and sharesBefore"); } } \ No newline at end of file diff --git a/src/test/utils/EigenLayerUnitTestSetup.sol b/src/test/utils/EigenLayerUnitTestSetup.sol new file mode 100644 index 0000000000..16f0ec921d --- /dev/null +++ b/src/test/utils/EigenLayerUnitTestSetup.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.12; + +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +import "forge-std/Test.sol"; + +abstract contract EigenLayerUnitTestSetup is Test { + Vm cheats = Vm(HEVM_ADDRESS); + + mapping(address => bool) public addressIsExcludedFromFuzzedInputs; + + address public constant pauser = address(555); + address public constant unpauser = address(556); + + // Helper Functions/Modifiers + modifier filterFuzzedAddressInputs(address fuzzedAddress) { + cheats.assume(!addressIsExcludedFromFuzzedInputs[fuzzedAddress]); + _; + } +} \ No newline at end of file