def merge_args_and_kwargs(function_abi: ABIFunction, args: Sequence[Any], kwargs: Dict[str, Any]) -> Tuple[Any, ...]: """ Takes a list of positional args (``args``) and a dict of keyword args (``kwargs``) defining values to be passed to a call to the contract function described by ``function_abi``. Checks to ensure that the correct number of args were given, no duplicate args were given, and no unknown args were given. Returns a list of argument values aligned to the order of inputs defined in ``function_abi``. """ # Ensure the function is being applied to the correct number of args if len(args) + len(kwargs) != len(function_abi.get('inputs', [])): raise TypeError( f"Incorrect argument count. Expected '{len(function_abi['inputs'])}" f". Got '{len(args) + len(kwargs)}'") # If no keyword args were given, we don't need to align them if not kwargs: return cast(Tuple[Any, ...], args) kwarg_names = set(kwargs.keys()) sorted_arg_names = tuple(arg_abi['name'] for arg_abi in function_abi['inputs']) args_as_kwargs = dict(zip(sorted_arg_names, args)) # Check for duplicate args duplicate_args = kwarg_names.intersection(args_as_kwargs.keys()) if duplicate_args: raise TypeError( f"{function_abi.get('name')}() got multiple values for argument(s) " f"'{', '.join(duplicate_args)}'") # Check for unknown args unknown_args = kwarg_names.difference(sorted_arg_names) if unknown_args: if function_abi.get('name'): raise TypeError( f"{function_abi.get('name')}() got unexpected keyword argument(s)" f" '{', '.join(unknown_args)}'") raise TypeError( f"Type: '{function_abi.get('type')}' got unexpected keyword argument(s)" f" '{', '.join(unknown_args)}'") # Sort args according to their position in the ABI and unzip them from their # names sorted_args = tuple( zip(*sorted( itertools.chain(kwargs.items(), args_as_kwargs.items()), key=lambda kv: sorted_arg_names.index(kv[0]), ))) if sorted_args: return sorted_args[1] else: return tuple()
def get_aligned_abi_inputs( abi: ABIFunction, args: Union[Tuple[Any, ...], Mapping[Any, Any]] ) -> Tuple[Tuple[Any, ...], Tuple[Any, ...]]: """ Takes a function ABI (``abi``) and a sequence or mapping of args (``args``). Returns a list of type strings for the function's inputs and a list of arguments which have been aligned to the layout of those types. The args contained in ``args`` may contain nested mappings or sequences corresponding to tuple-encoded values in ``abi``. """ input_abis = abi.get('inputs', []) if isinstance(args, abc.Mapping): # `args` is mapping. Align values according to abi order. args = tuple(args[abi['name']] for abi in input_abis) return ( # typed dict cannot be used w/ a normal Dict # https://github.com/python/mypy/issues/4976 tuple(collapse_if_tuple(abi) for abi in input_abis), # type: ignore type(args)( _align_abi_input(abi, arg) for abi, arg in zip(input_abis, args) ), )
def check_if_arguments_can_be_encoded( function_abi: ABIFunction, abi_codec: codec.ABIEncoder, args: Sequence[Any], kwargs: Dict[str, Any], ) -> bool: try: arguments = merge_args_and_kwargs(function_abi, args, kwargs) except TypeError: return False if len(function_abi.get('inputs', [])) != len(arguments): return False try: types, aligned_args = get_aligned_abi_inputs(function_abi, arguments) except TypeError: return False return all( abi_codec.is_encodable(_type, arg) for _type, arg in zip(types, aligned_args))