# Minimum borrow amount can be surpassed and borrower can be treated as being overdue earlier than their actual overdue time

UToken.borrow() checks _minBorrow on requested amount before assetManager.withdraw; partial/dust withdrawals let zero-amount borrows set lastRepay early, marking borrowers overdue prematurely.

- Payout: Contest reward
- Severity: medium
- 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/114
- Library page: https://bugbountylibrary.com/r/sherlock%3A2024-06-union-finance-update-2%3AM-4
- Tags: domain:web3 impact:dos impact:integrity kind:contest platform:sherlock sev:medium tech:solidity technique:source-review vuln:accounting-error vuln:business-logic

**Target:** UToken borrow() / AssetManager withdraw integration
**Root cause:** _minBorrow is validated against the requested amount before assetManager.withdraw, but the effective borrowed amount is reduced by the returned `remaining`; when the underlying market can only return dust/nothing, borrow() completes with sub-minimum or zero amount and still initializes lastRepay.
**Steps:**
1. Ensure assetManager's underlying markets restrict withdrawals so withdraw() returns remaining ≈ amount (dust or full).
2. Call `borrow(to, amount)` with amount >= _minBorrow while borrower has zero debt and lastRepay == 0.
3. Passes `actualAmount < _minBorrow` check, sets `lastRepay = getTimestamp()`, then withdraw returns remaining, reducing actualAmount to dust/zero.
4. Later perform a real material borrow; `getLastRepay != 0` so lastRepay is not reset.
5. After overdueTime from the dust borrow, `checkIsOverdue` returns true despite real debt being recent.
**How found:** Code reading of borrow() ordering: noticed _minBorrow check precedes withdraw and actualAmount is decremented by `remaining` afterward, so the effective amount is never re-validated; combined with lastRepay only set when zero.
**Exploitation:** Borrower triggers a dust/zero borrow during assetManager liquidity shortage to stamp lastRepay early; subsequent real borrows keep the stale timestamp, causing premature overdue status that blocks borrowing/vouching and freezes lenders' stakerFrozen/frozenCoinAge, reducing staking rewards.
**Insight:** When a function validates a minimum/maximum on the requested amount but then mutates it based on an external call's partial result, check whether the post-call effective value is re-validated; also audit timestamp-initialization guards (`== 0`) for state stamped by no-op operations.
