Skip to content

Commit

Permalink
Fix: Single Deallocation Queue (#827)
Browse files Browse the repository at this point in the history
* test: base regression

* test: regression

* fix: remove console

* test: logs

* test: add actual regression

* fix: use a single deallocation queue

* fix: comments

* refactor: use deallocation queue everywhere

* fix: address test comments

* fix: test comment
  • Loading branch information
ypatil12 authored Oct 16, 2024
1 parent dc7e929 commit c54fd93
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 67 deletions.
59 changes: 29 additions & 30 deletions src/contracts/core/AllocationManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;

Expand All @@ -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,
Expand All @@ -191,7 +192,7 @@ contract AllocationManager is
}

/// @inheritdoc IAllocationManager
function clearModificationQueue(
function clearDeallocationQueue(
address operator,
IStrategy[] calldata strategies,
uint16[] calldata numToClear
Expand All @@ -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]});
}
}

Expand All @@ -219,30 +220,30 @@ 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;
}

// 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;
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/contracts/core/AllocationManagerStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
10 changes: 5 additions & 5 deletions src/contracts/interfaces/IAllocationManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit c54fd93

Please sign in to comment.