// File: lib/solmate/src/tokens/ERC20.sol



// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event Transfer(address indexed from, address indexed to, uint256 amount);

    event Approval(address indexed owner, address indexed spender, uint256 amount);

    /*//////////////////////////////////////////////////////////////
                            METADATA STORAGE
    //////////////////////////////////////////////////////////////*/

    string public name;

    string public symbol;

    uint8 public immutable decimals;

    /*//////////////////////////////////////////////////////////////
                              ERC20 STORAGE
    //////////////////////////////////////////////////////////////*/

    uint256 public totalSupply;

    mapping(address => uint256) public balanceOf;

    mapping(address => mapping(address => uint256)) public allowance;

    /*//////////////////////////////////////////////////////////////
                            EIP-2612 STORAGE
    //////////////////////////////////////////////////////////////*/

    uint256 internal immutable INITIAL_CHAIN_ID;

    bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;

    mapping(address => uint256) public nonces;

    /*//////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    constructor(
        string memory _name,
        string memory _symbol,
        uint8 _decimals
    ) {
        name = _name;
        symbol = _symbol;
        decimals = _decimals;

        INITIAL_CHAIN_ID = block.chainid;
        INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
    }

    /*//////////////////////////////////////////////////////////////
                               ERC20 LOGIC
    //////////////////////////////////////////////////////////////*/

    function approve(address spender, uint256 amount) public virtual returns (bool) {
        allowance[msg.sender][spender] = amount;

        emit Approval(msg.sender, spender, amount);

        return true;
    }

    function transfer(address to, uint256 amount) public virtual returns (bool) {
        balanceOf[msg.sender] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(msg.sender, to, amount);

        return true;
    }

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual returns (bool) {
        uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.

        if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;

        balanceOf[from] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(from, to, amount);

        return true;
    }

    /*//////////////////////////////////////////////////////////////
                             EIP-2612 LOGIC
    //////////////////////////////////////////////////////////////*/

    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");

        // Unchecked because the only math done is incrementing
        // the owner's nonce which cannot realistically overflow.
        unchecked {
            address recoveredAddress = ecrecover(
                keccak256(
                    abi.encodePacked(
                        "\x19\x01",
                        DOMAIN_SEPARATOR(),
                        keccak256(
                            abi.encode(
                                keccak256(
                                    "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
                                ),
                                owner,
                                spender,
                                value,
                                nonces[owner]++,
                                deadline
                            )
                        )
                    )
                ),
                v,
                r,
                s
            );

            require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");

            allowance[recoveredAddress][spender] = value;
        }

        emit Approval(owner, spender, value);
    }

    function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
        return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
    }

    function computeDomainSeparator() internal view virtual returns (bytes32) {
        return
            keccak256(
                abi.encode(
                    keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                    keccak256(bytes(name)),
                    keccak256("1"),
                    block.chainid,
                    address(this)
                )
            );
    }

    /*//////////////////////////////////////////////////////////////
                        INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(address to, uint256 amount) internal virtual {
        totalSupply += amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(address(0), to, amount);
    }

    function _burn(address from, uint256 amount) internal virtual {
        balanceOf[from] -= amount;

        // Cannot underflow because a user's balance
        // will never be larger than the total supply.
        unchecked {
            totalSupply -= amount;
        }

        emit Transfer(from, address(0), amount);
    }
}

// File: lib/solmate/src/utils/SafeTransferLib.sol



// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

import {ERC20} from "../tokens/ERC20.sol";

/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
library SafeTransferLib {
    /*//////////////////////////////////////////////////////////////
                             ETH OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function safeTransferETH(address to, uint256 amount) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Transfer the ETH and store if it succeeded or not.
            success := call(gas(), to, amount, 0, 0, 0, 0)
        }

        require(success, "ETH_TRANSFER_FAILED");
    }

    /*//////////////////////////////////////////////////////////////
                            ERC20 OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function safeTransferFrom(
        ERC20 token,
        address from,
        address to,
        uint256 amount
    ) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), from) // Append the "from" argument.
            mstore(add(freeMemoryPointer, 36), to) // Append the "to" argument.
            mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
            )
        }

        require(success, "TRANSFER_FROM_FAILED");
    }

    function safeTransfer(
        ERC20 token,
        address to,
        uint256 amount
    ) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
            mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
            )
        }

        require(success, "TRANSFER_FAILED");
    }

    function safeApprove(
        ERC20 token,
        address to,
        uint256 amount
    ) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
            mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
            )
        }

        require(success, "APPROVE_FAILED");
    }
}

// File: src/FlashMEV.sol



// SPDX-License-Identifier: UNLICENSED

pragma solidity >=0.8.17 <0.9.0;

/// ============ Imports ============

import {ERC20} from "solmate/tokens/ERC20.sol";
import {IFlashBorrower} from "./interfaces/IFlashBorrower.sol";
import {IFlashLoanReceiver} from "./interfaces/IFlashLoanReceiver.sol";
import {IFlashLoanRecipient} from "./interfaces/IFlashLoanRecipient.sol";
import {IVault} from "./interfaces/IVault.sol";
import {ILendingPool} from "./interfaces/ILendingPool.sol";
import {IBentoBoxV1} from "./interfaces/IBentoBoxV1.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {IProtocolDataProvider} from "./interfaces/IProtocolDataProvider.sol";
import {IUniswapV2Router02} from "./interfaces/IUniswapV2Router02.sol";

/// @title FlashMEV
/// @author Sandy Bradley <@sandybradley>
/// @notice Generic flash loan for MEV execution (loans from Balancer (0% fee), BentoBox (0.05%), Aave (0.09%))
contract FlashMEV is IFlashLoanRecipient, IFlashBorrower, IFlashLoanReceiver {
    using SafeTransferLib for ERC20;

    error ZeroAddress();
    error Unauthorized();
    error UnsupportedToken();
    error InsufficientOutputAmount();

    event MEV(address indexed token, uint256 value);

    uint8 internal GOV_FEE;
    address internal GOV;
    address internal mevETH;
    address internal ORACLE_ROUTER;
    address internal constant WETH09 =
        0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    address internal constant BENTO =
        0xF5BCE5077908a1b7370B9ae04AdC565EBd643966; // bentobox vault
    address internal constant LENDING_POOL_ADDRESS =
        0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9; // aave lending pool address
    address internal constant AAVE_DATA_PROVIDER =
        0x057835Ad21a177dbdd3090bB1CAE03EaCF78Fc6d; // aave data provider
    address internal constant VAULT =
        0xBA12222222228d8Ba445958a75a0704d566BF2C8; // balancer vault
    mapping(address => bool) internal FLASH_FRIEND; // flashloan execute user access

    // address internal constant SPLIT_SWAP =
    //     0x77337dEEA78720542f0A1325394Def165918D562;  // Manifold split swap router

    /// @dev setup contract with governance fee and mevETH address
    /// @param _govFee governance fee on mev profit as 1/100 th of a decimal i.e. 1 == 0.01%
    /// @param _mevETH mevETH address
    /// @param _oracleRouter uni V2 style router for eth value lookup
    constructor(uint8 _govFee, address _mevETH, address _oracleRouter) {
        GOV = tx.origin; // tx.origin used over msg.sender for create2 deployment
        mevETH = _mevETH;
        GOV_FEE = _govFee;
        ORACLE_ROUTER = _oracleRouter;
        FLASH_FRIEND[GOV] = true;
    }

    /// @notice Admin can change ownership
    /// @param newGov Address of new owner
    function updateGov(address newGov) external {
        if (msg.sender != GOV) revert Unauthorized();
        if (newGov == address(0)) revert ZeroAddress();
        GOV = newGov;
    }

    /// @notice Admin can change fee
    /// @param _newFee New fee (1 == 0.01%)
    function updateFee(uint8 _newFee) external {
        if (msg.sender != GOV) revert Unauthorized();
        GOV_FEE = _newFee;
    }

    /// @notice Admin can change oracle router
    /// @param _oracleRouter uni V2 style router
    function updateOracle(address _oracleRouter) external {
        if (msg.sender != GOV) revert Unauthorized();
        ORACLE_ROUTER = _oracleRouter;
    }

    /// @notice Update internal Flash friend list
    /// @param flashAllowed Boolean flagging if user is allowed to execute
    /// @param friend Address of friend
    function updateFriend(bool flashAllowed, address friend) external {
        if (msg.sender != GOV) revert Unauthorized();
        FLASH_FRIEND[friend] = flashAllowed;
    }

    /// @notice Admin can change mevETH address
    /// @param newMevEth Address of new mevETH
    function updateMevETH(address newMevEth) external {
        if (msg.sender != GOV) revert Unauthorized();
        if (newMevEth == address(0)) revert ZeroAddress();
        mevETH = newMevEth;
    }

    /// @notice returns flash freind mapping for given address
    /// @param friend user address to query
    function isFriend(address friend) external view returns (bool) {
        return FLASH_FRIEND[friend];
    }

    /// @notice returns governance address
    function gov() external view returns (address) {
        return GOV;
    }

    /// @notice Main Flash loan call to execute MEV
    /// @param useBalancer true if its okay to use balancer flashloan (reentrancy protection makes it impossible to use balancer flashloan with balancer swaps)
    /// @param token token to loan
    /// @param amount Amount to loan of token
    /// @param transactions packed list of transaction data (value(256),contract(160),data_len(16),data(data_len*8))
    function flash(
        bool useBalancer,
        address token,
        uint256 amount,
        bytes calldata transactions
    ) external {
        if (!FLASH_FRIEND[msg.sender]) revert Unauthorized();
        address me = address(this);
        address sender = msg.sender;
        uint256 startGas = gasleft();
        uint256 balBefore = ERC20(token).balanceOf(me);

        // loan preference
        // 1) balancer
        // 2) bentobox
        // 3) aave
        if (useBalancer && ERC20(token).balanceOf(VAULT) >= amount) {
            // addresses of the reserves to flashloan
            address[] memory assets = new address[](1);
            assets[0] = token;
            // amounts of assets to flashloan.
            uint256[] memory amounts = new uint256[](1);
            amounts[0] = amount;
            IVault(VAULT).flashLoan(
                IFlashLoanRecipient(me),
                assets,
                amounts,
                transactions
            );
        } else if (ERC20(token).balanceOf(BENTO) >= amount) {
            IBentoBoxV1(BENTO).flashLoan(
                IFlashBorrower(me),
                me,
                token,
                amount,
                transactions
            );
        } else if (
            IProtocolDataProvider(AAVE_DATA_PROVIDER).getReserveData(token) >=
            amount
        ) {
            {
                // addresses of the reserves to flashloan
                address[] memory assets = new address[](1);
                assets[0] = token;
                // amounts of assets to flashloan.
                uint256[] memory amounts = new uint256[](1);
                amounts[0] = amount;
                // 0 = no debt (just revert), 1 = stable, 2 = variable
                uint256[] memory modes = new uint256[](1);
                modes[0] = 0;
                ILendingPool(LENDING_POOL_ADDRESS).flashLoan(
                    me,
                    assets,
                    amounts,
                    modes,
                    me,
                    transactions,
                    uint16(0)
                );
            }
        } else {
            revert UnsupportedToken();
        }
        // check profit exceeds gas cost
        uint256 profit = ((ERC20(token).balanceOf(me) - balBefore) *
            (10000 - uint256(GOV_FEE))) / 10000;
        if ((startGas - gasleft()) * block.basefee > _ethValue(token, profit))
            revert InsufficientOutputAmount();
        ERC20(token).safeTransfer(sender, profit);
        emit MEV(token, profit); // emit MEV event
    }

    /// @notice Sweep tokens and eth to recipient
    /// @param tokens Array of token addresses
    /// @param recipient Address of recipient
    function sweep(address[] calldata tokens, address recipient) external {
        if (msg.sender != GOV) revert Unauthorized();
        for (uint256 i; i < tokens.length; i++) {
            address token = tokens[i];
            ERC20(token).safeTransfer(
                recipient,
                ERC20(token).balanceOf(address(this))
            );
        }
        SafeTransferLib.safeTransferETH(recipient, address(this).balance);
    }

    /// @notice Admin function to remove code from blockchain and receive rebate
    /// @param recipient Address of rebate recipient
    function destroy(address payable recipient) external {
        if (msg.sender != GOV) revert Unauthorized();
        selfdestruct(recipient);
    }

    /// @notice Called from BentoBox Lending pool after contract has received the flash loaned amount
    /// @dev Reverts if not profitable.
    /// @param sender Address of flashloan initiator
    /// @param token Token to loan
    /// @param amount Amount to loan
    /// @param fee Fee to repay on loan amount
    /// @param data Encoded factories and tokens
    function onFlashLoan(
        address sender,
        address token,
        uint256 amount,
        uint256 fee,
        bytes calldata data
    ) external {
        if (msg.sender != BENTO) revert Unauthorized();
        _executeTransactions(data);
        ERC20(token).safeTransfer(BENTO, amount + fee);
    }

    /// @notice Called from Aave Lending pool after contract has received the flash loaned amount (https://docs.aave.com/developers/v/2.0/guides/flash-loans)
    /// @dev Reverts if not profitable.
    /// @param assets Array of tokens to loan
    /// @param amounts Array of amounts to loan
    /// @param premiums Array of premiums to repay on loan amounts
    /// @param initiator Address of flashloan initiator
    /// @param params Encoded factories and tokens
    /// @return success indicating success
    function executeOperation(
        address[] calldata assets,
        uint256[] calldata amounts,
        uint256[] calldata premiums,
        address initiator,
        bytes calldata params
    ) external returns (bool) {
        if (msg.sender != LENDING_POOL_ADDRESS) revert Unauthorized();
        _executeTransactions(params);
        ERC20(assets[0]).safeApprove(
            LENDING_POOL_ADDRESS,
            amounts[0] + premiums[0]
        );
        return true;
    }

    /// @notice Called from Balancer vault after contract has received flashloan
    /// @param tokens Array of tokens to loan
    /// @param amounts Array of amounts to loan
    /// @param feeAmounts Array of fees to repay on loan amounts
    function receiveFlashLoan(
        address[] memory tokens,
        uint256[] memory amounts,
        uint256[] memory feeAmounts,
        bytes memory userData
    ) external override {
        if (msg.sender != VAULT) revert Unauthorized();
        _executeTransactions(userData);
        ERC20(tokens[0]).safeTransfer(VAULT, amounts[0] + feeAmounts[0]);
    }

    /// @notice Calculate eth value of a token amount
    /// @param token Address of token
    /// @param amount Amount of token
    /// @return eth value
    function _ethValue(
        address token,
        uint256 amount
    ) internal view returns (uint256) {
        if (token == WETH09) return amount;
        if (token == mevETH) return amount;
        address[] memory path = new address[](2);
        path[0] = token;
        path[1] = WETH09;
        uint256[] memory amounts = IUniswapV2Router02(ORACLE_ROUTER)
            .getAmountsOut(amount, path);
        if (amounts.length < 2) return 0;
        return amounts[1];
    }

    /// @dev Sends multiple transactions
    /// @param transactions Encoded transactions. Each transaction is encoded as a packed bytes of
    ///                     value as a uint256 (=> 32 bytes),
    ///                     contract address (20 bytes)
    ///                     data length as a uint16 (=> 2 bytes),
    ///                     data as bytes.
    ///                     see abi.encodePacked for more information on packed encoding
    function _executeTransactions(bytes memory transactions) internal {
        assembly ("memory-safe") {
            let length := mload(transactions)
            let i := 0x20
            for {
                // Pre block is not used in "while mode"
            } lt(i, length) {
                // Post block is not used in "while mode"
            } {
                // let value = mload(add(transactions, i))
                let to := and(
                    shr(96, mload(add(transactions, add(i, 0x20)))),
                    0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
                )
                let dataLength := and(
                    shr(240, mload(add(transactions, add(i, 0x34)))),
                    0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
                )
                let success := call(
                    gas(), // gas left
                    to, // router
                    mload(add(transactions, i)), // value
                    add(transactions, add(i, 0x36)), // input data (0x(4 byte func sig hash)(abi encoded args))
                    dataLength, // input data byte length
                    0, // output
                    0 // output byte length
                )
                if iszero(success) {
                    revert(0, 0)
                }
                // Next entry starts at 0x35 byte + data length
                i := add(i, add(0x36, dataLength))
            }
        }
    }

    /// @notice Function to receive Ether. msg.data must be empty
    receive() external payable {}

    /// @notice Fallback function is called when msg.data is not empty
    fallback() external payable {}
}

// File: src/interfaces/IBentoBoxV1.sol



// SPDX-License-Identifier: MIT

pragma solidity >=0.8.17 <0.9.0;

import "./IFlashBorrower.sol";

/// @notice Minimal interface for BentoBox token vault (V1) interactions
interface IBentoBoxV1 {
    function flashLoan(
        IFlashBorrower borrower,
        address receiver,
        address token,
        uint256 amount,
        bytes calldata data
    ) external;
}

// File: src/interfaces/IFlashBorrower.sol



// SPDX-License-Identifier: MIT

pragma solidity >=0.8.17 <0.9.0;

interface IFlashBorrower {
    /// @notice The flashloan callback. `amount` + `fee` needs to repayed to msg.sender before this call returns.
    /// @param sender The address of the invoker of this flashloan.
    /// @param token The address of the token that is loaned.
    /// @param amount of the `token` that is loaned.
    /// @param fee The fee that needs to be paid on top for this loan. Needs to be the same as `token`.
    /// @param data Additional data that was passed to the flashloan function.
    function onFlashLoan(
        address sender,
        address token,
        uint256 amount,
        uint256 fee,
        bytes calldata data
    ) external;
}

// File: src/interfaces/IFlashLoanReceiver.sol



// SPDX-License-Identifier: agpl-3.0
pragma solidity >=0.8.17 <0.9.0;

/**
 * @title IFlashLoanReceiver interface
 * @notice Interface for the Aave fee IFlashLoanReceiver.
 * @author Aave
 * @dev implement this interface to develop a flashloan-compatible flashLoanReceiver contract
 **/
