Skip to content

Commit c2af245

Browse files
committed
initial
0 parents  commit c2af245

File tree

150 files changed

+194841
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

150 files changed

+194841
-0
lines changed

calculate_profit_from_logs.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import pygraphviz as pgv
2+
import csv, csv_hack, os
3+
import json
4+
from exchanges import get_trade_data_from_log_item
5+
6+
COLORS = ["red", "blue", "green", "orange", "purple", "black", "yellow", "grey", "darkgreen"] * 10
7+
8+
logsdict = csv.DictReader(open('data/all_logs_bigquery.csv'), delimiter=',',
9+
quotechar='"', quoting=csv.QUOTE_MINIMAL)
10+
logs = {}
11+
12+
def get_rate_label(token1, amount1, token2, amount2):
13+
if (token1 >= token2 and token1 != "ETH" and token1 != "WETH") or (token2 == "ETH" or token2 == "WETH"): # arbitrary ordering/ tiebreak
14+
try:
15+
return "%4g %s" % (amount1/amount2, token1 + '/' + token2)
16+
except ZeroDivisionError:
17+
return "[INF] %s" % (token1 + '/' + token2)
18+
try:
19+
return "%4g %s" % (amount2/amount1, token2 + '/' + token1)
20+
except ZeroDivisionError:
21+
return "[INF] %s" % (token2 + '/' + token1)
22+
23+
24+
def get_profit_graph(logset, txhash):
25+
dot = pgv.AGraph(label=txhash + ' Profit Flow', directed=True, strict=False, nodesep=1.0, ranksep=0.5, sep=0.0, labelfloat=False)
26+
unknown = False
27+
graph_edges = []
28+
logindex = 1
29+
tokens_involved = set()
30+
trades = []
31+
for logitem in logset:
32+
address = logitem[0]
33+
data = logitem[1]
34+
topicstext = logitem[2].replace('\'', '\"')
35+
topics = json.loads(topicstext)
36+
data = data[2:] # strip 0x from hex
37+
trades_data = get_trade_data_from_log_item(topics, data, address)
38+
if trades_data is not None:
39+
for trade_data in trades_data:
40+
(tokenget_addr, tokenget_label, tokenget, amountget, tokengive_addr, tokengive_label, tokengive, amountgive, exchange) = trade_data
41+
graph_edges.append((tokenget, "!" + exchange, amountget)) # (add "!" to mark special exchange node)
42+
graph_edges.append(("!" + exchange, tokengive, amountgive))
43+
44+
rate_label = get_rate_label(tokenget, amountget, tokengive, amountgive)
45+
tradenode_label = "Trade #" + str(logindex) + " (" + exchange + ")\n" + rate_label
46+
dot.add_edge(tokenget_label, tradenode_label, label=("%4g" % amountget), color=COLORS[logindex])
47+
dot.add_edge(tradenode_label, tokengive_label, label=("%4g" % amountgive), color=COLORS[logindex])
48+
trades.append(tradenode_label)
49+
tokens_involved.add(tokenget_label)
50+
tokens_involved.add(tokengive_label)
51+
logindex += 1
52+
else:
53+
# some item in the logset failed to parse => we don't have complete profit picture
54+
unknown = True
55+
for token in list(tokens_involved):
56+
dot.add_subgraph(token, rank='same')
57+
dot.add_subgraph(trades, rank='same')
58+
for i in range(0, len(trades) - 1):
59+
dot.add_edge(trades[i], trades[i+1], style="invis")
60+
return(graph_edges, unknown, dot)
61+
62+
def calculate_profit_for(profit_graph):
63+
token_profits = {}
64+
for edge in profit_graph:
65+
if not edge[0] in token_profits:
66+
token_profits[edge[0]] = 0
67+
if not edge[1] in token_profits:
68+
token_profits[edge[1]] = 0
69+
token_profits[edge[0]] -= edge[2]
70+
token_profits[edge[1]] += edge[2]
71+
return token_profits
72+
73+
for log in logsdict:
74+
hash = log['transaction_hash']
75+
if not hash in logs:
76+
logs[hash] = []
77+
logs[hash].append((log['address'], log['data'], log['topics']))
78+
79+
80+
spamwriter = csv.writer(open('data/profits.csv', 'w'), delimiter=',',
81+
quotechar='"', quoting=csv.QUOTE_MINIMAL)
82+
spamwriter.writerow(["txhash","drawn","unknown","all_positive","eth_profit","profit_graph","profit_calcs"])
83+
84+
i = 0
85+
total = len(logs)
86+
for txhash in logs:
87+
i += 1
88+
print(txhash, i, "of", total)
89+
output_file_name = 'profit_graphs/' + txhash + '.png'
90+
drawn = False
91+
(profit_graph, unknown, dot) = get_profit_graph(logs[txhash], txhash)
92+
if unknown:
93+
# failed to process given entry because some exchange that's in dex_list.py is missing a log parser
94+
print("UNKNOWN!", txhash)
95+
if not unknown and len(profit_graph) > 2:
96+
if not os.path.exists(output_file_name):
97+
dot.draw(output_file_name, prog="dot")
98+
drawn = True
99+
profit_calcs = calculate_profit_for(profit_graph)
100+
all_positive = True
101+
for token in profit_calcs:
102+
if token[0] != "!":
103+
if profit_calcs[token] < 0:
104+
all_positive = False
105+
profit_graph_data = json.dumps(profit_graph)
106+
profit_calcs_data = json.dumps(profit_calcs)
107+
spamwriter.writerow([txhash, drawn, unknown, all_positive, profit_calcs.get('ETH', 0), profit_graph_data, profit_calcs_data])

