From 325454496ee49e2f6bac385144caa07e4df29049 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Tue, 28 Oct 2025 02:17:47 -0400 Subject: [PATCH 1/5] feat(mypy): make Signature a Generic --- multicall/signature.py | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/multicall/signature.py b/multicall/signature.py index 08e7257..96ee93a 100644 --- a/multicall/signature.py +++ b/multicall/signature.py @@ -5,16 +5,15 @@ import faster_eth_abi.encoding import eth_hash.auto from eth_typing import Decodable, TypeStr +from faster_eth_abi.decoding import TupleDecoder +from faster_eth_abi.encoding import TupleEncoder _SIGNATURES: Final[Dict[str, "Signature"]] = {} -TupleEncoder: Final = faster_eth_abi.encoding.TupleEncoder -TupleDecoder: Final = faster_eth_abi.decoding.TupleDecoder - _keccak: Final = eth_hash.auto.keccak -_get_encoder: Final = faster_eth_abi.abi.default_codec._registry.get_encoder -_get_decoder: Final = faster_eth_abi.abi.default_codec._registry.get_decoder +_get_tuple_encoder: Final = faster_eth_abi.abi.default_codec._registry.get_tuple_encoder +_get_tuple_decoder: Final = faster_eth_abi.abi.default_codec._registry.get_tuple_decoder _stream_cls: Final = faster_eth_abi.abi.default_codec.stream_class @@ -95,7 +94,7 @@ def _get_signature(signature: str) -> "Signature": @final -class Signature: +class Signature(Generic[T]): __slots__ = ( "signature", "function", @@ -108,24 +107,16 @@ class Signature: def __init__(self, signature: str) -> None: self.signature: Final = signature - parsed = parse_signature(signature) - self.function: Final = parsed[0] - input_types = parsed[1] + function, input_types, output_types = parse_signature(signature) + self.function: Final = function self.input_types: Final = input_types - output_types = parsed[2] self.output_types: Final = output_types self.fourbyte: Final = get_4byte_selector(self.function) - self._encoder: Final = ( - TupleEncoder(encoders=tuple(_get_encoder(type_str) for type_str in input_types)) - if input_types - else None - ) - self._decoder: Final = TupleDecoder( - decoders=tuple(_get_decoder(type_str) for type_str in output_types) - ) + self._encoder: Final = _get_tuple_encoder(input_types) if input_types else None + self._decoder: Final[TupleDecoder[T]] = _get_tuple_decoder(output_types) if output_types else None def encode_data(self, args: Optional[Union[List[Any], Tuple[Any, ...]]] = None) -> bytes: - return self.fourbyte + self._encoder(args) if args else self.fourbyte # type: ignore [misc] + return self.fourbyte + self._encoder(args) if args else self.fourbyte - def decode_data(self, output: Decodable) -> Any: + def decode_data(self, output: Decodable) -> Tuple[T, ...]: return self._decoder(_stream_cls(output)) From 34d6c722fb9267d30920de12d962b36a813a0289 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 28 Oct 2025 06:18:11 +0000 Subject: [PATCH 2/5] chore: `black .` --- multicall/signature.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/multicall/signature.py b/multicall/signature.py index 96ee93a..2827373 100644 --- a/multicall/signature.py +++ b/multicall/signature.py @@ -113,7 +113,9 @@ def __init__(self, signature: str) -> None: self.output_types: Final = output_types self.fourbyte: Final = get_4byte_selector(self.function) self._encoder: Final = _get_tuple_encoder(input_types) if input_types else None - self._decoder: Final[TupleDecoder[T]] = _get_tuple_decoder(output_types) if output_types else None + self._decoder: Final[TupleDecoder[T]] = ( + _get_tuple_decoder(output_types) if output_types else None + ) def encode_data(self, args: Optional[Union[List[Any], Tuple[Any, ...]]] = None) -> bytes: return self.fourbyte + self._encoder(args) if args else self.fourbyte From 4cbc924d3d4c1422e637eb984e11bd182df7f399 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Tue, 28 Oct 2025 02:27:24 -0400 Subject: [PATCH 3/5] Update signature.py --- multicall/signature.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/multicall/signature.py b/multicall/signature.py index 2827373..3addcae 100644 --- a/multicall/signature.py +++ b/multicall/signature.py @@ -117,6 +117,12 @@ def __init__(self, signature: str) -> None: _get_tuple_decoder(output_types) if output_types else None ) + @classmethod + # TODO: overload this for common output types + def from_parts(cls, function: str, input_types: Iterable[TypeStr], output_types: Iterable[TypeStr]) -> "Signature[Any]": + # This classmethod exists to help you generate type-aware Signature objects from Literal type strs + return Signature(f"{function}({','.join(input_types)})({','.join(output_types)})") + def encode_data(self, args: Optional[Union[List[Any], Tuple[Any, ...]]] = None) -> bytes: return self.fourbyte + self._encoder(args) if args else self.fourbyte From f2798365d0eeef709751a260c6232e99e1607f7d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 28 Oct 2025 06:27:38 +0000 Subject: [PATCH 4/5] chore: `black .` --- multicall/signature.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/multicall/signature.py b/multicall/signature.py index 3addcae..1451c5b 100644 --- a/multicall/signature.py +++ b/multicall/signature.py @@ -119,7 +119,9 @@ def __init__(self, signature: str) -> None: @classmethod # TODO: overload this for common output types - def from_parts(cls, function: str, input_types: Iterable[TypeStr], output_types: Iterable[TypeStr]) -> "Signature[Any]": + def from_parts( + cls, function: str, input_types: Iterable[TypeStr], output_types: Iterable[TypeStr] + ) -> "Signature[Any]": # This classmethod exists to help you generate type-aware Signature objects from Literal type strs return Signature(f"{function}({','.join(input_types)})({','.join(output_types)})") From b1a32d487369eca39c3086ec8a60484fd35e9759 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Tue, 28 Oct 2025 02:39:51 -0400 Subject: [PATCH 5/5] Update call.py --- multicall/call.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/multicall/call.py b/multicall/call.py index e9fa93a..a03dadd 100644 --- a/multicall/call.py +++ b/multicall/call.py @@ -77,11 +77,20 @@ def __repr__(self) -> str: def data(self) -> bytes: return self.signature.encode_data(self.args) + @overload @staticmethod def decode_output( output: Decodable, signature: Signature, - returns: Optional[Sequence[Tuple[Any, Optional[Callable]]]] = None, + returns: Sequence[Tuple[TIdent, Optional[Callable[[Any], TReturn]]]], + success: Optional[bool] = None, + ) -> Dict[TIdent, TReturn]: ... + + @staticmethod + def decode_output( + output: Decodable, + signature: Signature, + returns: Optional[Sequence[Tuple[Any, Optional[Callable[[Any], Any]]]]] = None, success: Optional[bool] = None, ) -> Any: @@ -94,9 +103,10 @@ def decode_output( try: decoded = signature.decode_data(output) except: - success, decoded = False, [None] * (len(returns) if returns else 1) + success = False + decoded = tuple(None for i in len(returns)) if returns else (None,) else: - decoded = [None] * (len(returns) if returns else 1) + decoded = tuple(None for i in len(returns)) if returns else (None,) if returns: return {