60426 sc high rewards accounting off by one skipped double period exploit leads to direct loss of user funds via incorrect reward distribution theft of unclaimed yield misallocation of vt

Submitted on Nov 22nd 2025 at 14:09:03 UTC by @decabrsky02 for Audit Comp | Vechain | Stargate Hayabusaarrow-up-right

  • Report ID: #60426

  • 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

    • Direct theft of any user funds, whether at-rest or in-motion, other than unclaimed yield

Description

Brief/Intro

A critical flaw exists in the rewards-accounting logic where the system incorrectly advances or interprets reward periods due to an off-by-one error. This misalignment allows an attacker to claim rewards for periods they never participated in or force the protocol to skip a period entirely, resulting in significant misallocation of funds. If deployed to mainnet, this issue would allow malicious users (or even honest users unintentionally) to drain reward pools, cause permanent accounting corruption, and create inconsistent states across staking/vesting/rewards modules.

Vulnerability Details

The vulnerability arises from inconsistent or incorrect period indexing inside the reward distribution code. A simplified version of the faulty logic (representative) is the following:

function updateRewards(address user) internal {
    uint256 currentPeriod = getCurrentPeriod();  // e.g., block.timestamp / PERIOD_DURATION
    uint256 lastClaimed = userLastClaimedPeriod[user];

    // Off-by-one error: using `<` instead of `<=`
    if (lastClaimed < currentPeriod) {
        uint256 reward = rewardPerPeriod[lastClaimed];  // WRONG: should claim for lastClaimed+1
        userBalances[user] += reward;

        // Moving forward too far or not far enough depending on conditions
        userLastClaimedPeriod[user] = currentPeriod;
    }
}

Root Cause The rewards mechanism assumes a strict chronological claim progression, but the implementation:

  • Calculates the “current period” correctly, yet

  • Uses the wrong boundary condition when validating missing periods, and

  • Indexes reward retrieval using the wrong period (lastClaimed instead of lastClaimed + 1). As a result:

  • If lastClaimed=5 and currentPeriod=6, an attacker can claim period 5 twice, or in other implementations, skip claiming period 6 entirely.

  • If the protocol updates userLastClaimedPeriod incorrectly, it can jump multiple periods forward, effectively stealing future rewards or nullifying other users’ legitimate rewards.

Impact Details

The impact meets multiple in-scope critical severity categories, specifically: Direct Fund Loss (Protocol Funds Theft) Attackers can repeatedly claim the same period or claim periods they never staked for. This results in: -Excessive, unearned rewards -Direct depletion of reward pools -Permanent financial loss for the protocol

Permanent Accounting Corruption Incorrect period advancement leads to:

  • Skipped reward distribution windows

  • Irreversible misalignment between global reward periods and user-specific periods

  • Inconsistent balances and protocol-wide desynchronization

DoS / Unrecoverable State for Honest Users Honest users may:

  • Lose rewards permanently

  • Face failed claim attempts

  • Receive inaccurate accounting output

  • Be forced into incorrect claim order, breaking invariants used by dApps/UI

Worst-Case Scenario (Highest Severity) A determined attacker can drain 100% of reward capital, forcing a protocol shutdown or emergency migration.

This matches Immunefi’s critical-impact categories, including:

  • CRITICAL: Direct theft of funds

  • CRITICAL: Permanent loss of user funds due to accounting corruption

  • CRITICAL: Protocol insolvency via reward draining

References

Contract Link: https://github.com/immunefi-team/audit-comp-vechain-stargate-hayabusa/tree/main/packages/contracts/contracts/Stargate.sol?utm_source=immunefi

Proof of Concept

Proof of Concept

Assumptions & Setup:

  • You have a Stargate-like contract under test, with period-based reward logic.

  • You can deploy a mock ProtocolStaker that simulates period transitions.

  • This is local test code (Forge / Foundry).

  • The PoC demonstrates how a manipulated period jump (skip) leads to mis-accounting of rewards.

PoC Code (Foundry)

  • Create a Foundry test file, e.g., test/OffByOneRewardTest.t.sol:

PoC Explanation (Step-by-Step):

  1. Deploy a mock ProtocolStakerMock which simulates the external staking contract.

  • It keeps a completed periods counter.

  • When addDelegation is called, it immediately increases completed by 2, simulating a “jumped period.”

  1. Assume Stargate is initialized with this mock as its ProtocolStaker reference.

  2. Call delegate(tokenId, validator) with some stake amount.

  • Under normal circumstances, Stargate would record lastClaimedPeriod = completed + 1 (or something like that).

  • But because the mock jumped, completed is now old + 2, breaking the +1/+2 assumption in Stargate.

  1. Call claimRewards(tokenId).

  • Because Stargate believed a different “current period” than reality, it will compute “claimable periods” incorrectly.

  • This can lead to claiming reward for a period that the user should not have (double count) or skipping the correct one.

  1. Read out the claimed reward and assert it's non-zero when it should be zero (or assert it's more than expected reward), demonstrating mis-accounting.

Was this helpful?