|
1 | 1 | // SPDX-License-Identifier: BUSL-1.1 |
2 | 2 | pragma solidity ^0.8.27; |
3 | 3 |
|
4 | | -import "src/contracts/core/AllocationManager.sol"; |
| 4 | +import "src/test/harnesses/AllocationManagerHarness.sol"; |
5 | 5 | import "src/test/utils/EigenLayerUnitTestSetup.sol"; |
6 | 6 | import "src/test/mocks/MockAVSRegistrar.sol"; |
7 | 7 |
|
@@ -33,7 +33,7 @@ contract AllocationManagerUnitTests is EigenLayerUnitTestSetup, IAllocationManag |
33 | 33 | /// Mocks |
34 | 34 | /// ----------------------------------------------------------------------- |
35 | 35 |
|
36 | | - AllocationManager allocationManager; |
| 36 | + AllocationManagerHarness allocationManager; |
37 | 37 | ERC20PresetFixedSupply tokenMock; |
38 | 38 | StrategyBase strategyMock; |
39 | 39 |
|
@@ -86,12 +86,12 @@ contract AllocationManagerUnitTests is EigenLayerUnitTestSetup, IAllocationManag |
86 | 86 | address _initialOwner, |
87 | 87 | IPauserRegistry _pauserRegistry, |
88 | 88 | uint256 _initialPausedStatus |
89 | | - ) internal returns (AllocationManager) { |
90 | | - return allocationManager = AllocationManager( |
| 89 | + ) internal returns (AllocationManagerHarness) { |
| 90 | + return allocationManager = AllocationManagerHarness( |
91 | 91 | address( |
92 | 92 | new TransparentUpgradeableProxy( |
93 | 93 | address( |
94 | | - new AllocationManager( |
| 94 | + new AllocationManagerHarness( |
95 | 95 | IDelegationManager(address(delegationManagerMock)), |
96 | 96 | _pauserRegistry, |
97 | 97 | IPermissionController(address(permissionController)), |
@@ -259,6 +259,23 @@ contract AllocationManagerUnitTests is EigenLayerUnitTestSetup, IAllocationManag |
259 | 259 | console.log("Success!".green().bold()); |
260 | 260 | } |
261 | 261 |
|
| 262 | + /// @dev Check that the deallocation queue is in ascending order of effectBlocks |
| 263 | + function _checkDeallocationQueueOrder(address operator, IStrategy strategy, uint256 numDeallocations) internal view { |
| 264 | + uint32 lastEffectBlock = 0; |
| 265 | + |
| 266 | + for (uint256 i = 0; i < numDeallocations; ++i) { |
| 267 | + bytes32 operatorSetKey = allocationManager.deallocationQueueAtIndex(operator, strategy, i); |
| 268 | + Allocation memory allocation = allocationManager.getAllocation(operator, OperatorSetLib.decode(operatorSetKey), strategy); |
| 269 | + |
| 270 | + assertTrue( |
| 271 | + lastEffectBlock <= allocation.effectBlock, |
| 272 | + "Deallocation queue is not in ascending order of effectBlocks" |
| 273 | + ); |
| 274 | + |
| 275 | + lastEffectBlock = allocation.effectBlock; |
| 276 | + } |
| 277 | + } |
| 278 | + |
262 | 279 | function _checkSlashableStake( |
263 | 280 | OperatorSet memory operatorSet, |
264 | 281 | address operator, |
@@ -1916,6 +1933,191 @@ contract AllocationManagerUnitTests_ModifyAllocations is AllocationManagerUnitTe |
1916 | 1933 | allocationManager.modifyAllocations(defaultOperator, allocateParams); |
1917 | 1934 | } |
1918 | 1935 |
|
| 1936 | + /** |
| 1937 | + * @notice Regression tests for the bugfix where pending modifications were checked by |
| 1938 | + * require(allocation.pendingDiff == 0, ModificationAlreadyPending()); |
| 1939 | + * which would overwrite the effectBlock, pendingDiff if a pendingDiff |
| 1940 | + * of a deallocation was slashed to become 0. |
| 1941 | + * |
| 1942 | + * This test checks that the effectBlock, pendingDiff are not overwritten even if the pendingDiff is 0 |
| 1943 | + * when attempting to modify allocations again |
| 1944 | + */ |
| 1945 | + function test_modifyAllocations_PendingDiffZero() public { |
| 1946 | + // Step 1: Allocate to the operator set |
| 1947 | + AllocateParams[] memory allocateParams = _newAllocateParams(defaultOperatorSet, 501); |
| 1948 | + cheats.prank(defaultOperator); |
| 1949 | + allocationManager.modifyAllocations(defaultOperator, allocateParams); |
| 1950 | + |
| 1951 | + // Step 2: Roll blocks forward until the allocation effectBlock |
| 1952 | + cheats.roll(block.number + DEFAULT_OPERATOR_ALLOCATION_DELAY); |
| 1953 | + |
| 1954 | + // Step 3: Deallocate from the operator set |
| 1955 | + AllocateParams[] memory deallocateParams = _newAllocateParams(defaultOperatorSet, 500); |
| 1956 | + cheats.prank(defaultOperator); |
| 1957 | + allocationManager.modifyAllocations(defaultOperator, deallocateParams); |
| 1958 | + |
| 1959 | + Allocation memory allocation = allocationManager.getAllocation(defaultOperator, defaultOperatorSet, strategyMock); |
| 1960 | + uint32 originalEffectBlock = allocation.effectBlock; |
| 1961 | + |
| 1962 | + // Step 4: Slash the operator to adjust pendingDiff to 0, slashing rounds up the amount of magnitude to slash |
| 1963 | + // so with an existing deallocation/pendingDiff of 1, it should result in a pendingDiff of 0 |
| 1964 | + SlashingParams memory slashingParams = SlashingParams({ |
| 1965 | + operator: defaultOperator, |
| 1966 | + operatorSetId: defaultOperatorSet.id, |
| 1967 | + strategies: defaultStrategies, |
| 1968 | + wadsToSlash: 5e17.toArrayU256(), |
| 1969 | + description: "Test slashing" |
| 1970 | + }); |
| 1971 | + cheats.prank(defaultAVS); |
| 1972 | + allocationManager.slashOperator(defaultAVS, slashingParams); |
| 1973 | + allocation = allocationManager.getAllocation(defaultOperator, defaultOperatorSet, strategyMock); |
| 1974 | + assertEq(allocation.pendingDiff, 0, "Pending diff should be 0"); |
| 1975 | + assertEq(allocation.effectBlock, originalEffectBlock, "Effect block should not have changed"); |
| 1976 | + |
| 1977 | + // Step 5: Modify allocations again (Should not be called) |
| 1978 | + AllocateParams[] memory newAllocateParams = _newAllocateParams(defaultOperatorSet, 1000); |
| 1979 | + cheats.prank(defaultOperator); |
| 1980 | + allocationManager.modifyAllocations(defaultOperator, newAllocateParams); |
| 1981 | + |
| 1982 | + // Assert that the allocation was modified without reverting |
| 1983 | + allocation = allocationManager.getAllocation(defaultOperator, defaultOperatorSet, strategyMock); |
| 1984 | + assertEq(allocation.currentMagnitude, 250, "Allocation should be updated to 250 after slashing 50%"); |
| 1985 | + |
| 1986 | + // Note: These 2 assertions fail prior to the bugfix and if we kept the same |
| 1987 | + // require(allocation.pendingDiff == 0, ModificationAlreadyPending()); |
| 1988 | + // in the code. The effectBlock, pendingDiff would get overwritten with the new modification |
| 1989 | + // but the deallocationQueue would now be unordered(in terms of effectBlocks) with this overwrite. |
| 1990 | + assertEq(allocation.effectBlock, originalEffectBlock, "Effect block should not have changed"); |
| 1991 | + assertEq(allocation.pendingDiff, 0, "Pending diff should still be 0"); |
| 1992 | + } |
| 1993 | + |
| 1994 | + /** |
| 1995 | + * @notice Regression tests for the bugfix where pending modifications were checked by |
| 1996 | + * require(allocation.pendingDiff == 0, ModificationAlreadyPending()); |
| 1997 | + * which would overwrite the effectBlock, pendingDiff if a pendingDiff |
| 1998 | + * of a deallocation was slashed to become 0. |
| 1999 | + * |
| 2000 | + * This test checks that the deallocationQueue is ascending ordered by effectBlocks |
| 2001 | + */ |
| 2002 | + function test_modifyAllocations_PendingDiffZero_CheckOrderedDeallocationQueue() public { |
| 2003 | + // Step 1: Register the operator to multiple operator sets |
| 2004 | + OperatorSet memory operatorSet1 = OperatorSet(defaultAVS, 1); |
| 2005 | + OperatorSet memory operatorSet2 = OperatorSet(defaultAVS, 2); |
| 2006 | + _createOperatorSet(operatorSet1, defaultStrategies); |
| 2007 | + _createOperatorSet(operatorSet2, defaultStrategies); |
| 2008 | + _registerForOperatorSet(defaultOperator, operatorSet1); |
| 2009 | + _registerForOperatorSet(defaultOperator, operatorSet2); |
| 2010 | + |
| 2011 | + // Step 2: Allocate to both operator sets |
| 2012 | + AllocateParams[] memory allocateParams1 = _newAllocateParams(operatorSet1, 1001); |
| 2013 | + AllocateParams[] memory allocateParams2 = _newAllocateParams(operatorSet2, 1000); |
| 2014 | + cheats.prank(defaultOperator); |
| 2015 | + allocationManager.modifyAllocations(defaultOperator, allocateParams1); |
| 2016 | + cheats.prank(defaultOperator); |
| 2017 | + allocationManager.modifyAllocations(defaultOperator, allocateParams2); |
| 2018 | + |
| 2019 | + // Step 3: Roll blocks forward until the allocation effectBlock |
| 2020 | + cheats.roll(block.number + DEFAULT_OPERATOR_ALLOCATION_DELAY); |
| 2021 | + |
| 2022 | + // Step 4: Deallocate from both operator sets |
| 2023 | + AllocateParams[] memory deallocateParams1 = _newAllocateParams(operatorSet1, 1000); |
| 2024 | + AllocateParams[] memory deallocateParams2 = _newAllocateParams(operatorSet2, 0); |
| 2025 | + cheats.prank(defaultOperator); |
| 2026 | + allocationManager.modifyAllocations(defaultOperator, deallocateParams1); |
| 2027 | + // roll blocks forward so that deallocations have different effectBlocks |
| 2028 | + cheats.roll(block.number + 100); |
| 2029 | + cheats.prank(defaultOperator); |
| 2030 | + allocationManager.modifyAllocations(defaultOperator, deallocateParams2); |
| 2031 | + |
| 2032 | + // Step 5: Slash the first deallocation to adjust pendingDiff to 0 |
| 2033 | + SlashingParams memory slashingParams1 = SlashingParams({ |
| 2034 | + operator: defaultOperator, |
| 2035 | + operatorSetId: operatorSet1.id, |
| 2036 | + strategies: defaultStrategies, |
| 2037 | + wadsToSlash: 5e17.toArrayU256(), |
| 2038 | + description: "Test slashing" |
| 2039 | + }); |
| 2040 | + cheats.prank(defaultAVS); |
| 2041 | + allocationManager.slashOperator(defaultAVS, slashingParams1); |
| 2042 | + |
| 2043 | + // Step 6: Modify allocations again for operatorSet1 making another deallocation and |
| 2044 | + // overwriting/increasing the effectBlock |
| 2045 | + // roll blocks forward so that deallocations have different effectBlocks |
| 2046 | + cheats.roll(block.number + 100); |
| 2047 | + // Note: this should revert but previously it would not prior to the bugfix |
| 2048 | + AllocateParams[] memory newAllocateParams1 = _newAllocateParams(operatorSet1, 400); |
| 2049 | + cheats.prank(defaultOperator); |
| 2050 | + allocationManager.modifyAllocations(defaultOperator, newAllocateParams1); |
| 2051 | + |
| 2052 | + // Assert that the deallocationQueue is unordered for the 2 deallocations in queue |
| 2053 | + _checkDeallocationQueueOrder(defaultOperator, defaultStrategies[0], 2); |
| 2054 | + } |
| 2055 | + |
| 2056 | + /** |
| 2057 | + * @notice Regression tests for the bugfix where pending modifications were checked by |
| 2058 | + * require(allocation.pendingDiff == 0, ModificationAlreadyPending()); |
| 2059 | + * which would overwrite the effectBlock, pendingDiff if a pendingDiff |
| 2060 | + * of a deallocation was slashed to become 0. |
| 2061 | + * |
| 2062 | + * This test checks that the deallocationQueue is ascending ordered by effectBlocks |
| 2063 | + * In this case the new modifyAllocations call is an allocation |
| 2064 | + * where the effectBlock is increased and the deallocationQueue is unordered as well because the operator |
| 2065 | + * allocationDelay configured to be long enough. |
| 2066 | + */ |
| 2067 | + function test_modifyAllocations_PendingDiffZero_Allocation() public { |
| 2068 | + // Step 1: Register the operator to multiple operator sets |
| 2069 | + OperatorSet memory operatorSet1 = OperatorSet(defaultAVS, 1); |
| 2070 | + OperatorSet memory operatorSet2 = OperatorSet(defaultAVS, 2); |
| 2071 | + _createOperatorSet(operatorSet1, defaultStrategies); |
| 2072 | + _createOperatorSet(operatorSet2, defaultStrategies); |
| 2073 | + _registerForOperatorSet(defaultOperator, operatorSet1); |
| 2074 | + _registerForOperatorSet(defaultOperator, operatorSet2); |
| 2075 | + |
| 2076 | + // Step 2: Allocate to both operator sets |
| 2077 | + AllocateParams[] memory allocateParams1 = _newAllocateParams(operatorSet1, 1001); |
| 2078 | + AllocateParams[] memory allocateParams2 = _newAllocateParams(operatorSet2, 1000); |
| 2079 | + cheats.prank(defaultOperator); |
| 2080 | + allocationManager.modifyAllocations(defaultOperator, allocateParams1); |
| 2081 | + cheats.prank(defaultOperator); |
| 2082 | + allocationManager.modifyAllocations(defaultOperator, allocateParams2); |
| 2083 | + |
| 2084 | + // Step 3: Update operator allocation delay |
| 2085 | + cheats.prank(defaultOperator); |
| 2086 | + allocationManager.setAllocationDelay(defaultOperator, DEALLOCATION_DELAY + 10 days); |
| 2087 | + cheats.roll(block.number + ALLOCATION_CONFIGURATION_DELAY); |
| 2088 | + |
| 2089 | + // Step 4: Deallocate from both operator sets |
| 2090 | + AllocateParams[] memory deallocateParams1 = _newAllocateParams(operatorSet1, 1000); |
| 2091 | + AllocateParams[] memory deallocateParams2 = _newAllocateParams(operatorSet2, 0); |
| 2092 | + cheats.prank(defaultOperator); |
| 2093 | + allocationManager.modifyAllocations(defaultOperator, deallocateParams1); |
| 2094 | + // roll blocks forward so that deallocations have different effectBlocks |
| 2095 | + cheats.roll(block.number + 100); |
| 2096 | + cheats.prank(defaultOperator); |
| 2097 | + allocationManager.modifyAllocations(defaultOperator, deallocateParams2); |
| 2098 | + |
| 2099 | + // Step 5: Slash the first deallocation to adjust pendingDiff to 0 |
| 2100 | + SlashingParams memory slashingParams1 = SlashingParams({ |
| 2101 | + operator: defaultOperator, |
| 2102 | + operatorSetId: operatorSet1.id, |
| 2103 | + strategies: defaultStrategies, |
| 2104 | + wadsToSlash: 5e17.toArrayU256(), |
| 2105 | + description: "Test slashing" |
| 2106 | + }); |
| 2107 | + cheats.prank(defaultAVS); |
| 2108 | + allocationManager.slashOperator(defaultAVS, slashingParams1); |
| 2109 | + |
| 2110 | + // Step 6: Modify allocations again for operatorSet1 making an allocation and |
| 2111 | + // overwriting/increasing the effectBlock |
| 2112 | + // Note: this should revert but previously it would not prior to the bugfix |
| 2113 | + AllocateParams[] memory newAllocateParams1 = _newAllocateParams(operatorSet1, 5000); |
| 2114 | + cheats.prank(defaultOperator); |
| 2115 | + allocationManager.modifyAllocations(defaultOperator, newAllocateParams1); |
| 2116 | + |
| 2117 | + // Assert that the deallocationQueue is unordered for the 2 deallocations in queue |
| 2118 | + _checkDeallocationQueueOrder(defaultOperator, defaultStrategies[0], 2); |
| 2119 | + } |
| 2120 | + |
1919 | 2121 | function test_revert_allocateZeroMagnitude() public { |
1920 | 2122 | // Allocate exact same magnitude as initial allocation (0) |
1921 | 2123 | AllocateParams[] memory allocateParams = _randAllocateParams_DefaultOpSet(); |
|
0 commit comments