# `AssetManager::withdraw` will not return false, when fail to send the withdraw amount

AssetManager::withdraw returns true even when remaining amount is untransferred, so UserManager/UToken credit users for funds they never received.

- Payout: Contest reward
- Severity: medium
- Program: Union Finance
- Credited: Ch_301
- Disclosed: 2022-11-04
- Write-up quality: 6/10
- Original: https://github.com/sherlock-audit/2022-10-union-finance-judging/issues/27
- Library page: https://bugbountylibrary.com/r/sherlock%3A2022-10-union-finance%3AM-16
- Tags: domain:web3 impact:integrity kind:contest platform:sherlock sev:medium tech:solidity technique:source-review vuln:accounting-error vuln:business-logic

**Target:** AssetManager.sol withdraw() in union-v2-contracts
**Root cause:** withdraw tracks an untransferred `remaining` balance but returns true regardless of whether remaining is zero, instead of returning false when funds could not be sourced from AssetManager or moneyMarkets.
**Steps:**
1. Call `AssetManager::withdraw(account, amount)` when AssetManager and its moneyMarkets lack sufficient liquidity for `amount`.
2. Observe the function transfers what it can, leaves `remaining > 0`, yet returns `true`.
3. Caller `UserManager::unstake` / `UToken::borrow` / `redeem` / `removeReserves` treats true as full success and updates accounting (decrements stakedAmount, increments principal, burns UToken) for the full amount.
**How found:** Code reading of withdraw's `remaining` bookkeeping revealed the return value ignores leftover untransferred funds, then tracing callers showed they trust the boolean for accounting updates.
**Exploitation:** Precondition: insufficient liquidity in AssetManager and moneyMarkets. A borrower calls UToken::borrow or a staker calls unstake; withdraw returns true after partial transfer, so the user's principal/staked accounting reflects the full amount while receiving less, and reserves accounting diverges from actual transfers.
**Insight:** When an internal transfer helper returns a success boolean, check whether it validates the leftover/remaining balance before returning true; callers that mutate accounting on that boolean silently desync state from actual token movement.
