# Users can lose their staking rewards.

After unstaking all funds, a second withdrawRewards call recomputes zero effectiveStaked and overwrites the user's accrued rewards with 0, destroying them.

- Payout: Contest reward
- Severity: medium
- Program: Union
- Credited: CodingNameKiki
- Disclosed: 2023-02-07
- Write-up quality: 6/10
- Original: https://github.com/sherlock-audit/2023-02-union-judging/issues/29
- Library page: https://bugbountylibrary.com/r/sherlock%3A2023-02-union%3AM-2
- Tags: domain:web3 impact:funds-loss kind:contest platform:sherlock sev:medium tech:solidity technique:source-review vuln:accounting-error vuln:reward-distribution

**Target:** Comptroller.withdrawRewards / UserManager staking reward accounting
**Root cause:** withdrawRewards' else branch unconditionally sets users[account][token].accrued = amount, and _calculateRewardsByBlocks returns 0 when effectiveStaked == 0 (true after full unstake), so a second call overwrites the previously accrued balance with zero.
**Steps:**
1. Call `unstake(all)` — stakedAmount becomes 0, _updateStakedCoinAge sets lastUpdated to current block.
2. Call `withdrawRewards` while contract lacks unionToken balance: amount computed >0 but transfer branch fails, so `users[account][token].accrued = amount` stores rewards.
3. Call `withdrawRewards` again: onWithdrawRewards zeroes stakedCoinAge/lockedCoinAge/frozenCoinAge, _getEffectiveAmounts returns effectiveStaked=0, _calculateRewardsByBlocks returns 0.
4. else branch executes `users[account][token].accrued = 0`, wiping the stored rewards.
**How found:** Code-reading of the reward accounting path: noticed the else branch assigns accrued = amount unconditionally, then traced that effectiveStaked becomes 0 after full unstake, making amount 0 and overwriting prior accrued balance.
**Exploitation:** Any staker who fully unstakes and then calls withdrawRewards twice while the Comptroller is underfunded loses their accrued rewards; no attacker gain, self-inflicted loss triggered by normal user flow.
**Insight:** When a function has a 'store for later' fallback branch, check whether the stored value is recomputed from state that the same call path just zeroed — accrued balances overwritten by a zero recomputation are a recurring reward-accounting bug.
