Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: slashable window boundaries #965

Merged
merged 8 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/contracts/core/AllocationManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,9 @@ contract AllocationManager is
// the deallocation delay. This magnitude remains slashable until then.
deallocationQueue[operator][strategy].pushBack(operatorSet.key());

allocation.effectBlock = uint32(block.number) + DEALLOCATION_DELAY;
// deallocations are slashable in the window [block.number, block.number + deallocationDelay]
// therefore, the effectBlock is set to the block right after the slashable window
allocation.effectBlock = uint32(block.number) + DEALLOCATION_DELAY + 1;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: every comparison to effectBlock is < effectBlock. There is some minor difference in naming (and resulting comparisons) because we do:

  • < effectBlock
  • <= slashableUntil

Nomenclature does make sense here though

ypatil12 marked this conversation as resolved.
Show resolved Hide resolved
} else {
// Deallocation immediately updates/frees magnitude if the operator is not slashable
info.encumberedMagnitude = _addInt128(info.encumberedMagnitude, allocation.pendingDiff);
Expand Down Expand Up @@ -446,7 +448,9 @@ contract AllocationManager is
function _isOperatorSlashable(address operator, OperatorSet memory operatorSet) internal view returns (bool) {
RegistrationStatus memory status = registrationStatus[operator][operatorSet.key()];

return status.registered || block.number < status.slashableUntil;
// slashableUntil returns the last block the operator is slashable in so we check for
// than or equal to
8sunyuan marked this conversation as resolved.
Show resolved Hide resolved
return status.registered || block.number <= status.slashableUntil;
gpsanant marked this conversation as resolved.
Show resolved Hide resolved
}

/// @notice returns whether the operator's allocation is slashable in the given operator set
Expand Down
34 changes: 28 additions & 6 deletions src/contracts/core/DelegationManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,7 @@ contract DelegationManager is
* @dev This function completes a queued withdrawal for a staker.
* This will apply any slashing that has occurred since the the withdrawal was queued by multiplying the withdrawal's
* scaledShares by the operator's maxMagnitude for each strategy. This ensures that any slashing that has occurred
* during the period the withdrawal was queued until its completable timestamp is applied to the withdrawal amount.
* during the period the withdrawal was queued until its slashableUntil block is applied to the withdrawal amount.
* If receiveAsTokens is true, then these shares will be withdrawn as tokens.
* If receiveAsTokens is false, then they will be redeposited according to the current operator the staker is delegated to,
* and added back to the operator's delegatedShares.
Expand All @@ -550,16 +550,18 @@ contract DelegationManager is

uint256[] memory prevSlashingFactors;
{
uint32 completableBlock = withdrawal.startBlock + MIN_WITHDRAWAL_DELAY_BLOCKS;
require(completableBlock <= uint32(block.number), WithdrawalDelayNotElapsed());
// slashableUntil is block inclusive so we need to check if the current block is strictly greater than the slashableUntil block
// meaning the withdrawal can be completed.
uint32 slashableUntil = withdrawal.startBlock + MIN_WITHDRAWAL_DELAY_BLOCKS;
require(slashableUntil < uint32(block.number), WithdrawalDelayNotElapsed());
Copy link
Collaborator

@ypatil12 ypatil12 Dec 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_Revert_WhenWithdrawalDelayNotPassed validates this

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


// Given the max magnitudes of the operator the staker was originally delegated to, calculate
// the slashing factors for each of the withdrawal's strategies.
prevSlashingFactors = _getSlashingFactorsAtBlock({
staker: withdrawal.staker,
operator: withdrawal.delegatedTo,
strategies: withdrawal.strategies,
blockNumber: completableBlock
blockNumber: slashableUntil
});
}

Expand Down Expand Up @@ -761,9 +763,12 @@ contract DelegationManager is
) internal view returns (uint256) {
// Fetch the cumulative scaled shares sitting in the withdrawal queue both now and before
// the withdrawal delay.
// NOTE: We want all the shares in the window [block.number - MIN_WITHDRAWAL_DELAY_BLOCKS, block.number]
// as this is all slashable and since prevprevCumulativeScaledShares is being subtracted from curCumulativeScaledShares
8sunyuan marked this conversation as resolved.
Show resolved Hide resolved
// we do a -1 on the block number to also include (block.number - MIN_WITHDRAWAL_DELAY_BLOCKS) as slashable.
uint256 curCumulativeScaledShares = _cumulativeScaledSharesHistory[operator][strategy].latest();
uint256 prevCumulativeScaledShares = _cumulativeScaledSharesHistory[operator][strategy].upperLookup({
key: uint32(block.number) - MIN_WITHDRAWAL_DELAY_BLOCKS
key: uint32(block.number) - MIN_WITHDRAWAL_DELAY_BLOCKS - 1
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Burning test: 8d380fa

});

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

uint256[] memory slashingFactors = _getSlashingFactors(staker, operator, withdrawals[i].strategies);
uint32 slashableUntil = withdrawals[i].startBlock + MIN_WITHDRAWAL_DELAY_BLOCKS;

uint256[] memory slashingFactors;
// If slashableUntil block is in the past, read the slashing factors at that block
// Otherwise read the current slashing factors. Note that if the slashableUntil block is the current block
// or in the future then the slashing factors are still subject to change before the withdrawal is completable
// and the shares withdrawn to be less
if (slashableUntil < uint32(block.number)) {
slashingFactors = _getSlashingFactorsAtBlock({
staker: staker,
operator: operator,
strategies: withdrawals[i].strategies,
blockNumber: slashableUntil
});
} else {
slashingFactors =
_getSlashingFactors({staker: staker, operator: operator, strategies: withdrawals[i].strategies});
}

for (uint256 j; j < withdrawals[i].strategies.length; ++j) {
shares[i][j] = SlashingLib.scaleForCompleteWithdrawal({
Expand Down
4 changes: 2 additions & 2 deletions src/test/integration/IntegrationBase.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1317,7 +1317,7 @@ abstract contract IntegrationBase is IntegrationDeployer {
for (uint i = 0; i < withdrawals.length; ++i) {
if (withdrawals[i].startBlock > latest) latest = withdrawals[i].startBlock;
}
cheats.roll(latest + delegationManager.minWithdrawalDelayBlocks());
cheats.roll(latest + delegationManager.minWithdrawalDelayBlocks() + 1);
}

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

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

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