def from_annotation( cls, node: Union[vy_ast.Name, vy_ast.Call, vy_ast.Subscript], location: DataLocation = DataLocation.UNSET, is_immutable: bool = False, is_public: bool = False, ) -> MappingDefinition: if ( not isinstance(node, vy_ast.Subscript) or not isinstance(node.slice, vy_ast.Index) or not isinstance(node.slice.value, vy_ast.Tuple) or len(node.slice.value.elements) != 2 ): raise StructureException( "HashMap must be defined with a key type and a value type", node ) if location != DataLocation.STORAGE: raise StructureException("HashMap can only be declared as a storage variable", node) key_type = get_type_from_annotation(node.slice.value.elements[0], DataLocation.UNSET) value_type = get_type_from_annotation(node.slice.value.elements[1], DataLocation.STORAGE) return MappingDefinition( value_type, key_type, f"HashMap[{key_type}, {value_type}]", location, is_immutable, is_public, )
def _validate_single(self, arg, expected_type): # TODO using "TYPE_DEFINITION" is a kludge in derived classes, # refactor me. if expected_type == "TYPE_DEFINITION": # try to parse the type - call get_type_from_annotation # for its side effects (will throw if is not a type) get_type_from_annotation(arg, DataLocation.UNSET) else: validate_expected_type(arg, expected_type)
def from_annotation( cls, node: Union[vy_ast.Name, vy_ast.Call, vy_ast.Subscript], location: DataLocation = DataLocation.UNSET, is_constant: bool = False, is_public: bool = False, is_immutable: bool = False, ) -> DynamicArrayDefinition: # TODO fix circular import from vyper.semantics.types.utils import get_type_from_annotation if (not isinstance(node, vy_ast.Subscript) or not isinstance(node.slice, vy_ast.Index) or not isinstance(node.slice.value, vy_ast.Tuple) or not isinstance(node.slice.value.elements[1], vy_ast.Int) or len(node.slice.value.elements) != 2): raise StructureException( "DynArray must be defined with base type and max length, e.g. DynArray[bool, 5]", node, ) value_type = get_type_from_annotation(node.slice.value.elements[0], location, is_constant, is_public, is_immutable) max_length = node.slice.value.elements[1].value return DynamicArrayDefinition(value_type, max_length, location, is_constant, is_public, is_immutable)
def test_base_types_as_multidimensional_arrays(build_node, type_str, location, first, second): node = build_node(f"{type_str}[{first}][{second}]") type_definition = get_type_from_annotation(node, location) assert type_definition.size_in_bytes == first * second * 32
def test_array_value_types(build_node, type_str): node = build_node(f"{type_str}[1]") primitive = get_primitive_types()[type_str] type_definition = get_type_from_annotation(node, DataLocation.STORAGE) assert isinstance(type_definition, primitive._type)
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 from_AnnAssign(cls, node: vy_ast.AnnAssign) -> "ContractFunction": """ Generate a `ContractFunction` object from an `AnnAssign` node. Used to create getter functions for public variables. Arguments --------- node : AnnAssign Vyper ast node to generate the function definition from. Returns ------- ContractFunction """ if not isinstance(node.annotation, vy_ast.Call): raise CompilerPanic("Annotation must be a call to public()") type_ = get_type_from_annotation(node.annotation.args[0], location=DataLocation.STORAGE) arguments, return_type = type_.get_signature() args_dict: OrderedDict = OrderedDict() for item in arguments: args_dict[f"arg{len(args_dict)}"] = item return cls( node.target.id, args_dict, len(arguments), len(arguments), return_type, function_visibility=FunctionVisibility.EXTERNAL, state_mutability=StateMutability.VIEW, )
def test_mapping(build_node, type_str, type_str2): node = build_node(f"HashMap[{type_str}, {type_str2}]") primitives = get_primitive_types() type_definition = get_type_from_annotation(node, DataLocation.STORAGE) assert isinstance(type_definition, MappingDefinition) assert isinstance(type_definition.key_type, primitives[type_str]._type) assert isinstance(type_definition.value_type, primitives[type_str2]._type)
def test_base_types_as_arrays(build_node, type_str): node = build_node(f"{type_str}[3]") primitive = get_primitive_types()[type_str] type_definition = get_type_from_annotation(node, DataLocation.STORAGE) assert isinstance(type_definition, ArrayDefinition) assert type_definition.length == 3 assert isinstance(type_definition.value_type, primitive._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 visit_AnnAssign(self, node): name = node.get("target.id") if name is None: raise VariableDeclarationException("Invalid assignment", node) if not node.value: raise VariableDeclarationException( "Memory variables must be declared with an initial value", node) type_definition = get_type_from_annotation(node.annotation, DataLocation.MEMORY) validate_expected_type(node.value, type_definition) try: self.namespace[name] = type_definition except VyperException as exc: raise exc.with_annotation(node) from None
def replace_user_defined_constants(vyper_module: vy_ast.Module) -> int: """ Find user-defined constant assignments, and replace references to the constants with their literal values. Arguments --------- vyper_module : Module Top-level Vyper AST node. Returns ------- int Number of nodes that were replaced. """ changed_nodes = 0 for node in vyper_module.get_children(vy_ast.AnnAssign): if not isinstance(node.target, vy_ast.Name): # left-hand-side of assignment is not a variable continue if node.get("annotation.func.id") != "constant": # annotation is not wrapped in `constant(...)` continue # Extract type definition from propagated annotation constant_annotation = node.get("annotation.args")[0] try: type_ = ( get_type_from_annotation(constant_annotation, DataLocation.UNSET) if constant_annotation else None ) except UnknownType: # handle user-defined types e.g. structs - it's OK to not # propagate the type annotation here because user-defined # types can be unambiguously inferred at typechecking time type_ = None changed_nodes += replace_constant( vyper_module, node.target.id, node.value, False, type_=type_ ) return changed_nodes
def replace_user_defined_constants(vyper_module: vy_ast.Module) -> int: """ Find user-defined constant assignments, and replace references to the constants with their literal values. Arguments --------- vyper_module : Module Top-level Vyper AST node. Returns ------- int Number of nodes that were replaced. """ changed_nodes = 0 for node in vyper_module.get_children(vy_ast.AnnAssign): if not isinstance(node.target, vy_ast.Name): # left-hand-side of assignment is not a variable continue if node.get("annotation.func.id") != "constant": # annotation is not wrapped in `constant(...)` continue # Extract type definition from propagated annotation constant_annotation = node.get("annotation.args")[0] type_ = (get_type_from_annotation(constant_annotation, DataLocation.UNSET) if constant_annotation else None) changed_nodes += replace_constant(vyper_module, node.target.id, node.value, False, type_=type_) return changed_nodes
def test_array_value_types_as_arrays(build_node, type_str): node = build_node(f"{type_str}[1][1]") with pytest.raises(StructureException): get_type_from_annotation(node, DataLocation.STORAGE)
def test_base_types_as_arrays(build_node, type_str, location, length): node = build_node(f"{type_str}[{length}]") type_definition = get_type_from_annotation(node, location) assert type_definition.size_in_bytes == length * 32
def test_dynamic_array_lengths(build_node, type_str, location, length): node = build_node(f"DynArray[{type_str}, {length}]") type_definition = get_type_from_annotation(node, location) assert type_definition.size_in_bytes == 32 + length * 32
def test_array_value_types(build_node, type_str, location, length, size): node = build_node(f"{type_str}[{length}]") type_definition = get_type_from_annotation(node, location) assert type_definition.size_in_bytes == size
def test_base_types(build_node, type_str, location): node = build_node(type_str) type_definition = get_type_from_annotation(node, location) assert type_definition.size_in_bytes == 32
def test_invalid_index(build_node, idx, type_str): node = build_node(f"{type_str}[{idx}]") with pytest.raises((ArrayIndexException, InvalidType, StructureException, UndeclaredDefinition)): get_type_from_annotation(node, DataLocation.STORAGE)
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 test_array_value_types(build_node, type_str, location, length, size): node = build_node(f"{type_str}[{length}]") type_definition = get_type_from_annotation(node, location) # TODO once storage of bytes is optimized, remove the +32 assert type_definition.size_in_bytes == size + 32
def visit_AnnAssign(self, node): name = node.get("target.id") if name is None: raise VariableDeclarationException( "Invalid module-level assignment", node) if name == "implements": interface_name = node.annotation.id self.namespace[interface_name].validate_implements(node) return is_immutable, is_public = False, False annotation = node.annotation if isinstance(annotation, vy_ast.Call): # the annotation is a function call, e.g. `foo: constant(uint256)` call_name = annotation.get("func.id") if call_name in ("constant", "public"): validate_call_args(annotation, 1) if call_name == "constant": # declaring a constant is_immutable = True elif call_name == "public": # declaring a public variable is_public = True # generate function type and add to metadata # we need this when builing the public getter node._metadata[ "func_type"] = ContractFunction.from_AnnAssign(node) # remove the outer call node, to handle cases such as `public(map(..))` annotation = annotation.args[0] type_definition = get_type_from_annotation(annotation, DataLocation.STORAGE, is_immutable, is_public) node._metadata["type"] = type_definition if is_immutable: if not node.value: raise VariableDeclarationException( "Constant must be declared with a value", node) if not check_literal(node.value): raise StateAccessViolation("Value must be a literal", node.value) validate_expected_type(node.value, type_definition) try: self.namespace[name] = type_definition except VyperException as exc: raise exc.with_annotation(node) from None return if node.value: raise VariableDeclarationException( "Storage variables cannot have an initial value", node.value) try: self.namespace.validate_assignment(name) except NamespaceCollision as exc: raise exc.with_annotation(node) from None try: self.namespace["self"].add_member(name, type_definition) node.target._metadata["type"] = type_definition except NamespaceCollision: raise NamespaceCollision( f"Value '{name}' has already been declared", node) from None except VyperException as exc: raise exc.with_annotation(node) from None
def 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)