calculate_slots.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from statistics import mean
2+
import csv
3+
import numpy as np
4+
5+
import csv_hack
6+
7+
arbitrageurs = {}
8+
slotprices = {}
9+
10+
def add_to_count(arbitrageurs, arbitrageur):
11+
if arbitrageur in arbitrageurs:
12+
arbitrageurs[arbitrageur] += 1
13+
else:
14+
arbitrageurs[arbitrageur] = 1
15+
16+
slotsdict = csv.DictReader(open('data/gas_slots_6207336_6146507.csv'))
17+
slotsdict = csv.DictReader(open('data/gas_slots.csv'))
18+
for tx in slotsdict:
19+
slot = int(tx['tx_position'])
20+
if int(tx['gas_used']) < (int(tx['gas_limit']) * 0.6) and int(tx['gas_price']) > 310000000000 and tx['log_topics'].count("~") > 1 and not tx['to'].lower() in ["0xa62142888aba8370742be823c1782d17a0389da1", "0xdd9fd6b6f8f7ea932997992bbe67eabb3e316f3c"]:
21+
print(tx['hash'], tx['from'], tx['to'])
22+
add_to_count(arbitrageurs, tx['from'])
23+
if not slot in slotprices:
24+
slotprices[slot] = []
25+
slotprices[slot].append(int(tx['gas_price']))
26+
27+
for arbitrageur in arbitrageurs.keys():
28+
if arbitrageurs[arbitrageur] > 0:
29+
print("arber", "https://etherscan.io/address/" + arbitrageur, arbitrageurs[arbitrageur])
30+
31+
open("data/slots_new.csv", "w").write("\n".join([",".join([str(x/(10**9)) for x in [ np.percentile(slotprices[slot], 10), np.percentile(slotprices[slot], 50), np.percentile(slotprices[slot], 75), np.percentile(slotprices[slot], 90), np.percentile(slotprices[slot], 99)]]) for slot in range(0, 10)]))
32+
for slot in slotprices:
33+
prices = slotprices[slot]
34+
print(slot, np.percentile(prices, 10), np.percentile(prices, 50), np.percentile(prices, 75), np.percentile(prices, 99))

