An issue in the requestDelegationExit() and unstake() functions allows the effective stake of a validator to be decreased twice for the same delegation. When a user with an ACTIVE delegation calls requestDelegationExit() and the validator subsequently exits, calling unstake() will either cause an underflow revert (DoS) or corrupt the validator's accounting. This results in users being permanently unable to unstake their tokens and retrieve their staked VET, leading to complete loss of funds.
The issue stems from the fact that both requestDelegationExit() and unstake() call _updatePeriodEffectiveStake() with _isIncrease = false under certain conditions, without proper guards to prevent double-decreasing.
In the requestDelegationExit() function, when a delegation has ACTIVE status, the function:
signalDelegationExit() to mark the exit (line 555)_updatePeriodEffectiveStake($, delegation.validator, _tokenId, completedPeriods + 2, false);
The delegationId mapping is NOT cleared in this case, meaning the token still has a valid delegation record.
In the unstake() function, when unstaking a token with EXITED status and an exited validator:
if (
currentValidatorStatus == VALIDATOR_STATUS_EXITED ||
delegation.status == DelegationStatus.PENDING
) {
_updatePeriodEffectiveStake(
$,
delegation.validator,
_tokenId,
oldCompletedPeriods + 2,
false // decrease
);
}
The _updatePeriodEffectiveStake() function contains the following snippet, which will underflow and revert due to currentValue < effectiveStake
uint256 updatedValue = _isIncrease
? currentValue + effectiveStake
: currentValue - effectiveStake;
The revert above effectively causes a DoS and locks the user's VET in the contract permanently
I have supplied a PoC below