# 0x0DEX: swapOnWithdrawal parameter manipulation drains pool ETH

0x0DEX swapOnWithdrawal left _lastWithdrawal set to the swapped amount, letting an attacker withdraw far more ETH than deposited and drain ~$61K.

- Payout: Real-world hack
- Severity: high
- Program: 0x0DEX
- Analysis by: DeFiHackLabs
- Disclosed: 2023-09-11
- Write-up quality: 7/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2023-09/0x0DEX_exp.sol
- Library page: https://bugbountylibrary.com/r/dhl%3A20230911-0x0dex
- Tags: area:web3 domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:high tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:business-logic vuln:flash-loan

**Target:** 0x0DEX privacy DEX pool contract (deposit/withdraw/swapOnWithdrawal)
**Root cause:** swapOnWithdrawal sets _lastWithdrawal to the post-swap amount but the withdraw path reuses that stale value, so a small deposit can be paired with a withdrawal amount far exceeding it.
**Steps:**
1. Flash loan 11 ETH WETH from Balancer, unwrap to ETH.
2. Force-send ETH to pool so its balance is a multiple of 10 ETH.
3. Deposit 10 ETH via `deposit(10 ether, [0x1,0x2,Bx,By])` to get ringIndex.
4. Build WithdrawalData with amount 10 ETH, wType=Swap, forged ring signature (s[1]=1, s[0]=curveN+1-c[0]).
5. Call `swapOnWithdrawal(USDC, attacker, 0, 0, deadline, w)` to set _lastWithdrawal.
6. Loop: deposit 0.1 ETH, build WithdrawalData amount 0.1 ETH wType=Direct, call swapOnWithdrawal to withdraw while pool balance >= 10 ETH.
7. Swap drained USDC to ETH via Uniswap, repay Balancer flash loan.
**How found:** Analysis of swapOnWithdrawal showed _lastWithdrawal is only set on Swap-type withdrawals and reused by the withdraw path, suggesting a stale-parameter manipulation.
**Exploitation:** Attacker flash-loans 11 ETH, aligns pool balance to a 10 ETH multiple, performs one Swap-type withdrawal to poison _lastWithdrawal, then repeatedly calls swapOnWithdrawal with tiny 0.1 ETH deposits to withdraw the inflated _lastWithdrawal amount in USDC until the pool is drained (~$61K).
**Escalation:** Looping the deposit/withdraw cycle drains the entire pool balance rather than a single withdrawal.
**Insight:** When a protocol adds a new function that reuses state variables from an existing withdraw path, check whether that state (_lastWithdrawal) is refreshed per call or persists across calls with attacker-controlled amounts.

