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.
Vulnerability Details
The vulnerability 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:
Calls signalDelegationExit() to mark the exit (line 555)
then decreases the effective stake
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:
Withdraws the delegation
Gets the validator status
Decreases effective stake again if validator is EXITED
The _updatePeriodEffectiveStake() function contains the following snippet, which will underflow and revert due to currentValue < effectiveStake
Impact Details
The revert above effectively causes a DoS and locks the user's VET in the contract permanently, additionally the issue also breaks the Rewards calculation mechanism, causing future delegators to this validator receive inflated rewards, with time causing protocol insolvency and delegators losing their yield too
Modify the line "test:thor-solo" in packages/contracts to "test:thor-solo": "hardhat test --network vechain_solo --grep \"double decrease\"", , to allow runnng of only the poc test
Then paste the following test in the Delegation.test.ts