# Number of prize tiers always increases if just 1 canary prize is claimed

PrizePool's _computeNextNumberOfTiers expands tiers whenever largestTierClaimed+2 exceeds current tiers, so a single canary claim forces tier growth regardless of claim thresholds.

- Payout: Contest reward
- Severity: medium
- Program: PoolTogether
- Credited: dirk\_y
- Disclosed: 2023-09-27
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2023-07-pooltogether-findings/issues/332
- Library page: https://bugbountylibrary.com/r/c4%3A2023-07-pooltogether%3AM-14
- Tags: domain:web3 impact:integrity kind:contest platform:code4rena sev:medium tech:solidity technique:source-review vuln:accounting-error vuln:business-logic

**Target:** PrizePool.sol _computeNextNumberOfTiers / claimPrize
**Root cause:** _computeNextNumberOfTiers sets _nextNumberOfTiers = largestTierClaimed + 2 and returns it even when the claimExpansionThreshold checks fail, so any canary claim (largestTierClaimed = canary tier) bumps the tier count.
**Steps:**
1. Deploy PrizePool with 3 tiers (canary tier = 2).
2. Close a draw, then claim a single canary-tier prize so largestTierClaimed = 2.
3. Call closeDraw for the next draw; _computeNextNumberOfTiers computes _nextNumberOfTiers = 4 and returns it without the threshold checks passing.
4. Assert numberOfTiers() == 4 despite claimCount and canaryClaimCount being below claimExpansionThreshold.
**How found:** Code reading of _computeNextNumberOfTiers: noticed _nextNumberOfTiers is assigned largestTierClaimed + 2 before the expansion-threshold if-block and returned unconditionally, so the threshold gate is bypassed whenever a canary prize was claimed.
**Exploitation:** An attacker (or ordinary canary winner) claims one canary prize per draw; each closeDraw then increments the tier count, reaching MAXIMUM_NUMBER_OF_TIERS (15) in ~12 draws, diluting prize liquidity across too many tiers and breaking prize sizing.
**Escalation:** Repeated canary claims across draws drive tier count to the cap, making the protocol nearly unusable due to broken prize sizes.
**Insight:** When a function computes a candidate value before a conditional gate and returns it on the fall-through path, check whether the pre-gate assignment already encodes the state change the gate was meant to authorize.
