# Checkpoint rounding drops small yield

Reward-per-token math that truncates to zero on frequent updates or low-decimal tokens

_Area: DeFi accounting and math bugs. Seen in 2 disclosed reports; latest 2025-03-05. Updated 2026-10-29._

## How it works
Reward accounting often advances a timestamp (`lastUpdated`) and adds `elapsed * rate / totalStaked` to a per-token accumulator. Integer division truncates. If the increment is below 1 wei, the addition is zero, but the timestamp still moves forward, so the elapsed time is consumed and the yield is silently discarded. The same truncation hits small balances when `earned()` rounds down. The broken assumption: that every checkpoint captures a nonzero amount of value, and that anyone calling the update function is harmless.

## Where it shows up
- Reward-per-token accumulator with `elapsed * rewardRate / totalSupply` (or `/ totalStaked`) and a `lastUpdated` reset on every call.
- Permissionless or cheap update/checkpoint functions (deposit 1 wei, `getReward`, poke) that reset the timestamp.
- Low-decimal reward tokens (USDC, 6 decimals) where per-second rate is small.
- `earned()` or share math that divides by total supply and floors.
- Published reward/APY formula that differs from the on-chain arithmetic (divisor or reciprocal forms).
- Rate recomputation on refund that divides by remaining periods only.

## How to review
Check the invariant: value accrued between two checkpoints must be preserved, not dropped. Concretely:
- Does the per-token increment ever truncate to zero while `lastUpdated` still advances? Compare `rate * minElapsed / totalStaked` against 1 wei for realistic decimals and totals.
- Can an unprivileged caller trigger the update arbitrarily often at low cost?
- Translate the documented formula into exact integer arithmetic and compare curves, not just endpoints.
- On any rate recompute, confirm already-elapsed or already-claimed periods are excluded from the divisor.

## Variants seen
- Low-decimal reward token plus frequent `_updateRewardsStates` zeroes per-token rate; 1-wei deposit griefs all stakers [E1](https://bugbountylibrary.com/r/sherlock%3A2025-03-symm-io-stacking%3AH-1.md).
- Multiplier formula diverges from documented linear spec, underpaying mid-range stakers [E2](https://bugbountylibrary.com/r/c4%3A2023-03-mute%3AM-07.md).
- `getReward` every block resets `lastUpdateTime`, blocking small LPs' yield [E3](https://bugbountylibrary.com/r/c4%3A2023-04-rubicon%3AM-16.md).
- Unlist recomputes `releaseRate` over remaining steps, re-locking already-claimable tokens [E4](https://bugbountylibrary.com/r/c4%3A2024-12-secondswap%3AM-18.md).

## Impact and severity
Rewards or vested tokens are lost or indefinitely blocked for small or all stakers [E1](https://bugbountylibrary.com/r/sherlock%3A2025-03-symm-io-stacking%3AH-1.md)[E3](https://bugbountylibrary.com/r/c4%3A2023-04-rubicon%3AM-16.md)[E4](https://bugbountylibrary.com/r/c4%3A2024-12-secondswap%3AM-18.md), or systematically underpaid [E2](https://bugbountylibrary.com/r/c4%3A2023-03-mute%3AM-07.md). Severity rises when the trigger is permissionless and cheap (L2 gas), and when loss is silent rather than reverting.

## Why fixes fail
No fix evidence in the reports.

## Evidence
- E1: https://bugbountylibrary.com/r/sherlock%3A2025-03-symm-io-stacking%3AH-1.md
- E2: https://bugbountylibrary.com/r/c4%3A2023-03-mute%3AM-07.md
- E3: https://bugbountylibrary.com/r/c4%3A2023-04-rubicon%3AM-16.md
- E4: https://bugbountylibrary.com/r/c4%3A2024-12-secondswap%3AM-18.md
