For the complete documentation index, see llms.txt. This page is also available as Markdown.

59809 sc high user balances are permanently frozen in specific delegation scenarios

Submitted on Nov 16th 2025 at 02:59:50 UTC by @hrmneffdii for Audit Comp | Vechain | Stargate Hayabusa

  • Report ID: #59809

  • 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

This report details a critical vulnerability discovered in the Stargate contract's delegation logic. A flaw in the _delegate function can cause a validator's delegatorsEffectiveStake to be improperly reduced twice for the same user.

This "double-decrease" bug desynchronizes the validator's internal stake accounting, leading to an arithmetic underflow when another user attempts to interact with their own stake. As a result, other delegators on the same validator will have their funds permanently frozen, as all management functions (e.g., delegate, requestDelegationExit, unstake) will fail.

Vulnerability details

1

Path: requestDelegationExit()

When a user (e.g., "Bob" from the PoC) calls requestDelegationExit (Step 5 in PoC), the function correctly calls _updatePeriodEffectiveStake(..., isIncrease: false). This reduces the validator's EffectiveStake to reflect the user's exit. This is the expected behavior.

2

Path: _delegate() (during re-delegation)

Later, the validator exits. If the user then calls _delegate to move their stake to a new validator, the logic in _delegate can also reduce the original validator's EffectiveStake again:

  • _getDelegationStatus returns DelegationStatus.EXITED for the token.

  • This triggers the conditional if (status == DelegationStatus.EXITED || status == DelegationStatus.PENDING) which withdraws the user's VET via withdrawDelegation.

  • Later, a second condition if (currentValidatorStatus == VALIDATOR_STATUS_EXITED || status == DelegationStatus.PENDING) is true and the code calls _updatePeriodEffectiveStake(..., isIncrease: false) a second time.

The code does not account for the stake already having been subtracted during requestDelegationExit, causing a double-decrease of the same user's stake from the validator's EffectiveStake.

Destructive scenario (PoC summary)

1

A validator has EffectiveStake = 3e18 (Alice 1.5e18 + Bob 1.5e18).

2

Bob calls requestDelegationExit. Validator EffectiveStake becomes 1.5e18 (3e18 - 1.5e18). (Correct)

3

Validator exits. Bob calls delegate to a new validator. Due to the bug, the code reduces the validator's EffectiveStake again, resulting in EffectiveStake = 0 (1.5e18 - 1.5e18). (Incorrect)

4

Alice (still delegated to the exited validator with 1.5e18) attempts to call delegate to move her funds.

5

The _delegate logic for Alice attempts _updatePeriodEffectiveStake(..., isIncrease: false):

  • currentValue = 0 (incorrect)

  • effectiveStake (Alice) = 1.5e18

  • updatedValue = currentValue - effectiveStake -> 0 - 1.5e18 This causes an arithmetic underflow (Solidity 0.8.x+), and Alice's transaction reverts.

6

The same underflow occurs for Alice if she calls requestDelegationExit or other management operations; her funds become effectively frozen with no in-contract fix available.

Proof of Concept

How to run

Run the following test: yarn contracts:test:unit:verbose -- -- --grep "User balances are permanently frozen in specific delegation scenarios"

Test Case (excerpt)

Logs

Was this helpful?