Skip to content

Commit 52c1dfb

Browse files
committed
test: regression tests showing invalid state
1 parent bd2453c commit 52c1dfb

File tree

2 files changed

+239
-5
lines changed

2 files changed

+239
-5
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.27;
3+
4+
import "../../contracts/core/AllocationManager.sol";
5+
6+
contract AllocationManagerHarness is AllocationManager {
7+
using DoubleEndedQueue for DoubleEndedQueue.Bytes32Deque;
8+
9+
constructor(
10+
IDelegationManager _delegation,
11+
IPauserRegistry _pauserRegistry,
12+
IPermissionController _permissionController,
13+
uint32 _DEALLOCATION_DELAY,
14+
uint32 _ALLOCATION_CONFIGURATION_DELAY
15+
)
16+
AllocationManager(
17+
_delegation,
18+
_pauserRegistry,
19+
_permissionController,
20+
_DEALLOCATION_DELAY,
21+
_ALLOCATION_CONFIGURATION_DELAY
22+
)
23+
{}
24+
25+
function deallocationQueueAtIndex(
26+
address operator,
27+
IStrategy strategy,
28+
uint256 index
29+
) external view returns (bytes32) {
30+
return deallocationQueue[operator][strategy].at(index);
31+
}
32+
}

src/test/unit/AllocationManagerUnit.t.sol

Lines changed: 207 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// SPDX-License-Identifier: BUSL-1.1
22
pragma solidity ^0.8.27;
33

4-
import "src/contracts/core/AllocationManager.sol";
4+
import "src/test/harnesses/AllocationManagerHarness.sol";
55
import "src/test/utils/EigenLayerUnitTestSetup.sol";
66
import "src/test/mocks/MockAVSRegistrar.sol";
77

@@ -33,7 +33,7 @@ contract AllocationManagerUnitTests is EigenLayerUnitTestSetup, IAllocationManag
3333
/// Mocks
3434
/// -----------------------------------------------------------------------
3535

36-
AllocationManager allocationManager;
36+
AllocationManagerHarness allocationManager;
3737
ERC20PresetFixedSupply tokenMock;
3838
StrategyBase strategyMock;
3939

@@ -86,12 +86,12 @@ contract AllocationManagerUnitTests is EigenLayerUnitTestSetup, IAllocationManag
8686
address _initialOwner,
8787
IPauserRegistry _pauserRegistry,
8888
uint256 _initialPausedStatus
89-
) internal returns (AllocationManager) {
90-
return allocationManager = AllocationManager(
89+
) internal returns (AllocationManagerHarness) {
90+
return allocationManager = AllocationManagerHarness(
9191
address(
9292
new TransparentUpgradeableProxy(
9393
address(
94-
new AllocationManager(
94+
new AllocationManagerHarness(
9595
IDelegationManager(address(delegationManagerMock)),
9696
_pauserRegistry,
9797
IPermissionController(address(permissionController)),
@@ -259,6 +259,23 @@ contract AllocationManagerUnitTests is EigenLayerUnitTestSetup, IAllocationManag
259259
console.log("Success!".green().bold());
260260
}
261261

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+
262279
function _checkSlashableStake(
263280
OperatorSet memory operatorSet,
264281
address operator,
@@ -1916,6 +1933,191 @@ contract AllocationManagerUnitTests_ModifyAllocations is AllocationManagerUnitTe
19161933
allocationManager.modifyAllocations(defaultOperator, allocateParams);
19171934
}
19181935

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+
19192121
function test_revert_allocateZeroMagnitude() public {
19202122
// Allocate exact same magnitude as initial allocation (0)
19212123
AllocateParams[] memory allocateParams = _randAllocateParams_DefaultOpSet();

0 commit comments

Comments
 (0)