Reproduced Exploit
LiquidityGauge drops a reward interval when totalSupply is 0
_checkpoint adds unsafe_div(1e36 * dt, totalSupply) to the inverse-supply integral and then sets the integral time to block.timestamp. Vyper unsafe_div returns 0 when totalSupply is 0. The vested d_reward is multiplied by that zero growth, so it is marked processed and credited to nobody.
Loss
1000 reward tokens stay in the gauge. The later staker claims 0
Chain
Ethereum
Category
logic
Date
Aug 2025
Source
AuditVault
EVM Playground
Source-level debugger — step opcodes and Solidity in sync
The attack is replayed in an in-browser EVM preloaded with the exact dumped fork state. The execution tree shows every call; step by Solidity line or by opcode across all depths — source, Stack, Memory, Storage, Balances (native / ERC-20 / NFT), Transient storage and Return value stay in sync. Click a tree node, opcode, or source line to jump. No backend, no live RPC.
Source & credit. Reproduction of a public audit finding curated by AuditVault — the original finding: 61923-loss-of-rewards-when-liquiditygaugetotalsupply0-mixbytes-non. Standalone Foundry PoC and full write-up: 61923-loss-of-rewards-when-liquiditygaugetotalsupply0-mixbytes-non_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/logic/reward-calculation · vuln/dos/frozen-funds
Key info#
| Field | Value |
|---|---|
| Loss | 1000 reward tokens stay in the gauge. The later staker claims 0 |
| Vulnerable contract | LiquidityGauge._checkpoint |
| Attacker EOA | 0x1111111111111111111111111111111111111111 |
| Attack contract | Exploit |
| Attack tx | Exploit.run() |
| Chain / block / date | Ethereum model · one block · 2025-08 |
| Compiler | solc 0.8.27, cancun, via IR |
| Bug class | unsafe_div by a zero supply returns 0 and the interval is still consumed |
TL;DR#
_checkpoint adds unsafe_div(1e36 * dt, totalSupply) to the inverse-supply integral and then sets the integral time to block.timestamp. Vyper unsafe_div returns 0 when totalSupply is 0. The vested d_reward is multiplied by that zero growth, so it is marked processed and credited to nobody.
Here the reward window is one second, built by storing integral.t = block.timestamp - 1 in the same transaction. No warp runs inside run. A 1000 token reward is funded, the gauge is checkpointed with no stakers, and a staker who deposits afterwards claims 0. The 1000 remains on the gauge. If one share is deposited before the same checkpoint, that staker is paid the 1000.
The vulnerable code#
uint256 elapsed = block.timestamp - integralInv.t;
uint256 supply = totalSupply;
// @> VULN: unsafe_div by totalSupply 0 returns 0 and this interval is discarded
uint256 invDelta = _unsafeDiv(1e36 * elapsed, supply);
integralInv.v += invDelta;
integralInv.t = block.timestamp;
Root cause#
A zero supply is treated as a completed interval instead of an interval that should stay open until someone is staked.
Preconditions#
- Reward tokens are vested across a timestamp that is later than the last integral time. The one-second gap is stored up front.
totalSupplyis 0 at that checkpoint.
Attack walkthrough#
- Mint and approve 1000 reward tokens.
fundsets the window to the previous second and pulls the tokens. pokevests 1000 and checkpoints. The integral does not grow. Time advances.- A staker deposits 1 share in the same block and claims. The claim pays 0.
- The gauge still holds 1000.
- Control: the share is deposited first. The same fund and claim pay 1000 and leave 0 stuck.
Diagrams#
Impact#
1000 reward tokens are frozen in the gauge. The staker who arrives after the empty checkpoint cannot claim them. The control pays that staker the full 1000.
Remediation#
Do not advance the integral time, and do not vest, while totalSupply is 0. The next depositor then still receives the interval.
How to reproduce#
cd evm-hack-registry/61923-loss-of-rewards-when-liquiditygaugetotalsupply0-mixbytes-non_exp
forge test -vv
Sources#
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 61923-loss-of-rewards-when-liquiditygaugetotalsupply0-mixbytes-non_exp (evm-hack-registry mirror).
- AuditVault finding: 61923-loss-of-rewards-when-liquiditygaugetotalsupply0-mixbytes-non.
Alerts & third-party analyses
These dashboards index community alerts tweets, post-mortems, and independent write-ups. Reach them through the protocol name above to cross-check this reproduction against other analyses.