def generate_cache_key(value): """ Generates a cache key for the *args and **kwargs """ if is_bytes(value): return hashlib.md5(value).hexdigest() elif is_text(value): return generate_cache_key(force_bytes(value)) elif is_boolean(value) or is_null(value) or is_number(value): return generate_cache_key(repr(value)) elif is_dict(value): return generate_cache_key(( (key, value[key]) for key in sorted(value.keys()) )) elif is_list_like(value) or isinstance(value, Generator): return generate_cache_key("".join(( generate_cache_key(item) for item in value ))) else: raise TypeError("Cannot generate cache key for value {0} of type {1}".format( value, type(value), ))
def find_matching_fn_abi(abi, fn_identifier=None, args=None, kwargs=None): filters = [] if fn_identifier: if fn_identifier is FallbackFn: return get_fallback_func_abi(abi) elif is_text(fn_identifier): filters.append(functools.partial(filter_by_name, fn_identifier)) else: raise TypeError("Unsupported function identifier") if args is not None or kwargs is not None: if args is None: args = tuple() if kwargs is None: kwargs = {} num_arguments = len(args) + len(kwargs) filters.extend([ functools.partial(filter_by_argument_count, num_arguments), functools.partial(filter_by_encodability, args, kwargs), ]) function_candidates = pipe(abi, *filters) if len(function_candidates) == 1: return function_candidates[0] if not function_candidates: raise ValueError("No matching functions found") else: raise ValueError("Multiple functions found")
def test_encode_text_string(string_value): encoder = TextStringEncoder() if not is_text(string_value): with pytest.raises(EncodingTypeError) as exception_info: encoder(string_value) assert 'TextStringEncoder' in str(exception_info.value) return string_value_as_bytes = codecs.encode(string_value, 'utf8') expected_value = ( encode_uint_256(len(string_value_as_bytes)) + ( zpad_right( string_value_as_bytes, ceil32(len(string_value_as_bytes)), ) if string_value else b'\x00' * 32 ) ) encoded_value = encoder(string_value) assert encoded_value == expected_value
def encode_transaction_data(abi, web3, fn_identifier, args=None, kwargs=None): if fn_identifier is FallbackFn: fn_abi, fn_selector, fn_arguments = get_fallback_function_info(abi) elif is_text(fn_identifier): fn_abi, fn_selector, fn_arguments = get_function_info( abi, fn_identifier, args, kwargs ) else: raise TypeError("Unsupported function identifier") return add_0x_prefix(encode_abi(web3, fn_abi, fn_arguments, fn_selector))
def find_matching_fn_abi(abi, fn_identifier=None, args=None, kwargs=None): args = args or tuple() kwargs = kwargs or dict() filters = [] num_arguments = len(args) + len(kwargs) if fn_identifier is FallbackFn: return get_fallback_func_abi(abi) if not is_text(fn_identifier): raise TypeError("Unsupported function identifier") name_filter = functools.partial(filter_by_name, fn_identifier) arg_count_filter = functools.partial(filter_by_argument_count, num_arguments) encoding_filter = functools.partial(filter_by_encodability, args, kwargs) filters.extend([ name_filter, arg_count_filter, encoding_filter, ]) function_candidates = pipe(abi, *filters) if len(function_candidates) == 1: return function_candidates[0] else: matching_identifiers = name_filter(abi) matching_function_signatures = [abi_to_signature(func) for func in matching_identifiers] arg_count_matches = len(arg_count_filter(matching_identifiers)) encoding_matches = len(encoding_filter(matching_identifiers)) if arg_count_matches == 0: diagnosis = "\nFunction invocation failed due to improper number of arguments." elif encoding_matches == 0: diagnosis = "\nFunction invocation failed due to no matching argument types." elif encoding_matches > 1: diagnosis = ( "\nAmbiguous argument encoding. " "Provided arguments can be encoded to multiple functions matching this call." ) message = ( "\nCould not identify the intended function with name `{name}`, " "positional argument(s) of type `{arg_types}` and " "keyword argument(s) of type `{kwarg_types}`." "\nFound {num_candidates} function(s) with the name `{name}`: {candidates}" "{diagnosis}" ).format( name=fn_identifier, arg_types=tuple(map(type, args)), kwarg_types=valmap(type, kwargs), num_candidates=len(matching_identifiers), candidates=matching_function_signatures, diagnosis=diagnosis, ) raise ValidationError(message)
def _set_function_info(self): self.abi = find_matching_fn_abi(self.contract_abi, self.function_identifier, self.args, self.kwargs) if self.function_identifier is FallbackFn: self.selector = encode_hex(b'') elif is_text(self.function_identifier): self.selector = encode_hex(function_abi_to_4byte_selector(self.abi)) else: raise TypeError("Unsupported function identifier") self.arguments = merge_args_and_kwargs(self.abi, self.args, self.kwargs)
def is_predefined_block_number(value): if is_text(value): value_text = value elif is_bytes(value): # `value` could either be random bytes or the utf-8 encoding of # one of the words in: {"latest", "pending", "earliest"} # We cannot decode the bytes as utf8, because random bytes likely won't be valid. # So we speculatively decode as 'latin-1', which cannot fail. value_text = value.decode('latin-1') elif is_integer(value): return False else: raise TypeError("unrecognized block reference: %r" % value) return value_text in {"latest", "pending", "earliest"}
def find_matching_fn_abi( abi: ABI, abi_codec: ABICodec, fn_identifier: Union[str, Type[FallbackFn], Type[ReceiveFn]] = None, args: Sequence[Any] = None, kwargs: Any = None, ) -> ABIFunction: args = args or tuple() kwargs = kwargs or dict() num_arguments = len(args) + len(kwargs) if fn_identifier is FallbackFn: return get_fallback_func_abi(abi) if fn_identifier is ReceiveFn: return get_receive_func_abi(abi) if not is_text(fn_identifier): raise TypeError("Unsupported function identifier") name_filter = functools.partial(filter_by_name, fn_identifier) arg_count_filter = functools.partial(filter_by_argument_count, num_arguments) encoding_filter = functools.partial(filter_by_encodability, abi_codec, args, kwargs) function_candidates = pipe(abi, name_filter, arg_count_filter, encoding_filter) if len(function_candidates) == 1: return function_candidates[0] else: matching_identifiers = name_filter(abi) matching_function_signatures = [ abi_to_signature(func) for func in matching_identifiers ] arg_count_matches = len(arg_count_filter(matching_identifiers)) encoding_matches = len(encoding_filter(matching_identifiers)) if arg_count_matches == 0: diagnosis = "\nFunction invocation failed due to improper number of arguments." elif encoding_matches == 0: diagnosis = "\nFunction invocation failed due to no matching argument types." elif encoding_matches > 1: diagnosis = ( "\nAmbiguous argument encoding. " "Provided arguments can be encoded to multiple functions matching this call." ) message = ( "\nCould not identify the intended function with name `{name}`, " "positional argument(s) of type `{arg_types}` and " "keyword argument(s) of type `{kwarg_types}`." "\nFound {num_candidates} function(s) with the name `{name}`: {candidates}" "{diagnosis}").format( name=fn_identifier, arg_types=tuple(map(type, args)), kwarg_types=valmap(type, kwargs), num_candidates=len(matching_identifiers), candidates=matching_function_signatures, diagnosis=diagnosis, ) raise ValidationError(message)
def validate_private_key(value): if not is_text(value) or not is_hex(value) or not len( remove_0x_prefix(value)) == 64: raise ValidationError( "Private keys must be 32 bytes encoded as hexidecimal")
def bytes32_contract(web3, Bytes32Contract, request, address_conversion_func): if is_text(request.param) and request.param[:2] != '0x': with pytest.warns(DeprecationWarning): return deploy(web3, Bytes32Contract, address_conversion_func, args=[request.param]) else: return deploy(web3, Bytes32Contract, address_conversion_func, args=[request.param])
def validate_text(value): if not is_text(value): raise ValidationError( "Value must be a text string. Got type: {}".format(type(value)))
def is_32byte_hex_string(value): return is_text(value) and is_hex(value) and len( remove_0x_prefix(value)) == 64
def validate_value(cls, value): if not is_text(value): cls.invalidate_value(value)
def validate_raw_transaction(raw_transaction): if not is_text(raw_transaction) or not is_hex(raw_transaction): raise ValidationError( "Raw Transaction must be a hexidecimal encoded string. Got: " "{}".format(raw_transaction) )
def validate_transaction(value, txn_type): if txn_type not in ALLOWED_TRANSACTION_TYPES: raise TypeError("the `txn_type` parameter must be one of send/call/estimate") if not is_dict(value): raise ValidationError("Transaction must be a dictionary. Got: {}".format(type(value))) unknown_keys = tuple(sorted(set(value.keys()).difference( TRANSACTION_TYPE_INFO[txn_type], ))) if unknown_keys: raise ValidationError( "Only the keys '{}' are allowed. Got extra keys: '{}'".format( "/".join(tuple(sorted(TRANSACTION_TYPE_INFO[txn_type]))), "/".join(unknown_keys), ) ) if txn_type == 'send': required_keys = {'from', 'gas'} elif txn_type == 'send_signed': required_keys = {'from', 'gas'} | SIGNED_TRANSACTION_KEYS elif txn_type in {'estimate', 'call'}: required_keys = {'from'} else: raise Exception("Invariant: code path should be unreachable") missing_required_keys = tuple(sorted(required_keys.difference(value.keys()))) if missing_required_keys: raise ValidationError( "Transaction is missing the required keys: '{}'".format( "/".join(missing_required_keys), ) ) if 'from' in value: validate_account(value['from']) if 'to' in value and value['to'] != '': validate_account(value['to']) elif 'to' in value and value['to'] == '': validate_text(value['to']) if 'gas' in value: validate_uint256(value['gas']) if 'gas_price' in value: validate_uint256(value['gas_price']) if 'value' in value: validate_uint256(value['value']) if 'nonce' in value: validate_uint256(value['nonce']) if 'data' in value: bad_data_message = ( "Transaction data must be a hexidecimal encoded string. Got: " "{}".format(value['data']) ) if not is_text(value['data']): raise ValidationError(bad_data_message) elif not remove_0x_prefix(value['data']): pass elif not is_hex(value['data']): raise ValidationError(bad_data_message) try: decode_hex(value['data']) except (binascii.Error, TypeError): # TypeError is for python2 # binascii.Error is for python3 raise ValidationError(bad_data_message) if txn_type == 'send_signed': validate_uint256(value['r']) validate_uint256(value['s']) validate_uint8(value['v'])
def key_encoder(key) -> bytes: if is_text(key): return to_bytes(text=key) return key
def process_type(raw_type): if not is_text(raw_type): raise TypeError("The type must be a text string. Got {0}".format(type(raw_type))) typ = normalize_type(raw_type) return process_strict_type(typ)
def validate_transaction(value, txn_internal_type): if txn_internal_type not in ALLOWED_TRANSACTION_INTERNAL_TYPES: raise TypeError( "the `txn_internal_type` parameter must be one of send/call/estimate" ) if not is_dict(value): raise ValidationError( "Transaction must be a dictionary. Got: {}".format(type(value))) unknown_keys = tuple( sorted( set(value.keys()).difference( TRANSACTION_INTERNAL_TYPE_INFO[txn_internal_type], ))) if unknown_keys: raise ValidationError( "Only the keys '{}' are allowed. Got extra keys: '{}'".format( "/".join( tuple( sorted( TRANSACTION_INTERNAL_TYPE_INFO[txn_internal_type])) ), "/".join(unknown_keys), )) if txn_internal_type == 'send': required_keys = {'from', 'gas'} elif txn_internal_type == 'send_signed': required_keys = {'from', 'gas'} | SIGNED_TRANSACTION_KEYS elif txn_internal_type in {'estimate', 'call'}: required_keys = {'from'} else: raise Exception("Invariant: code path should be unreachable") missing_required_keys = tuple( sorted(required_keys.difference(value.keys()))) if missing_required_keys: raise ValidationError( "Transaction is missing the required keys: '{}'".format( "/".join(missing_required_keys), )) if 'type' in value: # type is validated but not required. If this value exists, it will be popped out of the # dict and the type will instead be inferred from the transaction params. validate_transaction_type(value['type']) if 'from' in value: validate_account(value['from']) if 'to' in value and value['to'] != '': validate_account(value['to']) elif 'to' in value and value['to'] == '': validate_text(value['to']) if 'gas' in value: validate_uint256(value['gas']) if 'gas_price' in value: validate_uint256(value['gas_price']) if 'max_fee_per_gas' in value: validate_uint256(value['max_fee_per_gas']) if 'gas_price' in value: raise ValidationError( 'Mixed legacy and dynamic fee transaction values') if 'max_priority_fee_per_gas' in value: validate_uint256(value['max_priority_fee_per_gas']) if 'gas_price' in value: raise ValidationError( 'Mixed legacy and dynamic fee transaction values') if 'value' in value: validate_uint256(value['value']) if 'nonce' in value: validate_uint256(value['nonce']) if 'data' in value: bad_data_message = ( "Transaction 'data' must be a hexadecimal encoded string. Got: " "{}".format(value['data'])) if not is_text(value['data']): raise ValidationError(bad_data_message) elif not remove_0x_prefix(value['data']): pass elif not is_hex(value['data']): raise ValidationError(bad_data_message) try: decode_hex(value['data']) except (binascii.Error, TypeError): # TypeError is for python2 # binascii.Error is for python3 raise ValidationError(bad_data_message) if 'access_list' in value: _validate_inbound_access_list(value['access_list']) if txn_internal_type == 'send_signed': validate_uint256(value['r']) validate_uint256(value['s']) validate_uint8(value['v'])