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 keccak256_helper(expr, ir_arg, context): sub = ir_arg # TODO get rid of useless variable _check_byteslike(sub.typ, expr) # Can hash literals # TODO this is dead code. if isinstance(sub, bytes): return IRnode.from_list(bytes_to_int(keccak256(sub)), typ=BaseType("bytes32")) # Can hash bytes32 objects if is_base_type(sub.typ, "bytes32"): return IRnode.from_list( [ "seq", ["mstore", MemoryPositions.FREE_VAR_SPACE, sub], ["sha3", MemoryPositions.FREE_VAR_SPACE, 32], ], typ=BaseType("bytes32"), add_gas_estimate=_gas_bound(1), ) sub = ensure_in_memory(sub, context) return IRnode.from_list( [ "with", "_buf", sub, ["sha3", ["add", "_buf", 32], ["mload", "_buf"]], ], typ=BaseType("bytes32"), annotation="keccak256", add_gas_estimate=_gas_bound(ceil(sub.typ.maxlen / 32)), )
def parse_UnaryOp(self): operand = Expr.parse_value_expr(self.expr.operand, self.context) if isinstance(self.expr.op, vy_ast.Not): if isinstance(operand.typ, BaseType) and operand.typ.typ == "bool": return IRnode.from_list(["iszero", operand], typ="bool") if isinstance(self.expr.op, vy_ast.Invert): if isinstance(operand.typ, EnumType): n_members = len(operand.typ.members) # use (xor 0b11..1 operand) to flip all the bits in # `operand`. `mask` could be a very large constant and # hurt codesize, but most user enums will likely have few # enough members that the mask will not be large. mask = (2**n_members) - 1 return IRnode.from_list(["xor", mask, operand], typ=operand.typ) if is_base_type(operand.typ, "uint256"): return IRnode.from_list(["not", operand], typ=operand.typ) # block `~` for all other integer types, since reasoning # about dirty bits is not entirely trivial. maybe revisit # this at a later date. raise UnimplementedException( f"~ is not supported for {operand.typ}", self.expr) if isinstance(self.expr.op, vy_ast.USub) and is_numeric_type( operand.typ): assert operand.typ._num_info.is_signed # Clamp on minimum signed integer value as we cannot negate that # value (all other integer values are fine) min_int_val, _ = operand.typ._num_info.bounds return IRnode.from_list( ["sub", 0, clamp("sgt", operand, min_int_val)], typ=operand.typ)
def parse_Hex(self): hexstr = self.expr.value t = self.expr._metadata.get("type") n_bytes = (len(hexstr) - 2) // 2 # e.g. "0x1234" is 2 bytes if t is not None: inferred_type = new_type_to_old_type(self.expr._metadata["type"]) # This branch is a band-aid to deal with bytes20 vs address literals # TODO handle this properly in the type checker elif len(hexstr) == 42: inferred_type = BaseType("address", is_literal=True) else: inferred_type = BaseType(f"bytes{n_bytes}", is_literal=True) if is_base_type(inferred_type, "address"): # sanity check typechecker did its job assert len(hexstr) == 42 and is_checksum_encoded(hexstr) typ = BaseType("address") return IRnode.from_list(int(self.expr.value, 16), typ=typ) elif is_bytes_m_type(inferred_type): assert n_bytes == inferred_type._bytes_info.m # bytes_m types are left padded with zeros val = int(hexstr, 16) << 8 * (32 - n_bytes) typ = BaseType(f"bytes{n_bytes}", is_literal=True) return IRnode.from_list(val, typ=typ)
def keccak256_helper(expr, to_hash, context): _check_byteslike(to_hash.typ, expr) # Can hash literals # TODO this is dead code. if isinstance(to_hash, bytes): return IRnode.from_list(bytes_to_int(keccak256(to_hash)), typ=BaseType("bytes32")) # Can hash bytes32 objects if is_base_type(to_hash.typ, "bytes32"): return IRnode.from_list( [ "seq", ["mstore", MemoryPositions.FREE_VAR_SPACE, to_hash], ["sha3", MemoryPositions.FREE_VAR_SPACE, 32], ], typ=BaseType("bytes32"), add_gas_estimate=_gas_bound(1), ) to_hash = ensure_in_memory(to_hash, context) with to_hash.cache_when_complex("buf") as (b1, to_hash): data = bytes_data_ptr(to_hash) len_ = get_bytearray_length(to_hash) return b1.resolve( IRnode.from_list( ["sha3", data, len_], typ="bytes32", annotation="keccak256", add_gas_estimate=_gas_bound(ceil(to_hash.typ.maxlen / 32)), ))
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 to_enum(expr, arg, out_typ): if not is_base_type(arg.typ, "uint256"): _FAIL(arg.typ, out_typ, expr) if len(out_typ.members) < 256: arg = int_clamp(arg, bits=len(out_typ.members), signed=False) 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 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 to_decimal(expr, arg, out_typ): # question: is converting from Bytes to decimal allowed? _check_bytes(expr, arg, out_typ, max_bytes_allowed=16) if isinstance(expr, vy_ast.Constant): return _literal_decimal(expr, out_typ) if isinstance(arg.typ, ByteArrayType): arg_typ = arg.typ arg = _bytes_to_num(arg, out_typ, signed=True) # TODO revisit this condition once we have more decimal types # and decimal bounds expand # will be something like: if info.m_bits > 168 if arg_typ.maxlen * 8 > 128: arg = IRnode.from_list(arg, typ=out_typ) arg = clamp_basetype(arg) return IRnode.from_list(arg, typ=out_typ) elif is_bytes_m_type(arg.typ): info = arg.typ._bytes_info arg = _bytes_to_num(arg, out_typ, signed=True) # TODO revisit this condition once we have more decimal types # and decimal bounds expand # will be something like: if info.m_bits > 168 if info.m_bits > 128: arg = IRnode.from_list(arg, typ=out_typ) arg = clamp_basetype(arg) return IRnode.from_list(arg, typ=out_typ) elif is_integer_type(arg.typ): int_info = arg.typ._int_info arg = _int_to_fixed(arg, out_typ) out_info = out_typ._decimal_info if int_info.bits > out_info.bits: # TODO: _num_clamp probably not necessary bc already # clamped in _int_to_fixed arg = _num_clamp(arg, out_info, int_info) return IRnode.from_list(arg, typ=out_typ) elif is_base_type(arg.typ, "bool"): arg = _int_to_fixed(arg, out_typ) return IRnode.from_list(arg, typ=out_typ) else: raise CompilerPanic("unreachable") # pragma: notest
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 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 to_decimal(expr, arg, out_typ): _check_bytes(expr, arg, out_typ, 32) out_info = out_typ._decimal_info if isinstance(expr, vy_ast.Constant): return _literal_decimal(expr, arg.typ, out_typ) if isinstance(arg.typ, ByteArrayType): arg_typ = arg.typ arg = _bytes_to_num(arg, out_typ, signed=True) if arg_typ.maxlen * 8 > 168: arg = IRnode.from_list(arg, typ=out_typ) arg = clamp_basetype(arg) return IRnode.from_list(arg, typ=out_typ) elif is_bytes_m_type(arg.typ): info = arg.typ._bytes_info arg = _bytes_to_num(arg, out_typ, signed=True) if info.m_bits > 168: arg = IRnode.from_list(arg, typ=out_typ) arg = clamp_basetype(arg) return IRnode.from_list(arg, typ=out_typ) elif is_integer_type(arg.typ): arg = _int_to_fixed(arg, out_typ) return IRnode.from_list(arg, typ=out_typ) elif is_base_type(arg.typ, "bool"): # TODO: consider adding _int_info to bool so we can use _int_to_fixed arg = ["mul", arg, 10**out_info.decimals] return IRnode.from_list(arg, typ=out_typ) else: raise CompilerPanic("unreachable") # pragma: notest
def _check_byteslike(typ, _expr): if not isinstance(typ, ByteArrayLike) and not is_base_type(typ, "bytes32"): # NOTE this may be checked at a higher level, but just be safe raise CompilerPanic("keccak256 only accepts bytes-like objects", )
def parse_Attribute(self): # x.balance: balance of address x if self.expr.attr == "balance": addr = Expr.parse_value_expr(self.expr.value, self.context) if is_base_type(addr.typ, "address"): if (isinstance(self.expr.value, vy_ast.Name) and self.expr.value.id == "self" and version_check(begin="istanbul")): seq = ["selfbalance"] else: seq = ["balance", addr] return LLLnode.from_list( seq, typ=BaseType("uint256"), location=None, pos=getpos(self.expr), ) # x.codesize: codesize of address x elif self.expr.attr == "codesize" or self.expr.attr == "is_contract": addr = Expr.parse_value_expr(self.expr.value, self.context) if is_base_type(addr.typ, "address"): if self.expr.attr == "codesize": if self.expr.value.id == "self": eval_code = ["codesize"] else: eval_code = ["extcodesize", addr] output_type = "uint256" else: eval_code = ["gt", ["extcodesize", addr], 0] output_type = "bool" return LLLnode.from_list( eval_code, typ=BaseType(output_type), location=None, pos=getpos(self.expr), ) # x.codehash: keccak of address x elif self.expr.attr == "codehash": addr = Expr.parse_value_expr(self.expr.value, self.context) if not version_check(begin="constantinople"): raise EvmVersionException( "address.codehash is unavailable prior to constantinople ruleset", self.expr) if is_base_type(addr.typ, "address"): return LLLnode.from_list( ["extcodehash", addr], typ=BaseType("bytes32"), location=None, pos=getpos(self.expr), ) # x.code: codecopy/extcodecopy of address x elif self.expr.attr == "code": addr = Expr.parse_value_expr(self.expr.value, self.context) if is_base_type(addr.typ, "address"): # These adhoc nodes will be replaced with a valid node in `Slice.build_LLL` if addr.value == "address": # for `self.code` return LLLnode.from_list(["~selfcode"], typ=ByteArrayType(0)) return LLLnode.from_list(["~extcode", addr], typ=ByteArrayType(0)) # self.x: global attribute elif isinstance(self.expr.value, vy_ast.Name) and self.expr.value.id == "self": type_ = self.expr._metadata["type"] var = self.context.globals[self.expr.attr] return LLLnode.from_list( type_.position.position, typ=var.typ, location="storage", pos=getpos(self.expr), annotation="self." + self.expr.attr, ) # Reserved keywords elif (isinstance(self.expr.value, vy_ast.Name) and self.expr.value.id in ENVIRONMENT_VARIABLES): key = f"{self.expr.value.id}.{self.expr.attr}" if key == "msg.sender": return LLLnode.from_list(["caller"], typ="address", pos=getpos(self.expr)) elif key == "msg.data": # This adhoc node will be replaced with a valid node in `Slice/Len.build_LLL` return LLLnode.from_list(["~calldata"], typ=ByteArrayType(0)) elif key == "msg.value" and self.context.is_payable: return LLLnode.from_list( ["callvalue"], typ=BaseType("uint256"), pos=getpos(self.expr), ) elif key == "msg.gas": return LLLnode.from_list( ["gas"], typ="uint256", pos=getpos(self.expr), ) elif key == "block.difficulty": return LLLnode.from_list( ["difficulty"], typ="uint256", pos=getpos(self.expr), ) elif key == "block.timestamp": return LLLnode.from_list( ["timestamp"], typ=BaseType("uint256"), pos=getpos(self.expr), ) elif key == "block.coinbase": return LLLnode.from_list(["coinbase"], typ="address", pos=getpos(self.expr)) elif key == "block.number": return LLLnode.from_list(["number"], typ="uint256", pos=getpos(self.expr)) elif key == "block.gaslimit": return LLLnode.from_list(["gaslimit"], typ="uint256", pos=getpos(self.expr)) elif key == "block.basefee": return LLLnode.from_list(["basefee"], typ="uint256", pos=getpos(self.expr)) elif key == "block.prevhash": return LLLnode.from_list( ["blockhash", ["sub", "number", 1]], typ="bytes32", pos=getpos(self.expr), ) elif key == "tx.origin": return LLLnode.from_list(["origin"], typ="address", pos=getpos(self.expr)) elif key == "tx.gasprice": return LLLnode.from_list(["gasprice"], typ="uint256", pos=getpos(self.expr)) elif key == "chain.id": if not version_check(begin="istanbul"): raise EvmVersionException( "chain.id is unavailable prior to istanbul ruleset", self.expr) return LLLnode.from_list(["chainid"], typ="uint256", pos=getpos(self.expr)) # Other variables else: sub = Expr(self.expr.value, self.context).lll_node # contract type if isinstance(sub.typ, InterfaceType): return sub if isinstance(sub.typ, StructType) and self.expr.attr in sub.typ.members: return get_element_ptr(sub, self.expr.attr, pos=getpos(self.expr))
def process_arg(index, arg, expected_arg_typelist, function_name, context): # temporary hack to support abstract types if hasattr(expected_arg_typelist, "_id_list"): expected_arg_typelist = expected_arg_typelist._id_list if isinstance(expected_arg_typelist, Optional): expected_arg_typelist = expected_arg_typelist.typ if not isinstance(expected_arg_typelist, tuple): expected_arg_typelist = (expected_arg_typelist, ) vsub = None for expected_arg in expected_arg_typelist: # temporary hack, once we refactor this package none of this will exist if hasattr(expected_arg, "_id"): expected_arg = expected_arg._id if expected_arg == "num_literal": if isinstance(arg, (vy_ast.Int, vy_ast.Decimal)): return arg.n elif expected_arg == "str_literal": if isinstance(arg, vy_ast.Str): bytez = b"" for c in arg.s: if ord(c) >= 256: raise InvalidLiteral( f"Cannot insert special character {c} into byte array", arg, ) bytez += bytes([ord(c)]) return bytez elif expected_arg == "bytes_literal": if isinstance(arg, vy_ast.Bytes): return arg.s elif expected_arg == "name_literal": if isinstance(arg, vy_ast.Name): return arg.id elif isinstance(arg, vy_ast.Subscript) and arg.value.id == "Bytes": return f"Bytes[{arg.slice.value.n}]" elif expected_arg == "*": return arg elif expected_arg == "Bytes": sub = Expr(arg, context).ir_node if isinstance(sub.typ, ByteArrayType): return sub elif expected_arg == "String": sub = Expr(arg, context).ir_node if isinstance(sub.typ, StringType): return sub else: parsed_expected_type = context.parse_type( vy_ast.parse_to_ast(expected_arg)[0].value) if isinstance(parsed_expected_type, BaseType): vsub = vsub or Expr.parse_value_expr(arg, context) is_valid_integer = ( (expected_arg in INTEGER_TYPES and isinstance(vsub.typ, BaseType)) and (vsub.typ.typ in INTEGER_TYPES and vsub.typ.is_literal) and (SizeLimits.in_bounds(expected_arg, vsub.value))) if is_base_type(vsub.typ, expected_arg): return vsub elif is_valid_integer: return vsub else: vsub = vsub or Expr(arg, context).ir_node if vsub.typ == parsed_expected_type: return Expr(arg, context).ir_node if len(expected_arg_typelist) == 1: raise TypeMismatch( f"Expecting {expected_arg} for argument {index} of {function_name}", arg) else: raise TypeMismatch( f"Expecting one of {expected_arg_typelist} for argument {index} of {function_name}", arg)