# Wrong calculation of Accure Reward in Comptroller.sol

Comptroller._accrueRewards reads globalTotalStaked before onWithdrawRewards updates frozen amounts, letting users earn rewards on freshly frozen tokens.

- Payout: Contest reward
- Severity: high
- Program: Union Finance Update 2
- Credited: Bigsam
- Disclosed: 2024-06-12
- Write-up quality: 6/10
- Original: https://github.com/sherlock-audit/2024-06-union-finance-update-2-judging/issues/26
- Library page: https://bugbountylibrary.com/r/sherlock%3A2024-06-union-finance-update-2%3AH-1
- Tags: domain:web3 impact:funds-loss kind:contest platform:sherlock sev:high tech:solidity technique:source-review vuln:business-logic vuln:reward-distribution

**Target:** Comptroller.sol reward accrual / UserManager staking accounting
**Root cause:** _accrueRewards fetches userManager.globalTotalStaked() before calling onWithdrawRewards(account), which updates the user's frozen amount and thus the global total frozen; the stale (higher) globalTotalStaked is used in _calculateRewardsInternal and gInflationIndex.
**Steps:**
1. Call a path that triggers Comptroller._accrueRewards(account, token) (e.g. withdraw rewards) for a user whose frozen amount has increased.
2. Observe `uint256 globalTotalStaked = userManager.globalTotalStaked();` executes before `userManager.onWithdrawRewards(account)`.
3. onWithdrawRewards recomputes effectiveStaked/effectiveLocked and updates _memberFrozen/_totalFrozen, lowering the true globalTotalStaked.
4. Reward is computed with the pre-update, larger globalTotalStaked, overpaying the user.
5. Compare with batchUpdateFrozenInfo which updates _totalFrozen first then calls comptroller.updateTotalStaked(_totalStaked - _totalFrozen).
**How found:** Code reading: noticed ordering difference between _accrueRewards (reads globalTotalStaked first) and batchUpdateFrozenInfo/updateTotalStaked (updates frozen totals first), implying stale state used in reward math.
**Exploitation:** User freezes tokens (increasing frozen amount), then triggers reward accrual; the stale globalTotalStaked yields inflated reward/inflation index, letting them claim rewards on recently frozen tokens.
**Insight:** When a contract caches a global aggregate before a call that mutates per-user state feeding that aggregate, check ordering against sibling functions that update the aggregate first; the discrepancy reveals stale-read accounting bugs.
