Skip to content

Commit

Permalink
UniSwap test script added and confirmed.
Browse files Browse the repository at this point in the history
  • Loading branch information
johngrantuk committed Nov 4, 2019
1 parent b08989a commit 230607c
Show file tree
Hide file tree
Showing 20 changed files with 2,301 additions and 63 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
$ pyenv activate uniswap

## Intro

A bot that does arbitrage between Uniswap and other exchanges. The idea is to do all the intelligence (decision of when to trade and what trade) off chain with some simple information about the state of prices, etc called from on chain via Infura. A proxy contract atomizes two transactions in one (so that they either both execute or none do).
Expand Down
239 changes: 239 additions & 0 deletions arbBotEXAMPLE.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
const Web3 = require("web3");
const fs = require('fs');
const LeaderToken = JSON.parse(fs.readFileSync("build/contracts/LeaderToken.json"));
const FollowerToken = JSON.parse(fs.readFileSync("build/contracts/FollowerToken.json"));
const UniSwapFactory = JSON.parse(fs.readFileSync("build/contracts/uniswap_factory.json"));
const UniSwapExchange = JSON.parse(fs.readFileSync("build/contracts/uniswap_exchange.json"));
const BigNumber = require('bignumber.js');
const Util = require("./src/utils/utils");
const SetUp = require("./setUp");
const {table} = require('table');
require('dotenv').config();

let data, output;

BigNumber.set({ DECIMAL_PLACES: 18});

SETUP = true;
// SETUP = false;

CHANGEPRICE = true;
CHANGEPRICE = false;

const TRADER_ACCOUNT = '0xD6d23522aD6bf85d2Ef804FA9187bDa2b8d3Ee65'; // take from ganache-cli if needed

let web3, id, LeaderTokenInstance, FollowerTokenInstance, UniSwapFactoryInstance, UniSwapExchangeInstance, leaderExchangeAddr, followerExchangeAddr;

