@@ -31,9 +31,9 @@ class MultiCall:
31
31
CALLER_ADDRESS = "0x0000000000000000000000000000000000000123"
32
32
33
33
MULTICALL_DEPLOYMENTS : dict [int , str ] = {
34
- 56 : "0xDD020f51961febA6C989b1865c44f3bFcEfCA58d " ,
35
- 1116 : "0x2De75065f4161c797d7168cE63CBc0261FE1ccF9 " ,
36
- 40 : "0xCA4E8AC62E4A6D26e34039E5ded68dcf9d38bEea " ,
34
+ 56 : "0xE396FfB7aa3123E3D742e50Ad409d212f87ADfA6 " ,
35
+ 1116 : "0xf7bfc56C50e544ba035d0b27978aF2f072b096f7 " ,
36
+ 40 : "0x4E1F64D55cD51E8D8D2125A5c1Fa613E78921C51 " ,
37
37
}
38
38
39
39
@classmethod
@@ -74,6 +74,10 @@ def add_undeployed_contract_call(self, contract_func: ContractFunction):
74
74
self .calls .append (contract_func )
75
75
76
76
def call (self , use_revert : Optional [bool ] = None , batch_size : int = 1_000 ):
77
+ results , _ = self .call_with_gas (use_revert = use_revert , batch_size = batch_size )
78
+ return results
79
+
80
+ def call_with_gas (self , use_revert : Optional [bool ] = None , batch_size : int = 1_000 ):
77
81
if use_revert is None :
78
82
use_revert = self .w3 .revert_reason_available
79
83
@@ -87,20 +91,23 @@ def _inner_call(
87
91
use_revert : bool ,
88
92
calls_with_calldata : list [tuple [ContractFunction , bytes ]],
89
93
batch_size : int
90
- ):
94
+ ) -> tuple [ list [ Exception | tuple [ any , ...]], list [ int ]] :
91
95
kwargs = dict (
92
96
use_revert = use_revert ,
93
97
batch_size = batch_size ,
94
98
)
95
99
# make sure calls are not bigger than batch_size
96
100
if len (calls_with_calldata ) > batch_size :
97
- results = []
101
+ results_combined = []
102
+ gas_usages_combined = []
98
103
for start in range (0 , len (calls_with_calldata ), batch_size ):
99
- results + = self ._inner_call (
104
+ results , gas_usages = self ._inner_call (
100
105
** kwargs ,
101
106
calls_with_calldata = calls_with_calldata [start : min (start + batch_size , len (calls_with_calldata ))],
102
107
)
103
- return results
108
+ results_combined += results
109
+ gas_usages_combined += gas_usages
110
+ return results_combined , gas_usages_combined
104
111
105
112
if self .multicall .address is None :
106
113
multicall_call = self ._build_constructor_calldata (
@@ -111,8 +118,9 @@ def _inner_call(
111
118
multicall_call = self ._build_calldata (
112
119
calls_with_calldata = calls_with_calldata
113
120
)
121
+
114
122
try :
115
- raw_returns = self ._call_multicall (
123
+ raw_returns , gas_usages = self ._call_multicall (
116
124
multicall_call = multicall_call ,
117
125
retry = len (calls_with_calldata ) == 1
118
126
)
@@ -122,14 +130,15 @@ def _inner_call(
122
130
sleep (1 )
123
131
return self ._inner_call (** kwargs , calls_with_calldata = calls_with_calldata )
124
132
print (f"Multicall got Exception '{ repr (e )} ', splitting and retrying" )
125
- left_results = self ._inner_call (** kwargs , calls_with_calldata = calls_with_calldata [:len (calls_with_calldata ) // 2 ])
126
- right_results = self ._inner_call (** kwargs , calls_with_calldata = calls_with_calldata [len (calls_with_calldata ) // 2 :])
127
- return left_results + right_results
133
+ left_results , left_gas_usages = self ._inner_call (** kwargs , calls_with_calldata = calls_with_calldata [:len (calls_with_calldata ) // 2 ])
134
+ right_results , right_gas_usages = self ._inner_call (** kwargs , calls_with_calldata = calls_with_calldata [len (calls_with_calldata ) // 2 :])
135
+ return left_results + right_results , left_gas_usages + right_gas_usages
128
136
results = self .decode_contract_function_results (raw_returns = raw_returns , contract_functions = [call for call , _ in calls_with_calldata ])
129
137
if len (results ) == len (calls_with_calldata ):
130
- return results
138
+ return results , gas_usages
131
139
# if not all calls were executed, recursively execute remaining calls and concatenate results
132
- return results + self ._inner_call (** kwargs , calls_with_calldata = calls_with_calldata [len (results ):])
140
+ right_results , right_gas_usages = self ._inner_call (** kwargs , calls_with_calldata = calls_with_calldata [len (results ):])
141
+ return results + right_results , gas_usages + right_gas_usages
133
142
134
143
@staticmethod
135
144
def calculate_expected_contract_address (sender : str , nonce : int ):
@@ -252,41 +261,48 @@ def _build_constructor_calldata(
252
261
return multicall_call
253
262
254
263
@staticmethod
255
- def _decode_muilticall (multicall_result : bytes | list [tuple [bool , int , bytes ]]) -> list [str | Exception ]:
264
+ def _decode_muilticall (
265
+ multicall_result : bytes | list [tuple [bool , int , bytes ]]
266
+ ) -> tuple [list [str | Exception ], list [int ]]:
256
267
raw_returns : list [str or Exception ] = []
268
+ gas_usages : list [int ] = []
257
269
258
270
if isinstance (multicall_result , list ) or isinstance (multicall_result , tuple ):
259
271
# deployed multicall
260
- for sucess , _ , raw_return in multicall_result :
272
+ for sucess , gas_usage , raw_return in multicall_result :
261
273
if not sucess :
262
274
decoded = MultiCall .get_revert_reason (raw_return )
263
275
raw_return = ContractLogicError (f"execution reverted: { decoded } " )
264
276
raw_returns .append (raw_return )
265
- return raw_returns
277
+ gas_usages .append (gas_usage )
278
+ return raw_returns , gas_usages
266
279
267
280
# undeployed multicall
268
281
# decode returned data into segments
269
282
multicall_result_copy = multicall_result [:]
270
283
raw_returns_encoded = []
271
284
while len (multicall_result_copy ) != 0 :
272
285
data_len = int .from_bytes (multicall_result_copy [:2 ], byteorder = 'big' )
273
- raw_returns_encoded .append (multicall_result_copy [2 :data_len + 2 ])
274
- multicall_result_copy = multicall_result_copy [data_len + 2 :]
286
+ raw_returns_encoded .append (multicall_result_copy [2 :data_len ])
287
+ multicall_result_copy = multicall_result_copy [data_len :]
275
288
276
289
# decode returned data for each call
277
290
for raw_return_encoded in raw_returns_encoded :
278
291
try :
279
292
# we are using packed encoding to decrease size of return data, if not we could have used
280
293
# success, raw_return = eth_abi.decode(['bool', 'bytes'], raw_return_encoded)
281
294
success = raw_return_encoded [0 ] == 1
282
- raw_return = raw_return_encoded [1 :]
295
+ gas_usage = int (raw_return_encoded [1 :5 ].hex (), 16 )
296
+ raw_return = raw_return_encoded [5 :]
283
297
if not success :
284
298
decoded = MultiCall .get_revert_reason (raw_return )
285
299
raw_return = ContractLogicError (f"execution reverted: { decoded } " )
286
300
except Exception as e :
287
301
raw_return = e
302
+ gas_usage = None
303
+ gas_usages .append (gas_usage )
288
304
raw_returns .append (raw_return )
289
- return raw_returns
305
+ return raw_returns , gas_usages
290
306
291
307
@staticmethod
292
308
def get_revert_reason (revert_bytes : bytes ) -> str :
@@ -398,8 +414,8 @@ def main(
398
414
# calling a deployed contract
399
415
multicall .add_call (usdt_contract .functions .decimals ())
400
416
401
- multicall_result = multicall .call ()
402
- print (multicall_result )
417
+ multicall_results , gas_usages = multicall .call_with_gas ()
418
+ print (list ( zip ( multicall_results , gas_usages )) )
403
419
404
420
405
421
if __name__ == "__main__" :
0 commit comments