/** * Source Code first verified at https://etherscan.io on Thursday, May 2, 2019 (UTC) */ // File: contracts/IUniswapExchange.sol pragma solidity ^0.5.0; // Solidity Interface contract IUniswapExchange { // Address of ERC20 token sold on this exchange function tokenAddress() external view returns (address token); // Address of Uniswap Factory function factoryAddress() external view returns (address factory); // Provide Liquidity function addLiquidity(uint256 min_liquidity, uint256 max_tokens, uint256 deadline) external payable returns (uint256); function removeLiquidity(uint256 amount, uint256 min_eth, uint256 min_tokens, uint256 deadline) external returns (uint256, uint256); // Get Prices function getEthToTokenInputPrice(uint256 eth_sold) external view returns (uint256 tokens_bought); function getEthToTokenOutputPrice(uint256 tokens_bought) external view returns (uint256 eth_sold); function getTokenToEthInputPrice(uint256 tokens_sold) external view returns (uint256 eth_bought); function getTokenToEthOutputPrice(uint256 eth_bought) external view returns (uint256 tokens_sold); // Trade ETH to ERC20 function ethToTokenSwapInput(uint256 min_tokens, uint256 deadline) external payable returns (uint256 tokens_bought); function ethToTokenTransferInput(uint256 min_tokens, uint256 deadline, address recipient) external payable returns (uint256 tokens_bought); function ethToTokenSwapOutput(uint256 tokens_bought, uint256 deadline) external payable returns (uint256 eth_sold); function ethToTokenTransferOutput(uint256 tokens_bought, uint256 deadline, address recipient) external payable returns (uint256 eth_sold); // Trade ERC20 to ETH function tokenToEthSwapInput(uint256 tokens_sold, uint256 min_eth, uint256 deadline) external returns (uint256 eth_bought); function tokenToEthTransferInput(uint256 tokens_sold, uint256 min_tokens, uint256 deadline, address recipient) external returns (uint256 eth_bought); function tokenToEthSwapOutput(uint256 eth_bought, uint256 max_tokens, uint256 deadline) external returns (uint256 tokens_sold); function tokenToEthTransferOutput(uint256 eth_bought, uint256 max_tokens, uint256 deadline, address recipient) external returns (uint256 tokens_sold); // Trade ERC20 to ERC20 function tokenToTokenSwapInput(uint256 tokens_sold, uint256 min_tokens_bought, uint256 min_eth_bought, uint256 deadline, address token_addr) external returns (uint256 tokens_bought); function tokenToTokenTransferInput(uint256 tokens_sold, uint256 min_tokens_bought, uint256 min_eth_bought, uint256 deadline, address recipient, address token_addr) external returns (uint256 tokens_bought); function tokenToTokenSwapOutput(uint256 tokens_bought, uint256 max_tokens_sold, uint256 max_eth_sold, uint256 deadline, address token_addr) external returns (uint256 tokens_sold); function tokenToTokenTransferOutput(uint256 tokens_bought, uint256 max_tokens_sold, uint256 max_eth_sold, uint256 deadline, address recipient, address token_addr) external returns (uint256 tokens_sold); // Trade ERC20 to Custom Pool function tokenToExchangeSwapInput(uint256 tokens_sold, uint256 min_tokens_bought, uint256 min_eth_bought, uint256 deadline, address exchange_addr) external returns (uint256 tokens_bought); function tokenToExchangeTransferInput(uint256 tokens_sold, uint256 min_tokens_bought, uint256 min_eth_bought, uint256 deadline, address recipient, address exchange_addr) external returns (uint256 tokens_bought); function tokenToExchangeSwapOutput(uint256 tokens_bought, uint256 max_tokens_sold, uint256 max_eth_sold, uint256 deadline, address exchange_addr) external returns (uint256 tokens_sold); function tokenToExchangeTransferOutput(uint256 tokens_bought, uint256 max_tokens_sold, uint256 max_eth_sold, uint256 deadline, address recipient, address exchange_addr) external returns (uint256 tokens_sold); // ERC20 comaptibility for liquidity tokens bytes32 public name; bytes32 public symbol; uint256 public decimals; function transfer(address _to, uint256 _value) external returns (bool); function transferFrom(address _from, address _to, uint256 value) external returns (bool); function approve(address _spender, uint256 _value) external returns (bool); function allowance(address _owner, address _spender) external view returns (uint256); function balanceOf(address _owner) external view returns (uint256); function totalSupply() public view returns (uint256); // Never use function setup(address token_addr) external; } // File: contracts/IUniswapFactory.sol pragma solidity ^0.5.0; // Solidity Interface contract IUniswapFactory { // Public Variables address public exchangeTemplate; uint256 public tokenCount; // Create Exchange function createExchange(address token) external returns (address exchange); // Get Exchange and Token Info function getExchange(address token) external view returns (address exchange); function getToken(address exchange) external view returns (address token); function getTokenWithId(uint256 tokenId) external view returns (address token); // Never use function initializeFactory(address template) external; } // File: contracts/IDutchExchange.sol pragma solidity ^0.5.0; contract IDutchExchange { mapping(address => mapping(address => uint)) public balances; // Token => Token => auctionIndex => amount mapping(address => mapping(address => mapping(uint => uint))) public extraTokens; // Token => Token => auctionIndex => user => amount mapping(address => mapping(address => mapping(uint => mapping(address => uint)))) public sellerBalances; mapping(address => mapping(address => mapping(uint => mapping(address => uint)))) public buyerBalances; mapping(address => mapping(address => mapping(uint => mapping(address => uint)))) public claimedAmounts; function ethToken() public view returns(address); function claimBuyerFunds(address, address, address, uint) public returns(uint, uint); function deposit(address tokenAddress, uint amount) public returns (uint); function withdraw(address tokenAddress, uint amount) public returns (uint); function getAuctionIndex(address token1, address token2) public returns(uint256); function postBuyOrder(address token1, address token2, uint256 auctionIndex, uint256 amount) public returns(uint256); function postSellOrder(address token1, address token2, uint256 auctionIndex, uint256 tokensBought) public returns(uint256, uint256); function getCurrentAuctionPrice(address token1, address token2, uint256 auctionIndex) public view returns(uint256, uint256); function claimAndWithdrawTokensFromSeveralAuctionsAsBuyer(address[] calldata, address[] calldata, uint[] calldata) external view returns(uint[] memory, uint); } // File: contracts/ITokenMinimal.sol pragma solidity ^0.5.0; contract ITokenMinimal { function allowance(address tokenOwner, address spender) public view returns (uint remaining); function balanceOf(address tokenOwner) public view returns (uint balance); function deposit() public payable; function withdraw(uint value) public; } // File: openzeppelin-solidity/contracts/utils/Address.sol pragma solidity ^0.5.2; /** * Utility library of inline functions on addresses */ library Address { /** * Returns whether the target address is a contract * @dev This function will return false if invoked during the constructor of a contract, * as the code is not actually created until after the constructor finishes. * @param account address of the account to check * @return whether the target address is a contract */ function isContract(address account) internal view returns (bool) { uint256 size; // XXX Currently there is no better way to check if there is a contract in an address // than to check the size of the code at that address. // See https://ethereum.stackexchange.com/a/14016/36603 // for more details about how this works. // TODO Check this again before the Serenity release, because all addresses will be // contracts then. // solhint-disable-next-line no-inline-assembly assembly { size := extcodesize(account) } return size > 0; } } // File: openzeppelin-solidity/contracts/token/ERC20/IERC20.sol pragma solidity ^0.5.2; /** * @title ERC20 interface * @dev see https://eips.ethereum.org/EIPS/eip-20 */ interface IERC20 { function transfer(address to, uint256 value) external returns (bool); function approve(address spender, uint256 value) external returns (bool); function transferFrom(address from, address to, uint256 value) external returns (bool); function totalSupply() external view returns (uint256); function balanceOf(address who) external view returns (uint256); function allowance(address owner, address spender) external view returns (uint256); event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); } // File: contracts/SafeERC20.sol /* SafeERC20 by daostack. The code is based on a fix by SECBIT Team. USE WITH CAUTION & NO WARRANTY REFERENCE & RELATED READING - https://github.com/ethereum/solidity/issues/4116 - https://medium.com/@chris_77367/explaining-unexpected-reverts-starting-with-solidity-0-4-22-3ada6e82308c - https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca - https://gist.github.com/BrendanChou/88a2eeb80947ff00bcf58ffdafeaeb61 */ pragma solidity ^0.5.0; library SafeERC20 { using Address for address; bytes4 constant private TRANSFER_SELECTOR = bytes4(keccak256(bytes("transfer(address,uint256)"))); bytes4 constant private TRANSFERFROM_SELECTOR = bytes4(keccak256(bytes("transferFrom(address,address,uint256)"))); bytes4 constant private APPROVE_SELECTOR = bytes4(keccak256(bytes("approve(address,uint256)"))); function safeTransfer(address _erc20Addr, address _to, uint256 _value) internal { // Must be a contract addr first! require(_erc20Addr.isContract(), "ERC20 is not a contract"); (bool success, bytes memory returnValue) = // solhint-disable-next-line avoid-low-level-calls _erc20Addr.call(abi.encodeWithSelector(TRANSFER_SELECTOR, _to, _value)); // call return false when something wrong require(success, "safeTransfer must succeed"); //check return value require(returnValue.length == 0 || (returnValue.length == 32 && (returnValue[31] != 0)), "safeTransfer must return nothing or true"); } function safeTransferFrom(address _erc20Addr, address _from, address _to, uint256 _value) internal { // Must be a contract addr first! require(_erc20Addr.isContract(), "ERC20 is not a contract"); (bool success, bytes memory returnValue) = // solhint-disable-next-line avoid-low-level-calls _erc20Addr.call(abi.encodeWithSelector(TRANSFERFROM_SELECTOR, _from, _to, _value)); // call return false when something wrong require(success, "safeTransferFrom must succeed"); //check return value require(returnValue.length == 0 || (returnValue.length == 32 && (returnValue[31] != 0)), "safeTransferFrom must return nothing or true"); } function safeApprove(address _erc20Addr, address _spender, uint256 _value) internal { // Must be a contract addr first! require(_erc20Addr.isContract(), "ERC20 is not a contract"); // vvv // This section has been commented out because it is not a necesarry safeguard // vvv /* // safeApprove should only be called when setting an initial allowance, // or when resetting it to zero. require((_value == 0) || (IERC20(_erc20Addr).allowance(address(this), _spender) == 0), "safeApprove should only be called when setting an initial allowance, or when resetting it to zero."); */ (bool success, bytes memory returnValue) = // solhint-disable-next-line avoid-low-level-calls _erc20Addr.call(abi.encodeWithSelector(APPROVE_SELECTOR, _spender, _value)); // call return false when something wrong require(success, "safeApprove must succeed"); //check return value require(returnValue.length == 0 || (returnValue.length == 32 && (returnValue[31] != 0)), "safeApprove must return nothing or true"); } } // File: openzeppelin-solidity/contracts/ownership/Ownable.sol pragma solidity ^0.5.2; /** * @title Ownable * @dev The Ownable contract has an owner address, and provides basic authorization control * functions, this simplifies the implementation of "user permissions". */ contract Ownable { address private _owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev The Ownable constructor sets the original `owner` of the contract to the sender * account. */ constructor () internal { _owner = msg.sender; emit OwnershipTransferred(address(0), _owner); } /** * @return the address of the owner. */ function owner() public view returns (address) { return _owner; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(isOwner()); _; } /** * @return true if `msg.sender` is the owner of the contract. */ function isOwner() public view returns (bool) { return msg.sender == _owner; } /** * @dev Allows the current owner to relinquish control of the contract. * It will not be possible to call the functions with the `onlyOwner` * modifier anymore. * @notice Renouncing ownership will leave the contract without an owner, * thereby removing any functionality that is only available to the owner. */ function renounceOwnership() public onlyOwner { emit OwnershipTransferred(_owner, address(0)); _owner = address(0); } /** * @dev Allows the current owner to transfer control of the contract to a newOwner. * @param newOwner The address to transfer ownership to. */ function transferOwnership(address newOwner) public onlyOwner { _transferOwnership(newOwner); } /** * @dev Transfers control of the contract to a newOwner. * @param newOwner The address to transfer ownership to. */ function _transferOwnership(address newOwner) internal { require(newOwner != address(0)); emit OwnershipTransferred(_owner, newOwner); _owner = newOwner; } } // File: contracts/Arbitrage.sol pragma solidity ^0.5.0; /// @title Uniswap Arbitrage - Executes arbitrage transactions between Uniswap and DutchX. /// @author Billy Rennekamp - <[email protected]> contract Arbitrage is Ownable { uint constant max = uint(-1); IUniswapFactory public uniFactory; IDutchExchange public dutchXProxy; event Profit(uint profit, bool wasDutchOpportunity); /// @dev Payable fallback function has nothing inside so it won't run out of gas with gas limited transfers function() external payable {} /// @dev Only owner can deposit contract Ether into the DutchX as WETH function depositEther() public payable onlyOwner { require(address(this).balance > 0, "Balance must be greater than 0 to deposit"); uint balance = address(this).balance; // // Deposit balance to WETH address weth = dutchXProxy.ethToken(); ITokenMinimal(weth).deposit.value(balance)(); uint wethBalance = ITokenMinimal(weth).balanceOf(address(this)); uint allowance = ITokenMinimal(weth).allowance(address(this), address(dutchXProxy)); if (allowance < wethBalance) { // Approve max amount of WETH to be transferred by dutchX // Keeping it max will have same or similar costs to making it exact over and over again SafeERC20.safeApprove(weth, address(dutchXProxy), max); } // Deposit new amount on dutchX, confirm there's at least the amount we just deposited uint newBalance = dutchXProxy.deposit(weth, balance); require(newBalance >= balance, "Deposit WETH to DutchX didn't work."); } /// @dev Only owner can withdraw WETH from DutchX, convert to Ether and transfer to owner /// @param amount The amount of Ether to withdraw function withdrawEtherThenTransfer(uint amount) external onlyOwner { _withdrawEther(amount); address(uint160(owner())).transfer(amount); } /// @dev Only owner can transfer any Ether currently in the contract to the owner address. /// @param amount The amount of Ether to withdraw function transferEther(uint amount) external onlyOwner { // If amount is zero, deposit the entire contract balance. address(uint160(owner())).transfer(amount == 0 ? address(this).balance : amount); } /// @dev Only owner function to withdraw WETH from the DutchX, convert it to Ether and keep it in contract /// @param amount The amount of WETH to withdraw and convert. function withdrawEther(uint amount) external onlyOwner { _withdrawEther(amount); } /// @dev Internal function to withdraw WETH from the DutchX, convert it to Ether and keep it in contract /// @param amount The amount of WETH to withdraw and convert. function _withdrawEther(uint amount) internal { address weth = dutchXProxy.ethToken(); dutchXProxy.withdraw(weth, amount); ITokenMinimal(weth).withdraw(amount); } /// @dev Only owner can withdraw a token from the DutchX /// @param token The token address that is being withdrawn. /// @param amount The amount of token to withdraw. Can be larger than available balance and maximum will be withdrawn. /// @return Returns the amount actually withdrawn from the DutchX function withdrawToken(address token, uint amount) external onlyOwner returns (uint) { return dutchXProxy.withdraw(token, amount); } /// @dev Only owner can transfer tokens to the owner that belong to this contract /// @param token The token address that is being transferred. /// @param amount The amount of token to transfer. function transferToken(address token, uint amount) external onlyOwner { SafeERC20.safeTransfer(token, owner(), amount); } /// @dev Only owner can approve tokens to be used by the DutchX /// @param token The token address to be approved for use /// @param allowance The amount of tokens that should be approved function approveToken(address token, uint allowance) external onlyOwner { SafeERC20.safeApprove(token, address(dutchXProxy), allowance); } /// @dev Only owner can deposit token to the DutchX /// @param token The token address that is being deposited. /// @param amount The amount of token to deposit. function depositToken(address token, uint amount) external onlyOwner { _depositToken(token, amount); } /// @dev Internal function to deposit token to the DutchX /// @param token The token address that is being deposited. /// @param amount The amount of token to deposit. function _depositToken(address token, uint amount) internal { uint allowance = ITokenMinimal(token).allowance(address(this), address(dutchXProxy)); if (allowance < amount) { SafeERC20.safeApprove(token, address(dutchXProxy), max); } // Confirm that the balance of the token on the DutchX is at least how much was deposited uint newBalance = dutchXProxy.deposit(token, amount); require(newBalance >= amount, "deposit didn't work"); } /// @dev Executes a trade opportunity on dutchX. Assumes that there is a balance of WETH already on the dutchX /// @param arbToken Address of the token that should be arbitraged. /// @param amount Amount of Ether to use in arbitrage. /// @return Returns if transaction can be executed. function dutchOpportunity(address arbToken, uint256 amount) external onlyOwner { address etherToken = dutchXProxy.ethToken(); // The order of parameters for getAuctionIndex don't matter uint256 dutchAuctionIndex = dutchXProxy.getAuctionIndex(arbToken, etherToken); // postBuyOrder(sellToken, buyToken, amount) // results in a decrease of the amount the user owns of the second token // which means the buyToken is what the buyer wants to get rid of. // "The buy token is what the buyer provides, the seller token is what the seller provides." dutchXProxy.postBuyOrder(arbToken, etherToken, dutchAuctionIndex, amount); (uint tokensBought, ) = dutchXProxy.claimBuyerFunds(arbToken, etherToken, address(this), dutchAuctionIndex); dutchXProxy.withdraw(arbToken, tokensBought); address uniswapExchange = uniFactory.getExchange(arbToken); uint allowance = ITokenMinimal(arbToken).allowance(address(this), address(uniswapExchange)); if (allowance < tokensBought) { // Approve Uniswap to transfer arbToken on contract's behalf // Keeping it max will have same or similar costs to making it exact over and over again SafeERC20.safeApprove(arbToken, address(uniswapExchange), max); } // tokenToEthSwapInput(inputToken, minimumReturn, timeToLive) // minimumReturn is enough to make a profit (excluding gas) // timeToLive is now because transaction is atomic uint256 etherReturned = IUniswapExchange(uniswapExchange).tokenToEthSwapInput(tokensBought, 1, block.timestamp); // gas costs were excluded because worse case scenario the tx fails and gas costs were spent up to here anyway // best worst case scenario the profit from the trade alleviates part of the gas costs even if still no total profit require(etherReturned >= amount, "no profit"); emit Profit(etherReturned, true); // Ether is deposited as WETH depositEther(); } /// @dev Executes a trade opportunity on uniswap. /// @param arbToken Address of the token that should be arbitraged. /// @param amount Amount of Ether to use in arbitrage. /// @return Returns if transaction can be executed. function uniswapOpportunity(address arbToken, uint256 amount) external onlyOwner { // WETH must be converted to Eth for Uniswap trade // (Uniswap allows ERC20:ERC20 but most liquidity is on ETH:ERC20 markets) _withdrawEther(amount); require(address(this).balance >= amount, "buying from uniswap takes real Ether"); // ethToTokenSwapInput(minTokens, deadline) // minTokens is 1 because it will revert without a profit regardless // deadline is now since trade is atomic // solium-disable-next-line security/no-block-members uint256 tokensBought = IUniswapExchange(uniFactory.getExchange(arbToken)).ethToTokenSwapInput.value(amount)(1, block.timestamp); // tokens need to be approved for the dutchX before they are deposited _depositToken(arbToken, tokensBought); address etherToken = dutchXProxy.ethToken(); // The order of parameters for getAuctionIndex don't matter uint256 dutchAuctionIndex = dutchXProxy.getAuctionIndex(arbToken, etherToken); // spend max amount of tokens currently on the dutch x (might be combined from previous remainders) // max is automatically reduced to maximum available tokens because there may be // token remainders from previous auctions which closed after previous arbitrage opportunities dutchXProxy.postBuyOrder(etherToken, arbToken, dutchAuctionIndex, max); // solium-disable-next-line no-unused-vars (uint etherReturned, ) = dutchXProxy.claimBuyerFunds(etherToken, arbToken, address(this), dutchAuctionIndex); // gas costs were excluded because worse case scenario the tx fails and gas costs were spent up to here anyway // best worst case scenario the profit from the trade alleviates part of the gas costs even if still no total profit require(etherReturned >= amount, "no profit"); emit Profit(etherReturned, false); // Ether returned is already in dutchX balance where Ether is assumed to be stored when not being used. } } // File: contracts/ArbitrageMainnet.sol pragma solidity ^0.5.0; /// @title Uniswap Arbitrage Module - Executes arbitrage transactions between Uniswap and DutchX. /// @author Billy Rennekamp - <[email protected]> contract ArbitrageMainnet is Arbitrage { constructor() public { uniFactory = IUniswapFactory(0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95); dutchXProxy = IDutchExchange(0xaf1745c0f8117384Dfa5FFf40f824057c70F2ed3); } }