# Reward payout griefing via shared state

Periodic payouts that call public cooldown functions or divide by live supply any caller can move

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

## How it works
A reward path depends on state any caller can move. Two shapes: (1) a privileged periodic function calls a public function that enforces a cooldown require, so whoever triggers that function first makes the periodic call revert; (2) a payout divides by a live supply variable that shrinks between calls, so splitting calls changes the result. The broken assumptions: that the caller controls timing, and that supply is constant across a payout.

## Where it shows up
- Periodic `distribute()`/`harvest()` calling a public `checkpoint*()` with a `block.timestamp > last + DEADLINE` require [E1](https://bugbountylibrary.com/r/cantina%3Azerolend_jan2024%3A3.1.4.md)
- The same checkpoint function also invoked by user `claim()`, so normal use pre-triggers the cooldown [E1](https://bugbountylibrary.com/r/cantina%3Azerolend_jan2024%3A3.1.4.md)
- Reward formula dividing by live `lpSupply`/`totalSupply` that a withdrawal can reduce mid-block [E2](https://bugbountylibrary.com/r/cantina%3Aterm_max_december2024%3A3.1.4.md)
- Reward-per-token accumulator with integer division and round-down `earned()`; update function callable every block [E3](https://bugbountylibrary.com/r/c4%3A2023-04-rubicon%3AM-16.md)

## How to review
- For every periodic function, list the public functions it calls and any timestamp/cooldown require inside them. Invariant: no unprivileged caller can make the periodic call revert by pre-triggering shared state [E1](https://bugbountylibrary.com/r/cantina%3Azerolend_jan2024%3A3.1.4.md).
- For payout math, check whether the denominator (supply) is read live and can change between payouts in one block or tx. Invariant: total payout for a given position is independent of how many calls it is split into [E2](https://bugbountylibrary.com/r/cantina%3Aterm_max_december2024%3A3.1.4.md).
- For accumulators, check whether `delta * rate / supply` can truncate to zero for the smallest real balance, and whether an attacker can force frequent updates. Invariant: a staker's yield cannot be suppressed to zero by another party calling update [E3](https://bugbountylibrary.com/r/c4%3A2023-04-rubicon%3AM-16.md).

## Variants seen
- Cooldown griefing: a 1-day checkpoint cooldown, triggered by front-run or ordinary claim, blocks weekly distribution [E1](https://bugbountylibrary.com/r/cantina%3Azerolend_jan2024%3A3.1.4.md)
- Withdrawal splitting against a shrinking live supply denominator, measured at ~2.5% extra tokens in that instance [E2](https://bugbountylibrary.com/r/cantina%3Aterm_max_december2024%3A3.1.4.md)
- Rounding-to-zero plus update-spam blocking small LPs' yield [E3](https://bugbountylibrary.com/r/c4%3A2023-04-rubicon%3AM-16.md)

## Impact and severity
Rewards stop or shrink: distribution reverts entirely [E1](https://bugbountylibrary.com/r/cantina%3Azerolend_jan2024%3A3.1.4.md), LPs extract more than intended [E2](https://bugbountylibrary.com/r/cantina%3Aterm_max_december2024%3A3.1.4.md), small stakers earn nothing [E3](https://bugbountylibrary.com/r/c4%3A2023-04-rubicon%3AM-16.md). Severity rises when the griefing call is cheap and repeatable, and when the blocked function is the only payout path (conditional, not shown in the evidence).

## Evidence
- E1: https://bugbountylibrary.com/r/cantina%3Azerolend_jan2024%3A3.1.4.md
- E2: https://bugbountylibrary.com/r/cantina%3Aterm_max_december2024%3A3.1.4.md
- E3: https://bugbountylibrary.com/r/c4%3A2023-04-rubicon%3AM-16.md
