Exemple #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)
Exemple #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)
Exemple #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)
Exemple #4
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)
Exemple #5
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
                # 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)

        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["self"].add_member(name, 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
Exemple #6
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)
Exemple #7
0
    def visit_AnnAssign(self, node):
        if not node.value:
            raise VariableDeclarationException(
                "Memory variables must be declared with an initial value", node
            )
        name = node.target.id
        if name in self.namespace["self"].members:
            raise NamespaceCollision("Variable name shadows an existing storage-scoped 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
Exemple #8
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
Exemple #9
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.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
Exemple #10
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,
                        )
                    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)
Exemple #11
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], Uint256Definition())
                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, ArrayDefinition)
            ]

        if not type_list:
            raise InvalidType("Not an iterable type", node.iter)

        if next((i for i in type_list if isinstance(i, ArrayDefinition)), False):
            raise StructureException("Cannot iterate over a nested list", 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_assign(node.iter, node)
            if assign:
                raise ImmutableViolation("Cannot modify array during iteration", assign)

        if node.iter.get("value.id") == "self":
            # 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_assign(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_assign(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,
                        )

        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_immutable = 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)
                    return
                except TypeMismatch 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)
            ),
        )
Exemple #12
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)
Exemple #13
0
 def get_index_type(self, node):
     validate_expected_type(node, self.key_type)
     return self.value_type
Exemple #14
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)