def safe_div(x, y): num_info = x.typ._num_info typ = x.typ ok = [1] # true if is_decimal_type(x.typ): lo, hi = num_info.bounds if max(abs(lo), abs(hi)) * num_info.divisor > 2**256 - 1: # stub to prevent us from adding fixed point numbers we don't know # how to deal with raise UnimplementedException( "safe_mul for decimal{num_info.bits}x{num_info.decimals}") x = ["mul", x, num_info.divisor] DIV = "sdiv" if num_info.is_signed else "div" res = IRnode.from_list([DIV, x, clamp("gt", y, 0)], typ=typ) with res.cache_when_complex("res") as (b1, res): # TODO: refactor this condition / push some things into the optimizer if num_info.is_signed and num_info.bits == 256: if version_check(begin="constantinople"): upper_bound = ["shl", 255, 1] else: upper_bound = -(2**255) if not x.is_literal and not y.typ.is_literal: ok = ["or", ["ne", y, ["not", 0]], ["ne", x, upper_bound]] # TODO push these rules into the optimizer elif x.is_literal and x.value == -(2**255): ok = ["ne", y, ["not", 0]] elif y.is_literal and y.value == -1: ok = ["ne", x, upper_bound] else: # x or y is a literal, and not an evil value. pass elif num_info.is_signed and is_integer_type(typ): lo, hi = num_info.bounds # we need to throw on min_value(typ) / -1, # but we can skip if one of the operands is a literal and not # the evil value can_skip_clamp = (x.is_literal and x.value != lo) or (y.is_literal and y.value != -1) if not can_skip_clamp: # clamp_basetype has fewer ops than the int256 rule. res = clamp_basetype(res) elif is_decimal_type(typ): # always clamp decimals, since decimal division can actually # result in something larger than either operand (e.g. 1.0 / 0.1) # TODO maybe use safe_mul res = clamp_basetype(res) check = IRnode.from_list(["assert", ok], error_msg="safemul") return IRnode.from_list(b1.resolve(["seq", check, res]))
def convert(expr, context): if len(expr.args) != 2: raise StructureException( "The convert function expects two parameters.", expr) arg_ast = expr.args[0] arg = Expr(arg_ast, context).ir_node out_typ = context.parse_type(expr.args[1]) if isinstance(arg.typ, BaseType): arg = unwrap_location(arg) with arg.cache_when_complex("arg") as (b, arg): if is_base_type(out_typ, "bool"): ret = to_bool(arg_ast, arg, out_typ) elif is_base_type(out_typ, "address"): ret = to_address(arg_ast, arg, out_typ) elif is_integer_type(out_typ): ret = to_int(arg_ast, arg, out_typ) elif is_bytes_m_type(out_typ): ret = to_bytes_m(arg_ast, arg, out_typ) elif is_decimal_type(out_typ): ret = to_decimal(arg_ast, arg, out_typ) elif isinstance(out_typ, ByteArrayType): ret = to_bytes(arg_ast, arg, out_typ) elif isinstance(out_typ, StringType): ret = to_string(arg_ast, arg, out_typ) else: raise StructureException(f"Conversion to {out_typ} is invalid.", arg_ast) ret = b.resolve(ret) return IRnode.from_list(ret)
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 to_int(expr, arg, out_typ): int_info = out_typ._int_info assert int_info.bits % 8 == 0 _check_bytes(expr, arg, out_typ, 32) if isinstance(expr, vy_ast.Constant): return _literal_int(expr, arg.typ, out_typ) elif isinstance(arg.typ, ByteArrayType): arg_typ = arg.typ arg = _bytes_to_num(arg, out_typ, signed=int_info.is_signed) if arg_typ.maxlen * 8 > int_info.bits: arg = int_clamp(arg, int_info.bits, signed=int_info.is_signed) elif is_bytes_m_type(arg.typ): arg_info = arg.typ._bytes_info arg = _bytes_to_num(arg, out_typ, signed=int_info.is_signed) if arg_info.m_bits > int_info.bits: arg = int_clamp(arg, int_info.bits, signed=int_info.is_signed) elif is_decimal_type(arg.typ): arg = _fixed_to_int(arg, out_typ) elif is_integer_type(arg.typ): arg = _int_to_int(arg, out_typ) elif is_base_type(arg.typ, "address"): if int_info.is_signed: # TODO if possible, refactor to move this validation close to the entry of the function _FAIL(arg.typ, out_typ, expr) if int_info.bits < 160: arg = int_clamp(arg, int_info.bits, signed=False) return IRnode.from_list(arg, typ=out_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 _type_class_of(typ): if is_integer_type(typ): return "int" if is_bytes_m_type(typ): return "bytes_m" if is_decimal_type(typ): return "decimal" if isinstance(typ, BaseType): return typ.typ # e.g., "bool" if isinstance(typ, ByteArrayType): return "bytes" if isinstance(typ, StringType): return "string"
def to_bytes_m(expr, arg, out_typ): out_info = out_typ._bytes_info _check_bytes(expr, arg, out_typ, max_bytes_allowed=out_info.m) if isinstance(arg.typ, ByteArrayType): bytes_val = LOAD(bytes_data_ptr(arg)) # zero out any dirty bytes (which can happen in the last # word of a bytearray) len_ = get_bytearray_length(arg) num_zero_bits = IRnode.from_list(["mul", ["sub", 32, len_], 8]) with num_zero_bits.cache_when_complex("bits") as (b, num_zero_bits): arg = shl(num_zero_bits, shr(num_zero_bits, bytes_val)) arg = b.resolve(arg) elif is_bytes_m_type(arg.typ): arg_info = arg.typ._bytes_info # clamp if it's a downcast if arg_info.m > out_info.m: arg = bytes_clamp(arg, out_info.m) elif is_integer_type(arg.typ) or is_base_type(arg.typ, "address"): int_bits = arg.typ._int_info.bits if out_info.m_bits < int_bits: # question: allow with runtime clamp? # arg = int_clamp(m_bits, signed=int_info.signed) _FAIL(arg.typ, out_typ, expr) # note: neg numbers not OOB. keep sign bit arg = shl(256 - out_info.m_bits, arg) elif is_decimal_type(arg.typ): if out_info.m_bits < arg.typ._decimal_info.bits: _FAIL(arg.typ, out_typ, expr) # note: neg numbers not OOB. keep sign bit arg = shl(256 - out_info.m_bits, arg) else: # bool arg = shl(256 - out_info.m_bits, arg) return IRnode.from_list(arg, typ=out_typ)
def convert(expr, context): if len(expr.args) != 2: raise StructureException( "The convert function expects two parameters.", expr) arg_ast = expr.args[0] arg = Expr(arg_ast, context).ir_node original_arg = arg out_typ = context.parse_type(expr.args[1]) if isinstance(arg.typ, BaseType): arg = unwrap_location(arg) with arg.cache_when_complex("arg") as (b, arg): if is_base_type(out_typ, "bool"): ret = to_bool(arg_ast, arg, out_typ) elif is_base_type(out_typ, "address"): ret = to_address(arg_ast, arg, out_typ) elif isinstance(out_typ, EnumType): ret = to_enum(arg_ast, arg, out_typ) elif is_integer_type(out_typ): ret = to_int(arg_ast, arg, out_typ) elif is_bytes_m_type(out_typ): ret = to_bytes_m(arg_ast, arg, out_typ) elif is_decimal_type(out_typ): ret = to_decimal(arg_ast, arg, out_typ) elif isinstance(out_typ, ByteArrayType): ret = to_bytes(arg_ast, arg, out_typ) elif isinstance(out_typ, StringType): ret = to_string(arg_ast, arg, out_typ) else: raise StructureException(f"Conversion to {out_typ} is invalid.", arg_ast) # test if arg actually changed. if not, we do not need to use # unwrap_location (this can reduce memory traffic for downstream # operations which are in-place, like the returndata routine) test_arg = IRnode.from_list(arg, typ=out_typ) if test_arg == ret: original_arg.typ = out_typ return original_arg return IRnode.from_list(b.resolve(ret))
def safe_mul(x, y): # precondition: x.typ.typ == y.typ.typ num_info = x.typ._num_info # optimizer rules work better for the safemul checks below # if second operand is literal if x.is_literal: tmp = x x = y y = tmp res = IRnode.from_list(["mul", x, y], typ=x.typ.typ) DIV = "sdiv" if num_info.is_signed else "div" with res.cache_when_complex("ans") as (b1, res): ok = [1] # True if num_info.bits > 128: # check overflow mod 256 # assert (res/y == x | y == 0) ok = ["or", ["eq", [DIV, res, y], x], ["iszero", y]] # int256 if num_info.is_signed and num_info.bits == 256: # special case: # in the above sdiv check, if (r==-1 and l==-2**255), # -2**255<res> / -1<r> will return -2**255<l>. # need to check: not (r == -1 and l == -2**255) if version_check(begin="constantinople"): upper_bound = ["shl", 255, 1] else: upper_bound = -(2**255) check_x = ["ne", x, upper_bound] check_y = ["ne", ["not", y], 0] if not x.is_literal and not y.is_literal: # TODO can simplify this condition? ok = ["and", ok, ["or", check_x, check_y]] # TODO push some of this constant folding into optimizer elif x.is_literal and x.value == -(2**255): ok = ["and", ok, check_y] elif y.is_literal and y.value == -1: ok = ["and", ok, check_x] else: # x or y is a literal, and we have determined it is # not an evil value pass if is_decimal_type(res.typ): res = IRnode.from_list([DIV, res, num_info.divisor], typ=res.typ) # check overflow mod <bits> # NOTE: if 128 < bits < 256, `x * y` could be between # MAX_<bits> and 2**256 OR it could overflow past 2**256. # so, we check for overflow in mod 256 AS WELL AS mod <bits> # (if bits == 256, clamp_basetype is a no-op) res = clamp_basetype(res) check = IRnode.from_list(["assert", ok], error_msg="safediv") res = IRnode.from_list(["seq", check, res], typ=res.typ) return b1.resolve(res)