├── .soliumignore ├── .gitattributes ├── contracts ├── Mock.sol ├── IToken.sol ├── ITokenMinimal.sol ├── ArbitrageKovan.sol ├── ArbitrageMainnet.sol ├── ArbitrageRinkeby.sol ├── ArbitrageDependencies.sol ├── ArbitrageLocal.sol ├── Migrations.sol ├── IUniswapFactory.sol ├── IDutchExchange.sol ├── SafeERC20.sol ├── IUniswapExchange.sol └── Arbitrage.sol ├── index.js ├── src ├── migrations-truffle-5 │ ├── index.js │ ├── EXAMPLE_migrate_all.js │ └── 2_deploy_uniswap.js ├── inject_network_info.js ├── extract_network_info.js ├── conf │ └── network-restore.js ├── inject_network_info_deps_gno.js └── inject_network_info_deps_utils.js ├── migrations ├── 1_initial_migration.js ├── 3_deploy_Arbitrage.js └── 2_DEV_local.js ├── .soliumrc.json ├── .npmignore ├── .gitignore ├── .env.example ├── networks.json ├── .solcover.js ├── README.md ├── package.json ├── scripts ├── Debug.js ├── Rinkeby.js └── uniswap-bytecode.js ├── abi ├── UniswapFactoryInterface.json ├── UniswapExchange.json └── DutchExchange.json ├── truffle.js ├── flat ├── SafeERC20.sol └── ArbitrageRinkeby.sol └── test └── Arbitrage.test.js /.soliumignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity -------------------------------------------------------------------------------- /contracts/Mock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | import "@gnosis.pm/mock-contract/contracts/MockContract.sol"; 3 | contract Mock {} 4 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var Arbitrage = require('./build/contracts/Arbitrage.json') 2 | 3 | module.exports = { 4 | Arbitrage: Arbitrage 5 | } 6 | -------------------------------------------------------------------------------- /src/migrations-truffle-5/index.js: -------------------------------------------------------------------------------- 1 | const deployUniswap = require('./2_deploy_uniswap') 2 | 3 | module.exports = async params => { 4 | await deployUniswap(params) 5 | } 6 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /.soliumrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solium:recommended", 3 | "plugins": ["security"], 4 | "rules": { 5 | "quotes": ["error", "double"], 6 | "indentation": ["error", 4] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /contracts/IToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | import "@gnosis.pm/util-contracts/contracts/EtherToken.sol"; 3 | contract IToken is EtherToken { 4 | string public constant symbol = "UNI"; 5 | string public constant name = "UNI Token"; 6 | } 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log 5 | yarn-error.log 6 | 7 | # Dependency directory 8 | node_modules 9 | dapp-module 10 | installed_contracts 11 | 12 | # OSX 13 | .DS_Store 14 | 15 | .env 16 | .history 17 | coverage 18 | 19 | coverage.json 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log 5 | yarn-error.log 6 | 7 | # Dependency directory 8 | node_modules 9 | dapp-module 10 | installed_contracts 11 | 12 | # OSX 13 | .DS_Store 14 | 15 | .env 16 | .history 17 | dist 18 | build 19 | coverage 20 | 21 | coverage.json 22 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | TRUFFLE_MNEMONIC=candy maple cake sugar pudding cream honey rich smooth crumble sweet treat 2 | GANACHE_MNEMONIC=grid voyage cream cry fence load stove sort grief fuel room save 3 | TESTNET_MNEMONIC=a twelve word mnemonic phrase that works with some test network buddy 4 | INFURA_API_KEY=yOUrInfURaKEy -------------------------------------------------------------------------------- /src/inject_network_info.js: -------------------------------------------------------------------------------- 1 | const injectNetworks = require('@gnosis.pm/util-contracts/src/util/injectNetworks') 2 | const path = require('path') 3 | 4 | const DEFAULT_CONF_FILE = path.join(__dirname, './conf/network-restore') 5 | 6 | const confFile = process.env.CONF_FILE || DEFAULT_CONF_FILE 7 | injectNetworks(confFile) 8 | .catch(console.error) 9 | -------------------------------------------------------------------------------- /src/extract_network_info.js: -------------------------------------------------------------------------------- 1 | const extractNetworks = require('@gnosis.pm/util-contracts/src/util/extractNetworks') 2 | const path = require('path') 3 | 4 | const DEFAULT_CONF_FILE = path.join(__dirname, './conf/network-restore') 5 | 6 | const confFile = process.env.CONF_FILE || DEFAULT_CONF_FILE 7 | extractNetworks(confFile) 8 | .catch(console.error) 9 | -------------------------------------------------------------------------------- /contracts/ITokenMinimal.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | contract ITokenMinimal { 4 | function allowance(address tokenOwner, address spender) public view returns (uint remaining); 5 | function balanceOf(address tokenOwner) public view returns (uint balance); 6 | function deposit() public payable; 7 | function withdraw(uint value) public; 8 | } -------------------------------------------------------------------------------- /src/conf/network-restore.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const BASE_DIR = path.join(__dirname, '../..') 4 | const BUILD_DIR = path.join(BASE_DIR, 'build/contracts') 5 | const NETWORKS_FILE_PATH = path.join(BASE_DIR, 'networks.json') 6 | 7 | module.exports = { 8 | buildPath: BUILD_DIR, 9 | networkFilePath: NETWORKS_FILE_PATH, 10 | buildDirDependencies: [] 11 | } 12 | -------------------------------------------------------------------------------- /src/inject_network_info_deps_gno.js: -------------------------------------------------------------------------------- 1 | const injectNetworksDeps = require('@gnosis.pm/util-contracts/src/util/injectNetworksDeps') 2 | const path = require('path') 3 | 4 | const NODE_MODULES_PATH = path.join(__dirname, '../node_modules') 5 | 6 | injectNetworksDeps({ 7 | buildPath: '@gnosis.pm/gno-token/build/contracts', 8 | packages: [ 9 | // '@gnosis.pm/owl-token' 10 | ], 11 | nodeModulesPath: NODE_MODULES_PATH 12 | }).catch(console.error) 13 | -------------------------------------------------------------------------------- /contracts/ArbitrageKovan.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | import "./Arbitrage.sol"; 3 | /// @title Uniswap Arbitrage Module - Executes arbitrage transactions between Uniswap and DutchX. 4 | /// @author Billy Rennekamp - 5 | contract ArbitrageKovan is Arbitrage { 6 | constructor() public { 7 | uniFactory = IUniswapFactory(0x2CF4E258f420ddFc0757321e7fE555E6150c2533); 8 | dutchXProxy = IDutchExchange(0x775ea749a82A87f12199019E5166980F305f4C8F); 9 | } 10 | } -------------------------------------------------------------------------------- /contracts/ArbitrageMainnet.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | import "./Arbitrage.sol"; 3 | /// @title Uniswap Arbitrage Module - Executes arbitrage transactions between Uniswap and DutchX. 4 | /// @author Billy Rennekamp - 5 | contract ArbitrageMainnet is Arbitrage { 6 | constructor() public { 7 | uniFactory = IUniswapFactory(0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95); 8 | dutchXProxy = IDutchExchange(0xb9812E2fA995EC53B5b6DF34d21f9304762C5497); 9 | } 10 | } -------------------------------------------------------------------------------- /contracts/ArbitrageRinkeby.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | import "./Arbitrage.sol"; 3 | /// @title Uniswap Arbitrage Module - Executes arbitrage transactions between Uniswap and DutchX. 4 | /// @author Billy Rennekamp - 5 | contract ArbitrageRinkeby is Arbitrage { 6 | constructor() public { 7 | uniFactory = IUniswapFactory(0xf5D915570BC477f9B8D6C0E980aA81757A3AaC36); 8 | dutchXProxy = IDutchExchange(0xaAEb2035FF394fdB2C879190f95e7676f1A9444B); 9 | } 10 | } -------------------------------------------------------------------------------- /src/inject_network_info_deps_utils.js: -------------------------------------------------------------------------------- 1 | const injectNetworksDeps = require('@gnosis.pm/util-contracts/src/util/injectNetworksDeps') 2 | const path = require('path') 3 | 4 | const NODE_MODULES_PATH = path.join(__dirname, '../node_modules') 5 | 6 | injectNetworksDeps({ 7 | buildPath: '@gnosis.pm/util-contracts/build/contracts', 8 | packages: [ 9 | // '@gnosis.pm/gno-token', 10 | // '@gnosis.pm/owl-token' 11 | ], 12 | nodeModulesPath: NODE_MODULES_PATH 13 | }).catch(console.error) 14 | -------------------------------------------------------------------------------- /contracts/ArbitrageDependencies.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | // NOTE: 4 | // This file porpouse is just to make sure truffle compiles all of depending 5 | // contracts when we are in development. 6 | // 7 | // For other environments, we just use the compiled contracts from the NPM 8 | // package 9 | 10 | import "./SafeERC20.sol"; 11 | import "./ArbitrageLocal.sol"; 12 | import "./IUniswapFactory.sol"; 13 | import "./IUniswapExchange.sol"; 14 | import "./IToken.sol"; 15 | 16 | contract ArbitrageDependencies {} -------------------------------------------------------------------------------- /contracts/ArbitrageLocal.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | import "./Arbitrage.sol"; 3 | /// @title Uniswap Arbitrage Module - Executes arbitrage transactions between Uniswap and DutchX. 4 | /// @author Billy Rennekamp - 5 | contract ArbitrageLocal is Arbitrage { 6 | /// @dev Constructor function sets initial storage of contract. 7 | /// @param _uniFactory The uniswap factory deployed address. 8 | /// @param _dutchXProxy The dutchX proxy deployed address. 9 | constructor(IUniswapFactory _uniFactory, IDutchExchange _dutchXProxy) public { 10 | uniFactory = _uniFactory; 11 | dutchXProxy = _dutchXProxy; 12 | } 13 | } -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | constructor() public { 8 | owner = msg.sender; 9 | } 10 | 11 | modifier restricted() { 12 | if (msg.sender == owner) _; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/IUniswapFactory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | // Solidity Interface 4 | 5 | contract IUniswapFactory { 6 | // Public Variables 7 | address public exchangeTemplate; 8 | uint256 public tokenCount; 9 | // Create Exchange 10 | function createExchange(address token) external returns (address exchange); 11 | // Get Exchange and Token Info 12 | function getExchange(address token) external view returns (address exchange); 13 | function getToken(address exchange) external view returns (address token); 14 | function getTokenWithId(uint256 tokenId) external view returns (address token); 15 | // Never use 16 | function initializeFactory(address template) external; 17 | } 18 | -------------------------------------------------------------------------------- /networks.json: -------------------------------------------------------------------------------- 1 | { 2 | "ArbitrageRinkeby": { 3 | "4": { 4 | "events": {}, 5 | "links": {}, 6 | "address": "0x457f91712d8Db46496fe1e0c67cABBCc7e997563", 7 | "transactionHash": "0x8262468229287e5af765862b5ca57f1af6707735efbee6443caaae69b25db50d" 8 | } 9 | }, 10 | "Migrations": { 11 | "4": { 12 | "events": {}, 13 | "links": {}, 14 | "address": "0x88a4f1cB8b3c0678a9a3C0D71530E781B9e7BdDb", 15 | "transactionHash": "0x0a70e4c655c13f2bc4c7bdbb9d6e58290108d24313b06a2bb5a63cae6e635ff1" 16 | } 17 | }, 18 | "SafeERC20": { 19 | "4": { 20 | "events": {}, 21 | "links": {}, 22 | "address": "0xfbCb3C64BDE15C19444CE0783D21aA64F1681e96", 23 | "transactionHash": "0x7996373861272ef66155ec915957231bb927665767b283bf39d7f0d458880ba8" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: 8556, 3 | skipFiles: [ 4 | 'EtherToken.sol', 5 | 'GnosisStandardToken.sol' 6 | ], 7 | testrpcOptions: '--port 8556 --account=0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d,50000000000000000000000 --account=0x6cbed15c793ce57650b9877cf6fa156fbef513c4e6134f022a85b1ffdd59b2a1,50000000000000000000000 --account=0x6370fd033278c143179d81c5526140625662b8daa446c22ee2d73db3707e620c,50000000000000000000000 --account=0x646f1ce2fdad0e6deeeb5c7e8e5543bdde65e86029e2fd9fc169899c440a7913,50000000000000000000000 --account=0xadd53f9a7e588d003326d1cbf9e4a43c061aadd9bc938c843a79e7b4fd2ad743,50000000000000000000000 --account=0x395df67f0c2d2d9fe1ad08d1bc8b6627011959b79c53d7dd6a3536a33ab8a4fd,50000000000000000000000 --account=0xe485d098507f54e7733a205420dfddbe58db035fa577fc294ebd14db90767a52,50000000000000000000000', 8 | testCommand: 'npx truffle test test/*.test.js', 9 | copyPackages: ['@gnosis.pm'] 10 | } 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Arbitrage DutchX/Uniswap 2 | Contract that arbitrages between: 3 | * DutchX protocol: [http://dutchx.readthedocs.io/en/latest/](http://dutchx.readthedocs.io/en/latest/) 4 | * Uniswap protocol: [https://uniswap.io/](https://uniswap.io/) 5 | 6 | ## Setup 7 | ``` 8 | # Install dependencies 9 | yarn 10 | 11 | # Compile contracts and inject networks 12 | yarn restore 13 | 14 | # Check out the contract addresses 15 | yarn networks 16 | ``` 17 | 18 | Create a file `.env` using [.env.example](.env.example) as your template. 19 | 20 | ```bash 21 | # Create env file 22 | cp .env.example .env 23 | ``` 24 | ## Run 25 | ``` 26 | yarn lint:watch 27 | ``` 28 | 29 | ## Test 30 | ``` 31 | truffle develop 32 | yarn test 33 | ``` 34 | 35 | ## Deploy 36 | ``` 37 | # Local: Run ganache (rpc) in one tab, and then migrate 38 | yarn rpc 39 | yarn migrate 40 | 41 | # Rinkeby 42 | yarn migrate --network rinkeby 43 | 44 | # Mainnet 45 | yarn mainnet --network mainnet 46 | ``` 47 | -------------------------------------------------------------------------------- /src/migrations-truffle-5/EXAMPLE_migrate_all.js: -------------------------------------------------------------------------------- 1 | /* global artifacts, web3 */ 2 | /* eslint no-undef: "error" */ 3 | 4 | const deployUtils = require('@gnosis.pm/util-contracts/src/migrations-truffle-4') 5 | const deployGno = require('@gnosis.pm/gno-token/src/migrations-truffle-4') 6 | const deployOwl = require('@gnosis.pm/owl-token/src/migrations-truffle-4') 7 | 8 | const migrationsDx = require('@gnosis.pm/dx-contracts/src/migrations-truffle-4') 9 | 10 | module.exports = async (deployer, network, accounts) => { 11 | if (network === 'development') { 12 | const deployParams = { 13 | artifacts, 14 | deployer, 15 | network, 16 | accounts, 17 | web3, 18 | initialTokenAmount: process.env.GNO_TOKEN_AMOUNT, 19 | gnoLockPeriodInHours: process.env.GNO_LOCK_PERIOD_IN_HOURS, 20 | thresholdNewTokenPairUsd: process.env.GNO_LOCK_PERIOD_IN_HOURS, 21 | thresholdAuctionStartUsd: process.env.GNO_LOCK_PERIOD_IN_HOURS 22 | } 23 | 24 | await deployUtils(deployParams) 25 | await deployGno(deployParams) 26 | await deployOwl(deployParams) 27 | await migrationsDx(deployParams) 28 | } else { 29 | throw new Error('Migrations are just for development. Current network is %s', network) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /migrations/3_deploy_Arbitrage.js: -------------------------------------------------------------------------------- 1 | const SafeERC20 = artifacts.require('./SafeERC20.sol') 2 | const _ = ' ' 3 | 4 | module.exports = (deployer, network) => { 5 | deployer.then(async () => { 6 | const contractName = getContractName(network) 7 | if (contractName) { 8 | const ArbitrageContract = artifacts.require(contractName) 9 | // Deploy SafeERC20 and link to ArbitrageRinkeby.sol 10 | await deployer.deploy(SafeERC20); 11 | await deployer.link(SafeERC20, ArbitrageContract); 12 | 13 | // Deploy ArbitrageRinkeby.sol 14 | await deployer.deploy(ArbitrageContract) 15 | const arbitrage = await ArbitrageContract.deployed() 16 | 17 | console.log(_ + 'ArbitrageContract deployed at: ' + arbitrage.address) 18 | } 19 | }) 20 | } 21 | 22 | function getContractName(network) { 23 | if (network === 'development-fork' || network === 'development') { 24 | console.log(_ + 'Skip Migration: Local deployment is already done in 2nd migration') 25 | return null 26 | } else if (network === 'rinkeby-fork' || network === 'rinkeby') { 27 | return 'ArbitrageRinkeby' 28 | } else if (network === 'kovan-fork' || network === 'kovan') { 29 | return 'ArbitrageKovan' 30 | } else if (network === 'mainnet-fork' || network === 'mainnet') { 31 | return 'ArbitrageMainnet' 32 | } else { 33 | throw new Error('Unknown network: ' + network) 34 | } 35 | } -------------------------------------------------------------------------------- /contracts/IDutchExchange.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | contract IDutchExchange { 4 | 5 | 6 | mapping(address => mapping(address => uint)) public balances; 7 | 8 | // Token => Token => auctionIndex => amount 9 | mapping(address => mapping(address => mapping(uint => uint))) public extraTokens; 10 | 11 | // Token => Token => auctionIndex => user => amount 12 | mapping(address => mapping(address => mapping(uint => mapping(address => uint)))) public sellerBalances; 13 | mapping(address => mapping(address => mapping(uint => mapping(address => uint)))) public buyerBalances; 14 | mapping(address => mapping(address => mapping(uint => mapping(address => uint)))) public claimedAmounts; 15 | 16 | 17 | function ethToken() public view returns(address); 18 | function claimBuyerFunds(address, address, address, uint) public returns(uint, uint); 19 | function deposit(address tokenAddress, uint amount) public returns (uint); 20 | function withdraw(address tokenAddress, uint amount) public returns (uint); 21 | function getAuctionIndex(address token1, address token2) public returns(uint256); 22 | function postBuyOrder(address token1, address token2, uint256 auctionIndex, uint256 amount) public returns(uint256); 23 | function postSellOrder(address token1, address token2, uint256 auctionIndex, uint256 tokensBought) public returns(uint256, uint256); 24 | function getCurrentAuctionPrice(address token1, address token2, uint256 auctionIndex) public view returns(uint256, uint256); 25 | function claimAndWithdrawTokensFromSeveralAuctionsAsBuyer(address[] calldata, address[] calldata, uint[] calldata) external view returns(uint[] memory, uint); 26 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@gnosis.pm/dx-uniswap-arbitrage", 3 | "version": "0.1.5", 4 | "description": "An Ethereum contract to execute arbitrage functions against DutchX protocol", 5 | "scripts": { 6 | "rpc": "ganache-cli -d --mnemonic 'grid voyage cream cry fence load stove sort grief fuel room save' --defaultBalanceEther '500000000000000000000'", 7 | "test": "truffle test test/*.test.js", 8 | "coverage": "npx solidity-coverage", 9 | "preversion": "npm run restore", 10 | "deploy": "truffle migrate --reset --compile-all", 11 | "lint": "solium --dir contracts/", 12 | "lint:watch": "solium --watch --dir contracts/", 13 | "lint:fix": "solium --dir contracts/ --fix", 14 | "prettier": "prettier --write --tab-width 4 --print-width 140 'contracts/**/*.sol'", 15 | "networks-extract": "node src/extract_network_info.js", 16 | "networks-inject": "node src/inject_network_info.js", 17 | "networks-reset": "mkdir -p build/contracts && npx truffle networks --clean && npm run networks-inject", 18 | "compile": "npx truffle compile", 19 | "restore": "rm -rf build && npm run compile && npm run networks-reset", 20 | "networks": "npx truffle networks", 21 | "migrate": "npx truffle migrate --reset --compile-all", 22 | "debug": "npx truffle compile --reset --compile-all && npx truffle test test/Debug.js", 23 | "install": "cd $INIT_CWD && npm explore truffle -- npm install solc@0.5.2" 24 | }, 25 | "main": "index.js", 26 | "directories": { 27 | "test": "test" 28 | }, 29 | "devDependencies": { 30 | "@gnosis.pm/util-contracts": "^2.0.0", 31 | "bignumber.js": "^8.0.1", 32 | "dotenv": "^7.0.0", 33 | "ganache-cli": "^6.4.2", 34 | "openzeppelin-solidity": "^2.2.0", 35 | "prettier": "^1.16.4", 36 | "prettier-plugin-solidity-refactor": "^1.0.0-alpha.14", 37 | "solidity-coverage": "^0.5.11", 38 | "solium": "^1.1.7", 39 | "truffle": "^5.0.2", 40 | "truffle-hdwallet-provider": "^1.0.8", 41 | "verify-on-etherscan": "^1.1.1" 42 | }, 43 | "dependencies": { 44 | "@gnosis.pm/mock-contract": "^3.0.7", 45 | "solc": "^0.5.2", 46 | "web3-provider-engine": "^14.2.0" 47 | }, 48 | "repository": { 49 | "type": "git", 50 | "url": "https://github.com/gnosis/dx-uniswap-arbitrage" 51 | }, 52 | "keywords": [ 53 | "truffle", 54 | "ethereum", 55 | "web3" 56 | ], 57 | "author": "Billy Rennekamp ", 58 | "license": "ISC" 59 | } 60 | -------------------------------------------------------------------------------- /scripts/Debug.js: -------------------------------------------------------------------------------- 1 | var ArbitrageLocal = artifacts.require('./ArbitrageLocal.sol') 2 | 3 | const MockContract = artifacts.require('./MockContract.sol'); 4 | const IToken = artifacts.require('./interface/IToken.sol'); 5 | const IDutchExchange = artifacts.require('./interface/IDutchExchange.sol'); 6 | const IUniswapFactory = artifacts.require('./interface/IUniswapFactory.sol'); 7 | const IUniswapExchange = artifacts.require('./interface/IUniswapExchange.sol'); 8 | 9 | var BigNumber = require('bignumber.js') 10 | let gasPrice = 1000000000 // 1GWEI 11 | 12 | const _ = ' ' 13 | const emptyAdd = '0x' + '0'.repeat(40) 14 | console.log('"?????') 15 | contract('Debug', function(accounts) { 16 | let arbitrage 17 | 18 | it('should pass', async () => { 19 | try { 20 | var unitoken = '0xd54b47F8e6A1b97F3A84f63c867286272b273b7C' 21 | var dutchProxy = '0xa4392264a2d8c998901d10c154c91725b1bf0158' 22 | 23 | let dutchExchange = await IDutchExchange.at(dutchProxy); 24 | 25 | console.log('ethtoken', await dutchExchange.ethToken()) 26 | 27 | uniswapFactory = await IUniswapFactory.at('0x4e71920b7330515faf5EA0c690f1aD06a85fB60c') 28 | // Deploy ArbitrageLocal.sol 29 | console.log("Deploy ...") 30 | arbitrage = await ArbitrageLocal.new(uniswapFactory.address, dutchProxy) 31 | console.log('arbitrage is ', arbitrage.address) 32 | console.log('dutchProxy address', await arbitrage.dutchXProxy()) 33 | console.log("Deposit Ether ...") 34 | 35 | 36 | 37 | var result = await arbitrage.depositEther({ value: 1e18 }) 38 | console.log(result.logs.map(l => { 39 | return Object.keys(l.args).map(k => typeof l.args[k] === 'object' ? l.args[k].toString(10) : l.args[k]) 40 | })) 41 | // console.log("Execute opportunity ...") 42 | // tx = await arbitrage.uniswapOpportunity('0x8273e4B8ED6c78e252a9fCa5563Adfcc75C91b2A', '990000000000001') 43 | // console.log(tx.receipt.rawLogs) 44 | // 373083712375058828 45 | // 284345144034671943 46 | 47 | // 440000000000000001 48 | // var exchange = await uniswapFactory.getExchange(unitoken) 49 | // var uniswapExchange = await IUniswapExchange.at(exchange) 50 | // var EthAmount = await uniswapExchange.getTokenToEthOutputPrice('330000000000000001') 51 | // console.log('TokenToEth', EthAmount.toString(10)) 52 | 53 | // console.log('Ether Spent on Opp', '283791165594153088') 54 | // tx = await arbitrage.dutchOpportunity.call(unitoken, '374289831206257697') 55 | // console.log('return', tx.toString(10)) 56 | 57 | // We force failure to print events 58 | // assert(false) 59 | } catch (error) { 60 | console.log(error) 61 | } 62 | }) 63 | 64 | }) 65 | -------------------------------------------------------------------------------- /abi/UniswapFactoryInterface.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "NewExchange", 4 | "inputs": [ 5 | { 6 | "type": "address", 7 | "name": "token", 8 | "indexed": true 9 | }, 10 | { 11 | "type": "address", 12 | "name": "exchange", 13 | "indexed": true 14 | } 15 | ], 16 | "anonymous": false, 17 | "type": "event" 18 | }, 19 | { 20 | "name": "initializeFactory", 21 | "outputs": [], 22 | "inputs": [ 23 | { 24 | "type": "address", 25 | "name": "template" 26 | } 27 | ], 28 | "constant": false, 29 | "payable": false, 30 | "type": "function", 31 | "gas": 35725 32 | }, 33 | { 34 | "name": "createExchange", 35 | "outputs": [ 36 | { 37 | "type": "address", 38 | "name": "out" 39 | } 40 | ], 41 | "inputs": [ 42 | { 43 | "type": "address", 44 | "name": "token" 45 | } 46 | ], 47 | "constant": false, 48 | "payable": false, 49 | "type": "function", 50 | "gas": 187911 51 | }, 52 | { 53 | "name": "getExchange", 54 | "outputs": [ 55 | { 56 | "type": "address", 57 | "name": "out" 58 | } 59 | ], 60 | "inputs": [ 61 | { 62 | "type": "address", 63 | "name": "token" 64 | } 65 | ], 66 | "constant": true, 67 | "stateMutability": "view", 68 | "payable": false, 69 | "type": "function", 70 | "gas": 715 71 | }, 72 | { 73 | "name": "getToken", 74 | "outputs": [ 75 | { 76 | "type": "address", 77 | "name": "out" 78 | } 79 | ], 80 | "inputs": [ 81 | { 82 | "type": "address", 83 | "name": "exchange" 84 | } 85 | ], 86 | "constant": true, 87 | "stateMutability": "view", 88 | "payable": false, 89 | "type": "function", 90 | "gas": 745 91 | }, 92 | { 93 | "name": "getTokenWithId", 94 | "outputs": [ 95 | { 96 | "type": "address", 97 | "name": "out" 98 | } 99 | ], 100 | "inputs": [ 101 | { 102 | "type": "uint256", 103 | "name": "token_id" 104 | } 105 | ], 106 | "constant": true, 107 | "stateMutability": "view", 108 | "payable": false, 109 | "type": "function", 110 | "gas": 736 111 | }, 112 | { 113 | "name": "exchangeTemplate", 114 | "outputs": [ 115 | { 116 | "type": "address", 117 | "name": "out" 118 | } 119 | ], 120 | "inputs": [], 121 | "constant": true, 122 | "stateMutability": "view", 123 | "payable": false, 124 | "type": "function", 125 | "gas": 633 126 | }, 127 | { 128 | "name": "tokenCount", 129 | "outputs": [ 130 | { 131 | "type": "uint256", 132 | "name": "out" 133 | } 134 | ], 135 | "inputs": [], 136 | "constant": true, 137 | "stateMutability": "view", 138 | "payable": false, 139 | "type": "function", 140 | "gas": 663 141 | } 142 | ] -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const HDWalletProvider = require('truffle-hdwallet-provider') 3 | const GAS_PRICE_GWEI = process.env.GAS_PRICE_GWEI || 5 4 | const GAS_LIMIT = 6.5e6 5 | 6 | const DEFAULT_MNEMONIC = 'candy maple cake sugar pudding cream honey rich smooth crumble sweet treat' 7 | 8 | // Load env vars 9 | require('dotenv').config() 10 | 11 | // Get the mnemonic 12 | const privateKey = process.env.PK 13 | let mnemonic = process.env.MNEMONIC 14 | if (!privateKey && !mnemonic) { 15 | mnemonic = DEFAULT_MNEMONIC 16 | } 17 | 18 | const infuraProjectId = process.env.INFURA_KEY || '9408f47dedf04716a03ef994182cf150' 19 | function truffleConfig({ 20 | mnemonic = DEFAULT_MNEMONIC, 21 | privateKey, 22 | gasPriceGWei = GAS_PRICE_GWEI, 23 | gas = GAS_LIMIT, 24 | optimizedEnabled = true, 25 | urlRinkeby = 'https://rinkeby.infura.io/v3/' + infuraProjectId, 26 | urlKovan = 'https://kovan.infura.io/v3/' + infuraProjectId, 27 | urlMainnet = 'https://mainnet.infura.io/v3/' + infuraProjectId, 28 | urlDevelopment = 'localhost', 29 | portDevelopment = 8545 30 | } = {}) { 31 | assert(mnemonic, 'The mnemonic has not been provided'); 32 | console.log(`Using gas limit: ${gas / 1000} K`); 33 | console.log(`Using gas price: ${gasPriceGWei} Gwei`); 34 | console.log(`Optimizer enabled: ${optimizedEnabled}`); 35 | console.log('Using default mnemonic: %s', mnemonic === DEFAULT_MNEMONIC); 36 | const gasPrice = gasPriceGWei * 1e9; 37 | 38 | let _getProvider 39 | if (privateKey) { 40 | console.log('Using private key') 41 | _getProvider = url => { 42 | return () => { 43 | assert(infuraProjectId, "Need an infura ProjectID. INFURA_KEY env var") 44 | return new HDWalletProvider([privateKey], url) 45 | } 46 | } 47 | } else { 48 | console.log(mnemonic === DEFAULT_MNEMONIC ? 'Using default mnemonic' : 'Using custom mnemonic') 49 | _getProvider = url => { 50 | return () => { 51 | assert(infuraProjectId, "Need an infura ProjectID. INFURA_KEY env var") 52 | return new HDWalletProvider(mnemonic, url) 53 | } 54 | } 55 | } 56 | 57 | return { 58 | networks: { 59 | development: { 60 | host: process.env.RPC_URL || urlDevelopment, 61 | port: portDevelopment, 62 | gas, 63 | gasPrice, 64 | network_id: '*' 65 | }, 66 | mainnet: { 67 | provider: _getProvider(urlMainnet), 68 | network_id: '1', 69 | gas, 70 | gasPrice 71 | }, 72 | rinkeby: { 73 | provider: _getProvider(urlRinkeby), 74 | network_id: '4', 75 | gas, 76 | gasPrice 77 | }, 78 | kovan: { 79 | provider: _getProvider(urlKovan), 80 | network_id: '42', 81 | gas, 82 | gasPrice 83 | }, 84 | }, 85 | compilers: { 86 | solc: { 87 | version: '0.5.2', 88 | docker: process.env.SOLC_USE_DOCKER === 'true' || false, 89 | settings: { 90 | optimizer: { 91 | enabled: optimizedEnabled, // Default: false 92 | runs: 200 93 | } 94 | // evmVersion: "byzantium" // Default: "byzantium". Others: "homestead", ... 95 | } 96 | } 97 | } 98 | }; 99 | } 100 | 101 | module.exports = truffleConfig({ 102 | optimizedEnabled: true, 103 | mnemonic, 104 | privateKey 105 | }) 106 | -------------------------------------------------------------------------------- /contracts/SafeERC20.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | SafeERC20 by daostack. 4 | The code is based on a fix by SECBIT Team. 5 | 6 | USE WITH CAUTION & NO WARRANTY 7 | 8 | REFERENCE & RELATED READING 9 | - https://github.com/ethereum/solidity/issues/4116 10 | - https://medium.com/@chris_77367/explaining-unexpected-reverts-starting-with-solidity-0-4-22-3ada6e82308c 11 | - https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca 12 | - https://gist.github.com/BrendanChou/88a2eeb80947ff00bcf58ffdafeaeb61 13 | 14 | */ 15 | pragma solidity ^0.5.0; 16 | 17 | import "openzeppelin-solidity/contracts/utils/Address.sol"; 18 | import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; 19 | 20 | library SafeERC20 { 21 | using Address for address; 22 | 23 | bytes4 constant private TRANSFER_SELECTOR = bytes4(keccak256(bytes("transfer(address,uint256)"))); 24 | bytes4 constant private TRANSFERFROM_SELECTOR = bytes4(keccak256(bytes("transferFrom(address,address,uint256)"))); 25 | bytes4 constant private APPROVE_SELECTOR = bytes4(keccak256(bytes("approve(address,uint256)"))); 26 | 27 | function safeTransfer(address _erc20Addr, address _to, uint256 _value) internal { 28 | 29 | // Must be a contract addr first! 30 | require(_erc20Addr.isContract(), "ERC20 is not a contract"); 31 | 32 | (bool success, bytes memory returnValue) = 33 | // solhint-disable-next-line avoid-low-level-calls 34 | _erc20Addr.call(abi.encodeWithSelector(TRANSFER_SELECTOR, _to, _value)); 35 | // call return false when something wrong 36 | require(success, "safeTransfer must succeed"); 37 | //check return value 38 | require(returnValue.length == 0 || (returnValue.length == 32 && (returnValue[31] != 0)), "safeTransfer must return nothing or true"); 39 | } 40 | 41 | function safeTransferFrom(address _erc20Addr, address _from, address _to, uint256 _value) internal { 42 | 43 | // Must be a contract addr first! 44 | require(_erc20Addr.isContract(), "ERC20 is not a contract"); 45 | 46 | (bool success, bytes memory returnValue) = 47 | // solhint-disable-next-line avoid-low-level-calls 48 | _erc20Addr.call(abi.encodeWithSelector(TRANSFERFROM_SELECTOR, _from, _to, _value)); 49 | // call return false when something wrong 50 | require(success, "safeTransferFrom must succeed"); 51 | //check return value 52 | require(returnValue.length == 0 || (returnValue.length == 32 && (returnValue[31] != 0)), "safeTransferFrom must return nothing or true"); 53 | } 54 | 55 | function safeApprove(address _erc20Addr, address _spender, uint256 _value) internal { 56 | 57 | // Must be a contract addr first! 58 | require(_erc20Addr.isContract(), "ERC20 is not a contract"); 59 | 60 | // safeApprove should only be called when setting an initial allowance, 61 | // or when resetting it to zero. 62 | 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."); 63 | 64 | (bool success, bytes memory returnValue) = 65 | // solhint-disable-next-line avoid-low-level-calls 66 | _erc20Addr.call(abi.encodeWithSelector(APPROVE_SELECTOR, _spender, _value)); 67 | // call return false when something wrong 68 | require(success, "safeApprove must succeed"); 69 | //check return value 70 | require(returnValue.length == 0 || (returnValue.length == 32 && (returnValue[31] != 0)), "safeApprove must return nothing or true"); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /contracts/IUniswapExchange.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | // Solidity Interface 4 | 5 | contract IUniswapExchange { 6 | // Address of ERC20 token sold on this exchange 7 | function tokenAddress() external view returns (address token); 8 | // Address of Uniswap Factory 9 | function factoryAddress() external view returns (address factory); 10 | // Provide Liquidity 11 | function addLiquidity(uint256 min_liquidity, uint256 max_tokens, uint256 deadline) external payable returns (uint256); 12 | function removeLiquidity(uint256 amount, uint256 min_eth, uint256 min_tokens, uint256 deadline) external returns (uint256, uint256); 13 | // Get Prices 14 | function getEthToTokenInputPrice(uint256 eth_sold) external view returns (uint256 tokens_bought); 15 | function getEthToTokenOutputPrice(uint256 tokens_bought) external view returns (uint256 eth_sold); 16 | function getTokenToEthInputPrice(uint256 tokens_sold) external view returns (uint256 eth_bought); 17 | function getTokenToEthOutputPrice(uint256 eth_bought) external view returns (uint256 tokens_sold); 18 | // Trade ETH to ERC20 19 | function ethToTokenSwapInput(uint256 min_tokens, uint256 deadline) external payable returns (uint256 tokens_bought); 20 | function ethToTokenTransferInput(uint256 min_tokens, uint256 deadline, address recipient) external payable returns (uint256 tokens_bought); 21 | function ethToTokenSwapOutput(uint256 tokens_bought, uint256 deadline) external payable returns (uint256 eth_sold); 22 | function ethToTokenTransferOutput(uint256 tokens_bought, uint256 deadline, address recipient) external payable returns (uint256 eth_sold); 23 | // Trade ERC20 to ETH 24 | function tokenToEthSwapInput(uint256 tokens_sold, uint256 min_eth, uint256 deadline) external returns (uint256 eth_bought); 25 | function tokenToEthTransferInput(uint256 tokens_sold, uint256 min_tokens, uint256 deadline, address recipient) external returns (uint256 eth_bought); 26 | function tokenToEthSwapOutput(uint256 eth_bought, uint256 max_tokens, uint256 deadline) external returns (uint256 tokens_sold); 27 | function tokenToEthTransferOutput(uint256 eth_bought, uint256 max_tokens, uint256 deadline, address recipient) external returns (uint256 tokens_sold); 28 | // Trade ERC20 to ERC20 29 | function tokenToTokenSwapInput(uint256 tokens_sold, uint256 min_tokens_bought, uint256 min_eth_bought, uint256 deadline, address token_addr) external returns (uint256 tokens_bought); 30 | 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); 31 | function tokenToTokenSwapOutput(uint256 tokens_bought, uint256 max_tokens_sold, uint256 max_eth_sold, uint256 deadline, address token_addr) external returns (uint256 tokens_sold); 32 | 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); 33 | // Trade ERC20 to Custom Pool 34 | function tokenToExchangeSwapInput(uint256 tokens_sold, uint256 min_tokens_bought, uint256 min_eth_bought, uint256 deadline, address exchange_addr) external returns (uint256 tokens_bought); 35 | 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); 36 | function tokenToExchangeSwapOutput(uint256 tokens_bought, uint256 max_tokens_sold, uint256 max_eth_sold, uint256 deadline, address exchange_addr) external returns (uint256 tokens_sold); 37 | 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); 38 | // ERC20 comaptibility for liquidity tokens 39 | bytes32 public name; 40 | bytes32 public symbol; 41 | uint256 public decimals; 42 | function transfer(address _to, uint256 _value) external returns (bool); 43 | function transferFrom(address _from, address _to, uint256 value) external returns (bool); 44 | function approve(address _spender, uint256 _value) external returns (bool); 45 | function allowance(address _owner, address _spender) external view returns (uint256); 46 | function balanceOf(address _owner) external view returns (uint256); 47 | function totalSupply() public view returns (uint256); 48 | // Never use 49 | function setup(address token_addr) external; 50 | } -------------------------------------------------------------------------------- /migrations/2_DEV_local.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const _ = ' ' 3 | 4 | const IUniswapFactory = artifacts.require('IUniswapFactory') 5 | const IUniswapExchange = artifacts.require('IUniswapExchange') 6 | const IToken = artifacts.require('IToken') 7 | const SafeERC20 = artifacts.require('SafeERC20') 8 | const ArbitrageLocal = artifacts.require('ArbitrageLocal') 9 | const DutchExchangeProxy = '0xa4392264a2d8c998901d10c154c91725b1bf0158' 10 | 11 | const uniswapBytecode = require('../scripts/uniswap-bytecode.js') 12 | const uniswapExchangeCode = uniswapBytecode.exchange 13 | const uniswapFactoryCode = uniswapBytecode.factory 14 | 15 | const deadline = '1649626618' // year 2022 16 | 17 | module.exports = (deployer, network, accounts) => { 18 | return deployer.then(async () => { 19 | try { 20 | if (network !== 'development') { 21 | console.log(_ + 'Skip Migration: Not on local but on ' + network + ' instead') 22 | return 23 | } 24 | const { uniswapFactory } = await deploy({ deployer, accounts }) 25 | await setup({ deployer, accounts, uniswapFactory }) 26 | } catch (error) { 27 | console.log(error) 28 | } 29 | }) 30 | } 31 | 32 | 33 | async function deploy({ deployer, accounts }) { 34 | let tx = await web3.eth.sendTransaction({ 35 | from: accounts[0], 36 | data: uniswapExchangeCode, 37 | gas: 6.5e6, 38 | gasPrice: 1 * 1e9 // GWEI 39 | }) 40 | let txReceipt = await web3.eth.getTransactionReceipt(tx.transactionHash); 41 | let uniswapTemplateAddress = txReceipt.contractAddress 42 | tx = await web3.eth.sendTransaction({ 43 | from: accounts[0], 44 | data: uniswapFactoryCode, 45 | gas: 6.5e6, 46 | gasPrice: 1 * 1e9 // GWEI 47 | }) 48 | txReceipt = await web3.eth.getTransactionReceipt(tx.transactionHash); 49 | let uniswapFactoryAddress = txReceipt.contractAddress 50 | console.log(_ + 'uniswapFactoryAddress deployed at: ' + uniswapFactoryAddress) 51 | 52 | let uniArtifact = JSON.parse(fs.readFileSync('build/contracts/IUniswapFactory.json', 'utf8')) 53 | uniArtifact.networks[deployer.network_id] = { 54 | "events": {}, 55 | "links": {}, 56 | "address": uniswapFactoryAddress, 57 | "transactionHash": tx.transactionHash 58 | } 59 | fs.writeFileSync('build/contracts/IUniswapFactory.json', JSON.stringify(uniArtifact)); 60 | 61 | 62 | 63 | const uniswapFactory = await IUniswapFactory.at(uniswapFactoryAddress) 64 | await uniswapFactory.initializeFactory(uniswapTemplateAddress) 65 | 66 | const params = [uniswapFactory.address, DutchExchangeProxy] 67 | 68 | // Deploy SafeERC20 and link to ArbitrageLocal.sol 69 | await deployer.deploy(SafeERC20); 70 | await deployer.link(SafeERC20, ArbitrageLocal); 71 | 72 | // deploy ArbitrageLocal.sol 73 | const arbitrage = await deployer.deploy(ArbitrageLocal, ...params) 74 | console.log(_ + 'ArbitrageLocal deployed at: ' + arbitrage.address) 75 | 76 | return { uniswapFactory } 77 | } 78 | 79 | 80 | async function setup({ deployer, accounts, uniswapFactory }) { 81 | const from = accounts[0] 82 | 83 | let iToken = await IToken.new() 84 | console.log(_ + 'Uni Token Address: ' + iToken.address) 85 | 86 | let tx = await iToken.deposit({ value: 1e18, from }) 87 | 88 | await uniswapFactory.createExchange(iToken.address) 89 | 90 | let uniSwapExchangeAddress = await uniswapFactory.getExchange(iToken.address) 91 | 92 | console.log(_ + 'uniSwapExchangeAddress: ' + uniSwapExchangeAddress) 93 | 94 | let uniswapExchange = await IUniswapExchange.at(uniSwapExchangeAddress) 95 | 96 | await iToken.approve(uniswapExchange.address, 1e18.toString(10)) 97 | 98 | tx = await uniswapExchange.addLiquidity(0, 1e18.toString(10), deadline, { value: 1e18, from }) 99 | 100 | // let balanceOf = await uniswapExchange.balanceOf(from) 101 | // console.log(balanceOf.toString(10)) 102 | 103 | 104 | // let tokensBought = await uniswapExchange.getEthToTokenInputPrice((1e18 / 2).toString(10)) 105 | // console.log(_ + 'tokensBought:' + tokensBought.toString()) 106 | // function getEthToTokenInputPrice(uint256 eth_sold) external view returns (uint256 tokens_bought); 107 | 108 | tx = await iToken.deposit({ value: 1e18, from }) 109 | 110 | // tx = await uniswapExchange.ethToTokenSwapInput('1', deadline, {value: (1e18 / 2).toString(10)}) 111 | // console.log({tx}) 112 | // function ethToTokenSwapInput(uint256 min_tokens, uint256 deadline) external payable returns (uint256 tokens_bought); 113 | 114 | let balanceOf = await iToken.balanceOf(from) 115 | console.log(_ + 'UNI BALANCE: ' + balanceOf.toString(10)) 116 | } -------------------------------------------------------------------------------- /src/migrations-truffle-5/2_deploy_uniswap.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | const deadline = '1649626618' // year 2022 3 | 4 | const uniswap = require('../../scripts/uniswap-bytecode.js'); 5 | const uniswapExchangeCode = uniswap.exchange 6 | const uniswapFactoryCode = uniswap.factory 7 | 8 | async function migrate({ 9 | artifacts, 10 | deployer, 11 | network, 12 | accounts, 13 | web3, 14 | DutchExchangeProxy 15 | // initialTokenAmount, 16 | // gnoLockPeriodInHours 17 | }) { 18 | if (network === 'development') { 19 | 20 | var SafeERC20 = artifacts.require('SafeERC20') 21 | var IToken = artifacts.require('IToken') 22 | var ArbitrageLocal = artifacts.require('ArbitrageLocal') 23 | var IUniswapFactory = artifacts.require('IUniswapFactory') 24 | var IUniswapExchange = artifacts.require('IUniswapExchange') 25 | 26 | try { 27 | // console.log(web3) 28 | let tx = await new Promise((resolve, reject) => {web3.eth.sendTransaction({from: accounts[0], data: uniswapExchangeCode, gas: 6000000}, (error, result) => {error ? reject(error) : resolve(result)})}) 29 | 30 | let txReceipt = await new Promise((resolve, reject) => {web3.eth.getTransactionReceipt(tx, (error, result) => {error ? reject(error) : resolve(result)})}) 31 | let uniswapTemplateAddress = txReceipt.contractAddress 32 | 33 | tx = await new Promise((resolve, reject) => {web3.eth.sendTransaction({from: accounts[0], data: uniswapFactoryCode, gas: 6000000}, (error, result) => {error ? reject(error) : resolve(result)})}) 34 | 35 | txReceipt = await new Promise((resolve, reject) => {web3.eth.getTransactionReceipt(tx, (error, result) => {error ? reject(error) : resolve(result)})}); 36 | let uniswapFactoryAddress = txReceipt.contractAddress 37 | 38 | console.log('uniswapFactoryAddress deployed at: ' + uniswapFactoryAddress) 39 | 40 | 41 | var uniArtifact = JSON.parse(fs.readFileSync('build/contracts/IUniswapFactory.json', 'utf8')) 42 | uniArtifact.networks[deployer.network_id] = { 43 | "events": {}, 44 | "links": {}, 45 | "address": uniswapFactoryAddress, 46 | "transactionHash": tx 47 | } 48 | fs.writeFileSync('build/contracts/IUniswapFactory.json', JSON.stringify(uniArtifact)); 49 | 50 | 51 | let uniswapFactory = await IUniswapFactory.at(uniswapFactoryAddress) 52 | await uniswapFactory.initializeFactory(uniswapTemplateAddress) 53 | 54 | const params = [uniswapFactory.address, DutchExchangeProxy] 55 | 56 | // Deploy SafeERC20 and link to ArbitrageLocal.sol 57 | await deployer.deploy(SafeERC20); 58 | await deployer.link(SafeERC20, ArbitrageLocal); 59 | 60 | // deploy ArbitrageLocal.sol 61 | let arbitrage = await deployer.deploy(ArbitrageLocal, ...params) 62 | console.log('ArbitrageLocal deployed at: ' + arbitrage.address) 63 | 64 | 65 | // SET UP UNI TOKEN FOR TESTING PURPOSES 66 | 67 | var from = accounts[0] 68 | 69 | // create new token 70 | let iToken = await IToken.new() 71 | console.log('Uni Token Address: ' + iToken.address) 72 | tx = await iToken.deposit({value:1e18, from}) 73 | 74 | // create exchange for token 75 | await uniswapFactory.createExchange(iToken.address) 76 | let uniSwapExchangeAddress = await uniswapFactory.getExchange(iToken.address) 77 | console.log('uniSwapExchangeAddress: ' + uniSwapExchangeAddress) 78 | 79 | // add liquidity to exchange 80 | let uniswapExchange = await IUniswapExchange.at(uniSwapExchangeAddress) 81 | await iToken.approve(uniswapExchange.address, 1e18.toString(10)) 82 | tx = await uniswapExchange.addLiquidity(0, 1e18.toString(10), deadline, {value: 1e18, from}) 83 | 84 | 85 | // get enough token to fund the dutch x 86 | tx = await iToken.deposit({value:10e18, from}) 87 | 88 | // tx = await uniswapExchange.ethToTokenSwapInput('1', deadline, {value: (1e18 / 2).toString(10)}) 89 | // console.log({tx}) 90 | // function ethToTokenSwapInput(uint256 min_tokens, uint256 deadline) external payable returns (uint256 tokens_bought); 91 | 92 | let balanceOf = await iToken.balanceOf(from) 93 | console.log('UNI BALANCE: ' + balanceOf.toString(10)) 94 | 95 | // fund the arbitrage contract with ether 96 | tx = await arbitrage.depositEther({value: 1e18}) 97 | 98 | } catch (error) { 99 | console.log('there was an error!!!!!') 100 | console.log(error) 101 | } 102 | } else { 103 | console.log('Not in development, so nothing to do. Current network is %s', network) 104 | } 105 | } 106 | 107 | module.exports = migrate 108 | -------------------------------------------------------------------------------- /flat/SafeERC20.sol: -------------------------------------------------------------------------------- 1 | 2 | // File: openzeppelin-solidity/contracts/utils/Address.sol 3 | 4 | pragma solidity ^0.5.0; 5 | 6 | /** 7 | * Utility library of inline functions on addresses 8 | */ 9 | library Address { 10 | /** 11 | * Returns whether the target address is a contract 12 | * @dev This function will return false if invoked during the constructor of a contract, 13 | * as the code is not actually created until after the constructor finishes. 14 | * @param account address of the account to check 15 | * @return whether the target address is a contract 16 | */ 17 | function isContract(address account) internal view returns (bool) { 18 | uint256 size; 19 | // XXX Currently there is no better way to check if there is a contract in an address 20 | // than to check the size of the code at that address. 21 | // See https://ethereum.stackexchange.com/a/14016/36603 22 | // for more details about how this works. 23 | // TODO Check this again before the Serenity release, because all addresses will be 24 | // contracts then. 25 | // solhint-disable-next-line no-inline-assembly 26 | assembly { size := extcodesize(account) } 27 | return size > 0; 28 | } 29 | } 30 | 31 | // File: openzeppelin-solidity/contracts/token/ERC20/IERC20.sol 32 | 33 | pragma solidity ^0.5.0; 34 | 35 | /** 36 | * @title ERC20 interface 37 | * @dev see https://github.com/ethereum/EIPs/issues/20 38 | */ 39 | interface IERC20 { 40 | function transfer(address to, uint256 value) external returns (bool); 41 | 42 | function approve(address spender, uint256 value) external returns (bool); 43 | 44 | function transferFrom(address from, address to, uint256 value) external returns (bool); 45 | 46 | function totalSupply() external view returns (uint256); 47 | 48 | function balanceOf(address who) external view returns (uint256); 49 | 50 | function allowance(address owner, address spender) external view returns (uint256); 51 | 52 | event Transfer(address indexed from, address indexed to, uint256 value); 53 | 54 | event Approval(address indexed owner, address indexed spender, uint256 value); 55 | } 56 | 57 | // File: contracts/SafeERC20.sol 58 | 59 | /* 60 | 61 | SafeERC20 by daostack. 62 | The code is based on a fix by SECBIT Team. 63 | 64 | USE WITH CAUTION & NO WARRANTY 65 | 66 | REFERENCE & RELATED READING 67 | - https://github.com/ethereum/solidity/issues/4116 68 | - https://medium.com/@chris_77367/explaining-unexpected-reverts-starting-with-solidity-0-4-22-3ada6e82308c 69 | - https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca 70 | - https://gist.github.com/BrendanChou/88a2eeb80947ff00bcf58ffdafeaeb61 71 | 72 | */ 73 | pragma solidity ^0.5.0; 74 | 75 | 76 | 77 | library SafeERC20 { 78 | using Address for address; 79 | 80 | bytes4 constant private TRANSFER_SELECTOR = bytes4(keccak256(bytes("transfer(address,uint256)"))); 81 | bytes4 constant private TRANSFERFROM_SELECTOR = bytes4(keccak256(bytes("transferFrom(address,address,uint256)"))); 82 | bytes4 constant private APPROVE_SELECTOR = bytes4(keccak256(bytes("approve(address,uint256)"))); 83 | 84 | function safeTransfer(address _erc20Addr, address _to, uint256 _value) internal { 85 | 86 | // Must be a contract addr first! 87 | require(_erc20Addr.isContract(), "ERC20 is not a contract"); 88 | 89 | (bool success, bytes memory returnValue) = 90 | // solhint-disable-next-line avoid-low-level-calls 91 | _erc20Addr.call(abi.encodeWithSelector(TRANSFER_SELECTOR, _to, _value)); 92 | // call return false when something wrong 93 | require(success, "safeTransfer must succeed"); 94 | //check return value 95 | require(returnValue.length == 0 || (returnValue.length == 32 && (returnValue[31] != 0)), "safeTransfer must return nothing or true"); 96 | } 97 | 98 | function safeTransferFrom(address _erc20Addr, address _from, address _to, uint256 _value) internal { 99 | 100 | // Must be a contract addr first! 101 | require(_erc20Addr.isContract(), "ERC20 is not a contract"); 102 | 103 | (bool success, bytes memory returnValue) = 104 | // solhint-disable-next-line avoid-low-level-calls 105 | _erc20Addr.call(abi.encodeWithSelector(TRANSFERFROM_SELECTOR, _from, _to, _value)); 106 | // call return false when something wrong 107 | require(success, "safeTransferFrom must succeed"); 108 | //check return value 109 | require(returnValue.length == 0 || (returnValue.length == 32 && (returnValue[31] != 0)), "safeTransferFrom must return nothing or true"); 110 | } 111 | 112 | function safeApprove(address _erc20Addr, address _spender, uint256 _value) internal { 113 | 114 | // Must be a contract addr first! 115 | require(_erc20Addr.isContract(), "ERC20 is not a contract"); 116 | 117 | // vvv 118 | // This section has been commented out because it is not a necesarry safeguard 119 | // vvv 120 | /* 121 | // safeApprove should only be called when setting an initial allowance, 122 | // or when resetting it to zero. 123 | 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."); 124 | */ 125 | 126 | (bool success, bytes memory returnValue) = 127 | // solhint-disable-next-line avoid-low-level-calls 128 | _erc20Addr.call(abi.encodeWithSelector(APPROVE_SELECTOR, _spender, _value)); 129 | // call return false when something wrong 130 | require(success, "safeApprove must succeed"); 131 | //check return value 132 | require(returnValue.length == 0 || (returnValue.length == 32 && (returnValue[31] != 0)), "safeApprove must return nothing or true"); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /scripts/Rinkeby.js: -------------------------------------------------------------------------------- 1 | var ArbitrageRinkeby = artifacts.require('./ArbitrageRinkeby.sol') 2 | 3 | // const MockContract = artifacts.require('./MockContract.sol'); 4 | const IToken = artifacts.require('./interface/IToken.sol'); 5 | const IDutchExchange = artifacts.require('./interface/IDutchExchange.sol'); 6 | const IUniswapFactory = artifacts.require('./interface/IUniswapFactory.sol'); 7 | const IUniswapExchange = artifacts.require('./interface/IUniswapExchange.sol'); 8 | 9 | // var BigNumber = require('bignumber.js') 10 | let gasPrice = 1000000000 // 1GWEI 11 | 12 | const _ = ' ' 13 | const emptyAdd = '0x' + '0'.repeat(40) 14 | // const deadline = '1749626618' // year 2022 15 | module.exports = async function(callback) { 16 | let arbitrage, tx 17 | let from = '0xEe4E56947c799127FB37392bf9333BDEF356865F' 18 | console.log(_ + 'my address ' + from) 19 | try { 20 | var OMG = '0x00df91984582e6e96288307e9c2f20b38c8fece9' 21 | var RDN = '0x3615757011112560521536258c1e7325ae3b48ae' 22 | var dutchProxy = '0xaAEb2035FF394fdB2C879190f95e7676f1A9444B' 23 | var uniFactoryAddress = '0xf5D915570BC477f9B8D6C0E980aA81757A3AaC36' 24 | 25 | let dutchExchange = await IDutchExchange.at(dutchProxy); 26 | uniswapFactory = await IUniswapFactory.at(uniFactoryAddress) 27 | 28 | let omgToken = await IToken.at(OMG) 29 | let rdnToken = await IToken.at(RDN) 30 | 31 | // Deploy ArbitrageRinkeby.sol 32 | arbitrage = await ArbitrageRinkeby.deployed() 33 | // arbitrage = await ArbitrageRinkeby.new() 34 | 35 | 36 | // let arbitrageDutch = await arbitrage.dutchXProxy() 37 | // console.log({arbitrageDutch}) 38 | 39 | // let ethToken = await dutchExchange.ethToken() 40 | // console.log({ethToken}) 41 | 42 | // let wethToken = await IToken.at(ethToken) 43 | 44 | // tx = await wethToken.deposit({value: 1e17}) 45 | 46 | // tx = await arbitrage.depositEther({value: 1e17}) 47 | // console.log({tx}) 48 | 49 | // tx = await uniswapFactory.createExchange(RDN) 50 | // console.log('uniswapFactory.createExchange', tx) 51 | 52 | let uniswapExchangeAddress = await uniswapFactory.getExchange(rdnToken.address) 53 | console.log(_ + 'uniswapExchangeAddress: ' + uniswapExchangeAddress) 54 | 55 | let uniswapExchange = await IUniswapExchange.at(uniswapExchangeAddress) 56 | 57 | 58 | // tokenReserve = await rdnToken.balanceOf(uniswapExchangeAddress) 59 | // console.log({tokenReserve}) 60 | 61 | 62 | 63 | 64 | // tx = await uniswapExchange.tokenToEthSwapInput("436299294941339884531", "1", deadline) 65 | // // function tokenToEthSwapInput(uint256 tokens_sold, uint256 min_eth, uint256 deadline) external returns (uint256 eth_bought); 66 | 67 | // console.log('tokenToEthSwapInput', tx.receipt.status) 68 | // // function ethToTokenSwapInput(uint256 min_tokens, uint256 deadline) external payable returns (uint256 tokens_bought); 69 | 70 | var blockNumber = await web3.eth.getBlockNumber(); 71 | var block = await web3.eth.getBlock(blockNumber); 72 | var timestamp = block.timestamp 73 | console.log(_, {timestamp}) 74 | 75 | var deadline = timestamp + (10*60*60*1000) 76 | console.log(_, {deadline}) 77 | 78 | 79 | liquidityTotalSupply = await uniswapExchange.totalSupply() 80 | console.log(_ + 'liquidityTotalSupply: ' + liquidityTotalSupply.toString(10)) 81 | 82 | myLiquidity = await uniswapExchange.balanceOf(from) 83 | console.log(_ + 'myLiquidity: ' + myLiquidity.toString(10)) 84 | 85 | 86 | 87 | let uniswapETHbalance = web3.utils.toBN(await web3.eth.getBalance(uniswapExchangeAddress)) 88 | console.log(_ + 'uniswapETHbalance: ' + uniswapETHbalance.toString(10)) 89 | 90 | 91 | let uniswapRDNbalance = await rdnToken.balanceOf(uniswapExchangeAddress) 92 | console.log(_ + 'uniswapRDNbalance: ' + uniswapRDNbalance.toString(10)) 93 | 94 | 95 | 96 | let myETHbalance = web3.utils.toBN(await web3.eth.getBalance(from)) 97 | console.log(_ + 'myETHbalance: ' + myETHbalance.toString(10)) 98 | console.log(_ + 'myETHbalance: ' + web3.utils.fromWei(myETHbalance.toString(10)).toString(10)) 99 | 100 | let myRDNbalance = await rdnToken.balanceOf(from) 101 | console.log(_ + 'myRDNbalance: ' + myRDNbalance.toString(10)) 102 | 103 | 104 | let uniswapPrice = (new BigNumber(uniswapETHbalance)).div(uniswapRDNbalance)) 105 | console.log(_ + 'uniswapPrice: ' + uniswapPrice.toString(10)) 106 | 107 | 108 | let maxAmount = myETHbalance.mul(uniswapPrice, 0) 109 | console.log(_ + 'maxAmount: ' + maxAmount.toString(10)) 110 | 111 | if (maxAmount.gt(myETHbalance)) { 112 | maxAmount = myETHbalance 113 | } 114 | console.log(_ + 'maxAmount: ' + maxAmount.toString(10)) 115 | 116 | maxAmount = maxAmount.sub(1e18 / 2) 117 | 118 | console.log(_ + 'maxAmount: ' + maxAmount.toString(10)) 119 | 120 | let tokenAmount = (maxAmount.mul(uniswapRDNbalance).divRound(uniswapETHbalance.plus(1)).mul(10)) 121 | console.log(_ + tokenAmount.toString(10) + ' tokenAmount') 122 | 123 | allowance = (await rdnToken.allowance(from, uniswapExchangeAddress)) 124 | console.log(_ + allowance.toString(10) + ' allowance') 125 | 126 | if (allowance.lt(tokenAmount.toString(10))) { 127 | console.log(_ + 'allowance is lt than tokenAmount') 128 | tx = await rdnToken.approve(uniswapExchangeAddress, tokenAmount.toString(10)) 129 | // console.log(tx.logs[0].args) 130 | var newAllowance = (await rdnToken.allowance(from, uniswapExchangeAddress)) 131 | console.log(_ + newAllowance.toString(10) + ' newAllowance') 132 | 133 | } 134 | 135 | if (uniswapRDNbalance.eq(0)) { 136 | console.log(_ + 'uniswapRDNbalance is 0') 137 | tx = await uniswapExchange.addLiquidity('1', (1e10).toString(10), deadline, {value: (1e10).toString(10)}) 138 | // function addLiquidity(uint256 min_liquidity, uint256 max_tokens, uint256 deadline) external payable returns (uint256); 139 | } else { 140 | tx = await uniswapExchange.addLiquidity('1', tokenAmount.toString(10), deadline, {value: maxAmount.toString(10)}) 141 | // function addLiquidity(uint256 min_liquidity, uint256 max_tokens, uint256 deadline) external payable returns (uint256); 142 | } 143 | console.log(_ + 'uniswapExchange.addLiquidity', tx.receipt.status) 144 | 145 | // let tokensBought = await uniswapExchange.getEthToTokenInputPrice((1e18 / 2).toString(10)) 146 | // console.log(_ + 'potential tokensBought for .5 ETH:' + tokensBought.toString()) 147 | 148 | // let balanceOf = await iToken.balanceOf(from) 149 | // console.log(_ + 'OMG BALANCE: ' + balanceOf.toString(10)) 150 | 151 | 152 | uniswapRDNbalance = await rdnToken.balanceOf(uniswapExchangeAddress) 153 | console.log(_ + 'uniswapRDNbalance: ' + uniswapRDNbalance.toString(10)) 154 | uniswapETHbalance = web3.utils.toBN(await web3.eth.getBalance(uniswapExchangeAddress)) 155 | console.log(_ + 'uniswapETHbalance: ' + uniswapETHbalance.toString(10)) 156 | 157 | 158 | callback() 159 | } catch (error) { 160 | console.log(error) 161 | callback(error) 162 | } 163 | 164 | } 165 | -------------------------------------------------------------------------------- /contracts/Arbitrage.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import "./IUniswapExchange.sol"; 4 | import "./IUniswapFactory.sol"; 5 | import "./IDutchExchange.sol"; 6 | import "./ITokenMinimal.sol"; 7 | import "./SafeERC20.sol"; 8 | import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; 9 | 10 | /// @title Uniswap Arbitrage - Executes arbitrage transactions between Uniswap and DutchX. 11 | /// @author Billy Rennekamp - 12 | contract Arbitrage is Ownable { 13 | 14 | uint constant max = uint(-1); 15 | 16 | IUniswapFactory public uniFactory; 17 | IDutchExchange public dutchXProxy; 18 | 19 | event Profit(uint profit, bool wasDutchOpportunity); 20 | 21 | /// @dev Payable fallback function has nothing inside so it won't run out of gas with gas limited transfers 22 | function() external payable {} 23 | 24 | /// @dev Only owner can deposit contract Ether into the DutchX as WETH 25 | function depositEther() public payable onlyOwner { 26 | 27 | require(address(this).balance > 0, "Balance must be greater than 0 to deposit"); 28 | uint balance = address(this).balance; 29 | 30 | // // Deposit balance to WETH 31 | address weth = dutchXProxy.ethToken(); 32 | ITokenMinimal(weth).deposit.value(balance)(); 33 | 34 | uint wethBalance = ITokenMinimal(weth).balanceOf(address(this)); 35 | uint allowance = ITokenMinimal(weth).allowance(address(this), address(dutchXProxy)); 36 | 37 | if (allowance < wethBalance) { 38 | if (allowance != 0) { 39 | SafeERC20.safeApprove(weth, address(dutchXProxy), 0); 40 | } 41 | // Approve max amount of WETH to be transferred by dutchX 42 | // Keeping it max will have same or similar costs to making it exact over and over again 43 | SafeERC20.safeApprove(weth, address(dutchXProxy), max); 44 | } 45 | 46 | // Deposit new amount on dutchX, confirm there's at least the amount we just deposited 47 | uint newBalance = dutchXProxy.deposit(weth, balance); 48 | require(newBalance >= balance, "Deposit WETH to DutchX didn't work."); 49 | } 50 | 51 | /// @dev Only owner can withdraw WETH from DutchX, convert to Ether and transfer to owner 52 | /// @param amount The amount of Ether to withdraw 53 | function withdrawEtherThenTransfer(uint amount) external onlyOwner { 54 | _withdrawEther(amount); 55 | address(uint160(owner())).transfer(amount); 56 | } 57 | 58 | /// @dev Only owner can transfer any Ether currently in the contract to the owner address. 59 | /// @param amount The amount of Ether to withdraw 60 | function transferEther(uint amount) external onlyOwner { 61 | // If amount is zero, deposit the entire contract balance. 62 | address(uint160(owner())).transfer(amount == 0 ? address(this).balance : amount); 63 | } 64 | 65 | /// @dev Only owner function to withdraw WETH from the DutchX, convert it to Ether and keep it in contract 66 | /// @param amount The amount of WETH to withdraw and convert. 67 | function withdrawEther(uint amount) external onlyOwner { 68 | _withdrawEther(amount); 69 | } 70 | 71 | /// @dev Internal function to withdraw WETH from the DutchX, convert it to Ether and keep it in contract 72 | /// @param amount The amount of WETH to withdraw and convert. 73 | function _withdrawEther(uint amount) internal { 74 | address weth = dutchXProxy.ethToken(); 75 | dutchXProxy.withdraw(weth, amount); 76 | ITokenMinimal(weth).withdraw(amount); 77 | } 78 | 79 | /// @dev Only owner can withdraw a token from the DutchX 80 | /// @param token The token address that is being withdrawn. 81 | /// @param amount The amount of token to withdraw. Can be larger than available balance and maximum will be withdrawn. 82 | /// @return Returns the amount actually withdrawn from the DutchX 83 | function withdrawToken(address token, uint amount) external onlyOwner returns (uint) { 84 | return dutchXProxy.withdraw(token, amount); 85 | } 86 | 87 | /// @dev Only owner can transfer tokens to the owner that belong to this contract 88 | /// @param token The token address that is being transferred. 89 | /// @param amount The amount of token to transfer. 90 | function transferToken(address token, uint amount) external onlyOwner { 91 | SafeERC20.safeTransfer(token, owner(), amount); 92 | } 93 | 94 | /// @dev Only owner can approve tokens to be used by the DutchX 95 | /// @param token The token address to be approved for use 96 | /// @param spender The address that should be approved 97 | /// @param allowance The amount of tokens that should be approved 98 | function approveToken(address token, address spender, uint allowance) external onlyOwner { 99 | SafeERC20.safeApprove(token, spender, allowance); 100 | } 101 | 102 | /// @dev Only owner can deposit token to the DutchX 103 | /// @param token The token address that is being deposited. 104 | /// @param amount The amount of token to deposit. 105 | function depositToken(address token, uint amount) external onlyOwner { 106 | _depositToken(token, amount); 107 | } 108 | 109 | /// @dev Internal function to deposit token to the DutchX 110 | /// @param token The token address that is being deposited. 111 | /// @param amount The amount of token to deposit. 112 | function _depositToken(address token, uint amount) internal returns(uint deposited) { 113 | uint balance = ITokenMinimal(token).balanceOf(address(this)); 114 | uint min = balance < amount ? balance : amount; 115 | require(min > 0, "Balance of token insufficient"); 116 | 117 | uint allowance = ITokenMinimal(token).allowance(address(this), address(dutchXProxy)); 118 | if (allowance < min) { 119 | if (allowance != 0) { 120 | SafeERC20.safeApprove(token, address(dutchXProxy), 0); 121 | } 122 | SafeERC20.safeApprove(token, address(dutchXProxy), max); 123 | } 124 | 125 | // Confirm that the balance of the token on the DutchX is at least how much was deposited 126 | uint newBalance = dutchXProxy.deposit(token, min); 127 | require(newBalance >= min, "deposit didn't work"); 128 | return min; 129 | } 130 | 131 | /// @dev Executes a trade opportunity on dutchX. Assumes that there is a balance of WETH already on the dutchX 132 | /// @param arbToken Address of the token that should be arbitraged. 133 | /// @param amount Amount of Ether to use in arbitrage. 134 | /// @return Returns if transaction can be executed. 135 | function dutchOpportunity(address arbToken, uint256 amount) external onlyOwner { 136 | 137 | address etherToken = dutchXProxy.ethToken(); 138 | 139 | // The order of parameters for getAuctionIndex don't matter 140 | uint256 dutchAuctionIndex = dutchXProxy.getAuctionIndex(arbToken, etherToken); 141 | 142 | // postBuyOrder(sellToken, buyToken, amount) 143 | // results in a decrease of the amount the user owns of the second token 144 | // which means the buyToken is what the buyer wants to get rid of. 145 | // "The buy token is what the buyer provides, the seller token is what the seller provides." 146 | dutchXProxy.postBuyOrder(arbToken, etherToken, dutchAuctionIndex, amount); 147 | 148 | (uint tokensBought, ) = dutchXProxy.claimBuyerFunds(arbToken, etherToken, address(this), dutchAuctionIndex); 149 | dutchXProxy.withdraw(arbToken, tokensBought); 150 | 151 | address uniswapExchange = uniFactory.getExchange(arbToken); 152 | 153 | uint allowance = ITokenMinimal(arbToken).allowance(address(this), address(uniswapExchange)); 154 | if (allowance < tokensBought) { 155 | if (allowance != 0) { 156 | SafeERC20.safeApprove(arbToken, address(uniswapExchange), 0); 157 | } 158 | // Approve Uniswap to transfer arbToken on contract's behalf 159 | // Keeping it max will have same or similar costs to making it exact over and over again 160 | SafeERC20.safeApprove(arbToken, address(uniswapExchange), max); 161 | } 162 | 163 | // tokenToEthSwapInput(inputToken, minimumReturn, timeToLive) 164 | // minimumReturn is enough to make a profit (excluding gas) 165 | // timeToLive is now because transaction is atomic 166 | uint256 etherReturned = IUniswapExchange(uniswapExchange).tokenToEthSwapInput(tokensBought, 1, block.timestamp); 167 | 168 | // gas costs were excluded because worse case scenario the tx fails and gas costs were spent up to here anyway 169 | // best worst case scenario the profit from the trade alleviates part of the gas costs even if still no total profit 170 | require(etherReturned >= amount, "no profit"); 171 | emit Profit(etherReturned, true); 172 | 173 | // Ether is deposited as WETH 174 | depositEther(); 175 | } 176 | 177 | /// @dev Executes a trade opportunity on uniswap. 178 | /// @param arbToken Address of the token that should be arbitraged. 179 | /// @param amount Amount of Ether to use in arbitrage. 180 | /// @return Returns if transaction can be executed. 181 | function uniswapOpportunity(address arbToken, uint256 amount) external onlyOwner { 182 | 183 | // WETH must be converted to Eth for Uniswap trade 184 | // (Uniswap allows ERC20:ERC20 but most liquidity is on ETH:ERC20 markets) 185 | _withdrawEther(amount); 186 | require(address(this).balance >= amount, "buying from uniswap takes real Ether"); 187 | 188 | // ethToTokenSwapInput(minTokens, deadline) 189 | // minTokens is 1 because it will revert without a profit regardless 190 | // deadline is now since trade is atomic 191 | // solium-disable-next-line security/no-block-members 192 | uint256 tokensBought = IUniswapExchange(uniFactory.getExchange(arbToken)).ethToTokenSwapInput.value(amount)(1, block.timestamp); 193 | 194 | // tokens need to be approved for the dutchX before they are deposited 195 | tokensBought = _depositToken(arbToken, tokensBought); 196 | 197 | address etherToken = dutchXProxy.ethToken(); 198 | 199 | // The order of parameters for getAuctionIndex don't matter 200 | uint256 dutchAuctionIndex = dutchXProxy.getAuctionIndex(arbToken, etherToken); 201 | 202 | // spend max amount of tokens currently on the dutch x (might be combined from previous remainders) 203 | // max is automatically reduced to maximum available tokens because there may be 204 | // token remainders from previous auctions which closed after previous arbitrage opportunities 205 | dutchXProxy.postBuyOrder(etherToken, arbToken, dutchAuctionIndex, max); 206 | // solium-disable-next-line no-unused-vars 207 | (uint etherReturned, ) = dutchXProxy.claimBuyerFunds(etherToken, arbToken, address(this), dutchAuctionIndex); 208 | 209 | // gas costs were excluded because worse case scenario the tx fails and gas costs were spent up to here anyway 210 | // best worst case scenario the profit from the trade alleviates part of the gas costs even if still no total profit 211 | require(etherReturned >= amount, "no profit"); 212 | emit Profit(etherReturned, false); 213 | // Ether returned is already in dutchX balance where Ether is assumed to be stored when not being used. 214 | } 215 | 216 | } 217 | -------------------------------------------------------------------------------- /test/Arbitrage.test.js: -------------------------------------------------------------------------------- 1 | var ArbitrageLocal = artifacts.require('./ArbitrageLocal.sol') 2 | 3 | const MockContract = artifacts.require('./MockContract.sol'); 4 | const SafeERC20 = artifacts.require('./SafeERC20.sol'); 5 | const IToken = artifacts.require('./IToken.sol'); 6 | const IDutchExchange = artifacts.require('./IDutchExchange.sol'); 7 | const IUniswapFactory = artifacts.require('./IUniswapFactory.sol'); 8 | const IUniswapExchange = artifacts.require('./IUniswapExchange.sol'); 9 | 10 | const abi = require('ethereumjs-abi') 11 | var BigNumber = require('bignumber.js') 12 | 13 | const _ = ' ' 14 | const emptyAdd = '0x' + '0'.repeat(40) 15 | 16 | const truffleConfigs = require('../truffle.js') 17 | 18 | const gasPrice = web3.utils.toBN(truffleConfigs.networks.development.gasPrice) 19 | 20 | contract('ArbitrageLocal', function(accounts) { 21 | 22 | let arbitrage, iToken, iDutchExchange, mockDutchExchange, iUniswapFactory, mockUniswapFactory, iUniswapExchange, mockUniswapExchange 23 | let balanceLast, gasSpent, balanceNext, shouldBe, tx 24 | 25 | const oneWei = 1 26 | const oneEth = web3.utils.toBN(1e18) 27 | 28 | before(async () => { 29 | try { 30 | let totalGas = new BigNumber(0) 31 | 32 | // Create Mocks 33 | mockEthToken = await MockContract.new() 34 | let tx = await web3.eth.getTransactionReceipt(mockEthToken.transactionHash) 35 | totalGas = totalGas.plus(tx.gasUsed) 36 | console.log(_ + tx.gasUsed + ' - Deploy mockEthToken') 37 | 38 | mockDutchExchange = await MockContract.new() 39 | tx = await web3.eth.getTransactionReceipt(mockDutchExchange.transactionHash) 40 | totalGas = totalGas.plus(tx.gasUsed) 41 | console.log(_ + tx.gasUsed + ' - Deploy mockDutchExchange') 42 | mockUniswapFactory = await MockContract.new() 43 | tx = await web3.eth.getTransactionReceipt(mockUniswapFactory.transactionHash) 44 | totalGas = totalGas.plus(tx.gasUsed) 45 | console.log(_ + tx.gasUsed + ' - Deploy mockUniswapFactory') 46 | 47 | mockUniswapExchange = await MockContract.new() 48 | tx = await web3.eth.getTransactionReceipt(mockUniswapExchange.transactionHash) 49 | totalGas = totalGas.plus(tx.gasUsed) 50 | console.log(_ + tx.gasUsed + ' - Deploy mockUniswapExchange') 51 | 52 | // Deploy interfaces for building mock methods 53 | iToken = await IToken.at(mockEthToken.address) 54 | iDutchExchange = await IDutchExchange.at(mockDutchExchange.address); 55 | iUniswapFactory = await IUniswapFactory.at(mockUniswapFactory.address); 56 | iUniswapExchange = await IUniswapExchange.at(mockUniswapExchange.address); 57 | 58 | // Deploy and link safeERC20 library 59 | const safeERC20 = await SafeERC20.new() 60 | await ArbitrageLocal.link('SafeERC20', safeERC20.address); 61 | 62 | // Deploy ArbitrageLocal.sol 63 | arbitrage = await ArbitrageLocal.new(iUniswapFactory.address, iDutchExchange.address) 64 | tx = await web3.eth.getTransactionReceipt(arbitrage.transactionHash) 65 | totalGas = totalGas.plus(tx.gasUsed) 66 | console.log(_ + tx.gasUsed + ' - Deploy arbitrage') 67 | 68 | console.log(_ + '-----------------------') 69 | console.log(_ + totalGas.toFormat(0) + ' - Total Gas') 70 | 71 | 72 | await setMocks(accounts) 73 | 74 | // done() 75 | } catch (error) { 76 | console.error(error) 77 | // done(false) 78 | } 79 | }) 80 | 81 | 82 | it('should not revert when depositEther()', async () => { 83 | let nonce = await web3.eth.getTransactionCount(accounts[0]); 84 | 85 | await arbitrage.depositEther({value: oneWei, nonce}) 86 | }) 87 | 88 | it('should revert when not owner & depositEther()', async () => { 89 | let err 90 | try { 91 | await arbitrage.depositEther({value: oneWei, from: accounts[1]}) 92 | } catch(error) { 93 | err = error 94 | } 95 | assert(err, 'depositEther as non-owner did not fail') 96 | }) 97 | 98 | it('should revert when no value & depositEther()', async () => { 99 | let err 100 | try { 101 | await arbitrage.depositEther() 102 | } catch(error) { 103 | err = error 104 | } 105 | assert(err, 'depositEther with no value did not fail') 106 | }) 107 | 108 | it('should not revert and update balances when withdrawEtherThenTransfer()', async () => { 109 | let nonce = await web3.eth.getTransactionCount(accounts[0]); 110 | 111 | balanceLast = web3.utils.toBN(await web3.eth.getBalance(accounts[0])) 112 | const sendEtherToContract = await web3.eth.sendTransaction({ 113 | from: accounts[0], 114 | to: arbitrage.address, 115 | value: oneEth.toString(10), 116 | nonce, 117 | gasPrice 118 | }); 119 | assert(sendEtherToContract.status, sendEtherToContract.status + ' wasn\'t true') 120 | gasSpent = web3.utils.toBN(sendEtherToContract.cumulativeGasUsed.toString(10)).mul(gasPrice) 121 | balanceNext = web3.utils.toBN(await web3.eth.getBalance(accounts[0])) 122 | 123 | shouldBe = balanceLast.sub(gasSpent).sub(oneEth) // oneEth should be removed 124 | 125 | assert(balanceNext.toString(10) == shouldBe.toString(10), balanceNext + ' wasn\'t equal to ' + shouldBe + ' (1)') 126 | balanceLast = balanceNext 127 | 128 | nonce = await web3.eth.getTransactionCount(accounts[0]); 129 | tx = await arbitrage.withdrawEtherThenTransfer(oneEth.toString(10), {nonce}) 130 | gasSpent = web3.utils.toBN(tx.receipt.cumulativeGasUsed).mul(gasPrice) 131 | 132 | balanceNext = web3.utils.toBN(await web3.eth.getBalance(accounts[0])) 133 | shouldBe = balanceLast.sub(gasSpent).add(oneEth) // oneEth should be returned 134 | assert(balanceNext.toString(10) == shouldBe.toString(10), balanceNext + ' wasn\'t equal to ' + shouldBe + ' (2)') 135 | balanceLast = balanceNext 136 | assert(tx.receipt.status, tx.receipt.status + ' wasn\'t true') 137 | }) 138 | 139 | it('should not revert and update blances when transferEther()', async () => { 140 | let nonce = await web3.eth.getTransactionCount(accounts[0]); 141 | 142 | balanceLast = web3.utils.toBN(await web3.eth.getBalance(accounts[0])) 143 | const sendEtherToContract = await web3.eth.sendTransaction({ 144 | from: accounts[0], 145 | to: arbitrage.address, 146 | value: oneEth.toString(10), 147 | nonce, 148 | gasPrice 149 | }); 150 | assert(sendEtherToContract.status, sendEtherToContract.status + ' wasn\'t true') 151 | gasSpent = web3.utils.toBN(sendEtherToContract.cumulativeGasUsed.toString(10)).mul(gasPrice) 152 | balanceNext = web3.utils.toBN(await web3.eth.getBalance(accounts[0])) 153 | shouldBe = balanceLast.sub(gasSpent).sub(oneEth) // oneEth should be removed 154 | assert(balanceNext.toString(10) == shouldBe.toString(10), balanceNext + ' wasn\'t equal to ' + shouldBe) 155 | balanceLast = balanceNext 156 | 157 | nonce = await web3.eth.getTransactionCount(accounts[0]); 158 | 159 | tx = await arbitrage.transferEther(oneEth.toString(10), {nonce}) 160 | gasSpent = web3.utils.toBN(tx.receipt.cumulativeGasUsed).mul(gasPrice) 161 | balanceNext = web3.utils.toBN(await web3.eth.getBalance(accounts[0])) 162 | shouldBe = balanceLast.sub(gasSpent).add(oneEth) // oneEth should be returned 163 | assert(balanceNext.toString(10) == shouldBe.toString(10), balanceNext + ' wasn\'t equal to ' + shouldBe) 164 | balanceLast = balanceNext 165 | assert(tx.receipt.status, tx.receipt.status + ' wasn\'t true') 166 | }) 167 | 168 | it('should not revert when withdrawEther()', async () => { 169 | let nonce = await web3.eth.getTransactionCount(accounts[0]); 170 | 171 | await arbitrage.withdrawEther('1', {nonce}) 172 | }) 173 | 174 | it('should revert when not owner & withdrawEther()', async () => { 175 | let nonce = await web3.eth.getTransactionCount(accounts[0]); 176 | 177 | let err 178 | try { 179 | await arbitrage.withdrawEther('1', {from: accounts[1], nonce}) 180 | } catch(error) { 181 | err = error 182 | } 183 | assert(err, 'withdrawEther as non-owner did not fail') 184 | }) 185 | 186 | it('should not revert when withdrawToken()', async () => { 187 | let nonce = await web3.eth.getTransactionCount(accounts[0]); 188 | 189 | await arbitrage.withdrawToken(iToken.address, '1', {nonce}) 190 | }) 191 | 192 | it('should revert when not owner & withdrawToken()', async () => { 193 | let nonce = await web3.eth.getTransactionCount(accounts[0]); 194 | 195 | let err 196 | try { 197 | await arbitrage.withdrawToken(iToken.address, '1', {from: accounts[1], nonce}) 198 | } catch(error) { 199 | err = error 200 | } 201 | assert(err, 'withdrawToken as non-owner did not fail') 202 | }) 203 | 204 | it('should not revert when transferToken()', async () => { 205 | let nonce = await web3.eth.getTransactionCount(accounts[0]); 206 | 207 | await arbitrage.transferToken(iToken.address, 0, {nonce}) 208 | }) 209 | 210 | it('should revert when not owner & transferToken()', async () => { 211 | let nonce = await web3.eth.getTransactionCount(accounts[0]); 212 | 213 | let err 214 | try { 215 | await arbitrage.transferToken(iToken.address, 0, {from: accounts[1], nonce}) 216 | } catch(error) { 217 | err = error 218 | } 219 | assert(err, 'transferToken as non-owner did not fail') 220 | }) 221 | 222 | it('should not revert when depositToken()', async () => { 223 | let nonce = await web3.eth.getTransactionCount(accounts[0]); 224 | 225 | await arbitrage.depositToken(iToken.address, oneWei, {nonce}) 226 | }) 227 | 228 | it('should revert when not owner & depositToken()', async () => { 229 | let nonce = await web3.eth.getTransactionCount(accounts[0]); 230 | 231 | let err 232 | try { 233 | await arbitrage.depositToken(iToken.address, {from: accounts[1], nonce}) 234 | } catch(error) { 235 | err = error 236 | } 237 | assert(err, 'depositToken as non-owner did not fail') 238 | }) 239 | 240 | it('should not revert when dutchOpportunity()', async () => { 241 | let nonce = await web3.eth.getTransactionCount(accounts[0]); 242 | 243 | // needs to be a balnce on the contract so when it deposits the imaginary arbitrage results 244 | // there is something to be "deposited" 245 | const sendEtherToContract = await web3.eth.sendTransaction({ 246 | from: accounts[0], 247 | to: arbitrage.address, 248 | value: oneWei.toString(10), 249 | nonce 250 | }); 251 | assert(sendEtherToContract.status, sendEtherToContract.status + ' wasn\'t true') 252 | nonce = await web3.eth.getTransactionCount(accounts[0]); 253 | 254 | await arbitrage.dutchOpportunity(iToken.address, oneWei, {nonce}) 255 | }) 256 | 257 | it('should revert when not owner & dutchOpportunity()', async () => { 258 | let nonce = await web3.eth.getTransactionCount(accounts[0]); 259 | 260 | let err 261 | try { 262 | await arbitrage.dutchOpportunity(iToken.address, oneWei, {from: accounts[1], nonce}) 263 | } catch(error) { 264 | err = error 265 | } 266 | assert(err, 'dutchOpportunity as non-owner did not fail') 267 | }) 268 | 269 | it('should not revert when uniswapOpportunity()', async () => { 270 | let nonce = await web3.eth.getTransactionCount(accounts[0]); 271 | 272 | const sendEtherToContract = await web3.eth.sendTransaction({ 273 | from: accounts[0], 274 | to: arbitrage.address, 275 | value: oneWei.toString(10), 276 | nonce 277 | }); 278 | assert(sendEtherToContract.status, sendEtherToContract.status + ' wasn\'t true') 279 | nonce = await web3.eth.getTransactionCount(accounts[0]); 280 | 281 | await arbitrage.uniswapOpportunity(iToken.address, oneWei, {nonce}) 282 | }) 283 | 284 | it('should revert when not owner & uniswapOpportunity()', async () => { 285 | let nonce = await web3.eth.getTransactionCount(accounts[0]); 286 | 287 | let err 288 | try { 289 | await arbitrage.uniswapOpportunity(iToken.address, oneWei, {from: accounts[1], nonce}) 290 | } catch(error) { 291 | err = error 292 | } 293 | assert(err, 'uniswapOpportunity as non-owner did not fail') 294 | }) 295 | 296 | async function setMocks(accounts) { 297 | let nonce = await web3.eth.getTransactionCount(accounts[0]); 298 | 299 | const iToken_withdraw = iToken.contract.methods.withdraw(0).encodeABI() 300 | await mockEthToken.givenMethodReturnBool(iToken_withdraw, true, {nonce}) 301 | 302 | const deposit = iToken.contract.methods.deposit().encodeABI() 303 | await mockEthToken.givenMethodReturn(deposit, []) 304 | 305 | const balanceOf = iToken.contract.methods.balanceOf(emptyAdd).encodeABI() 306 | await mockEthToken.givenMethodReturnUint(balanceOf, '1') 307 | 308 | const allowance = iToken.contract.methods.allowance(emptyAdd, emptyAdd).encodeABI() 309 | await mockEthToken.givenMethodReturnUint(allowance, '0') 310 | 311 | const approve = iToken.contract.methods.approve(emptyAdd, 0).encodeABI() 312 | await mockEthToken.givenMethodReturnBool(approve, true) 313 | 314 | const transfer = iToken.contract.methods.transfer(emptyAdd, 0).encodeABI() 315 | await mockEthToken.givenMethodReturnBool(transfer, true) 316 | 317 | const transferFrom = iToken.contract.methods.transferFrom(emptyAdd, emptyAdd, 0).encodeABI() 318 | await mockEthToken.givenMethodReturnBool(transferFrom, true) 319 | 320 | 321 | const postBuyOrder = iDutchExchange.contract.methods.postBuyOrder(emptyAdd, emptyAdd, 0, 0).encodeABI() 322 | await mockDutchExchange.givenMethodReturnUint(postBuyOrder, '0') 323 | 324 | const getAuctionIndex = iDutchExchange.contract.methods.getAuctionIndex(emptyAdd, emptyAdd).encodeABI() 325 | await mockDutchExchange.givenMethodReturnUint(getAuctionIndex, '0') 326 | 327 | const ethToken = iDutchExchange.contract.methods.ethToken().encodeABI() 328 | await mockDutchExchange.givenMethodReturnAddress(ethToken, mockEthToken.address) 329 | 330 | const dutchDeposit = iDutchExchange.contract.methods.deposit(emptyAdd, 0).encodeABI() 331 | await mockDutchExchange.givenMethodReturnUint(dutchDeposit, oneWei) 332 | 333 | const iDutchExchange_withdraw = iDutchExchange.contract.methods.withdraw(emptyAdd, 0).encodeABI() 334 | await mockDutchExchange.givenMethodReturnUint(iDutchExchange_withdraw, 0) 335 | 336 | const claimBuyerFunds = iDutchExchange.contract.methods.claimBuyerFunds(emptyAdd, emptyAdd, emptyAdd, 0).encodeABI() 337 | const claimBuyerFundsReturn = abi.rawEncode(['uint', 'uint'], [2, 1]) 338 | await mockDutchExchange.givenMethodReturn(claimBuyerFunds, claimBuyerFundsReturn) 339 | 340 | 341 | const tokenToEthSwapInput = iUniswapExchange.contract.methods.tokenToEthSwapInput(0, 0, 0).encodeABI() 342 | await mockUniswapExchange.givenMethodReturnUint(tokenToEthSwapInput, 1) 343 | 344 | const ethToTokenSwapInput = iUniswapExchange.contract.methods.ethToTokenSwapInput(0, 0).encodeABI() 345 | await mockUniswapExchange.givenMethodReturnUint(ethToTokenSwapInput, 1) 346 | 347 | const getExchange = iUniswapFactory.contract.methods.getExchange(emptyAdd).encodeABI() 348 | await mockUniswapFactory.givenMethodReturnUint(getExchange, iUniswapExchange.address) 349 | 350 | } 351 | 352 | }) 353 | 354 | function getBlockNumber() { 355 | return new Promise((resolve, reject) => { 356 | web3.eth.getBlockNumber((error, result) => { 357 | if (error) reject(error) 358 | resolve(result) 359 | }) 360 | }) 361 | } 362 | 363 | function increaseBlocks(blocks) { 364 | return new Promise((resolve, reject) => { 365 | increaseBlock().then(() => { 366 | blocks -= 1 367 | if (blocks == 0) { 368 | resolve() 369 | } else { 370 | increaseBlocks(blocks).then(resolve) 371 | } 372 | }) 373 | }) 374 | } 375 | 376 | function increaseBlock() { 377 | return new Promise((resolve, reject) => { 378 | web3.currentProvider.sendAsync( 379 | { 380 | jsonrpc: '2.0', 381 | method: 'evm_mine', 382 | id: 12345 383 | }, 384 | (err, result) => { 385 | if (err) reject(err) 386 | resolve(result) 387 | } 388 | ) 389 | }) 390 | } 391 | 392 | function decodeEventString(hexVal) { 393 | return hexVal 394 | .match(/.{1,2}/g) 395 | .map(a => 396 | a 397 | .toLowerCase() 398 | .split('') 399 | .reduce( 400 | (result, ch) => result * 16 + '0123456789abcdefgh'.indexOf(ch), 401 | 0 402 | ) 403 | ) 404 | .map(a => String.fromCharCode(a)) 405 | .join('') 406 | } 407 | -------------------------------------------------------------------------------- /abi/UniswapExchange.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "TokenPurchase", 4 | "inputs": [ 5 | { 6 | "type": "address", 7 | "name": "buyer", 8 | "indexed": true 9 | }, 10 | { 11 | "type": "uint256", 12 | "name": "eth_sold", 13 | "indexed": true 14 | }, 15 | { 16 | "type": "uint256", 17 | "name": "tokens_bought", 18 | "indexed": true 19 | } 20 | ], 21 | "anonymous": false, 22 | "type": "event" 23 | }, 24 | { 25 | "name": "EthPurchase", 26 | "inputs": [ 27 | { 28 | "type": "address", 29 | "name": "buyer", 30 | "indexed": true 31 | }, 32 | { 33 | "type": "uint256", 34 | "name": "tokens_sold", 35 | "indexed": true 36 | }, 37 | { 38 | "type": "uint256", 39 | "name": "eth_bought", 40 | "indexed": true 41 | } 42 | ], 43 | "anonymous": false, 44 | "type": "event" 45 | }, 46 | { 47 | "name": "AddLiquidity", 48 | "inputs": [ 49 | { 50 | "type": "address", 51 | "name": "provider", 52 | "indexed": true 53 | }, 54 | { 55 | "type": "uint256", 56 | "name": "eth_amount", 57 | "indexed": true 58 | }, 59 | { 60 | "type": "uint256", 61 | "name": "token_amount", 62 | "indexed": true 63 | } 64 | ], 65 | "anonymous": false, 66 | "type": "event" 67 | }, 68 | { 69 | "name": "RemoveLiquidity", 70 | "inputs": [ 71 | { 72 | "type": "address", 73 | "name": "provider", 74 | "indexed": true 75 | }, 76 | { 77 | "type": "uint256", 78 | "name": "eth_amount", 79 | "indexed": true 80 | }, 81 | { 82 | "type": "uint256", 83 | "name": "token_amount", 84 | "indexed": true 85 | } 86 | ], 87 | "anonymous": false, 88 | "type": "event" 89 | }, 90 | { 91 | "name": "Transfer", 92 | "inputs": [ 93 | { 94 | "type": "address", 95 | "name": "_from", 96 | "indexed": true 97 | }, 98 | { 99 | "type": "address", 100 | "name": "_to", 101 | "indexed": true 102 | }, 103 | { 104 | "type": "uint256", 105 | "name": "_value", 106 | "indexed": false 107 | } 108 | ], 109 | "anonymous": false, 110 | "type": "event" 111 | }, 112 | { 113 | "name": "Approval", 114 | "inputs": [ 115 | { 116 | "type": "address", 117 | "name": "_owner", 118 | "indexed": true 119 | }, 120 | { 121 | "type": "address", 122 | "name": "_spender", 123 | "indexed": true 124 | }, 125 | { 126 | "type": "uint256", 127 | "name": "_value", 128 | "indexed": false 129 | } 130 | ], 131 | "anonymous": false, 132 | "type": "event" 133 | }, 134 | { 135 | "name": "setup", 136 | "outputs": [], 137 | "inputs": [ 138 | { 139 | "type": "address", 140 | "name": "token_addr" 141 | } 142 | ], 143 | "constant": false, 144 | "payable": false, 145 | "type": "function", 146 | "gas": 175875 147 | }, 148 | { 149 | "name": "addLiquidity", 150 | "outputs": [ 151 | { 152 | "type": "uint256", 153 | "name": "out" 154 | } 155 | ], 156 | "inputs": [ 157 | { 158 | "type": "uint256", 159 | "name": "min_liquidity" 160 | }, 161 | { 162 | "type": "uint256", 163 | "name": "max_tokens" 164 | }, 165 | { 166 | "type": "uint256", 167 | "name": "deadline" 168 | } 169 | ], 170 | "constant": false, 171 | "payable": true, 172 | "type": "function", 173 | "gas": 82605 174 | }, 175 | { 176 | "name": "removeLiquidity", 177 | "outputs": [ 178 | { 179 | "type": "uint256", 180 | "name": "out" 181 | }, 182 | { 183 | "type": "uint256", 184 | "name": "out" 185 | } 186 | ], 187 | "inputs": [ 188 | { 189 | "type": "uint256", 190 | "name": "amount" 191 | }, 192 | { 193 | "type": "uint256", 194 | "name": "min_eth" 195 | }, 196 | { 197 | "type": "uint256", 198 | "name": "min_tokens" 199 | }, 200 | { 201 | "type": "uint256", 202 | "name": "deadline" 203 | } 204 | ], 205 | "constant": false, 206 | "payable": false, 207 | "type": "function", 208 | "gas": 116814 209 | }, 210 | { 211 | "name": "__default__", 212 | "outputs": [], 213 | "inputs": [], 214 | "constant": false, 215 | "payable": true, 216 | "type": "function" 217 | }, 218 | { 219 | "name": "ethToTokenSwapInput", 220 | "outputs": [ 221 | { 222 | "type": "uint256", 223 | "name": "out" 224 | } 225 | ], 226 | "inputs": [ 227 | { 228 | "type": "uint256", 229 | "name": "min_tokens" 230 | }, 231 | { 232 | "type": "uint256", 233 | "name": "deadline" 234 | } 235 | ], 236 | "constant": false, 237 | "payable": true, 238 | "type": "function", 239 | "gas": 12757 240 | }, 241 | { 242 | "name": "ethToTokenTransferInput", 243 | "outputs": [ 244 | { 245 | "type": "uint256", 246 | "name": "out" 247 | } 248 | ], 249 | "inputs": [ 250 | { 251 | "type": "uint256", 252 | "name": "min_tokens" 253 | }, 254 | { 255 | "type": "uint256", 256 | "name": "deadline" 257 | }, 258 | { 259 | "type": "address", 260 | "name": "recipient" 261 | } 262 | ], 263 | "constant": false, 264 | "payable": true, 265 | "type": "function", 266 | "gas": 12965 267 | }, 268 | { 269 | "name": "ethToTokenSwapOutput", 270 | "outputs": [ 271 | { 272 | "type": "uint256", 273 | "name": "out" 274 | } 275 | ], 276 | "inputs": [ 277 | { 278 | "type": "uint256", 279 | "name": "tokens_bought" 280 | }, 281 | { 282 | "type": "uint256", 283 | "name": "deadline" 284 | } 285 | ], 286 | "constant": false, 287 | "payable": true, 288 | "type": "function", 289 | "gas": 50455 290 | }, 291 | { 292 | "name": "ethToTokenTransferOutput", 293 | "outputs": [ 294 | { 295 | "type": "uint256", 296 | "name": "out" 297 | } 298 | ], 299 | "inputs": [ 300 | { 301 | "type": "uint256", 302 | "name": "tokens_bought" 303 | }, 304 | { 305 | "type": "uint256", 306 | "name": "deadline" 307 | }, 308 | { 309 | "type": "address", 310 | "name": "recipient" 311 | } 312 | ], 313 | "constant": false, 314 | "payable": true, 315 | "type": "function", 316 | "gas": 50663 317 | }, 318 | { 319 | "name": "tokenToEthSwapInput", 320 | "outputs": [ 321 | { 322 | "type": "uint256", 323 | "name": "out" 324 | } 325 | ], 326 | "inputs": [ 327 | { 328 | "type": "uint256", 329 | "name": "tokens_sold" 330 | }, 331 | { 332 | "type": "uint256", 333 | "name": "min_eth" 334 | }, 335 | { 336 | "type": "uint256", 337 | "name": "deadline" 338 | } 339 | ], 340 | "constant": false, 341 | "payable": false, 342 | "type": "function", 343 | "gas": 47503 344 | }, 345 | { 346 | "name": "tokenToEthTransferInput", 347 | "outputs": [ 348 | { 349 | "type": "uint256", 350 | "name": "out" 351 | } 352 | ], 353 | "inputs": [ 354 | { 355 | "type": "uint256", 356 | "name": "tokens_sold" 357 | }, 358 | { 359 | "type": "uint256", 360 | "name": "min_eth" 361 | }, 362 | { 363 | "type": "uint256", 364 | "name": "deadline" 365 | }, 366 | { 367 | "type": "address", 368 | "name": "recipient" 369 | } 370 | ], 371 | "constant": false, 372 | "payable": false, 373 | "type": "function", 374 | "gas": 47712 375 | }, 376 | { 377 | "name": "tokenToEthSwapOutput", 378 | "outputs": [ 379 | { 380 | "type": "uint256", 381 | "name": "out" 382 | } 383 | ], 384 | "inputs": [ 385 | { 386 | "type": "uint256", 387 | "name": "eth_bought" 388 | }, 389 | { 390 | "type": "uint256", 391 | "name": "max_tokens" 392 | }, 393 | { 394 | "type": "uint256", 395 | "name": "deadline" 396 | } 397 | ], 398 | "constant": false, 399 | "payable": false, 400 | "type": "function", 401 | "gas": 50175 402 | }, 403 | { 404 | "name": "tokenToEthTransferOutput", 405 | "outputs": [ 406 | { 407 | "type": "uint256", 408 | "name": "out" 409 | } 410 | ], 411 | "inputs": [ 412 | { 413 | "type": "uint256", 414 | "name": "eth_bought" 415 | }, 416 | { 417 | "type": "uint256", 418 | "name": "max_tokens" 419 | }, 420 | { 421 | "type": "uint256", 422 | "name": "deadline" 423 | }, 424 | { 425 | "type": "address", 426 | "name": "recipient" 427 | } 428 | ], 429 | "constant": false, 430 | "payable": false, 431 | "type": "function", 432 | "gas": 50384 433 | }, 434 | { 435 | "name": "tokenToTokenSwapInput", 436 | "outputs": [ 437 | { 438 | "type": "uint256", 439 | "name": "out" 440 | } 441 | ], 442 | "inputs": [ 443 | { 444 | "type": "uint256", 445 | "name": "tokens_sold" 446 | }, 447 | { 448 | "type": "uint256", 449 | "name": "min_tokens_bought" 450 | }, 451 | { 452 | "type": "uint256", 453 | "name": "min_eth_bought" 454 | }, 455 | { 456 | "type": "uint256", 457 | "name": "deadline" 458 | }, 459 | { 460 | "type": "address", 461 | "name": "token_addr" 462 | } 463 | ], 464 | "constant": false, 465 | "payable": false, 466 | "type": "function", 467 | "gas": 51007 468 | }, 469 | { 470 | "name": "tokenToTokenTransferInput", 471 | "outputs": [ 472 | { 473 | "type": "uint256", 474 | "name": "out" 475 | } 476 | ], 477 | "inputs": [ 478 | { 479 | "type": "uint256", 480 | "name": "tokens_sold" 481 | }, 482 | { 483 | "type": "uint256", 484 | "name": "min_tokens_bought" 485 | }, 486 | { 487 | "type": "uint256", 488 | "name": "min_eth_bought" 489 | }, 490 | { 491 | "type": "uint256", 492 | "name": "deadline" 493 | }, 494 | { 495 | "type": "address", 496 | "name": "recipient" 497 | }, 498 | { 499 | "type": "address", 500 | "name": "token_addr" 501 | } 502 | ], 503 | "constant": false, 504 | "payable": false, 505 | "type": "function", 506 | "gas": 51098 507 | }, 508 | { 509 | "name": "tokenToTokenSwapOutput", 510 | "outputs": [ 511 | { 512 | "type": "uint256", 513 | "name": "out" 514 | } 515 | ], 516 | "inputs": [ 517 | { 518 | "type": "uint256", 519 | "name": "tokens_bought" 520 | }, 521 | { 522 | "type": "uint256", 523 | "name": "max_tokens_sold" 524 | }, 525 | { 526 | "type": "uint256", 527 | "name": "max_eth_sold" 528 | }, 529 | { 530 | "type": "uint256", 531 | "name": "deadline" 532 | }, 533 | { 534 | "type": "address", 535 | "name": "token_addr" 536 | } 537 | ], 538 | "constant": false, 539 | "payable": false, 540 | "type": "function", 541 | "gas": 54928 542 | }, 543 | { 544 | "name": "tokenToTokenTransferOutput", 545 | "outputs": [ 546 | { 547 | "type": "uint256", 548 | "name": "out" 549 | } 550 | ], 551 | "inputs": [ 552 | { 553 | "type": "uint256", 554 | "name": "tokens_bought" 555 | }, 556 | { 557 | "type": "uint256", 558 | "name": "max_tokens_sold" 559 | }, 560 | { 561 | "type": "uint256", 562 | "name": "max_eth_sold" 563 | }, 564 | { 565 | "type": "uint256", 566 | "name": "deadline" 567 | }, 568 | { 569 | "type": "address", 570 | "name": "recipient" 571 | }, 572 | { 573 | "type": "address", 574 | "name": "token_addr" 575 | } 576 | ], 577 | "constant": false, 578 | "payable": false, 579 | "type": "function", 580 | "gas": 55019 581 | }, 582 | { 583 | "name": "tokenToExchangeSwapInput", 584 | "outputs": [ 585 | { 586 | "type": "uint256", 587 | "name": "out" 588 | } 589 | ], 590 | "inputs": [ 591 | { 592 | "type": "uint256", 593 | "name": "tokens_sold" 594 | }, 595 | { 596 | "type": "uint256", 597 | "name": "min_tokens_bought" 598 | }, 599 | { 600 | "type": "uint256", 601 | "name": "min_eth_bought" 602 | }, 603 | { 604 | "type": "uint256", 605 | "name": "deadline" 606 | }, 607 | { 608 | "type": "address", 609 | "name": "exchange_addr" 610 | } 611 | ], 612 | "constant": false, 613 | "payable": false, 614 | "type": "function", 615 | "gas": 49342 616 | }, 617 | { 618 | "name": "tokenToExchangeTransferInput", 619 | "outputs": [ 620 | { 621 | "type": "uint256", 622 | "name": "out" 623 | } 624 | ], 625 | "inputs": [ 626 | { 627 | "type": "uint256", 628 | "name": "tokens_sold" 629 | }, 630 | { 631 | "type": "uint256", 632 | "name": "min_tokens_bought" 633 | }, 634 | { 635 | "type": "uint256", 636 | "name": "min_eth_bought" 637 | }, 638 | { 639 | "type": "uint256", 640 | "name": "deadline" 641 | }, 642 | { 643 | "type": "address", 644 | "name": "recipient" 645 | }, 646 | { 647 | "type": "address", 648 | "name": "exchange_addr" 649 | } 650 | ], 651 | "constant": false, 652 | "payable": false, 653 | "type": "function", 654 | "gas": 49532 655 | }, 656 | { 657 | "name": "tokenToExchangeSwapOutput", 658 | "outputs": [ 659 | { 660 | "type": "uint256", 661 | "name": "out" 662 | } 663 | ], 664 | "inputs": [ 665 | { 666 | "type": "uint256", 667 | "name": "tokens_bought" 668 | }, 669 | { 670 | "type": "uint256", 671 | "name": "max_tokens_sold" 672 | }, 673 | { 674 | "type": "uint256", 675 | "name": "max_eth_sold" 676 | }, 677 | { 678 | "type": "uint256", 679 | "name": "deadline" 680 | }, 681 | { 682 | "type": "address", 683 | "name": "exchange_addr" 684 | } 685 | ], 686 | "constant": false, 687 | "payable": false, 688 | "type": "function", 689 | "gas": 53233 690 | }, 691 | { 692 | "name": "tokenToExchangeTransferOutput", 693 | "outputs": [ 694 | { 695 | "type": "uint256", 696 | "name": "out" 697 | } 698 | ], 699 | "inputs": [ 700 | { 701 | "type": "uint256", 702 | "name": "tokens_bought" 703 | }, 704 | { 705 | "type": "uint256", 706 | "name": "max_tokens_sold" 707 | }, 708 | { 709 | "type": "uint256", 710 | "name": "max_eth_sold" 711 | }, 712 | { 713 | "type": "uint256", 714 | "name": "deadline" 715 | }, 716 | { 717 | "type": "address", 718 | "name": "recipient" 719 | }, 720 | { 721 | "type": "address", 722 | "name": "exchange_addr" 723 | } 724 | ], 725 | "constant": false, 726 | "payable": false, 727 | "type": "function", 728 | "gas": 53423 729 | }, 730 | { 731 | "name": "getEthToTokenInputPrice", 732 | "outputs": [ 733 | { 734 | "type": "uint256", 735 | "name": "out" 736 | } 737 | ], 738 | "inputs": [ 739 | { 740 | "type": "uint256", 741 | "name": "eth_sold" 742 | } 743 | ], 744 | "constant": true, 745 | "stateMutability": "view", 746 | "payable": false, 747 | "type": "function", 748 | "gas": 5542 749 | }, 750 | { 751 | "name": "getEthToTokenOutputPrice", 752 | "outputs": [ 753 | { 754 | "type": "uint256", 755 | "name": "out" 756 | } 757 | ], 758 | "inputs": [ 759 | { 760 | "type": "uint256", 761 | "name": "tokens_bought" 762 | } 763 | ], 764 | "constant": true, 765 | "stateMutability": "view", 766 | "payable": false, 767 | "type": "function", 768 | "gas": 6872 769 | }, 770 | { 771 | "name": "getTokenToEthInputPrice", 772 | "outputs": [ 773 | { 774 | "type": "uint256", 775 | "name": "out" 776 | } 777 | ], 778 | "inputs": [ 779 | { 780 | "type": "uint256", 781 | "name": "tokens_sold" 782 | } 783 | ], 784 | "constant": true, 785 | "stateMutability": "view", 786 | "payable": false, 787 | "type": "function", 788 | "gas": 5637 789 | }, 790 | { 791 | "name": "getTokenToEthOutputPrice", 792 | "outputs": [ 793 | { 794 | "type": "uint256", 795 | "name": "out" 796 | } 797 | ], 798 | "inputs": [ 799 | { 800 | "type": "uint256", 801 | "name": "eth_bought" 802 | } 803 | ], 804 | "constant": true, 805 | "stateMutability": "view", 806 | "payable": false, 807 | "type": "function", 808 | "gas": 6897 809 | }, 810 | { 811 | "name": "tokenAddress", 812 | "outputs": [ 813 | { 814 | "type": "address", 815 | "name": "out" 816 | } 817 | ], 818 | "inputs": [], 819 | "constant": true, 820 | "stateMutability": "view", 821 | "payable": false, 822 | "type": "function", 823 | "gas": 1413 824 | }, 825 | { 826 | "name": "factoryAddress", 827 | "outputs": [ 828 | { 829 | "type": "address", 830 | "name": "out" 831 | } 832 | ], 833 | "inputs": [], 834 | "constant": true, 835 | "stateMutability": "view", 836 | "payable": false, 837 | "type": "function", 838 | "gas": 1443 839 | }, 840 | { 841 | "name": "balanceOf", 842 | "outputs": [ 843 | { 844 | "type": "uint256", 845 | "name": "out" 846 | } 847 | ], 848 | "inputs": [ 849 | { 850 | "type": "address", 851 | "name": "_owner" 852 | } 853 | ], 854 | "constant": true, 855 | "stateMutability": "view", 856 | "payable": false, 857 | "type": "function", 858 | "gas": 1645 859 | }, 860 | { 861 | "name": "transfer", 862 | "outputs": [ 863 | { 864 | "type": "bool", 865 | "name": "out" 866 | } 867 | ], 868 | "inputs": [ 869 | { 870 | "type": "address", 871 | "name": "_to" 872 | }, 873 | { 874 | "type": "uint256", 875 | "name": "_value" 876 | } 877 | ], 878 | "constant": false, 879 | "payable": false, 880 | "type": "function", 881 | "gas": 75034 882 | }, 883 | { 884 | "name": "transferFrom", 885 | "outputs": [ 886 | { 887 | "type": "bool", 888 | "name": "out" 889 | } 890 | ], 891 | "inputs": [ 892 | { 893 | "type": "address", 894 | "name": "_from" 895 | }, 896 | { 897 | "type": "address", 898 | "name": "_to" 899 | }, 900 | { 901 | "type": "uint256", 902 | "name": "_value" 903 | } 904 | ], 905 | "constant": false, 906 | "payable": false, 907 | "type": "function", 908 | "gas": 110907 909 | }, 910 | { 911 | "name": "approve", 912 | "outputs": [ 913 | { 914 | "type": "bool", 915 | "name": "out" 916 | } 917 | ], 918 | "inputs": [ 919 | { 920 | "type": "address", 921 | "name": "_spender" 922 | }, 923 | { 924 | "type": "uint256", 925 | "name": "_value" 926 | } 927 | ], 928 | "constant": false, 929 | "payable": false, 930 | "type": "function", 931 | "gas": 38769 932 | }, 933 | { 934 | "name": "allowance", 935 | "outputs": [ 936 | { 937 | "type": "uint256", 938 | "name": "out" 939 | } 940 | ], 941 | "inputs": [ 942 | { 943 | "type": "address", 944 | "name": "_owner" 945 | }, 946 | { 947 | "type": "address", 948 | "name": "_spender" 949 | } 950 | ], 951 | "constant": true, 952 | "stateMutability": "view", 953 | "payable": false, 954 | "type": "function", 955 | "gas": 1925 956 | }, 957 | { 958 | "name": "name", 959 | "outputs": [ 960 | { 961 | "type": "bytes32", 962 | "name": "out" 963 | } 964 | ], 965 | "inputs": [], 966 | "constant": true, 967 | "stateMutability": "view", 968 | "payable": false, 969 | "type": "function", 970 | "gas": 1623 971 | }, 972 | { 973 | "name": "symbol", 974 | "outputs": [ 975 | { 976 | "type": "bytes32", 977 | "name": "out" 978 | } 979 | ], 980 | "inputs": [], 981 | "constant": true, 982 | "stateMutability": "view", 983 | "payable": false, 984 | "type": "function", 985 | "gas": 1653 986 | }, 987 | { 988 | "name": "decimals", 989 | "outputs": [ 990 | { 991 | "type": "uint256", 992 | "name": "out" 993 | } 994 | ], 995 | "inputs": [], 996 | "constant": true, 997 | "stateMutability": "view", 998 | "payable": false, 999 | "type": "function", 1000 | "gas": 1683 1001 | }, 1002 | { 1003 | "name": "totalSupply", 1004 | "outputs": [ 1005 | { 1006 | "type": "uint256", 1007 | "name": "out" 1008 | } 1009 | ], 1010 | "inputs": [], 1011 | "constant": true, 1012 | "stateMutability": "view", 1013 | "payable": false, 1014 | "type": "function", 1015 | "gas": 1713 1016 | } 1017 | ] -------------------------------------------------------------------------------- /scripts/uniswap-bytecode.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | exchange: '0x61309c56600035601c52740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a0526366d38203600051141561013b57602060046101403734156100b457600080fd5b60043560205181106100c557600080fd5b506000610140511415600654156007541516166100e157600080fd5b33600755610140516006557f556e6973776170205631000000000000000000000000000000000000000000006000557f554e492d563100000000000000000000000000000000000000000000000000006001556012600255005b63422f104360005114156105ab5760606004610140376000341160006101605111164261018051111661016d57600080fd5b6003546101a05260006101a051111561043e576000610140511161019057600080fd5b343031101561019e57600080fd5b343031036103a0526006543b6101b357600080fd5b6006543014156101c257600080fd5b602061046060246370a082316103e05230610400526103fc6006545afa6101e857600080fd5b600050610460516103c0526103a05161020057600080fd5b6103a05134151561021257600061022f565b6103c051346103c0513402041461022857600080fd5b6103c05134025b0460016103a05161023f57600080fd5b6103a05134151561025157600061026e565b6103c051346103c0513402041461026757600080fd5b6103c05134025b0401101561027b57600080fd5b60016103a05161028a57600080fd5b6103a05134151561029c5760006102b9565b6103c051346103c051340204146102b257600080fd5b6103c05134025b0401610480526103a0516102cc57600080fd5b6103a0513415156102de5760006102fb565b6101a051346101a051340204146102f457600080fd5b6101a05134025b046104a052610140516104a0511015610480516101605110151661031e57600080fd5b60043360e05260c052604060c02080546104a051825401101561034057600080fd5b6104a0518154018155506101a0516104a0516101a05101101561036257600080fd5b6104a0516101a051016003556006543b61037b57600080fd5b60065430141561038a57600080fd5b602061058060646323b872dd6104c052336104e052306105005261048051610520526104dc60006006545af16103bf57600080fd5b600050610580516103cf57600080fd5b6104805134337f06239653922ac7bea6aa2b19dc486b9361821d37712eb796adfd38d81de278ca60006000a46104a0516105a0523360007fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206105a0a36104a05160005260206000f36105a9565b633b9aca003410156000600654141560006007541415161661045f57600080fd5b306007543b61046d57600080fd5b60075430141561047c57600080fd5b602061024060246306f2bf626101c0526006546101e0526101dc6007545afa6104a457600080fd5b60005061024051146104b557600080fd5b6101605161026052303161028052610280516003556102805160043360e05260c052604060c020556006543b6104ea57600080fd5b6006543014156104f957600080fd5b602061036060646323b872dd6102a052336102c052306102e05261026051610300526102bc60006006545af161052e57600080fd5b6000506103605161053e57600080fd5b6102605134337f06239653922ac7bea6aa2b19dc486b9361821d37712eb796adfd38d81de278ca60006000a461028051610380523360007fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef6020610380a36102805160005260206000f35b005b63f88bf15a600051141561084a57608060046101403734156105cc57600080fd5b600061018051116000610160511116426101a051116000610140511116166105f357600080fd5b6003546101c05260006101c0511161060a57600080fd5b6006543b61061757600080fd5b60065430141561062657600080fd5b602061028060246370a0823161020052306102205261021c6006545afa61064c57600080fd5b600050610280516101e0526101c05161066457600080fd5b6101c051610140511515610679576000610699565b30316101405130316101405102041461069157600080fd5b303161014051025b046102a0526101c0516106ab57600080fd5b6101c0516101405115156106c05760006106e6565b6101e051610140516101e051610140510204146106dc57600080fd5b6101e05161014051025b046102c052610180516102c0511015610160516102a05110151661070957600080fd5b60043360e05260c052604060c020610140518154101561072857600080fd5b61014051815403815550610140516101c051101561074557600080fd5b610140516101c0510360035560006000600060006102a051336000f161076a57600080fd5b6006543b61077757600080fd5b60065430141561078657600080fd5b6020610380604463a9059cbb6102e05233610300526102c051610320526102fc60006006545af16107b657600080fd5b600050610380516107c657600080fd5b6102c0516102a051337f0fbf06c058b90cb038a618f8c2acbf6145f8b3570fd1fa56abb8f0f3f05b36e860006000a4610140516103a0526000337fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206103a0a360406103c0526103e06102a05181526102c0518160200152506103c0516103e0f3005b6000156109c6575b6101a05261014052610160526101805260006101805111600061016051111661087a57600080fd5b61014051151561088b5760006108ae565b6103e5610140516103e5610140510204146108a557600080fd5b6103e561014051025b6101c0526101c05115156108c35760006108e9565b610180516101c051610180516101c0510204146108df57600080fd5b610180516101c051025b6101e0526101605115156108fe576000610921565b6103e8610160516103e86101605102041461091857600080fd5b6103e861016051025b6101c051610160511515610936576000610959565b6103e8610160516103e86101605102041461095057600080fd5b6103e861016051025b01101561096557600080fd5b6101c05161016051151561097a57600061099d565b6103e8610160516103e86101605102041461099457600080fd5b6103e861016051025b0161020052610200516109af57600080fd5b610200516101e051046000526000516101a0515650005b600015610bf3575b6101a0526101405261016052610180526000610180511160006101605111166109f657600080fd5b610160511515610a07576000610a2d565b61014051610160516101405161016051020414610a2357600080fd5b6101405161016051025b1515610a3a576000610af6565b6103e8610160511515610a4e576000610a74565b61014051610160516101405161016051020414610a6a57600080fd5b6101405161016051025b6103e8610160511515610a88576000610aae565b61014051610160516101405161016051020414610aa457600080fd5b6101405161016051025b020414610aba57600080fd5b6103e8610160511515610ace576000610af4565b61014051610160516101405161016051020414610aea57600080fd5b6101405161016051025b025b6101c05261014051610180511015610b0d57600080fd5b6101405161018051031515610b23576000610b8e565b6103e561014051610180511015610b3957600080fd5b6101405161018051036103e561014051610180511015610b5857600080fd5b610140516101805103020414610b6d57600080fd5b6103e561014051610180511015610b8357600080fd5b610140516101805103025b6101e0526101e051610b9f57600080fd5b6101e0516101c0510460016101e051610bb757600080fd5b6101e0516101c05104011015610bcc57600080fd5b60016101e051610bdb57600080fd5b6101e0516101c05104016000526000516101a0515650005b600015610df4575b6101e0526101405261016052610180526101a0526101c0526000610160511160006101405111164261018051101516610c3357600080fd5b6006543b610c4057600080fd5b600654301415610c4f57600080fd5b60206102a060246370a0823161022052306102405261023c6006545afa610c7557600080fd5b6000506102a051610200526101405161016051610180516101a0516101c0516101e05161020051610220516102405161026051610280516102a0516102c0516389f2a8716102e05261014051610300526101405130311015610cd657600080fd5b6101405130310361032052610200516103405261034051610320516103005160065801610852565b6103a0526102c0526102a05261028052610260526102405261022052610200526101e0526101c0526101a0526101805261016052610140526103a0516102c052610160516102c0511015610d5157600080fd5b6006543b610d5e57600080fd5b600654301415610d6d57600080fd5b6020610460604463a9059cbb6103c0526101c0516103e0526102c051610400526103dc60006006545af1610da057600080fd5b60005061046051610db057600080fd5b6102c051610140516101a0517fcd60aa75dea3072fbc07ae6d7d856b5dc5f4eee88854f5b4abf7b680ef8bc50f60006000a46102c0516000526000516101e0515650005b63f39b5b9b6000511415610e715760406004610140376101405161016051638c717a3361018052346101a052610140516101c052610160516101e0523361020052336102205261022051610200516101e0516101c0516101a05160065801610bfb565b6102805261016052610140526102805160005260206000f3005b63ad65d76d6000511415610f245760606004610140376044356020518110610e9857600080fd5b5060006101805114153061018051141516610eb257600080fd5b610140516101605161018051638c717a336101a052346101c052610140516101e0526101605161020052336102205261018051610240526102405161022051610200516101e0516101c05160065801610bfb565b6102a0526101805261016052610140526102a05160005260206000f3005b60001561116c575b6101e0526101405261016052610180526101a0526101c0526000610160511160006101405111164261018051101516610f6457600080fd5b6006543b610f7157600080fd5b600654301415610f8057600080fd5b60206102a060246370a0823161022052306102405261023c6006545afa610fa657600080fd5b6000506102a051610200526101405161016051610180516101a0516101c0516101e05161020051610220516102405161026051610280516102a0516102c05163fd11c2236102e0526101405161030052610160513031101561100757600080fd5b61016051303103610320526102005161034052610340516103205161030051600658016109ce565b6103a0526102c0526102a05261028052610260526102405261022052610200526101e0526101c0526101a0526101805261016052610140526103a0516102c05260016102c051026103e0526103e05161016051101561108d57600080fd5b6103e05161016051036103c05260006103c05111156110c35760006000600060006103c0516101a0516000f16110c257600080fd5b5b6006543b6110d057600080fd5b6006543014156110df57600080fd5b60206104a0604463a9059cbb610400526101c05161042052610140516104405261041c60006006545af161111257600080fd5b6000506104a05161112257600080fd5b6101405160016102c051026101a0517fcd60aa75dea3072fbc07ae6d7d856b5dc5f4eee88854f5b4abf7b680ef8bc50f60006000a460016102c051026000526000516101e0515650005b636b1d4db760005114156111e95760406004610140376101405161016051632dff394e61018052610140516101a052346101c052610160516101e0523361020052336102205261022051610200516101e0516101c0516101a05160065801610f2c565b6102805261016052610140526102805160005260206000f3005b630b573638600051141561129c576060600461014037604435602051811061121057600080fd5b506000610180511415306101805114151661122a57600080fd5b610140516101605161018051632dff394e6101a052610140516101c052346101e0526101605161020052336102205261018051610240526102405161022051610200516101e0516101c05160065801610f2c565b6102a0526101805261016052610140526102a05160005260206000f3005b6000156114b3575b6101e0526101405261016052610180526101a0526101c05260006101605111600061014051111642610180511015166112dc57600080fd5b6006543b6112e957600080fd5b6006543014156112f857600080fd5b60206102a060246370a0823161022052306102405261023c6006545afa61131e57600080fd5b6000506102a051610200526101405161016051610180516101a0516101c0516101e05161020051610220516102405161026051610280516102a0516102c0516389f2a8716102e0526101405161030052610200516103205230316103405261034051610320516103005160065801610852565b6103a0526102c0526102a05261028052610260526102405261022052610200526101e0526101c0526101a0526101805261016052610140526103a0516102c05260016102c051026103c052610160516103c05110156113ef57600080fd5b60006000600060006103c0516101c0516000f161140b57600080fd5b6006543b61141857600080fd5b60065430141561142757600080fd5b60206104a060646323b872dd6103e0526101a05161040052306104205261014051610440526103fc60006006545af161145f57600080fd5b6000506104a05161146f57600080fd5b6103c051610140516101a0517f7f4091b46c33e918a0f3aa42307641d17bb67029427a5369e54b35398423870560006000a46103c0516000526000516101e0515650005b6395e3c50b600051141561154657606060046101403734156114d457600080fd5b61014051610160516101805163fa1bb7be6101a052610140516101c052610160516101e0526101805161020052336102205233610240526102405161022051610200516101e0516101c051600658016112a4565b6102a0526101805261016052610140526102a05160005260206000f3005b637237e031600051141561160f576080600461014037341561156757600080fd5b606435602051811061157857600080fd5b5060006101a0511415306101a05114151661159257600080fd5b6101405161016051610180516101a05163fa1bb7be6101c052610140516101e0526101605161020052610180516102205233610240526101a05161026052610260516102405161022051610200516101e051600658016112a4565b6102c0526101a0526101805261016052610140526102c05160005260206000f3005b600015611813575b6101e0526101405261016052610180526101a0526101c05260006101405111426101805110151661164757600080fd5b6006543b61165457600080fd5b60065430141561166357600080fd5b60206102a060246370a0823161022052306102405261023c6006545afa61168957600080fd5b6000506102a051610200526101405161016051610180516101a0516101c0516101e05161020051610220516102405161026051610280516102a0516102c05163fd11c2236102e05261014051610300526102005161032052303161034052610340516103205161030051600658016109ce565b6103a0526102c0526102a05261028052610260526102405261022052610200526101e0526101c0526101a0526101805261016052610140526103a0516102c0526102c05161016051101561174f57600080fd5b6000600060006000610140516101c0516000f161176b57600080fd5b6006543b61177857600080fd5b60065430141561178757600080fd5b602061048060646323b872dd6103c0526101a0516103e05230610400526102c051610420526103dc60006006545af16117bf57600080fd5b600050610480516117cf57600080fd5b610140516102c0516101a0517f7f4091b46c33e918a0f3aa42307641d17bb67029427a5369e54b35398423870560006000a46102c0516000526000516101e0515650005b63013efd8b60005114156118a6576060600461014037341561183457600080fd5b61014051610160516101805163984fe8f66101a052610140516101c052610160516101e0526101805161020052336102205233610240526102405161022051610200516101e0516101c05160065801611617565b6102a0526101805261016052610140526102a05160005260206000f3005b63d4e4841d600051141561196f57608060046101403734156118c757600080fd5b60643560205181106118d857600080fd5b5060006101a0511415306101a0511415166118f257600080fd5b6101405161016051610180516101a05163984fe8f66101c052610140516101e0526101605161020052610180516102205233610240526101a05161026052610260516102405161022051610200516101e05160065801611617565b6102c0526101a0526101805261016052610140526102c05160005260206000f3005b600015611c0a575b610220526101405261016052610180526101a0526101c0526101e0526102005260006101805111600061016051111660006101405111426101a051101516166119bf57600080fd5b600061020051141530610200511415166119d857600080fd5b6006543b6119e557600080fd5b6006543014156119f457600080fd5b60206102e060246370a0823161026052306102805261027c6006545afa611a1a57600080fd5b6000506102e051610240526101405161016051610180516101a0516101c0516101e05161020051610220516102405161026051610280516102a0516102c0516102e051610300516389f2a871610320526101405161034052610240516103605230316103805261038051610360516103405160065801610852565b6103e052610300526102e0526102c0526102a05261028052610260526102405261022052610200526101e0526101c0526101a0526101805261016052610140526103e05161030052600161030051026104005261018051610400511015611afb57600080fd5b6006543b611b0857600080fd5b600654301415611b1757600080fd5b60206104e060646323b872dd610420526101c051610440523061046052610140516104805261043c60006006545af1611b4f57600080fd5b6000506104e051611b5f57600080fd5b610200513b611b6d57600080fd5b61020051301415611b7d57600080fd5b60206105e0606463ad65d76d6105205261016051610540526101a051610560526101e0516105805261053c61040051610200515af1611bbb57600080fd5b6000506105e0516105005261040051610140516101c0517f7f4091b46c33e918a0f3aa42307641d17bb67029427a5369e54b35398423870560006000a461050051600052600051610220515650005b63ddf7e1a76000511415611d575760a06004610140373415611c2b57600080fd5b6084356020518110611c3c57600080fd5b506007543b611c4a57600080fd5b600754301415611c5957600080fd5b602061028060246306f2bf62610200526101c0516102205261021c6007545afa611c8257600080fd5b600050610280516101e0526101405161016051610180516101a0516101c0516101e051610200516102205161024051610260516102805163204ea33b6102a052610140516102c052610160516102e05261018051610300526101a05161032052336103405233610360526101e0516103805261038051610360516103405161032051610300516102e0516102c05160065801611977565b6103e05261028052610260526102405261022052610200526101e0526101c0526101a0526101805261016052610140526103e05160005260206000f3005b63f552d91b6000511415611ec15760c06004610140373415611d7857600080fd5b6084356020518110611d8957600080fd5b5060a4356020518110611d9b57600080fd5b506007543b611da957600080fd5b600754301415611db857600080fd5b60206102a060246306f2bf62610220526101e0516102405261023c6007545afa611de157600080fd5b6000506102a051610200526101405161016051610180516101a0516101c0516101e05161020051610220516102405161026051610280516102a05163204ea33b6102c052610140516102e052610160516103005261018051610320526101a0516103405233610360526101c05161038052610200516103a0526103a05161038051610360516103405161032051610300516102e05160065801611977565b610400526102a05261028052610260526102405261022052610200526101e0526101c0526101a0526101805261016052610140526104005160005260206000f3005b6000156121d7575b610220526101405261016052610180526101a0526101c0526101e05261020052600061018051116000610140511116426101a051101516611f0957600080fd5b60006102005114153061020051141516611f2257600080fd5b610200513b611f3057600080fd5b61020051301415611f4057600080fd5b60206102e060246359e9486261026052610140516102805261027c610200515afa611f6a57600080fd5b6000506102e051610240526006543b611f8257600080fd5b600654301415611f9157600080fd5b60206103a060246370a0823161032052306103405261033c6006545afa611fb757600080fd5b6000506103a051610300526101405161016051610180516101a0516101c0516101e05161020051610220516102405161026051610280516102a0516102c0516102e05161030051610320516103405161036051610380516103a0516103c05163fd11c2236103e05261024051610400526103005161042052303161044052610440516104205161040051600658016109ce565b6104a0526103c0526103a05261038052610360526103405261032052610300526102e0526102c0526102a05261028052610260526102405261022052610200526101e0526101c0526101a0526101805261016052610140526104a0516103c052610240516101805110156103c051610160511015166120c857600080fd5b6006543b6120d557600080fd5b6006543014156120e457600080fd5b602061058060646323b872dd6104c0526101c0516104e05230610500526103c051610520526104dc60006006545af161211c57600080fd5b6000506105805161212c57600080fd5b610200513b61213a57600080fd5b6102005130141561214a57600080fd5b60206106806064630b5736386105c052610140516105e0526101a051610600526101e051610620526105dc61024051610200515af161218857600080fd5b600050610680516105a052610240516103c0516101c0517f7f4091b46c33e918a0f3aa42307641d17bb67029427a5369e54b35398423870560006000a46103c051600052600051610220515650005b63b040d54560005114156123245760a060046101403734156121f857600080fd5b608435602051811061220957600080fd5b506007543b61221757600080fd5b60075430141561222657600080fd5b602061028060246306f2bf62610200526101c0516102205261021c6007545afa61224f57600080fd5b600050610280516101e0526101405161016051610180516101a0516101c0516101e0516102005161022051610240516102605161028051631a7b28f26102a052610140516102c052610160516102e05261018051610300526101a05161032052336103405233610360526101e0516103805261038051610360516103405161032051610300516102e0516102c05160065801611ec9565b6103e05261028052610260526102405261022052610200526101e0526101c0526101a0526101805261016052610140526103e05160005260206000f3005b63f3c0efe9600051141561248e5760c0600461014037341561234557600080fd5b608435602051811061235657600080fd5b5060a435602051811061236857600080fd5b506007543b61237657600080fd5b60075430141561238557600080fd5b60206102a060246306f2bf62610220526101e0516102405261023c6007545afa6123ae57600080fd5b6000506102a051610200526101405161016051610180516101a0516101c0516101e05161020051610220516102405161026051610280516102a051631a7b28f26102c052610140516102e052610160516103005261018051610320526101a0516103405233610360526101c05161038052610200516103a0526103a05161038051610360516103405161032051610300516102e05160065801611ec9565b610400526102a05261028052610260526102405261022052610200526101e0526101c0526101a0526101805261016052610140526104005160005260206000f3005b63b1cb43bf600051141561255b5760a060046101403734156124af57600080fd5b60843560205181106124c057600080fd5b506101405161016051610180516101a0516101c05163204ea33b6101e0526101405161020052610160516102205261018051610240526101a051610260523361028052336102a0526101c0516102c0526102c0516102a051610280516102605161024051610220516102005160065801611977565b610320526101c0526101a0526101805261016052610140526103205160005260206000f3005b63ec384a3e60005114156126555760c0600461014037341561257c57600080fd5b608435602051811061258d57600080fd5b5060a435602051811061259f57600080fd5b50306101c05114156125b057600080fd5b6101405161016051610180516101a0516101c0516101e05163204ea33b610200526101405161022052610160516102405261018051610260526101a05161028052336102a0526101c0516102c0526101e0516102e0526102e0516102c0516102a0516102805161026051610240516102205160065801611977565b610340526101e0526101c0526101a0526101805261016052610140526103405160005260206000f3005b63ea650c7d60005114156127225760a0600461014037341561267657600080fd5b608435602051811061268757600080fd5b506101405161016051610180516101a0516101c051631a7b28f26101e0526101405161020052610160516102205261018051610240526101a051610260523361028052336102a0526101c0516102c0526102c0516102a051610280516102605161024051610220516102005160065801611ec9565b610320526101c0526101a0526101805261016052610140526103205160005260206000f3005b63981a1327600051141561281c5760c0600461014037341561274357600080fd5b608435602051811061275457600080fd5b5060a435602051811061276657600080fd5b50306101c051141561277757600080fd5b6101405161016051610180516101a0516101c0516101e051631a7b28f2610200526101405161022052610160516102405261018051610260526101a05161028052336102a0526101c0516102c0526101e0516102e0526102e0516102c0516102a0516102805161026051610240516102205160065801611ec9565b610340526101e0526101c0526101a0526101805261016052610140526103405160005260206000f3005b63cd7724c36000511415612918576020600461014037341561283d57600080fd5b6000610140511161284d57600080fd5b6006543b61285a57600080fd5b60065430141561286957600080fd5b602061020060246370a0823161018052306101a05261019c6006545afa61288f57600080fd5b60005061020051610160526101405161016051610180516101a0516101c0516101e051610200516389f2a871610220526101405161024052303161026052610160516102805261028051610260516102405160065801610852565b6102e052610200526101e0526101c0526101a0526101805261016052610140526102e05160005260206000f3005b6359e948626000511415612a27576020600461014037341561293957600080fd5b6000610140511161294957600080fd5b6006543b61295657600080fd5b60065430141561296557600080fd5b602061020060246370a0823161018052306101a05261019c6006545afa61298b57600080fd5b60005061020051610160526101405161016051610180516101a0516101c0516101e051610200516102205163fd11c223610240526101405161026052303161028052610160516102a0526102a0516102805161026051600658016109ce565b6103005261022052610200526101e0526101c0526101a05261018052610160526101405261030051610220526001610220510260005260206000f3005b6395b68fe76000511415612b365760206004610140373415612a4857600080fd5b60006101405111612a5857600080fd5b6006543b612a6557600080fd5b600654301415612a7457600080fd5b602061020060246370a0823161018052306101a05261019c6006545afa612a9a57600080fd5b60005061020051610160526101405161016051610180516101a0516101c0516101e05161020051610220516389f2a871610240526101405161026052610160516102805230316102a0526102a051610280516102605160065801610852565b6103005261022052610200526101e0526101c0526101a05261018052610160526101405261030051610220526001610220510260005260206000f3005b632640f62c6000511415612c325760206004610140373415612b5757600080fd5b60006101405111612b6757600080fd5b6006543b612b7457600080fd5b600654301415612b8357600080fd5b602061020060246370a0823161018052306101a05261019c6006545afa612ba957600080fd5b60005061020051610160526101405161016051610180516101a0516101c0516101e0516102005163fd11c2236102205261014051610240526101605161026052303161028052610280516102605161024051600658016109ce565b6102e052610200526101e0526101c0526101a0526101805261016052610140526102e05160005260206000f3005b639d76ea586000511415612c58573415612c4b57600080fd5b60065460005260206000f3005b63966dae0e6000511415612c7e573415612c7157600080fd5b60075460005260206000f3005b6370a082316000511415612ccd5760206004610140373415612c9f57600080fd5b6004356020518110612cb057600080fd5b5060046101405160e05260c052604060c0205460005260206000f3005b63a9059cbb6000511415612d985760406004610140373415612cee57600080fd5b6004356020518110612cff57600080fd5b5060043360e05260c052604060c0206101605181541015612d1f57600080fd5b6101605181540381555060046101405160e05260c052604060c0208054610160518254011015612d4e57600080fd5b61016051815401815550610160516101805261014051337fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef6020610180a3600160005260206000f3005b6323b872dd6000511415612eb35760606004610140373415612db957600080fd5b6004356020518110612dca57600080fd5b506024356020518110612ddc57600080fd5b5060046101405160e05260c052604060c0206101805181541015612dff57600080fd5b6101805181540381555060046101605160e05260c052604060c0208054610180518254011015612e2e57600080fd5b6101805181540181555060056101405160e05260c052604060c0203360e05260c052604060c0206101805181541015612e6657600080fd5b61018051815403815550610180516101a05261016051610140517fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206101a0a3600160005260206000f3005b63095ea7b36000511415612f485760406004610140373415612ed457600080fd5b6004356020518110612ee557600080fd5b506101605160053360e05260c052604060c0206101405160e05260c052604060c02055610160516101805261014051337f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9256020610180a3600160005260206000f3005b63dd62ed3e6000511415612fb85760406004610140373415612f6957600080fd5b6004356020518110612f7a57600080fd5b506024356020518110612f8c57600080fd5b5060056101405160e05260c052604060c0206101605160e05260c052604060c0205460005260206000f3005b6306fdde036000511415612fde573415612fd157600080fd5b60005460005260206000f3005b6395d89b416000511415613004573415612ff757600080fd5b60015460005260206000f3005b63313ce567600051141561302a57341561301d57600080fd5b60025460005260206000f3005b6318160ddd600051141561305057341561304357600080fd5b60035460005260206000f3005b638c717a33610140523461016052600161018052426101a052336101c052336101e0526101e0516101c0516101a051610180516101605160065801610bfb565b610240526102405b61000461309c0361000460003961000461309c036000f3', 3 | factory: '0x6103f056600035601c52740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a05263538a3f0e60005114156100ed57602060046101403734156100b457600080fd5b60043560205181106100c557600080fd5b50600054156100d357600080fd5b60006101405114156100e457600080fd5b61014051600055005b631648f38e60005114156102bf576020600461014037341561010e57600080fd5b600435602051811061011f57600080fd5b50600061014051141561013157600080fd5b6000600054141561014157600080fd5b60026101405160e05260c052604060c020541561015d57600080fd5b7f602e600c600039602e6000f33660006000376110006000366000730000000000610180526c010000000000000000000000006000540261019b527f5af41558576110006000f30000000000000000000000000000000000000000006101af5260406101806000f0806101cf57600080fd5b61016052610160513b6101e157600080fd5b610160513014156101f157600080fd5b6000600060246366d3820361022052610140516102405261023c6000610160515af161021c57600080fd5b6101605160026101405160e05260c052604060c020556101405160036101605160e05260c052604060c02055600154600160015401101561025c57600080fd5b6001600154016102a0526102a0516001556101405160046102a05160e05260c052604060c0205561016051610140517f9d42cb017eb05bd8944ab536a8b35bc68085931dd5f4356489801453923953f960006000a36101605160005260206000f3005b6306f2bf62600051141561030e57602060046101403734156102e057600080fd5b60043560205181106102f157600080fd5b5060026101405160e05260c052604060c0205460005260206000f3005b6359770438600051141561035d576020600461014037341561032f57600080fd5b600435602051811061034057600080fd5b5060036101405160e05260c052604060c0205460005260206000f3005b63aa65a6c0600051141561039a576020600461014037341561037e57600080fd5b60046101405160e05260c052604060c0205460005260206000f3005b631c2bbd1860005114156103c05734156103b357600080fd5b60005460005260206000f3005b639f181b5e60005114156103e65734156103d957600080fd5b60015460005260206000f3005b60006000fd5b6100046103f0036100046000396100046103f0036000f3' 4 | } -------------------------------------------------------------------------------- /flat/ArbitrageRinkeby.sol: -------------------------------------------------------------------------------- 1 | 2 | // File: contracts/IUniswapExchange.sol 3 | 4 | pragma solidity ^0.5.0; 5 | 6 | // Solidity Interface 7 | 8 | contract IUniswapExchange { 9 | // Address of ERC20 token sold on this exchange 10 | function tokenAddress() external view returns (address token); 11 | // Address of Uniswap Factory 12 | function factoryAddress() external view returns (address factory); 13 | // Provide Liquidity 14 | function addLiquidity(uint256 min_liquidity, uint256 max_tokens, uint256 deadline) external payable returns (uint256); 15 | function removeLiquidity(uint256 amount, uint256 min_eth, uint256 min_tokens, uint256 deadline) external returns (uint256, uint256); 16 | // Get Prices 17 | function getEthToTokenInputPrice(uint256 eth_sold) external view returns (uint256 tokens_bought); 18 | function getEthToTokenOutputPrice(uint256 tokens_bought) external view returns (uint256 eth_sold); 19 | function getTokenToEthInputPrice(uint256 tokens_sold) external view returns (uint256 eth_bought); 20 | function getTokenToEthOutputPrice(uint256 eth_bought) external view returns (uint256 tokens_sold); 21 | // Trade ETH to ERC20 22 | function ethToTokenSwapInput(uint256 min_tokens, uint256 deadline) external payable returns (uint256 tokens_bought); 23 | function ethToTokenTransferInput(uint256 min_tokens, uint256 deadline, address recipient) external payable returns (uint256 tokens_bought); 24 | function ethToTokenSwapOutput(uint256 tokens_bought, uint256 deadline) external payable returns (uint256 eth_sold); 25 | function ethToTokenTransferOutput(uint256 tokens_bought, uint256 deadline, address recipient) external payable returns (uint256 eth_sold); 26 | // Trade ERC20 to ETH 27 | function tokenToEthSwapInput(uint256 tokens_sold, uint256 min_eth, uint256 deadline) external returns (uint256 eth_bought); 28 | function tokenToEthTransferInput(uint256 tokens_sold, uint256 min_tokens, uint256 deadline, address recipient) external returns (uint256 eth_bought); 29 | function tokenToEthSwapOutput(uint256 eth_bought, uint256 max_tokens, uint256 deadline) external returns (uint256 tokens_sold); 30 | function tokenToEthTransferOutput(uint256 eth_bought, uint256 max_tokens, uint256 deadline, address recipient) external returns (uint256 tokens_sold); 31 | // Trade ERC20 to ERC20 32 | function tokenToTokenSwapInput(uint256 tokens_sold, uint256 min_tokens_bought, uint256 min_eth_bought, uint256 deadline, address token_addr) external returns (uint256 tokens_bought); 33 | 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); 34 | function tokenToTokenSwapOutput(uint256 tokens_bought, uint256 max_tokens_sold, uint256 max_eth_sold, uint256 deadline, address token_addr) external returns (uint256 tokens_sold); 35 | 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); 36 | // Trade ERC20 to Custom Pool 37 | function tokenToExchangeSwapInput(uint256 tokens_sold, uint256 min_tokens_bought, uint256 min_eth_bought, uint256 deadline, address exchange_addr) external returns (uint256 tokens_bought); 38 | 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); 39 | function tokenToExchangeSwapOutput(uint256 tokens_bought, uint256 max_tokens_sold, uint256 max_eth_sold, uint256 deadline, address exchange_addr) external returns (uint256 tokens_sold); 40 | 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); 41 | // ERC20 comaptibility for liquidity tokens 42 | bytes32 public name; 43 | bytes32 public symbol; 44 | uint256 public decimals; 45 | function transfer(address _to, uint256 _value) external returns (bool); 46 | function transferFrom(address _from, address _to, uint256 value) external returns (bool); 47 | function approve(address _spender, uint256 _value) external returns (bool); 48 | function allowance(address _owner, address _spender) external view returns (uint256); 49 | function balanceOf(address _owner) external view returns (uint256); 50 | function totalSupply() public view returns (uint256); 51 | // Never use 52 | function setup(address token_addr) external; 53 | } 54 | 55 | // File: contracts/IUniswapFactory.sol 56 | 57 | pragma solidity ^0.5.0; 58 | 59 | // Solidity Interface 60 | 61 | contract IUniswapFactory { 62 | // Public Variables 63 | address public exchangeTemplate; 64 | uint256 public tokenCount; 65 | // Create Exchange 66 | function createExchange(address token) external returns (address exchange); 67 | // Get Exchange and Token Info 68 | function getExchange(address token) external view returns (address exchange); 69 | function getToken(address exchange) external view returns (address token); 70 | function getTokenWithId(uint256 tokenId) external view returns (address token); 71 | // Never use 72 | function initializeFactory(address template) external; 73 | } 74 | 75 | // File: contracts/IDutchExchange.sol 76 | 77 | pragma solidity ^0.5.0; 78 | 79 | contract IDutchExchange { 80 | 81 | 82 | mapping(address => mapping(address => uint)) public balances; 83 | 84 | // Token => Token => auctionIndex => amount 85 | mapping(address => mapping(address => mapping(uint => uint))) public extraTokens; 86 | 87 | // Token => Token => auctionIndex => user => amount 88 | mapping(address => mapping(address => mapping(uint => mapping(address => uint)))) public sellerBalances; 89 | mapping(address => mapping(address => mapping(uint => mapping(address => uint)))) public buyerBalances; 90 | mapping(address => mapping(address => mapping(uint => mapping(address => uint)))) public claimedAmounts; 91 | 92 | 93 | function ethToken() public view returns(address); 94 | function claimBuyerFunds(address, address, address, uint) public returns(uint, uint); 95 | function deposit(address tokenAddress, uint amount) public returns (uint); 96 | function withdraw(address tokenAddress, uint amount) public returns (uint); 97 | function getAuctionIndex(address token1, address token2) public returns(uint256); 98 | function postBuyOrder(address token1, address token2, uint256 auctionIndex, uint256 amount) public returns(uint256); 99 | function postSellOrder(address token1, address token2, uint256 auctionIndex, uint256 tokensBought) public returns(uint256, uint256); 100 | function getCurrentAuctionPrice(address token1, address token2, uint256 auctionIndex) public view returns(uint256, uint256); 101 | function claimAndWithdrawTokensFromSeveralAuctionsAsBuyer(address[] calldata, address[] calldata, uint[] calldata) external view returns(uint[] memory, uint); 102 | } 103 | 104 | // File: contracts/ITokenMinimal.sol 105 | 106 | pragma solidity ^0.5.0; 107 | 108 | contract ITokenMinimal { 109 | function allowance(address tokenOwner, address spender) public view returns (uint remaining); 110 | function balanceOf(address tokenOwner) public view returns (uint balance); 111 | function deposit() public payable; 112 | function withdraw(uint value) public; 113 | } 114 | 115 | // File: openzeppelin-solidity/contracts/utils/Address.sol 116 | 117 | pragma solidity ^0.5.0; 118 | 119 | /** 120 | * Utility library of inline functions on addresses 121 | */ 122 | library Address { 123 | /** 124 | * Returns whether the target address is a contract 125 | * @dev This function will return false if invoked during the constructor of a contract, 126 | * as the code is not actually created until after the constructor finishes. 127 | * @param account address of the account to check 128 | * @return whether the target address is a contract 129 | */ 130 | function isContract(address account) internal view returns (bool) { 131 | uint256 size; 132 | // XXX Currently there is no better way to check if there is a contract in an address 133 | // than to check the size of the code at that address. 134 | // See https://ethereum.stackexchange.com/a/14016/36603 135 | // for more details about how this works. 136 | // TODO Check this again before the Serenity release, because all addresses will be 137 | // contracts then. 138 | // solhint-disable-next-line no-inline-assembly 139 | assembly { size := extcodesize(account) } 140 | return size > 0; 141 | } 142 | } 143 | 144 | // File: openzeppelin-solidity/contracts/token/ERC20/IERC20.sol 145 | 146 | pragma solidity ^0.5.0; 147 | 148 | /** 149 | * @title ERC20 interface 150 | * @dev see https://github.com/ethereum/EIPs/issues/20 151 | */ 152 | interface IERC20 { 153 | function transfer(address to, uint256 value) external returns (bool); 154 | 155 | function approve(address spender, uint256 value) external returns (bool); 156 | 157 | function transferFrom(address from, address to, uint256 value) external returns (bool); 158 | 159 | function totalSupply() external view returns (uint256); 160 | 161 | function balanceOf(address who) external view returns (uint256); 162 | 163 | function allowance(address owner, address spender) external view returns (uint256); 164 | 165 | event Transfer(address indexed from, address indexed to, uint256 value); 166 | 167 | event Approval(address indexed owner, address indexed spender, uint256 value); 168 | } 169 | 170 | // File: contracts/SafeERC20.sol 171 | 172 | /* 173 | 174 | SafeERC20 by daostack. 175 | The code is based on a fix by SECBIT Team. 176 | 177 | USE WITH CAUTION & NO WARRANTY 178 | 179 | REFERENCE & RELATED READING 180 | - https://github.com/ethereum/solidity/issues/4116 181 | - https://medium.com/@chris_77367/explaining-unexpected-reverts-starting-with-solidity-0-4-22-3ada6e82308c 182 | - https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca 183 | - https://gist.github.com/BrendanChou/88a2eeb80947ff00bcf58ffdafeaeb61 184 | 185 | */ 186 | pragma solidity ^0.5.0; 187 | 188 | 189 | 190 | library SafeERC20 { 191 | using Address for address; 192 | 193 | bytes4 constant private TRANSFER_SELECTOR = bytes4(keccak256(bytes("transfer(address,uint256)"))); 194 | bytes4 constant private TRANSFERFROM_SELECTOR = bytes4(keccak256(bytes("transferFrom(address,address,uint256)"))); 195 | bytes4 constant private APPROVE_SELECTOR = bytes4(keccak256(bytes("approve(address,uint256)"))); 196 | 197 | function safeTransfer(address _erc20Addr, address _to, uint256 _value) internal { 198 | 199 | // Must be a contract addr first! 200 | require(_erc20Addr.isContract(), "ERC20 is not a contract"); 201 | 202 | (bool success, bytes memory returnValue) = 203 | // solhint-disable-next-line avoid-low-level-calls 204 | _erc20Addr.call(abi.encodeWithSelector(TRANSFER_SELECTOR, _to, _value)); 205 | // call return false when something wrong 206 | require(success, "safeTransfer must succeed"); 207 | //check return value 208 | require(returnValue.length == 0 || (returnValue.length == 32 && (returnValue[31] != 0)), "safeTransfer must return nothing or true"); 209 | } 210 | 211 | function safeTransferFrom(address _erc20Addr, address _from, address _to, uint256 _value) internal { 212 | 213 | // Must be a contract addr first! 214 | require(_erc20Addr.isContract(), "ERC20 is not a contract"); 215 | 216 | (bool success, bytes memory returnValue) = 217 | // solhint-disable-next-line avoid-low-level-calls 218 | _erc20Addr.call(abi.encodeWithSelector(TRANSFERFROM_SELECTOR, _from, _to, _value)); 219 | // call return false when something wrong 220 | require(success, "safeTransferFrom must succeed"); 221 | //check return value 222 | require(returnValue.length == 0 || (returnValue.length == 32 && (returnValue[31] != 0)), "safeTransferFrom must return nothing or true"); 223 | } 224 | 225 | function safeApprove(address _erc20Addr, address _spender, uint256 _value) internal { 226 | 227 | // Must be a contract addr first! 228 | require(_erc20Addr.isContract(), "ERC20 is not a contract"); 229 | 230 | // vvv 231 | // This section has been commented out because it is not a necesarry safeguard 232 | // vvv 233 | /* 234 | // safeApprove should only be called when setting an initial allowance, 235 | // or when resetting it to zero. 236 | 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."); 237 | */ 238 | 239 | (bool success, bytes memory returnValue) = 240 | // solhint-disable-next-line avoid-low-level-calls 241 | _erc20Addr.call(abi.encodeWithSelector(APPROVE_SELECTOR, _spender, _value)); 242 | // call return false when something wrong 243 | require(success, "safeApprove must succeed"); 244 | //check return value 245 | require(returnValue.length == 0 || (returnValue.length == 32 && (returnValue[31] != 0)), "safeApprove must return nothing or true"); 246 | } 247 | } 248 | 249 | // File: openzeppelin-solidity/contracts/ownership/Ownable.sol 250 | 251 | pragma solidity ^0.5.0; 252 | 253 | /** 254 | * @title Ownable 255 | * @dev The Ownable contract has an owner address, and provides basic authorization control 256 | * functions, this simplifies the implementation of "user permissions". 257 | */ 258 | contract Ownable { 259 | address private _owner; 260 | 261 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 262 | 263 | /** 264 | * @dev The Ownable constructor sets the original `owner` of the contract to the sender 265 | * account. 266 | */ 267 | constructor () internal { 268 | _owner = msg.sender; 269 | emit OwnershipTransferred(address(0), _owner); 270 | } 271 | 272 | /** 273 | * @return the address of the owner. 274 | */ 275 | function owner() public view returns (address) { 276 | return _owner; 277 | } 278 | 279 | /** 280 | * @dev Throws if called by any account other than the owner. 281 | */ 282 | modifier onlyOwner() { 283 | require(isOwner()); 284 | _; 285 | } 286 | 287 | /** 288 | * @return true if `msg.sender` is the owner of the contract. 289 | */ 290 | function isOwner() public view returns (bool) { 291 | return msg.sender == _owner; 292 | } 293 | 294 | /** 295 | * @dev Allows the current owner to relinquish control of the contract. 296 | * @notice Renouncing to ownership will leave the contract without an owner. 297 | * It will not be possible to call the functions with the `onlyOwner` 298 | * modifier anymore. 299 | */ 300 | function renounceOwnership() public onlyOwner { 301 | emit OwnershipTransferred(_owner, address(0)); 302 | _owner = address(0); 303 | } 304 | 305 | /** 306 | * @dev Allows the current owner to transfer control of the contract to a newOwner. 307 | * @param newOwner The address to transfer ownership to. 308 | */ 309 | function transferOwnership(address newOwner) public onlyOwner { 310 | _transferOwnership(newOwner); 311 | } 312 | 313 | /** 314 | * @dev Transfers control of the contract to a newOwner. 315 | * @param newOwner The address to transfer ownership to. 316 | */ 317 | function _transferOwnership(address newOwner) internal { 318 | require(newOwner != address(0)); 319 | emit OwnershipTransferred(_owner, newOwner); 320 | _owner = newOwner; 321 | } 322 | } 323 | 324 | // File: contracts/Arbitrage.sol 325 | 326 | pragma solidity ^0.5.0; 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | /// @title Uniswap Arbitrage - Executes arbitrage transactions between Uniswap and DutchX. 335 | /// @author Billy Rennekamp - 336 | contract Arbitrage is Ownable { 337 | 338 | uint constant max = uint(-1); 339 | 340 | IUniswapFactory public uniFactory; 341 | IDutchExchange public dutchXProxy; 342 | 343 | event Profit(uint profit, bool wasDutchOpportunity); 344 | 345 | /// @dev Payable fallback function has nothing inside so it won't run out of gas with gas limited transfers 346 | function() external payable {} 347 | 348 | /// @dev Only owner can deposit contract Ether into the DutchX as WETH 349 | function depositEther() public payable onlyOwner { 350 | 351 | require(address(this).balance > 0, "Balance must be greater than 0 to deposit"); 352 | uint balance = address(this).balance; 353 | 354 | // // Deposit balance to WETH 355 | address weth = dutchXProxy.ethToken(); 356 | ITokenMinimal(weth).deposit.value(balance)(); 357 | 358 | uint wethBalance = ITokenMinimal(weth).balanceOf(address(this)); 359 | uint allowance = ITokenMinimal(weth).allowance(address(this), address(dutchXProxy)); 360 | 361 | if (allowance < wethBalance) { 362 | // Approve max amount of WETH to be transferred by dutchX 363 | // Keeping it max will have same or similar costs to making it exact over and over again 364 | SafeERC20.safeApprove(weth, address(dutchXProxy), max); 365 | } 366 | 367 | // Deposit new amount on dutchX, confirm there's at least the amount we just deposited 368 | uint newBalance = dutchXProxy.deposit(weth, balance); 369 | require(newBalance >= balance, "Deposit WETH to DutchX didn't work."); 370 | } 371 | 372 | /// @dev Only owner can withdraw WETH from DutchX, convert to Ether and transfer to owner 373 | /// @param amount The amount of Ether to withdraw 374 | function withdrawEtherThenTransfer(uint amount) external onlyOwner { 375 | _withdrawEther(amount); 376 | address(uint160(owner())).transfer(amount); 377 | } 378 | 379 | /// @dev Only owner can transfer any Ether currently in the contract to the owner address. 380 | /// @param amount The amount of Ether to withdraw 381 | function transferEther(uint amount) external onlyOwner { 382 | // If amount is zero, deposit the entire contract balance. 383 | address(uint160(owner())).transfer(amount == 0 ? address(this).balance : amount); 384 | } 385 | 386 | /// @dev Only owner function to withdraw WETH from the DutchX, convert it to Ether and keep it in contract 387 | /// @param amount The amount of WETH to withdraw and convert. 388 | function withdrawEther(uint amount) external onlyOwner { 389 | _withdrawEther(amount); 390 | } 391 | 392 | /// @dev Internal function to withdraw WETH from the DutchX, convert it to Ether and keep it in contract 393 | /// @param amount The amount of WETH to withdraw and convert. 394 | function _withdrawEther(uint amount) internal { 395 | address weth = dutchXProxy.ethToken(); 396 | dutchXProxy.withdraw(weth, amount); 397 | ITokenMinimal(weth).withdraw(amount); 398 | } 399 | 400 | /// @dev Only owner can withdraw a token from the DutchX 401 | /// @param token The token address that is being withdrawn. 402 | /// @param amount The amount of token to withdraw. Can be larger than available balance and maximum will be withdrawn. 403 | /// @return Returns the amount actually withdrawn from the DutchX 404 | function withdrawToken(address token, uint amount) external onlyOwner returns (uint) { 405 | return dutchXProxy.withdraw(token, amount); 406 | } 407 | 408 | /// @dev Only owner can claim a token from an auction on the DutchX 409 | /// @param token The token address that is being claimed. 410 | /// @param dutchAuctionIndex The auction index of the token to be claimed. 411 | /// @return Returns the amount actually claimed from the DutchX 412 | function claimBuyerFunds(address token, uint dutchAuctionIndex) external onlyOwner returns (uint) { 413 | address etherToken = dutchXProxy.ethToken(); 414 | (uint tokensClaimed, ) = dutchXProxy.claimBuyerFunds(token, etherToken, address(this), dutchAuctionIndex); 415 | return tokensClaimed; 416 | } 417 | 418 | /// @dev Only owner can transfer tokens to the owner that belong to this contract 419 | /// @param token The token address that is being transferred. 420 | /// @param amount The amount of token to transfer. 421 | function transferToken(address token, uint amount) external onlyOwner { 422 | SafeERC20.safeTransfer(token, owner(), amount); 423 | } 424 | 425 | /// @dev Only owner can deposit token to the DutchX 426 | /// @param token The token address that is being deposited. 427 | /// @param amount The amount of token to deposit. 428 | function depositToken(address token, uint amount) external onlyOwner { 429 | _depositToken(token, amount); 430 | } 431 | 432 | /// @dev Internal function to deposit token to the DutchX 433 | /// @param token The token address that is being deposited. 434 | /// @param amount The amount of token to deposit. 435 | function _depositToken(address token, uint amount) internal { 436 | 437 | uint allowance = ITokenMinimal(token).allowance(address(this), address(dutchXProxy)); 438 | if (allowance < amount) { 439 | SafeERC20.safeApprove(token, address(dutchXProxy), max); 440 | } 441 | 442 | // Confirm that the balance of the token on the DutchX is at least how much was deposited 443 | uint newBalance = dutchXProxy.deposit(token, amount); 444 | require(newBalance >= amount, "deposit didn't work"); 445 | } 446 | 447 | /// @dev Executes a trade opportunity on dutchX. Assumes that there is a balance of WETH already on the dutchX 448 | /// @param arbToken Address of the token that should be arbitraged. 449 | /// @param amount Amount of Ether to use in arbitrage. 450 | /// @return Returns if transaction can be executed. 451 | function dutchOpportunity(address arbToken, uint256 amount) external onlyOwner { 452 | 453 | address etherToken = dutchXProxy.ethToken(); 454 | 455 | // The order of parameters for getAuctionIndex don't matter 456 | uint256 dutchAuctionIndex = dutchXProxy.getAuctionIndex(arbToken, etherToken); 457 | 458 | // postBuyOrder(sellToken, buyToken, amount) 459 | // results in a decrease of the amount the user owns of the second token 460 | // which means the buyToken is what the buyer wants to get rid of. 461 | // "The buy token is what the buyer provides, the seller token is what the seller provides." 462 | dutchXProxy.postBuyOrder(arbToken, etherToken, dutchAuctionIndex, amount); 463 | 464 | (uint tokensBought, ) = dutchXProxy.claimBuyerFunds(arbToken, etherToken, address(this), dutchAuctionIndex); 465 | dutchXProxy.withdraw(arbToken, tokensBought); 466 | 467 | address uniswapExchange = uniFactory.getExchange(arbToken); 468 | 469 | uint allowance = ITokenMinimal(arbToken).allowance(address(this), address(uniswapExchange)); 470 | if (allowance < tokensBought) { 471 | // Approve Uniswap to transfer arbToken on contract's behalf 472 | // Keeping it max will have same or similar costs to making it exact over and over again 473 | SafeERC20.safeApprove(arbToken, address(uniswapExchange), max); 474 | } 475 | 476 | // tokenToEthSwapInput(inputToken, minimumReturn, timeToLive) 477 | // minimumReturn is enough to make a profit (excluding gas) 478 | // timeToLive is now because transaction is atomic 479 | uint256 etherReturned = IUniswapExchange(uniswapExchange).tokenToEthSwapInput(tokensBought, 1, block.timestamp); 480 | 481 | // gas costs were excluded because worse case scenario the tx fails and gas costs were spent up to here anyway 482 | // best worst case scenario the profit from the trade alleviates part of the gas costs even if still no total profit 483 | require(etherReturned >= amount, "no profit"); 484 | emit Profit(etherReturned, true); 485 | 486 | // Ether is deposited as WETH 487 | depositEther(); 488 | } 489 | 490 | /// @dev Executes a trade opportunity on uniswap. 491 | /// @param arbToken Address of the token that should be arbitraged. 492 | /// @param amount Amount of Ether to use in arbitrage. 493 | /// @return Returns if transaction can be executed. 494 | function uniswapOpportunity(address arbToken, uint256 amount) external onlyOwner { 495 | 496 | // WETH must be converted to Eth for Uniswap trade 497 | // (Uniswap allows ERC20:ERC20 but most liquidity is on ETH:ERC20 markets) 498 | _withdrawEther(amount); 499 | require(address(this).balance >= amount, "buying from uniswap takes real Ether"); 500 | 501 | // ethToTokenSwapInput(minTokens, deadline) 502 | // minTokens is 1 because it will revert without a profit regardless 503 | // deadline is now since trade is atomic 504 | // solium-disable-next-line security/no-block-members 505 | uint256 tokensBought = IUniswapExchange(uniFactory.getExchange(arbToken)).ethToTokenSwapInput.value(amount)(1, block.timestamp); 506 | 507 | // tokens need to be approved for the dutchX before they are deposited 508 | _depositToken(arbToken, tokensBought); 509 | 510 | address etherToken = dutchXProxy.ethToken(); 511 | 512 | // The order of parameters for getAuctionIndex don't matter 513 | uint256 dutchAuctionIndex = dutchXProxy.getAuctionIndex(arbToken, etherToken); 514 | 515 | // spend max amount of tokens currently on the dutch x (might be combined from previous remainders) 516 | // max is automatically reduced to maximum available tokens because there may be 517 | // token remainders from previous auctions which closed after previous arbitrage opportunities 518 | dutchXProxy.postBuyOrder(etherToken, arbToken, dutchAuctionIndex, max); 519 | // solium-disable-next-line no-unused-vars 520 | (uint etherReturned, ) = dutchXProxy.claimBuyerFunds(etherToken, arbToken, address(this), dutchAuctionIndex); 521 | 522 | // gas costs were excluded because worse case scenario the tx fails and gas costs were spent up to here anyway 523 | // best worst case scenario the profit from the trade alleviates part of the gas costs even if still no total profit 524 | require(etherReturned >= amount, "no profit"); 525 | emit Profit(etherReturned, false); 526 | // Ether returned is already in dutchX balance where Ether is assumed to be stored when not being used. 527 | } 528 | 529 | } 530 | 531 | // File: contracts/ArbitrageRinkeby.sol 532 | 533 | pragma solidity ^0.5.0; 534 | 535 | /// @title Uniswap Arbitrage Module - Executes arbitrage transactions between Uniswap and DutchX. 536 | /// @author Billy Rennekamp - 537 | contract ArbitrageRinkeby is Arbitrage { 538 | constructor() public { 539 | uniFactory = IUniswapFactory(0xf5D915570BC477f9B8D6C0E980aA81757A3AaC36); 540 | dutchXProxy = IDutchExchange(0xaAEb2035FF394fdB2C879190f95e7676f1A9444B); 541 | } 542 | } 543 | -------------------------------------------------------------------------------- /abi/DutchExchange.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [ 5 | { 6 | "name": "token1", 7 | "type": "address" 8 | }, 9 | { 10 | "name": "token2", 11 | "type": "address" 12 | } 13 | ], 14 | "name": "getTokenOrder", 15 | "outputs": [ 16 | { 17 | "name": "", 18 | "type": "address" 19 | }, 20 | { 21 | "name": "", 22 | "type": "address" 23 | } 24 | ], 25 | "payable": false, 26 | "stateMutability": "pure", 27 | "type": "function" 28 | }, 29 | { 30 | "constant": true, 31 | "inputs": [ 32 | { 33 | "name": "auctionSellTokens", 34 | "type": "address[]" 35 | }, 36 | { 37 | "name": "auctionBuyTokens", 38 | "type": "address[]" 39 | }, 40 | { 41 | "name": "user", 42 | "type": "address" 43 | } 44 | ], 45 | "name": "getSellerBalancesOfCurrentAuctions", 46 | "outputs": [ 47 | { 48 | "name": "", 49 | "type": "uint256[]" 50 | } 51 | ], 52 | "payable": false, 53 | "stateMutability": "view", 54 | "type": "function" 55 | }, 56 | { 57 | "constant": true, 58 | "inputs": [ 59 | { 60 | "name": "addressToCheck", 61 | "type": "address[]" 62 | } 63 | ], 64 | "name": "getApprovedAddressesOfList", 65 | "outputs": [ 66 | { 67 | "name": "", 68 | "type": "bool[]" 69 | } 70 | ], 71 | "payable": false, 72 | "stateMutability": "view", 73 | "type": "function" 74 | }, 75 | { 76 | "constant": true, 77 | "inputs": [], 78 | "name": "getMasterCopy", 79 | "outputs": [ 80 | { 81 | "name": "", 82 | "type": "address" 83 | } 84 | ], 85 | "payable": false, 86 | "stateMutability": "view", 87 | "type": "function" 88 | }, 89 | { 90 | "constant": false, 91 | "inputs": [ 92 | { 93 | "name": "sellToken", 94 | "type": "address" 95 | }, 96 | { 97 | "name": "buyToken", 98 | "type": "address" 99 | }, 100 | { 101 | "name": "user", 102 | "type": "address" 103 | }, 104 | { 105 | "name": "auctionIndex", 106 | "type": "uint256" 107 | }, 108 | { 109 | "name": "amount", 110 | "type": "uint256" 111 | } 112 | ], 113 | "name": "claimAndWithdraw", 114 | "outputs": [ 115 | { 116 | "name": "returned", 117 | "type": "uint256" 118 | }, 119 | { 120 | "name": "frtsIssued", 121 | "type": "uint256" 122 | }, 123 | { 124 | "name": "newBal", 125 | "type": "uint256" 126 | } 127 | ], 128 | "payable": false, 129 | "stateMutability": "nonpayable", 130 | "type": "function" 131 | }, 132 | { 133 | "constant": true, 134 | "inputs": [], 135 | "name": "masterCopyCountdown", 136 | "outputs": [ 137 | { 138 | "name": "", 139 | "type": "uint256" 140 | } 141 | ], 142 | "payable": false, 143 | "stateMutability": "view", 144 | "type": "function" 145 | }, 146 | { 147 | "constant": true, 148 | "inputs": [ 149 | { 150 | "name": "", 151 | "type": "address" 152 | }, 153 | { 154 | "name": "", 155 | "type": "address" 156 | } 157 | ], 158 | "name": "auctionStarts", 159 | "outputs": [ 160 | { 161 | "name": "", 162 | "type": "uint256" 163 | } 164 | ], 165 | "payable": false, 166 | "stateMutability": "view", 167 | "type": "function" 168 | }, 169 | { 170 | "constant": true, 171 | "inputs": [ 172 | { 173 | "name": "token1", 174 | "type": "address" 175 | }, 176 | { 177 | "name": "token2", 178 | "type": "address" 179 | } 180 | ], 181 | "name": "getAuctionIndex", 182 | "outputs": [ 183 | { 184 | "name": "auctionIndex", 185 | "type": "uint256" 186 | } 187 | ], 188 | "payable": false, 189 | "stateMutability": "view", 190 | "type": "function" 191 | }, 192 | { 193 | "constant": false, 194 | "inputs": [], 195 | "name": "updateMasterCopy", 196 | "outputs": [], 197 | "payable": false, 198 | "stateMutability": "nonpayable", 199 | "type": "function" 200 | }, 201 | { 202 | "constant": true, 203 | "inputs": [ 204 | { 205 | "name": "a", 206 | "type": "int256" 207 | } 208 | ], 209 | "name": "atleastZero", 210 | "outputs": [ 211 | { 212 | "name": "", 213 | "type": "uint256" 214 | } 215 | ], 216 | "payable": false, 217 | "stateMutability": "pure", 218 | "type": "function" 219 | }, 220 | { 221 | "constant": true, 222 | "inputs": [ 223 | { 224 | "name": "", 225 | "type": "address" 226 | }, 227 | { 228 | "name": "", 229 | "type": "address" 230 | }, 231 | { 232 | "name": "", 233 | "type": "uint256" 234 | }, 235 | { 236 | "name": "", 237 | "type": "address" 238 | } 239 | ], 240 | "name": "buyerBalances", 241 | "outputs": [ 242 | { 243 | "name": "", 244 | "type": "uint256" 245 | } 246 | ], 247 | "payable": false, 248 | "stateMutability": "view", 249 | "type": "function" 250 | }, 251 | { 252 | "constant": false, 253 | "inputs": [ 254 | { 255 | "name": "_ethUSDOracle", 256 | "type": "address" 257 | } 258 | ], 259 | "name": "initiateEthUsdOracleUpdate", 260 | "outputs": [], 261 | "payable": false, 262 | "stateMutability": "nonpayable", 263 | "type": "function" 264 | }, 265 | { 266 | "constant": false, 267 | "inputs": [ 268 | { 269 | "name": "tokenAddress", 270 | "type": "address" 271 | }, 272 | { 273 | "name": "amount", 274 | "type": "uint256" 275 | } 276 | ], 277 | "name": "deposit", 278 | "outputs": [ 279 | { 280 | "name": "", 281 | "type": "uint256" 282 | } 283 | ], 284 | "payable": false, 285 | "stateMutability": "nonpayable", 286 | "type": "function" 287 | }, 288 | { 289 | "constant": true, 290 | "inputs": [ 291 | { 292 | "name": "token1", 293 | "type": "address" 294 | }, 295 | { 296 | "name": "token2", 297 | "type": "address" 298 | }, 299 | { 300 | "name": "auctionIndex", 301 | "type": "uint256" 302 | } 303 | ], 304 | "name": "getPriceInPastAuction", 305 | "outputs": [ 306 | { 307 | "name": "num", 308 | "type": "uint256" 309 | }, 310 | { 311 | "name": "den", 312 | "type": "uint256" 313 | } 314 | ], 315 | "payable": false, 316 | "stateMutability": "view", 317 | "type": "function" 318 | }, 319 | { 320 | "constant": true, 321 | "inputs": [ 322 | { 323 | "name": "a", 324 | "type": "uint256" 325 | }, 326 | { 327 | "name": "b", 328 | "type": "uint256" 329 | } 330 | ], 331 | "name": "safeToAdd", 332 | "outputs": [ 333 | { 334 | "name": "", 335 | "type": "bool" 336 | } 337 | ], 338 | "payable": false, 339 | "stateMutability": "pure", 340 | "type": "function" 341 | }, 342 | { 343 | "constant": false, 344 | "inputs": [ 345 | { 346 | "name": "sellToken", 347 | "type": "address" 348 | }, 349 | { 350 | "name": "buyToken", 351 | "type": "address" 352 | }, 353 | { 354 | "name": "auctionIndex", 355 | "type": "uint256" 356 | }, 357 | { 358 | "name": "amount", 359 | "type": "uint256" 360 | } 361 | ], 362 | "name": "postSellOrder", 363 | "outputs": [ 364 | { 365 | "name": "", 366 | "type": "uint256" 367 | }, 368 | { 369 | "name": "", 370 | "type": "uint256" 371 | } 372 | ], 373 | "payable": false, 374 | "stateMutability": "nonpayable", 375 | "type": "function" 376 | }, 377 | { 378 | "constant": false, 379 | "inputs": [ 380 | { 381 | "name": "sellToken", 382 | "type": "address" 383 | }, 384 | { 385 | "name": "buyToken", 386 | "type": "address" 387 | }, 388 | { 389 | "name": "auctionIndex", 390 | "type": "uint256" 391 | }, 392 | { 393 | "name": "amount", 394 | "type": "uint256" 395 | } 396 | ], 397 | "name": "postBuyOrder", 398 | "outputs": [ 399 | { 400 | "name": "", 401 | "type": "uint256" 402 | } 403 | ], 404 | "payable": false, 405 | "stateMutability": "nonpayable", 406 | "type": "function" 407 | }, 408 | { 409 | "constant": true, 410 | "inputs": [], 411 | "name": "auctioneer", 412 | "outputs": [ 413 | { 414 | "name": "", 415 | "type": "address" 416 | } 417 | ], 418 | "payable": false, 419 | "stateMutability": "view", 420 | "type": "function" 421 | }, 422 | { 423 | "constant": false, 424 | "inputs": [ 425 | { 426 | "name": "sellToken", 427 | "type": "address" 428 | }, 429 | { 430 | "name": "buyToken", 431 | "type": "address" 432 | }, 433 | { 434 | "name": "user", 435 | "type": "address" 436 | }, 437 | { 438 | "name": "auctionIndex", 439 | "type": "uint256" 440 | } 441 | ], 442 | "name": "claimSellerFunds", 443 | "outputs": [ 444 | { 445 | "name": "returned", 446 | "type": "uint256" 447 | }, 448 | { 449 | "name": "frtsIssued", 450 | "type": "uint256" 451 | } 452 | ], 453 | "payable": false, 454 | "stateMutability": "nonpayable", 455 | "type": "function" 456 | }, 457 | { 458 | "constant": false, 459 | "inputs": [ 460 | { 461 | "name": "sellToken", 462 | "type": "address" 463 | }, 464 | { 465 | "name": "buyToken", 466 | "type": "address" 467 | }, 468 | { 469 | "name": "amount", 470 | "type": "uint256" 471 | } 472 | ], 473 | "name": "depositAndSell", 474 | "outputs": [ 475 | { 476 | "name": "newBal", 477 | "type": "uint256" 478 | }, 479 | { 480 | "name": "auctionIndex", 481 | "type": "uint256" 482 | }, 483 | { 484 | "name": "newSellerBal", 485 | "type": "uint256" 486 | } 487 | ], 488 | "payable": false, 489 | "stateMutability": "nonpayable", 490 | "type": "function" 491 | }, 492 | { 493 | "constant": false, 494 | "inputs": [ 495 | { 496 | "name": "token", 497 | "type": "address[]" 498 | }, 499 | { 500 | "name": "approved", 501 | "type": "bool" 502 | } 503 | ], 504 | "name": "updateApprovalOfToken", 505 | "outputs": [], 506 | "payable": false, 507 | "stateMutability": "nonpayable", 508 | "type": "function" 509 | }, 510 | { 511 | "constant": true, 512 | "inputs": [ 513 | { 514 | "name": "", 515 | "type": "address" 516 | } 517 | ], 518 | "name": "approvedTokens", 519 | "outputs": [ 520 | { 521 | "name": "", 522 | "type": "bool" 523 | } 524 | ], 525 | "payable": false, 526 | "stateMutability": "view", 527 | "type": "function" 528 | }, 529 | { 530 | "constant": true, 531 | "inputs": [], 532 | "name": "thresholdNewTokenPair", 533 | "outputs": [ 534 | { 535 | "name": "", 536 | "type": "uint256" 537 | } 538 | ], 539 | "payable": false, 540 | "stateMutability": "view", 541 | "type": "function" 542 | }, 543 | { 544 | "constant": true, 545 | "inputs": [], 546 | "name": "newMasterCopy", 547 | "outputs": [ 548 | { 549 | "name": "", 550 | "type": "address" 551 | } 552 | ], 553 | "payable": false, 554 | "stateMutability": "view", 555 | "type": "function" 556 | }, 557 | { 558 | "constant": true, 559 | "inputs": [], 560 | "name": "ethUSDOracle", 561 | "outputs": [ 562 | { 563 | "name": "", 564 | "type": "address" 565 | } 566 | ], 567 | "payable": false, 568 | "stateMutability": "view", 569 | "type": "function" 570 | }, 571 | { 572 | "constant": true, 573 | "inputs": [ 574 | { 575 | "name": "a", 576 | "type": "uint256" 577 | }, 578 | { 579 | "name": "b", 580 | "type": "uint256" 581 | } 582 | ], 583 | "name": "add", 584 | "outputs": [ 585 | { 586 | "name": "", 587 | "type": "uint256" 588 | } 589 | ], 590 | "payable": false, 591 | "stateMutability": "pure", 592 | "type": "function" 593 | }, 594 | { 595 | "constant": false, 596 | "inputs": [ 597 | { 598 | "name": "auctionSellTokens", 599 | "type": "address[]" 600 | }, 601 | { 602 | "name": "auctionBuyTokens", 603 | "type": "address[]" 604 | }, 605 | { 606 | "name": "auctionIndices", 607 | "type": "uint256[]" 608 | }, 609 | { 610 | "name": "user", 611 | "type": "address" 612 | } 613 | ], 614 | "name": "claimTokensFromSeveralAuctionsAsSeller", 615 | "outputs": [], 616 | "payable": false, 617 | "stateMutability": "nonpayable", 618 | "type": "function" 619 | }, 620 | { 621 | "constant": false, 622 | "inputs": [ 623 | { 624 | "name": "_auctioneer", 625 | "type": "address" 626 | } 627 | ], 628 | "name": "updateAuctioneer", 629 | "outputs": [], 630 | "payable": false, 631 | "stateMutability": "nonpayable", 632 | "type": "function" 633 | }, 634 | { 635 | "constant": true, 636 | "inputs": [ 637 | { 638 | "name": "a", 639 | "type": "uint256" 640 | }, 641 | { 642 | "name": "b", 643 | "type": "uint256" 644 | } 645 | ], 646 | "name": "min", 647 | "outputs": [ 648 | { 649 | "name": "", 650 | "type": "uint256" 651 | } 652 | ], 653 | "payable": false, 654 | "stateMutability": "pure", 655 | "type": "function" 656 | }, 657 | { 658 | "constant": true, 659 | "inputs": [], 660 | "name": "ethToken", 661 | "outputs": [ 662 | { 663 | "name": "", 664 | "type": "address" 665 | } 666 | ], 667 | "payable": false, 668 | "stateMutability": "view", 669 | "type": "function" 670 | }, 671 | { 672 | "constant": false, 673 | "inputs": [ 674 | { 675 | "name": "sellToken", 676 | "type": "address" 677 | }, 678 | { 679 | "name": "buyToken", 680 | "type": "address" 681 | }, 682 | { 683 | "name": "auctionIndex", 684 | "type": "uint256" 685 | } 686 | ], 687 | "name": "closeTheoreticalClosedAuction", 688 | "outputs": [], 689 | "payable": false, 690 | "stateMutability": "nonpayable", 691 | "type": "function" 692 | }, 693 | { 694 | "constant": true, 695 | "inputs": [], 696 | "name": "frtToken", 697 | "outputs": [ 698 | { 699 | "name": "", 700 | "type": "address" 701 | } 702 | ], 703 | "payable": false, 704 | "stateMutability": "view", 705 | "type": "function" 706 | }, 707 | { 708 | "constant": true, 709 | "inputs": [ 710 | { 711 | "name": "tokens", 712 | "type": "address[]" 713 | } 714 | ], 715 | "name": "getRunningTokenPairs", 716 | "outputs": [ 717 | { 718 | "name": "tokens1", 719 | "type": "address[]" 720 | }, 721 | { 722 | "name": "tokens2", 723 | "type": "address[]" 724 | } 725 | ], 726 | "payable": false, 727 | "stateMutability": "view", 728 | "type": "function" 729 | }, 730 | { 731 | "constant": true, 732 | "inputs": [ 733 | { 734 | "name": "", 735 | "type": "address" 736 | }, 737 | { 738 | "name": "", 739 | "type": "address" 740 | }, 741 | { 742 | "name": "", 743 | "type": "uint256" 744 | }, 745 | { 746 | "name": "", 747 | "type": "address" 748 | } 749 | ], 750 | "name": "claimedAmounts", 751 | "outputs": [ 752 | { 753 | "name": "", 754 | "type": "uint256" 755 | } 756 | ], 757 | "payable": false, 758 | "stateMutability": "view", 759 | "type": "function" 760 | }, 761 | { 762 | "constant": true, 763 | "inputs": [], 764 | "name": "oracleInterfaceCountdown", 765 | "outputs": [ 766 | { 767 | "name": "", 768 | "type": "uint256" 769 | } 770 | ], 771 | "payable": false, 772 | "stateMutability": "view", 773 | "type": "function" 774 | }, 775 | { 776 | "constant": true, 777 | "inputs": [], 778 | "name": "masterCopy", 779 | "outputs": [ 780 | { 781 | "name": "", 782 | "type": "address" 783 | } 784 | ], 785 | "payable": false, 786 | "stateMutability": "view", 787 | "type": "function" 788 | }, 789 | { 790 | "constant": false, 791 | "inputs": [ 792 | { 793 | "name": "_frtToken", 794 | "type": "address" 795 | }, 796 | { 797 | "name": "_owlToken", 798 | "type": "address" 799 | }, 800 | { 801 | "name": "_auctioneer", 802 | "type": "address" 803 | }, 804 | { 805 | "name": "_ethToken", 806 | "type": "address" 807 | }, 808 | { 809 | "name": "_ethUSDOracle", 810 | "type": "address" 811 | }, 812 | { 813 | "name": "_thresholdNewTokenPair", 814 | "type": "uint256" 815 | }, 816 | { 817 | "name": "_thresholdNewAuction", 818 | "type": "uint256" 819 | } 820 | ], 821 | "name": "setupDutchExchange", 822 | "outputs": [], 823 | "payable": false, 824 | "stateMutability": "nonpayable", 825 | "type": "function" 826 | }, 827 | { 828 | "constant": false, 829 | "inputs": [ 830 | { 831 | "name": "sellToken", 832 | "type": "address" 833 | }, 834 | { 835 | "name": "buyToken", 836 | "type": "address" 837 | }, 838 | { 839 | "name": "user", 840 | "type": "address" 841 | }, 842 | { 843 | "name": "auctionIndex", 844 | "type": "uint256" 845 | } 846 | ], 847 | "name": "claimBuyerFunds", 848 | "outputs": [ 849 | { 850 | "name": "returned", 851 | "type": "uint256" 852 | }, 853 | { 854 | "name": "frtsIssued", 855 | "type": "uint256" 856 | } 857 | ], 858 | "payable": false, 859 | "stateMutability": "nonpayable", 860 | "type": "function" 861 | }, 862 | { 863 | "constant": true, 864 | "inputs": [ 865 | { 866 | "name": "", 867 | "type": "address" 868 | }, 869 | { 870 | "name": "", 871 | "type": "address" 872 | } 873 | ], 874 | "name": "sellVolumesNext", 875 | "outputs": [ 876 | { 877 | "name": "", 878 | "type": "uint256" 879 | } 880 | ], 881 | "payable": false, 882 | "stateMutability": "view", 883 | "type": "function" 884 | }, 885 | { 886 | "constant": true, 887 | "inputs": [ 888 | { 889 | "name": "", 890 | "type": "address" 891 | }, 892 | { 893 | "name": "", 894 | "type": "address" 895 | } 896 | ], 897 | "name": "sellVolumesCurrent", 898 | "outputs": [ 899 | { 900 | "name": "", 901 | "type": "uint256" 902 | } 903 | ], 904 | "payable": false, 905 | "stateMutability": "view", 906 | "type": "function" 907 | }, 908 | { 909 | "constant": true, 910 | "inputs": [ 911 | { 912 | "name": "a", 913 | "type": "uint256" 914 | }, 915 | { 916 | "name": "b", 917 | "type": "uint256" 918 | } 919 | ], 920 | "name": "sub", 921 | "outputs": [ 922 | { 923 | "name": "", 924 | "type": "uint256" 925 | } 926 | ], 927 | "payable": false, 928 | "stateMutability": "pure", 929 | "type": "function" 930 | }, 931 | { 932 | "constant": true, 933 | "inputs": [ 934 | { 935 | "name": "", 936 | "type": "address" 937 | }, 938 | { 939 | "name": "", 940 | "type": "address" 941 | } 942 | ], 943 | "name": "buyVolumes", 944 | "outputs": [ 945 | { 946 | "name": "", 947 | "type": "uint256" 948 | } 949 | ], 950 | "payable": false, 951 | "stateMutability": "view", 952 | "type": "function" 953 | }, 954 | { 955 | "constant": true, 956 | "inputs": [ 957 | { 958 | "name": "auctionSellTokens", 959 | "type": "address[]" 960 | }, 961 | { 962 | "name": "auctionBuyTokens", 963 | "type": "address[]" 964 | }, 965 | { 966 | "name": "user", 967 | "type": "address" 968 | } 969 | ], 970 | "name": "getBuyerBalancesOfCurrentAuctions", 971 | "outputs": [ 972 | { 973 | "name": "", 974 | "type": "uint256[]" 975 | } 976 | ], 977 | "payable": false, 978 | "stateMutability": "view", 979 | "type": "function" 980 | }, 981 | { 982 | "constant": true, 983 | "inputs": [ 984 | { 985 | "name": "", 986 | "type": "address" 987 | }, 988 | { 989 | "name": "", 990 | "type": "address" 991 | }, 992 | { 993 | "name": "", 994 | "type": "uint256" 995 | }, 996 | { 997 | "name": "", 998 | "type": "address" 999 | } 1000 | ], 1001 | "name": "sellerBalances", 1002 | "outputs": [ 1003 | { 1004 | "name": "", 1005 | "type": "uint256" 1006 | } 1007 | ], 1008 | "payable": false, 1009 | "stateMutability": "view", 1010 | "type": "function" 1011 | }, 1012 | { 1013 | "constant": true, 1014 | "inputs": [ 1015 | { 1016 | "name": "", 1017 | "type": "address" 1018 | }, 1019 | { 1020 | "name": "", 1021 | "type": "address" 1022 | } 1023 | ], 1024 | "name": "balances", 1025 | "outputs": [ 1026 | { 1027 | "name": "", 1028 | "type": "uint256" 1029 | } 1030 | ], 1031 | "payable": false, 1032 | "stateMutability": "view", 1033 | "type": "function" 1034 | }, 1035 | { 1036 | "constant": false, 1037 | "inputs": [ 1038 | { 1039 | "name": "_thresholdNewAuction", 1040 | "type": "uint256" 1041 | } 1042 | ], 1043 | "name": "updateThresholdNewAuction", 1044 | "outputs": [], 1045 | "payable": false, 1046 | "stateMutability": "nonpayable", 1047 | "type": "function" 1048 | }, 1049 | { 1050 | "constant": true, 1051 | "inputs": [ 1052 | { 1053 | "name": "a", 1054 | "type": "uint256" 1055 | }, 1056 | { 1057 | "name": "b", 1058 | "type": "uint256" 1059 | } 1060 | ], 1061 | "name": "mul", 1062 | "outputs": [ 1063 | { 1064 | "name": "", 1065 | "type": "uint256" 1066 | } 1067 | ], 1068 | "payable": false, 1069 | "stateMutability": "pure", 1070 | "type": "function" 1071 | }, 1072 | { 1073 | "constant": true, 1074 | "inputs": [ 1075 | { 1076 | "name": "a", 1077 | "type": "uint256" 1078 | }, 1079 | { 1080 | "name": "b", 1081 | "type": "uint256" 1082 | } 1083 | ], 1084 | "name": "safeToMul", 1085 | "outputs": [ 1086 | { 1087 | "name": "", 1088 | "type": "bool" 1089 | } 1090 | ], 1091 | "payable": false, 1092 | "stateMutability": "pure", 1093 | "type": "function" 1094 | }, 1095 | { 1096 | "constant": true, 1097 | "inputs": [], 1098 | "name": "newProposalEthUSDOracle", 1099 | "outputs": [ 1100 | { 1101 | "name": "", 1102 | "type": "address" 1103 | } 1104 | ], 1105 | "payable": false, 1106 | "stateMutability": "view", 1107 | "type": "function" 1108 | }, 1109 | { 1110 | "constant": true, 1111 | "inputs": [], 1112 | "name": "owlToken", 1113 | "outputs": [ 1114 | { 1115 | "name": "", 1116 | "type": "address" 1117 | } 1118 | ], 1119 | "payable": false, 1120 | "stateMutability": "view", 1121 | "type": "function" 1122 | }, 1123 | { 1124 | "constant": true, 1125 | "inputs": [ 1126 | { 1127 | "name": "auctionSellToken", 1128 | "type": "address" 1129 | }, 1130 | { 1131 | "name": "auctionBuyToken", 1132 | "type": "address" 1133 | }, 1134 | { 1135 | "name": "user", 1136 | "type": "address" 1137 | }, 1138 | { 1139 | "name": "lastNAuctions", 1140 | "type": "uint256" 1141 | } 1142 | ], 1143 | "name": "getIndicesWithClaimableTokensForBuyers", 1144 | "outputs": [ 1145 | { 1146 | "name": "indices", 1147 | "type": "uint256[]" 1148 | }, 1149 | { 1150 | "name": "usersBalances", 1151 | "type": "uint256[]" 1152 | } 1153 | ], 1154 | "payable": false, 1155 | "stateMutability": "view", 1156 | "type": "function" 1157 | }, 1158 | { 1159 | "constant": false, 1160 | "inputs": [ 1161 | { 1162 | "name": "auctionSellTokens", 1163 | "type": "address[]" 1164 | }, 1165 | { 1166 | "name": "auctionBuyTokens", 1167 | "type": "address[]" 1168 | }, 1169 | { 1170 | "name": "auctionIndices", 1171 | "type": "uint256[]" 1172 | }, 1173 | { 1174 | "name": "user", 1175 | "type": "address" 1176 | } 1177 | ], 1178 | "name": "claimTokensFromSeveralAuctionsAsBuyer", 1179 | "outputs": [], 1180 | "payable": false, 1181 | "stateMutability": "nonpayable", 1182 | "type": "function" 1183 | }, 1184 | { 1185 | "constant": true, 1186 | "inputs": [ 1187 | { 1188 | "name": "token1", 1189 | "type": "address" 1190 | }, 1191 | { 1192 | "name": "token2", 1193 | "type": "address" 1194 | } 1195 | ], 1196 | "name": "getAuctionStart", 1197 | "outputs": [ 1198 | { 1199 | "name": "auctionStart", 1200 | "type": "uint256" 1201 | } 1202 | ], 1203 | "payable": false, 1204 | "stateMutability": "view", 1205 | "type": "function" 1206 | }, 1207 | { 1208 | "constant": true, 1209 | "inputs": [ 1210 | { 1211 | "name": "sellToken", 1212 | "type": "address" 1213 | }, 1214 | { 1215 | "name": "buyToken", 1216 | "type": "address" 1217 | }, 1218 | { 1219 | "name": "user", 1220 | "type": "address" 1221 | }, 1222 | { 1223 | "name": "auctionIndex", 1224 | "type": "uint256" 1225 | } 1226 | ], 1227 | "name": "getUnclaimedBuyerFunds", 1228 | "outputs": [ 1229 | { 1230 | "name": "unclaimedBuyerFunds", 1231 | "type": "uint256" 1232 | }, 1233 | { 1234 | "name": "num", 1235 | "type": "uint256" 1236 | }, 1237 | { 1238 | "name": "den", 1239 | "type": "uint256" 1240 | } 1241 | ], 1242 | "payable": false, 1243 | "stateMutability": "view", 1244 | "type": "function" 1245 | }, 1246 | { 1247 | "constant": false, 1248 | "inputs": [ 1249 | { 1250 | "name": "_thresholdNewTokenPair", 1251 | "type": "uint256" 1252 | } 1253 | ], 1254 | "name": "updateThresholdNewTokenPair", 1255 | "outputs": [], 1256 | "payable": false, 1257 | "stateMutability": "nonpayable", 1258 | "type": "function" 1259 | }, 1260 | { 1261 | "constant": true, 1262 | "inputs": [ 1263 | { 1264 | "name": "a", 1265 | "type": "uint256" 1266 | }, 1267 | { 1268 | "name": "b", 1269 | "type": "uint256" 1270 | } 1271 | ], 1272 | "name": "safeToSub", 1273 | "outputs": [ 1274 | { 1275 | "name": "", 1276 | "type": "bool" 1277 | } 1278 | ], 1279 | "payable": false, 1280 | "stateMutability": "pure", 1281 | "type": "function" 1282 | }, 1283 | { 1284 | "constant": false, 1285 | "inputs": [ 1286 | { 1287 | "name": "token1", 1288 | "type": "address" 1289 | }, 1290 | { 1291 | "name": "token2", 1292 | "type": "address" 1293 | }, 1294 | { 1295 | "name": "token1Funding", 1296 | "type": "uint256" 1297 | }, 1298 | { 1299 | "name": "token2Funding", 1300 | "type": "uint256" 1301 | }, 1302 | { 1303 | "name": "initialClosingPriceNum", 1304 | "type": "uint256" 1305 | }, 1306 | { 1307 | "name": "initialClosingPriceDen", 1308 | "type": "uint256" 1309 | } 1310 | ], 1311 | "name": "addTokenPair", 1312 | "outputs": [], 1313 | "payable": false, 1314 | "stateMutability": "nonpayable", 1315 | "type": "function" 1316 | }, 1317 | { 1318 | "constant": true, 1319 | "inputs": [ 1320 | { 1321 | "name": "", 1322 | "type": "address" 1323 | }, 1324 | { 1325 | "name": "", 1326 | "type": "address" 1327 | }, 1328 | { 1329 | "name": "", 1330 | "type": "uint256" 1331 | } 1332 | ], 1333 | "name": "closingPrices", 1334 | "outputs": [ 1335 | { 1336 | "name": "num", 1337 | "type": "uint256" 1338 | }, 1339 | { 1340 | "name": "den", 1341 | "type": "uint256" 1342 | } 1343 | ], 1344 | "payable": false, 1345 | "stateMutability": "view", 1346 | "type": "function" 1347 | }, 1348 | { 1349 | "constant": true, 1350 | "inputs": [ 1351 | { 1352 | "name": "user", 1353 | "type": "address" 1354 | } 1355 | ], 1356 | "name": "getFeeRatio", 1357 | "outputs": [ 1358 | { 1359 | "name": "num", 1360 | "type": "uint256" 1361 | }, 1362 | { 1363 | "name": "den", 1364 | "type": "uint256" 1365 | } 1366 | ], 1367 | "payable": false, 1368 | "stateMutability": "view", 1369 | "type": "function" 1370 | }, 1371 | { 1372 | "constant": true, 1373 | "inputs": [], 1374 | "name": "thresholdNewAuction", 1375 | "outputs": [ 1376 | { 1377 | "name": "", 1378 | "type": "uint256" 1379 | } 1380 | ], 1381 | "payable": false, 1382 | "stateMutability": "view", 1383 | "type": "function" 1384 | }, 1385 | { 1386 | "constant": false, 1387 | "inputs": [ 1388 | { 1389 | "name": "tokenAddress", 1390 | "type": "address" 1391 | }, 1392 | { 1393 | "name": "amount", 1394 | "type": "uint256" 1395 | } 1396 | ], 1397 | "name": "withdraw", 1398 | "outputs": [ 1399 | { 1400 | "name": "", 1401 | "type": "uint256" 1402 | } 1403 | ], 1404 | "payable": false, 1405 | "stateMutability": "nonpayable", 1406 | "type": "function" 1407 | }, 1408 | { 1409 | "constant": true, 1410 | "inputs": [ 1411 | { 1412 | "name": "token", 1413 | "type": "address" 1414 | } 1415 | ], 1416 | "name": "getPriceOfTokenInLastAuction", 1417 | "outputs": [ 1418 | { 1419 | "name": "num", 1420 | "type": "uint256" 1421 | }, 1422 | { 1423 | "name": "den", 1424 | "type": "uint256" 1425 | } 1426 | ], 1427 | "payable": false, 1428 | "stateMutability": "view", 1429 | "type": "function" 1430 | }, 1431 | { 1432 | "constant": false, 1433 | "inputs": [], 1434 | "name": "updateEthUSDOracle", 1435 | "outputs": [], 1436 | "payable": false, 1437 | "stateMutability": "nonpayable", 1438 | "type": "function" 1439 | }, 1440 | { 1441 | "constant": false, 1442 | "inputs": [ 1443 | { 1444 | "name": "_masterCopy", 1445 | "type": "address" 1446 | } 1447 | ], 1448 | "name": "startMasterCopyCountdown", 1449 | "outputs": [], 1450 | "payable": false, 1451 | "stateMutability": "nonpayable", 1452 | "type": "function" 1453 | }, 1454 | { 1455 | "constant": true, 1456 | "inputs": [ 1457 | { 1458 | "name": "", 1459 | "type": "address" 1460 | }, 1461 | { 1462 | "name": "", 1463 | "type": "address" 1464 | }, 1465 | { 1466 | "name": "", 1467 | "type": "uint256" 1468 | } 1469 | ], 1470 | "name": "extraTokens", 1471 | "outputs": [ 1472 | { 1473 | "name": "", 1474 | "type": "uint256" 1475 | } 1476 | ], 1477 | "payable": false, 1478 | "stateMutability": "view", 1479 | "type": "function" 1480 | }, 1481 | { 1482 | "constant": true, 1483 | "inputs": [ 1484 | { 1485 | "name": "auctionSellToken", 1486 | "type": "address" 1487 | }, 1488 | { 1489 | "name": "auctionBuyToken", 1490 | "type": "address" 1491 | }, 1492 | { 1493 | "name": "user", 1494 | "type": "address" 1495 | }, 1496 | { 1497 | "name": "lastNAuctions", 1498 | "type": "uint256" 1499 | } 1500 | ], 1501 | "name": "getIndicesWithClaimableTokensForSellers", 1502 | "outputs": [ 1503 | { 1504 | "name": "indices", 1505 | "type": "uint256[]" 1506 | }, 1507 | { 1508 | "name": "usersBalances", 1509 | "type": "uint256[]" 1510 | } 1511 | ], 1512 | "payable": false, 1513 | "stateMutability": "view", 1514 | "type": "function" 1515 | }, 1516 | { 1517 | "constant": true, 1518 | "inputs": [ 1519 | { 1520 | "name": "", 1521 | "type": "address" 1522 | }, 1523 | { 1524 | "name": "", 1525 | "type": "address" 1526 | } 1527 | ], 1528 | "name": "latestAuctionIndices", 1529 | "outputs": [ 1530 | { 1531 | "name": "", 1532 | "type": "uint256" 1533 | } 1534 | ], 1535 | "payable": false, 1536 | "stateMutability": "view", 1537 | "type": "function" 1538 | }, 1539 | { 1540 | "constant": true, 1541 | "inputs": [ 1542 | { 1543 | "name": "sellToken", 1544 | "type": "address" 1545 | }, 1546 | { 1547 | "name": "buyToken", 1548 | "type": "address" 1549 | }, 1550 | { 1551 | "name": "auctionIndex", 1552 | "type": "uint256" 1553 | } 1554 | ], 1555 | "name": "getCurrentAuctionPrice", 1556 | "outputs": [ 1557 | { 1558 | "name": "num", 1559 | "type": "uint256" 1560 | }, 1561 | { 1562 | "name": "den", 1563 | "type": "uint256" 1564 | } 1565 | ], 1566 | "payable": false, 1567 | "stateMutability": "view", 1568 | "type": "function" 1569 | }, 1570 | { 1571 | "anonymous": false, 1572 | "inputs": [ 1573 | { 1574 | "indexed": true, 1575 | "name": "token", 1576 | "type": "address" 1577 | }, 1578 | { 1579 | "indexed": false, 1580 | "name": "amount", 1581 | "type": "uint256" 1582 | } 1583 | ], 1584 | "name": "NewDeposit", 1585 | "type": "event" 1586 | }, 1587 | { 1588 | "anonymous": false, 1589 | "inputs": [ 1590 | { 1591 | "indexed": false, 1592 | "name": "priceOracleInterface", 1593 | "type": "address" 1594 | } 1595 | ], 1596 | "name": "NewOracleProposal", 1597 | "type": "event" 1598 | }, 1599 | { 1600 | "anonymous": false, 1601 | "inputs": [ 1602 | { 1603 | "indexed": false, 1604 | "name": "newMasterCopy", 1605 | "type": "address" 1606 | } 1607 | ], 1608 | "name": "NewMasterCopyProposal", 1609 | "type": "event" 1610 | }, 1611 | { 1612 | "anonymous": false, 1613 | "inputs": [ 1614 | { 1615 | "indexed": true, 1616 | "name": "token", 1617 | "type": "address" 1618 | }, 1619 | { 1620 | "indexed": false, 1621 | "name": "amount", 1622 | "type": "uint256" 1623 | } 1624 | ], 1625 | "name": "NewWithdrawal", 1626 | "type": "event" 1627 | }, 1628 | { 1629 | "anonymous": false, 1630 | "inputs": [ 1631 | { 1632 | "indexed": true, 1633 | "name": "sellToken", 1634 | "type": "address" 1635 | }, 1636 | { 1637 | "indexed": true, 1638 | "name": "buyToken", 1639 | "type": "address" 1640 | }, 1641 | { 1642 | "indexed": true, 1643 | "name": "user", 1644 | "type": "address" 1645 | }, 1646 | { 1647 | "indexed": false, 1648 | "name": "auctionIndex", 1649 | "type": "uint256" 1650 | }, 1651 | { 1652 | "indexed": false, 1653 | "name": "amount", 1654 | "type": "uint256" 1655 | } 1656 | ], 1657 | "name": "NewSellOrder", 1658 | "type": "event" 1659 | }, 1660 | { 1661 | "anonymous": false, 1662 | "inputs": [ 1663 | { 1664 | "indexed": true, 1665 | "name": "sellToken", 1666 | "type": "address" 1667 | }, 1668 | { 1669 | "indexed": true, 1670 | "name": "buyToken", 1671 | "type": "address" 1672 | }, 1673 | { 1674 | "indexed": true, 1675 | "name": "user", 1676 | "type": "address" 1677 | }, 1678 | { 1679 | "indexed": false, 1680 | "name": "auctionIndex", 1681 | "type": "uint256" 1682 | }, 1683 | { 1684 | "indexed": false, 1685 | "name": "amount", 1686 | "type": "uint256" 1687 | } 1688 | ], 1689 | "name": "NewBuyOrder", 1690 | "type": "event" 1691 | }, 1692 | { 1693 | "anonymous": false, 1694 | "inputs": [ 1695 | { 1696 | "indexed": true, 1697 | "name": "sellToken", 1698 | "type": "address" 1699 | }, 1700 | { 1701 | "indexed": true, 1702 | "name": "buyToken", 1703 | "type": "address" 1704 | }, 1705 | { 1706 | "indexed": true, 1707 | "name": "user", 1708 | "type": "address" 1709 | }, 1710 | { 1711 | "indexed": false, 1712 | "name": "auctionIndex", 1713 | "type": "uint256" 1714 | }, 1715 | { 1716 | "indexed": false, 1717 | "name": "amount", 1718 | "type": "uint256" 1719 | }, 1720 | { 1721 | "indexed": false, 1722 | "name": "frtsIssued", 1723 | "type": "uint256" 1724 | } 1725 | ], 1726 | "name": "NewSellerFundsClaim", 1727 | "type": "event" 1728 | }, 1729 | { 1730 | "anonymous": false, 1731 | "inputs": [ 1732 | { 1733 | "indexed": true, 1734 | "name": "sellToken", 1735 | "type": "address" 1736 | }, 1737 | { 1738 | "indexed": true, 1739 | "name": "buyToken", 1740 | "type": "address" 1741 | }, 1742 | { 1743 | "indexed": true, 1744 | "name": "user", 1745 | "type": "address" 1746 | }, 1747 | { 1748 | "indexed": false, 1749 | "name": "auctionIndex", 1750 | "type": "uint256" 1751 | }, 1752 | { 1753 | "indexed": false, 1754 | "name": "amount", 1755 | "type": "uint256" 1756 | }, 1757 | { 1758 | "indexed": false, 1759 | "name": "frtsIssued", 1760 | "type": "uint256" 1761 | } 1762 | ], 1763 | "name": "NewBuyerFundsClaim", 1764 | "type": "event" 1765 | }, 1766 | { 1767 | "anonymous": false, 1768 | "inputs": [ 1769 | { 1770 | "indexed": true, 1771 | "name": "sellToken", 1772 | "type": "address" 1773 | }, 1774 | { 1775 | "indexed": true, 1776 | "name": "buyToken", 1777 | "type": "address" 1778 | } 1779 | ], 1780 | "name": "NewTokenPair", 1781 | "type": "event" 1782 | }, 1783 | { 1784 | "anonymous": false, 1785 | "inputs": [ 1786 | { 1787 | "indexed": true, 1788 | "name": "sellToken", 1789 | "type": "address" 1790 | }, 1791 | { 1792 | "indexed": true, 1793 | "name": "buyToken", 1794 | "type": "address" 1795 | }, 1796 | { 1797 | "indexed": false, 1798 | "name": "sellVolume", 1799 | "type": "uint256" 1800 | }, 1801 | { 1802 | "indexed": false, 1803 | "name": "buyVolume", 1804 | "type": "uint256" 1805 | }, 1806 | { 1807 | "indexed": true, 1808 | "name": "auctionIndex", 1809 | "type": "uint256" 1810 | } 1811 | ], 1812 | "name": "AuctionCleared", 1813 | "type": "event" 1814 | }, 1815 | { 1816 | "anonymous": false, 1817 | "inputs": [ 1818 | { 1819 | "indexed": true, 1820 | "name": "token", 1821 | "type": "address" 1822 | }, 1823 | { 1824 | "indexed": false, 1825 | "name": "approved", 1826 | "type": "bool" 1827 | } 1828 | ], 1829 | "name": "Approval", 1830 | "type": "event" 1831 | }, 1832 | { 1833 | "anonymous": false, 1834 | "inputs": [ 1835 | { 1836 | "indexed": true, 1837 | "name": "sellToken", 1838 | "type": "address" 1839 | }, 1840 | { 1841 | "indexed": true, 1842 | "name": "buyToken", 1843 | "type": "address" 1844 | }, 1845 | { 1846 | "indexed": true, 1847 | "name": "auctionIndex", 1848 | "type": "uint256" 1849 | }, 1850 | { 1851 | "indexed": false, 1852 | "name": "auctionStart", 1853 | "type": "uint256" 1854 | } 1855 | ], 1856 | "name": "AuctionStartScheduled", 1857 | "type": "event" 1858 | }, 1859 | { 1860 | "anonymous": false, 1861 | "inputs": [ 1862 | { 1863 | "indexed": true, 1864 | "name": "primaryToken", 1865 | "type": "address" 1866 | }, 1867 | { 1868 | "indexed": true, 1869 | "name": "secondarToken", 1870 | "type": "address" 1871 | }, 1872 | { 1873 | "indexed": true, 1874 | "name": "user", 1875 | "type": "address" 1876 | }, 1877 | { 1878 | "indexed": false, 1879 | "name": "auctionIndex", 1880 | "type": "uint256" 1881 | }, 1882 | { 1883 | "indexed": false, 1884 | "name": "fee", 1885 | "type": "uint256" 1886 | } 1887 | ], 1888 | "name": "Fee", 1889 | "type": "event" 1890 | } 1891 | ] --------------------------------------------------------------------------------