Pre-auditThree days of senior audit time for $1,500, credited in full toward your Zealynx audit.3 audit days, credited toward your auditSee how →
When Staking Rewards Spend User Principal

When Staking Rewards Spend User Principal

15 min
On this page▾
A staking contract can hold enough tokens for the next withdrawal and still be insolvent for everyone who remains.
The reason is not necessarily a broken transfer or reward formula. It can be a category error. When principal deposits and reward funding use the same token, balanceOf(address(this)) reports custody, not the obligations attached to that custody.

TL;DR

  • Source fact: Zealynx's Ample finding reports that one staking balance did not distinguish user principal from rewards. Earlier reward payments could therefore consume tokens needed for later principal withdrawals.[1]
  • Comparative evidence: A Code4rena Locke finding shows a related same-token failure in recovery logic, while Velodrome and Ajna findings show adjacent ways reward liabilities can exceed or disappear against available funding.[3][4][5]
  • Zealynx classification: These records form three distinct classes: reserve aliasing, over-accrual, and destructive shortfall handling.
  • Practical rule: A staking system should track assets by obligation and test that every valid claim sequence preserves principal plus accrued reward liabilities.
The Ample case study contains the engagement narrative. This article removes the client-specific frame and tests the underlying reserve mechanism against other public technical records.

Methodology and evidence boundary

This is a mechanism-led comparison, not a prevalence study. A record entered the comparison if its public source described at least one of these conditions:
  1. one token balance served multiple obligations without a correct aggregate reserve calculation;
  2. reward accounting created liabilities beyond the funded reward amount; or
  3. a shortfall path erased a user's remaining claim instead of preserving it.
The core record includes the Ample finding, Locke Streaming issue #215, Velodrome issue #186, and Ajna issue #135. The Synthetix-derived staking model is included as a defensive comparison because its public implementation pattern explicitly bounds a reward rate against token balance.[6]
The statements below use four labels:
  • Source fact: what a cited source reports.
  • Zealynx classification: how this article groups the mechanism.
  • Inference: a conclusion derived across the selected records.
  • Limitation: what the evidence does not establish.
Contest labels are not normalized into a new severity scale. Duplicate status, judge commentary, and source-reported remediation remain part of their original context.

The invariant: assets must cover obligations, not transfers

For a staking contract using one token for both stake and reward, the useful inequality is:
1actual token assets
2 >=
3principal owed to open positions
4 + accrued reward liabilities
5 + other reserved claims
A stricter promise-time model also accounts for rewards that have been committed by an active schedule but have not accrued yet:
1free reward reserve
2 =
3actual token assets
4 - principal liability
5 - accrued reward liability
6 - other reserved claims
A new reward promise is safe only if its maximum additional liability fits inside that free reserve.
This is a protocol solvency statement. It is different from asking whether the next ERC-20 transfer can execute. Transfer success proves that the sender had enough tokens for one payment. It does not prove that the remaining assets cover everyone else.

Why one ERC-20 balance loses economic meaning

Consider a contract with two stakers and one reward funder:
1principal deposited: 200 tokens
2reward funding: 10 tokens
3contract balance: 210 tokens
4promised rewards: 40 tokens
5required assets: 240 tokens
6shortfall: 30 tokens
The contract can pay one user 120 tokens. That transfer succeeds. The remaining balance is 90 tokens, while the second user's principal alone is 100 tokens.
These numbers are an illustrative model, not an observed transaction or Ample balance. The point is structural: an ERC-20 balance does not contain labels. The contract must maintain the labels in its own state or derive them from complete liabilities.
The mistake often appears in one of four places:
  • funding logic treats every incoming token as new reward capacity;
  • reward scheduling checks total balance without subtracting principal;
  • payout logic sends principal plus reward without reducing a reward reserve;
  • recovery logic calculates the same physical balance as excess under two accounting categories.

Anchor record: Ample's reserve aliasing

