Status DataClose notification

VeChain Disclosed Report

there is a problem Double-decrease in _updatePeriodEffectiveStake permanently locks staked VET after exit

Company
Created date
Nov 21 2025

Target

hidden

Vulnerability Details

Brief to explain the issue

the issue in the contract is came from that the contract can subtract the same NFT’s “effective stake” twice from the validator’s delegators total, in two different paths. so When a user first requests an exit and later tries to unstake after the validator has exited, this is double-decrease causes an underflow in the _updatePeriodEffectiveStake, and this is making the unstake() and any path that calls the same logic revert forever. so this is not correct beacause this is leaves the VET backing that NFT is stuck in the contract until fix the issue check the test is how theissue

Vulnerability Details

so the bug is exist here because the same NFT’s effective stake is decreased twice along a normal user flow, and is usie two different places in the code. First, when the user calls requestDelegationExit while the delegation is still ACTIVE, then the contract correctly signals the exit and immediately reduces the delegators’ effective stake for a future period by calling here where this is happen :

(, , , uint32 completedPeriods) =
    $.protocolStakerContract.getValidationPeriodDetails(delegation.validator);

_updatePeriodEffectiveStake(
    $,
    delegation.validator,
    _tokenId,
    completedPeriods + 2,
    false // << first decrease
);

Later, if the validator status changes to VALIDATOR_STATUS_EXITED and the user calls unstake, the contract again tries to reduce the same NFT’s effective stake for the same future period and this is happen here :

(, , , , uint8 currentValidatorStatus, ) =
    $.protocolStakerContract.getValidation(delegation.validator);

if (
    currentValidatorStatus == VALIDATOR_STATUS_EXITED ||
    delegation.status == DelegationStatus.PENDING
) {
    (, , , uint32 oldCompletedPeriods) =
        $.protocolStakerContract.getValidationPeriodDetails(delegation.validator);

    _updatePeriodEffectiveStake(
        $,
        delegation.validator,
        _tokenId,
        oldCompletedPeriods + 2,
        false // << second decrease
    );
}

and here in the _updatePeriodEffectiveStake, :

uint256 effectiveStake = _calculateEffectiveStake($, _tokenId);
uint256 currentValue = $.delegatorsEffectiveStake[_validator].upperLookup(_period);

uint256 updatedValue = _isIncrease
    ? currentValue + effectiveStake
    : currentValue - effectiveStake; // << here is vulnerable when currentValue == 0

$.delegatorsEffectiveStake[_validator].push(_period, SafeCast.toUint224(updatedValue));

After the first decrease, the currentValue at that period is already 0, but effectiveStake is still > 0 (the NFT still has staked VET and a reward factor), so on the second decrease currentValue - effectiveStake underflows and reverts and this is mean the unstake() never reaches the part where it burns the NFT and returns the user’s VET.

Impact Details

If a user hits this double-decrease path, their position is gone bricked, even though they’re the legit NFT owner and they did everything “correct”, every call to unstake(_tokenId) will always revert because it keeps hitting _updatePeriodEffectiveStake(..., false) with currentValue = 0 and effectiveStake > 0, and this is cause an underflow. this is means their VET is stuck behind the NFT forever they can’t turn the NFT back into VET, they can’t meaningfully move the position, and they can’t re-delegate somewhere else because the same broken accounting is still in the way.

References

  • https://github.com/immunefi-team/audit-comp-vechain-stargate-hayabusa/blob/e9c0bc9b0f24dc0c44de273181d9a99aaf2c31b0/packages/contracts/contracts/Stargate.sol#L274C1-L283C10
  • https://github.com/immunefi-team/audit-comp-vechain-stargate-hayabusa/blob/e9c0bc9b0f24dc0c44de273181d9a99aaf2c31b0/packages/contracts/contracts/Stargate.sol#L406C15-L414C14
  • https://github.com/immunefi-team/audit-comp-vechain-stargate-hayabusa/blob/e9c0bc9b0f24dc0c44de273181d9a99aaf2c31b0/packages/contracts/contracts/Stargate.sol#L1007C1-L1009C45

Validation steps

cehck the poc is show the issue and run it use the yarn hardhat test --network hardhat test/unit/Stargate/DoubleDecreaseBug.test.ts the resuklt are pass :

Critical bug: double-decrease in _updatePeriodEffectiveStake causes permanent fund freeze
    ✔ proves double-decrease bug: unstake reverts after requestExit + validator EXITED (74ms)


  1 passing (733ms)

Done in 3.48s.

Attachments

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