def LOAD(ptr: IRnode) -> IRnode: if ptr.location is None: raise CompilerPanic("cannot dereference non-pointer type") op = ptr.location.load_op if op is None: raise CompilerPanic(f"unreachable {ptr.location}") # pragma: notest return IRnode.from_list([op, ptr])
def calculate_largest_power(a: int, num_bits: int, is_signed: bool) -> int: """ For a given base `a`, compute the maximum power `b` that will not produce an overflow in the equation `a ** b` Arguments --------- a : int Base value for the equation `a ** b` num_bits : int The maximum number of bits that the resulting value must fit in is_signed : bool Is the operation being performed on signed integers? Returns ------- int Largest possible value for `b` where the result does not overflow `num_bits` """ if num_bits % 8: raise CompilerPanic("Type is not a modulo of 8") value_bits = num_bits - (1 if is_signed else 0) if a >= 2**value_bits: raise TypeCheckFailure("Value is too large and will always throw") elif a < -(2**value_bits): raise TypeCheckFailure("Value is too small and will always throw") a_is_negative = a < 0 a = abs(a) # No longer need to know if it's signed or not if a in (0, 1): raise CompilerPanic("Exponential operation is useless!") # NOTE: There is an edge case if `a` were left signed where the following # operation would not work (`ln(a)` is undefined if `a <= 0`) b = int( decimal.Decimal(value_bits) / (decimal.Decimal(a).ln() / decimal.Decimal(2).ln())) if b <= 1: return 1 # Value is assumed to be in range, therefore power of 1 is max # Do a bit of iteration to ensure we have the exact number num_iterations = 0 while a**(b + 1) < 2**value_bits: b += 1 num_iterations += 1 assert num_iterations < 10000 while a**b >= 2**value_bits: b -= 1 num_iterations += 1 assert num_iterations < 10000 # Edge case: If a is negative and the values of a and b are such that: # (a) ** (b + 1) == -(2 ** value_bits) # we can actually squeak one more out of it because it's on the edge if a_is_negative and (-a)**(b + 1) == -(2**value_bits): # NOTE: a = abs(a) return b + 1 else: return b # Exact
def clamp_basetype(lll_node): t = lll_node.typ if not isinstance(t, BaseType): raise CompilerPanic(f"{t} passed to clamp_basetype") # pragma: notest # copy of the input lll_node = unwrap_location(lll_node) if t.typ in ("int128"): return int_clamp(lll_node, 128, signed=True) if t.typ == "uint8": return int_clamp(lll_node, 8) if t.typ in ("decimal"): return [ "clamp", ["mload", MemoryPositions.MINDECIMAL], lll_node, ["mload", MemoryPositions.MAXDECIMAL], ] if t.typ in ("address", ): return int_clamp(lll_node, 160) if t.typ in ("bool", ): return int_clamp(lll_node, 1) if t.typ in ("int256", "uint256", "bytes32"): return lll_node # special case, no clamp. raise CompilerPanic(f"{t} passed to clamp_basetype") # pragma: notest
def clamp_basetype(ir_node): t = ir_node.typ if not isinstance(t, BaseType): raise CompilerPanic(f"{t} passed to clamp_basetype") # pragma: notest # copy of the input ir_node = unwrap_location(ir_node) if isinstance(t, EnumType): bits = len(t.members) # assert x >> bits == 0 ret = int_clamp(ir_node, bits, signed=False) elif is_integer_type(t) or is_decimal_type(t): if t._num_info.bits == 256: ret = ir_node else: ret = int_clamp(ir_node, t._num_info.bits, signed=t._num_info.is_signed) elif is_bytes_m_type(t): if t._bytes_info.m == 32: ret = ir_node # special case, no clamp. else: ret = bytes_clamp(ir_node, t._bytes_info.m) elif t.typ in ("address", ): ret = int_clamp(ir_node, 160) elif t.typ in ("bool", ): ret = int_clamp(ir_node, 1) else: # pragma: nocover raise CompilerPanic(f"{t} passed to clamp_basetype") return IRnode.from_list(ret, typ=ir_node.typ)
def abi_type_of(lll_typ): if isinstance(lll_typ, BaseType): t = lll_typ.typ if "uint256" == t: return ABI_GIntM(256, False) elif "int128" == t: return ABI_GIntM(128, True) elif "address" == t: return ABI_Address() elif "bytes32" == t: return ABI_BytesM(32) elif "bool" == t: return ABI_Bool() elif "decimal" == t: return ABI_FixedMxN(168, 10, True) else: raise CompilerPanic(f"Unrecognized type {t}") elif isinstance(lll_typ, TupleLike): return ABI_Tuple([abi_type_of(t) for t in lll_typ.tuple_members()]) elif isinstance(lll_typ, ListType): return ABI_StaticArray(abi_type_of(lll_typ.subtype), lll_typ.count) elif isinstance(lll_typ, ByteArrayType): return ABI_Bytes(lll_typ.maxlen) elif isinstance(lll_typ, StringType): return ABI_String(lll_typ.maxlen) else: raise CompilerPanic(f"Unrecognized type {lll_typ}")
def clamp_basetype(ir_node): t = ir_node.typ if not isinstance(t, BaseType): raise CompilerPanic(f"{t} passed to clamp_basetype") # pragma: notest # copy of the input ir_node = unwrap_location(ir_node) if is_integer_type(t) or is_decimal_type(t): if t._num_info.bits == 256: return ir_node else: return int_clamp(ir_node, t._num_info.bits, signed=t._num_info.is_signed) if is_bytes_m_type(t): if t._bytes_info.m == 32: return ir_node # special case, no clamp. else: return bytes_clamp(ir_node, t._bytes_info.m) if t.typ in ("address", ): return int_clamp(ir_node, 160) if t.typ in ("bool", ): return int_clamp(ir_node, 1) raise CompilerPanic(f"{t} passed to clamp_basetype") # pragma: notest
def _get_element_ptr_tuplelike(parent, key, pos): typ = parent.typ assert isinstance(typ, TupleLike) if isinstance(typ, StructType): assert isinstance(key, str) subtype = typ.members[key] attrs = list(typ.tuple_keys()) index = attrs.index(key) annotation = key else: assert isinstance(key, int) subtype = typ.members[key] attrs = list(range(len(typ.members))) index = key annotation = None # generated by empty() + make_setter if parent.value == "~empty": return LLLnode.from_list("~empty", typ=subtype) if parent.value == "multi": assert parent.encoding != Encoding.ABI, "no abi-encoded literals" return parent.args[index] ofst = 0 # offset from parent start if parent.encoding in (Encoding.ABI, Encoding.JSON_ABI): if parent.location == "storage": raise CompilerPanic("storage variables should not be abi encoded" ) # pragma: notest member_t = typ.members[attrs[index]] for i in range(index): member_abi_t = typ.members[attrs[i]].abi_type ofst += member_abi_t.embedded_static_size() return _getelemptr_abi_helper(parent, member_t, ofst, pos) if parent.location == "storage": for i in range(index): ofst += typ.members[attrs[i]].storage_size_in_words elif parent.location in ("calldata", "memory", "code"): for i in range(index): ofst += typ.members[attrs[i]].memory_bytes_required else: raise CompilerPanic( f"bad location {parent.location}") # pragma: notest return LLLnode.from_list( add_ofst(parent, ofst), typ=subtype, location=parent.location, encoding=parent.encoding, annotation=annotation, pos=pos, )
def set_position(self, position: DataPosition) -> None: if hasattr(self, "position"): raise CompilerPanic("Position was already assigned") if self.location != position._location: if self.location == DataLocation.UNSET: self.location = position._location else: raise CompilerPanic("Incompatible locations") self.position = position
def __init__(self, m_bits, n_places, signed): if not (0 < m_bits <= 256 and 0 == m_bits % 8): raise CompilerPanic("Invalid M for FixedMxN") if not (0 < n_places and n_places <= 80): raise CompilerPanic("Invalid N for FixedMxN") self.m_bits = m_bits self.n_places = n_places self.signed = signed
def STORE(ptr: IRnode, val: IRnode) -> IRnode: if ptr.location is None: raise CompilerPanic("cannot dereference non-pointer type") op = ptr.location.store_op if op is None: raise CompilerPanic(f"unreachable {ptr.location}") # pragma: notest _check = _freshname(f"{op}_") return IRnode.from_list(["seq", eval_once_check(_check), [op, ptr, val]])
def set_reentrancy_key_position(self, position: StorageSlot) -> None: if hasattr(self, "reentrancy_key_position"): raise CompilerPanic("Position was already assigned") if self.nonreentrant is None: raise CompilerPanic(f"No reentrant key {self}") # sanity check even though implied by the type if position._location != DataLocation.STORAGE: raise CompilerPanic("Non-storage reentrant key") self.reentrancy_key_position = position
def set_min_length(self, min_length): """ Sets the minimum length of the type. May only be used to increase the minimum length. May not be called if an exact length has been set. """ if self._length: raise CompilerPanic("Type already has a fixed length") if self._min_length > min_length: raise CompilerPanic("Cannot reduce the min_length of ArrayValueType") self._min_length = min_length
def replace_in_tree(self, old_node: VyperNode, new_node: VyperNode) -> None: """ Perform an in-place substitution of a node within the tree. Parameters ---------- old_node : VyperNode Node object to be replaced. If the node does not currently exist within the AST, a `CompilerPanic` is raised. new_node : VyperNode Node object to replace new_node. Returns ------- None """ parent = old_node._parent if old_node not in self.get_descendants(type(old_node)): raise CompilerPanic( "Node to be replaced does not exist within the tree") if old_node not in parent._children: raise CompilerPanic( "Node to be replaced does not exist within parent children") is_replaced = False for key in parent.get_fields(): obj = getattr(parent, key, None) if obj == old_node: if is_replaced: raise CompilerPanic( "Node to be replaced exists as multiple members in parent" ) setattr(parent, key, new_node) is_replaced = True elif isinstance(obj, list) and obj.count(old_node): if is_replaced or obj.count(old_node) > 1: raise CompilerPanic( "Node to be replaced exists as multiple members in parent" ) obj[obj.index(old_node)] = new_node is_replaced = True if not is_replaced: raise CompilerPanic( "Node to be replaced does not exist within parent members") parent._children.remove(old_node) new_node._parent = parent new_node._depth = old_node._depth parent._children.add(new_node)
def bytes_clamp(ir_node: IRnode, n_bytes: int) -> IRnode: if not (0 < n_bytes <= 32): raise CompilerPanic(f"bad type: bytes{n_bytes}") with ir_node.cache_when_complex("val") as (b, val): assertion = ["assert", ["iszero", shl(n_bytes * 8, val)]] ret = b.resolve(["seq", assertion, val]) return IRnode.from_list(ret, annotation=f"bytes{n_bytes}_clamp")
def int_clamp(ir_node, bits, signed=False): """Generalized clamper for integer types. Takes the number of bits, whether it's signed, and returns an IR node which checks it is in bounds. (Consumers should use clamp_basetype instead which uses type-based dispatch and is a little safer.) """ if bits >= 256: raise CompilerPanic( f"invalid clamp: {bits}>=256 ({ir_node})") # pragma: notest with ir_node.cache_when_complex("val") as (b, val): if signed: # example for bits==128: # promote_signed_int(val, bits) is the "canonical" version of val # if val is in bounds, the bits above bit 128 should be equal. # (this works for both val >= 0 and val < 0. in the first case, # all upper bits should be 0 if val is a valid int128, # in the latter case, all upper bits should be 1.) assertion = ["assert", ["eq", val, promote_signed_int(val, bits)]] else: assertion = ["assert", ["iszero", shr(bits, val)]] ret = b.resolve(["seq", assertion, val]) # TODO fix this annotation return IRnode.from_list(ret, annotation=f"int_clamp {ir_node.typ}")
def STORE(ptr: IRnode, val: IRnode) -> IRnode: if ptr.location is None: raise CompilerPanic("cannot dereference non-pointer type") op = ptr.location.store_op if op is None: raise CompilerPanic(f"unreachable {ptr.location}") # pragma: notest _check = _freshname(f"{op}_") store = [op, ptr, val] # don't use eval_once_check for memory, immutables because it interferes # with optimizer if ptr.location in (MEMORY, IMMUTABLES): return IRnode.from_list(store) return IRnode.from_list(["seq", eval_once_check(_check), store])
def increase_memory(self, size: int) -> Tuple[int, int]: if size % 32 != 0: raise CompilerPanic( 'Memory misaligment, only multiples of 32 supported.') before_value = self.next_mem self.next_mem += size return before_value, self.next_mem
def _add_import( node: Union[vy_ast.Import, vy_ast.ImportFrom], module: str, name: str, alias: str, interface_codes: InterfaceDict, namespace: dict, ) -> None: if module == "vyper.interfaces": interface_codes = _get_builtin_interfaces() if name not in interface_codes: suggestions_str = get_levenshtein_error_suggestions(name, _get_builtin_interfaces(), 1.0) raise UndeclaredDefinition(f"Unknown interface: {name}. {suggestions_str}", node) if interface_codes[name]["type"] == "vyper": interface_ast = vy_ast.parse_to_ast(interface_codes[name]["code"], contract_name=name) type_ = namespace["interface"].build_primitive_from_node(interface_ast) elif interface_codes[name]["type"] == "json": type_ = namespace["interface"].build_primitive_from_abi(name, interface_codes[name]["code"]) else: raise CompilerPanic(f"Unknown interface format: {interface_codes[name]['type']}") try: namespace[alias] = type_ except VyperException as exc: raise exc.with_annotation(node) from None
def parse_Int(self): typ_ = self.expr._metadata.get("type") if typ_ is None: raise CompilerPanic("Type of integer literal is unknown") new_typ = new_type_to_old_type(typ_) new_typ.is_literal = True return IRnode.from_list(self.expr.n, typ=new_typ)
def allocate_memory(self, size: int) -> int: """ Allocate `size` bytes in memory. *** No guarantees are made that allocated memory is clean! *** If no memory was previously de-allocated, memory is expanded and the free memory pointer is increased. If sufficient space is available within de-allocated memory, the lowest available offset is returned and that memory is now marked as allocated. Arguments --------- size : int The number of bytes to allocate. Must be divisible by 32. Returns ------- int Start offset of the newly allocated memory. """ if size % 32 != 0: raise CompilerPanic("Memory misaligment, only multiples of 32 supported.") # check for deallocated memory prior to expanding for i, free_memory in enumerate(self.deallocated_mem): if free_memory.size == size: del self.deallocated_mem[i] return free_memory.position if free_memory.size > size: return free_memory.partially_allocate(size) # if no deallocated slots are available, expand memory return self.expand_memory(size)
def _flip_comparison_op(opname): assert opname in COMPARISON_OPS if "g" in opname: return opname.replace("g", "l") if "l" in opname: return opname.replace("l", "g") raise CompilerPanic(f"bad comparison op {opname}") # pragma: notest
def visit_Num(self, node): """ Adjust numeric node class based on the value type. Python uses `Num` to represent floats and integers. Integers may also be given in binary, octal, decimal, or hexadecimal format. This method modifies `ast_type` to seperate `Num` into more granular Vyper node classes. """ # modify vyper AST type according to the format of the literal value self.generic_visit(node) value = node.node_source_code # deduce non base-10 types based on prefix literal_prefixes = {'0x': "Hex", '0b': "Binary", '0o': "Octal"} if value.lower()[:2] in literal_prefixes: node.ast_type = literal_prefixes[value.lower()[:2]] node.n = value elif isinstance(node.n, float): node.ast_type = "Decimal" node.n = Decimal(value) elif isinstance(node.n, int): node.ast_type = "Int" else: raise CompilerPanic(f"Unexpected type for Constant value: {type(node.n).__name__}") return node
def lazy_abi_decode(typ, src, pos=None): if isinstance(typ, (ListType, TupleLike)): if isinstance(typ, TupleLike): ts = typ.tuple_members() else: ts = [typ.subtyp for _ in range(typ.count)] ofst = 0 os = [] for t in ts: child_abi_t = abi_type_of(t) loc = _add_ofst(src, ofst) if child_abi_t.is_dynamic(): # load the offset word, which is the # (location-independent) offset from the start of the # src buffer. dyn_ofst = unwrap_location(ofst) loc = _add_ofst(src, dyn_ofst) os.append(lazy_abi_decode(t, loc, pos)) ofst += child_abi_t.embedded_static_size() return LLLnode.from_list(["multi"] + os, typ=typ, pos=pos) elif isinstance(typ, (BaseType, ByteArrayLike)): return unwrap_location(src) else: raise CompilerPanic(f"unknown type for lazy_abi_decode {typ}")
def from_AnnAssign(cls, node: vy_ast.AnnAssign) -> "ContractFunction": """ Generate a `ContractFunction` object from an `AnnAssign` node. Used to create function definitions 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), return_type, function_visibility=FunctionVisibility.EXTERNAL, state_mutability=StateMutability.NONPAYABLE, )
def _add_import( node: Union[vy_ast.Import, vy_ast.ImportFrom], module: str, name: str, interface_codes: InterfaceDict, namespace: dict, ) -> None: if module == "vyper.interfaces": interface_codes = _get_builtin_interfaces() if name not in interface_codes: raise UndeclaredDefinition(f"Unknown interface: {name}", node) try: if interface_codes[name]["type"] == "vyper": interface_ast = vy_ast.parse_to_ast(interface_codes[name]["code"]) interface_ast.name = name type_ = namespace["interface"].build_primitive_from_node(interface_ast) elif interface_codes[name]["type"] == "json": type_ = namespace["interface"].build_primitive_from_abi( name, interface_codes[name]["code"] ) else: raise CompilerPanic(f"Unknown interface format: {interface_codes[name]['type']}") namespace[name] = type_ except VyperException as exc: raise exc.with_annotation(node) from None
def validate_call_args(node: vy_ast.Call, arg_count: Union[int, tuple], kwargs: Optional[list] = None) -> None: """ Validate positional and keyword arguments of a Call node. This function does not handle type checking of arguments, it only checks correctness of the number of arguments given and keyword names. Arguments --------- node : Call Vyper ast Call node to be validated. arg_count : int | tuple The required number of positional arguments. When given as a tuple the value is interpreted as the minimum and maximum number of arguments. kwargs : list, optional A list of valid keyword arguments. When arg_count is a tuple and the number of positional arguments exceeds the minimum, the excess values are considered to fill the first values on this list. Returns ------- None. Raises an exception when the arguments are invalid. """ if kwargs is None: kwargs = [] if not isinstance(node, vy_ast.Call): raise StructureException("Expected Call", node) if not isinstance(arg_count, (int, tuple)): raise CompilerPanic( f"Invalid type for arg_count: {type(arg_count).__name__}") if isinstance(arg_count, int) and len(node.args) != arg_count: raise ArgumentException( f"Invalid argument count: expected {arg_count}, got {len(node.args)}", node) elif (isinstance(arg_count, tuple) and not arg_count[0] <= len(node.args) <= arg_count[1]): raise ArgumentException( f"Invalid argument count: expected between " f"{arg_count[0]} and {arg_count[1]}, got {len(node.args)}", node, ) if not kwargs and node.keywords: raise ArgumentException("Keyword arguments are not accepted here", node.keywords[0]) for key in node.keywords: if key.arg is None: raise StructureException("Use of **kwargs is not supported", key.value) if key.arg not in kwargs: raise ArgumentException(f"Invalid keyword argument '{key.arg}'", key) if (isinstance(arg_count, tuple) and kwargs.index(key.arg) < len(node.args) - arg_count[0]): raise ArgumentException( f"'{key.arg}' was given as a positional argument", key)
def __init__( self, typ, unit=False, positional=False, override_signature=False, is_literal=False ): self.typ = typ if unit or positional: raise CompilerPanic("Units are no longer supported") self.override_signature = override_signature self.is_literal = is_literal
def storage_size_in_words(self) -> int: """ Returns the number of words required to allocate in storage for this type """ r = self.memory_bytes_required if r % 32 != 0: raise CompilerPanic("Memory bytes must be multiple of 32") return r // 32
def needs_clamp(t, encoding): if encoding == Encoding.VYPER: return False if encoding != Encoding.ABI: raise CompilerPanic("unreachable") # pragma: notest if isinstance(t, (ByteArrayLike, DArrayType)): return True if isinstance(t, EnumType): return len(t.members) < 256 if isinstance(t, BaseType): return t.typ not in ("int256", "uint256", "bytes32") if isinstance(t, SArrayType): return needs_clamp(t.subtype, encoding) if isinstance(t, TupleLike): return any(needs_clamp(m, encoding) for m in t.tuple_members()) raise CompilerPanic("unreachable") # pragma: notest
def pp_constancy(self): if self.in_assertion: return "an assertion" elif self.in_range_expr: return "a range expression" elif self.constancy == Constancy.Constant: return "a constant function" raise CompilerPanic(f"unknown constancy in pp_constancy: {self.constancy}")