// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.13 <0.9.0;

/** 
Optimal MEV router contract (IUniswapV2Router compatible)

For the User it aims to offer:
  1. Better order routing for minimal slippage
  2. At source MEV
  3. Lower gas costs for swaps and liquidity changes

For the Liquidity providers:
  1. Inclusive rewards
  2. Reduced impermanent loss

For the Exchange providers:
  1. Inclusive rewards
  2. Increased adoption

For the Ethereum environment:
  1. Reduced MEV attacks and fee spikes
  2. Healthy growth in MEV space with inclusive incentives

Version 1 MEV Strategies
  - cross-dex backruns for swaps
  - reduced slippage fallback router 

The contract leverages and depends on 3 external protocols:
  1. Uniswap V2 (or equivalent on another network) for backrun completion and fallback swaps
  2. BentoBox for flashloan backruns
  3. Aave V2 for flashloan backruns that require more liquidity than BentoBox has 

Business logic
  - Profits from backruns are retained on the contract to improve efficiency (gas cost and profit) of future backruns
  - Contract should therefore be deployed or ownership transfered to a multisig address
  - Harvest function can be called from the multisig owners to distribute profits by consensus 

Resources
  - IUniswapV2Router: https://github.com/Uniswap/v2-periphery/blob/master/contracts/interfaces/IUniswapV2Router02.sol
  - Contract size reduction techniques: https://ethereum.org/en/developers/tutorials/downsizing-contracts-to-fight-the-contract-size-limit/
  - Contract gas reduction techniques: https://gist.github.com/hrkrshnn/ee8fabd532058307229d65dcd5836ddc

Dev Notes
  - Normal sushiswap router functions. Swaps have 2 material changes:
      1) slippage fallback router (uniswap v2)
      2) backruns after user swap
  - For gas and size efficiency, requires are modified to reverts with custom errors
  - Factory hash is now passed to library functions because we are working with 2 factories
  - Other changes are trade-offs for reducing contract size and / or gas usage and stack too deep errors
*/

============ Internal Imports ============

@title OpenMevRouter


@author Sandy Bradley sandy@manifoldx.com,
Sam Bacha sam@manifoldfinance.com

@notice Optimal MEV router contract (IUniswapV2Router compatible)


