def parse_unit(item, custom_units): if isinstance(item, vy_ast.Name): if (item.id not in VALID_UNITS) and (custom_units is not None) and ( item.id not in custom_units): # noqa: E501 raise InvalidType("Invalid base unit", item) return {item.id: 1} elif isinstance(item, vy_ast.Int) and item.n == 1: return {} elif not isinstance(item, vy_ast.BinOp): raise InvalidType("Invalid unit expression", item) elif isinstance(item.op, vy_ast.Mult): left, right = parse_unit(item.left, custom_units), parse_unit( item.right, custom_units) return combine_units(left, right) elif isinstance(item.op, vy_ast.Div): left, right = parse_unit(item.left, custom_units), parse_unit( item.right, custom_units) return combine_units(left, right, div=True) elif isinstance(item.op, vy_ast.Pow): if not isinstance(item.left, vy_ast.Name): raise InvalidType("Can only raise a base type to an exponent", item) if not isinstance(item.right, vy_ast.Int) or not isinstance( item.right.n, int) or item.right.n <= 0: # noqa: E501 raise InvalidType("Exponent must be positive integer", item) return {item.left.id: item.right.n} else: raise InvalidType("Invalid unit expression", item)
def canonicalize_type(t, is_indexed=False): if isinstance(t, ByteArrayLike): # Check to see if maxlen is small enough for events byte_type = 'string' if isinstance(t, StringType) else 'bytes' if is_indexed: return f'{byte_type}{t.maxlen}' else: return f'{byte_type}' if isinstance(t, ListType): if not isinstance(t.subtype, (ListType, BaseType)): raise InvalidType(f"List of {t.subtype}s not allowed") return canonicalize_type(t.subtype) + f"[{t.count}]" if isinstance(t, TupleLike): return f"({','.join(canonicalize_type(x) for x in t.tuple_members())})" if not isinstance(t, BaseType): raise InvalidType(f"Cannot canonicalize non-base type: {t}") t = t.typ if t in ('int128', 'uint256', 'bool', 'address', 'bytes32'): return t elif t == 'decimal': return 'fixed168x10' raise InvalidType(f"Invalid or unsupported type: {repr(t)}")
def get_static_size_of_type(typ): if isinstance(typ, BaseType): return 1 elif isinstance(typ, ByteArrayLike): return 1 elif isinstance(typ, ListType): return get_size_of_type(typ.subtype) * typ.count elif isinstance(typ, MappingType): raise InvalidType("Maps are not supported for function arguments or outputs.") elif isinstance(typ, TupleLike): return sum([get_size_of_type(v) for v in typ.tuple_members()]) else: raise InvalidType(f"Can not get size of type, Unexpected type: {repr(typ)}")
def get_size_of_type(typ): if isinstance(typ, BaseType): return 1 elif isinstance(typ, ByteArrayLike): # 1 word for offset (in static section), 1 word for length, # up to maxlen words for actual data. return ceil32(typ.maxlen) // 32 + 2 elif isinstance(typ, ListType): return get_size_of_type(typ.subtype) * typ.count elif isinstance(typ, MappingType): raise InvalidType("Maps are not supported for function arguments or outputs.") elif isinstance(typ, TupleLike): return sum([get_size_of_type(v) for v in typ.tuple_members()]) else: raise InvalidType(f"Can not get size of type, Unexpected type: {repr(typ)}")
def validate_expected_type(node, expected_type): """ Validate that the given node matches the expected type(s) Raises if the node does not match one of the expected types. Arguments --------- node : VyperNode Vyper ast node. expected_type : Tuple | BaseType A type object, or tuple of type objects Returns ------- None """ given_types = _ExprTypeChecker().get_possible_types_from_node(node) if not isinstance(expected_type, tuple): expected_type = (expected_type, ) if isinstance(node, (vy_ast.List, vy_ast.Tuple)): # special case - for literal arrays or tuples we individually validate each item for expected in (i for i in expected_type if isinstance(i, ArrayDefinition)): if _validate_literal_array(node, expected): return else: for given, expected in itertools.product(given_types, expected_type): if expected.compare_type(given): return # validation failed, prepare a meaningful error message if len(expected_type) > 1: expected_str = f"one of {', '.join(str(i) for i in expected_type)}" else: expected_str = expected_type[0] if len(given_types) == 1 and getattr(given_types[0], "_is_callable", False): raise StructureException( f"{given_types[0]} cannot be referenced directly, it must be called", node) if not isinstance(node, (vy_ast.List, vy_ast.Tuple)) and node.get_descendants( vy_ast.Name, include_self=True): given = given_types[0] raise TypeMismatch( f"Given reference has type {given}, expected {expected_str}", node) else: if len(given_types) == 1: given_str = str(given_types[0]) else: types_str = sorted(str(i) for i in given_types) given_str = f"{', '.join(types_str[:1])} or {types_str[-1]}" raise InvalidType( f"Expected {expected_str} but literal can only be cast as {given_str}", node)
def types_from_Call(self, node): # function calls, e.g. `foo()` var = self.get_exact_type_from_node(node.func, False) return_value = var.fetch_call_return(node) if return_value: return [return_value] raise InvalidType(f"{var} did not return a value", node)
def make_struct(self, node: "vy_ast.StructDef") -> list: members = [] for item in node.body: if isinstance(item, vy_ast.AnnAssign): member_name = item.target member_type = item.annotation # Check well-formedness of member names if not isinstance(member_name, vy_ast.Name): raise InvalidType( f"Invalid member name for struct {node.name}, needs to be a valid name. ", item, ) # Check well-formedness of member types # Note this kicks out mutually recursive structs, # raising an exception instead of stackoverflow. # A struct must be defined before it is referenced. # This feels like a semantic step and maybe should be pushed # to a later compilation stage. parse_type( member_type, "storage", custom_structs=self._structs, ) members.append((member_name, member_type)) else: raise StructureException("Structs can only contain variables", item) return members
def make_struct_type(name, location, members, custom_units, custom_structs, constants): o = OrderedDict() for key, value in members: if not isinstance(key, vy_ast.Name): raise InvalidType( f"Invalid member variable for struct {key.id}, expected a name.", key, ) check_valid_varname( key.id, custom_units, custom_structs, constants, "Invalid member variable for struct", ) o[key.id] = parse_type( value, location, custom_units=custom_units, custom_structs=custom_structs, constants=constants, ) return StructType(o, name)
def new_type_to_old_type(typ: new.BasePrimitive) -> old.NodeType: if isinstance(typ, new.BoolDefinition): return old.BaseType("bool") if isinstance(typ, new.AddressDefinition): return old.BaseType("address") if isinstance(typ, new.Bytes32Definition): return old.BaseType("bytes32") if isinstance(typ, new.BytesArrayDefinition): return old.ByteArrayType(typ.length) if isinstance(typ, new.StringDefinition): return old.StringType(typ.length) if isinstance(typ, new.DecimalDefinition): return old.BaseType("decimal") if isinstance(typ, new.SignedIntegerAbstractType): bits = typ._bits # type: ignore return old.BaseType("int" + str(bits)) if isinstance(typ, new.UnsignedIntegerAbstractType): bits = typ._bits # type: ignore return old.BaseType("uint" + str(bits)) if isinstance(typ, new.ArrayDefinition): return old.SArrayType(new_type_to_old_type(typ.value_type), typ.length) if isinstance(typ, new.DynamicArrayDefinition): return old.DArrayType(new_type_to_old_type(typ.value_type), typ.length) if isinstance(typ, new.TupleDefinition): return old.TupleType(typ.value_type) if isinstance(typ, new.StructDefinition): return old.StructType( {n: new_type_to_old_type(t) for (n, t) in typ.members.items()}, typ._id) raise InvalidType(f"unknown type {typ}")
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"): raise InvalidType("Reason must UNREACHABLE or a string literal", msg_node)
def validate_index_type(self, node): if not isinstance(node, vy_ast.Int): raise InvalidType("Tuple indexes must be literals", node) if node.value < 0: raise ArrayIndexException("Vyper does not support negative indexing", node) if node.value >= self.length: raise ArrayIndexException("Index out of range", node)
def get_index_value(node: vy_ast.Index) -> int: """ Return the literal value for a `Subscript` index. Arguments --------- node : vy_ast.Index Vyper ast node from the `slice` member of a Subscript node. Must be an `Index` object (Vyper does not support `Slice` or `ExtSlice`). Returns ------- int Literal integer value. """ if not isinstance(node.get("value"), vy_ast.Int): if hasattr(node, "value"): # even though the subscript is an invalid type, first check if it's a valid _something_ # this gives a more accurate error in case of e.g. a typo in a constant variable name try: get_possible_types_from_node(node.value) except StructureException: # StructureException is a very broad error, better to raise InvalidType in this case pass raise InvalidType("Subscript must be a literal integer", node) if node.value.value <= 0: raise ArrayIndexException("Subscript must be greater than 0", node) return node.value.value
def get_type_from_annotation( node: vy_ast.VyperNode, location: DataLocation, is_constant: bool = False, is_public: bool = False, is_immutable: bool = False, ) -> BaseTypeDefinition: """ Return a type object for the given AST node. Arguments --------- node : VyperNode Vyper ast node from the `annotation` member of a `VariableDef` or `AnnAssign` node. Returns ------- BaseTypeDefinition Type definition object. """ namespace = get_namespace() if isinstance(node, vy_ast.Tuple): values = node.elements types = tuple( get_type_from_annotation(v, DataLocation.UNSET) for v in values) return TupleDefinition(types) try: # get id of leftmost `Name` node from the annotation type_name = next( i.id for i in node.get_descendants(vy_ast.Name, include_self=True)) except StopIteration: raise StructureException("Invalid syntax for type declaration", node) try: type_obj = namespace[type_name] except UndeclaredDefinition: suggestions_str = get_levenshtein_error_suggestions( type_name, namespace, 0.3) raise UnknownType( f"No builtin or user-defined type named '{type_name}'. {suggestions_str}", node) from None if (getattr(type_obj, "_as_array", False) and isinstance(node, vy_ast.Subscript) and node.value.get("id") != "DynArray"): # TODO: handle `is_immutable` for arrays # if type can be an array and node is a subscript, create an `ArrayDefinition` length = get_index_value(node.slice) value_type = get_type_from_annotation(node.value, location, is_constant, False, is_immutable) return ArrayDefinition(value_type, length, location, is_constant, is_public, is_immutable) try: return type_obj.from_annotation(node, location, is_constant, is_public, is_immutable) except AttributeError: raise InvalidType(f"'{type_name}' is not a valid type", node) from None
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)
def add_globals_and_events(self, item): item_attributes = {"public": False} if self._nonrentrant_counter: raise CompilerPanic( "Re-entrancy lock was set before all storage slots were defined" ) # Make sure we have a valid variable name. if not isinstance(item.target, vy_ast.Name): raise StructureException("Invalid global variable name", item.target) # Handle constants. if self.get_call_func_name(item) == "constant": return item_name, item_attributes = self.get_item_name_and_attributes( item, item_attributes) # references to `len(self._globals)` are remnants of deprecated code, retained # to preserve existing interfaces while we complete a larger refactor. location # and size of storage vars is handled in `vyper.context.validation.data_positions` if item_name in self._contracts or item_name in self._interfaces: if self.get_call_func_name(item) == "address": raise StructureException( f"Persistent address({item_name}) style contract declarations " "are not support anymore." f" Use {item.target.id}: {item_name} instead") self._globals[item.target.id] = ContractRecord( item.target.id, len(self._globals), InterfaceType(item_name), True, ) elif self.get_call_func_name(item) == "public": if isinstance(item.annotation.args[0], vy_ast.Name) and item_name in self._contracts: typ = InterfaceType(item_name) else: typ = self.parse_type(item.annotation.args[0], "storage") self._globals[item.target.id] = VariableRecord( item.target.id, len(self._globals), typ, True, ) elif isinstance(item.annotation, (vy_ast.Name, vy_ast.Call, vy_ast.Subscript)): self._globals[item.target.id] = VariableRecord( item.target.id, len(self._globals), self.parse_type(item.annotation, "storage"), True, ) else: raise InvalidType("Invalid global type specified", item)
def make_struct_type(name, sigs, members, custom_structs, enums): o = OrderedDict() for key, value in members: if not isinstance(key, vy_ast.Name): raise InvalidType(f"Invalid member variable for struct {key.id}, expected a name.", key) o[key.id] = parse_type(value, sigs=sigs, custom_structs=custom_structs, enums=enums) return StructType(o, name)
def parse_integer_typeinfo(typename: str) -> IntegerTypeInfo: t = _int_parser.fullmatch(typename) if not t: raise InvalidType(f"Invalid integer type {typename}") # pragma: notest return IntegerTypeInfo( is_signed=t.group(1) != "u", bits=int(t.group(2)), )
def evaluate(self) -> "VyperNode": """ Attempt to evaluate the content of a node and generate a new node from it. If a node cannot be evaluated it should raise `InvalidType`. This base method acts as a catch-all to raise on any inherited classes that do not implement the method. """ raise InvalidType(f"{type(self)} cannot be evaluated", self)
def evaluate(self) -> VyperNode: """ Attempt to evaluate the arithmetic operation. Returns ------- Int | Decimal Node representing the result of the evaluation. """ left, right = self.left, self.right if type(left) is not type(right): raise InvalidType("Node contains invalid field(s) for evaluation", self) if not isinstance(left, (Int, Decimal)): raise InvalidType("Node contains invalid field(s) for evaluation", self) value = self.op._op(left.value, right.value) return type(left).from_node(self, value=value)
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 evaluate(self) -> VyperNode: """ Attempt to evaluate the unary operation. Returns ------- Int | Decimal Node representing the result of the evaluation. """ if isinstance(self.op, Not) and not isinstance(self.operand, NameConstant): raise InvalidType("Node contains invalid field(s) for evaluation", self) if isinstance(self.op, USub) and not isinstance(self.operand, (Int, Decimal)): raise InvalidType("Node contains invalid field(s) for evaluation", self) value = self.op._op(self.operand.value) return type(self.operand).from_node(self, value=value)
def has_dynamic_data(typ): if isinstance(typ, BaseType): return False elif isinstance(typ, ByteArrayLike): return True elif isinstance(typ, ListType): return has_dynamic_data(typ.subtype) elif isinstance(typ, TupleLike): return any([has_dynamic_data(v) for v in typ.tuple_members()]) else: raise InvalidType(f"Unexpected type: {repr(typ)}")
def evaluate(self) -> VyperNode: """ Attempt to evaluate the boolean operation. Returns ------- NameConstant Node representing the result of the evaluation. """ if next((i for i in self.values if not isinstance(i, NameConstant)), None): raise InvalidType("Node contains invalid field(s) for evaluation", self) values = [i.value for i in self.values] if None in values: raise InvalidType("Node contains invalid field(s) for evaluation", self) value = self.op._op(values) return NameConstant.from_node(self, value=value)
def evaluate(self) -> VyperNode: """ Attempt to evaluate the comparison. Returns ------- NameConstant Node representing the result of the evaluation. """ left, right = self.left, self.right if not isinstance(left, Constant): raise InvalidType("Node contains invalid field(s) for evaluation", self) if isinstance(self.op, In): if not isinstance(right, List): raise InvalidType( "Node contains invalid field(s) for evaluation", self) if next((i for i in right.elts if not isinstance(i, Constant)), None): raise InvalidType( "Node contains invalid field(s) for evaluation", self) if len(set([type(i) for i in right.elts])) > 1: raise InvalidType("List contains multiple literal types", self.right) value = self.op._op(left.value, [i.value for i in right.elts]) return NameConstant.from_node(self, value=value) if not isinstance(left, type(right)): raise InvalidType("Cannot compare different literal types", self) if not isinstance(self.op, (Eq, NotEq)) and not isinstance( left, (Int, Decimal)): raise TypeMismatch( f"Invalid literal types for {self.op.description} comparison", self) value = self.op._op(left.value, right.value) return NameConstant.from_node(self, value=value)
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
def evaluate(self) -> VyperNode: """ Attempt to evaluate the subscript. This method reduces an indexed reference to a literal array into the value within the array, e.g. `["foo", "bar"][1]` becomes `"bar"` Returns ------- VyperNode Node representing the result of the evaluation. """ if not isinstance(self.value, List): raise InvalidType("Subscript object is not a literal list", self.value) elts = self.value.elts if len(set([type(i) for i in elts])) > 1: raise InvalidType("List contains multiple node types", self.value) idx = self.slice.get('value.value') if not isinstance(idx, int) or idx < 0 or idx >= len(elts): raise InvalidType("Invalid index value", self.slice) return elts[idx]
def canonicalize_type(t, is_indexed=False): if isinstance(t, ByteArrayLike): # Check to see if maxlen is small enough for events byte_type = "string" if isinstance(t, StringType) else "bytes" return byte_type if isinstance(t, ListType): if not isinstance(t.subtype, (ListType, BaseType)): raise InvalidType(f"List of {t.subtype} not allowed") return canonicalize_type(t.subtype) + f"[{t.count}]" if isinstance(t, TupleLike): return f"({','.join(canonicalize_type(x) for x in t.tuple_members())})" if not isinstance(t, BaseType): raise InvalidType(f"Cannot canonicalize non-base type: {t}") t = t.typ if t in ("int128", "uint256", "bool", "address", "bytes32"): return t elif t == "decimal": return "fixed168x10" raise InvalidType(f"Invalid or unsupported type: {repr(t)}")
def check_input_type(expr, arg, out_typ): # convert arg to out_typ. # (expr is the AST corresponding to `arg`) ityp = _type_class_of(arg.typ) ok = ityp in allowed_types if not ok: _FAIL(arg.typ, out_typ, expr) # user safety: disallow convert from type to itself # note allowance of [u]int256; this is due to type inference # on literals not quite working yet. if arg.typ == out_typ and not is_base_type(arg.typ, ("uint256", "int256")): raise InvalidType(f"value and target are both {out_typ}", expr) return f(expr, arg, out_typ)
def _basetype_to_abi_type(t: "BaseType") -> ABIType: if is_integer_type(t): typinfo = parse_integer_typeinfo(t.typ) return ABI_GIntM(typinfo.bits, typinfo.is_signed) if t.typ == "address": return ABI_Address() if t.typ == "bytes32": # TODO must generalize to more bytes types return ABI_BytesM(32) if t.typ == "bool": return ABI_Bool() if t.typ == "decimal": return ABI_FixedMxN(168, 10, True) raise InvalidType(f"Unrecognized type {t}") # pragma: notest
def _basetype_to_abi_type(t: "BaseType") -> ABIType: if is_integer_type(t): info = t._int_info return ABI_GIntM(info.bits, info.is_signed) if is_decimal_type(t): info = t._decimal_info return ABI_FixedMxN(info.bits, info.decimals, signed=True) if is_bytes_m_type(t): return ABI_BytesM(t._bytes_info.m) if t.typ == "address": return ABI_Address() if t.typ == "bool": return ABI_Bool() raise InvalidType(f"Unrecognized type {t}") # pragma: notest