def add_member(self, name: str, type_: BaseTypeDefinition) -> None: if name in self.members: raise NamespaceCollision( f"Member '{name}' already exists in {self}") if name in getattr(self, "_type_members", []): raise NamespaceCollision( f"Member '{name}' already exists in {self}") self.members[name] = type_
def validate_assignment(self, attr): if attr in self: if attr not in [x for i in self._scopes for x in i]: raise NamespaceCollision( f"Cannot assign to '{attr}', it is a builtin") obj = super().__getitem__(attr) raise NamespaceCollision( f"'{attr}' has already been declared as a {obj}")
def __setitem__(self, attr, obj): if attr in self: if attr not in [x for i in self._scopes for x in i]: raise NamespaceCollision( f"Cannot assign to '{attr}', it is a builtin") obj = super().__getitem__(attr) raise NamespaceCollision( f"'{attr}' has already been declared as a {obj}") if self._scopes: self._scopes[-1].add(attr) super().__setitem__(attr, obj)
def build_primitive_from_abi(name: str, abi: dict) -> InterfacePrimitive: """ Generate an `InterfacePrimitive` object from an ABI. Arguments --------- name : str The name of the interface abi : dict Contract ABI Returns ------- InterfacePrimitive primitive interface type """ members: OrderedDict = OrderedDict() events: Dict = {} names = [i["name"] for i in abi if i.get("type") in ("event", "function")] collisions = set(i for i in names if names.count(i) > 1) if collisions: collision_list = ", ".join(sorted(collisions)) raise NamespaceCollision( f"ABI '{name}' has multiple functions or events with the same name: {collision_list}" ) for item in [i for i in abi if i.get("type") == "function"]: members[item["name"]] = ContractFunction.from_abi(item) for item in [i for i in abi if i.get("type") == "event"]: events[item["name"]] = Event.from_abi(item) return InterfacePrimitive(name, members, events)
def build_primitive_from_node(base_node: vy_ast.EventDef) -> StructPrimitive: """ Generate a `StructPrimitive` object from a Vyper ast node. Arguments --------- node : EventDef Vyper ast node defining the struct Returns ------- StructPrimitive Primitive struct type """ members: OrderedDict = OrderedDict() for node in base_node.body: if not isinstance(node, vy_ast.AnnAssign): raise StructureException("Structs can only variable definitions", node) if node.value is not None: raise StructureException( "Cannot assign a value during struct declaration", node) if not isinstance(node.target, vy_ast.Name): raise StructureException("Invalid syntax for struct member name", node.target) member_name = node.target.id if member_name in members: raise NamespaceCollision( f"Struct member '{member_name}' has already been declared", node.target) members[member_name] = get_type_from_annotation( node.annotation, DataLocation.UNSET) return StructPrimitive(base_node.name, 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 _get_module_definitions( base_node: vy_ast.Module) -> Tuple[OrderedDict, Dict]: functions: OrderedDict = OrderedDict() events: Dict = {} for node in base_node.get_children(vy_ast.FunctionDef): if "external" in [ i.id for i in node.decorator_list if isinstance(i, vy_ast.Name) ]: func = ContractFunction.from_FunctionDef(node) if node.name in functions: # compare the input arguments of the new function and the previous one # if one function extends the inputs, this is a valid function name overload existing_args = list(functions[node.name].arguments) new_args = list(func.arguments) for a, b in zip(existing_args, new_args): if not isinstance(a, type(b)): raise NamespaceCollision( f"Interface contains multiple functions named '{node.name}' " "with incompatible input types", base_node, ) if len(new_args) <= len(existing_args): # only keep the `ContractFunction` with the longest set of input args continue functions[node.name] = func for node in base_node.get_children(vy_ast.AnnAssign, {"annotation.func.id": "public"}): name = node.target.id if name in functions: raise NamespaceCollision( f"Interface contains multiple functions named '{name}'", base_node) functions[name] = ContractFunction.from_AnnAssign(node) for node in base_node.get_children(vy_ast.EventDef): name = node.name if name in functions or name in events: raise NamespaceCollision( f"Interface contains multiple objects named '{name}'", base_node) events[name] = Event.from_EventDef(node) return functions, events
def validate_identifier(attr): namespace = get_namespace() if attr in namespace and attr not in [ x for i in namespace._scopes for x in i ]: raise NamespaceCollision(f"Cannot assign to '{attr}', it is a builtin") if attr.lower() in RESERVED_KEYWORDS or attr.upper() in OPCODES: raise StructureException(f"'{attr}' is a reserved keyword") if not re.match("^[_a-zA-Z][a-zA-Z0-9_]*$", attr): raise StructureException(f"'{attr}' contains invalid character(s)")
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 _get_class_functions(base_node: vy_ast.InterfaceDef) -> OrderedDict: functions = OrderedDict() for node in base_node.body: if not isinstance(node, vy_ast.FunctionDef): raise StructureException("Interfaces can only contain function definitions", node) if node.name in functions: raise NamespaceCollision( f"Interface contains multiple functions named '{node.name}'", node ) functions[node.name] = ContractFunction.from_FunctionDef(node, is_interface=True) return functions
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
def from_FunctionDef( cls, node: vy_ast.FunctionDef, is_interface: Optional[bool] = False, ) -> "ContractFunction": """ Generate a `ContractFunction` object from a `FunctionDef` node. Arguments --------- node : FunctionDef Vyper ast node to generate the function definition from. is_interface: bool, optional Boolean indicating if the function definition is part of an interface. Returns ------- ContractFunction """ kwargs: Dict[str, Any] = {} if is_interface: # FunctionDef with stateMutability in body (Interface defintions) if (len(node.body) == 1 and isinstance(node.body[0], vy_ast.Expr) and isinstance(node.body[0].value, vy_ast.Name) and StateMutability.is_valid_value(node.body[0].value.id)): # Interfaces are always public kwargs["function_visibility"] = FunctionVisibility.EXTERNAL kwargs["state_mutability"] = StateMutability( node.body[0].value.id) elif len(node.body) == 1 and node.body[0].get("value.id") in ( "constant", "modifying"): if node.body[0].value.id == "constant": expected = "view or pure" else: expected = "payable or nonpayable" raise StructureException( f"State mutability should be set to {expected}", node.body[0]) else: raise StructureException( "Body must only contain state mutability label", node.body[0]) else: # FunctionDef with decorators (normal functions) for decorator in node.decorator_list: if isinstance(decorator, vy_ast.Call): if "nonreentrant" in kwargs: raise StructureException( "nonreentrant decorator is already set with key: " f"{kwargs['nonreentrant']}", node, ) if decorator.get("func.id") != "nonreentrant": raise StructureException("Decorator is not callable", decorator) if len(decorator.args) != 1 or not isinstance( decorator.args[0], vy_ast.Str): raise StructureException( "@nonreentrant name must be given as a single string literal", decorator, ) kwargs["nonreentrant"] = decorator.args[0].value elif isinstance(decorator, vy_ast.Name): if FunctionVisibility.is_valid_value(decorator.id): if "function_visibility" in kwargs: raise FunctionDeclarationException( f"Visibility is already set to: {kwargs['function_visibility']}", node, ) kwargs["function_visibility"] = FunctionVisibility( decorator.id) elif StateMutability.is_valid_value(decorator.id): if "state_mutability" in kwargs: raise FunctionDeclarationException( f"Mutability is already set to: {kwargs['state_mutability']}", node) kwargs["state_mutability"] = StateMutability( decorator.id) else: if decorator.id == "constant": warnings.warn( "'@constant' decorator has been removed (see VIP2040). " "Use `@view` instead.", DeprecationWarning, ) raise FunctionDeclarationException( f"Unknown decorator: {decorator.id}", decorator) else: raise StructureException("Bad decorator syntax", decorator) if "function_visibility" not in kwargs: raise FunctionDeclarationException( f"Visibility must be set to one of: {', '.join(FunctionVisibility.values())}", node) if "state_mutability" not in kwargs: # Assume nonpayable if not set at all (cannot accept Ether, but can modify state) kwargs["state_mutability"] = StateMutability.NONPAYABLE # call arguments arg_count: Union[Tuple[int, int], int] = len(node.args.args) if node.args.defaults: arg_count = ( len(node.args.args) - len(node.args.defaults), len(node.args.args), ) arguments = OrderedDict() defaults = [None] * (len(node.args.args) - len(node.args.defaults)) + node.args.defaults namespace = get_namespace() for arg, value in zip(node.args.args, defaults): if arg.arg in ("gas", "value"): raise ArgumentException( f"Cannot use '{arg.arg}' as a variable name in a function input", arg, ) if arg.arg in arguments: raise ArgumentException( f"Function contains multiple inputs named {arg.arg}", arg) if arg.arg in namespace["self"].members: raise NamespaceCollision( "Name shadows an existing storage-scoped value", arg) if arg.arg in namespace: raise NamespaceCollision(arg.arg, arg) if arg.annotation is None: raise ArgumentException( f"Function argument '{arg.arg}' is missing a type", arg) type_definition = get_type_from_annotation( arg.annotation, location=DataLocation.CALLDATA, is_immutable=True) if isinstance( type_definition, StructDefinition) and type_definition.is_dynamic_size: # this is a temporary restriction and should be removed once support for dynamically # sized structs is implemented - https://github.com/vyperlang/vyper/issues/2190 raise ArgumentException( "Struct with dynamically sized data cannot be used as a function input", arg) if value is not None: if not check_constant(value): raise StateAccessViolation( "Value must be literal or environment variable", value) validate_expected_type(value, type_definition) arguments[arg.arg] = type_definition # return types if node.returns is None: return_type = None elif isinstance(node.returns, (vy_ast.Name, vy_ast.Call, vy_ast.Subscript)): return_type = get_type_from_annotation( node.returns, location=DataLocation.MEMORY) elif isinstance(node.returns, vy_ast.Tuple): tuple_types: Tuple = () for n in node.returns.elements: tuple_types += (get_type_from_annotation( n, location=DataLocation.MEMORY), ) return_type = TupleDefinition(tuple_types) else: raise InvalidType( "Function return value must be a type name or tuple", node.returns) return cls(node.name, arguments, arg_count, return_type, **kwargs)
def 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 validate_assignment(self, attr): validate_identifier(attr) if attr in self: obj = super().__getitem__(attr) raise NamespaceCollision( f"'{attr}' has already been declared as a {obj}")
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)