# Stakers will lose their rewards as updateLocked() updates only the first active vouches until there is a prepayment

Union Finance updateLocked() breaks its loop once prepayment accounting is exhausted, so only the first vouch's lastUpdated is refreshed and remaining stakers lose rewards.

- Payout: Contest reward
- Severity: high
- Program: Union Finance
- Credited: hyh
- Disclosed: 2022-11-04
- Write-up quality: 7/10
- Original: https://github.com/sherlock-audit/2022-10-union-finance-judging/issues/135
- Library page: https://bugbountylibrary.com/r/sherlock%3A2022-10-union-finance%3AH-2
- Tags: domain:web3 impact:funds-loss kind:contest platform:sherlock sev:high tech:solidity technique:source-review vuln:accounting-error vuln:business-logic vuln:reward-distribution

**Target:** UserManager.updateLocked() / UToken._repayBorrowFresh() reward accounting
**Root cause:** updateLocked() is reused for marking non-overdue vouches but exits via `if (remaining <= 0) break` once the notional prepayment is accounted for; with interest-only repayment amount-interest=0, only the first vouch gets lastUpdated refreshed.
**Steps:**
1. Borrower Bob has multiple stakers/vouches in `vouchers[borrower]`.
2. Bob calls `repayBorrow(borrower, repayAmount)` paying only full interest, no principal prepayment.
3. `_repayBorrowFresh` computes `interest = calculatingInterest(borrower)` and calls `updateLocked(borrower, repayAmount - interest, false)` with amount 0.
4. In updateLocked loop, first vouch gets `vouch.lastUpdated = block.number`, then `remaining -= innerAmount` makes remaining <= 0 and loop breaks.
5. All other vouches keep stale lastUpdated; `getFrozenInfo` sees `block.number - lastUpdated > overdueBlocks` and counts them as frozen/overdue, cutting their reward multiplier.
**How found:** Code review of the repay path: noticed updateLocked() is invoked with `repayAmount - interest` (zero for interest-only payments) yet is also responsible for refreshing lastUpdated on all active vouches; traced the `remaining <= 0` break and cross-referenced getFrozenInfo's use of vouch.lastUpdated for reward coin age.
**Exploitation:** No attacker needed; occurs in normal operation whenever a borrower with multiple stakers pays interest only. All but the first staker lose UNION reward share tied to that loan despite timely payment.
**Insight:** When a function serves two purposes (prepayment accounting and health-marking), check whether early-exit conditions valid for one purpose silently truncate the other; here the loop bound is driven by prepayment amount, not by the number of vouches needing a timestamp refresh.
