60575 sc high double subtraction of delegator effective stake on exit can freeze vet and break reward distribution
Submitted on Nov 24th 2025 at 07:12:15 UTC by @unineko for Audit Comp | Vechain | Stargate Hayabusa
Report ID: #60575
Report Type: Smart Contract
Report severity: High
Target: https://github.com/immunefi-team/audit-comp-vechain-stargate-hayabusa/tree/main/packages/contracts/contracts/Stargate.sol
Impacts:
Permanent freezing of funds
Contract fails to deliver promised returns, but doesn't lose value
Permanent freezing of unclaimed yield
Description
Brief/Intro
When a delegator requests to exit and later unstakes after the validator has exited, Stargate decreases that delegator's effective stake twice for the same period range. The first decrease happens in requestDelegationExit, and a second decrease happens in unstake (and similarly in some re-delegation flows), without checking whether an exit was already requested. In the single-delegator case this can cause an underflow and revert, permanently blocking unstake and freezing the staked VET. In the multi-delegator case it silently double-subtracts the exiting delegator's stake from the per-period totals, causing remaining delegators to receive no rewards for those periods. If no upgrade or manual rescue mechanism is available, this effectively results in a permanent freezing of the user's staked VET for affected validators.
Vulnerability Details
1. One-Time Decrease on requestDelegationExit
When a user requests to exit an active delegation, Stargate decreases that token's effective stake starting from a future period so that it stops participating in rewards:
Conceptually:
Before the call,
delegatorsEffectiveStake[validator][period]includes this delegator's effective stakerequestDelegationExitcalls_updatePeriodEffectiveStakewith_isIncrease = false, subtracting that stake from the delegator totals used to compute rewardsAt this point, the accounting correctly reflects that the delegator will not earn rewards from that future period onward
2. Second Decrease on Unstake After Validator Exit
Later, once the validator has exited and the user calls unstake, Stargate's unstake implementation performs another decrease of the same delegator's effective stake based only on validator status and delegation status, without checking whether requestDelegationExit already ran:
Key issues:
The condition uses only
currentValidatorStatusanddelegation.statusIt does not check whether an exit was already requested (for example via
endPeriod != type(uint32).maxor a dedicatedhasRequestedExitflag)In the common flow where the user first calls
requestDelegationExitand later unstake after the validator has exited, the same token's effective stake is decreased twice for overlapping or identical period ranges
3. Decrease Implementation Can Underflow
The decrease logic is centralized in _updatePeriodEffectiveStake:
Solidity 0.8.x reverts on underflow. Therefore:
If
currentValuealready excludes this delegator's stake (because it was previously subtracted inrequestDelegationExit), andcurrentValue < effectiveStake,then
currentValue - effectiveStakeunderflows and the entire transaction reverts
4. Concrete Scenarios
Case A: Single Delegator on a Validator (Underflow and Freeze)
Validator V has a single delegator A with effective stake 100
For the future period
P = completedPeriods + 2,delegatorsEffectiveStake[V][P] = 100A calls
requestDelegationExit(tokenId):_updatePeriodEffectiveStakeis invoked with_isIncrease = falsecurrentValue = 100,effectiveStake = 100New value =
100 - 100 = 0Stored value:
delegatorsEffectiveStake[V][P] = 0
The validator eventually exits and its status becomes
VALIDATOR_STATUS_EXITEDA calls
unstake(tokenId):The if condition in unstake is satisfied (
currentValidatorStatus == EXITED)_updatePeriodEffectiveStakeis called again for the same_validatorand period rangecurrentValue = delegatorsEffectiveStake[V][P] = 0,effectiveStake = 100New value attempts to compute
0 - 100, which underflows and reverts
Result:
unstakereverts before the VET refundThere is no alternative unstake path that skips this second decrease
A's staked VET is effectively frozen until a contract upgrade or manual intervention
No special attacker privileges are required; this is triggered by the normal sequence "request exit → validator exit → unstake"
Case B: Multiple Delegators (Silent Reward Mis-Accounting)
Now assume three delegators on V:
A with effective stake 100 (eventually exits)
B with effective stake 50
C with effective stake 50
Total effective stake prior to A's exit is 200
A calls
requestDelegationExit:delegatorsEffectiveStake[V][P]goes from 200 to 100 (only B + C)This is correct so far
Later, when V is EXITED, A calls
unstake:currentValueat_updatePeriodEffectiveStaketime is 100 (the stake of B + C)effectiveStakefor A is still 100New value becomes
100 - 100 = 0delegatorsEffectiveStake[V][P]is now 0
From this point onward, reward calculation for period P that relies on delegatorsEffectiveStake will treat the total effective stake as 0, even though B and C are still active and should have a combined stake of 100. Depending on how division-by-zero or zero-total cases are handled, this can:
Completely remove B and C from future reward allocations for that period range, or
Force a special case that results in no rewards distributed to delegators
Effectively, the protocol underpays or never pays promised rewards to remaining delegators for those periods, even though no funds are directly stolen.
Impact Details
Chosen Impacts
1. Permanent Freezing of Funds
In the single-delegator scenario (Case A):
The delegator follows the intended flow: delegate, request exit, wait for validator exit, then call unstake
Because the effective stake has already been subtracted once, the second subtraction in unstake underflows and reverts
The transaction fails before any VET is refunded and there is no alternative unstake path that avoids this logic
If no upgrade or administrative workaround is available, this amounts to effectively permanent freezing of the delegator's staked VET. Every subsequent attempt to unstake under the same conditions will revert for the same reason
2. Contract Fails to Deliver Promised Returns / Permanent Freezing of Unclaimed Yield
In the multi-delegator scenario (Case B):
The exiting delegator's effective stake is subtracted twice from the validator's per-period delegator totals
The second subtraction removes not only the exiting delegator's contribution but also the remaining delegators' contribution
As a result, the protocol's accounting may consider the total effective stake for that period to be zero, even though other delegators are still active
Depending on the exact reward calculation, this can:
Cause remaining delegators to receive no yield for those periods
Permanently lose their claim to rewards that conceptually should be theirs (unclaimed yield is effectively frozen or never generated at the accounting level)
This is not a direct theft of principal, but it is a failure to deliver promised rewards to honest delegators and a permanent loss of expected yield.
Attacker Model and Likelihood
No special role or privileged access is required
The issue is triggered by normal usage patterns:
Delegator requests exit
Validator later exits
Delegator calls unstake
Single-delegator validators or small-validator sets make the underflow scenario especially likely
For larger validator sets, mis-accounting of rewards for remaining delegators is possible without causing a revert, degrading reward correctness over time
References
Contract References
contracts/Stargate.sol
requestDelegationExit(uint256 _tokenId)(Line 586) - first decrease of effective stake via_updatePeriodEffectiveStake(..., false)usingcompletedPeriods + 2unstake(uint256 _tokenId)(Lines 266-288) - second decrease of effective stake whencurrentValidatorStatus == EXITEDordelegation.status == PENDING, without checking whether exit was already requested_updatePeriodEffectiveStake(...)(Lines 1026-1046) - performscurrentValue - effectiveStakeondelegatorsEffectiveStakeand relies on Solidity 0.8 underflow checks, which revert on negative results
These functions together implement a state machine where a delegator's effective stake can be decreased twice for the same period range, leading to either a reverting unstake (funds freeze) or incorrect reward totals (permanent loss of yield) depending on validator composition.
Proof of Concept
npx hardhat test --network hardhat test/unit/PoC/001_C1_PoC.test.ts --show-stack-traces
Was this helpful?