From 557efde826c21b30a3d7bb16094b494d2f734650 Mon Sep 17 00:00:00 2001 From: Michael Sun Date: Mon, 6 Jan 2025 15:10:37 -0500 Subject: [PATCH] refactor: add burnableShares for epm storage --- src/contracts/core/DelegationManager.sol | 7 ++- src/contracts/core/StrategyManager.sol | 2 +- .../interfaces/IDelegationManager.sol | 2 +- src/contracts/interfaces/IEigenPodManager.sol | 6 +++ src/contracts/interfaces/IShareManager.sol | 9 ++++ src/contracts/interfaces/IStrategyManager.sol | 10 ---- src/contracts/pods/EigenPodManager.sol | 6 +++ src/contracts/pods/EigenPodManagerStorage.sol | 5 +- src/test/unit/EigenPodManagerUnit.t.sol | 46 ++++++++++++++++++- 9 files changed, 75 insertions(+), 18 deletions(-) diff --git a/src/contracts/core/DelegationManager.sol b/src/contracts/core/DelegationManager.sol index ce36f2f21e..2d5350b408 100644 --- a/src/contracts/core/DelegationManager.sol +++ b/src/contracts/core/DelegationManager.sol @@ -307,10 +307,9 @@ contract DelegationManager is sharesToDecrease: operatorSharesSlashed }); - // NOTE: native ETH shares will be burned by a different mechanism in a future release - if (strategy != beaconChainETHStrategy) { - strategyManager.increaseBurnableShares(strategy, totalDepositSharesToBurn); - } + IShareManager shareManager = _getShareManager(strategy); + // NOTE: for beaconChainETHStrategy, increased burnable shares currently have no mechanism for burning + shareManager.increaseBurnableShares(strategy, totalDepositSharesToBurn); } /** diff --git a/src/contracts/core/StrategyManager.sol b/src/contracts/core/StrategyManager.sol index 0f45977b65..3f986fedec 100644 --- a/src/contracts/core/StrategyManager.sol +++ b/src/contracts/core/StrategyManager.sol @@ -140,7 +140,7 @@ contract StrategyManager is strategy.withdraw(staker, token, shares); } - /// @inheritdoc IStrategyManager + /// @inheritdoc IShareManager function increaseBurnableShares(IStrategy strategy, uint256 addedSharesToBurn) external onlyDelegationManager { burnableShares[strategy] += addedSharesToBurn; emit BurnableSharesIncreased(strategy, addedSharesToBurn); diff --git a/src/contracts/interfaces/IDelegationManager.sol b/src/contracts/interfaces/IDelegationManager.sol index c3429830a2..4533fffe7b 100644 --- a/src/contracts/interfaces/IDelegationManager.sol +++ b/src/contracts/interfaces/IDelegationManager.sol @@ -351,7 +351,7 @@ interface IDelegationManager is ISignatureUtils, IDelegationManagerErrors, IDele /** * @notice Decreases the operators shares in storage after a slash and increases the burnable shares by calling - * into the StrategyManager. Burnable shares for the beaconChainETHStrategy/EigenPodManager are specifically not handled. + * into either the StrategyManager or EigenPodManager (if the strategy is beaconChainETH). * @param operator The operator to decrease shares for * @param strategy The strategy to decrease shares for * @param prevMaxMagnitude the previous maxMagnitude of the operator diff --git a/src/contracts/interfaces/IEigenPodManager.sol b/src/contracts/interfaces/IEigenPodManager.sol index 3417965414..b7611b50a4 100644 --- a/src/contracts/interfaces/IEigenPodManager.sol +++ b/src/contracts/interfaces/IEigenPodManager.sol @@ -54,6 +54,9 @@ interface IEigenPodManagerEvents { event BeaconChainSlashingFactorDecreased( address staker, uint64 prevBeaconChainSlashingFactor, uint64 newBeaconChainSlashingFactor ); + + /// @notice Emitted when an operator is slashed and shares to be burned are increased + event BurnableETHSharesIncreased(uint256 shares); } interface IEigenPodManagerTypes { @@ -161,4 +164,7 @@ interface IEigenPodManager is function beaconChainSlashingFactor( address staker ) external view returns (uint64); + + /// @notice Returns the accumulated amount of beacon chain ETH Strategy shares + function burnableETHShares() external view returns (uint256); } diff --git a/src/contracts/interfaces/IShareManager.sol b/src/contracts/interfaces/IShareManager.sol index aaf5d68546..6ee50bc627 100644 --- a/src/contracts/interfaces/IShareManager.sol +++ b/src/contracts/interfaces/IShareManager.sol @@ -37,4 +37,13 @@ interface IShareManager { /// @dev strategy must be beaconChainETH when talking to the EigenPodManager /// @dev returns 0 if the user has negative shares function stakerDepositShares(address user, IStrategy strategy) external view returns (uint256 depositShares); + + /** + * @notice Increase the amount of burnable shares for a given Strategy. This is called by the DelegationManager + * when an operator is slashed in EigenLayer. + * @param strategy The strategy to burn shares in. + * @param addedSharesToBurn The amount of added shares to burn. + * @dev This function is only called by the DelegationManager when an operator is slashed. + */ + function increaseBurnableShares(IStrategy strategy, uint256 addedSharesToBurn) external; } diff --git a/src/contracts/interfaces/IStrategyManager.sol b/src/contracts/interfaces/IStrategyManager.sol index 238991a417..6b31224c72 100644 --- a/src/contracts/interfaces/IStrategyManager.sol +++ b/src/contracts/interfaces/IStrategyManager.sol @@ -114,16 +114,6 @@ interface IStrategyManager is IStrategyManagerErrors, IStrategyManagerEvents, IS bytes memory signature ) external returns (uint256 shares); - /** - * @notice Increases burnable Strategy shares for the given strategy when an operator is slashed. - * @notice Increase the amount of burnable shares for a given Strategy. This is called by the DelegationManager - * when an operator is slashed in EigenLayer. The shares can be burned in a separate transaction calling `burnShares`. - * @param strategy The strategy to burn shares in. - * @param addedSharesToBurn The amount of added shares to burn. - * @dev This function is only called by the DelegationManager when an operator is slashed. - */ - function increaseBurnableShares(IStrategy strategy, uint256 addedSharesToBurn) external; - /** * @notice Burns Strategy shares for the given strategy by calling into the strategy to transfer * to the default burn address. diff --git a/src/contracts/pods/EigenPodManager.sol b/src/contracts/pods/EigenPodManager.sol index eb3f36b0ee..c15b60f8fe 100644 --- a/src/contracts/pods/EigenPodManager.sol +++ b/src/contracts/pods/EigenPodManager.sol @@ -219,6 +219,12 @@ contract EigenPodManager is } } + /// @inheritdoc IShareManager + function increaseBurnableShares(IStrategy, uint256 addedSharesToBurn) external onlyDelegationManager { + burnableETHShares += addedSharesToBurn; + emit BurnableETHSharesIncreased(addedSharesToBurn); + } + // INTERNAL FUNCTIONS function _deployPod() internal returns (IEigenPod) { diff --git a/src/contracts/pods/EigenPodManagerStorage.sol b/src/contracts/pods/EigenPodManagerStorage.sol index c02d63bcc3..dc1281521a 100644 --- a/src/contracts/pods/EigenPodManagerStorage.sol +++ b/src/contracts/pods/EigenPodManagerStorage.sol @@ -78,6 +78,9 @@ abstract contract EigenPodManagerStorage is IEigenPodManager { /// Note: this is specifically updated when the staker's beacon chain balance decreases mapping(address staker => BeaconChainSlashingFactor) internal _beaconChainSlashingFactor; + /// @notice Returns the amount of `shares` that have been slashed on EigenLayer but not burned yet. + uint256 public burnableETHShares; + constructor(IETHPOSDeposit _ethPOS, IBeacon _eigenPodBeacon, IDelegationManager _delegationManager) { ethPOS = _ethPOS; eigenPodBeacon = _eigenPodBeacon; @@ -89,5 +92,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[42] private __gap; } diff --git a/src/test/unit/EigenPodManagerUnit.t.sol b/src/test/unit/EigenPodManagerUnit.t.sol index 72befd4fdf..f1286f0316 100644 --- a/src/test/unit/EigenPodManagerUnit.t.sol +++ b/src/test/unit/EigenPodManagerUnit.t.sol @@ -521,4 +521,48 @@ contract EigenPodManagerUnitTests_BeaconChainETHBalanceUpdateTests is EigenPodMa assertEq(eigenPodManager.podOwnerDepositShares(defaultStaker), int(sharesBefore), "Shares should not be adjusted"); assertTrue(eigenPodManager.beaconChainSlashingFactor(defaultStaker) <= prevSlashingFactor, "bcsf should always decrease"); } -} \ No newline at end of file +} + +contract EigenPodManagerUnitTests_increaseBurnableShares is EigenPodManagerUnitTests { + function testFuzz_onlyDelegationManager(address invalidCaller) public filterFuzzedAddressInputs(invalidCaller) { + cheats.assume(invalidCaller != address(delegationManagerMock)); + cheats.prank(invalidCaller); + cheats.expectRevert(IEigenPodManagerErrors.OnlyDelegationManager.selector); + eigenPodManager.increaseBurnableShares(beaconChainETHStrategy, 1 ether); + } + + function testFuzz_singleDeposit(uint256 increasedBurnableShares) public { + cheats.expectEmit(true, true, true, true, address(eigenPodManager)); + emit BurnableETHSharesIncreased(increasedBurnableShares); + + cheats.prank(address(delegationManagerMock)); + eigenPodManager.increaseBurnableShares(beaconChainETHStrategy, increasedBurnableShares); + + assertEq(eigenPodManager.burnableETHShares(), increasedBurnableShares, "Burnable shares not updated correctly"); + } + + function testFuzz_existingDeposit( + uint256 existingBurnableShares, + uint256 increasedBurnableShares + ) public { + // prevent overflow + cheats.assume(existingBurnableShares < type(uint256).max - increasedBurnableShares); + + cheats.expectEmit(true, true, true, true, address(eigenPodManager)); + emit BurnableETHSharesIncreased(existingBurnableShares); + cheats.prank(address(delegationManagerMock)); + eigenPodManager.increaseBurnableShares(beaconChainETHStrategy, existingBurnableShares); + + assertEq(eigenPodManager.burnableETHShares(), existingBurnableShares, "Burnable shares not setup correctly"); + + cheats.expectEmit(true, true, true, true, address(eigenPodManager)); + emit BurnableETHSharesIncreased(increasedBurnableShares); + cheats.prank(address(delegationManagerMock)); + eigenPodManager.increaseBurnableShares(beaconChainETHStrategy, increasedBurnableShares); + + assertEq( + eigenPodManager.burnableETHShares(), + existingBurnableShares + increasedBurnableShares, "Burnable shares not updated correctly" + ); + } +}