59723 sc high double decrease after exit validator exited leads to underflow and permanent freeze
Submitted on Nov 15th 2025 at 08:50:10 UTC by @yesofcourse for Audit Comp | Vechain | Stargate Hayabusa
Report ID: #59723
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
Brief / Intro
When a delegator requests exit while their validator is ACTIVE, the contract schedules a future decrease to the validator’s delegatorsEffectiveStake trace.
If, before the user calls unstake() or redelegates, the validator later transitions to EXITED, unstake()/_delegate() schedule a second decrease for the same token/validator line.
Because the first decrease already removed this token’s stake at that (or an earlier) checkpoint, the second subtracts from a zero (or too-small) value and triggers a Solidity 0.8 arithmetic underflow (panic 0x11), permanently reverting exit/redelegation.
On mainnet this strands the user’s principal VET.
Vulnerability details (core snippet)
The protocol mutates per-validator total delegators’ effective stake using Checkpoints.Trace224, writing an updated value for a future period:
Two separate call sites schedule decreases:
User-initiated exit (schedules a decrease one period in the future):
Validator EXITED (or PENDING) branch in exit/move flows (schedules another decrease):
Because the first decrease already “removes” the NFT’s effective stake for that future period, the second call often finds:
This is not an owner/admin/multisig scenario: it’s a natural race between (a) the user having requested exit and (b) the validator later becoming EXITED before the user finalizes with unstake()/redelegation.
In sole-delegator cases the underflow is guaranteed; in multi-delegator cases it may still underflow (if current < effectiveStake) or at minimum double-subtract accounting.
Reproduced behavior
Step 5: User calls unstake() / redelegates -> revert
unstake() / redelegates -> revertunstake()(or redelegation) schedules a second decrease foroldCompletedPeriods + 2.Both decreases land on the same checkpoint, so the second subtracts from zero and triggers a Solidity underflow (panic 0x11), reverting the transaction and freezing the position.
Impact details
Direct user loss (Critical — Permanent freezing of funds):
For any affected NFT, unstake() and redelegation permanently revert on the double-decrease path as long as the validator remains EXITED. The user cannot withdraw or move staked VET via the trustless interface.
Blast radius:
Deterministic for sole-delegator validators (second decrease subtracts from zero).
Probabilistic but likely for multi-delegator validators (if aggregate at the second checkpoint < effectiveStake). Even when it doesn’t underflow, accounting is double-subtracted for an already-exited validator.
No user-land recovery: there is no user-accessible method to “unschedule” or compensate the duplicate decrease; only an admin upgrade/migration could unbrick affected NFTs.
Cost to protocol/users: frozen principal VET for each impacted token; potential support load and reputational risk; any downstream logic relying on the trace can be skewed.
Proof of Concept
Add the following file to contracts/test/unit/Stargate/DoubleDecreaseFreeze.test.ts and run with:
npx hardhat test test/unit/Stargate/DoubleDecreaseFreeze.test.ts
from the packages/contracts directory.
The PoC demonstrates that:
requesting exit while ACTIVE schedules a first future decrease at
completedPeriods + 2;if the validator later becomes EXITED,
unstake()schedules a second decrease atoldCompletedPeriods + 2for the same token/validator;both decreases land on the same checkpoint, so
currentValueat that period is already0when the second decrease runs;_updatePeriodEffectiveStakethen computes0 - effectiveStake, triggering a Solidity ≥0.8 underflow panic (0x11);unstake()(and redelegation) consistently reverts, leaving the position non-exitable.
References (code links)
Was this helpful?