# Inequitable reward distribution in zlrewardscontroller due to dynamic available rewards adjustment

ZLRewardsController shrinks availableRewards after each pool update, so later pools in _massUpdatePools receive progressively less than their allocation-point share.

- Payout: Contest reward
- Severity: medium
- Program: Zerolend
- Credited: elhaj
- Disclosed: 2024-09-16
- Write-up quality: 6/10
- Original: https://cdn.cantina.xyz/reports/cantina_competition_zerolend_jan2024.pdf#3.2.17
- Library page: https://bugbountylibrary.com/r/cantina%3Azerolend_jan2024%3A3.2.17
- Tags: domain:web3 impact:integrity kind:contest platform:cantina sev:medium tech:solidity technique:source-review vuln:business-logic vuln:precision-loss vuln:reward-distribution

**Target:** ZLRewardsController reward distribution contract
**Root cause:** _newRewards caps rawReward to availableRewards(), but availableRewards decreases as each pool is updated within _massUpdatePools, so the cap tightens for later pools and leftover rewards stay undistributed.
**Steps:**
1. Set 3 pools each with 100 allocPoints, totalAllocPoint=300, each 1000 LP supply.
2. rewardsPerSecond=3, elapsed=150s, availableRewards=300.
3. rawReward=450 > 300, so capped to 300.
4. Pool1: 300*100/300=100; remaining=200.
5. Pool2: 200*100/300=66.6; remaining=134.4.
6. Pool3: 134.4*100/300=44.8; 89.6 left undistributed.
**How found:** Code reading of _massUpdatePools/_updatePool/_newRewards: noticed availableRewards() is recomputed per pool while accountedRewards grows, so the cap applied to rawReward changes mid-loop.
**Insight:** When a loop distributes a shared budget, check whether the budget variable is mutated inside the loop; a per-iteration cap on a shrinking balance silently starves later iterations and strands remainders.
