# `updateLocked()` locks a rounded down value

UToken.updateLocked() rounds down the locked amount below the borrowed principal, so debt write-off leaves dust principal and lastRepay unreset, blocking future borrows.

- Payout: Contest reward
- Severity: medium
- Program: Union Finance Update 2
- Credited: hyh
- Disclosed: 2024-06-12
- Write-up quality: 6/10
- Original: https://github.com/sherlock-audit/2024-06-union-finance-update-2-judging/issues/133
- Library page: https://bugbountylibrary.com/r/sherlock%3A2024-06-union-finance-update-2%3AM-5
- Tags: domain:web3 impact:dos kind:contest platform:sherlock sev:medium tech:solidity technique:source-review vuln:accounting-error vuln:business-logic vuln:precision-loss

**Target:** UToken lending market contract (updateLocked/debtWriteOff)
**Root cause:** decimalReducing(actualAmount + fee, underlyingDecimal) truncates when converting 18-dp to 6-dp, so the locked amount is less than the 18-dp principal recorded on borrow.
**Steps:**
1. Set USDC market originationFee = 1e-8 (1e10 in 18dp); Bob has no loans, Alice opened trust for him.
2. Bob borrows 99 USDC; principal = 99e18 + 1e10*99e18/1e8 = 99*(1e18+1e10).
3. updateLocked calls decimalReducing(actualAmount+fee, 6) which locks only 99*(1e18+1e10)/1e12 = 99e6.
4. Debt is written off via debtWriteOff using 99e6; repayAmount < oldPrincipal, leaving 99e10 dust principal.
5. Because repayAmount != oldPrincipal, lastRepay is not reset; Bob's position is deemed overdue.
6. Bob's next borrow from Alice's trust reverts as checkIsOverdue()==true.
**How found:** Code reading of updateLocked's decimalReducing call and tracing how the truncated locked amount flows into debtWriteOff's repayAmount == oldPrincipal check that gates lastRepay reset.
**Exploitation:** Requires a write-off arrangement (e.g. OTC repayment) between borrower and trust opener; after write-off the borrower's residual dust principal keeps lastRepay stale, making the trust unusable for future borrowing.
**Insight:** When a lock/collateral amount is derived by truncating a scaled value, check downstream equality gates (like repayAmount == oldPrincipal) that assume exact match; a 1-wei mismatch can permanently skip state resets such as lastRepay.