async function run(){
if(SETUP)
await SetUp.setUp();

if(CHANGEPRICE)
await SetUp.ChangePrice();

web3 = new Web3(new Web3.providers.HttpProvider('http://127.0.0.1:8545'));
id = await web3.eth.net.getId();

LeaderTokenInstance = new web3.eth.Contract(LeaderToken.abi, LeaderToken.networks[id].address);
FollowerTokenInstance = new web3.eth.Contract(FollowerToken.abi, FollowerToken.networks[id].address);
UniSwapFactoryInstance = new web3.eth.Contract(UniSwapFactory.abi, UniSwapFactory.networks[id].address);
UniSwapExchangeInstance = new web3.eth.Contract(UniSwapExchange.abi, UniSwapExchange.networks[id].address);

leaderExchangeAddr = await UniSwapFactoryInstance.methods.getExchange(LeaderToken.networks[id].address).call();
followerExchangeAddr = await UniSwapFactoryInstance.methods.getExchange(FollowerToken.networks[id].address).call();

var followerBalanceWei = await FollowerTokenInstance.methods.balanceOf(TRADER_ACCOUNT).call();
var leaderBalanceWei = await LeaderTokenInstance.methods.balanceOf(TRADER_ACCOUNT).call();
console.log(web3.utils.fromWei(followerBalanceWei.toString(10), 'ether'));
console.log(web3.utils.fromWei(leaderBalanceWei.toString(10), 'ether'));

while(true){
console.log('\nChecking For Arb Op...');
var ethBalance = await web3.eth.getBalance(TRADER_ACCOUNT);
console.log('Balance Eth: ' + web3.utils.fromWei(ethBalance.toString(10), 'ether'));
await PoolInfo();

var titles = ['Eth Sell', 'Eff Price', 'Token Buy Qty', 'Tokens To Sell', 'Eff Price', 'Ether Buy Qty', 'Profit', 'Result']
var trades = [];

var maxProfit = new BigNumber(0);
var maxProfitSpend = 0;
var tokensToBuy = 0;
var ethToBuy = 0;

var leaderExEthBalanceWei = await web3.eth.getBalance(leaderExchangeAddr);
var leaderExTokenBalanceWei = await LeaderTokenInstance.methods.balanceOf(leaderExchangeAddr).call();
var followerExEthBalanceWei = await web3.eth.getBalance(followerExchangeAddr);
var followerExTokenBalanceWei = await FollowerTokenInstance.methods.balanceOf(followerExchangeAddr).call();

var ethSpend = new BigNumber(0);
var spendCheck = new BigNumber(1);
var maxCheck = new BigNumber(10);
var diviser = new BigNumber(10);
var divisionCount = 0;
var divisionMax = 7; // Each division divides ethSpend by 10. So this would be 0.00001.

while(true){

var trade = [];
ethSpend = ethSpend.plus(spendCheck);

if(ethSpend.isGreaterThan(maxCheck)){
console.log('Max Spend Checked.')
break;
}

let ethSpendWei = web3.utils.toWei(ethSpend.toString(10), 'ether'); // Amount of Eth that will be spent on trade
let ethSpendWeiBN = new BigNumber(ethSpendWei);

var followerEffectivePrice = await Util.getEffectivePrices(web3, ethSpendWei, followerExEthBalanceWei, followerExTokenBalanceWei, false);

trade.push(web3.utils.fromWei(followerEffectivePrice.ethSpendWei, 'ether'));
trade.push(followerEffectivePrice.effectivePriceBN.toString(10));
trade.push(web3.utils.fromWei(followerEffectivePrice.tokensToBuyWeiBN.toString(10), 'ether'));

var tokensToSellWei = web3.utils.toWei(followerEffectivePrice.tokensToBuyWeiBN.toString(10), 'wei');
var leaderEffectivePrice = await Util.getEffectivePrices(web3, tokensToSellWei, leaderExTokenBalanceWei, leaderExEthBalanceWei, false);

trade.push(web3.utils.fromWei(leaderEffectivePrice.ethSpendWei, 'ether'));
trade.push(followerEffectivePrice.effectivePriceBN.toString(10));
trade.push(web3.utils.fromWei(leaderEffectivePrice.tokensToBuyWeiBN.toString(10), 'ether'));

var profit = leaderEffectivePrice.tokensToBuyWeiBN.minus(ethSpendWeiBN);
trade.push(web3.utils.fromWei(profit.toString(10), 'ether'));

if(profit.isGreaterThan(maxProfit)){
trade.push('New max profit');
trades.push(trade);
maxProfit = profit;
maxProfitSpend = ethSpend;
tokensToBuy = followerEffectivePrice.tokensToBuyWeiBN;
ethToBuy = leaderEffectivePrice.tokensToBuyWeiBN;
continue;
}

if(profit.isNegative()){
if(divisionCount < divisionMax){
trade.push('No Profit');
trades.push(trade);
divisionCount++;
spendCheck = spendCheck.dividedBy(diviser);
maxCheck = maxCheck.dividedBy(diviser);
ethSpend = BigNumber(0);
continue;
}
trade.push("No Profit\nCan't Arb");
trades.push(trade);
break;
}

if(followerEffectivePrice.effectivePriceBN.gt(leaderEffectivePrice.effectivePriceBN)){
trade.push("Profits");
continue;
// console.log('????')
//console.log('Max Spend Found: ' + (ethSpend - spendCheck).toString());
//console.log('Max Profit at: ' + maxProfitSpend + ' ' + web3.utils.fromWei(maxProfit.toString(10), 'ether'))
//return;
}
trade.push("Profit");
trades.push(trade);
}

trades.unshift(titles);
// console.log(trades)
output = table(trades);
console.log(output);

if(maxProfitSpend == 0){
console.log('No Arb Op.');
console.log('--------------------------------------------');
await sleep(6000);
continue;
}

console.log('\n******** Arb Op ***********');
var followerTokenBalanceStartWei = await FollowerTokenInstance.methods.balanceOf(TRADER_ACCOUNT).call();
var leaderTokenBalanceStartWei = await LeaderTokenInstance.methods.balanceOf(TRADER_ACCOUNT).call();
var ethBalanceStartWei = await web3.eth.getBalance(TRADER_ACCOUNT);
console.log('Max Profit at: ' + maxProfitSpend + ' ' + web3.utils.fromWei(maxProfit.toString(10), 'ether'));

let ethSpendWei = web3.utils.toWei(maxProfitSpend.toString(), 'ether');

var block = await web3.eth.getBlock("latest");
const DEADLINE = block.timestamp + 300;
var slippage = new BigNumber('0.997');
// var minTokensWeiBNCheck = prices.tokensToBuyWeiBN.multipliedBy(slippage).precision(18);

var minTokensWeiBN = tokensToBuy.multipliedBy(slippage).precision(18);
var wei = new BigNumber(1e18);
var minTokensEthBN = minTokensWeiBN.dividedBy(wei);
var minTokensWei = web3.utils.toWei(minTokensEthBN.toString(10), 'ether');
// console.log(minTokensEthBN.toString(10))
// console.log(tokensToBuy.toString(10))
// console.log(minTokensWei.toString(10))
const followerContract = new web3.eth.Contract(UniSwapExchange.abi, followerExchangeAddr);

var tb1 = await FollowerTokenInstance.methods.balanceOf(TRADER_ACCOUNT).call();
var tb1BN = new BigNumber(tb1.toString(10));
var tx = await followerContract.methods.ethToTokenSwapInput(minTokensWei, DEADLINE);
await Util.sendTransactionWithValue(web3, tx, TRADER_ACCOUNT, process.env.PRIVATEKEYTRADE, followerExchangeAddr, ethSpendWei); // Would be good to get return value here as its should be actual amount of tokens bought
var tb2 = await FollowerTokenInstance.methods.balanceOf(TRADER_ACCOUNT).call();
var tb2BN = new BigNumber(tb2.toString(10));
var bought = tb2BN.minus(tb1BN);
console.log('!!BOUGHT: ' + bought.toString(10));

// Sell tokens for eth
var tokensToSellWei = web3.utils.toWei(tokensToBuy.toString(10), 'wei');

const leaderContract = new web3.eth.Contract(UniSwapExchange.abi, leaderExchangeAddr);
tx = await leaderContract.methods.tokenToEthSwapInput(tokensToSellWei, ethSpendWei, DEADLINE);
await Util.sendTransaction(web3, tx, TRADER_ACCOUNT, process.env.PRIVATEKEYTRADE, leaderExchangeAddr);

var followerBalanceWei = await FollowerTokenInstance.methods.balanceOf(TRADER_ACCOUNT).call();
var leaderBalanceWei = await LeaderTokenInstance.methods.balanceOf(TRADER_ACCOUNT).call();
var ethBalanceFinish = await web3.eth.getBalance(TRADER_ACCOUNT);
var realisedProfit = BigNumber(ethBalanceFinish).minus(BigNumber(ethBalanceStartWei));
console.log('Profit: ' + web3.utils.fromWei(realisedProfit.toString(10), 'ether'));
console.log('*****************************')
/*
console.log('Old Trade Balance Follower Token: ' + web3.utils.fromWei(followerTokenBalanceStartWei.toString(10), 'ether'));
console.log('New Trade Balance Follower Token: ' + web3.utils.fromWei(followerBalanceWei.toString(10), 'ether'));
console.log('Old Trade Balance Leader Token: ' + web3.utils.fromWei(leaderTokenBalanceStartWei.toString(10), 'ether'));
console.log('New Trade Balance Leader Token: ' + web3.utils.fromWei(leaderBalanceWei.toString(10), 'ether'));
console.log('Old Trade Eth: ' + web3.utils.fromWei(ethBalanceStartWei.toString(10), 'ether'));
console.log('New Trade Eth: ' + web3.utils.fromWei(ethBalanceFinish.toString(10), 'ether'));
*/
console.log('--------------------------------------------');

}
}

