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, 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 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 test_mapping_node_types(): with raises(Exception): MappingType(int, int) node1 = MappingType(BaseType("int128"), BaseType("int128")) node2 = MappingType(BaseType("int128"), BaseType("int128")) assert node1 == node2 assert str(node1) == "HashMap[int128, int128]"
def parse_Int(self): # Literal (mostly likely) becomes int256 if self.expr.n < 0: return IRnode.from_list(self.expr.n, typ=BaseType("int256", is_literal=True)) # Literal is large enough (mostly likely) becomes uint256. else: return IRnode.from_list(self.expr.n, typ=BaseType("uint256", is_literal=True))
def parse_NameConstant(self): if self.expr.value is True: return LLLnode.from_list( 1, typ=BaseType("bool", is_literal=True), pos=getpos(self.expr), ) elif self.expr.value is False: return LLLnode.from_list( 0, typ=BaseType("bool", is_literal=True), pos=getpos(self.expr), )
def parse_Hex(self): orignum = self.expr.value if len(orignum) == 42 and checksum_encode(orignum) == orignum: return LLLnode.from_list( int(self.expr.value, 16), typ=BaseType("address", is_literal=True), pos=getpos(self.expr), ) elif len(orignum) == 66: return LLLnode.from_list( int(self.expr.value, 16), typ=BaseType("bytes32", is_literal=True), pos=getpos(self.expr), )
def internal_memory_scope(self): if not self._mock_vars: for i in range(20): self._new_variable(f"#mock{i}", BaseType(self._size), self._size, bool(i % 2)) self._mock_vars = True return super().internal_memory_scope()
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) if self.stmt.value is None: return sub = Expr(self.stmt.value, self.context).ir_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 = IRnode( util.bytes_to_int(self.stmt.value.s), typ=BaseType("bytes32"), ) variable_loc = IRnode.from_list(pos, typ=typ, location=MEMORY) ir_node = make_setter(variable_loc, sub) return ir_node
def parse_Name(self): if self.expr.id == "self": return LLLnode.from_list(["address"], typ="address", pos=getpos(self.expr)) elif self.expr.id in self.context.vars: var = self.context.vars[self.expr.id] return LLLnode.from_list( var.pos, typ=var.typ, location=var. location, # either 'memory' or 'calldata' storage is handled above. encoding=var.encoding, pos=getpos(self.expr), annotation=self.expr.id, mutable=var.mutable, ) elif self.expr.id in BUILTIN_CONSTANTS: obj, typ = BUILTIN_CONSTANTS[self.expr.id] return LLLnode.from_list([obj], typ=BaseType(typ, is_literal=True), pos=getpos(self.expr)) elif self.expr._metadata["type"].is_immutable: # immutable variable # need to handle constructor and outside constructor var = self.context.globals[self.expr.id] is_constructor = self.expr.get_ancestor( vy_ast.FunctionDef).get("name") == "__init__" if is_constructor: # store memory position for later access in module.py in the variable record memory_loc = self.context.new_variable(self.expr.id, var.typ) self.context.global_ctx._globals[self.expr.id].pos = memory_loc # store the data offset in the variable record as well for accessing data_offset = self.expr._metadata["type"].position.offset self.context.global_ctx._globals[ self.expr.id].data_offset = data_offset return LLLnode.from_list( memory_loc, typ=var.typ, location="memory", pos=getpos(self.expr), annotation=self.expr.id, mutable=True, ) else: immutable_section_size = self.context.global_ctx.immutable_section_size offset = self.expr._metadata["type"].position.offset # TODO: resolve code offsets for immutables at compile time return LLLnode.from_list( ["sub", "codesize", immutable_section_size - offset], typ=var.typ, location="code", pos=getpos(self.expr), annotation=self.expr.id, mutable=False, )
def from_list( cls, obj: Any, typ: NodeType = None, location: Optional[AddrSpace] = None, source_pos: Optional[Tuple[int, int]] = None, annotation: Optional[str] = None, error_msg: Optional[str] = None, mutable: bool = True, add_gas_estimate: int = 0, encoding: Encoding = Encoding.VYPER, ) -> "IRnode": if isinstance(typ, str): typ = BaseType(typ) if isinstance(obj, IRnode): # note: this modify-and-returnclause is a little weird since # the input gets modified. CC 20191121. if typ is not None: obj.typ = typ if obj.source_pos is None: obj.source_pos = source_pos if obj.location is None: obj.location = location if obj.encoding is None: obj.encoding = encoding if obj.error_msg is None: obj.error_msg = error_msg return obj elif not isinstance(obj, list): return cls( obj, [], typ, location=location, annotation=annotation, mutable=mutable, add_gas_estimate=add_gas_estimate, source_pos=source_pos, encoding=encoding, error_msg=error_msg, ) else: return cls( obj[0], [cls.from_list(o, source_pos=source_pos) for o in obj[1:]], typ, location=location, annotation=annotation, mutable=mutable, source_pos=source_pos, add_gas_estimate=add_gas_estimate, encoding=encoding, error_msg=error_msg, )
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 parse_Decimal(self): val = self.expr.value * DECIMAL_DIVISOR # sanity check that type checker did its job assert isinstance(val, decimal.Decimal) assert SizeLimits.in_bounds("decimal", val) assert math.ceil(val) == math.floor(val) val = int(val) return IRnode.from_list(val, typ=BaseType("decimal", is_literal=True))
def from_list( cls, obj: Any, typ: NodeType = None, location: str = None, pos: Tuple[int, int] = None, annotation: Optional[str] = None, mutable: bool = True, add_gas_estimate: int = 0, valency: Optional[int] = None, encoding: Encoding = Encoding.VYPER, ) -> "LLLnode": if isinstance(typ, str): typ = BaseType(typ) if isinstance(obj, LLLnode): # note: this modify-and-returnclause is a little weird since # the input gets modified. CC 20191121. if typ is not None: obj.typ = typ if obj.pos is None: obj.pos = pos if obj.location is None: obj.location = location if obj.encoding is None: obj.encoding = encoding return obj elif not isinstance(obj, list): return cls( obj, [], typ, location=location, pos=pos, annotation=annotation, mutable=mutable, add_gas_estimate=add_gas_estimate, valency=valency, encoding=encoding, ) else: return cls( obj[0], [cls.from_list(o, pos=pos) for o in obj[1:]], typ, location=location, pos=pos, annotation=annotation, mutable=mutable, add_gas_estimate=add_gas_estimate, valency=valency, encoding=encoding, )
def parse_Decimal(self): numstring, num, den = get_number_as_fraction(self.expr, self.context) if not (SizeLimits.MIN_INT128 * den <= num <= SizeLimits.MAX_INT128 * den): return if DECIMAL_DIVISOR % den: return return LLLnode.from_list( num * DECIMAL_DIVISOR // den, typ=BaseType("decimal", is_literal=True), pos=getpos(self.expr), )
def test_bytearray_node_type(): node1 = ByteArrayType(12) node2 = ByteArrayType(12) assert node1 == node2 node3 = ByteArrayType(13) node4 = BaseType("int128") assert node1 != node3 assert node1 != node4
def parse_Hex(self): hexstr = self.expr.value if len(hexstr) == 42: # sanity check typechecker did its job assert checksum_encode(hexstr) == hexstr typ = BaseType("address") # TODO allow non-checksum encoded bytes20 return IRnode.from_list(int(self.expr.value, 16), typ=typ) else: n_bytes = (len(hexstr) - 2) // 2 # e.g. "0x1234" is 2 bytes # TODO: typ = new_type_to_old_type(self.expr._metadata["type"]) # assert n_bytes == typ._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) typ.is_literal = True return IRnode.from_list(val, typ=typ)
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 = IRnode.from_list(0, typ=iter_typ) 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 = IRnode.from_list(arg0_val, typ=iter_typ) rounds = IRnode.from_list(arg1_val - arg0_val, typ=iter_typ) # 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 = IRnode.from_list(self.context.fresh_varname("range_ix"), typ="uint256") iptr = self.context.new_variable(varname, BaseType(iter_typ)) 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)) ir_node = IRnode.from_list( ["repeat", i, start, rounds, rounds, loop_body]) del self.context.forvars[varname] return ir_node
def _encode_dyn_array_helper(dst, ir_node, context): # if it's a literal, first serialize to memory as we # don't have a compile-time abi encoder # TODO handle this upstream somewhere if ir_node.value == "multi": buf = context.new_internal_variable(dst.typ) buf = IRnode.from_list(buf, typ=dst.typ, location=MEMORY) _bufsz = dst.typ.abi_type.size_bound() return [ "seq", make_setter(buf, ir_node), [ "set", "dyn_ofst", abi_encode(dst, buf, context, _bufsz, returns_len=True) ], ] subtyp = ir_node.typ.subtype child_abi_t = subtyp.abi_type ret = ["seq"] len_ = get_dyn_array_count(ir_node) with len_.cache_when_complex("len") as (b, len_): # set the length word ret.append(STORE(dst, len_)) # prepare the loop t = BaseType("uint256") i = IRnode.from_list(context.fresh_varname("ix"), typ=t) # offset of the i'th element in ir_node child_location = get_element_ptr(ir_node, i, array_bounds_check=False) # offset of the i'th element in dst dst = add_ofst(dst, 32) # jump past length word static_elem_size = child_abi_t.embedded_static_size() static_ofst = ["mul", i, static_elem_size] loop_body = _encode_child_helper(dst, child_location, static_ofst, "dyn_child_ofst", context) loop = ["repeat", i, 0, len_, ir_node.typ.count, loop_body] x = ["seq", loop, "dyn_child_ofst"] start_dyn_ofst = ["mul", len_, static_elem_size] run_children = ["with", "dyn_child_ofst", start_dyn_ofst, x] new_dyn_ofst = ["add", "dyn_ofst", run_children] # size of dynarray is size of encoded children + size of the length word # TODO optimize by adding 32 to the initial value of dyn_ofst new_dyn_ofst = ["add", 32, new_dyn_ofst] ret.append(["set", "dyn_ofst", new_dyn_ofst]) return b.resolve(ret)
def get_dyn_array_count(arg): assert isinstance(arg.typ, DArrayType) typ = BaseType("uint256") if arg.value == "multi": return IRnode.from_list(len(arg.args), typ=typ) if arg.value == "~empty": # empty(DynArray[...]) return IRnode.from_list(0, typ=typ) return IRnode.from_list(LOAD(arg), typ=typ)
def get_dyn_array_count(arg): assert isinstance(arg.typ, DArrayType) typ = BaseType("uint256") if arg.value == "multi": return LLLnode.from_list(len(arg.args), typ=typ) if arg.value == "~empty": # empty(DynArray[]) return LLLnode.from_list(0, typ=typ) return LLLnode.from_list([load_op(arg.location), arg], typ=typ)
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 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_Name(self): if self.expr.id == "self": return LLLnode.from_list(["address"], typ="address", pos=getpos(self.expr)) elif self.expr.id in self.context.vars: var = self.context.vars[self.expr.id] return LLLnode.from_list( var.pos, typ=var.typ, location=var. location, # either 'memory' or 'calldata' storage is handled above. encoding=var.encoding, pos=getpos(self.expr), annotation=self.expr.id, mutable=var.mutable, ) elif self.expr.id in BUILTIN_CONSTANTS: obj, typ = BUILTIN_CONSTANTS[self.expr.id] return LLLnode.from_list([obj], typ=BaseType(typ, is_literal=True), pos=getpos(self.expr)) elif self.expr._metadata["type"].is_immutable: var = self.context.globals[self.expr.id] ofst = self.expr._metadata["type"].position.offset if self.context.sig.is_init_func: mutable = True location = "immutables" else: mutable = False location = "data" return LLLnode.from_list( ofst, typ=var.typ, location=location, pos=getpos(self.expr), annotation=self.expr.id, mutable=mutable, )
def parse_BinOp(self): left = Expr.parse_value_expr(self.expr.left, self.context) right = Expr.parse_value_expr(self.expr.right, self.context) if not is_numeric_type(left.typ) or not is_numeric_type(right.typ): return ltyp, rtyp = left.typ.typ, right.typ.typ # Sanity check - ensure that we aren't dealing with different types # This should be unreachable due to the type check pass assert ltyp == rtyp, f"unreachable, {ltyp}!={rtyp}, {self.expr}" if isinstance(self.expr.op, vy_ast.BitAnd): new_typ = left.typ return IRnode.from_list(["and", left, right], typ=new_typ) if isinstance(self.expr.op, vy_ast.BitOr): new_typ = left.typ return IRnode.from_list(["or", left, right], typ=new_typ) if isinstance(self.expr.op, vy_ast.BitXor): new_typ = left.typ return IRnode.from_list(["xor", left, right], typ=new_typ) out_typ = BaseType(ltyp) with left.cache_when_complex("x") as ( b1, x), right.cache_when_complex("y") as (b2, y): if isinstance(self.expr.op, vy_ast.Add): ret = arithmetic.safe_add(x, y) elif isinstance(self.expr.op, vy_ast.Sub): ret = arithmetic.safe_sub(x, y) elif isinstance(self.expr.op, vy_ast.Mult): ret = arithmetic.safe_mul(x, y) elif isinstance(self.expr.op, vy_ast.Div): ret = arithmetic.safe_div(x, y) elif isinstance(self.expr.op, vy_ast.Mod): ret = arithmetic.safe_mod(x, y) elif isinstance(self.expr.op, vy_ast.Pow): ret = arithmetic.safe_pow(x, y) else: return # raises return IRnode.from_list(b1.resolve(b2.resolve(ret)), typ=out_typ)
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 test_type_storage_sizes(): assert BaseType("int128").storage_size_in_words == 1 assert ByteArrayType(12).storage_size_in_words == 2 assert ByteArrayType(33).storage_size_in_words == 3 assert SArrayType(BaseType("int128"), 10).storage_size_in_words == 10 _tuple = TupleType([BaseType("int128"), BaseType("decimal")]) assert _tuple.storage_size_in_words == 2 _struct = StructType({ "a": BaseType("int128"), "b": BaseType("decimal") }, "Foo") assert _struct.storage_size_in_words == 2 # Don't allow unknown types. with raises(Exception): _ = int.storage_size_in_words # Maps are not supported for function arguments or outputs with raises(Exception): _ = MappingType(BaseType("int128"), BaseType("int128")).storage_size_in_words
def test_canonicalize_type(): # TODO add more types # Test ABI format of multiple args. c = TupleType([BaseType("int128"), BaseType("address")]) assert c.abi_type.selector_name() == "(int128,address)"
def test_tuple_node_types(): node1 = TupleType([BaseType("int128"), BaseType("decimal")]) node2 = TupleType([BaseType("int128"), BaseType("decimal")]) assert node1 == node2 assert str(node1) == "(int128, decimal)"
def get_bytearray_length(arg): typ = BaseType("uint256") # TODO add "~empty" case to mirror get_dyn_array_count return IRnode.from_list(LOAD(arg), typ=typ)