Beispiel #1
0
    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)
Beispiel #2
0
    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)
Beispiel #3
0
 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)
Beispiel #4
0
 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)
Beispiel #5
0
    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)
Beispiel #6
0
    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
Beispiel #7
0
    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
Beispiel #8
0
 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)
Beispiel #9
0
    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)
Beispiel #10
0
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
Beispiel #11
0
    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
Beispiel #12
0
    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
Beispiel #13
0
    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)
Beispiel #14
0
    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)
Beispiel #15
0
    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
Beispiel #16
0
 def validate_index_type(self, node):
     validate_expected_type(node, self.key_type)
Beispiel #17
0
 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)
Beispiel #18
0
 def get_index_type(self, node):
     validate_expected_type(node, self.key_type)
     return self.value_type
Beispiel #19
0
    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
Beispiel #20
0
    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)),
        )
Beispiel #21
0
    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)
Beispiel #22
0
 def infer_arg_types(self, node):
     validate_call_args(node, 1)
     validate_expected_type(node.args[0], AddressDefinition())
     return [AddressDefinition()]