count_wins.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import csv
2+
def get_winner_dict():
3+
winner_dict = {}
4+
slotsdict = csv.DictReader(open('data/slot_auction.csv'))
5+
for slot in slotsdict:
6+
slot['log_count'] = slot['log_addrs'].count("~") + min(1, len(slot['log_addrs']))
7+
winner_dict[slot['hash']] = slot
8+
return winner_dict
9+
10+
11+
arbs = {}
12+
13+
winner_dict = get_winner_dict()
14+
print(len(winner_dict.keys()))
15+
16+
for hash in winner_dict:
17+
if winner_dict[hash]['log_count'] > 0:
18+
sender = winner_dict[hash]['from']
19+
if not sender in arbs:
20+
arbs[sender] = 0
21+
arbs[sender] += 1
22+
23+
print(arbs)

csv_hack.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# QUICK AND DIRTY HACK FROM https://stackoverflow.com/questions/15063936/csv-error-field-larger-than-field-limit-131072
2+
import sys, csv
3+
maxInt = sys.maxsize
4+
5+
decrement = False
6+
try:
7+
csv.field_size_limit(maxInt)
8+
except OverflowError:
9+
maxInt = int(maxInt/10)
10+
decrement = True
11+
# END HACK
12+

csv_to_sqlite.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import os
2+
3+
os.system('sqlite3 data/arbitrage_new.db ".mode csv" ".import data/block_fees.csv block_fees" ".import data/eth.csv eth_data" ".import data/all_logs_bigquery.csv logs" ".import data/block_data.csv blocks" ".import data/all_success_arb_txs_bigquery.csv success" ".import data/all_inclfail_arb_txs_bigquery.csv wfail" ".import data/auctions.csv auctions" ".import data/profits.csv profits" "CREATE TABLE mergedprofitabletxs AS SELECT *,substr(timestamp,0,11) as date FROM wfail LEFT JOIN profits on profits.txhash=wfail.transaction_hash LEFT JOIN success on success.transaction_hash=wfail.transaction_hash LEFT JOIN blocks on blocks.number=wfail.block_number GROUP BY wfail.transaction_hash ORDER BY CAST(wfail.block_number as INTEGER) DESC, CAST(wfail.transaction_index AS INTEGER) ASC;" ".quit" ""')
4+
os.system('mv data/arbitrage_new.db data/arbitrage.db')

data/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Coming soon; check/star this space over the next day!

etherdelta/readme.txt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
The file contract_addr.txt contains all the contracts (including EtherDelta contract itself) with successful trade transactions sent to it within [3900000, 5550000)
2+
3+
for each file:
4+
5+
all_txs/all_txs-{begin_block}-{end_block}-1.txt
6+
7+
contains all the transactions sent to the addresses in contract_addr.txt in the block range [begin_block, end_block).
8+
9+
There are 5 coloumns in each file, each line represents one transaction:
10+
11+
BlockNumber TransactionHash From To GasPrice(Wei) GasUsed InputData
12+
13+
14+
for each file:
15+
16+
succ_txs/succ_txs-{begin_block}-{end_block}-1.txt
17+
18+
contains all the transactions with one or more Etherdelta Trade Event in the block range [begin_block, end_block),
19+
20+
There are 6 coloumns in each file, each line represents one transaction:
21+
22+
BlockNumber TransactionHash Tag From To InputData
23+
24+
25+
The Tag is one of { Trade, Arbitrage, Unknown}:
26+
27+
Trade means this transctions only generated one Trade Event, which means it is a normal trade transaction.
28+
29+
Arbitrage mean this transction generated exactly 2 Trade Events and the buy/sell tokens form a pair.
30+
31+
Otherwise, the transation will be tagged as Unknown.
32+
33+

etherdelta/scripts/find_all_txs.py

Lines changed: 50 additions & 0 deletions
Large diffs are not rendered by default.

