60081 sc high exited delegator can continue to accrue and claim delegation rewards
Submitted on Nov 18th 2025 at 10:05:04 UTC by @Diavol0 for Audit Comp | Vechain | Stargate Hayabusa
Report ID: #60081
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:
Theft of unclaimed yield
Description
In the Hayabusa Stargate staking protocol, each delegator’s staking position is represented by an NFT. When a user exits delegation, their NFT should stop accruing delegation rewards after the exit period, and further rewards should be distributed only to remaining active delegators.
Due to an off‑by‑one logic bug in the delegation rewards window calculation, an exited delegator’s NFT continues to have its claimable rewards increase over time as validator periods complete, as long as there are still other active delegators on that validator. This effectively allows the exited delegator to keep “participating” in future reward periods and to siphon a portion of the yield that should have belonged to active delegators.
This behaviour fits Immunefi’s “Theft of unclaimed yield” category: the attacker does not directly steal at‑rest principal, but steals yield that should accrue to other users. From the victims’ perspective, the protocol fails to deliver the promised share of rewards proportional to their stake.
Root cause
The bug is located in the reward window calculation logic:
File:
packages/contracts/contracts/Stargate.solFunction:
_claimableDelegationPeriods(private view)
This function returns the pair (firstClaimablePeriod, lastClaimablePeriod) for a given NFT (_tokenId). It is used both by the read‑only claimableDelegationPeriods / claimableRewards helpers and by the state‑changing claimRewards function.
Relevant excerpt (simplified):
Two key points:
When a delegation exit is signalled in the
ProtocolStakermock, itsendPeriodis set tocompletedPeriods + 1(i.e. first period after the last completed period).The condition that attempts to handle “ended delegations” uses
endPeriod > nextClaimablePeriodinstead of>=:
This creates a corner case when the delegator has claimed rewards such that nextClaimablePeriod == endPeriod. In that situation:
The “ended delegation” branch is not taken, because
endPeriod > nextClaimablePeriodfails whenendPeriod == nextClaimablePeriod.The code falls through to the “active/pending” branch, which returns
(nextClaimablePeriod, completedPeriods). In other words, the NFT is treated as if the delegation were still active up to the latestcompletedPeriodsreported by the validator, even though the delegation has logically ended.
As the validator continues to complete more periods over time (with other delegators still active), completedPeriods increases and thus lastClaimablePeriod for the exited NFT also keeps increasing. Consequently, claimableRewards(tokenId) for this exited NFT continues to grow even after exit, stealing a slice of the delegators’ rewards in those later periods.
Why this is a vulnerability and not just “dust” or rounding
The protocol’s documentation and code comments state that rewards for a delegation should only accrue while the delegation is active and until its exit period. After that, the NFT’s owner should only be entitled to rewards for completed periods between the delegation’s start and end.
In the affected corner case, rewards from later periods — in which the NFT was no longer actively delegated — are still counted into the exited NFT’s claimable rewards.
Those rewards necessarily come at the expense of other delegators on the same validator, because the total validator rewards for a period are fixed (
getDelegatorsRewards), and distribution is based on relative effective stake. The exited NFT’s effective stake has been removed from the denominator for those periods, but its owner still receives a numerator share based on their historical stake, causing other delegators’ shares to be reduced.
This is not just a UX or dust issue. It is a systematic misallocation of yield in favour of the exited NFT holder, i.e. “Theft of unclaimed yield” from other delegators.
Link to Proof of Concept
https://gist.github.com/6newbie/84368ec9da05363585286636a48d15ca
Proof of Concept
Below is a step‑by‑step PoC based on a Hardhat unit test added to the repository:
packages/contracts/test/unit/Stargate/RewardsExploit_H01.test.ts
Setup — Environment & Contracts
Network: Hardhat in‑memory network.
Contracts deployed via existing helper
getOrDeployContracts({ forceDeploy: true, config })with a local config (createLocalConfig()), as used by the project’s ownRewards.test.ts.ProtocolStakerMockis used as the staking protocol backend.
Observed buggy behaviour
The test asserts:
Under current implementation this passes:
claimableLongAfterExit > claimableSoonAfterExit.Interpretation:
An NFT whose delegation has exited should be bounded by the exit window; further increases in
completedPeriodsshould not increase its rewards.Instead, as more periods elapse while other delegators remain active, the exited NFT’s
claimableRewardskeeps increasing as if still active, showing it improperly shares in future periods’ rewards.
Impact on other delegators
Because getDelegatorsRewards(validator, period) per period is fixed, any extra rewards granted to the exited NFT for a period must come at the expense of remaining delegators’ shares. The exited NFT is treated as if it can claim a proportional slice based on its historical stake, reducing other delegators’ received rewards — effectively theft of unclaimed yield.
If you want, I can:
Suggest a minimal code fix to the
_claimableDelegationPeriodslogic (e.g., change the>to>=and add tests), orProduce a proposed unit test that reproduces the PoC in the repo’s test style.
Was this helpful?