def to_bytes32(expr, args, kwargs, context): in_arg = args[0] input_type, _len = get_type(in_arg) if input_type == "Bytes": if _len > 32: raise TypeMismatch( f"Unable to convert bytes[{_len}] to bytes32, max length is too " "large.") with in_arg.cache_when_complex("bytes") as (b1, in_arg): op = load_op(in_arg.location) ofst = wordsize(in_arg.location) * DYNAMIC_ARRAY_OVERHEAD bytes_val = [op, ["add", in_arg, ofst]] # zero out any dirty bytes (which can happen in the last # word of a bytearray) len_ = get_bytearray_length(in_arg) num_zero_bits = LLLnode.from_list(["mul", ["sub", 32, len_], 8]) with num_zero_bits.cache_when_complex("bits") as (b2, num_zero_bits): ret = shl(num_zero_bits, shr(num_zero_bits, bytes_val)) ret = b1.resolve(b2.resolve(ret)) else: # literal ret = in_arg return LLLnode.from_list(ret, typ="bytes32", pos=getpos(expr))
def _assert_reason(self, test_expr, msg): if isinstance(msg, vy_ast.Name) and msg.id == "UNREACHABLE": return LLLnode.from_list(["assert_unreachable", test_expr], typ=None, pos=getpos(msg)) # set constant so that revert reason str is well behaved try: tmp = self.context.constancy self.context.constancy = Constancy.Constant msg_lll = Expr(msg, self.context).lll_node finally: self.context.constancy = tmp # TODO this is probably useful in codegen.core # compare with eval_seq. def _get_last(lll): if len(lll.args) == 0: return lll.value return _get_last(lll.args[-1]) # TODO maybe use ensure_in_memory if msg_lll.location != "memory": buf = self.context.new_internal_variable(msg_lll.typ) instantiate_msg = make_byte_array_copier(buf, msg_lll) else: buf = _get_last(msg_lll) if not isinstance(buf, int): raise CompilerPanic(f"invalid bytestring {buf}\n{self}") instantiate_msg = msg_lll # offset of bytes in (bytes,) method_id = util.abi_method_id("Error(string)") # abi encode method_id + bytestring assert buf >= 36, "invalid buffer" # we don't mind overwriting other memory because we are # getting out of here anyway. _runtime_length = ["mload", buf] revert_seq = [ "seq", instantiate_msg, zero_pad(buf), ["mstore", buf - 64, method_id], ["mstore", buf - 32, 0x20], [ "revert", buf - 36, ["add", 4 + 32 + 32, ["ceil32", _runtime_length]] ], ] if test_expr is not None: lll_node = ["if", ["iszero", test_expr], revert_seq] else: lll_node = revert_seq return LLLnode.from_list(lll_node, typ=None, pos=getpos(self.stmt))
def to_uint256(expr, args, kwargs, context): in_arg = args[0] input_type, _ = get_type(in_arg) if input_type == "num_literal": if isinstance(in_arg, int): if not SizeLimits.in_bounds("uint256", in_arg): raise InvalidLiteral(f"Number out of range: {in_arg}") return LLLnode.from_list( in_arg, typ=BaseType("uint256", ), pos=getpos(expr), ) elif isinstance(in_arg, Decimal): if not SizeLimits.in_bounds("uint256", math.trunc(in_arg)): raise InvalidLiteral( f"Number out of range: {math.trunc(in_arg)}") return LLLnode.from_list(math.trunc(in_arg), typ=BaseType("uint256"), pos=getpos(expr)) else: raise InvalidLiteral(f"Unknown numeric literal type: {in_arg}") elif isinstance(in_arg, LLLnode) and input_type in ("int128", "int256"): return LLLnode.from_list(["clampge", in_arg, 0], typ=BaseType("uint256"), pos=getpos(expr)) elif isinstance(in_arg, LLLnode) and input_type == "decimal": return LLLnode.from_list( ["div", ["clampge", in_arg, 0], DECIMAL_DIVISOR], typ=BaseType("uint256"), pos=getpos(expr), ) elif isinstance(in_arg, LLLnode) and input_type in ("bool", "uint8"): return LLLnode.from_list(in_arg, typ=BaseType("uint256"), pos=getpos(expr)) elif isinstance(in_arg, LLLnode) and input_type in ("bytes32", "address"): return LLLnode(value=in_arg.value, args=in_arg.args, typ=BaseType("uint256"), pos=getpos(expr)) elif isinstance(in_arg, LLLnode) and input_type == "Bytes": if in_arg.typ.maxlen > 32: raise InvalidLiteral( f"Cannot convert bytes array of max length {in_arg.typ.maxlen} to uint256", expr, ) return byte_array_to_num(in_arg, "uint256") else: raise InvalidLiteral(f"Invalid input for uint256: {in_arg}", expr)
def _parse_For_list(self): with self.context.range_scope(): iter_list = Expr(self.stmt.iter, self.context).lll_node # override with type inferred at typechecking time # TODO investigate why stmt.target.type != stmt.iter.type.subtype target_type = new_type_to_old_type(self.stmt.target._metadata["type"]) iter_list.typ.subtype = target_type # user-supplied name for loop variable varname = self.stmt.target.id loop_var = LLLnode.from_list( self.context.new_variable(varname, target_type), typ=target_type, location="memory", ) i = LLLnode.from_list(self.context.fresh_varname("for_list_ix"), typ="uint256") self.context.forvars[varname] = True ret = ["seq"] # list literal, force it to memory first if isinstance(self.stmt.iter, vy_ast.List): tmp_list = LLLnode.from_list( self.context.new_internal_variable(iter_list.typ), typ=iter_list.typ, location="memory", ) ret.append(make_setter(tmp_list, iter_list, pos=getpos(self.stmt))) iter_list = tmp_list # set up the loop variable loop_var_ast = getpos(self.stmt.target) e = get_element_ptr(iter_list, i, array_bounds_check=False, pos=loop_var_ast) body = [ "seq", make_setter(loop_var, e, pos=loop_var_ast), parse_body(self.stmt.body, self.context), ] repeat_bound = iter_list.typ.count if isinstance(iter_list.typ, DArrayType): array_len = get_dyn_array_count(iter_list) else: array_len = repeat_bound ret.append(["repeat", i, 0, array_len, repeat_bound, body]) del self.context.forvars[varname] return LLLnode.from_list(ret, pos=getpos(self.stmt))
def parse_AugAssign(self): target = self._get_target(self.stmt.target) sub = Expr.parse_value_expr(self.stmt.value, self.context) if not isinstance(target.typ, BaseType): return if target.location == "storage": lll_node = Expr.parse_value_expr( vy_ast.BinOp( left=LLLnode.from_list(["sload", "_stloc"], typ=target.typ, pos=target.pos), right=sub, op=self.stmt.op, lineno=self.stmt.lineno, col_offset=self.stmt.col_offset, end_lineno=self.stmt.end_lineno, end_col_offset=self.stmt.end_col_offset, node_source_code=self.stmt.get("node_source_code"), ), self.context, ) return LLLnode.from_list( [ "with", "_stloc", target, ["sstore", "_stloc", unwrap_location(lll_node)] ], typ=None, pos=getpos(self.stmt), ) elif target.location == "memory": lll_node = Expr.parse_value_expr( vy_ast.BinOp( left=LLLnode.from_list(["mload", "_mloc"], typ=target.typ, pos=target.pos), right=sub, op=self.stmt.op, lineno=self.stmt.lineno, col_offset=self.stmt.col_offset, end_lineno=self.stmt.end_lineno, end_col_offset=self.stmt.end_col_offset, node_source_code=self.stmt.get("node_source_code"), ), self.context, ) return LLLnode.from_list( [ "with", "_mloc", target, ["mstore", "_mloc", unwrap_location(lll_node)] ], typ=None, pos=getpos(self.stmt), )
def _parse_For_range(self): # attempt to use the type specified by type checking, fall back to `int256` # this is a stopgap solution to allow uint256 - it will be properly solved # once we refactor type system iter_typ = "int256" if "type" in self.stmt.target._metadata: iter_typ = self.stmt.target._metadata["type"]._id # Get arg0 arg0 = self.stmt.iter.args[0] num_of_args = len(self.stmt.iter.args) # Type 1 for, e.g. for i in range(10): ... if num_of_args == 1: arg0_val = self._get_range_const_value(arg0) start = LLLnode.from_list(0, typ=iter_typ, pos=getpos(self.stmt)) rounds = arg0_val # Type 2 for, e.g. for i in range(100, 110): ... elif self._check_valid_range_constant(self.stmt.iter.args[1], raise_exception=False)[0]: arg0_val = self._get_range_const_value(arg0) arg1_val = self._get_range_const_value(self.stmt.iter.args[1]) start = LLLnode.from_list(arg0_val, typ=iter_typ, pos=getpos(self.stmt)) rounds = LLLnode.from_list(arg1_val - arg0_val, typ=iter_typ, pos=getpos(self.stmt)) # Type 3 for, e.g. for i in range(x, x + 10): ... else: arg1 = self.stmt.iter.args[1] rounds = self._get_range_const_value(arg1.right) start = Expr.parse_value_expr(arg0, self.context) r = rounds if isinstance(rounds, int) else rounds.value if r < 1: return varname = self.stmt.target.id i = LLLnode.from_list(self.context.fresh_varname("range_ix"), typ="uint256") iptr = self.context.new_variable(varname, BaseType(iter_typ), pos=getpos(self.stmt)) self.context.forvars[varname] = True loop_body = ["seq"] # store the current value of i so it is accessible to userland loop_body.append(["mstore", iptr, i]) loop_body.append(parse_body(self.stmt.body, self.context)) lll_node = LLLnode.from_list( ["repeat", i, start, rounds, rounds, loop_body], pos=getpos(self.stmt), ) del self.context.forvars[varname] return lll_node
def parse_Assert(self): test_expr = Expr.parse_value_expr(self.stmt.test, self.context) if test_expr.typ.is_literal: if test_expr.value == 1: # skip literal assertions that always pass return LLLnode.from_list(["pass"], typ=None, pos=getpos(self.stmt)) else: test_expr = test_expr.value if self.stmt.msg: return self._assert_reason(test_expr, self.stmt.msg) else: return LLLnode.from_list(["assert", test_expr], typ=None, pos=getpos(self.stmt))
def parse_Raise(self): if self.stmt.exc: return self._assert_reason(None, self.stmt.exc) else: return LLLnode.from_list(["revert", 0, 0], typ=None, pos=getpos(self.stmt))
def parse_body(code, context): if not isinstance(code, list): return parse_stmt(code, context) lll_node = ["seq"] for stmt in code: lll = parse_stmt(stmt, context) lll_node.append(lll) lll_node.append("pass") # force zerovalent, even last statement return LLLnode.from_list(lll_node, pos=getpos(code[0]) if code else None)
def to_bool(expr, args, kwargs, context): in_arg = args[0] input_type, _ = get_type(in_arg) if input_type == "Bytes": if in_arg.typ.maxlen > 32: raise TypeMismatch( f"Cannot convert bytes array of max length {in_arg.typ.maxlen} to bool", expr, ) else: num = byte_array_to_num(in_arg, "uint256") return LLLnode.from_list(["iszero", ["iszero", num]], typ=BaseType("bool"), pos=getpos(expr)) else: return LLLnode.from_list(["iszero", ["iszero", in_arg]], typ=BaseType("bool"), pos=getpos(expr))
def to_uint8(expr, args, kwargs, context): in_arg = args[0] input_type, _ = get_type(in_arg) if input_type == "Bytes": if in_arg.typ.maxlen > 32: raise TypeMismatch( f"Cannot convert bytes array of max length {in_arg.typ.maxlen} to uint8", expr, ) else: # uint8 clamp is already applied in byte_array_to_num in_arg = byte_array_to_num(in_arg, "uint8") else: # cast to output type so clamp_basetype works in_arg = LLLnode.from_list(in_arg, typ="uint8") return LLLnode.from_list(clamp_basetype(in_arg), typ=BaseType("uint8"), pos=getpos(expr))
def parse_If(self): if self.stmt.orelse: with self.context.block_scope(): add_on = [parse_body(self.stmt.orelse, self.context)] else: add_on = [] with self.context.block_scope(): test_expr = Expr.parse_value_expr(self.stmt.test, self.context) body = ["if", test_expr, parse_body(self.stmt.body, self.context)] + add_on lll_node = LLLnode.from_list(body, typ=None, pos=getpos(self.stmt)) return lll_node
def parse_body(code, context, ensure_terminated=False): if not isinstance(code, list): return parse_stmt(code, context) lll_node = ["seq"] for stmt in code: lll = parse_stmt(stmt, context) lll_node.append(lll) # force using the return routine / exit_to cleanup for end of function if ensure_terminated and context.return_type is None and not _is_terminated(code): lll_node.append(parse_stmt(vy_ast.Return(value=None), context)) # force zerovalent, even last statement lll_node.append("pass") # CMC 2022-01-16 is this necessary? return LLLnode.from_list(lll_node, pos=getpos(code[0]) if code else None)
def parse_AnnAssign(self): typ = parse_type( self.stmt.annotation, sigs=self.context.sigs, custom_structs=self.context.structs, ) varname = self.stmt.target.id pos = self.context.new_variable(varname, typ, pos=self.stmt) if self.stmt.value is None: return sub = Expr(self.stmt.value, self.context).lll_node is_literal_bytes32_assign = ( isinstance(sub.typ, ByteArrayType) and sub.typ.maxlen == 32 and isinstance(typ, BaseType) and typ.typ == "bytes32" and sub.typ.is_literal ) # If bytes[32] to bytes32 assignment rewrite sub as bytes32. if is_literal_bytes32_assign: sub = LLLnode( util.bytes_to_int(self.stmt.value.s), typ=BaseType("bytes32"), pos=getpos(self.stmt), ) variable_loc = LLLnode.from_list( pos, typ=typ, location="memory", pos=getpos(self.stmt), ) lll_node = make_setter(variable_loc, sub, pos=getpos(self.stmt)) return lll_node
def byte_array_to_num(arg, out_type): """ Takes a <32 byte array as input, and outputs a number. """ # the location of the bytestring bs_start = (LLLnode.from_list( "bs_start", typ=arg.typ, location=arg.location, encoding=arg.encoding) if arg.is_complex_lll else arg) if arg.location == "storage": len_ = get_bytearray_length(bs_start) data = LLLnode.from_list(["sload", add_ofst(bs_start, 1)], typ=BaseType("int256")) else: op = load_op(arg.location) len_ = LLLnode.from_list([op, bs_start], typ=BaseType("int256")) data = LLLnode.from_list([op, add_ofst(bs_start, 32)], typ=BaseType("int256")) # converting a bytestring to a number: # bytestring is right-padded with zeroes, int is left-padded. # convert by shr the number of zero bytes (converted to bits) # e.g. "abcd000000000000" -> bitcast(000000000000abcd, output_type) num_zero_bits = ["mul", 8, ["sub", 32, "len_"]] bitcasted = LLLnode.from_list(shr(num_zero_bits, "val"), typ=out_type) result = clamp_basetype(bitcasted) # TODO use cache_when_complex for these `with` values ret = ["with", "val", data, ["with", "len_", len_, result]] if arg.is_complex_lll: ret = ["with", "bs_start", arg, ret] return LLLnode.from_list( ret, typ=BaseType(out_type), annotation=f"__intrinsic__byte_array_to_num({out_type})", )
def parse_Break(self): return LLLnode.from_list("break", typ=None, pos=getpos(self.stmt))
def to_address(expr, args, kwargs, context): # cast to output type so clamp_basetype works lll_node = LLLnode.from_list(args[0], typ="address") return LLLnode.from_list(clamp_basetype(lll_node), typ=BaseType("address"), pos=getpos(expr))
def to_decimal(expr, args, kwargs, context): in_arg = args[0] input_type, _ = get_type(in_arg) if input_type == "Bytes": if in_arg.typ.maxlen > 32: raise TypeMismatch( f"Cannot convert bytes array of max length {in_arg.typ.maxlen} to decimal", expr, ) # use byte_array_to_num(int128) because it is cheaper to clamp int128 num = byte_array_to_num(in_arg, "int128") return LLLnode.from_list(["mul", num, DECIMAL_DIVISOR], typ=BaseType("decimal"), pos=getpos(expr)) else: if input_type == "uint256": if in_arg.typ.is_literal: if not SizeLimits.in_bounds("int128", (in_arg.value * DECIMAL_DIVISOR)): raise InvalidLiteral( f"Number out of range: {in_arg.value}", expr, ) else: return LLLnode.from_list(["mul", in_arg, DECIMAL_DIVISOR], typ=BaseType("decimal"), pos=getpos(expr)) else: return LLLnode.from_list( [ "uclample", ["mul", in_arg, DECIMAL_DIVISOR], ["mload", MemoryPositions.MAXDECIMAL], ], typ=BaseType("decimal"), pos=getpos(expr), ) elif input_type == "address": return LLLnode.from_list( [ "mul", [ "signextend", 15, ["and", in_arg, (SizeLimits.ADDRSIZE - 1)] ], DECIMAL_DIVISOR, ], typ=BaseType("decimal"), pos=getpos(expr), ) elif input_type == "bytes32": if in_arg.typ.is_literal: if not SizeLimits.in_bounds("int128", (in_arg.value * DECIMAL_DIVISOR)): raise InvalidLiteral( f"Number out of range: {in_arg.value}", expr, ) else: return LLLnode.from_list(["mul", in_arg, DECIMAL_DIVISOR], typ=BaseType("decimal"), pos=getpos(expr)) else: return LLLnode.from_list( [ "clamp", ["mload", MemoryPositions.MINDECIMAL], ["mul", in_arg, DECIMAL_DIVISOR], ["mload", MemoryPositions.MAXDECIMAL], ], typ=BaseType("decimal"), pos=getpos(expr), ) elif input_type == "int256": # cast in_arg so clamp_basetype works in_arg = LLLnode.from_list(in_arg, typ="int128") return LLLnode.from_list( ["mul", clamp_basetype(in_arg), DECIMAL_DIVISOR], typ=BaseType("decimal"), pos=getpos(expr), ) elif input_type in ("uint8", "int128", "bool"): return LLLnode.from_list(["mul", in_arg, DECIMAL_DIVISOR], typ=BaseType("decimal"), pos=getpos(expr)) else: raise InvalidLiteral(f"Invalid input for decimal: {in_arg}", expr)
def to_int256(expr, args, kwargs, context): in_arg = args[0] input_type, _ = get_type(in_arg) if input_type == "num_literal": if isinstance(in_arg, int): if not SizeLimits.in_bounds("int256", in_arg): raise InvalidLiteral(f"Number out of range: {in_arg}") return LLLnode.from_list( in_arg, typ=BaseType("int256", ), pos=getpos(expr), ) elif isinstance(in_arg, Decimal): if not SizeLimits.in_bounds("int256", math.trunc(in_arg)): raise InvalidLiteral( f"Number out of range: {math.trunc(in_arg)}") return LLLnode.from_list(math.trunc(in_arg), typ=BaseType("int256"), pos=getpos(expr)) else: raise InvalidLiteral(f"Unknown numeric literal type: {in_arg}") elif isinstance(in_arg, LLLnode) and input_type == "int128": return LLLnode.from_list(in_arg, typ=BaseType("int256"), pos=getpos(expr)) elif isinstance(in_arg, LLLnode) and input_type == "uint256": if version_check(begin="constantinople"): upper_bound = ["shl", 255, 1] else: upper_bound = -(2**255) return LLLnode.from_list(["uclamplt", in_arg, upper_bound], typ=BaseType("int256"), pos=getpos(expr)) elif isinstance(in_arg, LLLnode) and input_type == "decimal": return LLLnode.from_list( ["sdiv", in_arg, DECIMAL_DIVISOR], typ=BaseType("int256"), pos=getpos(expr), ) elif isinstance(in_arg, LLLnode) and input_type in ("bool", "uint8"): return LLLnode.from_list(in_arg, typ=BaseType("int256"), pos=getpos(expr)) elif isinstance(in_arg, LLLnode) and input_type in ("bytes32", "address"): return LLLnode(value=in_arg.value, args=in_arg.args, typ=BaseType("int256"), pos=getpos(expr)) elif isinstance(in_arg, LLLnode) and input_type in ("Bytes", "String"): if in_arg.typ.maxlen > 32: raise TypeMismatch( f"Cannot convert bytes array of max length {in_arg.typ.maxlen} to int256", expr, ) return byte_array_to_num(in_arg, "int256") else: raise InvalidLiteral(f"Invalid input for int256: {in_arg}", expr)
def parse_Pass(self): return LLLnode.from_list("pass", typ=None, pos=getpos(self.stmt))
def to_int128(expr, args, kwargs, context): in_arg = args[0] input_type, _ = get_type(in_arg) if input_type == "num_literal": if isinstance(in_arg, int): if not SizeLimits.in_bounds("int128", in_arg): raise InvalidLiteral(f"Number out of range: {in_arg}") return LLLnode.from_list(in_arg, typ=BaseType("int128"), pos=getpos(expr)) elif isinstance(in_arg, Decimal): if not SizeLimits.in_bounds("int128", math.trunc(in_arg)): raise InvalidLiteral( f"Number out of range: {math.trunc(in_arg)}") return LLLnode.from_list(math.trunc(in_arg), typ=BaseType("int128"), pos=getpos(expr)) else: raise InvalidLiteral(f"Unknown numeric literal type: {in_arg}") elif input_type in ("bytes32", "int256"): if in_arg.typ.is_literal: if not SizeLimits.in_bounds("int128", in_arg.value): raise InvalidLiteral(f"Number out of range: {in_arg.value}", expr) else: return LLLnode.from_list(in_arg, typ=BaseType("int128"), pos=getpos(expr)) else: # cast to output type so clamp_basetype works in_arg = LLLnode.from_list(in_arg, typ="int128") return LLLnode.from_list( clamp_basetype(in_arg), typ=BaseType("int128"), pos=getpos(expr), ) # CMC 20211020: what is the purpose of this .. it lops off 32 bits elif input_type == "address": return LLLnode.from_list( ["signextend", 15, ["and", in_arg, (SizeLimits.ADDRSIZE - 1)]], typ=BaseType("int128"), pos=getpos(expr), ) elif input_type in ("String", "Bytes"): if in_arg.typ.maxlen > 32: raise TypeMismatch( f"Cannot convert bytes array of max length {in_arg.typ.maxlen} to int128", expr, ) return byte_array_to_num(in_arg, "int128") elif input_type == "uint256": if in_arg.typ.is_literal: if not SizeLimits.in_bounds("int128", in_arg.value): raise InvalidLiteral(f"Number out of range: {in_arg.value}", expr) else: return LLLnode.from_list(in_arg, typ=BaseType("int128"), pos=getpos(expr)) # !! do not use clamp_basetype. check that 0 <= input <= MAX_INT128. res = int_clamp(in_arg, 127, signed=False) return LLLnode.from_list( res, typ="int128", pos=getpos(expr), ) elif input_type == "decimal": # cast to int128 so clamp_basetype works res = LLLnode.from_list(["sdiv", in_arg, DECIMAL_DIVISOR], typ="int128") return LLLnode.from_list(clamp_basetype(res), typ="int128", pos=getpos(expr)) elif input_type in ("bool", "uint8"): # note: for int8, would need signextend return LLLnode.from_list(in_arg, typ=BaseType("int128"), pos=getpos(expr)) else: raise InvalidLiteral(f"Invalid input for int128: {in_arg}", expr)
def parse_Continue(self): return LLLnode.from_list("continue", typ=None, pos=getpos(self.stmt))