diff --git a/src/contracts/core/AllocationManager.sol b/src/contracts/core/AllocationManager.sol index 311f4fe9b..6758a3e6b 100644 --- a/src/contracts/core/AllocationManager.sol +++ b/src/contracts/core/AllocationManager.sol @@ -137,8 +137,8 @@ contract AllocationManager is require(allocation.operatorSets.length == allocation.magnitudes.length, InputArrayLengthMismatch()); require(avsDirectory.isOperatorSetBatch(allocation.operatorSets), InvalidOperatorSet()); - // 1. For the given (operator,strategy) complete any pending modifications to free up encumberedMagnitude - _clearModificationQueue({operator: msg.sender, strategy: allocation.strategy, numToClear: type(uint16).max}); + // 1. For the given (operator,strategy) complete any pending deallocation to free up encumberedMagnitude + _clearDeallocationQueue({operator: msg.sender, strategy: allocation.strategy, numToClear: type(uint16).max}); // 2. Check current totalMagnitude matches expected value. This is to check for slashing race conditions // where an operator gets slashed from an operatorSet and as a result all the configured allocations have larger @@ -160,6 +160,9 @@ contract AllocationManager is // Calculate the effectTimestamp for the modification if (info.pendingDiff < 0) { info.effectTimestamp = uint32(block.timestamp) + DEALLOCATION_DELAY; + + // Add the operatorSet to the deallocation queue + deallocationQueue[msg.sender][allocation.strategy].pushBack(operatorSetKey); } else if (info.pendingDiff > 0) { info.effectTimestamp = uint32(block.timestamp) + operatorAllocationDelay; @@ -169,9 +172,7 @@ contract AllocationManager is require(info.encumberedMagnitude <= maxMagnitude, InsufficientAllocatableMagnitude()); } - // Add the operatorSet to the modification queue and update the allocation - // in storage - modificationQueue[msg.sender][allocation.strategy].pushBack(operatorSetKey); + // Update the modification in storage _updateMagnitudeInfo({ operator: msg.sender, strategy: allocation.strategy, @@ -191,7 +192,7 @@ contract AllocationManager is } /// @inheritdoc IAllocationManager - function clearModificationQueue( + function clearDeallocationQueue( address operator, IStrategy[] calldata strategies, uint16[] calldata numToClear @@ -200,7 +201,7 @@ contract AllocationManager is require(delegation.isOperator(operator), OperatorNotRegistered()); for (uint256 i = 0; i < strategies.length; ++i) { - _clearModificationQueue({operator: operator, strategy: strategies[i], numToClear: numToClear[i]}); + _clearDeallocationQueue({operator: operator, strategy: strategies[i], numToClear: numToClear[i]}); } } @@ -219,21 +220,21 @@ contract AllocationManager is } /** - * @dev Clear one or more pending modifications to a strategy's allocated magnitude - * @param operator the operator whose pending modifications will be cleared + * @dev Clear one or more pending deallocations to a strategy's allocated magnitude + * @param operator the operator whose pending deallocations will be cleared * @param strategy the strategy to update - * @param numToClear the number of pending modifications to complete + * @param numToClear the number of pending deallocations to complete */ - function _clearModificationQueue(address operator, IStrategy strategy, uint16 numToClear) internal { + function _clearDeallocationQueue(address operator, IStrategy strategy, uint16 numToClear) internal { uint256 numCompleted; - uint256 length = modificationQueue[operator][strategy].length(); + uint256 length = deallocationQueue[operator][strategy].length(); while (length > 0 && numCompleted < numToClear) { - bytes32 operatorSetKey = modificationQueue[operator][strategy].front(); + bytes32 operatorSetKey = deallocationQueue[operator][strategy].front(); PendingMagnitudeInfo memory info = _getPendingMagnitudeInfo(operator, strategy, operatorSetKey); - // If we've reached a pending modification that isn't completable yet, - // we can stop. Any subsequent modificaitons will also be uncompletable. + // If we've reached a pending deallocation that isn't completable yet, + // we can stop. Any subsequent deallocation will also be uncompletable. if (block.timestamp < info.effectTimestamp) { break; } @@ -241,8 +242,8 @@ contract AllocationManager is // Update the operator's allocation in storage _updateMagnitudeInfo(operator, strategy, operatorSetKey, info); - // Remove the modification from the queue - modificationQueue[operator][strategy].popFront(); + // Remove the deallocation from the queue + deallocationQueue[operator][strategy].popFront(); ++numCompleted; --length; } @@ -272,8 +273,8 @@ contract AllocationManager is /** * @dev For an operator set, get the operator's effective allocated magnitude. - * If the operator set has a pending modification that can be completed at the - * current timestamp, this method returns a view of the allocation as if the modification + * If the operator set has a pending deallocation that can be completed at the + * current timestamp, this method returns a view of the allocation as if the deallocation * was completed. * @return info the effective allocated and pending magnitude for the operator set, and * the effective encumbered magnitude for all operator sets belonging to this strategy @@ -417,29 +418,27 @@ contract AllocationManager is /// @inheritdoc IAllocationManager function getAllocatableMagnitude(address operator, IStrategy strategy) external view returns (uint64) { - // This method needs to simulate clearing any pending allocation modifications. - // This roughly mimics the calculations done in `_clearModificationQueue` and + // This method needs to simulate clearing any pending deallocations. + // This roughly mimics the calculations done in `_clearDeallocationQueue` and // `_getPendingMagnitudeInfo`, while operating on a `curEncumberedMagnitude` // rather than continually reading/updating state. uint64 curEncumberedMagnitude = encumberedMagnitude[operator][strategy]; - uint256 length = modificationQueue[operator][strategy].length(); + uint256 length = deallocationQueue[operator][strategy].length(); for (uint256 i = 0; i < length; ++i) { - bytes32 operatorSetKey = modificationQueue[operator][strategy].at(i); + bytes32 operatorSetKey = deallocationQueue[operator][strategy].at(i); MagnitudeInfo memory info = _operatorMagnitudeInfo[operator][strategy][operatorSetKey]; - // If we've reached a pending modification that isn't completable yet, + // If we've reached a pending deallocation that isn't completable yet, // we can stop. Any subsequent modificaitons will also be uncompletable. if (block.timestamp < info.effectTimestamp) { break; } - // If the diff is a deallocation, add to encumbered magnitude. Allocations - // do not need to be considered, because encumbered magnitude is updated as - // soon as the allocation is created. - if (info.pendingDiff < 0) { - curEncumberedMagnitude = _addInt128(curEncumberedMagnitude, info.pendingDiff); - } + // The diff is a deallocation. Add to encumbered magnitude. Note that this is a deallocation + // queue and allocations aren't considered because encumbered magnitude + // is updated as soon as the allocation is created. + curEncumberedMagnitude = _addInt128(curEncumberedMagnitude, info.pendingDiff); } // The difference between the operator's max magnitude and its encumbered magnitude diff --git a/src/contracts/core/AllocationManagerStorage.sol b/src/contracts/core/AllocationManagerStorage.sol index 9d8bdc180..bedbb8768 100644 --- a/src/contracts/core/AllocationManagerStorage.sol +++ b/src/contracts/core/AllocationManagerStorage.sol @@ -51,8 +51,8 @@ abstract contract AllocationManagerStorage is IAllocationManager { /// @notice Mapping: operator => strategy => operatorSet (encoded) => MagnitudeInfo mapping(address => mapping(IStrategy => mapping(bytes32 => MagnitudeInfo))) internal _operatorMagnitudeInfo; - /// @notice Mapping: operator => strategy => operatorSet[] (encoded) to keep track of pending modifications - mapping(address => mapping(IStrategy => DoubleEndedQueue.Bytes32Deque)) internal modificationQueue; + /// @notice Mapping: operator => strategy => operatorSet[] (encoded) to keep track of pending deallocations + mapping(address => mapping(IStrategy => DoubleEndedQueue.Bytes32Deque)) internal deallocationQueue; /// @notice Mapping: operator => allocation delay (in seconds) for the operator. /// This determines how long it takes for allocations to take effect in the future. diff --git a/src/contracts/interfaces/IAllocationManager.sol b/src/contracts/interfaces/IAllocationManager.sol index 3ff914532..f5a3109ca 100644 --- a/src/contracts/interfaces/IAllocationManager.sol +++ b/src/contracts/interfaces/IAllocationManager.sol @@ -161,16 +161,16 @@ interface IAllocationManager is ISignatureUtils, IAllocationManagerErrors, IAllo ) external; /** - * @notice This function takes a list of strategies and adds all completable modifications for each strategy, + * @notice This function takes a list of strategies and adds all completable deallocations for each strategy, * updating the encumberedMagnitude of the operator as needed. * - * @param operator address to complete modifications for - * @param strategies a list of strategies to complete modifications for - * @param numToComplete a list of number of pending modifications to complete for each strategy + * @param operator address to complete deallocations for + * @param strategies a list of strategies to complete deallocations for + * @param numToComplete a list of number of pending deallocations to complete for each strategy * * @dev can be called permissionlessly by anyone */ - function clearModificationQueue( + function clearDeallocationQueue( address operator, IStrategy[] calldata strategies, uint16[] calldata numToComplete diff --git a/src/test/unit/AllocationManagerUnit.t.sol b/src/test/unit/AllocationManagerUnit.t.sol index 473a02a3c..67651fe1e 100644 --- a/src/test/unit/AllocationManagerUnit.t.sol +++ b/src/test/unit/AllocationManagerUnit.t.sol @@ -833,7 +833,7 @@ contract AllocationManagerUnitTests_SlashOperator is AllocationManagerUnitTests // Check storage after complete modification cheats.warp(deallocationEffectTimestamp); - allocationManager.clearModificationQueue(defaultOperator, _strategyMockArray(), _maxNumToClear()); + allocationManager.clearDeallocationQueue(defaultOperator, _strategyMockArray(), _maxNumToClear()); mInfos = allocationManager.getAllocationInfo(defaultOperator, strategyMock, allocations[0].operatorSets); assertEq(magnitudeAfterDeallocationSlash, mInfos[0].currentMagnitude, "currentMagnitude not updated"); assertEq(magnitudeAfterDeallocationSlash, maxMagnitudeAfterSlash / 2, "magnitude after deallocation should be half of max magnitude, since we originally deallocated by half"); @@ -929,8 +929,8 @@ contract AllocationManagerUnitTests_SlashOperator is AllocationManagerUnitTests /** * Slashes the operator after deallocation, even if the deallocation has not been cleared. Validates that: - * 1. Even if we do not clear modification queue, the deallocation is NOT slashed from since we're passed the deallocationEffectTimestamp - * 2. Validates storage post slash & post clearing modification queue + * 1. Even if we do not clear deallocation queue, the deallocation is NOT slashed from since we're passed the deallocationEffectTimestamp + * 2. Validates storage post slash & post clearing deallocation queue * 3. Total magnitude only decreased proportionally by the magnitude set after deallocation */ function test_allocate_deallocate_slashAfterDeallocation() public { @@ -995,7 +995,7 @@ contract AllocationManagerUnitTests_SlashOperator is AllocationManagerUnitTests // Check storage after complete modification. Expect encumberedMag to be emitted again cheats.expectEmit(true, true, true, true, address(allocationManager)); emit EncumberedMagnitudeUpdated(defaultOperator, strategyMock, expectedEncumberedMagnitude); - allocationManager.clearModificationQueue(defaultOperator, _strategyMockArray(), _maxNumToClear()); + allocationManager.clearDeallocationQueue(defaultOperator, _strategyMockArray(), _maxNumToClear()); mInfos = allocationManager.getAllocationInfo(defaultOperator, strategyMock, allocations[0].operatorSets); assertEq(allocatableMagnitudeAfterSlash, allocationManager.getAllocatableMagnitude(defaultOperator, strategyMock), "allocatable mag after slash shoudl be equal to allocatable mag after clearing queue"); } @@ -1213,10 +1213,10 @@ contract AllocationManagerUnitTests_SlashOperator is AllocationManagerUnitTests assertEq(-int128(uint128(pendingDiff - pendingDiff/2)), mInfos[0].pendingDiff, "pendingDiff should be -secondMod"); assertEq(deallocationEffectTimestamp, mInfos[0].effectTimestamp, "effectTimestamp should be deallocationEffectTimestamp"); - // Warp to deallocation effect timestamp & clear modification queue + // Warp to deallocation effect timestamp & clear deallocation queue console.log("encumbered mag before: ", allocationManager.encumberedMagnitude(defaultOperator, strategyMock)); cheats.warp(deallocationEffectTimestamp); - allocationManager.clearModificationQueue(defaultOperator, _strategyMockArray(), _maxNumToClear()); + allocationManager.clearDeallocationQueue(defaultOperator, _strategyMockArray(), _maxNumToClear()); console.log("encumbered mag after: ", allocationManager.encumberedMagnitude(defaultOperator, strategyMock)); // Check expected max and allocatable @@ -1569,8 +1569,8 @@ contract AllocationManagerUnitTests_ModifyAllocations is AllocationManagerUnitTe * Allocates to `firstMod` magnitude and then deallocate to `secondMod` magnitude * Validates the storage * - 1. After deallocation is alled - * - 2. After the deallocationd elay is hit - * - 3. After the modification queue is cleared + * - 2. After the deallocationd delay is hit + * - 3. After the deallocation queue is cleared */ function testFuzz_allocate_deallocate(uint256 r) public { // Bound allocation and deallocation @@ -1633,12 +1633,12 @@ contract AllocationManagerUnitTests_ModifyAllocations is AllocationManagerUnitTe "encumberedMagnitude should not be updated" ); - // Check storage after clearing modification queue + // Check storage after clearing deallocation queue IStrategy[] memory strategies = new IStrategy[](1); strategies[0] = strategyMock; uint16[] memory numToClear = new uint16[](1); numToClear[0] = 1; - allocationManager.clearModificationQueue(defaultOperator, strategies, numToClear); + allocationManager.clearDeallocationQueue(defaultOperator, strategies, numToClear); assertEq( secondMod, allocationManager.encumberedMagnitude(defaultOperator, strategyMock), @@ -1661,13 +1661,13 @@ contract AllocationManagerUnitTests_ModifyAllocations is AllocationManagerUnitTe cheats.prank(defaultOperator); allocationManager.modifyAllocations(allocations); - // Warp to completion and clear modification queue + // Warp to completion and clear deallocation queue cheats.warp(block.timestamp + DEALLOCATION_DELAY); IStrategy[] memory strategies = new IStrategy[](1); strategies[0] = strategyMock; uint16[] memory numToClear = new uint16[](1); numToClear[0] = 1; - allocationManager.clearModificationQueue(defaultOperator, strategies, numToClear); + allocationManager.clearDeallocationQueue(defaultOperator, strategies, numToClear); // Check storage assertEq( @@ -1745,14 +1745,14 @@ contract AllocationManagerUnitTests_ModifyAllocations is AllocationManagerUnitTe assertEq(0, mInfos[i].effectTimestamp, "effectTimestamp not updated"); } - // Clear modification queue + // Clear deallocation queue IStrategy[] memory strategies = new IStrategy[](1); strategies[0] = strategyMock; uint16[] memory numToClear = new uint16[](1); numToClear[0] = numOpSets; - allocationManager.clearModificationQueue(defaultOperator, strategies, numToClear); + allocationManager.clearDeallocationQueue(defaultOperator, strategies, numToClear); - // Check storage after clearing modification queue + // Check storage after clearing deallocation queue assertEq( postDeallocMag, allocationManager.encumberedMagnitude(defaultOperator, strategyMock), @@ -1761,7 +1761,7 @@ contract AllocationManagerUnitTests_ModifyAllocations is AllocationManagerUnitTe } } -contract AllocationManagerUnitTests_ClearModificationQueue is AllocationManagerUnitTests { +contract AllocationManagerUnitTests_ClearDeallocationQueue is AllocationManagerUnitTests { /// ----------------------------------------------------------------------- /// clearModificationQueue() /// ----------------------------------------------------------------------- @@ -1769,7 +1769,7 @@ contract AllocationManagerUnitTests_ClearModificationQueue is AllocationManagerU function test_revert_paused() public { allocationManager.pause(2 ** PAUSED_MODIFY_ALLOCATIONS); cheats.expectRevert(IPausable.CurrentlyPaused.selector); - allocationManager.clearModificationQueue(defaultOperator, new IStrategy[](0), new uint16[](0)); + allocationManager.clearDeallocationQueue(defaultOperator, new IStrategy[](0), new uint16[](0)); } function test_revert_arrayMismatch() public { @@ -1777,7 +1777,7 @@ contract AllocationManagerUnitTests_ClearModificationQueue is AllocationManagerU uint16[] memory numToClear = new uint16[](2); cheats.expectRevert(IAllocationManagerErrors.InputArrayLengthMismatch.selector); - allocationManager.clearModificationQueue(defaultOperator, strategies, numToClear); + allocationManager.clearDeallocationQueue(defaultOperator, strategies, numToClear); } function test_revert_operatorNotRegistered() public { @@ -1785,13 +1785,13 @@ contract AllocationManagerUnitTests_ClearModificationQueue is AllocationManagerU delegationManagerMock.setIsOperator(defaultOperator, false); cheats.expectRevert(IAllocationManagerErrors.OperatorNotRegistered.selector); - allocationManager.clearModificationQueue(defaultOperator, new IStrategy[](0), new uint16[](0)); + allocationManager.clearDeallocationQueue(defaultOperator, new IStrategy[](0), new uint16[](0)); } /** * @notice Allocates magnitude to an operator and then - * - Clears modification queue when nothing can be completed - * - Clears modification queue when the alloc can be completed - asserts emit has been emitted + * - Clears deallocation queue when only an allocation exists + * - Clears deallocation queue when the alloc can be completed - asserts emit has been emitted * - Validates storage after the second clear */ function testFuzz_allocate( @@ -1802,17 +1802,15 @@ contract AllocationManagerUnitTests_ClearModificationQueue is AllocationManagerU _queueRandomAllocation_singleStrat_singleOpSet(defaultOperator, r, 0); // Attempt to clear queue, assert no events emitted - allocationManager.clearModificationQueue(defaultOperator, _strategyMockArray(), _maxNumToClear()); + allocationManager.clearDeallocationQueue(defaultOperator, _strategyMockArray(), _maxNumToClear()); Vm.Log[] memory entries = vm.getRecordedLogs(); assertEq(0, entries.length, "should not have emitted any events"); // Warp to allocation complete timestamp cheats.warp(block.timestamp + DEFAULT_OPERATOR_ALLOCATION_DELAY); - // Clear queue - cheats.expectEmit(true, true, true, true, address(allocationManager)); - emit EncumberedMagnitudeUpdated(defaultOperator, strategyMock, allocations[0].magnitudes[0]); - allocationManager.clearModificationQueue(defaultOperator, _strategyMockArray(), _maxNumToClear()); + // Clear queue - this is a noop + allocationManager.clearDeallocationQueue(defaultOperator, _strategyMockArray(), _maxNumToClear()); // Validate storage (although this is technically tested in allocation tests, adding for sanity) // TODO: maybe add a harness here to actually introspect storage @@ -1825,9 +1823,9 @@ contract AllocationManagerUnitTests_ClearModificationQueue is AllocationManagerU /** * @notice Allocates magnitude to an operator and then - * - Clears modification queue when nothing can be completed + * - Clears deallocation queue when nothing can be completed * - After the first clear, asserts the allocation info takes into account the deallocation - * - Clears modification queue when the dealloc can be completed + * - Clears deallocation queue when the dealloc can be completed * - Assert events & validates storage after the deallocations are completed */ function testFuzz_allocate_deallocate(uint256 r) public { @@ -1841,7 +1839,7 @@ contract AllocationManagerUnitTests_ClearModificationQueue is AllocationManagerU ); // Clear queue & check storage - allocationManager.clearModificationQueue(defaultOperator, _strategyMockArray(), _maxNumToClear()); + allocationManager.clearDeallocationQueue(defaultOperator, _strategyMockArray(), _maxNumToClear()); assertEq( allocations[0].magnitudes[0], allocationManager.encumberedMagnitude(defaultOperator, strategyMock), @@ -1862,7 +1860,7 @@ contract AllocationManagerUnitTests_ClearModificationQueue is AllocationManagerU // Clear queue cheats.expectEmit(true, true, true, true, address(allocationManager)); emit EncumberedMagnitudeUpdated(defaultOperator, strategyMock, deallocations[0].magnitudes[0]); - allocationManager.clearModificationQueue(defaultOperator, _strategyMockArray(), _maxNumToClear()); + allocationManager.clearDeallocationQueue(defaultOperator, _strategyMockArray(), _maxNumToClear()); // Validate storage - encumbered magnitude should just be deallocations (we only have 1 deallocation) assertEq( @@ -1875,6 +1873,121 @@ contract AllocationManagerUnitTests_ClearModificationQueue is AllocationManagerU assertEq(0, mInfos[0].pendingDiff, "pendingMagnitude should be 0"); assertEq(0, mInfos[0].effectTimestamp, "effectTimestamp should be 0"); } + + /** + * Allocates, deallocates, and then allocates again. Asserts that + * - The deallocation does not block state updates from the second allocation, even though the allocation has an earlier + * effect timestamp + */ + function test_allocate_deallocate_allocate() public { + uint32 allocationDelay = 15 days; + // Set allocation delay to be 15 days + cheats.prank(defaultOperator); + allocationManager.setAllocationDelay(allocationDelay); + cheats.warp(block.timestamp + ALLOCATION_CONFIGURATION_DELAY); + (,uint32 storedDelay) = allocationManager.getAllocationDelay(defaultOperator); + assertEq(allocationDelay, storedDelay, "allocation delay not valid"); + + // Allocate half of mag to opset1 + IAllocationManagerTypes.MagnitudeAllocation[] memory firstAllocation = + _generateMagnitudeAllocationCalldataForOpSet(defaultAVS, 1, 5e17, 1e18); + cheats.prank(defaultOperator); + allocationManager.modifyAllocations(firstAllocation); + cheats.warp(block.timestamp + 15 days); + + // Deallocate half from opset1. + uint32 deallocationEffectTimestamp = uint32(block.timestamp + DEALLOCATION_DELAY); + IAllocationManagerTypes.MagnitudeAllocation[] memory firstDeallocation = + _generateMagnitudeAllocationCalldataForOpSet(defaultAVS, 1, 25e16, 1e18); + cheats.prank(defaultOperator); + allocationManager.modifyAllocations(firstDeallocation); + MagnitudeInfo[] memory mInfos = allocationManager.getAllocationInfo(defaultOperator, strategyMock, firstDeallocation[0].operatorSets); + assertEq(deallocationEffectTimestamp, mInfos[0].effectTimestamp, "effect timestamp not correct"); + + // Allocate 33e16 mag to opset2 + uint32 allocationEffectTimestamp = uint32(block.timestamp + allocationDelay); + IAllocationManagerTypes.MagnitudeAllocation[] memory secondAllocation = + _generateMagnitudeAllocationCalldataForOpSet(defaultAVS, 2, 33e16, 1e18); + cheats.prank(defaultOperator); + allocationManager.modifyAllocations(secondAllocation); + mInfos = allocationManager.getAllocationInfo(defaultOperator, strategyMock, secondAllocation[0].operatorSets); + console.log("deallocation effect timestamp: ", deallocationEffectTimestamp); + console.log("allocation effect timestamp: ", allocationEffectTimestamp); + assertEq(allocationEffectTimestamp, mInfos[0].effectTimestamp, "effect timestamp not correct"); + assertLt(allocationEffectTimestamp, deallocationEffectTimestamp, "invalid test setup"); + + // Warp to allocation effect timestamp & clear the queue + cheats.warp(allocationEffectTimestamp); + allocationManager.clearDeallocationQueue(defaultOperator, _strategyMockArray(), _maxNumToClear()); + + // Validate `getAllocatableMagnitude`. Allocatable magnitude should be the difference between the total magnitude and the encumbered magnitude + uint64 allocatableMagnitude = allocationManager.getAllocatableMagnitude(defaultOperator, strategyMock); + assertEq(WAD - 33e16 - 5e17, allocatableMagnitude, "allocatableMagnitude not correct"); + + // Validate that we can allocate again for opset2. This should not revert + IAllocationManagerTypes.MagnitudeAllocation[] memory thirdAllocation = + _generateMagnitudeAllocationCalldataForOpSet(defaultAVS, 2, 10e16, 1e18); + cheats.prank(defaultOperator); + allocationManager.modifyAllocations(thirdAllocation); + } + + /** + * Allocates to opset1, allocates to opset2, deallocates from opset1. Asserts that the allocation, which has a higher + * effect timestamp is not blocking the deallocation. + * The allocs/deallocs looks like + * 1. (allocation, opSet2, mag: 5e17, effectTimestamp: 50th day) + * 2. (deallocation, opSet1, mag: 0, effectTimestamp: 42.5 day) + * + * The deallocation queue looks like + * 1. (deallocation, opSet1, mag: 0, effectTimestamp: 42.5 day) + */ + function test_regression_deallocationNotBlocked() public { + uint32 allocationDelay = 25 days; + // Set allocation delay to be 25 days, greater than the deallocation timestamp + cheats.prank(defaultOperator); + allocationManager.setAllocationDelay(allocationDelay); + cheats.warp(block.timestamp + ALLOCATION_CONFIGURATION_DELAY); + (,uint32 storedDelay) = allocationManager.getAllocationDelay(defaultOperator); + assertEq(allocationDelay, storedDelay, "allocation delay not valid"); + + // Allocate half of mag to opset1 + IAllocationManagerTypes.MagnitudeAllocation[] memory firstAllocation = + _generateMagnitudeAllocationCalldataForOpSet(defaultAVS, 1, 5e17, 1e18); + cheats.prank(defaultOperator); + allocationManager.modifyAllocations(firstAllocation); + cheats.warp(block.timestamp + 25 days); + + // Allocate half of mag to opset2 + IAllocationManagerTypes.MagnitudeAllocation[] memory secondAllocation = + _generateMagnitudeAllocationCalldataForOpSet(defaultAVS, 2, 5e17, 1e18); + cheats.prank(defaultOperator); + allocationManager.modifyAllocations(secondAllocation); + + uint32 allocationEffectTimestamp = uint32(block.timestamp + allocationDelay); + MagnitudeInfo[] memory mInfos = allocationManager.getAllocationInfo(defaultOperator, strategyMock, secondAllocation[0].operatorSets); + assertEq(allocationEffectTimestamp, mInfos[0].effectTimestamp, "effect timestamp not correct"); + + // Deallocate all from opSet1 + uint32 deallocationEffectTimestamp = uint32(block.timestamp + DEALLOCATION_DELAY); + IAllocationManagerTypes.MagnitudeAllocation[] memory firstDeallocation = + _generateMagnitudeAllocationCalldataForOpSet(defaultAVS, 1, 0, 1e18); + cheats.prank(defaultOperator); + allocationManager.modifyAllocations(firstDeallocation); + mInfos = allocationManager.getAllocationInfo(defaultOperator, strategyMock, firstDeallocation[0].operatorSets); + assertEq(deallocationEffectTimestamp, mInfos[0].effectTimestamp, "effect timestamp not correct"); + assertLt(deallocationEffectTimestamp, allocationEffectTimestamp, "invalid test setup"); + + // Warp to deallocation effect timestamp & clear the queue + cheats.warp(deallocationEffectTimestamp); + allocationManager.clearDeallocationQueue(defaultOperator, _strategyMockArray(), _maxNumToClear()); + + // At this point, we should be able to allocate again to opSet1 AND have only 5e17 encumbered magnitude + assertEq(5e17, allocationManager.encumberedMagnitude(defaultOperator, strategyMock), "encumbered magnitude not correct"); + IAllocationManagerTypes.MagnitudeAllocation[] memory thirdAllocation = + _generateMagnitudeAllocationCalldataForOpSet(defaultAVS, 1, 5e17, 1e18); + cheats.prank(defaultOperator); + allocationManager.modifyAllocations(thirdAllocation); + } } contract AllocationManagerUnitTests_SetAllocationDelay is AllocationManagerUnitTests {