# RewardThrottle.setTimekeeper: If changing the timekeeper causes the epoch to change, it will mess up the system

RewardThrottle.setTimekeeper lets POOL_UPDATER_ROLE swap the timekeeper mid-epoch, causing epoch jumps that over-distribute or lock rewards.

- Payout: Contest reward
- Severity: medium
- Program: Malt Protocol
- Credited: cccz
- Disclosed: 2023-05-01
- Write-up quality: 6/10
- Original: https://github.com/code-423n4/2023-02-malt-findings/issues/21
- Library page: https://bugbountylibrary.com/r/c4%3A2023-02-malt%3AM-11
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:medium tech:solidity technique:source-review vuln:access-control vuln:accounting-error vuln:reward-distribution

**Target:** RewardThrottle.setTimekeeper in RewardThrottle.sol
**Root cause:** setTimekeeper does not validate that the new timekeeper's epoch matches the current epoch, so changing it mid-epoch desynchronizes RewardThrottle's epoch state from the timekeeper.
**Steps:**
1. POOL_UPDATER_ROLE calls `setTimekeeper(newTimekeeper)` where `newTimekeeper.epoch() > current epoch`.
2. User calls `checkRewardUnderflow` to fill the gap epochs, distributing a large amount of rewards.
3. Alternatively, set a timekeeper with a lower epoch; `state.rewarded` for that epoch is already at cap, so no rewards can be claimed.
**How found:** Code review of setTimekeeper noticed it lacks epoch consistency checks, then reasoned about epoch increase/decrease effects on checkRewardUnderflow and state.rewarded.
**Exploitation:** A privileged POOL_UPDATER_ROLE (or compromised key) swaps the timekeeper to one with a higher epoch, then any user calls checkRewardUnderflow to mint excess rewards across skipped epochs. Lower-epoch swap bricks reward claims for the current epoch.
**Insight:** When a contract caches an external contract's epoch/counter, any setter that swaps the external contract must validate or reset the cached epoch; otherwise skipped or repeated epochs corrupt reward accounting.
