Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multicall #1743

Merged
merged 5 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions brownie/network/multicall.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from brownie.network import accounts, web3
from brownie.network.contract import Contract, ContractCall
from brownie.project import compile_source
from brownie.utils import color

DATA_DIR = BROWNIE_FOLDER.joinpath("data")
MULTICALL2_ABI = json.loads(DATA_DIR.joinpath("interfaces", "Multicall2.json").read_text())
Expand All @@ -24,6 +25,7 @@ class Call:

calldata: Tuple[str, bytes]
decoder: FunctionType
readable: str


class Result(ObjectProxy):
Expand All @@ -47,7 +49,9 @@ class Multicall:

def __init__(self) -> None:
self.address = None
self.default_verbose = False
self._block_number = defaultdict(lambda: None) # type: ignore
self._verbose = defaultdict(lambda: None) # type: ignore
self._contract = None
self._pending_calls: Dict[int, List[Call]] = defaultdict(list)

Expand All @@ -61,10 +65,14 @@ def block_number(self) -> int:
return self._block_number[get_ident()]

def __call__(
self, address: Optional[str] = None, block_identifier: Union[str, bytes, int, None] = None
self,
address: Optional[str] = None,
block_identifier: Union[str, bytes, int, None] = None,
verbose: Optional[bool] = None,
) -> "Multicall":
self.address = address # type: ignore
self._block_number[get_ident()] = block_identifier # type: ignore
self._verbose[get_ident()] = verbose if verbose is not None else self.default_verbose
return self

def _flush(self, future_result: Result = None) -> Any:
Expand All @@ -76,6 +84,18 @@ def _flush(self, future_result: Result = None) -> Any:
# or this result has already been retrieved
return future_result
with self._lock:
if self._verbose.get(get_ident(), self.default_verbose):
message = (
"Multicall:"
f"\n Thread ID: {get_ident()}"
f"\n Block number: {self._block_number[get_ident()]}"
f"\n Calls: {len(pending_calls)}"
)
for c, item in enumerate(pending_calls, start=1):
u = "\u2514" if c == len(pending_calls) else "\u251c"
message = f"{message}\n {u}\u2500{item.readable}"
print(color.highlight(f"{message}\n"))

ContractCall.__call__.__code__ = getattr(ContractCall, "__original_call_code")
results = self._contract.tryAggregate( # type: ignore
False,
Expand All @@ -96,7 +116,8 @@ def flush(self) -> Any:
def _call_contract(self, call: ContractCall, *args: Tuple, **kwargs: Dict[str, Any]) -> Proxy:
"""Add a call to the buffer of calls to be made"""
calldata = (call._address, call.encode_input(*args, **kwargs)) # type: ignore
call_obj = Call(calldata, call.decode_output) # type: ignore
readable = f"{call._name}({', '.join(str(i) for i in args)})"
call_obj = Call(calldata, call.decode_output, readable) # type: ignore
# future result
result = Result(call_obj)
self._pending_calls[get_ident()].append(result)
Expand Down Expand Up @@ -150,6 +171,9 @@ def __exit__(self, exc_type: Exception, exc_val: Any, exc_tb: TracebackType) ->
self.flush()
getattr(ContractCall, "__multicall")[get_ident()] = None

self._block_number.pop(get_ident(), None)
self._verbose.pop(get_ident(), None)

@staticmethod
def deploy(tx_params: Dict) -> Contract:
"""Deploy an instance of the `Multicall2` contract.
Expand Down
15 changes: 15 additions & 0 deletions docs/api-network.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1900,6 +1900,21 @@ Multicall Attributes
... brownie.multicall.block_number
12733683

.. py:attribute:: Multicall.default_verbose

Default verbosity setting for multicall. Set to ``False`` by default. If set to ``True``, the content of each batched call is printed to the console. This is useful for debugging, to ensure a multicall is performing as expected.

.. code-block:: python

>>> multicall.default_verbose = True

You can also enable verbosity for individual multicalls by setting the `verbose` keyword:

.. code-block:: python

>>> with brownie.multicall(verbose=True):
...

Multicall Methods
*****************

Expand Down
Loading