diff --git a/src/test/recon/EigenLayerSystem.sol b/src/test/recon/EigenLayerSystem.sol index a52e4ed0..6651d4e7 100644 --- a/src/test/recon/EigenLayerSystem.sol +++ b/src/test/recon/EigenLayerSystem.sol @@ -2,8 +2,13 @@ pragma solidity ^0.8.12; import {EigenLayerSetupV2} from "./EigenLayerSetupV2.sol"; +import {IStrategy} from "../../contracts/interfaces/IStrategy.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "forge-std/Test.sol"; + +contract EigenLayerSystem is EigenLayerSetupV2, Test { + address immutable TOKEN_BURN_ADDRESS = address(0xDEADBEEF); // used to simulate token burning for tokens that don't allow transfer to 0 address -contract EigenLayerSystem is EigenLayerSetupV2 { /// @notice simulates a native slashing event on a validator /// @dev when calling this through a target function, need to prank as the pod's address to allow modifying balances in EigenPodManager /// @param podOwner the owner of the pod being slashed @@ -15,6 +20,52 @@ contract EigenLayerSystem is EigenLayerSetupV2 { ethPOSDepositMock.slash(1 ether); } + /// @notice simulates an AVS slashing event + /// @dev this assumes slashing amounts for an LST and native ETH can be different + function slashAVS( + address user, + address[] memory activeStrategies, + uint256 nativeSlashAmount, + uint256 lstSlashAmount + ) public { + // Slash native ETH if user has any staked in an EigenPod + uint256 nativeEthShares = uint256(eigenPodManager.podOwnerShares(address(user))); + if (nativeEthShares > 0) { + // user can be slashed a max amount of their entire stake + nativeSlashAmount = nativeSlashAmount % nativeEthShares; + + // shares are 1:1 with ETH in EigenPod so can slash the share amount directly + ethPOSDepositMock.slash(nativeSlashAmount); + + // update the OperatorDelegator's share balance in EL by calling EigenPodManager as the pod + address podAddress = getPodForOwner(user); + vm.prank(podAddress); + eigenPodManager.recordBeaconChainETHBalanceUpdate(address(user), -int256(nativeSlashAmount)); + } + + // loop through strategies to slash if a user has any shares in them + for (uint256 i; i < activeStrategies.length; i++) { + IStrategy strategy = IStrategy(activeStrategies[i]); + uint256 lstShares = strategy.shares(address(user)); + + // Slash LST if user has any shares of the given LST strategy + if (lstShares > 0) { + uint256 slashingAmountLSTShares = lstSlashAmount % lstShares; + // convert share amount to slash to amount of collateral token + uint256 amountLSTToken = strategy.sharesToUnderlyingView(slashingAmountLSTShares); + + // "burn" tokens in strategy to ensure they don't effect accounting + vm.prank(activeStrategies[i]); + IERC20 underlyingToken = strategy.underlyingToken(); + underlyingToken.transfer(TOKEN_BURN_ADDRESS, amountLSTToken); + + // remove shares to update user's accounting + vm.prank(address(delegation)); + _removeSharesFromStrategyManager(address(user), address(activeStrategies[i]), slashingAmountLSTShares); + } + } + } + /// @notice returns the address of an EigenPod for an Owner, if one exists function getPodForOwner(address owner) public view returns (address eigenPod) { return address(eigenPodManager.getPod(address(owner))); diff --git a/src/test/recon/EigenLayerSystemTest.t.sol b/src/test/recon/EigenLayerSystemTest.t.sol index 574c2882..a3271dd9 100644 --- a/src/test/recon/EigenLayerSystemTest.t.sol +++ b/src/test/recon/EigenLayerSystemTest.t.sol @@ -6,7 +6,7 @@ import {EigenLayerSystem} from "./EigenLayerSystem.sol"; import {MockERC20} from "../mocks/MockERC20.sol"; import "forge-std/Test.sol"; -contract EigenLayerSystemTest is EigenLayerSystem, Test { +contract EigenLayerSystemTest is EigenLayerSystem { MockERC20 stETH; MockERC20 cbETH;