defi-state-querier 0.4.29__py3-none-any.whl → 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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)