python interface for makerdao's multicall and a port of multicall.js
pip install multicall
-
Some interface changes; see below.
-
Removed default
Web3
instance. -
Batch sizes and number of workers specified by user; removed
NotSoBrightBatcher
. -
Multicalls are much more configurable.
-
A fallback to individual
eth_call
s if multicall keeps failing with out of gas errors. This provides handling for calls thatthrow()
. -
The package still uses asyncio+multiprocessing, but the workflow has changed:
- Calls are split into batches of
batch_size
. - Encoding is done in a (per
Multicall
object) process pool (depending on the value ofparallel_threshold
). - The web3 call is done in an asyncio loop in the main process.
- Decoding is done in a process pool (depending on the value of
parallel_threshold
). - The calls in failed batches are rebatched and rerun for
retries
times.
By default the multiprocessing
forkserver
start method is used to avoid high memory consumption. - Calls are split into batches of
from brownie import web3
from multicall import Call, Multicall
# assuming you are on kovan
MKR_TOKEN = '0xaaf64bfcc32d0f15873a02163e7e500671a4ffcd'
MKR_WHALE = '0xdb33dfd3d61308c33c63209845dad3e6bfb2c674'
MKR_FISH = '0x2dfcedcb401557354d0cf174876ab17bfd6f4efd'
def from_wei(value):
return value / 1e18
multi = Multicall(
[
Call(MKR_TOKEN, ['balanceOf(address)(uint256)', MKR_WHALE], [['whale', from_wei]]),
Call(MKR_TOKEN, ['balanceOf(address)(uint256)', MKR_FISH], [['fish', from_wei]]),
Call(MKR_TOKEN, 'totalSupply()(uint256)', [['supply', from_wei]]),
],
_w3=web3.web
)
multi() # {'whale': 566437.0921992733, 'fish': 7005.0, 'supply': 1000003.1220798912}
# seth-style calls
Call(MKR_TOKEN, ['balanceOf(address)(uint256)', MKR_WHALE], _w3=web3.web)()
Call(MKR_TOKEN, 'balanceOf(address)(uint256)', _w3=web3.web)(MKR_WHALE)
# return values processing
Call(MKR_TOKEN, 'totalSupply()(uint256)', [['supply', from_wei]], _w3=web3.web)()
signature
is a seth-style function signature offunction_name(input,types)(output,types)
. it also supports structs which need to be broken down to basic parts, e.g.(address,bytes)[]
.
use encode_data(args)
with input args to get the calldata. use decode_data(output)
with the output to decode the result.
target
is theto
address which is supplied toeth_call
.function
can be either seth-style signature ofmethod(input,types)(output,types)
or a list of[signature, *args]
.returns
is a list of[name, handler]
for return values. ifreturns
argument is omitted, you get a tuple, otherwise you get a dict. to skip processing of a value, passNone
as a handler._w3
is aWeb3
instance to be used for theeth_call
. IfNone
,Call(...)()
will raise aRuntimeError
.
use Call(...)()
with predefined args or Call(...)(args)
to reuse a prepared call with different args.
use decode_output(output)
with to decode the output and process it with returns
handlers.
Multicall(
calls: List[Call],
batch_size: Optional[int] = None,
block_id: Optional[int] = None,
gas_limit: int = 1 << 31,
retries: int = 3,
require_success: bool = True,
_w3: Optional[Web3] = None,
max_conns: int = 20,
max_workers: int = min(12, multiprocessing.cpu_count() - 1),
parallel_threshold: int = 1,
batch_timeout: int = 300,
)
calls
is a list ofCall
s with prepared values.batch_size
is the size of batches to give to the multicall contract.block_id
is the block number at which the multicall is executed.gas_limit
is the amount of gas (in wei) allocated to the multicall.retries
is the number of attempts to rerun failed calls.require_success
determines whether or not all calls must be successful._w3
is aWeb3
instance whose uri is to be used for the multicall. IfNone
,Multicall(...)()
will raise aRuntimeError
.max_conns
is the maximum number of connections used for the multicall.max_workers
is the number of processes used by the multicall encoding/decoding.parallel_threshold
is the number of calls above which parallel encoding/decoding is activated.batch_timeout
is the timeout for a single multicall batch.
use Multicall(...)()
to get the result of a prepared multicall.
- MULTICALL_DEBUG: if set, sets logging level for all library loggers to logging.DEBUG
Tests require a config file at tests/conf.json
.
The config file must have the following format:
{
"networks": {
"1": http://localhost,
...
}
}
To test the package, make sure pytest
is installed in your venv
, then run the following command:
python -m pytest
Some of the test multicalls are JSON serialized and GZIP-compressed (under the directory /tests/data
), so your mileage may vary dependening on OS.