Exploitability: No special permissions required; any NFT owner following normal flows (delegate → request exit → wait → claim) can over-claim.
The private function _claimableDelegationPeriods determines the first and last claimable periods. For delegations that have ended, the intended logic is to clamp the last claimable period to endPeriod. However, the current condition enforces the clamp only when endPeriod > nextClaimablePeriod, thereby missing the equality case.
When endPeriod == nextClaimablePeriod (a common case during exit in the first active period), the condition fails and the logic falls through to the “active” branch:
This incorrectly includes post-exit periods, allowing claimableRewards to pay for future periods that should be excluded.
Faulty Flow
1
User delegates NFT to a validator
User delegates their NFT to a validator as normal.
2
User requests exit during their first active period
This makes endPeriod = current period.
3
Several validator periods complete
_claimableDelegationPeriods mistakenly returns lastClaimable = completedPeriods which extends beyond endPeriod.
4
_claimableRewards aggregates over the incorrect range
This causes overpayment to the user.
Attack Scenario
1
Delegate an NFT to a validator.
2
Request exit during the first active period (very realistic scenario).
3
Wait a few more periods.
4
Call claimRewards or claimableRewards and receive post-exit rewards.
This process can be repeated across many tokens, effectively stealing unclaimed yield and risking protocol reserves (VTHO depletion).
Proof of Concept
The PoC leverages the repository’s existing mocks (ProtocolStakerMock, StargateNFTMock) and deploys Stargate via its proxy, as required. It demonstrates:
Overpaid rewards = 0.15 ETH (0.05 for valid period 2 + 0.10 for invalid period 3)
Place file at: packages/contracts/test/unit/Stargate/POC_ClaimAfterExit.test.ts
Run:
npx hardhat test --network hardhat test/unit/Stargate/POC_ClaimAfterExit.test.ts
Output (observed):
PoC test passes.
lastClaimable = 3 while endPeriod = 2 (range exceeds end).
claimableRewards(tokenIdA) = 0.15 ETH (overpayment confirmed).
Fix
Goal: Clamp claimable range to endPeriod for delegations that have ended.
Suggested patch in contracts/Stargate.sol for _claimableDelegationPeriods:
(Primary change: ensure the ended-delegation branch triggers whenever endPeriod < currentValidatorPeriod — and handle equality where nextClaimablePeriod == endPeriod by returning (nextClaimablePeriod, endPeriod) — avoiding falling through to the active branch.)
Why This Fix Is Sufficient
claimRewards and claimableRewards rely solely on the range returned by _claimableDelegationPeriods.
Clamping ensures post-exit periods are excluded, eliminating overpayment.
The equality case (endPeriod == nextClaimablePeriod) is now properly treated as ended, preventing invalid fallthrough.
The fix is minimal, interface-compatible, and aligns with reward semantics: no rewards after endPeriod.