# Reward accounting is incorrect in BathBuddy contract

BathBuddy reward accounting never updates on BathToken mint/burn/transfer, so a mid-period transfer lets the recipient claim 100% of rewards while the prior holder gets nothing.

- Payout: Contest reward
- Severity: high
- Program: Rubicon
- Credited: adriro
- Disclosed: 2025-02-17
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2023-04-rubicon-findings/issues/1279
- Library page: https://bugbountylibrary.com/r/c4%3A2023-04-rubicon%3AH-03
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:incomplete-fix technique:source-review vuln:business-logic vuln:reward-distribution

**Target:** BathBuddy staking-rewards fork (Solidity contract)
**Root cause:** BathBuddy forks Synthetix StakingRewards but removes stake/withdraw, reading BathToken balanceOf/totalSupply directly; the updateReward modifier never fires on mint, burn, transfer or transferFrom, so userRewardsPerTokenPaid and rewardPerToken snapshots go stale.
**Steps:**
1. Deploy BathBuddy paired to a BathToken; set rewards duration and notifyRewardAmount(100 ether).
2. Mint 1 ether BathToken to Alice.
3. vm.warp to startTime + duration/2.
4. Alice calls bathToken.transfer(bob, 1 ether).
5. vm.warp to startTime + duration.
6. Call bathBuddy.getReward(rewardToken, alice) then getReward(rewardToken, bob).
7. Observe alice balance 0, bob balance 100 ether — Bob captures full rewards despite holding only half the period.
**How found:** Compared the BathBuddy fork against the original Synthetix StakingRewards: noticed the stake/withdraw actions and their updateReward modifier were removed while rewardPerToken and earned still depend on BathToken totalSupply and balanceOf, so any state change in the token bypasses reward accounting.
**Exploitation:** Attacker mints or receives BathTokens just before the reward period ends (or transfers tokens to a fresh address), then calls getReward; because userRewardsPerTokenPaid was never initialized/updated on balance change, the new holder claims rewards for the entire duration, and repeated transfers between addresses can re-claim and drain the reward pool.
**Escalation:** Chaining the stale-snapshot flaw with token transfers between multiple addresses allows repeated claims until the contract's reward balance is drained, reducing honest LPs' rewards to zero.
**Insight:** When auditing a fork of a well-known rewards/staking contract, diff the invariants: if the fork removes the stake/withdraw hooks but keeps balanceOf/totalSupply-based accounting, every external balance mutation (mint/burn/transfer) becomes an unaccounted reward event — check whether userRewardsPerTokenPaid is initialized and refreshed on those paths.
