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