def validate_call_args(node: vy_ast.Call, arg_count: Union[int, tuple], kwargs: Optional[list] = None) -> None: """ Validate positional and keyword arguments of a Call node. This function does not handle type checking of arguments, it only checks correctness of the number of arguments given and keyword names. Arguments --------- node : Call Vyper ast Call node to be validated. arg_count : int | tuple The required number of positional arguments. When given as a tuple the value is interpreted as the minimum and maximum number of arguments. kwargs : list, optional A list of valid keyword arguments. When arg_count is a tuple and the number of positional arguments exceeds the minimum, the excess values are considered to fill the first values on this list. Returns ------- None. Raises an exception when the arguments are invalid. """ if kwargs is None: kwargs = [] if not isinstance(node, vy_ast.Call): raise StructureException("Expected Call", node) if not isinstance(arg_count, (int, tuple)): raise CompilerPanic( f"Invalid type for arg_count: {type(arg_count).__name__}") if isinstance(arg_count, int) and len(node.args) != arg_count: raise ArgumentException( f"Invalid argument count: expected {arg_count}, got {len(node.args)}", node) elif (isinstance(arg_count, tuple) and not arg_count[0] <= len(node.args) <= arg_count[1]): raise ArgumentException( f"Invalid argument count: expected between " f"{arg_count[0]} and {arg_count[1]}, got {len(node.args)}", node, ) if not kwargs and node.keywords: raise ArgumentException("Keyword arguments are not accepted here", node.keywords[0]) for key in node.keywords: if key.arg is None: raise StructureException("Use of **kwargs is not supported", key.value) if key.arg not in kwargs: raise ArgumentException(f"Invalid keyword argument '{key.arg}'", key) if (isinstance(arg_count, tuple) and kwargs.index(key.arg) < len(node.args) - arg_count[0]): raise ArgumentException( f"'{key.arg}' was given as a positional argument", key)
def fetch_call_return(self, node: vy_ast.Call) -> Optional[BaseTypeDefinition]: if node.get( "func.value.id" ) == "self" and self.visibility == FunctionVisibility.EXTERNAL: raise CallViolation("Cannot call external functions via 'self'", node) # for external calls, include gas and value as optional kwargs kwarg_keys = self.kwarg_keys.copy() if node.get("func.value.id") != "self": kwarg_keys += list(self.call_site_kwargs.keys()) validate_call_args(node, (self.min_arg_count, self.max_arg_count), kwarg_keys) if self.mutability < StateMutability.PAYABLE: kwarg_node = next((k for k in node.keywords if k.arg == "value"), None) if kwarg_node is not None: raise CallViolation("Cannot send ether to nonpayable function", kwarg_node) for arg, expected in zip(node.args, self.arguments.values()): validate_expected_type(arg, expected) # TODO this should be moved to validate_call_args for kwarg in node.keywords: if kwarg.arg in self.call_site_kwargs: kwarg_settings = self.call_site_kwargs[kwarg.arg] validate_expected_type(kwarg.value, kwarg_settings.typ) if kwarg_settings.require_literal: if not isinstance(kwarg.value, vy_ast.Constant): raise InvalidType( f"{kwarg.arg} must be literal {kwarg_settings.typ}", kwarg.value) else: # Generate the modified source code string with the kwarg removed # as a suggestion to the user. kwarg_pattern = rf"{kwarg.arg}\s*=\s*{re.escape(kwarg.value.node_source_code)}" modified_line = re.sub(kwarg_pattern, kwarg.value.node_source_code, node.node_source_code) error_suggestion = ( f"\n(hint: Try removing the kwarg: `{modified_line}`)" if modified_line != node.node_source_code else "") raise ArgumentException( ("Usage of kwarg in Vyper is restricted to " + ", ".join([f"{k}=" for k in self.call_site_kwargs.keys()]) + f". {error_suggestion}"), kwarg, ) return self.return_type
def from_FunctionDef( cls, node: vy_ast.FunctionDef, is_interface: Optional[bool] = False, ) -> "ContractFunction": """ Generate a `ContractFunction` object from a `FunctionDef` node. Arguments --------- node : FunctionDef Vyper ast node to generate the function definition from. is_interface: bool, optional Boolean indicating if the function definition is part of an interface. Returns ------- ContractFunction """ kwargs: Dict[str, Any] = {} if is_interface: # FunctionDef with stateMutability in body (Interface defintions) if (len(node.body) == 1 and isinstance(node.body[0], vy_ast.Expr) and isinstance(node.body[0].value, vy_ast.Name) and StateMutability.is_valid_value(node.body[0].value.id)): # Interfaces are always public kwargs["function_visibility"] = FunctionVisibility.EXTERNAL kwargs["state_mutability"] = StateMutability( node.body[0].value.id) elif len(node.body) == 1 and node.body[0].get("value.id") in ( "constant", "modifying"): if node.body[0].value.id == "constant": expected = "view or pure" else: expected = "payable or nonpayable" raise StructureException( f"State mutability should be set to {expected}", node.body[0]) else: raise StructureException( "Body must only contain state mutability label", node.body[0]) else: # FunctionDef with decorators (normal functions) for decorator in node.decorator_list: if isinstance(decorator, vy_ast.Call): if "nonreentrant" in kwargs: raise StructureException( "nonreentrant decorator is already set with key: " f"{kwargs['nonreentrant']}", node, ) if decorator.get("func.id") != "nonreentrant": raise StructureException("Decorator is not callable", decorator) if len(decorator.args) != 1 or not isinstance( decorator.args[0], vy_ast.Str): raise StructureException( "@nonreentrant name must be given as a single string literal", decorator, ) kwargs["nonreentrant"] = decorator.args[0].value elif isinstance(decorator, vy_ast.Name): if FunctionVisibility.is_valid_value(decorator.id): if "function_visibility" in kwargs: raise FunctionDeclarationException( f"Visibility is already set to: {kwargs['function_visibility']}", node, ) kwargs["function_visibility"] = FunctionVisibility( decorator.id) elif StateMutability.is_valid_value(decorator.id): if "state_mutability" in kwargs: raise FunctionDeclarationException( f"Mutability is already set to: {kwargs['state_mutability']}", node) kwargs["state_mutability"] = StateMutability( decorator.id) else: if decorator.id == "constant": warnings.warn( "'@constant' decorator has been removed (see VIP2040). " "Use `@view` instead.", DeprecationWarning, ) raise FunctionDeclarationException( f"Unknown decorator: {decorator.id}", decorator) else: raise StructureException("Bad decorator syntax", decorator) if "function_visibility" not in kwargs: raise FunctionDeclarationException( f"Visibility must be set to one of: {', '.join(FunctionVisibility.values())}", node) if "state_mutability" not in kwargs: # Assume nonpayable if not set at all (cannot accept Ether, but can modify state) kwargs["state_mutability"] = StateMutability.NONPAYABLE # call arguments arg_count: Union[Tuple[int, int], int] = len(node.args.args) if node.args.defaults: arg_count = ( len(node.args.args) - len(node.args.defaults), len(node.args.args), ) arguments = OrderedDict() defaults = [None] * (len(node.args.args) - len(node.args.defaults)) + node.args.defaults namespace = get_namespace() for arg, value in zip(node.args.args, defaults): if arg.arg in ("gas", "value"): raise ArgumentException( f"Cannot use '{arg.arg}' as a variable name in a function input", arg, ) if arg.arg in arguments: raise ArgumentException( f"Function contains multiple inputs named {arg.arg}", arg) if arg.arg in namespace["self"].members: raise NamespaceCollision( "Name shadows an existing storage-scoped value", arg) if arg.arg in namespace: raise NamespaceCollision(arg.arg, arg) if arg.annotation is None: raise ArgumentException( f"Function argument '{arg.arg}' is missing a type", arg) type_definition = get_type_from_annotation( arg.annotation, location=DataLocation.CALLDATA, is_immutable=True) if isinstance( type_definition, StructDefinition) and type_definition.is_dynamic_size: # this is a temporary restriction and should be removed once support for dynamically # sized structs is implemented - https://github.com/vyperlang/vyper/issues/2190 raise ArgumentException( "Struct with dynamically sized data cannot be used as a function input", arg) if value is not None: if not check_constant(value): raise StateAccessViolation( "Value must be literal or environment variable", value) validate_expected_type(value, type_definition) arguments[arg.arg] = type_definition # return types if node.returns is None: return_type = None elif isinstance(node.returns, (vy_ast.Name, vy_ast.Call, vy_ast.Subscript)): return_type = get_type_from_annotation( node.returns, location=DataLocation.MEMORY) elif isinstance(node.returns, vy_ast.Tuple): tuple_types: Tuple = () for n in node.returns.elements: tuple_types += (get_type_from_annotation( n, location=DataLocation.MEMORY), ) return_type = TupleDefinition(tuple_types) else: raise InvalidType( "Function return value must be a type name or tuple", node.returns) return cls(node.name, arguments, arg_count, return_type, **kwargs)
def validate_call_args(node: vy_ast.Call, arg_count: Union[int, tuple], kwargs: Optional[list] = None) -> None: """ Validate positional and keyword arguments of a Call node. This function does not handle type checking of arguments, it only checks correctness of the number of arguments given and keyword names. Arguments --------- node : Call Vyper ast Call node to be validated. arg_count : int | tuple The required number of positional arguments. When given as a tuple the value is interpreted as the minimum and maximum number of arguments. kwargs : list, optional A list of valid keyword arguments. When arg_count is a tuple and the number of positional arguments exceeds the minimum, the excess values are considered to fill the first values on this list. Returns ------- None. Raises an exception when the arguments are invalid. """ if not isinstance(node, vy_ast.Call): raise StructureException("Expected Call", node) if not isinstance(arg_count, (int, tuple)): raise CompilerPanic( f"Invalid type for arg_count: {type(arg_count).__name__}") if isinstance(arg_count, tuple) and arg_count[0] == arg_count[1]: arg_count == arg_count[0] if isinstance(node.func, vy_ast.Attribute): msg = f" for call to '{node.func.attr}'" elif isinstance(node.func, vy_ast.Name): msg = f" for call to '{node.func.id}'" if isinstance(arg_count, int) and len(node.args) != arg_count: if not node.args: exc_node = node elif len(node.args) < arg_count: exc_node = node.args[-1] else: exc_node = node.args[arg_count] raise ArgumentException( f"Invalid argument count{msg}: expected {arg_count}, got {len(node.args)}", exc_node) if isinstance( arg_count, tuple) and not arg_count[0] <= len(node.args) <= arg_count[1]: if not node.args: exc_node = node elif len(node.args) < arg_count[0]: exc_node = node.args[-1] else: exc_node = node.args[arg_count[1]] raise ArgumentException( f"Invalid argument count{msg}: expected {arg_count[0]} " f"to {arg_count[1]}, got {len(node.args)}", exc_node, ) if kwargs is None: if node.keywords: raise ArgumentException("Keyword arguments are not accepted here", node.keywords[0]) return kwargs_seen = set() for key in node.keywords: if key.arg is None: raise StructureException("Use of **kwargs is not supported", key.value) if key.arg not in kwargs: raise ArgumentException(f"Invalid keyword argument '{key.arg}'", key) if key.arg in kwargs_seen: raise ArgumentException(f"Duplicate keyword argument '{key.arg}'", key) kwargs_seen.add(key.arg)
def parse_type(item, sigs, custom_structs, enums): # sigs: set of interface or contract names in scope # custom_structs: struct definitions in scope def _sanity_check(x): assert x, "typechecker missed this" def _parse_type(item): return parse_type(item, sigs, custom_structs, enums) def FAIL(): raise InvalidType(f"{item.id}", item) # Base and custom types, e.g. num if isinstance(item, vy_ast.Name): if item.id in BASE_TYPES: return BaseType(item.id) elif item.id in sigs: return InterfaceType(item.id) elif item.id in enums: return EnumType(item.id, enums[item.id].members.copy()) elif item.id in custom_structs: return make_struct_type(item.id, sigs, custom_structs[item.id], custom_structs, enums) else: FAIL() # pragma: notest # Units, e.g. num (1/sec) or contracts elif isinstance(item, vy_ast.Call) and isinstance(item.func, vy_ast.Name): # Contract_types if item.func.id == "address": if sigs and item.args[0].id in sigs: return InterfaceType(item.args[0].id) # Struct types elif item.func.id in custom_structs: return make_struct_type(item.id, sigs, custom_structs[item.id], custom_structs, enums) elif item.func.id == "immutable": if len(item.args) != 1: # is checked earlier but just for sanity, verify # immutable call is given only one argument raise ArgumentException("Invalid number of arguments to `immutable`", item) return BaseType(item.args[0].id) else: FAIL() # pragma: notest # Subscripts elif isinstance(item, vy_ast.Subscript): # Fixed size lists or bytearrays, e.g. num[100] if isinstance(item.slice.value, vy_ast.Int): length = item.slice.value.n _sanity_check(isinstance(length, int) and length > 0) # ByteArray if getattr(item.value, "id", None) == "Bytes": return ByteArrayType(length) elif getattr(item.value, "id", None) == "String": return StringType(length) # List else: value_type = _parse_type(item.value) return SArrayType(value_type, length) elif item.value.id == "DynArray": _sanity_check(isinstance(item.slice.value, vy_ast.Tuple)) length = item.slice.value.elements[1].n _sanity_check(isinstance(length, int) and length > 0) value_type_annotation = item.slice.value.elements[0] value_type = _parse_type(value_type_annotation) return DArrayType(value_type, length) elif item.value.id in ("HashMap",) and isinstance(item.slice.value, vy_ast.Tuple): # Mappings, e.g. HashMap[address, uint256] key_type = _parse_type(item.slice.value.elements[0]) value_type = _parse_type(item.slice.value.elements[1]) return MappingType(key_type, value_type) else: FAIL() elif isinstance(item, vy_ast.Tuple): member_types = [_parse_type(t) for t in item.elements] return TupleType(member_types) else: FAIL()
def from_FunctionDef( cls, node: vy_ast.FunctionDef, is_interface: Optional[bool] = False) -> "ContractFunction": """ Generate a `ContractFunction` object from a `FunctionDef` node. Arguments --------- node : FunctionDef Vyper ast node to generate the function definition from. is_interface: bool, optional Boolean indicating if the function definition is part of an interface. Returns ------- ContractFunction """ kwargs: Dict[str, Any] = {} if is_interface: # FunctionDef with stateMutability in body (Interface defintions) if (len(node.body) == 1 and isinstance(node.body[0], vy_ast.Expr) and isinstance(node.body[0].value, vy_ast.Name) and StateMutability.is_valid_value(node.body[0].value.id)): # Interfaces are always public kwargs["function_visibility"] = FunctionVisibility.EXTERNAL kwargs["state_mutability"] = StateMutability( node.body[0].value.id) elif len(node.body) == 1 and node.body[0].get("value.id") in ( "constant", "modifying"): if node.body[0].value.id == "constant": expected = "view or pure" else: expected = "payable or nonpayable" raise StructureException( f"State mutability should be set to {expected}", node.body[0]) else: raise StructureException( "Body must only contain state mutability label", node.body[0]) else: # FunctionDef with decorators (normal functions) for decorator in node.decorator_list: if isinstance(decorator, vy_ast.Call): if "nonreentrant" in kwargs: raise StructureException( "nonreentrant decorator is already set with key: " f"{kwargs['nonreentrant']}", node, ) if decorator.get("func.id") != "nonreentrant": raise StructureException("Decorator is not callable", decorator) if len(decorator.args) != 1 or not isinstance( decorator.args[0], vy_ast.Str): raise StructureException( "@nonreentrant name must be given as a single string literal", decorator) if node.name == "__init__": msg = "Nonreentrant decorator disallowed on `__init__`" raise FunctionDeclarationException(msg, decorator) kwargs["nonreentrant"] = decorator.args[0].value elif isinstance(decorator, vy_ast.Name): if FunctionVisibility.is_valid_value(decorator.id): if "function_visibility" in kwargs: raise FunctionDeclarationException( f"Visibility is already set to: {kwargs['function_visibility']}", node, ) kwargs["function_visibility"] = FunctionVisibility( decorator.id) elif StateMutability.is_valid_value(decorator.id): if "state_mutability" in kwargs: raise FunctionDeclarationException( f"Mutability is already set to: {kwargs['state_mutability']}", node) kwargs["state_mutability"] = StateMutability( decorator.id) else: if decorator.id == "constant": warnings.warn( "'@constant' decorator has been removed (see VIP2040). " "Use `@view` instead.", DeprecationWarning, ) raise FunctionDeclarationException( f"Unknown decorator: {decorator.id}", decorator) else: raise StructureException("Bad decorator syntax", decorator) if "function_visibility" not in kwargs: raise FunctionDeclarationException( f"Visibility must be set to one of: {', '.join(FunctionVisibility.values())}", node) if node.name == "__default__": if kwargs["function_visibility"] != FunctionVisibility.EXTERNAL: raise FunctionDeclarationException( "Default function must be marked as `@external`", node) if node.args.args: raise FunctionDeclarationException( "Default function may not receive any arguments", node.args.args[0]) if "state_mutability" not in kwargs: # Assume nonpayable if not set at all (cannot accept Ether, but can modify state) kwargs["state_mutability"] = StateMutability.NONPAYABLE if kwargs[ "state_mutability"] == StateMutability.PURE and "nonreentrant" in kwargs: raise StructureException( "Cannot use reentrancy guard on pure functions", node) # call arguments if node.args.defaults and node.name == "__init__": raise FunctionDeclarationException( "Constructor may not use default arguments", node.args.defaults[0]) arguments = OrderedDict() max_arg_count = len(node.args.args) min_arg_count = max_arg_count - len(node.args.defaults) defaults = [None] * min_arg_count + node.args.defaults namespace = get_namespace() for arg, value in zip(node.args.args, defaults): if arg.arg in ("gas", "value", "skip_contract_check", "default_return_value"): raise ArgumentException( f"Cannot use '{arg.arg}' as a variable name in a function input", arg) if arg.arg in arguments: raise ArgumentException( f"Function contains multiple inputs named {arg.arg}", arg) if arg.arg in namespace: raise NamespaceCollision(arg.arg, arg) if arg.annotation is None: raise ArgumentException( f"Function argument '{arg.arg}' is missing a type", arg) type_definition = get_type_from_annotation( arg.annotation, location=DataLocation.CALLDATA, is_constant=True) if value is not None: if not check_kwargable(value): raise StateAccessViolation( "Value must be literal or environment variable", value) validate_expected_type(value, type_definition) arguments[arg.arg] = type_definition # return types if node.returns is None: return_type = None elif node.name == "__init__": raise FunctionDeclarationException( "Constructor may not have a return type", node.returns) elif isinstance(node.returns, (vy_ast.Name, vy_ast.Call, vy_ast.Subscript)): return_type = get_type_from_annotation( node.returns, location=DataLocation.MEMORY) elif isinstance(node.returns, vy_ast.Tuple): tuple_types: Tuple = () for n in node.returns.elements: tuple_types += (get_type_from_annotation( n, location=DataLocation.MEMORY), ) return_type = TupleDefinition(tuple_types) else: raise InvalidType( "Function return value must be a type name or tuple", node.returns) return cls(node.name, arguments, min_arg_count, max_arg_count, return_type, **kwargs)
def empty(expr, context): if len(expr.args) != 1: raise ArgumentException('function expects two parameters.', expr) output_type = context.parse_type(expr.args[0], expr.args[0]) return LLLnode(None, typ=output_type, pos=getpos(expr))