# MultiRewardStaking.changeRewardSpeed() breaks the distribution

MultiRewardStaking.changeRewardSpeed miscalculates endTimestamp by adding already-accrued rewards to the balance, extending distribution and over-promising tokens.

- Payout: Contest reward
- Severity: medium
- Program: Popcorn
- Credited: Ruhum
- Disclosed: 2023-09-07
- Write-up quality: 6/10
- Original: https://github.com/code-423n4/2023-01-popcorn-findings/issues/190
- Library page: https://bugbountylibrary.com/r/c4%3A2023-01-popcorn%3AM-29
- Tags: domain:web3 impact:integrity kind:contest platform:code4rena sev:medium tech:solidity technique:source-review vuln:accounting-error vuln:reward-distribution

**Target:** MultiRewardStaking staking contract (Solidity)
**Root cause:** _calcRewardsEnd adds the remaining streamed amount (rewardsPerSecond * (prevEnd - now)) to the current token balance, double-counting rewards already accounted for, so the new endTimestamp is computed from an inflated amount.
**Steps:**
1. Configure reward: startTimestamp=0, endTimestamp=100, rewardPerSecond=2, balance=200.
2. At timestamp=50, call changeRewardSpeed with new rewardsPerSecond=4.
3. remainder = balanceOf = 100; prevEndTime=100 > now, so amount = 100 + 4*(100-50) = 300.
4. new endTimestamp = 50 + 300/4 = 125, extending instead of shortening the stream.
5. Contract now owes 4*(125-50)=300 tokens but holds only 100, breaking distribution.
**How found:** Code review of changeRewardSpeed and _calcRewardsEnd; noticed the accrued-remainder term is added to the live token balance, double-counting, then verified with a numeric example.
**Insight:** When recomputing a streaming schedule, the remaining amount must be derived from unstreamed balance, not balanceOf plus the already-accrued term; check that new endTimestamp moves inversely to rewardsPerSecond.
