59802 sc high double subtraction of validator effective stake will permanently lock other delegators staked vet
Submitted on Nov 16th 2025 at 00:10:54 UTC by @xKeywordx for Audit Comp | Vechain | Stargate Hayabusa
Report ID: #59802
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
Description
The Stargate contract maintains a per-validator checkpointed value of delegators’ effective stake in this mapping:
mapping(address validator => Checkpoints.Trace224 amount) delegatorsEffectiveStake;This is updated via _updatePeriodEffectiveStake whenever delegations are added, moved, exited, or unstaked.
There is a sequence of interaction between the Stargate::requestDelegationExit, Stargate::unstake and Stargate::getDelegationStatus functions, which can cause the delegatorsEffectiveStake of a validator to be subtracted twice, corrupting the delegatorsEffectiveStake accounting and leading to a permanent freezing of funds.
The following stepper explains how the bug can occur:
Step
Bob calls unstake. Because the validator exited in the meantime, unstake will reduce the validator's delegatorsEffectiveStake again. This is wrong because Bob's amount was already subtracted when he called requestDelegationExit. Excerpt from the code:
function unstake(uint256 _tokenId) external whenNotPaused onlyTokenOwner(_tokenId) nonReentrant {
// ..
// if the delegation is pending or the validator is exited or unknown
// decrease the effective stake of the previous validator
if (currentValidatorStatus == VALIDATOR_STATUS_EXITED || delegation.status == DelegationStatus.PENDING) {
// get the completed periods of the previous validator
(, , , uint32 oldCompletedPeriods) = $.protocolStakerContract.getValidationPeriodDetails(delegation.validator);
// decrease the effective stake of the previous validator
_updatePeriodEffectiveStake(
$,
delegation.validator,
_tokenId,
oldCompletedPeriods + 2,
false // decrease
);
}
// ..
}Root cause
Double subtraction of delegators’ effective stake for the same delegation, combined with a global (per-validator) aggregation.
Impact
Critical – Permanent freezing of funds
At some point, when other delegators later attempt to unstake, _updatePeriodEffectiveStake(..., false) runs with currentValue == 0 and effectiveStake > 0, causing a checked arithmetic underflow and revert.
In other words, a user with a staked NFT will reach a state where they have a valid delegation that should be exited, but any call to unstake(...) reverts with a panic 0x11, because delegatorsEffectiveStake[validator] has been driven to 0 by other delegators’ double subtraction.
Recommended mitigation
Ensure that each delegation’s effective stake is added and removed exactly once.
Proof of Concept
Add this test in the following file Stake.test.ts and run it:
Test output:
As shown by the test, two users stake at the same level and delegate to the same validator. If the validator exits between requestDelegationExit and unstake called by one of the users, the validator's delegatorsEffectiveStake can be decreased twice, causing other delegators' funds to become permanently stuck.
Was this helpful?