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
def _validate_arg_types(self, node): num_args = len( self._inputs) # the number of args the signature indicates expect_num_args = num_args if self._has_varargs: # note special meaning for -1 in validate_call_args API expect_num_args = (num_args, -1) validate_call_args(node, expect_num_args, self._kwargs) for arg, (_, expected) in zip(node.args, self._inputs): self._validate_single(arg, expected) for kwarg in node.keywords: kwarg_settings = self._kwargs[kwarg.arg] if kwarg_settings.require_literal and not isinstance( kwarg.value, vy_ast.Constant): raise TypeMismatch("Value for kwarg must be a literal", kwarg.value) self._validate_single(kwarg.value, kwarg_settings.typ) # typecheck varargs. we don't have type info from the signature, # so ensure that the types of the args can be inferred exactly. varargs = node.args[num_args:] if len(varargs) > 0: assert self._has_varargs # double check validate_call_args for arg in varargs: # call get_exact_type_from_node for its side effects - # ensures the type can be inferred exactly. get_exact_type_from_node(arg)
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_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
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 from_EventDef(cls, base_node: vy_ast.EventDef) -> "Event": """ Generate an `Event` object from a Vyper ast node. Arguments --------- base_node : EventDef Vyper ast node defining the event Returns ------- Event """ members: OrderedDict = OrderedDict() indexed: List = [] if len(base_node.body) == 1 and isinstance(base_node.body[0], vy_ast.Pass): return Event(base_node.name, members, indexed) for node in base_node.body: if not isinstance(node, vy_ast.AnnAssign): raise StructureException( "Events can only contain variable definitions", node) if node.value is not None: raise StructureException( "Cannot assign a value during event declaration", node) if not isinstance(node.target, vy_ast.Name): raise StructureException( "Invalid syntax for event member name", node.target) member_name = node.target.id if member_name in members: raise NamespaceCollision( f"Event member '{member_name}' has already been declared", node.target) annotation = node.annotation if isinstance( annotation, vy_ast.Call) and annotation.get("func.id") == "indexed": validate_call_args(annotation, 1) if indexed.count(True) == 3: raise EventDeclarationException( "Event cannot have more than three indexed arguments", annotation) indexed.append(True) annotation = annotation.args[0] else: indexed.append(False) members[member_name] = get_type_from_annotation( annotation, DataLocation.UNSET) return Event(base_node.name, members, indexed)
def fetch_call_return(self, node: vy_ast.Call) -> Optional[BaseTypeDefinition]: validate_call_args(node, (self.min_arg_count, self.max_arg_count)) if isinstance(self.underlying_type, DynamicArrayDefinition): if self.name == "append": return None elif self.name == "pop": value_type = self.underlying_type.value_type return value_type raise CallViolation("Function does not exist on given type", node)
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 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 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) ), )
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 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 infer_arg_types(self, node): validate_call_args(node, 1) validate_expected_type(node.args[0], AddressDefinition()) return [AddressDefinition()]