etherdelta/scripts/find_succ_txs.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import argparse
2+
from _pysha3 import keccak_256
3+
from web3 import Web3, HTTPProvider
4+
import json
5+
6+
web3 = Web3(HTTPProvider('http://localhost:8549'))
7+
#web3 = Web3(HTTPProvider('https://mainnet.infura.io/Ky03pelFIxoZdAUsr82w'))
8+
9+
etherDeltaAddress = '0x8d12A197cB00D4747a1fe03395095ce2A5CC6819'
10+
etherAddress = '0000000000000000000000000000000000000000000000000000000000000000'
11+
12+
tradeAPI = '0x' + \
13+
keccak_256(
14+
b'Trade(address,uint256,address,uint256,address,address)'
15+
).hexdigest()
16+
17+
parser = argparse.ArgumentParser(description='EtherDelta Arbitrage Bot.')
18+
parser.add_argument('--st',dest='st' ,type=int, action='store', default='5000000')
19+
parser.add_argument('--len',dest='len' ,type=int, action='store', default='100')
20+
parser.add_argument('--r',dest='r' ,type=int, action='store', default='20')
21+
args = parser.parse_args()
22+
23+
startBlock = args.st
24+
endBlock = args.st + args.len
25+
ratio = args.r
26+
result_dir = '../results/succ_txs-{}-{}-{}.txt'.format(startBlock,endBlock,ratio)
27+
28+
29+
import os
30+
if os.path.isfile(result_dir):
31+
print('Previous file exists.')
32+
with open(result_dir, 'r') as f:
33+
lines = f.readlines()
34+
if(len(lines) >= 1):
35+
print('last line:',lines[-1])
36+
number = int(lines[-1].split()[0])
37+
38+
else:
39+
print('No previous file.')
40+
number = 0
41+
42+
43+
44+
for idx in range(max(startBlock, number), endBlock + 1, ratio):
45+
block = web3.eth.getBlock(idx)
46+
transactions = block['transactions']
47+
print('block number:', idx)
48+
for txHash in transactions:
49+
tx = web3.eth.getTransaction(txHash)
50+
receipt = web3.eth.getTransactionReceipt(txHash)
51+
token_pair_list = []
52+
for log in receipt['logs']:
53+
if 'topics' in log and len(log['topics']):
54+
if log['topics'][0].hex() == tradeAPI:
55+
token_pair_list.append((log['data'][24 + 2: 64 + 2],log['data'][24 + 128 + 2: 64 + 128+ 2]))
56+
57+
num = len(token_pair_list)
58+
tag = None
59+
if num == 2 and token_pair_list[0][0] == token_pair_list[1][1] and token_pair_list[1][0] == token_pair_list[0][1]:
60+
tag = 'Arbitrage'
61+
elif num == 1:
62+
tag = 'Trade'
63+
elif num:
64+
tag = 'Unknown'
65+
if tag is not None:
66+
result = "{} {} {} {} {} {}\n".format(idx, txHash.hex(), tag, tx['from'], tx['to'], tx['input'])
67+
print(result)
68+
with open(result_dir,'a') as f:
69+
f.write(result)
70+
71+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/usr/bin/env bash
2+
3+
onexit() {
4+
kill -TERM -0
5+
wait
6+
}
7+
trap onexit INT
8+
9+
set -x
10+
11+
for (( i=3900000; i<5550000; i+=10000))
12+
do
13+
python3 find_all_txs.py --st $i --len 10000 --r 1 &
14+
15+
done
16+
17+
wait
18+
19+
#python3 find_succ_txs.py --st 3900000 --len 100000 --r 20
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/usr/bin/env bash
2+
3+
onexit() {
4+
kill -TERM -0
5+
wait
6+
}
7+
trap onexit INT
8+
9+
set -x
10+
11+
for (( i=3900000; i<=5550000; i+=50000))
12+
do
13+
python3 find_succ_txs.py --st $i --len 50000 --r 1 &
14+
# cmd="python3 find_succ_txs.py --st $i --len 100000 --r 20 &"
15+
# echo "running $cmd"
16+
# eval $cmd
17+
done
18+
19+
wait
20+
21+
#python3 find_succ_txs.py --st 3900000 --len 100000 --r 20

0 commit comments

Comments
 (0)