# Reward accrual ignores holding time

Payout sized by pool balance or global elapsed time, not per-user deposit time

_Area: DeFi accounting and math bugs. Seen in 3 disclosed reports; latest 2026-05-17. Updated 2026-10-24._

## How it works
Reward math assumes whoever holds a position when a payout is computed has earned the yield behind it. When the payout is sized from a shared distributor balance, a global elapsed time, or an instantaneous stake snapshot, that assumption breaks: a position opened moments before the claim earns the same as one held for the whole period. The missing property is per-user accrual start — the time the stake actually entered — so participation is never weighted by duration.

## Where it shows up
- Reward formula uses `currentTime - openMarketTime` (a global start) times user amount, with no per-user deposit timestamp [E2](https://bugbountylibrary.com/r/cantina%3Aterm_max_december2024%3A3.1.1.md).
- Payout sized from a distributor's accumulated balance rather than the redeemer's contribution [E1](https://bugbountylibrary.com/r/dhl%3A20260517-seatoken.md).
- Emission set to a lump sum for a single second/block and applied against current `totalStaked` [E3](https://bugbountylibrary.com/r/cantina%3Asilo%3A3.2.6.md).
- Open/deposit and redeem/withdraw callable atomically in one transaction, with no lockup or anti-JIT guard [E1](https://bugbountylibrary.com/r/dhl%3A20260517-seatoken.md)[E2](https://bugbountylibrary.com/r/cantina%3Aterm_max_december2024%3A3.1.1.md).

## How to review
For every reward or fee path, find the accrual variable and ask: does it record when *this* position started? The invariant: a position's payout is proportional to stake × time held, and adding stake cannot retroactively earn past yield. Concretely, if A stakes 100 for 10 blocks and B stakes 100 for 1 block, B's payout must be ~1/10 of A's; a same-block open+claim must yield zero (or near-zero). Check that deposit writes a per-user timestamp or index checkpoint, that claims read that checkpoint, and that a same-block open+claim yields nothing. Trace the token Transfer on claim: if value leaves the distributor without a matching deposit by the claimant, the payout is pool-sized, not stake-sized [E1](https://bugbountylibrary.com/r/dhl%3A20260517-seatoken.md).

## Variants seen
- Same-block open+redeem drains a shared distributor sized from its balance [E1](https://bugbountylibrary.com/r/dhl%3A20260517-seatoken.md).
- Global elapsed time used as the accrual window for every LP [E2](https://bugbountylibrary.com/r/cantina%3Aterm_max_december2024%3A3.1.1.md).
- One-second lump-sum emission against instantaneous stake, frontrunnable by ordering — an adjacent snapshot variant, not strictly deposit time [E3](https://bugbountylibrary.com/r/cantina%3Asilo%3A3.2.6.md).

## Impact and severity
Yield and fee diversion from honest long-term holders: ~$110K drained from a shared distributor [E1](https://bugbountylibrary.com/r/dhl%3A20260517-seatoken.md), fee siphoning from LPs [E2](https://bugbountylibrary.com/r/cantina%3Aterm_max_december2024%3A3.1.1.md), frontrun capture of emissions [E3](https://bugbountylibrary.com/r/cantina%3Asilo%3A3.2.6.md). Severity rises when the pool is shared and large, when open+claim is atomic, and when the loop is flash-loan-fundable, making the drain repeatable within one block.

## Evidence
- E1: https://bugbountylibrary.com/r/dhl%3A20260517-seatoken.md
- E2: https://bugbountylibrary.com/r/cantina%3Aterm_max_december2024%3A3.1.1.md
- E3: https://bugbountylibrary.com/r/cantina%3Asilo%3A3.2.6.md
