60049 sc high double effective stake decrement locks delegators unstake reverts due to duplicate effectivestake decrements in exit flow

Submitted on Nov 17th 2025 at 21:02:32 UTC by @Johnyfwesh for Audit Comp | Vechain | Stargate Hayabusaarrow-up-right

  • Report ID: #60049

  • 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

    • Permanent freezing of unclaimed yield

    • Griefing (e.g. no profit motive for an attacker, but damage to the users or the protocol)

Description

When an ACTIVE delegation requests exit and later the owner calls unstake(), the contract can decrement the same delegation's effective stake twice: once in requestDelegationExit() and again in unstake() via _updatePeriodEffectiveStake(..., false). Both code paths recompute the NFT's effectiveStake and perform the subtraction without tracking whether the stake was already removed. This can cause a checked-arithmetic underflow (panic 0x11) or corrupt checkpoint accounting.

Finding Description and Impact

  • requestDelegationExit() subtracts the NFT’s effective stake from the validator checkpoints when an ACTIVE delegation starts exiting.

  • unstake() repeats the same _updatePeriodEffectiveStake(..., false) call when the validator turns VALIDATOR_STATUS_EXITED or the delegation is PENDING.

  • Both entry points compute effectiveStake and subtract it using Solidity 0.8 checked arithmetic. There is no per-delegation flag indicating the stake was already removed. A normal lifecycle delegates → requestDelegationExit → unstake thus triggers two decrements against the same checkpoint.

Consequences:

  • If the delegator is the only (or primary) contributor for that validator, the second subtraction can underflow and revert unstake(), permanently preventing withdrawal and locking funds/rewards.

  • If other delegators mask the underflow, the checkpoint becomes under-reported, corrupting reward splits and validator accounting.

  • Attackers can grief validators by forcing exit flows to be requested and causing DoS or requiring manual intervention.

Affected code (examples)

  • contracts/Stargate.sol Lines 231-250 (excerpt from unstake)

  • contracts/Stargate.sol Lines 523-542 (excerpt from requestDelegationExit)

  • contracts/Stargate.sol Lines 993-1012 (excerpt from _updatePeriodEffectiveStake)

  • _updatePeriodEffectiveStake() subtracts without verifying the stake wasn't already removed and without ensuring currentValue >= effectiveStake. A second subtraction can underflow or corrupt the checkpoint.

Impact

  • Funds frozen: Delegators who followed documented exit flow may be unable to call unstake() after requestDelegationExit() once the validator exits, due to underflow revert.

  • Accounting corruption: Even if underflow is avoided, additional subtraction skews delegatorsEffectiveStake, misallocating future rewards.

  • Validator DoS: Attackers can grief validators by forcing exits and blocking withdrawals.

1

Track per-delegation stake removal

Track whether a delegation’s effective stake has already been removed (e.g., a boolean in storage). Skip _updatePeriodEffectiveStake(..., false) in unstake() if the exit path already removed the stake.

2

Centralize checkpoint decrease in requestDelegationExit()

Move the checkpoint decrease exclusively into requestDelegationExit() for the ACTIVE path and ensure unstake() only handles pending delegations that never triggered an exit signal.

3

Add sanity checks

Add sanity checks such as:

  • if (!_isIncrease && currentValue < effectiveStake) revert so similar regressions fail predictably during testing instead of bricking user funds.

Proof of Concept

chevron-rightPoC test: DoubleEffectiveStakeDecrement.test.ts (click to expand)hashtag

PoC demonstrates the issue by:

  1. Staking an NFT and delegating so the validator checkpoint records the effective stake.

  2. Calling requestDelegationExit() while the delegation is ACTIVE, which triggers the first decrement.

  3. Transitioning the validator to VALIDATOR_STATUS_EXITED and calling unstake(), which triggers the second decrement and causes a panic 0x11 revert.

Test file to add to packages/contracts/test/unit/Stargate/DoubleEffectiveStakeDecrement.test.ts:

Run the targeted test:

Expected result:

Was this helpful?