Status DataClose notification

VeChain Disclosed Report

Permanent Fund Lock via Double Stake Deduction when Unstaking from Exited Validator

Company
Created date
Nov 27 2025

Target

hidden

Vulnerability Details

Description The Stargate contract fails to properly handle the state transition where a user has already requested a delegation exit (requestDelegationExit), but the target validator subsequently transitions to an EXITED state before the user calls unstake. The Logic Flaw: When a user calls requestDelegationExit(), the contract decreases the user's effective stake from the validator's accounting for the next period. When the user later calls unstake(), the contract checks the validator's status. If the validator status is VALIDATOR_STATUS_EXITED, the unstake function unconditionally attempts to decrease the effective stake again.

if (
    currentValidatorStatus == VALIDATOR_STATUS_EXITED || // <--- Trigger condition
    delegation.status == DelegationStatus.PENDING
) {
    // ... logic to get period ...

    // CRITICAL: Attempts to decrease stake a second time
    _updatePeriodEffectiveStake(..., false); 
}

Since the stake was already subtracted during the initial requestDelegationExit, the Checkpoints library (using uint224) attempts to subtract the amount from a value that has already been reduced. If the remaining stake is 0 (or less than the user's stake), this causes an Arithmetic Underflow, reverting the transaction.

Impact Permanent Lock of End-User Funds. Users who are unfortunate enough to have their delegated validator exit (or get banned) after they have initiated an exit request will be unable to unstake their VET. Every attempt to retrieve their funds will revert due to the underflow error.

Recommendation Modify the logic in unstake to ensure that effective stake is not deducted again if the user has already requested an exit.

// In Stargate.sol -> unstake function

// ...
bool hasAlreadyRequestedExit = delegation.endPeriod != type(uint32).max;

if (
    (currentValidatorStatus == VALIDATOR_STATUS_EXITED || delegation.status == DelegationStatus.PENDING) &&
    !hasAlreadyRequestedExit // FIX: Only deduct if exit wasn't already processed
) {
    // ... existing logic to decrease effective stake ...
     _updatePeriodEffectiveStake(..., false);
}
//

Validation steps

Validation steps

  • I have created a reproduction script using Hardhat that simulates the exact conditions:
  • User stakes and delegates.
  • User requests exit (First deduction happens here).
  • Validator status changes to EXITED.
  • User tries to unstake.
  • Transaction reverts.

Reproduction Steps

  • Create MockStargateDependencies.sol file with the mock capable of simulating validator status changes.
  • Create StargateDoubleDedup.test.ts test file.
  • compile npx hardhat compile
  • Run npx hardhat test test/StargateDoubleDedup.test.ts

Attachments

hidden
CommentsReport History
Comments on this report are hidden
Details
Statedisclosed
Severity
Critical
Bounty$612
Visibilitypartially
VulnerabilityBlockchain
Participants
hidden