contract OpenMevRouter is IFlashBorrower, IOpenMevRouter, TwoStepOwnable {
    using SafeTransferLib for ERC20;

    // Custom errors save gas, encoding to 4 bytes
    error Expired();
    error NoTokens();
    error NotPercent();
    error NoReceivers();
    error InvalidPath();
    error TransferFailed();
    error InsufficientBAmount();
    error InsufficientAAmount();
    error TokenIsFeeOnTransfer();
    error ExcessiveInputAmount();
    error ExecuteNotAuthorized();
    error InsufficientAllowance();
    error InsufficientLiquidity();
    error InsufficientOutputAmount();

    event MEV(address indexed user, address indexed token, uint256 value);
    event LoanError(address indexed token, uint256 amountIn);

    bytes4 internal constant SWAP_SELECTOR = bytes4(keccak256("swap(uint256,uint256,address,bytes)"));
    address internal constant WETH09 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    address internal constant BACKUP_FACTORY = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; // uniswap v2 factory
    address internal constant LENDING_POOL_ADDRESS = 0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9; // aave lending pool address
    address internal constant BENTO = 0xF5BCE5077908a1b7370B9ae04AdC565EBd643966;
    address internal constant AAVE_DATA_PROVIDER = 0x057835Ad21a177dbdd3090bB1CAE03EaCF78Fc6d;
    IBentoBoxV1 internal constant bento = IBentoBoxV1(0xF5BCE5077908a1b7370B9ae04AdC565EBd643966); // BENTO vault contract
    mapping(address => bool) internal IS_AAVE_ASSET; // boolean address mapping for flagging aave assets

    /**
     * 
@dev Initialize contract by setting aave assets

*/ constructor() { address[] memory aaveAssets = ILendingPool(LENDING_POOL_ADDRESS).getReservesList(); uint256 length = aaveAssets.length; for (uint256 i; i < length; i = _inc(i)) { address asset = aaveAssets[i]; IS_AAVE_ASSET[asset] = true; } }

@notice

Ensures deadline is not passed, otherwise revert. (0 = no deadline)

@dev Modifier has been replaced with a function for gas efficiency
@param deadline Unix timestamp in seconds for transaction to execute before


    function ensure(uint256 deadline) internal view {
        if (deadline < block.timestamp) revert Expired();
    }

@notice

Checks amounts for token A and token B are balanced for pool. Creates a pair if none exists

@dev Reverts with custom errors replace requires
@param tokenA Token in pool
@param tokenB Token in pool
@param amountADesired Amount of token A desired to add to pool
@param amountBDesired Amount of token B desired to add to pool
@param amountAMin Minimum amount of token A, can be 0
@param amountBMin Minimum amount of token B, can be 0

@return amountA exact amount of token A to be added @return amountB exact amount of token B to be added

    function _addLiquidity(
        address tokenA,
        address tokenB,
        uint256 amountADesired,
        uint256 amountBDesired,
        uint256 amountAMin,
        uint256 amountBMin
    ) internal virtual returns (uint256 amountA, uint256 amountB) {
        // create the pair if it doesn't exist yet
        address factory = OpenMevLibrary.SUSHI_FACTORY;
        if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {
            IUniswapV2Factory(factory).createPair(tokenA, tokenB);
        }
        (uint256 reserveA, uint256 reserveB) = OpenMevLibrary.getReserves(factory, tokenA, tokenB);
        if (_isZero(reserveA) && _isZero(reserveB)) {
            (amountA, amountB) = (amountADesired, amountBDesired);
        } else {
            uint256 amountBOptimal = OpenMevLibrary.quote(amountADesired, reserveA, reserveB);
            if (amountBOptimal > amountBDesired) {
                uint256 amountAOptimal = OpenMevLibrary.quote(amountBDesired, reserveB, reserveA);
                if (amountAOptimal > amountADesired) revert InsufficientAAmount();
                if (amountAOptimal < amountAMin) revert InsufficientAAmount();
                (amountA, amountB) = (amountAOptimal, amountBDesired);
            } else {
                if (amountBOptimal < amountBMin) revert InsufficientBAmount();
                (amountA, amountB) = (amountADesired, amountBOptimal);
            }
        }
    }

@notice

Adds liquidity to an ERC-20⇄ERC-20 pool. msg.sender should have already given the router an allowance of at least amountADesired/amountBDesired on tokenA/tokenB
@param tokenA Token in pool
@param tokenB Token in pool
@param amountADesired Amount of token A desired to add to pool
@param amountBDesired Amount of token B desired to add to pool
@param amountAMin Minimum amount of token A, can be 0
@param amountBMin Minimum amount of token B, can be 0
@param to Address to receive liquidity token
@param deadline Unix timestamp in seconds after which the transaction will revert
@return amountA exact amount of token A added to pool
@return amountB exact amount of token B added to pool
@return liquidity amount of liquidity token received

    function addLiquidity(
        address tokenA,
        address tokenB,
        uint256 amountADesired,
        uint256 amountBDesired,
        uint256 amountAMin,
        uint256 amountBMin,
        address to,
        uint256 deadline
    )
        external
        virtual
        returns (
            uint256 amountA,
            uint256 amountB,
            uint256 liquidity
        )
    {
        ensure(deadline);
        (amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin);
        address pair = OpenMevLibrary.pairFor(OpenMevLibrary.SUSHI_FACTORY, tokenA, tokenB);
        ERC20(tokenA).safeTransferFrom(msg.sender, pair, amountA);
        ERC20(tokenB).safeTransferFrom(msg.sender, pair, amountB);
        liquidity = IUniswapV2Pair(pair).mint(to);
    }

@notice

Adds liquidity to an ERC-20⇄WETH pool with ETH. msg.sender should have already given the router an allowance of at least amountTokenDesired on token. msg.value is treated as a amountETHDesired. Leftover ETH, if any, is returned to msg.sender
@param token Token in pool
@param amountTokenDesired Amount of token desired to add to pool
@param amountTokenMin Minimum amount of token, can be 0
@param amountETHMin Minimum amount of ETH, can be 0
@param to Address to receive liquidity token
@param deadline Unix timestamp in seconds after which the transaction will revert
@return amountToken exact amount of token added to pool
@return amountETH exact amount of ETH added to pool
@return liquidity amount of liquidity token received

    function addLiquidityETH(
        address token,
        uint256 amountTokenDesired,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline
    )
        external
        payable
        virtual
        returns (
            uint256 amountToken,
            uint256 amountETH,
            uint256 liquidity
        )
    {
        ensure(deadline);
        address weth = WETH09;
        (amountToken, amountETH) = _addLiquidity(
            token,
            weth,
            amountTokenDesired,
            msg.value,
            amountTokenMin,
            amountETHMin
        );
        address pair = OpenMevLibrary.pairFor(OpenMevLibrary.SUSHI_FACTORY, token, weth);
        ERC20(token).safeTransferFrom(msg.sender, pair, amountToken);
        IWETH(weth).deposit{ value: amountETH }();
        ERC20(weth).safeTransfer(pair, amountETH);
        liquidity = IUniswapV2Pair(pair).mint(to);
        // refund dust eth, if any
        if (msg.value > amountETH) SafeTransferLib.safeTransferETH(msg.sender, msg.value - amountETH);
    }

**** REMOVE LIQUIDITY **** @notice Removes liquidity from an ERC-20⇄ERC-20 pool. msg.sender should have already given the router an allowance of at least liquidity on the pool.
@param tokenA Token in pool
@param tokenB Token in pool
@param liquidity Amount of liquidity tokens to remove
@param amountAMin Minimum amount of token A, can be 0
@param amountBMin Minimum amount of token B, can be 0
@param to Address to receive pool tokens
@param deadline Unix timestamp in seconds after which the transaction will revert
@return amountA Amount of token A received @return amountB Amount of token B received

    function removeLiquidity(
        address tokenA,
        address tokenB,
        uint256 liquidity,
        uint256 amountAMin,
        uint256 amountBMin,
        address to,
        uint256 deadline
    ) public virtual returns (uint256 amountA, uint256 amountB) {
        ensure(deadline);
        address pair = OpenMevLibrary.pairFor(OpenMevLibrary.SUSHI_FACTORY, tokenA, tokenB);
        ERC20(pair).safeTransferFrom(msg.sender, pair, liquidity); // send liquidity to pair
        (uint256 amount0, uint256 amount1) = IUniswapV2Pair(pair).burn(to);
        (address token0, ) = OpenMevLibrary.sortTokens(tokenA, tokenB);
        (amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);
        if (amountA < amountAMin) revert InsufficientAAmount();
        if (amountB < amountBMin) revert InsufficientBAmount();
    }

@notice

Removes liquidity from an ERC-20⇄WETH pool and receive ETH. msg.sender should have already given the router an allowance of at least liquidity on the pool.
@param token Token in pool
@param liquidity Amount of liquidity tokens to remove
@param amountTokenMin Minimum amount of token, can be 0
@param amountETHMin Minimum amount of ETH, can be 0
@param to Address to receive pool tokens
@param deadline Unix timestamp in seconds after which the transaction will revert
@return amountToken Amount of token received @return amountETH Amount of ETH received

    function removeLiquidityETH(
        address token,
        uint256 liquidity,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline
    ) public virtual returns (uint256 amountToken, uint256 amountETH) {
        address weth = WETH09;
        uint256 balanceBefore = ERC20(token).balanceOf(address(this));
        (amountToken, amountETH) = removeLiquidity(
            token,
            weth,
            liquidity,
            amountTokenMin,
            amountETHMin,
            address(this),
            deadline
        );
        // exploit check from fee-on-transfer tokens
        if (amountToken != ERC20(token).balanceOf(address(this)) - balanceBefore) revert TokenIsFeeOnTransfer();
        ERC20(token).safeTransfer(to, amountToken);
        IWETH(weth).withdraw(amountETH);
        SafeTransferLib.safeTransferETH(to, amountETH);
    }

@notice

Removes liquidity from an ERC-20⇄ERC-20 pool without pre-approval, thanks to permit.
@param tokenA Token in pool
@param tokenB Token in pool
@param liquidity Amount of liquidity tokens to remove
@param amountAMin Minimum amount of token A, can be 0
@param amountBMin Minimum amount of token B, can be 0
@param to Address to receive pool tokens
@param deadline Unix timestamp in seconds after which the transaction will revert
@param approveMax Whether or not the approval amount in the signature is for liquidity or uint(-1)
@param v The v component of the permit signature
@param r The r component of the permit signature
@param s The s component of the permit signature
@return amountA Amount of token A received @return amountB Amount of token B received

    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 virtual returns (uint256 amountA, uint256 amountB) {
        IUniswapV2Pair(OpenMevLibrary.pairFor(OpenMevLibrary.SUSHI_FACTORY, tokenA, tokenB)).permit(
            msg.sender,
            address(this),
            approveMax ? type(uint256).max : liquidity,
            deadline,
            v,
            r,
            s
        );
        (amountA, amountB) = removeLiquidity(tokenA, tokenB, liquidity, amountAMin, amountBMin, to, deadline);
    }

@notice

Removes liquidity from an ERC-20⇄WETTH pool and receive ETH without pre-approval, thanks to permit
@param token Token in pool
@param liquidity Amount of liquidity tokens to remove
@param amountTokenMin Minimum amount of token, can be 0
@param amountETHMin Minimum amount of ETH, can be 0
@param to Address to receive pool tokens
@param deadline Unix timestamp in seconds after which the transaction will revert
@param approveMax Whether or not the approval amount in the signature is for liquidity or uint(-1)
@param v The v component of the permit signature
@param r The r component of the permit signature
@param s The s component of the permit signature
@return amountToken Amount of token received @return amountETH Amount of ETH received

    function removeLiquidityETHWithPermit(
        address token,
        uint256 liquidity,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline,
        bool approveMax,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external virtual returns (uint256 amountToken, uint256 amountETH) {
        IUniswapV2Pair(OpenMevLibrary.pairFor(OpenMevLibrary.SUSHI_FACTORY, token, WETH09)).permit(
            msg.sender,
            address(this),
            approveMax ? type(uint256).max : liquidity,
            deadline,
            v,
            r,
            s
        );
        (amountToken, amountETH) = removeLiquidityETH(token, liquidity, amountTokenMin, amountETHMin, to, deadline);
    }

@notice

Identical to removeLiquidityETH, but succeeds for tokens that take a fee on transfer. msg.sender should have already given the router an allowance of at least liquidity on the pool.
@param token Token in pool
@param liquidity Amount of liquidity tokens to remove
@param amountTokenMin Minimum amount of token, can be 0
@param amountETHMin Minimum amount of ETH, can be 0
@param to Address to receive pool tokens
@param deadline Unix timestamp in seconds after which the transaction will revert
@return amountETH Amount of ETH received

    function removeLiquidityETHSupportingFeeOnTransferTokens(
        address token,
        uint256 liquidity,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline
    ) public virtual returns (uint256 amountETH) {
        address weth = WETH09;
        uint256 balanceBefore = ERC20(token).balanceOf(address(this));
        (, amountETH) = removeLiquidity(token, weth, liquidity, amountTokenMin, amountETHMin, address(this), deadline);
        ERC20(token).safeTransfer(to, ERC20(token).balanceOf(address(this)) - balanceBefore);
        IWETH(weth).withdraw(amountETH);
        SafeTransferLib.safeTransferETH(to, amountETH);
    }

@notice

Identical to removeLiquidityETHWithPermit, but succeeds for tokens that take a fee on transfer.
@param token Token in pool
@param liquidity Amount of liquidity tokens to remove
@param amountTokenMin Minimum amount of token, can be 0
@param amountETHMin Minimum amount of ETH, can be 0
@param to Address to receive pool tokens
@param deadline Unix timestamp in seconds after which the transaction will revert
@param approveMax Whether or not the approval amount in the signature is for liquidity or uint(-1)
@param v The v component of the permit signature
@param r The r component of the permit signature
@param s The s component of the permit signature
@return amountETH Amount of ETH received

    function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
        address token,
        uint256 liquidity,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline,
        bool approveMax,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external virtual returns (uint256 amountETH) {
        IUniswapV2Pair(OpenMevLibrary.pairFor(OpenMevLibrary.SUSHI_FACTORY, token, WETH09)).permit(
            msg.sender,
            address(this),
            approveMax ? type(uint256).max : liquidity,
            deadline,
            v,
            r,
            s
        );
        amountETH = removeLiquidityETHSupportingFeeOnTransferTokens(
            token,
            liquidity,
            amountTokenMin,
            amountETHMin,
            to,
            deadline
        );
    }

@notice

Internal core swap. Requires the initial amount to have already been sent to the first pair.
@param _to Address of receiver
@param swaps Array of user swap data


    function _swap(address _to, OpenMevLibrary.Swap[] memory swaps)
        internal
        virtual
        returns (uint256[] memory amounts)
    {
        uint256 length = swaps.length;
        amounts = new uint256[](_inc(length));
        amounts[0] = swaps[0].amountIn;
        for (uint256 i; i < length; i = _inc(i)) {
            uint256 amountOut = swaps[i].amountOut;
            {
                (uint256 amount0Out, uint256 amount1Out) = swaps[i].isReverse
                    ? (amountOut, uint256(0))
                    : (uint256(0), amountOut);
                address to = i < _dec(length) ? swaps[_inc(i)].pair : _to;
                address pair = swaps[i].pair;
                _asmSwap(pair, amount0Out, amount1Out, to);
            }
            amounts[_inc(i)] = amountOut;
        }
    }

@notice

Swaps an exact amount of input tokens for as many output tokens as possible, along the route determined by the path. The first element of path is the input token, the last is the output token, and any intermediate elements represent intermediate pairs to trade through. msg.sender should have already given the router an allowance of at least amountIn on the input token.

@dev Require has been replaced with revert for gas optimization. Fallback alternate router check for insufficient output amount. Attempt to back-run swaps.
@param amountIn Amount of input tokens to send.
@param amountOutMin Minimum amount of output tokens that must be received
@param path Array of token addresses. path.length must be >= 2. Pools for each consecutive pair of addresses must exist and have liquidity
@param to Address of receiver
@param deadline Unix timestamp in seconds after which the transaction will revert
@return amounts Array of input token amount and all
subsequent output token amounts

    function swapExactTokensForTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external virtual returns (uint256[] memory amounts) {
        ensure(deadline);
        address factory = OpenMevLibrary.SUSHI_FACTORY;
        OpenMevLibrary.Swap[] memory swaps = OpenMevLibrary.getSwapsOut(factory, amountIn, path);
        uint256 length = swaps.length;
        if (swaps[_dec(length)].amountOut < amountOutMin) {
            // Change 1 -> fallback for insufficient output amount, check backup router
            factory = BACKUP_FACTORY;
            swaps = OpenMevLibrary.getSwapsOut(factory, amountIn, path);
            if (swaps[_dec(length)].amountOut < amountOutMin) revert InsufficientOutputAmount();
        }
        ERC20(path[0]).safeTransferFrom(msg.sender, swaps[0].pair, amountIn);
        amounts = _swap(to, swaps);
        // Change 2 -> back-run swaps
        _backrunSwaps(factory, swaps);
    }

@notice

Receive an exact amount of output tokens for as few input tokens as possible, along the route determined by the path. msg.sender should have already given the router an allowance of at least amountInMax on the input token.

@dev Require has been replaced with revert for gas optimization. Fallback alternate router check for insufficient output amount. Attempt to back-run swaps.
@param amountOut Amount of output tokens to receive
@param amountInMax Maximum amount of input tokens
@param path Array of token addresses. path.length must be >= 2. Pools for each consecutive pair of addresses must exist and have liquidity
@param to Address of receiver
@param deadline Unix timestamp in seconds after which the transaction will revert
@return amounts Array of input token amount and all
subsequent output token amounts

    function swapTokensForExactTokens(
        uint256 amountOut,
        uint256 amountInMax,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external virtual returns (uint256[] memory amounts) {
        ensure(deadline);
        address factory = OpenMevLibrary.SUSHI_FACTORY;
        // amounts = OpenMevLibrary.getAmountsIn(factory, amountOut, path);
        OpenMevLibrary.Swap[] memory swaps = OpenMevLibrary.getSwapsIn(factory, amountOut, path);
        if (swaps[0].amountIn > amountInMax) {
            factory = BACKUP_FACTORY;
            // Change 1 -> fallback for insufficient output amount, check backup router
            swaps = OpenMevLibrary.getSwapsIn(factory, amountOut, path);
            if (swaps[0].amountIn > amountInMax) revert ExcessiveInputAmount();
        }

          

@notice

Uint256 zero check gas saver
@param value Number to check


    function _isZero(uint256 value) internal pure returns (bool boolValue) {
        assembly {
            boolValue := iszero(value)
        }
    }

@notice

Uint256 not zero check gas saver
@param value Number to check


    function _isNonZero(uint256 value) internal pure returns (bool boolValue) {
        assembly {
            boolValue := iszero(iszero(value))
        }
    }

@notice

Unchecked increment gas saver for loops
@param i Number to increment


    function _inc(uint256 i) internal pure returns (uint256) {
        unchecked {
            return i + 1;
        }
    }

@notice

Unchecked decrement gas saver for loops
@param i Number to decrement


    function _dec(uint256 i) internal pure returns (uint256) {
        unchecked {
            return i - 1;
        }
    }

@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 {}
}