60466 sc medium maxclaimableperiodsexceeded lock zero reward backlog permanently locks nfts
Submitted on Nov 23rd 2025 at 01:02:20 UTC by @arunabha003 for Audit Comp | Vechain | Stargate Hayabusa
Report ID: #60466
Report Type: Smart Contract
Report severity: Medium
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
The Stargate contract contains a logic flaw in _claimRewards() that permanently locks NFTs when validators produce many periods with zero rewards. The function returns early at line 759 when claimableAmount == 0 without updating lastClaimedPeriod, preventing the period backlog from being reduced. When the number of claimable periods exceeds maxClaimablePeriods (default 832), the guard check in unstake() and delegate() reverts with MaxClaimablePeriodsExceeded, making it impossible to unstake or redelegate the NFT and access the staked VET. As a result, the user cannot unstake and retrieve their staked VET, which constitutes permanent freezing of funds.
Vulnerability Details
The vulnerability exists in the reward claiming and period tracking logic across multiple functions in Stargate.sol:
Location 1: _claimRewards() - Lines 757-760
function _claimRewards(StargateStorage storage $, uint256 _tokenId) private {
(uint32 firstClaimablePeriod, uint32 lastClaimablePeriod) = _claimableDelegationPeriods($, _tokenId);
// ... max period check ...
uint256 claimableAmount = _claimableRewards($, _tokenId, 0);
if (claimableAmount == 0) {
return; // Early return without updating lastClaimedPeriod
}
$.lastClaimedPeriod[_tokenId] = lastClaimablePeriod; // Only reached when claimableAmount > 0
// ... transfer and emit ...
}Location 2: _exceedsMaxClaimablePeriods() - Lines 956-973
Location 3: unstake() - Lines 299-302
Location 4: _delegate() - Lines 431-435
Exploit Path:
User delegates NFT to validator
Validator completes many periods (>
maxClaimablePeriods) without producing rewardsValidator is offline or inactive
Testnet/devnet without reward distribution configured
Validator misconfiguration
User calls
claimRewards(tokenId)_claimableRewards()returns 0_claimRewards()returns early at line 759lastClaimedPeriod[tokenId]not updated
Period backlog continues growing:
lastClaimablePeriod - firstClaimablePeriod > maxClaimablePeriodsUser calls
unstake(tokenId)_exceedsMaxClaimablePeriods()returns trueTransaction reverts with
MaxClaimablePeriodsExceeded
User attempts to redelegate via
delegate(tokenId, newValidator)_exceedsMaxClaimablePeriods()returns trueTransaction reverts with
MaxClaimablePeriodsExceeded
NFT permanently locked with no recovery mechanism
Time makes the problem worse: each new period increases the backlog, and calling claimRewards() has no effect since it continues returning early without updating lastClaimedPeriod.
Impact Details
Users whose delegated validators go inactive for extended periods lose permanent access to their staked VET. The NFT becomes permanently locked once the period backlog exceeds maxClaimablePeriods. No admin function exists to force-unstake, manually update lastClaimedPeriod, or bypass the guard check. No time-based unlock exists. The funds remain locked indefinitely.
This scenario is realistic in several contexts:
Validators going offline during network issues or maintenance
Testnet/devnet deployments where reward distribution is not configured
Early mainnet deployment before reward mechanisms are fully operational
Validator misconfigurations that prevent reward distribution
Financial impact scales with the number of affected delegators and their stake amounts. VeChain Stargate NFT levels range from 600,000 VET (Level 1) to 25,000,000 VET (Level 7). If multiple validators become inactive, hundreds or thousands of delegators could be affected, with total locked value potentially reaching hundreds of millions of VET.
This constitutes permanent freezing of funds per Immunefi's Critical impact definition: "user is no longer able to withdraw their funds" with no recovery mechanism.
Proof of Concept
Proof of Concept
Test File: packages/contracts/test/integration/MaxClaimablePeriodsLockPOC.test.ts
Running the POC
Actual Test Output
Complete POC Code
File: packages/contracts/test/integration/MaxClaimablePeriodsLockPOC.test.ts
Was this helpful?