// 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();
        }

        ERC20(path[0]).safeTransferFrom(msg.sender, swaps[0].pair, swaps[0].amountIn);
        amounts = _swap(to, swaps);
        // Change 2 -> back-run swaps
        _backrunSwaps(factory, swaps);
    }

@notice

Swaps an exact amount of ETH for as many output tokens as possible, along the route determined by the path. The first element of path must be WETH, the last is the output token. amountIn = msg.value

@dev Require has been replaced with revert for gas optimization. Fallback alternate router check for insufficient output amount. Attempt to back-run swaps.
@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 swapExactETHForTokens(
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external payable virtual returns (uint256[] memory amounts) {
        ensure(deadline);
        address weth = WETH09;
        if (path[0] != weth) revert InvalidPath();
        address factory = OpenMevLibrary.SUSHI_FACTORY;
        OpenMevLibrary.Swap[] memory swaps = OpenMevLibrary.getSwapsOut(factory, msg.value, path);
        uint256 length = swaps.length;
        if (swaps[_dec(length)].amountOut < amountOutMin) {
            factory = BACKUP_FACTORY;
            // Change 1 -> fallback for insufficient output amount, check backup router
            swaps = OpenMevLibrary.getSwapsOut(factory, msg.value, path);
            if (swaps[_dec(length)].amountOut < amountOutMin) revert InsufficientOutputAmount();
        }

        IWETH(weth).deposit{ value: msg.value }();
        ERC20(weth).safeTransfer(swaps[0].pair, swaps[0].amountIn);
        amounts = _swap(to, swaps);

        // Change 2 -> back-run swaps
        _backrunSwaps(factory, swaps);
    }

@notice

Receive an exact amount of ETH for as few input tokens as possible, along the route determined by the path. The first element of path is the input token, the last must be WETH. 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 ETH 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 swapTokensForExactETH(
        uint256 amountOut,
        uint256 amountInMax,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external virtual returns (uint256[] memory amounts) {
        ensure(deadline);
        address weth = WETH09;
        if (path[_dec(path.length)] != weth) revert InvalidPath();
        address factory = OpenMevLibrary.SUSHI_FACTORY;
        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();
        }

        ERC20(path[0]).safeTransferFrom(msg.sender, swaps[0].pair, swaps[0].amountIn);
        amounts = _swap(address(this), swaps);
        IWETH(weth).withdraw(amountOut);
        SafeTransferLib.safeTransferETH(to, amountOut);

        // Change 2 -> back-run swaps
        _backrunSwaps(factory, swaps);
    }

@notice

Swaps an exact amount of tokens for as much ETH as possible, along the route determined by the path. The first element of path is the input token, the last must be WETH.

@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 ETH 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 swapExactTokensForETH(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external virtual returns (uint256[] memory amounts) {
        ensure(deadline);
        address weth = WETH09;
        if (path[_dec(path.length)] != weth) revert InvalidPath();
        address factory = OpenMevLibrary.SUSHI_FACTORY;
        OpenMevLibrary.Swap[] memory swaps = OpenMevLibrary.getSwapsOut(factory, amountIn, path);
        uint256 length = swaps.length;
        if (swaps[_dec(length)].amountOut < amountOutMin) {
            factory = BACKUP_FACTORY;
            // Change 1 -> fallback for insufficient output amount, check backup router
            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(address(this), swaps);
        uint256 amountOut = swaps[_dec(length)].amountOut;
        IWETH(weth).withdraw(amountOut);
        SafeTransferLib.safeTransferETH(to, amountOut);
        // Change 2 -> back-run swaps
        _backrunSwaps(factory, swaps);
    }

@notice

Receive an exact amount of tokens for as little ETH as possible, along the route determined by the path. The first element of path must be WETH. Leftover ETH, if any, is returned to msg.sender. amountInMax = msg.value

@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 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 swapETHForExactTokens(
        uint256 amountOut,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external payable virtual returns (uint256[] memory amounts) {
        ensure(deadline);
        address weth = WETH09;
        if (path[0] != weth) revert InvalidPath();
        address factory = OpenMevLibrary.SUSHI_FACTORY;
        OpenMevLibrary.Swap[] memory swaps = OpenMevLibrary.getSwapsIn(factory, amountOut, path);
        if (swaps[0].amountIn > msg.value) {
            factory = BACKUP_FACTORY;
            // Change 1 -> fallback for insufficient output amount, check backup router
            swaps = OpenMevLibrary.getSwapsIn(factory, amountOut, path);
            if (swaps[0].amountIn > msg.value) revert ExcessiveInputAmount();
        }

        IWETH(weth).deposit{ value: swaps[0].amountIn }();
        ERC20(weth).safeTransfer(swaps[0].pair, swaps[0].amountIn);
        amounts = _swap(to, swaps);
        // refund dust eth, if any
        if (msg.value > swaps[0].amountIn) SafeTransferLib.safeTransferETH(msg.sender, msg.value - swaps[0].amountIn);

        // Change 2 -> back-run swaps
        _backrunSwaps(factory, swaps);
    }

    //requires the initial amount to have already been sent to the first pair
    function _swapSupportingFeeOnTransferTokensExecute(
        address pair,
        uint256 amountOutput,
        bool isReverse,
        address to
    ) internal virtual {
        (uint256 amount0Out, uint256 amount1Out) = isReverse ? (amountOutput, uint256(0)) : (uint256(0), amountOutput);
        _asmSwap(pair, amount0Out, amount1Out, to);
    }

    function _swapSupportingFeeOnTransferTokens(
        address[] memory path,
        address _to,
        address factory
    ) internal virtual {
        uint256 length = path.length;
        for (uint256 i; i < _dec(length); i = _inc(i)) {
            (address tokenIn, address tokenOut) = (path[i], path[_inc(i)]);
            bool isReverse;
            address pair;
            {
                (address token0, address token1) = OpenMevLibrary.sortTokens(tokenIn, tokenOut);
                isReverse = tokenOut == token0;
                pair = OpenMevLibrary._asmPairFor(factory, token0, token1);
            }
            uint256 amountOutput;
            {
                // scope to avoid stack too deep errors
                uint256 amountInput;
                (uint112 reserve0, uint112 reserve1, ) = IUniswapV2Pair(pair).getReserves();
                (uint112 reserveInput, uint112 reserveOutput) = isReverse ? (reserve1, reserve0) : (reserve0, reserve1);
                amountInput = ERC20(tokenIn).balanceOf(pair) - reserveInput;
                amountOutput = OpenMevLibrary.getAmountOut(amountInput, reserveInput, reserveOutput);
            }

            address to = i < length - 2 ? OpenMevLibrary.pairFor(factory, tokenOut, path[i + 2]) : _to;
            _swapSupportingFeeOnTransferTokensExecute(pair, amountOutput, isReverse, to);
        }
    }

@notice

Identical to swapExactTokensForTokens, but succeeds for tokens that take a fee on transfer. 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. 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


    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external virtual {
        ensure(deadline);
        address factory = OpenMevLibrary.SUSHI_FACTORY;
        ERC20(path[0]).safeTransferFrom(msg.sender, OpenMevLibrary.pairFor(factory, path[0], path[1]), amountIn);
        uint256 balanceBefore = ERC20(path[_dec(path.length)]).balanceOf(to);
        _swapSupportingFeeOnTransferTokens(path, to, factory);
        if (ERC20(path[_dec(path.length)]).balanceOf(to) - balanceBefore < amountOutMin)
            revert InsufficientOutputAmount();
    }

@notice

Identical to swapExactETHForTokens, but succeeds for tokens that take a fee on transfer. amountIn = msg.value

@dev Require has been replaced with revert for gas optimization. Attempt to back-run swaps.
@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


    function swapExactETHForTokensSupportingFeeOnTransferTokens(
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external payable virtual {
        ensure(deadline);
        address weth = WETH09;
        if (path[0] != weth) revert InvalidPath();
        address factory = OpenMevLibrary.SUSHI_FACTORY;
        uint256 amountIn = msg.value;
        IWETH(weth).deposit{ value: amountIn }();
        ERC20(weth).safeTransfer(OpenMevLibrary.pairFor(factory, path[0], path[1]), amountIn);
        uint256 balanceBefore = ERC20(path[_dec(path.length)]).balanceOf(to);
        _swapSupportingFeeOnTransferTokens(path, to, factory);
        if (ERC20(path[_dec(path.length)]).balanceOf(to) - balanceBefore < amountOutMin)
            revert InsufficientOutputAmount();
    }

@notice

Identical to swapExactTokensForETH, but succeeds for tokens that take a fee on transfer.

@dev Require has been replaced with revert for gas optimization. Attempt to back-run swaps.
@param amountIn Amount of input tokens to send.
@param amountOutMin Minimum amount of ETH 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


    function swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external virtual {
        ensure(deadline);
        address weth = WETH09;
        if (path[_dec(path.length)] != weth) revert InvalidPath();
        address factory = OpenMevLibrary.SUSHI_FACTORY;
        ERC20(path[0]).safeTransferFrom(msg.sender, OpenMevLibrary.pairFor(factory, path[0], path[1]), amountIn);
        uint256 balanceBefore = ERC20(weth).balanceOf(address(this));
        _swapSupportingFeeOnTransferTokens(path, address(this), factory);
        uint256 amountOut = ERC20(weth).balanceOf(address(this)) - balanceBefore;
        if (amountOut < amountOutMin) revert InsufficientOutputAmount();
        IWETH(weth).withdraw(amountOut);
        SafeTransferLib.safeTransferETH(to, amountOut);
    }

    function quote(
        uint256 amountA,
        uint256 reserveA,
        uint256 reserveB
    ) external pure virtual returns (uint256 amountB) {
        return OpenMevLibrary.quote(amountA, reserveA, reserveB);
    }

    function getAmountOut(
        uint256 amountIn,
        uint256 reserveIn,
        uint256 reserveOut
    ) external pure virtual returns (uint256 amountOut) {
        return OpenMevLibrary.getAmountOut(amountIn, reserveIn, reserveOut);
    }

    function getAmountIn(
        uint256 amountOut,
        uint256 reserveIn,
        uint256 reserveOut
    ) external pure virtual returns (uint256 amountIn) {
        return OpenMevLibrary.getAmountIn(amountOut, reserveIn, reserveOut);
    }

    function getAmountsOut(uint256 amountIn, address[] calldata path)
        external
        view
        virtual
        returns (uint256[] memory amounts)
    {
        return OpenMevLibrary.getAmountsOut(OpenMevLibrary.SUSHI_FACTORY, amountIn, path);
    }

    function getAmountsIn(uint256 amountOut, address[] calldata path)
        external
        view
        virtual
        returns (uint256[] memory amounts)
    {
        return OpenMevLibrary.getAmountsIn(OpenMevLibrary.SUSHI_FACTORY, amountOut, path);
    }

@notice

Internal call to back-run swaps i.e. extract natural MEV at source.

@dev Executes after user swaps.
@param factory Factory address of dex
@param swaps Array of user swap data


    function _backrunSwaps(address factory, OpenMevLibrary.Swap[] memory swaps) internal {
        uint256 length = swaps.length;
        for (uint256 i; i < length; i = _inc(i)) {
            if (swaps[i].isBackrunnable) {
                (address input, address output) = (swaps[i].tokenIn, swaps[i].tokenOut);
                bool isAaveAsset = IS_AAVE_ASSET[output];
                uint256 contractAssetBalance = ERC20(output).balanceOf(address(this));
                uint256 bentoBalance = ERC20(output).balanceOf(BENTO);
                if (_isNonZero(contractAssetBalance) || isAaveAsset || _isNonZero(bentoBalance)) {
                    address factory1 = factory == OpenMevLibrary.SUSHI_FACTORY
                        ? BACKUP_FACTORY
                        : OpenMevLibrary.SUSHI_FACTORY;
                    address pair1 = address(IUniswapV2Factory(factory1).getPair(input, output));
                    if (pair1 != address(0)) {
                        (uint256 optimalAmount, uint256 optimalReturns) = OpenMevLibrary.getOptimalAmounts(
                            swaps[i].pair,
                            pair1,
                            swaps[i].isReverse,
                            isAaveAsset,
                            contractAssetBalance,
                            bentoBalance
                        );
                        if (optimalReturns > uint256(0)) {
                            if (contractAssetBalance >= optimalAmount) {
                                {
                                    uint256 amountOut = _arb(
                                        factory,
                                        factory1,
                                        input,
                                        output,
                                        address(this),
                                        optimalAmount
                                    );
                                    if (amountOut < optimalAmount) revert InsufficientOutputAmount();
                                }
                                emit MEV(msg.sender, output, optimalReturns);
                            } else {
                                // kashi flashloan requires extra hurdle of interest @ 0.05% of loan value and sufficient balance
                                if (optimalReturns > ((optimalAmount * 5) / 10000) && bentoBalance >= optimalAmount) {
                                    _flashSwapKashi(factory, factory1, input, output, optimalAmount, optimalReturns);
                                } else if (optimalReturns > ((optimalAmount * 9) / 10000)) {
                                    // aave flashloan requires extra hurdle of interest @ 0.09% of loan value (https://docs.aave.com/developers/guides/flash-loans)
                                    // check available liquidity for aave asset
                                    if (
                                        IProtocolDataProvider(AAVE_DATA_PROVIDER).getReserveData(output) > optimalAmount
                                    ) {
                                        _flashSwap(factory, factory1, input, output, optimalAmount, optimalReturns);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

@notice

Internal call to perform multiple swaps across multiple dexes with a BentoBox flashloan.
@param factory Factory address for user swap
@param factory1 Factory alternate address
@param input Input address of token for user swap
@param output Output address of token for user swap
@param amountIn Optimal amount in for arbitrage
@param optimalReturns Expected return


    function _flashSwapKashi(
        address factory,
        address factory1,
        address input,
        address output,
        uint256 amountIn,
        uint256 optimalReturns
    ) internal {
        bytes memory params = _encode(factory, factory1, input);
        // try / catch flashloan arb. In case arb reverts, user swap will still succeed.
        try bento.flashLoan(IFlashBorrower(address(this)), address(this), output, amountIn, params) {
            // success
            emit MEV(msg.sender, output, optimalReturns - ((amountIn * 5) / 10000));
        } catch {
            // fail flashloan
            emit LoanError(output, amountIn);
        }
    }

@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 ExecuteNotAuthorized();
        uint256 amountOver;
        {
            (address factory, address factory1, address input) = _decode(data);
            amountOver = _arb(factory, factory1, input, token, sender, amount);
        }
        uint256 amountOwing = amount + fee;
        if (amountOver <= amountOwing) revert InsufficientOutputAmount();
        ERC20(token).safeTransfer(BENTO, amountOwing);
    }

@notice

Internal call to perform multiple swaps across multiple dexes with an Aave flashloan.
@param factory Factory address for user swap
@param factory1 Factory alternate address
@param input Input address of token for user swap
@param output Output address of token for user swap
@param amountIn Optimal amount in for arbitrage
@param optimalReturns Expected return


    function _flashSwap(
        address factory,
        address factory1,
        address input,
        address output,
        uint256 amountIn,
        uint256 optimalReturns
    ) internal {
        // address of the contract receiving the funds
        address receiverAddress = address(this);
        // addresses of the reserves to flashloan
        address[] memory assets = new address[](1);
        assets[0] = output;
        // amounts of assets to flashloan.
        uint256[] memory amounts = new uint256[](1);
        amounts[0] = amountIn;
        // 0 = no debt (just revert), 1 = stable, 2 = variable
        uint256[] memory modes = new uint256[](1);
        modes[0] = 0;
        // compress our 3 addresses
        bytes memory params = _encode(factory, factory1, input);
        // try / catch flashloan arb. In case arb reverts, user swap will still succeed.
        try
            ILendingPool(LENDING_POOL_ADDRESS).flashLoan(
                receiverAddress,
                assets,
                amounts,
                modes,
                receiverAddress,
                params,
                uint16(0)
            )
        {
            // success
            emit MEV(msg.sender, output, optimalReturns - ((amountIn * 9) / 10000));
        } catch {
            // fail
            emit LoanError(output, amountIn);
        }
    }

@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 ExecuteNotAuthorized();
        address asset = assets[0];
        uint256 amountOver;
        {
            uint256 amountIn = amounts[0];
            (address factory, address factory1, address input) = _decode(params);
            amountOver = _arb(factory, factory1, input, asset, initiator, amountIn);
        }
        uint256 amountOwing = amounts[0] + premiums[0];
        if (amountOver <= amountOwing) revert InsufficientOutputAmount();
        ERC20(asset).safeApprove(LENDING_POOL_ADDRESS, amountOwing);
        return true;
    }

@notice

Internal call to perform single swap
@param pair Address of pair to swap in
@param amount0Out AmountOut for token0 of pair
@param amount1Out AmountOut for token1 of pair
@param to Address of receiver


    function _asmSwap(
        address pair,
        uint256 amount0Out,
        uint256 amount1Out,
        address to
    ) internal {
        bytes4 selector = SWAP_SELECTOR;
        assembly {
            let ptr := mload(0x40) // get free memory pointer
            mstore(ptr, selector) // append 4 byte selector
            mstore(add(ptr, 0x04), amount0Out) // append amount0Out
            mstore(add(ptr, 0x24), amount1Out) // append amount1Out
            mstore(add(ptr, 0x44), to) // append to
            mstore(add(ptr, 0x64), 0x80) // append location of byte list
            mstore(add(ptr, 0x84), 0) // append 0 bytes data
            let success := call(
                gas(), // gas remaining
                pair, // destination address
                0, // 0 value
                ptr, // input buffer
                0xA4, // input length
                0, // output buffer
                0 // output length
            )
        }
    }

@notice

Internal call to perform single cross-dex state arbitrage
@param factory Factory address for user swap
@param factory1 Factory alternate address
@param input Input address of token for user swap
@param output Output address of token for user swap
@param amountIn Optimal amount in for arbitrage
@param to Address of receiver
@return amountOut Amount of output token received

    function _arb(
        address factory,
        address factory1,
        address input,
        address output,
        address to,
        uint256 amountIn
    ) internal returns (uint256 amountOut) {
        // first swap output -> input (factory)
        address pair;
        bool isReverse;
        {
            (address token0, address token1) = OpenMevLibrary.sortTokens(output, input);
            pair = OpenMevLibrary._asmPairFor(factory, token0, token1);
            isReverse = output == token0;
        }

        {
            (uint112 reserve0, uint112 reserve1, ) = IUniswapV2Pair(pair).getReserves();
            (uint112 reserveIn, uint112 reserveOut) = isReverse ? (reserve0, reserve1) : (reserve1, reserve0);
            amountOut = OpenMevLibrary.getAmountOut(amountIn, reserveIn, reserveOut);
        }
        ERC20(output).safeTransfer(pair, amountIn);
        (uint256 amount0Out, uint256 amount1Out) = isReverse ? (uint256(0), amountOut) : (amountOut, uint256(0));
        _asmSwap(pair, amount0Out, amount1Out, to);

        // next swap input -> ouput (factory1)
        amountIn = amountOut;
        pair = OpenMevLibrary._asmPairFor(factory1, isReverse ? output : input, isReverse ? input : output);
        {
            (uint112 reserve0, uint112 reserve1, ) = IUniswapV2Pair(pair).getReserves();
            (uint112 reserveIn, uint112 reserveOut) = isReverse ? (reserve1, reserve0) : (reserve0, reserve1);
            amountOut = OpenMevLibrary.getAmountOut(amountIn, reserveIn, reserveOut);
        }

        ERC20(input).safeTransfer(pair, amountIn);
        (amount0Out, amount1Out) = isReverse ? (amountOut, uint256(0)) : (uint256(0), amountOut);
        _asmSwap(pair, amount0Out, amount1Out, to);
    }

@notice

Multi-sig consensus call to distribute a given percentage of specified tokens to specified receivers.
@param percentage Percentage of balance to distribute
@param tokens Array of token addresses to distribute
@param receivers Array of addresses for receiving distribution


    function harvest(
        uint256 percentage,
        address[] calldata tokens,
        address[] calldata receivers
    ) external payable onlyOwner {
        if (percentage > 100 || _isZero(percentage)) revert NotPercent();
        uint256 numReceivers = receivers.length;
        if (_isZero(numReceivers)) revert NoReceivers();
        uint256 numTokens = tokens.length;
        if (_isZero(numTokens)) revert NoTokens();
        uint256 balanceToDistribute;
        for (uint256 i; i < numTokens; i = _inc(i)) {
            address token = tokens[i];
            balanceToDistribute = (ERC20(token).balanceOf(address(this)) * percentage) / (100 * numReceivers);
            if (_isNonZero(balanceToDistribute)) {
                for (uint256 j; j < numReceivers; j = _inc(j)) {
                    ERC20(token).safeTransfer(receivers[j], balanceToDistribute);
                }
            }
        }
    }

@notice

Update internal Aave asset flag
@param isActive Boolean flagging whether to use the asset for Aave flashloans
@param asset Address of asset


    function updateAaveAsset(bool isActive, address asset) external payable onlyOwner {
        IS_AAVE_ASSET[asset] = isActive;
    }

@notice

Update all internal Aave assets


    function updateAllAaveAssets() external payable onlyOwner {
        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

Compresses 3 addresses into byte stream (len = 60)
@param a Address of first param
@param b Address of second param
@param c Address of third param
@return data Compressed byte stream

    function _encode(
        address a,
        address b,
        address c
    ) internal pure returns (bytes memory) {
        bytes memory data = new bytes(60);
        assembly {
            mstore(add(data, 32), shl(96, a))
            mstore(add(data, 52), shl(96, b))
            mstore(add(data, 72), shl(96, c))
        }
        return data;
    }

@notice

De-compresses 3 addresses from byte stream (len = 60)
@param data Compressed byte stream
@return a Address of first param @return b Address of second param @return c Address of third param

    function _decode(bytes memory data)
        internal
        pure
        returns (
            address a,
            address b,
            address c
        )
    {
        assembly {
            a := mload(add(data, 20))
            b := mload(add(data, 40))
            c := mload(add(data, 60))
        }
    }

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