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 _get_target(self, target): # Check if we are doing assignment of an iteration loop. if isinstance(target, vy_ast.Subscript) and self.context.in_for_loop: raise_exception = False if isinstance(target.value, vy_ast.Attribute): if f"{target.value.value.id}.{target.value.attr}" in self.context.in_for_loop: raise_exception = True if target.get("value.id") in self.context.in_for_loop: raise_exception = True if raise_exception: raise TypeCheckFailure("Failed for-loop constancy check") if isinstance(target, vy_ast.Name) and target.id in self.context.forvars: raise TypeCheckFailure("Failed for-loop constancy check") if isinstance(target, vy_ast.Tuple): target = Expr(target, self.context).lll_node for node in target.args: if (node.location == "storage" and self.context.is_constant()) or not node.mutable: raise TypeCheckFailure("Failed for-loop constancy check") return target target = Expr.parse_variable_location(target, self.context) if (target.location == "storage" and self.context.is_constant()) or not target.mutable: raise TypeCheckFailure("Failed for-loop constancy check") return target
def __init__(self, node: vy_ast.VyperNode, context: Context) -> None: self.stmt = node self.context = context fn = getattr(self, f"parse_{type(node).__name__}", None) if fn is None: raise TypeCheckFailure( f"Invalid statement node: {type(node).__name__}") self.lll_node = fn() if self.lll_node is None: raise TypeCheckFailure("Statement node did not produce LLL")
def calculate_largest_base(b: int, num_bits: int, is_signed: bool) -> int: """ For a given power `b`, compute the maximum base `a` that will not produce an overflow in the equation `a ** b` Arguments --------- b : int Power 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 `a` where the result does not overflow `num_bits` """ if num_bits % 8: raise CompilerPanic("Type is not a modulo of 8") if b < 0: raise TypeCheckFailure("Cannot calculate negative exponents") value_bits = num_bits - (1 if is_signed else 0) if b > value_bits: raise TypeCheckFailure("Value is too large and will always throw") elif b < 2: return 2**value_bits - 1 # Maximum value for type # CMC 2022-05-06 TODO we should be able to do this with algebra # instead of looping): # x ** b == 2**value_bits # b ln(x) == ln(2**value_bits) # ln(x) == ln(2**value_bits) / b # x == exp( ln(2**value_bits) / b) # Estimate (up to ~39 digits precision required) a = math.ceil(2**(decimal.Decimal(value_bits) / decimal.Decimal(b))) # Do a bit of iteration to ensure we have the exact number num_iterations = 0 while (a + 1)**b < 2**value_bits: a += 1 num_iterations += 1 assert num_iterations < 10000 while a**b >= 2**value_bits: a -= 1 num_iterations += 1 assert num_iterations < 10000 return a
def __init__(self, node: vy_ast.VyperNode, context: Context) -> None: self.stmt = node self.context = context fn = getattr(self, f"parse_{type(node).__name__}", None) if fn is None: raise TypeCheckFailure(f"Invalid statement node: {type(node).__name__}") with context.internal_memory_scope(): self.lll_node = fn() if self.lll_node is None: raise TypeCheckFailure("Statement node did not produce LLL") self.lll_node.annotation = self.stmt.get("node_source_code")
def _get_target(self, target): if isinstance(target, vy_ast.Name) and target.id in self.context.forvars: raise TypeCheckFailure("Failed for-loop constancy check") if isinstance(target, vy_ast.Tuple): target = Expr(target, self.context).lll_node for node in target.args: if (node.location == "storage" and self.context.is_constant()) or not node.mutable: raise TypeCheckFailure("Failed for-loop constancy check") return target target = Expr.parse_variable_location(target, self.context) if (target.location == "storage" and self.context.is_constant()) or not target.mutable: raise TypeCheckFailure("Failed for-loop constancy check") return target
def get_external_call_output(sig, context): if not sig.output_type: return 0, 0, [] output_placeholder = context.new_internal_variable(typ=sig.output_type) output_size = get_size_of_type(sig.output_type) * 32 if isinstance(sig.output_type, BaseType): returner = [0, output_placeholder] elif isinstance(sig.output_type, ByteArrayLike): returner = [0, output_placeholder + 32] elif isinstance(sig.output_type, TupleLike): # incase of struct we need to decode the output and then return it returner = ["seq"] decoded_placeholder = context.new_internal_variable( typ=sig.output_type) decoded_node = LLLnode(decoded_placeholder, typ=sig.output_type, location="memory") output_node = LLLnode(output_placeholder, typ=sig.output_type, location="memory") returner.append(abi_decode(decoded_node, output_node)) returner.extend([0, decoded_placeholder]) elif isinstance(sig.output_type, ListType): returner = [0, output_placeholder] else: raise TypeCheckFailure(f"Invalid output type: {sig.output_type}") return output_placeholder, output_size, returner
def _parse_kwargs(call_expr, context): from vyper.codegen.expr import Expr # TODO rethink this circular import def _bool(x): assert x.value in (0, 1), "type checker missed this" return bool(x.value) # note: codegen for kwarg values in AST order call_kwargs = { kw.arg: Expr(kw.value, context).ir_node for kw in call_expr.keywords } ret = _CallKwargs( value=unwrap_location(call_kwargs.pop("value", IRnode(0))), gas=unwrap_location(call_kwargs.pop("gas", IRnode("gas"))), skip_contract_check=_bool( call_kwargs.pop("skip_contract_check", IRnode(0))), default_return_value=call_kwargs.pop("default_return_value", None), ) if len(call_kwargs) != 0: raise TypeCheckFailure(f"Unexpected keyword arguments: {call_kwargs}") return ret
def _check_assign_list(left, right): def FAIL(): # pragma: nocover raise TypeCheckFailure(f"assigning {right.typ} to {left.typ}") if left.value == "multi": # Cannot do something like [a, b, c] = [1, 2, 3] FAIL() # pragma: notest if isinstance(left, SArrayType): if not isinstance(right, SArrayType): FAIL() # pragma: notest if left.typ.count != right.typ.count: FAIL() # pragma: notest # TODO recurse into left, right if literals? check_assign(dummy_node_for_type(left.typ.subtyp), dummy_node_for_type(right.typ.subtyp)) if isinstance(left, DArrayType): if not isinstance(right, DArrayType): FAIL() # pragma: notest if left.typ.count < right.typ.count: FAIL() # pragma: notest # stricter check for zeroing if right.value == "~empty" and right.typ.count != left.typ.count: raise TypeCheckFailure( f"Bad type for clearing bytes: expected {left.typ} but got {right.typ}" ) # pragma: notest # TODO recurse into left, right if literals? check_assign(dummy_node_for_type(left.typ.subtyp), dummy_node_for_type(right.typ.subtyp))
def __init__(self, node, context): self.expr = node self.context = context if isinstance(node, LLLnode): # TODO this seems bad self.lll_node = node return fn = getattr(self, f"parse_{type(node).__name__}", None) if fn is None: raise TypeCheckFailure(f"Invalid statement node: {type(node).__name__}") self.lll_node = fn() if self.lll_node is None: raise TypeCheckFailure(f"{type(node).__name__} node did not produce LLL")
def _call_make_placeholder(stmt_expr, context, sig): if sig.output_type is None: return 0, 0, 0 output_placeholder = context.new_placeholder(typ=sig.output_type) output_size = get_size_of_type(sig.output_type) * 32 if isinstance(sig.output_type, BaseType): returner = output_placeholder elif isinstance(sig.output_type, ByteArrayLike): returner = output_placeholder elif isinstance(sig.output_type, TupleLike): # incase of struct we need to decode the output and then return it returner = ["seq"] decoded_placeholder = context.new_placeholder(typ=sig.output_type) decoded_node = LLLnode(decoded_placeholder, typ=sig.output_type, location="memory") output_node = LLLnode(output_placeholder, typ=sig.output_type, location="memory") returner.append(abi_decode(decoded_node, output_node)) returner.extend([decoded_placeholder]) elif isinstance(sig.output_type, ListType): returner = output_placeholder else: raise TypeCheckFailure(f"Invalid output type: {sig.output_type}") return output_placeholder, returner, output_size
def safe_pow(x, y): num_info = x.typ._num_info if not is_integer_type(x.typ): # type checker should have caught this raise TypeCheckFailure("non-integer pow") if x.is_literal: # cannot pass 1 or 0 to `calculate_largest_power` if x.value == 1: return IRnode.from_list([1]) if x.value == 0: return IRnode.from_list(["iszero", y]) upper_bound = calculate_largest_power(x.value, num_info.bits, num_info.is_signed) + 1 # for signed integers, this also prevents negative values ok = ["lt", y, upper_bound] elif y.is_literal: upper_bound = calculate_largest_base(y.value, num_info.bits, num_info.is_signed) + 1 if num_info.is_signed: ok = ["and", ["slt", x, upper_bound], ["sgt", x, -upper_bound]] else: ok = ["lt", x, upper_bound] else: # `a ** b` where neither `a` or `b` are known # TODO this is currently unreachable, once we implement a way to do it safely # remove the check in `vyper/context/types/value/numeric.py` return return IRnode.from_list(["seq", ["assert", ok], ["exp", x, y]])
def _get_target(self, target): _dbg_expr = target if isinstance(target, vy_ast.Name) and target.id in self.context.forvars: raise TypeCheckFailure(f"Failed constancy check\n{_dbg_expr}") if isinstance(target, vy_ast.Tuple): target = Expr(target, self.context).lll_node for node in target.args: if (node.location == "storage" and self.context.is_constant()) or not node.mutable: raise TypeCheckFailure(f"Failed constancy check\n{_dbg_expr}") return target target = Expr.parse_pointer_expr(target, self.context) if (target.location == "storage" and self.context.is_constant()) or not target.mutable: raise TypeCheckFailure(f"Failed constancy check\n{_dbg_expr}") return target
def parse_BoolOp(self): for value in self.expr.values: # Check for boolean operations with non-boolean inputs _expr = Expr.parse_value_expr(value, self.context) if not is_base_type(_expr.typ, "bool"): return def _build_if_lll(condition, true, false): # generate a basic if statement in LLL o = ["if", condition, true, false] return o jump_label = f"_boolop_{self.expr.src}" if isinstance(self.expr.op, vy_ast.And): if len(self.expr.values) == 2: # `x and y` is a special case, it doesn't require jumping lll_node = _build_if_lll( Expr.parse_value_expr(self.expr.values[0], self.context), Expr.parse_value_expr(self.expr.values[1], self.context), [0], ) return LLLnode.from_list(lll_node, typ="bool") # create the initial `x and y` from the final two values lll_node = _build_if_lll( Expr.parse_value_expr(self.expr.values[-2], self.context), Expr.parse_value_expr(self.expr.values[-1], self.context), [0], ) # iterate backward through the remaining values for node in self.expr.values[-3::-1]: lll_node = _build_if_lll( Expr.parse_value_expr(node, self.context), lll_node, [0, ["goto", jump_label]]) elif isinstance(self.expr.op, vy_ast.Or): # create the initial `x or y` from the final two values lll_node = _build_if_lll( Expr.parse_value_expr(self.expr.values[-2], self.context), [1, ["goto", jump_label]], Expr.parse_value_expr(self.expr.values[-1], self.context), ) # iterate backward through the remaining values for node in self.expr.values[-3::-1]: lll_node = _build_if_lll( Expr.parse_value_expr(node, self.context), [1, ["goto", jump_label]], lll_node, ) else: raise TypeCheckFailure( f"Unexpected boolean operator: {type(self.expr.op).__name__}") # add `jump_label` at the end of the boolop lll_node = ["seq_unchecked", lll_node, ["label", jump_label]] return LLLnode.from_list(lll_node, typ="bool")
def is_valid_varname(self, name, pos): # Global context check first. if self.global_ctx.is_valid_varname(name, pos): check_valid_varname( name, custom_structs=self.structs, constants=self.constants, pos=pos, ) # Local context duplicate context check. if any((name in self.vars, name in self.globals, name in self.constants)): raise TypeCheckFailure(f"Duplicate variable name: {name}") return True
def _get_element_ptr_mapping(parent, key): assert isinstance(parent.typ, MappingType) subtype = parent.typ.valuetype key = unwrap_location(key) # TODO when is key None? if key is None or parent.location != STORAGE: raise TypeCheckFailure(f"bad dereference on mapping {parent}[{key}]") return IRnode.from_list(["sha3_64", parent, key], typ=subtype, location=STORAGE)
def __init__(self, node, context): self.expr = node self.context = context if isinstance(node, IRnode): # TODO this seems bad self.ir_node = node return fn = getattr(self, f"parse_{type(node).__name__}", None) if fn is None: raise TypeCheckFailure( f"Invalid statement node: {type(node).__name__}") self.ir_node = fn() if self.ir_node is None: raise TypeCheckFailure( f"{type(node).__name__} node did not produce IR. {self.expr}") self.ir_node.annotation = self.expr.get("node_source_code") self.ir_node.source_pos = getpos(self.expr)
def get_external_interface_keywords(stmt_expr, context): from vyper.parser.expr import Expr value, gas = None, None for kw in stmt_expr.keywords: if kw.arg == "gas": gas = Expr.parse_value_expr(kw.value, context) elif kw.arg == "value": value = Expr.parse_value_expr(kw.value, context) else: raise TypeCheckFailure("Unexpected keyword argument") return value, gas
def from_declaration(cls, class_node, global_ctx): name = class_node.name pos = 0 check_valid_varname( name, global_ctx._structs, global_ctx._constants, pos=class_node, error_prefix="Event name invalid. ", exc=EventDeclarationException, ) args = [] indexed_list = [] if len(class_node.body) != 1 or not isinstance(class_node.body[0], vy_ast.Pass): for node in class_node.body: arg_item = node.target arg = node.target.id typ = node.annotation if isinstance(typ, vy_ast.Call) and typ.get("func.id") == "indexed": indexed_list.append(True) typ = typ.args[0] else: indexed_list.append(False) check_valid_varname( arg, global_ctx._structs, global_ctx._constants, pos=arg_item, error_prefix="Event argument name invalid or reserved.", ) if arg in (x.name for x in args): raise TypeCheckFailure( f"Duplicate function argument name: {arg}") # Can struct be logged? parsed_type = global_ctx.parse_type(typ, None) args.append(VariableRecord(arg, pos, parsed_type, False)) if isinstance(parsed_type, ByteArrayType): pos += ceil32(typ.slice.value.n) else: pos += get_size_of_type(parsed_type) * 32 sig = (name + "(" + ",".join([ canonicalize_type(arg.typ, indexed_list[pos]) for pos, arg in enumerate(args) ]) + ")") # noqa F812 event_id = bytes_to_int(keccak256(bytes(sig, "utf-8"))) return cls(name, args, indexed_list, event_id, sig)
def get_external_call_output(sig, context): if not sig.output_type: return 0, 0, [] output_placeholder = context.new_placeholder(typ=sig.output_type) output_size = get_size_of_type(sig.output_type) * 32 if isinstance(sig.output_type, BaseType): returner = [0, output_placeholder] elif isinstance(sig.output_type, ByteArrayLike): returner = [0, output_placeholder + 32] elif isinstance(sig.output_type, TupleLike): returner = [0, output_placeholder] elif isinstance(sig.output_type, ListType): returner = [0, output_placeholder] else: raise TypeCheckFailure(f"Invalid output type: {sig.output_type}") return output_placeholder, output_size, returner
def _get_special_kwargs(stmt_expr, context): from vyper.codegen.expr import Expr # TODO rethink this circular import value, gas, skip_contract_check = None, None, None for kw in stmt_expr.keywords: if kw.arg == "gas": gas = Expr.parse_value_expr(kw.value, context) elif kw.arg == "value": value = Expr.parse_value_expr(kw.value, context) elif kw.arg == "skip_contract_check": skip_contract_check = kw.value.value assert isinstance(skip_contract_check, bool), "type checker missed this" else: raise TypeCheckFailure("Unexpected keyword argument") # TODO maybe return a small dataclass to reduce verbosity return value, gas, skip_contract_check
def parse_BoolOp(self): for value in self.expr.values: # Check for boolean operations with non-boolean inputs _expr = Expr.parse_value_expr(value, self.context) if not is_base_type(_expr.typ, "bool"): return def _build_if_lll(condition, true, false): # generate a basic if statement in LLL o = ["if", condition, true, false] return o if isinstance(self.expr.op, vy_ast.And): # create the initial `x and y` from the final two values lll_node = _build_if_lll( Expr.parse_value_expr(self.expr.values[-2], self.context), Expr.parse_value_expr(self.expr.values[-1], self.context), [0], ) # iterate backward through the remaining values for node in self.expr.values[-3::-1]: lll_node = _build_if_lll( Expr.parse_value_expr(node, self.context), lll_node, [0]) elif isinstance(self.expr.op, vy_ast.Or): # create the initial `x or y` from the final two values lll_node = _build_if_lll( Expr.parse_value_expr(self.expr.values[-2], self.context), [1], Expr.parse_value_expr(self.expr.values[-1], self.context), ) # iterate backward through the remaining values for node in self.expr.values[-3::-1]: lll_node = _build_if_lll( Expr.parse_value_expr(node, self.context), 1, lll_node) else: raise TypeCheckFailure( f"Unexpected boolean operator: {type(self.expr.op).__name__}") return LLLnode.from_list(lll_node, typ="bool")
def _get_element_ptr_array(parent, key, array_bounds_check): assert isinstance(parent.typ, ArrayLike) if not is_integer_type(key.typ): raise TypeCheckFailure(f"{key.typ} used as array index") subtype = parent.typ.subtype if parent.value == "~empty": if array_bounds_check: # this case was previously missing a bounds check. codegen # is a bit complicated when bounds check is required, so # block it. there is no reason to index into a literal empty # array anyways! raise TypeCheckFailure("indexing into zero array not allowed") return IRnode.from_list("~empty", subtype) if parent.value == "multi": assert isinstance(key.value, int) return parent.args[key.value] ix = unwrap_location(key) if array_bounds_check: is_darray = isinstance(parent.typ, DArrayType) bound = get_dyn_array_count(parent) if is_darray else parent.typ.count # uclamplt works, even for signed ints. since two's-complement # is used, if the index is negative, (unsigned) LT will interpret # it as a very large number, larger than any practical value for # an array index, and the clamp will throw an error. # NOTE: there are optimization rules for this when ix or bound is literal ix = clamp("lt", ix, bound) if parent.encoding == Encoding.ABI: if parent.location == STORAGE: raise CompilerPanic("storage variables should not be abi encoded" ) # pragma: notest member_abi_t = subtype.abi_type ofst = _mul(ix, member_abi_t.embedded_static_size()) return _getelemptr_abi_helper(parent, subtype, ofst) if parent.location.word_addressable: element_size = subtype.storage_size_in_words elif parent.location.byte_addressable: element_size = subtype.memory_bytes_required else: raise CompilerPanic("unreachable") # pragma: notest ofst = _mul(ix, element_size) if has_length_word(parent.typ): data_ptr = add_ofst( parent, parent.location.word_scale * DYNAMIC_ARRAY_OVERHEAD) else: data_ptr = parent return IRnode.from_list(add_ofst(data_ptr, ofst), typ=subtype, location=parent.location)
def _wrapped(*args, **kwargs): return_value = fn(*args, **kwargs) if return_value is None: raise TypeCheckFailure(f"{fn.__name__} did not return a value") return return_value
def FAIL(): # pragma: nocover raise TypeCheckFailure( f"assigning {right.typ} to {left.typ} {left} {right}")
def calculate_largest_base(b: int, num_bits: int, is_signed: bool) -> Tuple[int, int]: """ For a given power `b`, compute the maximum base `a` that will not produce an overflow in the equation `a ** b` Arguments --------- b : int Power 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 ------- Tuple[int, int] Smallest and largest possible values for `a` where the result does not overflow `num_bits`. Note that the lower and upper bounds are not always negatives of each other, due to lower/upper bounds for int_<value_bits> being slightly asymmetric. """ if num_bits % 8: # pragma: no cover raise CompilerPanic("Type is not a modulo of 8") if b in (0, 1): # pragma: no cover raise CompilerPanic("Exponential operation is useless!") if b < 0: # pragma: no cover raise TypeCheckFailure("Cannot calculate negative exponents") value_bits = num_bits - (1 if is_signed else 0) if b > value_bits: # pragma: no cover raise TypeCheckFailure("Value is too large and will always throw") # CMC 2022-05-06 TODO we should be able to do this with algebra # instead of looping): # x ** b == 2**value_bits # b ln(x) == ln(2**value_bits) # ln(x) == ln(2**value_bits) / b # x == exp( ln(2**value_bits) / b) # Estimate (up to ~39 digits precision required) a = math.ceil(2**(decimal.Decimal(value_bits) / decimal.Decimal(b))) # Do a bit of iteration to ensure we have the exact number num_iterations = 0 while (a + 1)**b < 2**value_bits: a += 1 num_iterations += 1 assert num_iterations < 10000 while a**b >= 2**value_bits: a -= 1 num_iterations += 1 assert num_iterations < 10000 if not is_signed: return 0, a if (a + 1)**b == (2**value_bits): # edge case: lower bound is slightly wider than upper bound return -(a + 1), a else: return -a, a