Zealynx finding F-2025-0001 reports that Ample's staking contract did not differentiate staked tokens from reward tokens in its balance. According to the public finding, earlier rewards could consume staked principal and leave later withdrawals underfunded.[1]
The public audit record identifies the reviewed scope as one 125-line Solidity contract at commit 45f12c5d992d82c14166aba34a455fe9412045ba. It reports the finding as High severity and fixed, with verification by Zealynx.[2]
Zealynx classifies the mechanism as reserve aliasing:
1one physical token balance
2+ two economic obligations
3- aggregate liability accounting
4=
5ambiguous spendable reserve
The report's recommended direction was separate reward accounting, updates when positions close, and observable reward metrics.[1] "Fixed and verified" is a report status for the reviewed remediation. It does not independently establish deployment, later-code security, or that exploitation occurred.

Exact analogue: same-token recovery counted excess twice

Code4rena issue #215 for Locke's Streaming contract is not a staking reward schedule, but it is a close accounting analogue. The source reports that deposit and reward amounts were tracked independently while both could refer to the same ERC-20 token. Recovery logic then evaluated that physical balance once as the deposit token and again as the reward token.[3]
The issue's worked example assigns 1,000 units to remaining deposits and 500 units to rewards. Because the recovery branches considered the same balance independently, the creator could recover value twice and leave user deposits or rewards unavailable. The proposed mitigation calculated excess only after subtracting both obligations from the shared balance.[3]
Zealynx classifies this as double interpretation of one reserve. Ample lacked sufficient separation during reward payment. Locke had separate counters but failed to combine them when deciding what was recoverable. The code shapes differ. The invariant is the same:
1recoverable excess
2 =
3actual assets
4 - all obligations denominated in that asset
A token address can occupy several business roles. It remains one pool of physical assets.

Adjacent mechanism: reward schedules can over-accrue

Velodrome issue #186 reports a different path to the same terminal condition. Its source describes a mismatch between reward duration and period finish when updating rewardRate. The reported result was that aggregate user rewards could exceed available reward tokens, leaving later claimers unable to receive rewards.[4]
The judge commentary treated the issue as Medium severity and noted that a later reward round might make some users whole, while permanent loss remained possible if no later funding arrived.[4]
Zealynx classifies this as liability creation beyond funded reserve, not reserve aliasing. Principal and rewards do not need to share a token for this class to exist. The schedule itself can create too much reward debt.
The distinction matters during review:
  • reserve aliasing asks whether assets assigned to principal are being mistaken for rewards;
  • over-accrual asks whether the reward ledger promises more than reward funding can cover.
Both must be tested. Fixing one does not fix the other.

Adjacent mechanism: a shortfall must not erase debt

Ajna issue #135 reports that when RewardsManager lacked enough AJNA to pay a calculated reward, the claim was truncated to the current contract balance. The issue was marked as a duplicate, judged High severity, and marked satisfactory in the source thread.[5]
If a user was owed more than the available balance, the unpaid portion could be lost rather than retained as a claim. The source recommendation was to support partial claims while preserving the remainder.[5]
Zealynx classifies this as destructive shortfall handling. The shortfall may arise from funding, accounting, or another defect. The claim path then converts a temporary liquidity problem into permanent accounting loss by clearing more liability than it pays.
A safe partial-payment relationship is:
1paid = min(owed, available reward assets)
2remaining debt = owed - paid
The state transition must reduce the user's claim by paid, not by owed.

Comparative matrix

RecordSource-reported mechanismZealynx classificationWhat must be reservedEvidence limit
Ample F-2025-0001 [1][2]Principal and rewards share an undifferentiated balanceReserve aliasingAll open principal plus reward liabilitiesNo public evidence of exploitation or deployment
Locke Streaming #215 [3]Same token balance is treated separately in deposit and reward recovery branchesDouble interpretation of one reserveDeposits plus rewards plus feesStreaming system, not the same staking lifecycle
Velodrome #186 [4]Reward-rate timing can accrue more reward than fundedLiability creation beyond reserveMaximum aggregate reward debtJudge described some loss as potentially temporary
Ajna #135 [5]Insufficient-balance claim truncates and can erase the remainderDestructive shortfall handlingUnpaid reward debt after partial paymentDuplicate issue, not an independent deployment study
Synthetix-derived pattern [6]Reward rate is bounded against available reward balanceDefensive funding boundScheduled reward amountA balance-only bound needs adjustment if reward and stake tokens are identical
The comparison does not establish equal severity, exploitability, or remediation status. It shows that staking solvency can fail at reserve classification, schedule creation, recovery, or claim settlement.

