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.
@@ -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)