@@ -34,6 +34,10 @@ import {IStrategy} from "lib/eigenlayer-contracts/src/contracts/interfaces/IStra
34
34
import {StakingNodeTestBase, IEigenPodSimplified} from "./StakingNodeTestBase.sol " ;
35
35
import {IRewardsCoordinator} from "lib/eigenlayer-contracts/src/contracts/interfaces/IRewardsCoordinator.sol " ;
36
36
import {SlashingLib} from "lib/eigenlayer-contracts/src/contracts/libraries/SlashingLib.sol " ;
37
+ import {IAllocationManager, IAllocationManagerTypes} from "lib/eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol " ;
38
+ import {OperatorSet} from "lib/eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol " ;
39
+ import {AllocationManagerStorage} from "lib/eigenlayer-contracts/src/contracts/core/AllocationManagerStorage.sol " ;
40
+ import {MockAVS} from "test/mocks/MockAVS.sol " ;
37
41
import {console} from "forge-std/console.sol " ;
38
42
39
43
contract StakingNodeEigenPod is StakingNodeTestBase {
@@ -1306,8 +1310,6 @@ contract StakingNodeWithdrawals is StakingNodeTestBase {
1306
1310
initialState.podOwnerDepositShares - int256 (withdrawalAmount),
1307
1311
"Pod owner shares should decrease by withdrawalAmount "
1308
1312
);
1309
-
1310
-
1311
1313
}
1312
1314
1313
1315
function testQueueWithdrawalsFailsWhenNotAdmin () public {
@@ -1788,3 +1790,307 @@ contract StakingNodeSyncQueuedShares is StakingNodeTestBase {
1788
1790
stakingNodeInstance.syncQueuedShares ();
1789
1791
}
1790
1792
}
1793
+
1794
+ contract StakingNodeOperatorSlashing is StakingNodeTestBase {
1795
+ using stdStorage for StdStorage;
1796
+ using BytesLib for bytes ;
1797
+ using SlashingLib for * ;
1798
+
1799
+
1800
+ address user = vm.addr (156_737 );
1801
+ uint40 [] validatorIndices;
1802
+
1803
+ address avs;
1804
+ address operator1 = address (0x9999 );
1805
+ address operator2 = address (0x8888 );
1806
+
1807
+ uint256 nodeId;
1808
+ IStakingNode stakingNodeInstance;
1809
+ IAllocationManager allocationManager;
1810
+
1811
+ function setUp () public override {
1812
+ super .setUp ();
1813
+
1814
+ avs = address (new MockAVS ());
1815
+
1816
+ address [] memory operators = new address [](2 );
1817
+ operators[0 ] = operator1;
1818
+ operators[1 ] = operator2;
1819
+
1820
+ for (uint256 i = 0 ; i < operators.length ; i++ ) {
1821
+ vm.prank (operators[i]);
1822
+ delegationManager.registerAsOperator (address (0 ),0 , "ipfs://some-ipfs-hash " );
1823
+ }
1824
+
1825
+ nodeId = createStakingNodes (1 )[0 ];
1826
+ stakingNodeInstance = stakingNodesManager.nodes (nodeId);
1827
+
1828
+ vm.prank (actors.admin.STAKING_NODES_DELEGATOR);
1829
+ stakingNodeInstance.delegate (
1830
+ operator1, ISignatureUtils.SignatureWithExpiry ({signature: "" , expiry: 0 }), bytes32 (0 )
1831
+ );
1832
+
1833
+ allocationManager = IAllocationManager (chainAddresses.eigenlayer.ALLOCATION_MANAGER_ADDRESS);
1834
+ IStrategy[] memory strategies = new IStrategy [](1 );
1835
+ strategies[0 ] = IStrategy (stakingNodeInstance.beaconChainETHStrategy ());
1836
+ IAllocationManagerTypes.CreateSetParams[] memory createSetParams = new IAllocationManagerTypes.CreateSetParams [](1 );
1837
+ createSetParams[0 ] = IAllocationManagerTypes.CreateSetParams ({
1838
+ operatorSetId: 1 ,
1839
+ strategies: strategies
1840
+ });
1841
+
1842
+ vm.prank (avs);
1843
+ allocationManager.createOperatorSets (avs, createSetParams);
1844
+
1845
+ uint32 allocationConfigurationDelay = AllocationManagerStorage (address (allocationManager)).ALLOCATION_CONFIGURATION_DELAY ();
1846
+
1847
+ uint32 [] memory operatorSetIds = new uint32 [](1 );
1848
+ operatorSetIds[0 ] = uint32 (1 );
1849
+ IAllocationManagerTypes.RegisterParams memory registerParams = IAllocationManagerTypes.RegisterParams ({
1850
+ avs: avs,
1851
+ operatorSetIds: operatorSetIds,
1852
+ data: ""
1853
+ });
1854
+ OperatorSet memory operatorSet = OperatorSet ({
1855
+ avs: avs,
1856
+ id: 1
1857
+ });
1858
+ uint64 [] memory newMagnitudes = new uint64 [](1 );
1859
+ newMagnitudes[0 ] = uint64 (1 ether);
1860
+ IAllocationManagerTypes.AllocateParams[] memory allocateParams = new IAllocationManagerTypes.AllocateParams [](1 );
1861
+ allocateParams[0 ] = IAllocationManagerTypes.AllocateParams ({
1862
+ operatorSet: operatorSet,
1863
+ strategies: strategies,
1864
+ newMagnitudes: newMagnitudes
1865
+ });
1866
+
1867
+ vm.roll (block .number + allocationConfigurationDelay);
1868
+
1869
+ vm.startPrank (operator1);
1870
+ allocationManager.registerForOperatorSets (operator1, registerParams);
1871
+ allocationManager.modifyAllocations (operator1, allocateParams);
1872
+ vm.stopPrank ();
1873
+ }
1874
+
1875
+ function testSlashedOperatorBeforeQueuedWithdrawals () public {
1876
+ uint256 validatorCount = 2 ;
1877
+ uint256 totalDepositedAmount;
1878
+
1879
+ {
1880
+ // Setup
1881
+ uint256 depositAmount = 32 ether ;
1882
+ totalDepositedAmount = depositAmount * validatorCount;
1883
+ address user = vm.addr (156_737 );
1884
+ vm.deal (user, 1000 ether);
1885
+ yneth.depositETH {value: totalDepositedAmount}(user); // Deposit for validators
1886
+ }
1887
+
1888
+ {
1889
+ // Setup: Create multiple validators and verify withdrawal credentials
1890
+ uint40 [] memory validatorIndices = createValidators (repeat (nodeId, validatorCount), validatorCount);
1891
+ beaconChain.advanceEpoch_NoRewards ();
1892
+ registerValidators (repeat (nodeId, validatorCount));
1893
+
1894
+ beaconChain.advanceEpoch_NoRewards ();
1895
+
1896
+ for (uint256 i = 0 ; i < validatorCount; i++ ) {
1897
+ _verifyWithdrawalCredentials (nodeId, validatorIndices[i]);
1898
+ }
1899
+
1900
+ beaconChain.advanceEpoch_NoRewards ();
1901
+ }
1902
+
1903
+ // Capture initial state
1904
+ StateSnapshot memory initialState = takeSnapshot (nodeId);
1905
+ IStrategy beaconChainETHStrategy = stakingNodeInstance.beaconChainETHStrategy ();
1906
+ uint256 beaconChainSlashingFactorBefore = eigenPodManager.beaconChainSlashingFactor (address (stakingNodeInstance));
1907
+ uint256 operatorMaxMagnitudeBefore = allocationManager.getMaxMagnitude (operator1, beaconChainETHStrategy);
1908
+
1909
+ // Start and verify checkpoint for all validators
1910
+ startAndVerifyCheckpoint (nodeId, validatorIndices);
1911
+
1912
+ uint256 slashingPercent = 0.3 ether ;
1913
+ {
1914
+ IStrategy[] memory strategies = new IStrategy [](1 );
1915
+ strategies[0 ] = IStrategy (stakingNodeInstance.beaconChainETHStrategy ());
1916
+ uint256 [] memory wadsToSlash = new uint256 [](1 );
1917
+ wadsToSlash[0 ] = slashingPercent; // slash 30% of the operator's stake
1918
+ IAllocationManagerTypes.SlashingParams memory slashingParams = IAllocationManagerTypes.SlashingParams ({
1919
+ operator: operator1,
1920
+ operatorSetId: 1 ,
1921
+ strategies: strategies,
1922
+ wadsToSlash: wadsToSlash,
1923
+ description: "Slashing operator1 "
1924
+ });
1925
+
1926
+ vm.prank (avs);
1927
+ allocationManager.slashOperator (avs, slashingParams);
1928
+
1929
+ stakingNodeInstance.stakingNodesManager ().updateTotalETHStaked ();
1930
+ }
1931
+
1932
+ {
1933
+ // Get final state
1934
+ StateSnapshot memory finalState = takeSnapshot (nodeId);
1935
+ uint256 beaconChainSlashingFactorAfter = eigenPodManager.beaconChainSlashingFactor (address (stakingNodeInstance));
1936
+ uint256 operatorMaxMagnitudeAfter = allocationManager.getMaxMagnitude (operator1, beaconChainETHStrategy);
1937
+
1938
+ uint256 slashedAmountInWei = totalDepositedAmount.mulWad (slashingPercent);
1939
+ // Assert
1940
+ assertEq (beaconChainSlashingFactorBefore, beaconChainSlashingFactorAfter, "Beacon chain slashing factor should not change " );
1941
+ assertLt (operatorMaxMagnitudeAfter, operatorMaxMagnitudeBefore, "Operator max magnitude should decrease due to slashing " );
1942
+ assertEq (finalState.totalAssets, initialState.totalAssets - slashedAmountInWei, "Total assets should decrease by slashed amount " );
1943
+ assertEq (finalState.totalSupply, initialState.totalSupply, "Total supply should remain unchanged " );
1944
+ assertEq (
1945
+ finalState.stakingNodeBalance,
1946
+ initialState.stakingNodeBalance - slashedAmountInWei,
1947
+ "Staking node balance should decrease by slashed amount "
1948
+ );
1949
+ assertEq (
1950
+ finalState.queuedShares,
1951
+ initialState.queuedShares,
1952
+ "Queued shares should remain unchanged "
1953
+ );
1954
+ assertEq (finalState.withdrawnETH, initialState.withdrawnETH, "Withdrawn ETH should remain unchanged " );
1955
+ assertEq (
1956
+ finalState.unverifiedStakedETH,
1957
+ initialState.unverifiedStakedETH,
1958
+ "Unverified staked ETH should remain unchanged "
1959
+ );
1960
+ assertEq (
1961
+ finalState.podOwnerDepositShares,
1962
+ initialState.podOwnerDepositShares,
1963
+ "Pod owner shares should remain unchanged "
1964
+ );
1965
+ }
1966
+ }
1967
+
1968
+ function testSlashedOperatorBetweenQueuedAndCompletedWithdrawals () public {
1969
+ uint256 validatorCount = 2 ;
1970
+ uint256 totalDepositedAmount;
1971
+
1972
+ {
1973
+ // Setup
1974
+ uint256 depositAmount = 32 ether ;
1975
+ totalDepositedAmount = depositAmount * validatorCount;
1976
+ address user = vm.addr (156_737 );
1977
+ vm.deal (user, 1000 ether);
1978
+ yneth.depositETH {value: totalDepositedAmount}(user); // Deposit for validators
1979
+ }
1980
+ uint40 [] memory validatorIndices;
1981
+ {
1982
+ // Setup: Create multiple validators and verify withdrawal credentials
1983
+ validatorIndices = createValidators (repeat (nodeId, validatorCount), validatorCount);
1984
+ beaconChain.advanceEpoch_NoRewards ();
1985
+ registerValidators (repeat (nodeId, validatorCount));
1986
+
1987
+ beaconChain.advanceEpoch_NoRewards ();
1988
+
1989
+ for (uint256 i = 0 ; i < validatorCount; i++ ) {
1990
+ _verifyWithdrawalCredentials (nodeId, validatorIndices[i]);
1991
+ }
1992
+
1993
+ beaconChain.advanceEpoch_NoRewards ();
1994
+ }
1995
+
1996
+ // Capture initial state
1997
+ StateSnapshot memory initialState = takeSnapshot (nodeId);
1998
+ IStrategy beaconChainETHStrategy = stakingNodeInstance.beaconChainETHStrategy ();
1999
+ uint256 beaconChainSlashingFactorBefore = eigenPodManager.beaconChainSlashingFactor (address (stakingNodeInstance));
2000
+ uint256 operatorMaxMagnitudeBefore = allocationManager.getMaxMagnitude (operator1, beaconChainETHStrategy);
2001
+
2002
+ uint256 withdrawalAmount = 32 ether ;
2003
+ uint256 expectedWithdrawalAmount;
2004
+ {
2005
+ // Queue withdrawals for all validators
2006
+ vm.prank (actors.ops.STAKING_NODES_WITHDRAWER);
2007
+ bytes32 [] memory withdrawalRoots = stakingNodeInstance.queueWithdrawals (withdrawalAmount);
2008
+
2009
+ uint256 queuedSharesAmountBeforeSlashing = stakingNodeInstance.queuedSharesAmount ();
2010
+ assertEq (queuedSharesAmountBeforeSlashing, withdrawalAmount, "Queued shares should be equal to withdrawal amount " );
2011
+
2012
+ // Exit validator
2013
+ beaconChain.exitValidator (validatorIndices[0 ]);
2014
+ beaconChain.advanceEpoch_NoRewards ();
2015
+
2016
+ // Start and verify checkpoint for all validators
2017
+ startAndVerifyCheckpoint (nodeId, validatorIndices);
2018
+
2019
+ uint256 slashingPercent = 0.3 ether ;
2020
+ {
2021
+ IStrategy[] memory strategies = new IStrategy [](1 );
2022
+ strategies[0 ] = IStrategy (stakingNodeInstance.beaconChainETHStrategy ());
2023
+ uint256 [] memory wadsToSlash = new uint256 [](1 );
2024
+ wadsToSlash[0 ] = slashingPercent; // slash 30% of the operator's stake
2025
+ IAllocationManagerTypes.SlashingParams memory slashingParams = IAllocationManagerTypes.SlashingParams ({
2026
+ operator: operator1,
2027
+ operatorSetId: 1 ,
2028
+ strategies: strategies,
2029
+ wadsToSlash: wadsToSlash,
2030
+ description: "Slashing operator1 "
2031
+ });
2032
+
2033
+ vm.prank (avs);
2034
+ allocationManager.slashOperator (avs, slashingParams);
2035
+
2036
+
2037
+
2038
+
2039
+ // expect revert when completing withdrawals due to syncQueuedShares not done
2040
+ _completeQueuedWithdrawals (withdrawalRoots, nodeId, true );
2041
+
2042
+
2043
+ vm.prank (actors.admin.STAKING_NODES_DELEGATOR);
2044
+ stakingNodeInstance.syncQueuedShares ();
2045
+ }
2046
+ {
2047
+ uint256 nodeBalanceReceived;
2048
+ uint256 nodeBalanceBeforeWithdrawal = address (stakingNodeInstance).balance;
2049
+ _completeQueuedWithdrawals (withdrawalRoots, nodeId, false );
2050
+ uint256 nodeBalanceAfterWithdrawal = address (stakingNodeInstance).balance;
2051
+ nodeBalanceReceived = nodeBalanceAfterWithdrawal - nodeBalanceBeforeWithdrawal;
2052
+ expectedWithdrawalAmount = withdrawalAmount.mulWad (1 ether - slashingPercent);
2053
+ assertEq (nodeBalanceReceived, expectedWithdrawalAmount, "Node's ETH balance should increase by expected withdrawal amount " );
2054
+ }
2055
+ }
2056
+
2057
+ stakingNodeInstance.stakingNodesManager ().updateTotalETHStaked ();
2058
+
2059
+
2060
+
2061
+ {
2062
+ // Get final state
2063
+ StateSnapshot memory finalState = takeSnapshot (nodeId);
2064
+ uint256 beaconChainSlashingFactorAfter = eigenPodManager.beaconChainSlashingFactor (address (stakingNodeInstance));
2065
+ uint256 operatorMaxMagnitudeAfter = allocationManager.getMaxMagnitude (operator1, beaconChainETHStrategy);
2066
+ uint256 slashedAmount = totalDepositedAmount - totalDepositedAmount.mulWad (operatorMaxMagnitudeAfter);
2067
+
2068
+ // Assert
2069
+ assertEq (beaconChainSlashingFactorBefore, beaconChainSlashingFactorAfter, "Beacon chain slashing factor should not change " );
2070
+ assertLt (operatorMaxMagnitudeAfter, operatorMaxMagnitudeBefore, "Operator max magnitude should decrease due to slashing " );
2071
+ assertEq (finalState.totalAssets, initialState.totalAssets - slashedAmount, "Total assets should decrease by slashed amount " );
2072
+ assertEq (finalState.totalSupply, initialState.totalSupply, "Total supply should remain unchanged " );
2073
+ assertEq (
2074
+ finalState.stakingNodeBalance,
2075
+ initialState.stakingNodeBalance - slashedAmount,
2076
+ "Staking node balance should decrease by slashed amount "
2077
+ );
2078
+ assertEq (
2079
+ finalState.queuedShares,
2080
+ initialState.queuedShares,
2081
+ "Queued shares should remain unchanged "
2082
+ );
2083
+ assertEq (finalState.withdrawnETH, initialState.withdrawnETH + expectedWithdrawalAmount, "Withdrawn ETH should remain unchanged " );
2084
+ assertEq (
2085
+ finalState.unverifiedStakedETH,
2086
+ initialState.unverifiedStakedETH,
2087
+ "Unverified staked ETH should remain unchanged "
2088
+ );
2089
+ assertEq (
2090
+ finalState.podOwnerDepositShares,
2091
+ initialState.podOwnerDepositShares - int256 (withdrawalAmount),
2092
+ "Pod owner shares should remain unchanged "
2093
+ );
2094
+ }
2095
+ }
2096
+ }
0 commit comments