# The calculation of the total weight in the TokenLocker is incorrect

TokenLocker.withdrawWithPenalty decrements totalDecayRate before recomputing total weight, so total voting weight diverges from sum of user weights, corrupting DAO proposal accounting.

- Payout: Contest reward
- Severity: high
- Program: Bima
- Credited: etherSky
- Disclosed: 2025-02-05
- Write-up quality: 6/10
- Original: https://cdn.cantina.xyz/reports/cantina_competition_bima_december2024.pdf#3.1.2
- Library page: https://bugbountylibrary.com/r/cantina%3Abima_december2024%3A3.1.2
- Tags: domain:web3 impact:integrity kind:contest platform:cantina sev:high tech:solidity technique:source-review vuln:accounting-error vuln:governance

**Target:** TokenLocker.sol voting weight accounting
**Root cause:** withdrawWithPenalty reduces totalDecayRate before calling getTotalWeightWrite(), so the decay for the withdrawing user's remaining weeks is dropped from the total weight computation while individual weights decay normally.
**Steps:**
1. Deploy TokenLocker; enable penalty withdrawals after 6 weeks via `setAllowPenaltyWithdrawAfter` and `setPenaltyWithdrawalsEnabled(true)`.
2. User1 and User2 each call `lock(account, 100, 10)`; total weight = 2000, each user = 1000.
3. Warp 1 week; User1 calls `withdrawWithPenalty(type(uint256).max)`.
4. Observe total weight = 1000 but User2 weight = 900 (sum should be 900).
5. Warp another week: total = 900, User2 = 800; gap persists and grows.
**How found:** Reasoned about invariant that total voting weight must equal sum of individual weights; traced withdrawWithPenalty ordering and noticed totalDecayRate was decremented before getTotalWeightWrite() consumed it.
**Insight:** When a contract maintains both an aggregate accumulator and per-user accumulators, check ordering of mutations vs. lazy-recompute helpers (getTotalWeightWrite) — decrementing a rate before the recompute silently drops the withdrawing user's future decay from the aggregate.
