Skip to content

Commit dadd4b0

Browse files
8sunyuanypatil12
andcommitted
fix: slashable window boundaries (#965)
* fix: slashable window boundaries * test: regression for alm * test: update withdrawal delay not passed reversion * test: burning indices * refactor: switch conditionals * fix: added unit tests * test: assert slashable shares in queue * fix: typos --------- Co-authored-by: Yash Patil <[email protected]>
1 parent 93aeb7d commit dadd4b0

File tree

5 files changed

+595
-50
lines changed

5 files changed

+595
-50
lines changed

src/contracts/core/AllocationManager.sol

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,9 @@ contract AllocationManager is
200200
// the deallocation delay. This magnitude remains slashable until then.
201201
deallocationQueue[operator][strategy].pushBack(operatorSet.key());
202202

203-
allocation.effectBlock = uint32(block.number) + DEALLOCATION_DELAY;
203+
// deallocations are slashable in the window [block.number, block.number + deallocationDelay]
204+
// therefore, the effectBlock is set to the block right after the slashable window
205+
allocation.effectBlock = uint32(block.number) + DEALLOCATION_DELAY + 1;
204206
} else {
205207
// Deallocation immediately updates/frees magnitude if the operator is not slashable
206208
info.encumberedMagnitude = _addInt128(info.encumberedMagnitude, allocation.pendingDiff);
@@ -446,7 +448,9 @@ contract AllocationManager is
446448
function _isOperatorSlashable(address operator, OperatorSet memory operatorSet) internal view returns (bool) {
447449
RegistrationStatus memory status = registrationStatus[operator][operatorSet.key()];
448450

449-
return status.registered || block.number < status.slashableUntil;
451+
// slashableUntil returns the last block the operator is slashable in so we check for
452+
// less than or equal to
453+
return status.registered || block.number <= status.slashableUntil;
450454
}
451455

452456
/// @notice returns whether the operator's allocation is slashable in the given operator set

src/contracts/core/DelegationManager.sol

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,7 @@ contract DelegationManager is
533533
* @dev This function completes a queued withdrawal for a staker.
534534
* This will apply any slashing that has occurred since the the withdrawal was queued by multiplying the withdrawal's
535535
* scaledShares by the operator's maxMagnitude for each strategy. This ensures that any slashing that has occurred
536-
* during the period the withdrawal was queued until its completable timestamp is applied to the withdrawal amount.
536+
* during the period the withdrawal was queued until its slashableUntil block is applied to the withdrawal amount.
537537
* If receiveAsTokens is true, then these shares will be withdrawn as tokens.
538538
* If receiveAsTokens is false, then they will be redeposited according to the current operator the staker is delegated to,
539539
* and added back to the operator's delegatedShares.
@@ -550,16 +550,18 @@ contract DelegationManager is
550550

551551
uint256[] memory prevSlashingFactors;
552552
{
553-
uint32 completableBlock = withdrawal.startBlock + MIN_WITHDRAWAL_DELAY_BLOCKS;
554-
require(completableBlock <= uint32(block.number), WithdrawalDelayNotElapsed());
553+
// slashableUntil is block inclusive so we need to check if the current block is strictly greater than the slashableUntil block
554+
// meaning the withdrawal can be completed.
555+
uint32 slashableUntil = withdrawal.startBlock + MIN_WITHDRAWAL_DELAY_BLOCKS;
556+
require(slashableUntil < uint32(block.number), WithdrawalDelayNotElapsed());
555557

556558
// Given the max magnitudes of the operator the staker was originally delegated to, calculate
557559
// the slashing factors for each of the withdrawal's strategies.
558560
prevSlashingFactors = _getSlashingFactorsAtBlock({
559561
staker: withdrawal.staker,
560562
operator: withdrawal.delegatedTo,
561563
strategies: withdrawal.strategies,
562-
blockNumber: completableBlock
564+
blockNumber: slashableUntil
563565
});
564566
}
565567

@@ -761,9 +763,12 @@ contract DelegationManager is
761763
) internal view returns (uint256) {
762764
// Fetch the cumulative scaled shares sitting in the withdrawal queue both now and before
763765
// the withdrawal delay.
766+
// NOTE: We want all the shares in the window [block.number - MIN_WITHDRAWAL_DELAY_BLOCKS, block.number]
767+
// as this is all slashable and since prevCumulativeScaledShares is being subtracted from curCumulativeScaledShares
768+
// we do a -1 on the block number to also include (block.number - MIN_WITHDRAWAL_DELAY_BLOCKS) as slashable.
764769
uint256 curCumulativeScaledShares = _cumulativeScaledSharesHistory[operator][strategy].latest();
765770
uint256 prevCumulativeScaledShares = _cumulativeScaledSharesHistory[operator][strategy].upperLookup({
766-
key: uint32(block.number) - MIN_WITHDRAWAL_DELAY_BLOCKS
771+
key: uint32(block.number) - MIN_WITHDRAWAL_DELAY_BLOCKS - 1
767772
});
768773

769774
// The difference between these values represents the number of scaled shares that entered the
@@ -937,7 +942,24 @@ contract DelegationManager is
937942
withdrawals[i] = queuedWithdrawals[withdrawalRoots[i]];
938943
shares[i] = new uint256[](withdrawals[i].strategies.length);
939944

940-
uint256[] memory slashingFactors = _getSlashingFactors(staker, operator, withdrawals[i].strategies);
945+
uint32 slashableUntil = withdrawals[i].startBlock + MIN_WITHDRAWAL_DELAY_BLOCKS;
946+
947+
uint256[] memory slashingFactors;
948+
// If slashableUntil block is in the past, read the slashing factors at that block
949+
// Otherwise read the current slashing factors. Note that if the slashableUntil block is the current block
950+
// or in the future then the slashing factors are still subject to change before the withdrawal is completable
951+
// and the shares withdrawn to be less
952+
if (slashableUntil < uint32(block.number)) {
953+
slashingFactors = _getSlashingFactorsAtBlock({
954+
staker: staker,
955+
operator: operator,
956+
strategies: withdrawals[i].strategies,
957+
blockNumber: slashableUntil
958+
});
959+
} else {
960+
slashingFactors =
961+
_getSlashingFactors({staker: staker, operator: operator, strategies: withdrawals[i].strategies});
962+
}
941963

942964
for (uint256 j; j < withdrawals[i].strategies.length; ++j) {
943965
shares[i][j] = SlashingLib.scaleForCompleteWithdrawal({

src/test/integration/IntegrationBase.t.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,7 +1317,7 @@ abstract contract IntegrationBase is IntegrationDeployer {
13171317
for (uint i = 0; i < withdrawals.length; ++i) {
13181318
if (withdrawals[i].startBlock > latest) latest = withdrawals[i].startBlock;
13191319
}
1320-
cheats.roll(latest + delegationManager.minWithdrawalDelayBlocks());
1320+
cheats.roll(latest + delegationManager.minWithdrawalDelayBlocks() + 1);
13211321
}
13221322

13231323
/// @dev Rolls forward by the default allocation delay blocks.
@@ -1328,7 +1328,7 @@ abstract contract IntegrationBase is IntegrationDeployer {
13281328

13291329
/// @dev Rolls forward by the default deallocation delay blocks.
13301330
function _rollBlocksForCompleteDeallocation() internal {
1331-
cheats.roll(block.number + allocationManager.DEALLOCATION_DELAY());
1331+
cheats.roll(block.number + allocationManager.DEALLOCATION_DELAY() + 1);
13321332
}
13331333

13341334
/// @dev Uses timewarp modifier to get the operator set strategy allocations at the last snapshot.

0 commit comments

Comments
 (0)