The subtle limit of a balance-based funding check

A common Synthetix-derived pattern checks:
1require(
2 rewardRate * duration <= rewardsToken.balanceOf(address(this)),
3 "reward amount > balance"
4);
The pattern is useful when the reward balance is entirely available for rewards.[6] It prevents a newly configured schedule from distributing more than the contract currently holds.
But if rewardsToken == stakingToken, the right side may include user principal. The check can pass while the reward schedule is economically underfunded.
The same-token version needs a net reserve:
1uint256 freeRewards = token.balanceOf(address(this))
2 - principalLiability
3 - accruedRewardLiability
4 - otherReservedClaims;
5
6require(maxAdditionalRewardLiability <= freeRewards);
This is an inference from the comparison, not a claim that the cited Synthetix-derived implementation supports same-token staking. The design rule is broader: every funding bound must subtract obligations that cannot fund the new promise.

Design patterns that preserve principal

Track liabilities explicitly

At minimum, maintain enough state to derive:
1total principal owed
2total accrued rewards owed
3maximum additional rewards committed by active schedules
4reserved administrative or fee claims
5free reward reserve
Per-user state alone may be insufficient for on-chain checks if calculating the aggregate requires iterating every position. Aggregate counters should move with every lifecycle transition.

Reserve rewards when the promise is created

A contract can reserve rewards at one of two points:
  1. Schedule time: reserve the maximum distributable amount when a reward period starts.
  2. Position time: reserve the maximum reward when a fixed-rate position opens.
Either model can work. Mixing them without a conservation equation makes capacity difficult to prove.

Treat token recovery as a solvency operation

Recovery is not administrative housekeeping. For each token, calculate all obligations denominated in that token before declaring any excess.
A safe relationship is:
1recoverable <= actual balance - total reserved obligations
If the result is negative, recovery must be zero and the system is already underfunded.

Preserve unpaid liabilities

If partial claims are allowed, unpaid debt must survive. If partial claims are not allowed, revert without changing claim state. Never zero a complete entitlement after transferring only the available fraction.

Expose reserve state

Useful views include:
  • principal liability;
  • accrued reward liability;
  • committed but unaccrued rewards;
  • actual token assets;
  • free reward reserve;
  • current shortfall, if any.
Observability does not enforce solvency, but it lets monitors and users check the same invariant the contract is expected to preserve.

Tests that catch the first-claimer advantage

Ordinary unit tests often fund a contract generously and close one position. That proves the happy path, not solvency.
Use these tests instead:
  1. Same-token configuration: Set staking and reward token to the same address.
  2. Minimum reward funding: Fund only the exact permitted reserve.
  3. Multiple positions: Open positions with different sizes, durations, and boosts.
  4. Every exit order: Close the largest reward first, smallest first, and in randomized order.
  5. Aggregate assertion: After every transition, assert assets cover principal and accrued rewards.
  6. Maximum promise assertion: Include rewards committed by active schedules if the contract guarantees them.
  7. Partial claim: Underfund the reward side deliberately and verify unpaid debt remains.
  8. Recovery test: Attempt to recover the shared token while principal and rewards remain owed.
  9. Donation test: Transfer tokens directly to the contract and verify the accounting policy is explicit.
  10. Fee-on-transfer test: If supported, account from actual balance deltas rather than requested transfer amounts.
  11. Administrative withdrawal: Verify privileged paths cannot remove principal or committed rewards.
  12. Fuzzed sequence: Randomize stake, fund, claim, close, recover, and direct-transfer operations.
