# V3Proxy swapTokensForExactETH does not send back to the caller the unused input tokens

V3Proxy.swapTokensForExactETH pulls amountInMax but never refunds the unspent input tokens, locking user funds in the contract.

- Payout: Contest reward
- Severity: high
- Program: Good Entry
- Credited: 3docSec
- Disclosed: 2023-10-02
- Write-up quality: 6/10
- Original: https://github.com/code-423n4/2023-08-goodentry-findings/issues/64
- Library page: https://bugbountylibrary.com/r/c4%3A2023-08-goodentry%3AH-05
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity vuln:accounting-error vuln:business-logic

**Target:** V3Proxy swapTokensForExactETH (Uniswap V3 router wrapper)
**Root cause:** The function transfers amountInMax from the caller and approves the router for amountInMax, but after exactOutputSingle only forwards amountOut ETH and never returns amountInMax - amounts[0] to the caller.
**Steps:**
1. Deploy V3Proxy with Uniswap SwapRouter 0xE592427A0AEce92De3Edee1F18E0157C05861564 and Quoter 0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6, feeTier 500.
2. Alice approves V3Proxy for 1870e6 USDC and calls `swapTokensForExactETH(1e18, 1870e6, [USDC, WETH], alice, block.timestamp)`.
3. Swap executes: amounts[0] < 1870e6, amounts[1] == 1e18, Alice receives 1e18 ETH.
4. Assert `USDC.balanceOf(alice) == balanceUsdcBefore - amounts[0]` — fails; the difference amountInMax - amounts[0] stays in V3Proxy.
**How found:** Code review of the swap wrapper: noticed amountInMax is pulled and approved but only amountOut is forwarded, so the change is never refunded.
**Exploitation:** Any user calling swapTokensForExactETH with amountInMax above the actual required input permanently loses the excess, which remains locked in the V3Proxy contract with no withdrawal path.
**Insight:** In exact-output swap wrappers, always verify the refund path for amountInMax - amounts[0]; the router only consumes what it needs, so any wrapper that pre-pulls the max must explicitly return the remainder.