async function PoolInfo(){
var leaderExEthBalanceWei = await web3.eth.getBalance(leaderExchangeAddr);
var leaderExTokenBalanceWei = await LeaderTokenInstance.methods.balanceOf(leaderExchangeAddr).call();
var followerExEthBalanceWei = await web3.eth.getBalance(followerExchangeAddr);
var followerExTokenBalanceWei = await FollowerTokenInstance.methods.balanceOf(followerExchangeAddr).call();

var leaderSpotPrices = await Util.getSpotPrices(leaderExEthBalanceWei, leaderExTokenBalanceWei, false);
var followerSpotPrices = await Util.getSpotPrices(followerExEthBalanceWei, followerExTokenBalanceWei, false);

data = [
['Pool', 'Leader Token', 'Follower Token'],
['Eth Pool', web3.utils.fromWei(leaderExEthBalanceWei, 'ether'), web3.utils.fromWei(followerExEthBalanceWei, 'ether')],
['Token Pool', web3.utils.fromWei(leaderExTokenBalanceWei, 'ether'), web3.utils.fromWei(followerExTokenBalanceWei, 'ether')],
['Eth Spot Price', leaderSpotPrices.ethPrice.toString(10), followerSpotPrices.ethPrice.toString(10)],
['Token Spot Price', leaderSpotPrices.tokenPrice.toString(10), followerSpotPrices.tokenPrice.toString(10)]
];

output = table(data);
console.log(output);
return;
}

sleep = (x) => {
return new Promise(resolve => {
setTimeout(() => { resolve(true) }, x )
})
}

run();
2 changes: 1 addition & 1 deletion client/src/contracts/ERC20.json
Original file line number Diff line number Diff line change
Expand Up @@ -11016,7 +11016,7 @@
},
"networks": {},
"schemaVersion": "3.0.16",
"updatedAt": "2019-10-20T21:15:55.079Z",
"updatedAt": "2019-11-04T18:56:07.494Z",
"devdoc": {
"details": "Implementation of the `IERC20` interface. * This implementation is agnostic to the way tokens are created. This means that a supply mechanism has to be added in a derived contract using `_mint`. For a generic mechanism see `ERC20Mintable`. * *For a detailed writeup see our guide [How to implement supply mechanisms](https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226).* * We have followed general OpenZeppelin guidelines: functions revert instead of returning `false` on failure. This behavior is nonetheless conventional and does not conflict with the expectations of ERC20 applications. * Additionally, an `Approval` event is emitted on calls to `transferFrom`. This allows applications to reconstruct the allowance for all accounts just by listening to said events. Other implementations of the EIP may not emit these events, as it isn't required by the specification. * Finally, the non-standard `decreaseAllowance` and `increaseAllowance` functions have been added to mitigate the well-known issues around setting allowances. See `IERC20.approve`.",
"methods": {
Expand Down
Loading

0 comments on commit 230607c

Please sign in to comment.