defi-state-querier 0.4.29__py3-none-any.whl → 0.5.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- defi_services/__init__.py +1 -1
- defi_services/abis/multicall_v3_abi.py +440 -0
- defi_services/constants/network_constants.py +387 -0
- defi_services/jobs/processors/multicall_state_processor.py +15 -0
- defi_services/jobs/queriers/multicall_state_querier.py +101 -0
- defi_services/jobs/queriers/state_querier.py +6 -3
- defi_services/services/eth/__init__.py +0 -0
- defi_services/services/eth/eth_services.py +91 -0
- defi_services/services/multicall/__init__.py +0 -0
- defi_services/services/multicall/batch_queries_service.py +102 -0
- defi_services/services/multicall/multicall_v2.py +492 -0
- defi_services/services/multicall/state_query_service.py +549 -0
- defi_services/services/token_services.py +4 -3
- defi_services/utils/dict_utils.py +95 -0
- {defi_state_querier-0.4.29.dist-info → defi_state_querier-0.5.0.dist-info}/METADATA +1 -1
- {defi_state_querier-0.4.29.dist-info → defi_state_querier-0.5.0.dist-info}/RECORD +19 -8
- {defi_state_querier-0.4.29.dist-info → defi_state_querier-0.5.0.dist-info}/LICENSE +0 -0
- {defi_state_querier-0.4.29.dist-info → defi_state_querier-0.5.0.dist-info}/WHEEL +0 -0
- {defi_state_querier-0.4.29.dist-info → defi_state_querier-0.5.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,102 @@
|
|
1
|
+
from query_state_lib.base.mappers.eth_call_mapper import EthCall
|
2
|
+
from web3 import Web3, contract
|
3
|
+
|
4
|
+
from defi_services.constants.token_constant import Token, ProtocolNFT
|
5
|
+
from defi_services.utils.logger_utils import get_logger
|
6
|
+
|
7
|
+
logger = get_logger('Batch queries')
|
8
|
+
|
9
|
+
w3 = Web3()
|
10
|
+
|
11
|
+
|
12
|
+
def add_rpc_call(abi, fn_name, contract_address, block_number=None, fn_paras=None, list_rpc_call=None,
|
13
|
+
list_call_id=None, call_id=None):
|
14
|
+
args = []
|
15
|
+
if fn_paras is not None:
|
16
|
+
if type(fn_paras) is list:
|
17
|
+
args = fn_paras
|
18
|
+
else:
|
19
|
+
if Web3.is_address(fn_paras):
|
20
|
+
fn_paras = Web3.to_checksum_address(fn_paras)
|
21
|
+
args = [fn_paras]
|
22
|
+
|
23
|
+
if call_id is None:
|
24
|
+
call_id = f"{fn_name}_{contract_address}_{fn_paras}_{block_number}".lower()
|
25
|
+
else:
|
26
|
+
if call_id is None:
|
27
|
+
call_id = f"{fn_name}_{contract_address}_{block_number}".lower()
|
28
|
+
|
29
|
+
if call_id in list_call_id:
|
30
|
+
return
|
31
|
+
|
32
|
+
c = contract.Contract
|
33
|
+
c.w3 = w3
|
34
|
+
c.abi = abi
|
35
|
+
data_call = c.encodeABI(fn_name=fn_name, args=args)
|
36
|
+
|
37
|
+
if block_number:
|
38
|
+
eth_call = EthCall(to=Web3.to_checksum_address(contract_address), block_number=block_number, data=data_call,
|
39
|
+
abi=abi, fn_name=fn_name, id=call_id)
|
40
|
+
else:
|
41
|
+
eth_call = EthCall(to=Web3.to_checksum_address(contract_address), data=data_call,
|
42
|
+
abi=abi, fn_name=fn_name, id=call_id)
|
43
|
+
|
44
|
+
list_rpc_call.append(eth_call)
|
45
|
+
list_call_id.append(call_id)
|
46
|
+
|
47
|
+
|
48
|
+
def decode_data_response(data_responses, list_call_id):
|
49
|
+
decoded_datas = {}
|
50
|
+
for call_id in list_call_id:
|
51
|
+
try:
|
52
|
+
response_data = data_responses.get(call_id)
|
53
|
+
decoded_data = response_data.decode_result()
|
54
|
+
except Exception as e:
|
55
|
+
logger.error(f"An exception when decode data from provider: {e}")
|
56
|
+
raise
|
57
|
+
|
58
|
+
if len(decoded_data) == 1:
|
59
|
+
decoded_datas[call_id] = decoded_data[0]
|
60
|
+
else:
|
61
|
+
decoded_datas[call_id] = decoded_data
|
62
|
+
return decoded_datas
|
63
|
+
|
64
|
+
|
65
|
+
def decode_data_response_ignore_error(data_responses, list_call_id):
|
66
|
+
decoded_datas = {}
|
67
|
+
for call_id in list_call_id:
|
68
|
+
response_data = data_responses.get(call_id)
|
69
|
+
try:
|
70
|
+
decoded_data = response_data.decode_result()
|
71
|
+
except OverflowError:
|
72
|
+
result = response_data.result
|
73
|
+
if result.startswith('0x'):
|
74
|
+
result = result[2:]
|
75
|
+
bytes_array = bytearray.fromhex(result)
|
76
|
+
bytes32 = bytes_array.hex().rstrip("0")
|
77
|
+
if len(bytes32) % 2 != 0:
|
78
|
+
bytes32 = bytes32 + '0'
|
79
|
+
decoded_data = bytes.fromhex(bytes32).decode('utf8')
|
80
|
+
except Exception as e:
|
81
|
+
decoded_datum = check_data(call_id, response_data)
|
82
|
+
if decoded_datum is not None:
|
83
|
+
decoded_datas[call_id] = decoded_datum
|
84
|
+
continue
|
85
|
+
logger.error(f"An exception when decode data from provider: {e}")
|
86
|
+
continue
|
87
|
+
|
88
|
+
if len(decoded_data) == 1:
|
89
|
+
decoded_datas[call_id] = decoded_data[0]
|
90
|
+
else:
|
91
|
+
decoded_datas[call_id] = decoded_data
|
92
|
+
return decoded_datas
|
93
|
+
|
94
|
+
|
95
|
+
def check_data(call_id: str, data):
|
96
|
+
keys = call_id.split("_")
|
97
|
+
fn = keys[0]
|
98
|
+
if fn == "underlying" and data.result == "0x":
|
99
|
+
return Token.native_token
|
100
|
+
if fn == "decimals" and keys[1] in ProtocolNFT.nft:
|
101
|
+
return 0
|
102
|
+
return None
|
@@ -0,0 +1,492 @@
|
|
1
|
+
import csv
|
2
|
+
import json
|
3
|
+
import time
|
4
|
+
from collections import defaultdict
|
5
|
+
from typing import Tuple, List, Union, Any, Optional, Dict
|
6
|
+
|
7
|
+
from query_state_lib.base.utils.decoder import decode_eth_call_data
|
8
|
+
from query_state_lib.client.client_querier import ClientQuerier
|
9
|
+
from web3 import Web3, contract
|
10
|
+
|
11
|
+
from defi_services.abis.dex.pancakeswap.pancakeswap_lp_token_abi import LP_TOKEN_ABI
|
12
|
+
from defi_services.abis.multicall_v3_abi import MULTICALL_V3_ABI
|
13
|
+
from defi_services.abis.token.erc20_abi import ERC20_ABI
|
14
|
+
from defi_services.constants.network_constants import MulticallContract, Chains, Networks
|
15
|
+
from defi_services.services.multicall.batch_queries_service import add_rpc_call, decode_data_response, \
|
16
|
+
decode_data_response_ignore_error
|
17
|
+
from defi_services.utils.dict_utils import all_equal
|
18
|
+
from defi_services.utils.logger_utils import get_logger
|
19
|
+
|
20
|
+
logger = get_logger('Multicall V2')
|
21
|
+
|
22
|
+
w3 = Web3()
|
23
|
+
|
24
|
+
|
25
|
+
def get_fn_abi(abi, fn_name):
|
26
|
+
for abi_ in abi:
|
27
|
+
if abi_.get('type') == 'function' and abi_.get('name') == fn_name:
|
28
|
+
return abi_
|
29
|
+
|
30
|
+
return None
|
31
|
+
|
32
|
+
|
33
|
+
def _unpack_aggregate_outputs(outputs: Any) -> Tuple[Tuple[Union[None, bool], bytes], ...]:
|
34
|
+
return tuple((success, output) for success, output in outputs)
|
35
|
+
|
36
|
+
|
37
|
+
class W3Multicall:
|
38
|
+
"""
|
39
|
+
Interface for multicall3.sol contract
|
40
|
+
"""
|
41
|
+
|
42
|
+
class Call:
|
43
|
+
|
44
|
+
def __init__(self, address, abi, fn_name, fn_paras=None, block_number: Union[int, str] = 'latest', key=None):
|
45
|
+
if key:
|
46
|
+
self.id = key
|
47
|
+
elif fn_paras is not None:
|
48
|
+
self.id = f"{fn_name}_{address}_{fn_paras}_{block_number}".lower()
|
49
|
+
else:
|
50
|
+
self.id = f"{fn_name}_{address}_{block_number}".lower()
|
51
|
+
|
52
|
+
self.address = Web3.to_checksum_address(address)
|
53
|
+
self.fn_name = fn_name
|
54
|
+
self.fn_abi = get_fn_abi(abi=abi, fn_name=fn_name)
|
55
|
+
self.block_number = block_number
|
56
|
+
|
57
|
+
args = []
|
58
|
+
if fn_paras is not None:
|
59
|
+
if type(fn_paras) is list:
|
60
|
+
args = fn_paras
|
61
|
+
else:
|
62
|
+
if Web3.is_address(fn_paras):
|
63
|
+
fn_paras = Web3.to_checksum_address(fn_paras)
|
64
|
+
args = [fn_paras]
|
65
|
+
|
66
|
+
c = contract.Contract
|
67
|
+
c.w3 = w3
|
68
|
+
c.abi = abi
|
69
|
+
|
70
|
+
self.data = c.encodeABI(fn_name=fn_name, args=args)
|
71
|
+
|
72
|
+
def __init__(self, web3, address='0xcA11bde05977b3631167028862bE2a173976CA11', calls: Dict[str, 'W3Multicall.Call'] = None, require_success: bool = False):
|
73
|
+
"""
|
74
|
+
:param web3: Web3 instance
|
75
|
+
:param address: (optional) address of the multicall3.sol contract
|
76
|
+
:param calls: (optional) list of W3Multicall.Call to perform
|
77
|
+
"""
|
78
|
+
self.web3 = web3
|
79
|
+
self.address = address
|
80
|
+
self.calls: Dict[str, 'W3Multicall.Call'] = {} if calls is None else calls.copy()
|
81
|
+
|
82
|
+
self.require_success = require_success
|
83
|
+
|
84
|
+
def add(self, call: 'W3Multicall.Call'):
|
85
|
+
self.calls[call.id] = call
|
86
|
+
|
87
|
+
def get_params(self, calls: Optional[Dict[str, Call]] = None) -> List[Union[bool, List[List[Any]]]]:
|
88
|
+
args = self._get_args(calls=calls)
|
89
|
+
return args
|
90
|
+
|
91
|
+
def decode(self, aggregated, calls: Optional[Dict[str, Call]] = None, ignore_error=True):
|
92
|
+
if calls is None:
|
93
|
+
calls = self.calls
|
94
|
+
|
95
|
+
unpacked = _unpack_aggregate_outputs(aggregated)
|
96
|
+
|
97
|
+
outputs = {}
|
98
|
+
for (call_id, call), (success, output) in zip(calls.items(), unpacked):
|
99
|
+
if not success:
|
100
|
+
logger.warning(f'Fail to query {call_id}')
|
101
|
+
continue
|
102
|
+
|
103
|
+
try:
|
104
|
+
call_output = '0x' + output.hex()
|
105
|
+
decoded_data = decode_eth_call_data([call.fn_abi], call.fn_name, call_output)
|
106
|
+
except OverflowError:
|
107
|
+
bytes_array = bytearray.fromhex(output.hex())
|
108
|
+
bytes32 = '0x' + bytes_array.hex().rstrip("0")
|
109
|
+
if len(bytes32) % 2 != 0:
|
110
|
+
bytes32 += '0'
|
111
|
+
decoded_data = decode_eth_call_data([call.fn_abi], call.fn_name, bytes32)
|
112
|
+
except Exception as ex:
|
113
|
+
logger.error(f"An exception when decode data from provider: {ex}")
|
114
|
+
if ignore_error:
|
115
|
+
continue
|
116
|
+
|
117
|
+
raise ex
|
118
|
+
|
119
|
+
if len(decoded_data) == 1:
|
120
|
+
decoded_data = decoded_data[0]
|
121
|
+
|
122
|
+
outputs[call_id] = decoded_data
|
123
|
+
|
124
|
+
return outputs
|
125
|
+
|
126
|
+
def _get_args(self, calls: Optional[Dict[str, Call]] = None) -> List[Union[bool, List[List[Any]]]]:
|
127
|
+
if calls is None:
|
128
|
+
calls = self.calls
|
129
|
+
|
130
|
+
block_numbers = [call.block_number for call in calls.values()]
|
131
|
+
if not all_equal(block_numbers):
|
132
|
+
raise ValueError('All calls must be queried at the same block number')
|
133
|
+
|
134
|
+
return [self.require_success, [[call.address, call.data] for call in calls.values()]]
|
135
|
+
|
136
|
+
def batch_calls_iterator(self, batch_size=2000):
|
137
|
+
calls_by_block_number = defaultdict(lambda: {})
|
138
|
+
for call_id, call in self.calls.items():
|
139
|
+
block_number = call.block_number
|
140
|
+
calls_by_block_number[block_number][call_id] = call
|
141
|
+
|
142
|
+
for block_number, calls in calls_by_block_number.items():
|
143
|
+
batch = {}
|
144
|
+
|
145
|
+
for call_id, call in calls.items():
|
146
|
+
batch[call_id] = call
|
147
|
+
if len(batch) >= batch_size:
|
148
|
+
yield block_number, batch
|
149
|
+
batch = {}
|
150
|
+
|
151
|
+
if len(batch) > 0:
|
152
|
+
yield block_number, batch
|
153
|
+
|
154
|
+
|
155
|
+
def add_rpc_multicall(w3_multicall: W3Multicall, list_rpc_call=None, list_call_id=None, batch_size=2000):
|
156
|
+
batch_idx = 0
|
157
|
+
for block_number, batch_calls in w3_multicall.batch_calls_iterator(batch_size=batch_size):
|
158
|
+
inputs = w3_multicall.get_params(calls=batch_calls)
|
159
|
+
add_rpc_call(
|
160
|
+
fn_name="tryAggregate", fn_paras=inputs, block_number=block_number,
|
161
|
+
abi=MULTICALL_V3_ABI, contract_address=w3_multicall.address,
|
162
|
+
list_call_id=list_call_id, list_rpc_call=list_rpc_call, call_id=f'{batch_idx}_{block_number}'
|
163
|
+
)
|
164
|
+
batch_idx += 1
|
165
|
+
|
166
|
+
|
167
|
+
def decode_multical_response(w3_multicall: W3Multicall, data_responses, list_call_id, ignore_error=True, batch_size=2000):
|
168
|
+
if ignore_error:
|
169
|
+
decoded_data = decode_data_response_ignore_error(data_responses=data_responses, list_call_id=list_call_id)
|
170
|
+
else:
|
171
|
+
decoded_data = decode_data_response(data_responses=data_responses, list_call_id=list_call_id)
|
172
|
+
|
173
|
+
batch_idx = 0
|
174
|
+
results = {}
|
175
|
+
for block_number, batch_calls in w3_multicall.batch_calls_iterator(batch_size=batch_size):
|
176
|
+
multicall_data = decoded_data.get(f'{batch_idx}_{block_number}')
|
177
|
+
decode_multicall_data = w3_multicall.decode(multicall_data, calls=batch_calls, ignore_error=ignore_error)
|
178
|
+
results.update(decode_multicall_data)
|
179
|
+
|
180
|
+
batch_idx += 1
|
181
|
+
|
182
|
+
return results
|
183
|
+
|
184
|
+
|
185
|
+
def _test_multicall(_w3: Web3, multicall_contract, wallets, tokens):
|
186
|
+
start_time = time.time()
|
187
|
+
encode_time = 0
|
188
|
+
|
189
|
+
rpc_call_id = {}
|
190
|
+
|
191
|
+
n_calls = 0
|
192
|
+
data = {}
|
193
|
+
for wallet in wallets:
|
194
|
+
start_encode_time = time.time()
|
195
|
+
data[wallet] = {}
|
196
|
+
|
197
|
+
w3_multicall = W3Multicall(_w3, address=MulticallContract.get_multicall_contract(Chains.arbitrum))
|
198
|
+
for token in tokens:
|
199
|
+
# if token['address'] == '0x0000000000000000000000000000000000000000':
|
200
|
+
# continue
|
201
|
+
|
202
|
+
w3_multicall.add(W3Multicall.Call(
|
203
|
+
Web3.to_checksum_address(token['address']), # contract address
|
204
|
+
ERC20_ABI, # method signature to call
|
205
|
+
'balanceOf',
|
206
|
+
wallet
|
207
|
+
))
|
208
|
+
|
209
|
+
decimals_call = W3Multicall.Call(
|
210
|
+
Web3.to_checksum_address(token['address']), # contract address
|
211
|
+
ERC20_ABI, # method signature to call
|
212
|
+
'decimals'
|
213
|
+
)
|
214
|
+
if decimals_call not in rpc_call_id:
|
215
|
+
w3_multicall.add(decimals_call)
|
216
|
+
rpc_call_id[decimals_call] = None
|
217
|
+
|
218
|
+
encode_time += time.time() - start_encode_time
|
219
|
+
n_calls += len(w3_multicall.calls)
|
220
|
+
|
221
|
+
# For single call
|
222
|
+
inputs = w3_multicall.get_params()
|
223
|
+
response = multicall_contract.functions.tryAggregate(*inputs).call()
|
224
|
+
results = w3_multicall.decode(response)
|
225
|
+
|
226
|
+
for token in tokens:
|
227
|
+
# if token['address'] == '0x0000000000000000000000000000000000000000':
|
228
|
+
# continue
|
229
|
+
|
230
|
+
decimals_call_id = f'decimals_{token["address"]}_latest'.lower()
|
231
|
+
balance_call_id = f'balanceOf_{token["address"]}_{wallet}_latest'.lower()
|
232
|
+
|
233
|
+
decimals = results.get(decimals_call_id)
|
234
|
+
if decimals is None:
|
235
|
+
decimals = rpc_call_id.get(decimals_call_id) or 18
|
236
|
+
else:
|
237
|
+
rpc_call_id[decimals_call_id] = decimals
|
238
|
+
|
239
|
+
balance = results.get(balance_call_id)
|
240
|
+
if balance is None:
|
241
|
+
balance = rpc_call_id.get(balance_call_id) or 0
|
242
|
+
else:
|
243
|
+
rpc_call_id[balance_call_id] = balance
|
244
|
+
balance /= 10 ** decimals
|
245
|
+
|
246
|
+
data[wallet][token['address']] = {
|
247
|
+
'name': token['name'],
|
248
|
+
'symbol': token['symbol'],
|
249
|
+
'balance': balance,
|
250
|
+
'decimals': decimals
|
251
|
+
}
|
252
|
+
|
253
|
+
with open('results_multicall.json', 'w') as f:
|
254
|
+
json.dump(data, f, indent=2)
|
255
|
+
duration = time.time() - start_time
|
256
|
+
|
257
|
+
print(f'There are {n_calls} calls')
|
258
|
+
print(f'Encode took {round(encode_time, 3)}s')
|
259
|
+
print(f'Done after {round(duration, 3)}s')
|
260
|
+
|
261
|
+
return duration, encode_time, n_calls
|
262
|
+
|
263
|
+
|
264
|
+
def _test_state_querier(client_querier: ClientQuerier, wallets, tokens):
|
265
|
+
start_time = time.time()
|
266
|
+
|
267
|
+
list_call_id = []
|
268
|
+
list_rpc_call = []
|
269
|
+
|
270
|
+
for wallet in wallets:
|
271
|
+
for token in tokens:
|
272
|
+
if token['address'] == '0x0000000000000000000000000000000000000000':
|
273
|
+
continue
|
274
|
+
|
275
|
+
add_rpc_call(
|
276
|
+
abi=ERC20_ABI, contract_address=Web3.to_checksum_address(token['address']),
|
277
|
+
fn_name="balanceOf", fn_paras=wallet, block_number='latest',
|
278
|
+
list_call_id=list_call_id, list_rpc_call=list_rpc_call
|
279
|
+
)
|
280
|
+
add_rpc_call(
|
281
|
+
abi=ERC20_ABI, contract_address=Web3.to_checksum_address(token['address']),
|
282
|
+
fn_name="decimals", block_number='latest',
|
283
|
+
list_call_id=list_call_id, list_rpc_call=list_rpc_call
|
284
|
+
)
|
285
|
+
|
286
|
+
encode_time = time.time() - start_time
|
287
|
+
print(f'There are {len(list_rpc_call)} calls')
|
288
|
+
print(f'Encode took {round(encode_time, 3)}s')
|
289
|
+
|
290
|
+
responses = client_querier.sent_batch_to_provider(list_rpc_call, batch_size=2000)
|
291
|
+
decoded_data = decode_data_response_ignore_error(data_responses=responses, list_call_id=list_call_id)
|
292
|
+
|
293
|
+
data = {}
|
294
|
+
for wallet in wallets:
|
295
|
+
data[wallet] = {}
|
296
|
+
for token in tokens:
|
297
|
+
if token['address'] == '0x0000000000000000000000000000000000000000':
|
298
|
+
continue
|
299
|
+
|
300
|
+
decimals = decoded_data.get(f'decimals_{token["address"]}_latest'.lower())
|
301
|
+
balance = decoded_data.get(f'balanceOf_{token["address"]}_{wallet}_latest'.lower()) / 10 ** decimals
|
302
|
+
|
303
|
+
data[wallet][token['address']] = {
|
304
|
+
'name': token['name'],
|
305
|
+
'symbol': token['symbol'],
|
306
|
+
'balance': balance,
|
307
|
+
'decimals': decimals
|
308
|
+
}
|
309
|
+
|
310
|
+
with open('results_querier.json', 'w') as f:
|
311
|
+
json.dump(data, f, indent=2)
|
312
|
+
duration = time.time() - start_time
|
313
|
+
print(f'Done after {round(duration, 3)}s')
|
314
|
+
|
315
|
+
return duration, encode_time, len(list_rpc_call)
|
316
|
+
|
317
|
+
|
318
|
+
def _test_multicall_with_multiprocessing(_w3: Web3, client_querier: ClientQuerier, wallets, tokens):
|
319
|
+
start_time = time.time()
|
320
|
+
|
321
|
+
w3_multicall = W3Multicall(_w3, address=MulticallContract.get_multicall_contract(Chains.arbitrum))
|
322
|
+
for wallet in wallets:
|
323
|
+
for token in tokens:
|
324
|
+
# if token['address'] == '0x0000000000000000000000000000000000000000':
|
325
|
+
# continue
|
326
|
+
|
327
|
+
w3_multicall.add(W3Multicall.Call(
|
328
|
+
Web3.to_checksum_address(token['address']), # contract address
|
329
|
+
ERC20_ABI, # method signature to call
|
330
|
+
'balanceOf',
|
331
|
+
wallet
|
332
|
+
))
|
333
|
+
|
334
|
+
w3_multicall.add(W3Multicall.Call(
|
335
|
+
Web3.to_checksum_address(token['address']), # contract address
|
336
|
+
ERC20_ABI, # method signature to call
|
337
|
+
'decimals'
|
338
|
+
))
|
339
|
+
|
340
|
+
w3_multicall.add(W3Multicall.Call(
|
341
|
+
Web3.to_checksum_address(token['address']), # contract address
|
342
|
+
LP_TOKEN_ABI, # method signature to call
|
343
|
+
'token0'
|
344
|
+
))
|
345
|
+
|
346
|
+
list_call_id, list_rpc_call = [], []
|
347
|
+
add_rpc_multicall(w3_multicall, list_rpc_call=list_rpc_call, list_call_id=list_call_id)
|
348
|
+
|
349
|
+
encode_time = time.time() - start_time
|
350
|
+
|
351
|
+
responses = client_querier.sent_batch_to_provider(list_rpc_call, batch_size=1)
|
352
|
+
decoded_data = decode_multical_response(
|
353
|
+
w3_multicall=w3_multicall, data_responses=responses,
|
354
|
+
list_call_id=list_call_id, ignore_error=True
|
355
|
+
)
|
356
|
+
|
357
|
+
data = {}
|
358
|
+
for wallet in wallets:
|
359
|
+
data[wallet] = {}
|
360
|
+
for token in tokens:
|
361
|
+
# if token['address'] == '0x0000000000000000000000000000000000000000':
|
362
|
+
# continue
|
363
|
+
|
364
|
+
decimals_call_id = f'decimals_{token["address"]}_latest'.lower()
|
365
|
+
balance_call_id = f'balanceOf_{token["address"]}_{wallet}_latest'.lower()
|
366
|
+
|
367
|
+
decimals = decoded_data.get(decimals_call_id) or 18
|
368
|
+
balance = decoded_data.get(balance_call_id, 0) / 10 ** decimals
|
369
|
+
|
370
|
+
data[wallet][token['address']] = {
|
371
|
+
'name': token['name'],
|
372
|
+
'symbol': token['symbol'],
|
373
|
+
'balance': balance,
|
374
|
+
'decimals': decimals
|
375
|
+
}
|
376
|
+
|
377
|
+
with open('results_multicall_with_multiprocessing.json', 'w') as f:
|
378
|
+
json.dump(data, f, indent=2)
|
379
|
+
duration = time.time() - start_time
|
380
|
+
|
381
|
+
print(f'There are {len(w3_multicall.calls)} calls')
|
382
|
+
print(f'Encode took {round(encode_time, 3)}s')
|
383
|
+
print(f'Done after {round(duration, 3)}s')
|
384
|
+
|
385
|
+
return duration, encode_time, len(w3_multicall.calls)
|
386
|
+
|
387
|
+
|
388
|
+
def query_multi_contracts(n_times=20, wallets_batch_size=5, wallets_distribute_similar=True):
|
389
|
+
with open('../../../.data/wallets.json') as f:
|
390
|
+
wallets = json.load(f)
|
391
|
+
wallets = ['0x6FfA563915CB3186b9Dd206D0E08bdeDcd2EA2ED']
|
392
|
+
|
393
|
+
with open('../../../.data/tokens.json') as f:
|
394
|
+
tokens_ = json.load(f)
|
395
|
+
tokens_ = [
|
396
|
+
{
|
397
|
+
"address": "0xE2035f04040A135c4dA2f96AcA742143c57c79F9",
|
398
|
+
"name": "UXUY",
|
399
|
+
"symbol": "uxuy"
|
400
|
+
},
|
401
|
+
{
|
402
|
+
"address": "0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9",
|
403
|
+
"name": "Tether",
|
404
|
+
"symbol": "usdt"
|
405
|
+
},
|
406
|
+
{
|
407
|
+
"address": "0x0000000000000000000000000000000000000000",
|
408
|
+
"name": "ETH",
|
409
|
+
"symbol": "ETH"
|
410
|
+
}
|
411
|
+
]
|
412
|
+
|
413
|
+
print(f'There are maximum {len(wallets)} wallets')
|
414
|
+
print(f'There are {len(tokens_)} tokens')
|
415
|
+
|
416
|
+
if wallets_distribute_similar:
|
417
|
+
wallets_batch = [wallets[:300], wallets[:300], wallets[:300]]
|
418
|
+
else:
|
419
|
+
wallets_batch = [wallets[:300], wallets[300:600], wallets[600:900]]
|
420
|
+
|
421
|
+
# Methodology 1: Query State Lib
|
422
|
+
start_time = time.time()
|
423
|
+
client_querier = ClientQuerier(provider_url=Networks.providers['arbitrum'])
|
424
|
+
starting_time_1 = time.time() - start_time
|
425
|
+
logger.info(f'Starting state querier: {round(starting_time_1, 3)}s')
|
426
|
+
|
427
|
+
_multiple(
|
428
|
+
n_times=n_times, starting_time=starting_time_1, filename='query_state_lib',
|
429
|
+
wallets=wallets_batch[0], wallets_batch_size=wallets_batch_size, tokens=tokens_, func=_test_state_querier,
|
430
|
+
client_querier=client_querier
|
431
|
+
)
|
432
|
+
|
433
|
+
# Methodology 2: Multicall
|
434
|
+
start_time = time.time()
|
435
|
+
_w3 = Web3(Web3.HTTPProvider(Networks.providers['arbitrum']))
|
436
|
+
starting_time_2_1 = time.time() - start_time
|
437
|
+
multicall_address = MulticallContract.get_multicall_contract(Chains.arbitrum)
|
438
|
+
multicall_contract = _w3.eth.contract(Web3.to_checksum_address(multicall_address), abi=MULTICALL_V3_ABI)
|
439
|
+
starting_time_2 = time.time() - start_time
|
440
|
+
logger.info(f'Starting multicall: {round(starting_time_2, 3)}s')
|
441
|
+
|
442
|
+
_multiple(
|
443
|
+
n_times=n_times, starting_time=starting_time_2, filename='multicall',
|
444
|
+
wallets=wallets_batch[1], wallets_batch_size=wallets_batch_size, tokens=tokens_, func=_test_multicall,
|
445
|
+
_w3=_w3, multicall_contract=multicall_contract
|
446
|
+
)
|
447
|
+
|
448
|
+
# Methodology 3: Combine Multicall with multiprocessing
|
449
|
+
starting_time_3 = starting_time_1 + starting_time_2_1
|
450
|
+
logger.info(f'Starting combined: {round(starting_time_3, 3)}s')
|
451
|
+
|
452
|
+
_multiple(
|
453
|
+
n_times=n_times, starting_time=starting_time_3, filename='combined',
|
454
|
+
wallets=wallets_batch[2], wallets_batch_size=wallets_batch_size, tokens=tokens_,
|
455
|
+
func=_test_multicall_with_multiprocessing, _w3=_w3, client_querier=client_querier
|
456
|
+
)
|
457
|
+
|
458
|
+
|
459
|
+
def _multiple(n_times, starting_time, filename, wallets, wallets_batch_size, tokens, func, *args, **kwargs):
|
460
|
+
data = [['Total Time', 'Encode Time']]
|
461
|
+
overview = {
|
462
|
+
'avg_total_time': 0, 'avg_encode_time': 0,
|
463
|
+
'times': 0, 'errors': 0,
|
464
|
+
'queries': 0, 'start_time': starting_time
|
465
|
+
}
|
466
|
+
for i in range(n_times):
|
467
|
+
idx = i * wallets_batch_size
|
468
|
+
sub_wallets = wallets[idx:idx + wallets_batch_size]
|
469
|
+
try:
|
470
|
+
total_time, encode_time, n_calls = func(wallets=sub_wallets, tokens=tokens, *args, **kwargs)
|
471
|
+
|
472
|
+
overview['queries'] = n_calls
|
473
|
+
data.append([total_time, encode_time])
|
474
|
+
overview['times'] += 1
|
475
|
+
except Exception as ex:
|
476
|
+
logger.exception(ex)
|
477
|
+
overview['errors'] += 1
|
478
|
+
finally:
|
479
|
+
time.sleep(13)
|
480
|
+
|
481
|
+
with open(f'results_{filename}.csv', 'w') as f:
|
482
|
+
writer = csv.writer(f)
|
483
|
+
writer.writerows(data)
|
484
|
+
|
485
|
+
overview['avg_total_time'] = sum([t[0] for t in data[1:]]) / overview['times']
|
486
|
+
overview['avg_encode_time'] = sum([t[1] for t in data[1:]]) / overview['times']
|
487
|
+
with open(f'overview_results_{filename}.json', 'w') as f:
|
488
|
+
json.dump(overview, f, indent=2)
|
489
|
+
|
490
|
+
|
491
|
+
if __name__ == '__main__':
|
492
|
+
query_multi_contracts(n_times=1, wallets_batch_size=1, wallets_distribute_similar=True)
|