# Rewards can be claimed multiple times

TwabRewards packs claimed epochs into a uint256 bitmap; epoch IDs above 255 shift out of range, so _isClaimedEpoch always returns false and rewards can be claimed repeatedly.

- Payout: Audit contest
- Severity: high
- Program: PoolTogether
- Found by: johnnycash
- Disclosed: 2022-02-03
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2021-12-pooltogether-findings/issues/3
- Library page: https://bugbountylibrary.com/r/c4%3A2021-12-pooltogether%3AH-06
- Tags: area:web3 domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:business-logic

**Target:** TwabRewards.sol claimRewards / epoch bitmap
**Root cause:** _claimedEpochs is a uint256 bitmap indexed by _epochId; for _epochId >= 256 the shift `(_userClaimedEpochs >> _epochId) & 1` is always 0, so _isClaimedEpoch never reports claimed. _epochId is uint256 with no upper bound.
**Steps:**
1. Deploy/observe promotion with epochDuration; mint tickets to attacker.
2. createPromotion(ticket.address) and wait `epochDuration * 257` so epoch 256 is claimable.
3. Call `twabRewards.claimRewards(attacker, promotionId, ['256','256'])` — duplicate epoch 256 in one call.
4. Transaction succeeds instead of reverting 'TwabRewards/rewards-already-claimed', paying the reward twice.
5. Repeat claimRewards with epoch 256 (or any >255) to drain the promotion's tokens.
**How found:** Code review of the bitmap-based _claimedEpochs anti-double-claim mechanism; noticed the comment says 255 epochs but uint256 holds 256, then reasoned that any _epochId beyond the bit width makes the shift check always false.
**Exploitation:** Attacker waits until epoch index exceeds 255, then repeatedly calls claimRewards with the same out-of-range epoch ID (even duplicated within one call) to collect the reward multiple times, draining all tokens allocated to the promotion.
**Insight:** When a bitmap is used as a claimed-set, check whether the index type can exceed the bitmap width — any shift >= bit width silently yields 0, disabling the guard. Also distrust comments claiming a smaller capacity than the type allows.
