def visit_Return(self, node): values = node.value if values is None: if self.func.return_type: raise FunctionDeclarationException( "Return statement is missing a value", node) return elif self.func.return_type is None: raise FunctionDeclarationException( "Function does not return any values", node) if isinstance(values, vy_ast.Tuple): values = values.elements if not isinstance(self.func.return_type, TupleDefinition): raise FunctionDeclarationException( "Function only returns a single value", node) if self.func.return_type.length != len(values): raise FunctionDeclarationException( f"Incorrect number of return values: " f"expected {self.func.return_type.length}, got {len(values)}", node, ) for given, expected in zip(values, self.func.return_type.value_type): validate_expected_type(given, expected) else: validate_expected_type(values, self.func.return_type) self.expr_visitor.visit(node.value)
def fetch_call_return(self, node: vy_ast.Call) -> StructDefinition: validate_call_args(node, 1) if not isinstance(node.args[0], vy_ast.Dict): raise VariableDeclarationException( "Struct values must be declared via dictionary", node.args[0]) if next( (i for i in self.members.values() if isinstance(i, MappingDefinition)), False, ): raise VariableDeclarationException( "Struct contains a mapping and so cannot be declared as a literal", node) members = self.members.copy() for key, value in zip(node.args[0].keys, node.args[0].values): if key is None or key.get("id") not in members: raise UnknownAttribute("Unknown or duplicate struct member", key or value) validate_expected_type(value, members.pop(key.id)) if members: raise VariableDeclarationException( f"Struct declaration does not define all fields: {', '.join(list(members))}", node, ) return StructDefinition(self._id, self.members)
def visit_If(self, node): validate_expected_type(node.test, BoolDefinition()) with self.namespace.enter_scope(): for n in node.body: self.visit(n) with self.namespace.enter_scope(): for n in node.orelse: self.visit(n)
def _validate_single(self, arg, expected_type): # TODO using "TYPE_DEFINITION" is a kludge in derived classes, # refactor me. if expected_type == "TYPE_DEFINITION": # try to parse the type - call get_type_from_annotation # for its side effects (will throw if is not a type) get_type_from_annotation(arg, DataLocation.UNSET) else: validate_expected_type(arg, expected_type)
def visit_Assert(self, node): if node.msg: _validate_revert_reason(node.msg) try: validate_expected_type(node.test, BoolDefinition()) except InvalidType: raise InvalidType("Assertion test value must be a boolean", node.test)
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 fetch_call_return(self, node: vy_ast.Call) -> Optional[BaseTypeDefinition]: validate_call_args(node, len(self.arg_types)) assert len(node.args) == len( self.arg_types) # validate_call_args postcondition for arg, expected_type in zip(node.args, self.arg_types): # CMC 2022-04-01 this should probably be in the validation module validate_expected_type(arg, expected_type) return self.return_type
def visit_AugAssign(self, node): if isinstance(node.value, vy_ast.Tuple): raise StructureException( "Right-hand side of assignment cannot be a tuple", node.value) target = get_exact_type_from_node(node.target) validate_expected_type(node.value, target) if self.func.mutability <= StateMutability.VIEW and target.location == DataLocation.STORAGE: raise StateAccessViolation( f"Cannot modify storage in a {self.func.mutability.value} function", node) target.validate_modification(node)
def visit_AugAssign(self, node): if isinstance(node.value, vy_ast.Tuple): raise StructureException( "Right-hand side of assignment cannot be a tuple", node.value) target = get_exact_type_from_node(node.target) validate_expected_type(node.value, target) target.validate_modification(node, self.func.mutability) self.expr_visitor.visit(node.value)
def _validate_revert_reason(msg_node: vy_ast.VyperNode) -> None: if msg_node: if isinstance(msg_node, vy_ast.Str): if not msg_node.value.strip(): raise StructureException("Reason string cannot be empty", msg_node) elif not (isinstance(msg_node, vy_ast.Name) and msg_node.id == "UNREACHABLE"): try: validate_expected_type(msg_node, StringDefinition(1024)) except TypeMismatch as e: raise InvalidType( "revert reason must fit within String[1024]") from e
def visit_AnnAssign(self, node): name = node.get("target.id") if name is None: raise VariableDeclarationException("Invalid assignment", node) if not node.value: raise VariableDeclarationException( "Memory variables must be declared with an initial value", node) type_definition = get_type_from_annotation(node.annotation, DataLocation.MEMORY) validate_expected_type(node.value, type_definition) try: self.namespace[name] = type_definition except VyperException as exc: raise exc.with_annotation(node) from None
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("Cannnot 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 += ["gas", "value"] 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("Cannnot send ether to nonpayable function", kwarg_node) for arg, expected in zip(node.args, self.arguments.values()): validate_expected_type(arg, expected) for kwarg in node.keywords: if kwarg.arg in ("gas", "value"): validate_expected_type(kwarg.value, Uint256Definition()) else: validate_expected_type(kwarg.arg, kwarg.value) return self.return_type
def fetch_call_return(self, node: vy_ast.Call) -> StructDefinition: validate_call_args(node, 1) if not isinstance(node.args[0], vy_ast.Dict): raise VariableDeclarationException( "Struct values must be declared via dictionary", node.args[0]) if next((i for i in self.members.values() if isinstance(i, MappingDefinition)), False): raise VariableDeclarationException( "Struct contains a mapping and so cannot be declared as a literal", node) members = self.members.copy() keys = list(self.members.keys()) for i, (key, value) in enumerate(zip(node.args[0].keys, node.args[0].values)): if key is None or key.get("id") not in members: suggestions_str = get_levenshtein_error_suggestions( key.get("id"), members, 1.0) raise UnknownAttribute( f"Unknown or duplicate struct member. {suggestions_str}", key or value) expected_key = keys[i] if key.id != expected_key: raise InvalidAttribute( "Struct keys are required to be in order, but got " f"`{key.id}` instead of `{expected_key}`. (Reminder: the " f"keys in this struct are {list(self.members.items())})", key, ) validate_expected_type(value, members.pop(key.id)) if members: raise VariableDeclarationException( f"Struct declaration does not define all fields: {', '.join(list(members))}", node) return StructDefinition(self._id, self.members)
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 visit_AnnAssign(self, node): name = node.get("target.id") if name is None: raise VariableDeclarationException( "Invalid module-level assignment", node) if name == "implements": interface_name = node.annotation.id self.namespace[interface_name].validate_implements(node) return is_immutable, is_public = False, False annotation = node.annotation if isinstance(annotation, vy_ast.Call): # the annotation is a function call, e.g. `foo: constant(uint256)` call_name = annotation.get("func.id") if call_name in ("constant", "public"): validate_call_args(annotation, 1) if call_name == "constant": # declaring a constant is_immutable = True elif call_name == "public": # declaring a public variable is_public = True # generate function type and add to metadata # we need this when builing the public getter node._metadata[ "func_type"] = ContractFunction.from_AnnAssign(node) # remove the outer call node, to handle cases such as `public(map(..))` annotation = annotation.args[0] type_definition = get_type_from_annotation(annotation, DataLocation.STORAGE, is_immutable, is_public) node._metadata["type"] = type_definition if is_immutable: if not node.value: raise VariableDeclarationException( "Constant must be declared with a value", node) if not check_literal(node.value): raise StateAccessViolation("Value must be a literal", node.value) validate_expected_type(node.value, type_definition) try: self.namespace[name] = type_definition except VyperException as exc: raise exc.with_annotation(node) from None return if node.value: raise VariableDeclarationException( "Storage variables cannot have an initial value", node.value) try: self.namespace.validate_assignment(name) except NamespaceCollision as exc: raise exc.with_annotation(node) from None try: self.namespace["self"].add_member(name, type_definition) node.target._metadata["type"] = type_definition except NamespaceCollision: raise NamespaceCollision( f"Value '{name}' has already been declared", node) from None except VyperException as exc: raise exc.with_annotation(node) from None
def validate_index_type(self, node): validate_expected_type(node, self.key_type)
def fetch_call_return(self, node: vy_ast.Call) -> None: validate_call_args(node, len(self.arguments)) for arg, expected in zip(node.args, self.arguments.values()): validate_expected_type(arg, expected)
def get_index_type(self, node): validate_expected_type(node, self.key_type) return self.value_type
def visit_AnnAssign(self, node): name = node.get("target.id") if name is None: raise VariableDeclarationException("Invalid module-level assignment", node) if name == "implements": interface_name = node.annotation.id self.namespace[interface_name].validate_implements(node) return is_constant, is_public, is_immutable = False, False, False annotation = node.annotation if isinstance(annotation, vy_ast.Call): # the annotation is a function call, e.g. `foo: constant(uint256)` call_name = annotation.get("func.id") if call_name in ("constant", "public", "immutable"): validate_call_args(annotation, 1) if call_name == "constant": # declaring a constant is_constant = True elif call_name == "public": # declaring a public variable is_public = True # generate function type and add to metadata # we need this when builing the public getter node._metadata["func_type"] = ContractFunction.from_AnnAssign(node) elif call_name == "immutable": # declaring an immutable variable is_immutable = True # mutability is checked automatically preventing assignment # outside of the constructor, here we just check a value is assigned, # not necessarily where assignments = self.ast.get_descendants( vy_ast.Assign, filters={"target.id": node.target.id} ) if not assignments: # Special error message for common wrong usages via `self.<immutable name>` wrong_self_attribute = self.ast.get_descendants( vy_ast.Attribute, {"value.id": "self", "attr": node.target.id} ) message = ( "Immutable variables must be accessed without 'self'" if len(wrong_self_attribute) > 0 else "Immutable definition requires an assignment in the constructor" ) raise SyntaxException( message, node.node_source_code, node.lineno, node.col_offset ) # remove the outer call node, to handle cases such as `public(map(..))` annotation = annotation.args[0] data_loc = DataLocation.CODE if is_immutable else DataLocation.STORAGE type_definition = get_type_from_annotation( annotation, data_loc, is_constant, is_public, is_immutable ) node._metadata["type"] = type_definition if is_constant: if not node.value: raise VariableDeclarationException("Constant must be declared with a value", node) if not check_constant(node.value): raise StateAccessViolation("Value must be a literal", node.value) validate_expected_type(node.value, type_definition) try: self.namespace[name] = type_definition except VyperException as exc: raise exc.with_annotation(node) from None return if node.value: var_type = "Immutable" if is_immutable else "Storage" raise VariableDeclarationException( f"{var_type} variables cannot have an initial value", node.value ) if is_immutable: try: # block immutable if storage variable already exists if name in self.namespace["self"].members: raise NamespaceCollision( f"Value '{name}' has already been declared", node ) from None self.namespace[name] = type_definition except VyperException as exc: raise exc.with_annotation(node) from None return try: self.namespace.validate_assignment(name) except NamespaceCollision as exc: raise exc.with_annotation(node) from None try: self.namespace["self"].add_member(name, type_definition) node.target._metadata["type"] = type_definition except NamespaceCollision: raise NamespaceCollision(f"Value '{name}' has already been declared", node) from None except VyperException as exc: raise exc.with_annotation(node) from None
def visit_For(self, node): if isinstance(node.iter, vy_ast.Subscript): raise StructureException("Cannot iterate over a nested list", node.iter) if isinstance(node.iter, vy_ast.Call): # iteration via range() if node.iter.get("func.id") != "range": raise IteratorException( "Cannot iterate over the result of a function call", node.iter) validate_call_args(node.iter, (1, 2)) args = node.iter.args if len(args) == 1: # range(CONSTANT) if not isinstance(args[0], vy_ast.Num): raise StateAccessViolation("Value must be a literal", node) if args[0].value <= 0: raise StructureException( "For loop must have at least 1 iteration", args[0]) validate_expected_type(args[0], IntegerAbstractType()) type_list = get_possible_types_from_node(args[0]) else: validate_expected_type(args[0], IntegerAbstractType()) type_list = get_common_types(*args) if not isinstance(args[0], vy_ast.Constant): # range(x, x + CONSTANT) if not isinstance(args[1], vy_ast.BinOp) or not isinstance( args[1].op, vy_ast.Add): raise StructureException( "Second element must be the first element plus a literal value", args[0]) if not vy_ast.compare_nodes(args[0], args[1].left): raise StructureException( "First and second variable must be the same", args[1].left) if not isinstance(args[1].right, vy_ast.Int): raise InvalidLiteral("Literal must be an integer", args[1].right) if args[1].right.value < 1: raise StructureException( f"For loop has invalid number of iterations ({args[1].right.value})," " the value must be greater than zero", args[1].right, ) else: # range(CONSTANT, CONSTANT) if not isinstance(args[1], vy_ast.Int): raise InvalidType("Value must be a literal integer", args[1]) validate_expected_type(args[1], IntegerAbstractType()) if args[0].value >= args[1].value: raise StructureException( "Second value must be > first value", args[1]) else: # iteration over a variable or literal list type_list = [ i.value_type for i in get_possible_types_from_node(node.iter) if isinstance(i, (DynamicArrayDefinition, ArrayDefinition)) ] if not type_list: raise InvalidType("Not an iterable type", node.iter) if isinstance(node.iter, (vy_ast.Name, vy_ast.Attribute)): # check for references to the iterated value within the body of the loop assign = _check_iterator_modification(node.iter, node) if assign: raise ImmutableViolation( "Cannot modify array during iteration", assign) # Check if `iter` is a storage variable. get_descendants` is used to check for # nested `self` (e.g. structs) iter_is_storage_var = (isinstance(node.iter, vy_ast.Attribute) and len( node.iter.get_descendants(vy_ast.Name, {"id": "self"})) > 0) if iter_is_storage_var: # check if iterated value may be modified by function calls inside the loop iter_name = node.iter.attr for call_node in node.get_descendants(vy_ast.Call, {"func.value.id": "self"}): fn_name = call_node.func.attr fn_node = self.vyper_module.get_children( vy_ast.FunctionDef, {"name": fn_name})[0] if _check_iterator_modification(node.iter, fn_node): # check for direct modification raise ImmutableViolation( f"Cannot call '{fn_name}' inside for loop, it potentially " f"modifies iterated storage variable '{iter_name}'", call_node, ) for name in self.namespace["self"].members[ fn_name].recursive_calls: # check for indirect modification fn_node = self.vyper_module.get_children( vy_ast.FunctionDef, {"name": name})[0] if _check_iterator_modification(node.iter, fn_node): raise ImmutableViolation( f"Cannot call '{fn_name}' inside for loop, it may call to '{name}' " f"which potentially modifies iterated storage variable '{iter_name}'", call_node, ) self.expr_visitor.visit(node.iter) for_loop_exceptions = [] iter_name = node.target.id for type_ in type_list: # type check the for loop body using each possible type for iterator value type_ = copy.deepcopy(type_) type_.is_constant = True with self.namespace.enter_scope(): try: self.namespace[iter_name] = type_ except VyperException as exc: raise exc.with_annotation(node) from None try: for n in node.body: self.visit(n) # type information is applied directly because the scope is # closed prior to the call to `StatementAnnotationVisitor` node.target._metadata["type"] = type_ return except (TypeMismatch, InvalidOperation) as exc: for_loop_exceptions.append(exc) if len(set(str(i) for i in for_loop_exceptions)) == 1: # if every attempt at type checking raised the same exception raise for_loop_exceptions[0] # return an aggregate TypeMismatch that shows all possible exceptions # depending on which type is used types_str = [str(i) for i in type_list] given_str = f"{', '.join(types_str[:1])} or {types_str[-1]}" raise TypeMismatch( f"Iterator value '{iter_name}' may be cast as {given_str}, " "but type checking fails with all possible types:", node, *((f"Casting '{iter_name}' as {type_}: {exc.message}", exc.annotations[0]) for type_, exc in zip(type_list, for_loop_exceptions)), )
def fetch_call_return(self, node: vy_ast.Call) -> InterfaceDefinition: validate_call_args(node, 1) validate_expected_type(node.args[0], AddressDefinition()) return InterfaceDefinition(self._id, self.members)
def infer_arg_types(self, node): validate_call_args(node, 1) validate_expected_type(node.args[0], AddressDefinition()) return [AddressDefinition()]