A compact invariant test is:
1function invariant_assetsCoverRecognizedLiabilities() public view {
2 uint256 assets = token.balanceOf(address(staking));
3 uint256 liabilities = staking.totalPrincipal()
4 + staking.totalAccruedRewards()
5 + staking.otherReservedClaims();
6
7 assertGe(assets, liabilities);
8}
If future rewards are guaranteed once scheduled, add the unaccrued committed amount. If they are explicitly conditional, document that weaker promise rather than silently assuming full funding.
This complements the decomposition method in Divide and Conquer Auditing and the transaction-level conservation model in When Every Check Passes but a Protocol Is Underbacked. The first isolates liabilities before reassembling the system. The second shows why locally valid operations can still violate an aggregate reserve boundary.

What this research does not establish

This corpus is purposive and small. It cannot support a claim about how common staking reserve failures are.
The article does not independently reproduce every cited proof of concept. It relies on the cited public technical records for their reported mechanisms and labels.
A recorded finding does not establish that vulnerable code was deployed, exploited, or associated with a monetary loss. No such claim is made for Ample.
The records are not identical. Locke concerns same-token stream recovery. Velodrome concerns reward-rate timing. Ajna concerns shortfall settlement. They are included to test different stages of one liability lifecycle, not to imply one shared implementation bug.
Finally, the invariant depends on the protocol's actual promise. Some systems distribute only what is funded and make no guarantee about future emissions. Others promise fixed returns at position creation. The liability model must reflect the documented promise, then the code and monitoring must enforce it.

The durable rule: separate custody from availability

The selected records point to one operational rule:
A token held by a contract is available for a new purpose only after every prior obligation denominated in that token has been subtracted.
Ample shows what happens when principal and rewards are not separated.[1] Locke shows that separate counters still fail if recovery does not aggregate them.[3] Velodrome shows that reward liabilities can outrun funding through schedule arithmetic.[4] Ajna shows that a shortfall path can erase the unpaid remainder.[5]
Staking solvency is therefore not one check. It is a lifecycle property from funding to promise creation, accrual, claim, position closure, and recovery. Systems that add delegation, slashing, or liquid wrappers need a wider threat model, covered separately in Restaking Risk.

Frequently asked questions

Can staking and reward tokens safely be the same token?
Yes, but the contract must account for principal and reward obligations separately and aggregate them whenever it schedules rewards, pays claims, or recovers excess tokens. A raw token balance is not sufficient.
Does a successful withdrawal prove the staking contract is solvent?
No. It proves only that one transfer could execute. Solvency requires the remaining assets to cover all remaining principal and reward liabilities regardless of valid withdrawal order.
Should future rewards count as liabilities?
Count rewards according to the protocol's promise. If a schedule or position guarantees them, reserve the maximum committed amount. If rewards are conditional on later funding, make that condition explicit in code and documentation.
What should happen when reward funding is temporarily insufficient?
Either revert without changing the user's claim or pay partially and preserve the unpaid remainder. Do not clear a complete entitlement after transferring only part of it.
What is the most useful monitoring metric?
Track actual assets minus principal, accrued rewards, committed rewards, and other reserved claims. Alert before the resulting free reserve becomes negative, not only after a transfer fails.

Glossary

TermMeaning in this study
Protocol solvencyThe property that assets cover the obligations the protocol has recognized.
InvariantA property that must remain true across every valid state transition.
Invariant testingStateful testing that searches operation sequences for a property violation.
Balance deltaThe measured change in token custody across an operation, useful when requested and received amounts may differ.
SolidityThe contract language used by the selected staking and reward-accounting implementations.

Sources

[6] https://www.cyfrin.io/glossary/staking-rewards-solidity-code-example - Cyfrin, Synthetix-derived staking rewards implementation

Working auditors in your corner, all year

Zealynx Insiders: weekly live sessions, 1:1 advisory, pair-auditing, and Krait runs on your code, from an audit firm that publishes its reports.

Carlos (Bloqarl)
Written by

Founder at Zealynx

Keep reading
When a Safe View Becomes an Unsafe Settlement Input
ResearchOct 8

When a Safe View Becomes an Unsafe Settlement Input

14 min read
ERC-20 Transfers Need Three Proofs, Not One
ResearchOct 12

ERC-20 Transfers Need Three Proofs, Not One

16 min read
When Operator Approval Stops Meaning Consent
ResearchOct 15

When Operator Approval Stops Meaning Consent

18 min read