interface IFlashLoanReceiver {
    function executeOperation(
        address[] calldata assets,
        uint256[] calldata amounts,
        uint256[] calldata premiums,
        address initiator,
        bytes calldata params
    ) external returns (bool);
}

// File: src/interfaces/IFlashLoanRecipient.sol



// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

pragma solidity >=0.7.0 <0.9.0;

// Inspired by Aave Protocol's IFlashLoanReceiver.

interface IFlashLoanRecipient {
    /**
     * @dev When `flashLoan` is called on the Vault, it invokes the `receiveFlashLoan` hook on the recipient.
     *
     * At the time of the call, the Vault will have transferred `amounts` for `tokens` to the recipient. Before this
     * call returns, the recipient must have transferred `amounts` plus `feeAmounts` for each token back to the
     * Vault, or else the entire flash loan will revert.
     *
     * `userData` is the same value passed in the `IVault.flashLoan` call.
     */
    function receiveFlashLoan(
        address[] memory tokens,
        uint256[] memory amounts,
        uint256[] memory feeAmounts,
        bytes memory userData
    ) external;
}

// File: src/interfaces/ILendingPool.sol



// SPDX-License-Identifier: agpl-3.0

pragma solidity >=0.8.17 <0.9.0;

interface ILendingPool {
    /**
     * @dev Allows smartcontracts to access the liquidity of the pool within one transaction,
     * as long as the amount taken plus a fee is returned.
     * IMPORTANT There are security concerns for developers of flashloan receiver contracts that must be kept into consideration.
     * For further details please visit https://developers.aave.com
     * @param receiverAddress The address of the contract receiving the funds, implementing the IFlashLoanReceiver interface
     * @param assets The addresses of the assets being flash-borrowed
     * @param amounts The amounts amounts being flash-borrowed
     * @param modes Types of the debt to open if the flash loan is not returned:
     *   0 -> Don't open any debt, just revert if funds can't be transferred from the receiver
     *   1 -> Open debt at stable rate for the value of the amount flash-borrowed to the `onBehalfOf` address
     *   2 -> Open debt at variable rate for the value of the amount flash-borrowed to the `onBehalfOf` address
     * @param onBehalfOf The address  that will receive the debt in the case of using on `modes` 1 or 2
     * @param params Variadic packed params to pass to the receiver as extra information
     * @param referralCode Code used to register the integrator originating the operation, for potential rewards.
     *   0 if the action is executed directly by the user, without any middle-man
     **/
    function flashLoan(
        address receiverAddress,
        address[] calldata assets,
        uint256[] calldata amounts,
        uint256[] calldata modes,
        address onBehalfOf,
        bytes calldata params,
        uint16 referralCode
    ) external;
}

