# Unrestricted Access to trackUnseenRewards Cause Rewards Loss

Anyone could call trackUnseenRewards, which updated rewardPerSecond and lastUpdateTime without updating rewardPerTokenStored, zeroing users' accrued claimable rewards.

- Payout: Contest reward
- Severity: high
- Program: Defi App
- Credited: aksoy
- Disclosed: 2025-03-27
- Write-up quality: 6/10
- Original: https://cdn.cantina.xyz/reports/cantina_competition_defi_app_february2025.pdf#3.1.2
- Library page: https://bugbountylibrary.com/r/cantina%3Adefi_app_february2025%3A3.1.2
- Tags: domain:web3 impact:funds-loss kind:contest platform:cantina sev:high tech:solidity technique:source-review vuln:access-control vuln:accounting-error vuln:reward-distribution

**Target:** MultiFeeDistribution staking contract (MFDBase.sol)
**Root cause:** trackUnseenRewards is public and _handleUnseenReward updates rewardPerSecond and lastUpdateTime but never calls updateReward to checkpoint rewardPerTokenStored, so pending user rewards are recomputed against stale state and lost.
**Steps:**
1. Deploy staking with a reward token; stake 1 ether as User1 and distribute rewards.
2. Warp 10 days past periodFinish so User1 has claimable rewards > 0.
3. Mint 10 wei of reward token directly to the staker contract (dust donation).
4. Call `staker.trackUnseenRewards()` from any address.
5. Observe `getUserClaimableRewards(User1)[1].amount` drops to 0.
**How found:** Code review of the public trackUnseenRewards entrypoint noticed _handleUnseenReward mutates rewardPerSecond/lastUpdateTime but omits the updateReward checkpoint, then validated with a Foundry test showing claimable rewards going to zero.
**Exploitation:** Attacker sends a dust amount of the reward token to the contract, then calls the permissionless trackUnseenRewards; the unseen-balance path triggers _handleUnseenReward, resetting reward accounting without checkpointing, erasing other stakers' accrued rewards.
**Insight:** In Synthetix-style staking forks, any externally callable path that touches rewardPerSecond/lastUpdateTime must first checkpoint rewardPerTokenStored; grep for reward-mutating functions lacking the updateReward modifier and test with a dust donation to trigger the unseen-balance branch.
