60079 sc low critical historical state corruption via stale checkpoints leads to permanent loss of future yield
Submitted on Nov 18th 2025 at 09:53:06 UTC by @kind0dev for Audit Comp | Vechain | Stargate Hayabusa
Report ID: #60079
Report Type: Smart Contract
Report severity: Low
Target: https://github.com/immunefi-team/audit-comp-vechain-stargate-hayabusa/tree/main/packages/contracts/contracts/Stargate.sol
Impacts:
Contract fails to deliver promised returns, but doesn't lose value
Theft of unclaimed yield
Description
Brief/Intro
A critical state corruption vulnerability exists in the unstake() function of the Stargate.sol contract. When a user unstakes from a delegation that was forcibly EXITED (due to the validator failing), the cleanup logic incorrectly writes a new historical checkpoint instead of correcting the last scheduled one. This leaves a permanent, incorrect "ghost stake" entry in the historical ledger for a reward period that never occurred for that validator. This corruption makes any future reward reconciliation impossible and will lead to an irrecoverable loss and unfair distribution of yield for other users who were staked in that same period.
Scheduling Future Stake
When a user delegates, the _delegate() function calls _updatePeriodEffectiveStake() to schedule an increase in the validator's stake for a future period, specifically completedPeriods + 2. This writes a checkpoint that is not yet active. For example, if a validator has completed Period 9, a new delegation will write a checkpoint for Period 11.
The Forced Exit Scenario
A validator can be forcefully removed from the network by the protocol (e.g., for being offline). When this happens:
The validator's status in
IProtocolStakerbecomesEXITED.Its
completedPeriodscounter freezes permanently. In the example, it freezes at 10 after completing Period 10.Any delegations to this validator now implicitly have a status of
EXITEDas determined by the_getDelegationStatus()function. This occurs without the user callingrequestDelegationExit().
The Flawed Cleanup in unstake()
unstake()When a user from the failed validator calls unstake(), the following flawed sequence occurs:
The function identifies the delegation as
EXITEDand proceeds.It determines it needs to clean up the user's stake. It calculates the period to update as
completedPeriods + 2(which is10 + 2 = 12in the example).It calls
_updatePeriodEffectiveStake(..., period=12, isIncrease=false).Inside this function, it reads the previous stake value by looking up the last checkpoint (the one for Period 11) and subtracts the user's stake, calculating a new value of 0.
It then pushes a new checkpoint for Period 12 with a value of 0.
The historical record for the validator is now permanently corrupted: Checkpoints array: [{key: 11, value: 1,000,000}, {key: 12, value: 0}]
The "ghost stake" entry at Period 11 is now immutable and incorrect. It represents a stake on the validator for a period that the validator never completed.
Impact Details
This vulnerability has critical, long-term financial consequences for the protocol and its users. While it does not cause an immediate, direct theft of principal, it corrupts the ledger in a way that guarantees future financial loss.
Permanent Loss of User Yield: The primary impact is the theft of unclaimed yield. In any future scenario that requires re-calculating historical rewards (such as a contract upgrade, a manual reconciliation event, or fixing an unrelated bug), the rewards for the "ghost" period (Period 11) will be calculated against an inflated
delegatorsEffectiveStake. The portion of rewards allocated to the non-existent ghost stake will be permanently lost and can never be claimed by the legitimate delegators of that period. This aligns with the High severity impact "Theft of unclaimed yield."Protocol Insolvency for Historical Periods: The protocol becomes unable to fulfill its promise of fair reward distribution for the corrupted historical period. It creates a deficit where the rewards earned by the validator for that period cannot be fully and correctly distributed to the actual stakeholders of that period.
Corruption of Ecosystem Data: The on-chain historical record is the source of truth for all off-chain services. This bug poisons that data, leading to incorrect calculations for user dashboards, tax reporting software, and validator reputation systems that rely on accurate historical APY data.
The "it's in the past" argument is not valid for a blockchain protocol. The history is the state, and a permanent error in the historical financial ledger is a fundamental flaw that will inevitably cause financial harm when that history is referenced.
References
Vulnerable Function:
unstake()inStargate.solhttps://github.com/immunefi-team/audit-comp-vechain-stargate-hayabusa/blob/main/packages/contracts/contracts/Stargate.sol#L231Flawed Cleanup Logic: The call to
_updatePeriodEffectiveStake()withinunstake()forEXITEDvalidators. https://github.com/immunefi-team/audit-comp-vechain-stargate-hayabusa/blob/main/packages/contracts/contracts/Stargate.sol#L276
Proof of Concept
The following Hardhat unit test provides an executable demonstration of the vulnerability.
How to Run: Save the code below as a test file (e.g., test/unit/poc.test.ts) and run the following command from the repository root:
yarn dotenv -v VITE_APP_ENV=local -v TEST_LOGS=1 hardhat test --network hardhat test/unit/poc.test.ts
Test Code
Was this helpful?