// File: src/interfaces/IProtocolDataProvider.sol



// SPDX-License-Identifier: agpl-3.0
pragma solidity >=0.8.17 <0.9.0;

interface IProtocolDataProvider {
    /**
     * @notice getReserveData() allows users to get the available liquidity of a given asset.
     * @dev getReserveData() takes an address of an asset as an argument and returns the available liquidity of that asset.
     */
    function getReserveData(
        address asset
    ) external view returns (uint256 availableLiquidity);
}

// File: src/interfaces/IUniswapV2Router02.sol



// SPDX-License-Identifier: UNLICENSED

pragma solidity >=0.8.13 <0.9.0;

interface IUniswapV2Router02 {
    function getAmountsOut(
        uint256 amountIn,
        address[] calldata path
    ) external view returns (uint256[] memory amounts);

    function swapExactETHForTokens(
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external payable returns (uint[] memory amounts);
}

// File: src/interfaces/IVault.sol



// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

pragma solidity >=0.8.17 <0.9.0;

import "./IFlashLoanRecipient.sol";

/**
 * @dev Full external interface for the Vault core contract - no external or public methods exist in the contract that
 * don't override one of these declarations.
 */
interface IVault {
    // Flash Loans

    /**
     * @dev Performs a 'flash loan', sending tokens to `recipient`, executing the `receiveFlashLoan` hook on it,
     * and then reverting unless the tokens plus a proportional protocol fee have been returned.
     *
     * The `tokens` and `amounts` arrays must have the same length, and each entry in these indicates the loan amount
     * for each token contract. `tokens` must be sorted in ascending order.
     *
     * The 'userData' field is ignored by the Vault, and forwarded as-is to `recipient` as part of the
     * `receiveFlashLoan` call.
     *
     * Emits `FlashLoan` events.
     */
    function flashLoan(
        IFlashLoanRecipient recipient,
        address[] memory tokens,
        uint256[] memory amounts,
        bytes memory userData
    ) external;
}