59730 sc high permanent dos users cannot unstake after double exit scenario

Submitted on Nov 15th 2025 at 09:35:36 UTC by @Max36935 for Audit Comp | Vechain | Stargate Hayabusaarrow-up-right

  • Report ID: #59730

  • 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

    • Direct theft of any user funds, whether at-rest or in-motion, other than unclaimed yield

Title

Permanent Denial of Service: Users Cannot Unstake When Both User and Validator Exit Due to Double Effective Stake Decrease


Description

A critical vulnerability exists in the Stargate.sol contract's unstake() function that can permanently lock user funds when a specific sequence of events occurs. The contract attempts to decrease a validator's effective stake twice when both the user requests an exit AND the validator exits, which can cause an arithmetic underflow and revert.

Vulnerability Flow

1

User delegates

User delegates their NFT to a validator β†’ effective stake is increased.

2

User requests exit

User calls requestDelegationExit() β†’ effective stake is decreased (FIRST DECREASE).

3

Validator exits

Validator exits (external event) β†’ validator status becomes VALIDATOR_STATUS_EXITED.

4

User attempts unstake

When the user calls unstake(), the contract tries to decrease effective stake AGAIN (SECOND DECREASE).

5

SafeCast overflow revert

The second decrease causes an arithmetic underflow (0 - stake amount), triggering a SafeCast revert.

6

Funds permanently locked

The revert prevents unstake β€” user funds, withdrawals, and redelegations are blocked permanently until a contract upgrade.

Root Cause

The unstake() function contains a conditional that decreases effective stake when the validator is EXITED but does not verify whether the user already requested an exit (which already decreased the effective stake):

requestDelegationExit() already decreased the effective stake earlier (line 568). The second decrease attempt causes:


Impact

Direct Financial Impact:

  • Users' staked VET can become permanently locked with no recovery mechanism.

  • Users cannot withdraw their principal funds.

  • Users lose access to unclaimed rewards.

  • Affected users cannot redelegate to other validators.

Scale of Impact:

  • Affects any user who requests exit before their validator exits.

  • Validators may exit for maintenance, rotation, or penalties β€” potentially affecting many users.

  • No workaround exists β€” funds are locked until a contract upgrade.

Affected functions:

  • unstake() - can permanently revert.

  • delegate() - cannot redelegate if stuck in EXITED accounting.

  • Users have no way to recover funds except a contract upgrade.

Attack Scenarios

1

Natural occurrence (high probability)

  • User delegates to validator V1.

  • User requests exit to switch validators.

  • Validator exits for scheduled maintenance.

  • User's funds become permanently locked.

2

Griefing attack (medium probability)

  • Attacker delegates and immediately requests exit.

  • When validator exits, attacker's funds are locked.

  • Demonstrates protocol vulnerability publicly.

3

Mass DoS (low probability, extreme impact)

  • Social engineering to get users to request exits.

  • Coordinated validator exit.

  • Hundreds of users may be locked simultaneously.

  • Severe reputational damage.


Code Execution Trace

Step 1: User requests exit

Step 2: Validator exits (external event)

Step 3: User attempts unstake

Step 4: Underflow causes revert


Solution: Check if User Already Requested Exit

Add a check to verify if the user previously requested an exit before attempting to decrease effective stake again.

Fix implementation:

Why this fix works:

  1. endPeriod == type(uint32).max indicates the user never called requestDelegationExit().

  2. When requestDelegationExit() is called, it sets endPeriod to a specific period number.

  3. By checking this condition, the code decreases effective stake only if it has not been decreased already.

  4. This prevents the double decrease that causes the underflow.

Alternative Solution (Defense in Depth)

Add underflow protection directly in _updatePeriodEffectiveStake():

Recommendation: Implement both fixes for defense in depth.


Proof of Concept

chevron-rightClick to expand the full PoC test (TypeScript / Hardhat)hashtag

Save File In: packages/contracts/test/integration/CriticalBug_DoubleDecreaseDoS.test.ts

To run the test:


If you want, I can:

  • Produce a minimal patch/diff for the suggested fixes (both the unstake() fix and the _updatePeriodEffectiveStake() defensive check) in unified-diff format for easier PR creation.

  • Run through the critical code locations to list exact line numbers and function contexts for the changes (based on the target file path you provided).

Was this helpful?