# USDMPegRecovery.sol#withdraw() withdraw may often fail

USDM withdraw() pays from contract balance, but provide() moves all funds into the Curve pool, so withdrawals revert on insufficient balance.

- Payout: Audit contest
- Severity: medium
- Program: Concur Finance
- Found by: WatchPug
- Disclosed: 2022-05-24
- Write-up quality: 6/10
- Original: https://github.com/code-423n4/2022-02-concur-findings/issues/212
- Library page: https://bugbountylibrary.com/r/c4%3A2022-02-concur%3AM-03
- Tags: domain:web3 impact:dos kind:contest platform:code4rena sev:medium tech:evm tech:solidity vuln:accounting-error vuln:business-logic

**Target:** Solidity contract USDMPegRecovery.sol withdraw()
**Root cause:** withdraw() transfers tokens directly from the contract's current balance while accounting is tracked in internal storage (totalLiquidity/userLiquidity), so once provide() deposits funds into usdm3crv the balance no longer covers withdrawals.
**Steps:**
1. Alice deposits 4M USDM and 4M pool3 via deposit(), updating userLiquidity.
2. Guardian calls provide(), moving all usdm and pool3 into the usdm3crv Curve pool.
3. Alice calls withdraw() with her amounts; usdm.safeTransfer/pool3.safeTransfer revert due to insufficient contract balance.
**How found:** Code reading: noticed withdraw() uses safeTransfer from live balance rather than removing liquidity from the pool, while provide() sweeps all funds into usdm3crv; traced the accounting-vs-balance mismatch.
**Insight:** When a contract tracks user shares in storage but pays out from its live token balance, check every path that moves that balance elsewhere (e.g. provide/stake) — withdrawals can become unbacked even though accounting looks correct.
