Skip to content

Commit e5c11d3

Browse files
add test
1 parent a79cb1e commit e5c11d3

File tree

5 files changed

+322
-7
lines changed

5 files changed

+322
-7
lines changed

script/ContractAddresses.sol

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ contract ContractAddresses {
3535
address STRATEGY_MANAGER_ADDRESS;
3636
address STRATEGY_MANAGER_PAUSER_ADDRESS;
3737
address REWARDS_COORDINATOR_ADDRESS;
38+
address ALLOCATION_MANAGER_ADDRESS;
3839
}
3940

4041
struct LSDAddresses {
@@ -97,7 +98,8 @@ contract ContractAddresses {
9798
DELEGATION_PAUSER_ADDRESS: 0x369e6F597e22EaB55fFb173C6d9cD234BD699111, // TODO: remove this if unused
9899
STRATEGY_MANAGER_ADDRESS: 0x858646372CC42E1A627fcE94aa7A7033e7CF075A,
99100
STRATEGY_MANAGER_PAUSER_ADDRESS: 0xBE1685C81aA44FF9FB319dD389addd9374383e90,
100-
REWARDS_COORDINATOR_ADDRESS: 0x7750d328b314EfFa365A0402CcfD489B80B0adda
101+
REWARDS_COORDINATOR_ADDRESS: 0x7750d328b314EfFa365A0402CcfD489B80B0adda,
102+
ALLOCATION_MANAGER_ADDRESS: 0x0000000000000000000000000000000000000000 // TODO: Update this with correct address after mainnet deployment
101103
}),
102104
lsd: LSDAddresses({
103105
SFRXETH_ADDRESS: 0xac3E018457B222d93114458476f3E3416Abbe38F,
@@ -158,7 +160,8 @@ contract ContractAddresses {
158160
DELEGATION_PAUSER_ADDRESS: 0x28Ade60640fdBDb2609D8d8734D1b5cBeFc0C348, // Placeholder address, replaced with address(1) for holesky
159161
STRATEGY_MANAGER_ADDRESS: 0xdfB5f6CE42aAA7830E94ECFCcAd411beF4d4D5b6, // Placeholder address, replaced with address(1) for holesky
160162
STRATEGY_MANAGER_PAUSER_ADDRESS: 0x28Ade60640fdBDb2609D8d8734D1b5cBeFc0C348,
161-
REWARDS_COORDINATOR_ADDRESS: 0xAcc1fb458a1317E886dB376Fc8141540537E68fE
163+
REWARDS_COORDINATOR_ADDRESS: 0xAcc1fb458a1317E886dB376Fc8141540537E68fE,
164+
ALLOCATION_MANAGER_ADDRESS: 0x78469728304326CBc65f8f95FA756B0B73164462
162165
}),
163166
lsd: LSDAddresses({
164167
SFRXETH_ADDRESS: 0xa63f56985F9C7F3bc9fFc5685535649e0C1a55f3,

src/WithdrawalQueueManager.sol

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -296,9 +296,7 @@ contract WithdrawalQueueManager is IWithdrawalQueueManager, ERC721EnumerableUpgr
296296
);
297297

298298
uint256 unitOfAccountAmount = calculateRedemptionAmount(request.amount, redemptionRate);
299-
// TODO: check if this is correct
300-
//// decrements pendingRequestedRedemptionAmount by the amount that is to be sent to the user
301-
//// this should be decremented by the amount with which it was incremented when withdrawal was created
299+
302300
pendingRequestedRedemptionAmount -= unitOfAccountAmount;
303301

304302
_burn(tokenId);

src/interfaces/IStakingNodesManager.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ interface IStakingNodesManager {
5252

5353
function totalDeposited() external view returns (uint256);
5454

55+
function updateTotalETHStaked() external;
56+
5557
function processPrincipalWithdrawals(
5658
WithdrawalAction[] memory actions
5759
) external;

test/integration/StakingNode.t.sol

Lines changed: 308 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ import {IStrategy} from "lib/eigenlayer-contracts/src/contracts/interfaces/IStra
3434
import {StakingNodeTestBase, IEigenPodSimplified} from "./StakingNodeTestBase.sol";
3535
import {IRewardsCoordinator} from "lib/eigenlayer-contracts/src/contracts/interfaces/IRewardsCoordinator.sol";
3636
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";
3741
import {console} from "forge-std/console.sol";
3842

3943
contract StakingNodeEigenPod is StakingNodeTestBase {
@@ -1306,8 +1310,6 @@ contract StakingNodeWithdrawals is StakingNodeTestBase {
13061310
initialState.podOwnerDepositShares - int256(withdrawalAmount),
13071311
"Pod owner shares should decrease by withdrawalAmount"
13081312
);
1309-
1310-
13111313
}
13121314

13131315
function testQueueWithdrawalsFailsWhenNotAdmin() public {
@@ -1788,3 +1790,307 @@ contract StakingNodeSyncQueuedShares is StakingNodeTestBase {
17881790
stakingNodeInstance.syncQueuedShares();
17891791
}
17901792
}
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+
}

test/mocks/MockAVS.sol

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// SPDX-License-Identifier: BSD 3-Clause License
2+
pragma solidity ^0.8.24;
3+
4+
contract MockAVS {
5+
function registerOperator(address operator, uint32[] calldata operatorSetIds, bytes calldata data) external {}
6+
}

0 commit comments

Comments
 (0)