def test_version_check(evm_version): assert opcodes.version_check(begin=evm_version) assert opcodes.version_check(end=evm_version) assert opcodes.version_check(begin=evm_version, end=evm_version) if evm_version not in ("byzantium", "atlantis"): assert not opcodes.version_check(end="byzantium") if evm_version != "istanbul": assert not opcodes.version_check(begin="istanbul")
def test_version_check(evm_version): assert opcodes.version_check(begin=evm_version) assert opcodes.version_check(end=evm_version) assert opcodes.version_check(begin=evm_version, end=evm_version) if evm_version not in ("byzantium", "atlantis"): assert not opcodes.version_check(end="byzantium") istanbul_check = opcodes.version_check(begin="istanbul") assert istanbul_check == (opcodes.EVM_VERSIONS[evm_version] >= opcodes.EVM_VERSIONS["istanbul"])
def shift(expr, args, kwargs, context): if args[1].typ.is_literal: shift_abs = abs(args[1].value) else: shift_abs = ['sub', 0, '_s'] if version_check(begin="constantinople"): left_shift = ['shl', '_s', '_v'] right_shift = ['shr', shift_abs, '_v'] else: # If second argument is positive, left-shift so multiply by a power of two # If it is negative, divide by a power of two # node that if the abs of the second argument >= 256, then in the EVM # 2**(second arg) = 0, and multiplying OR dividing by 0 gives 0 left_shift = ['mul', '_v', ['exp', 2, '_s']] right_shift = ['div', '_v', ['exp', 2, shift_abs]] if not args[1].typ.is_literal: node_list = ['if', ['slt', '_s', 0], right_shift, left_shift] elif args[1].value >= 0: node_list = left_shift else: node_list = right_shift return LLLnode.from_list( [ 'with', '_v', args[0], [ 'with', '_s', args[1], node_list, ], ], typ=BaseType('uint256'), pos=getpos(expr), )
def int128_clamp(lll_node): if version_check(begin="constantinople"): return [ "with", "_val", lll_node, [ "seq_unchecked", ["dup1", "_val"], ["if", ["slt", "_val", 0], ["not", "pass"]], ["assert", ["iszero", ["shr", 127, "pass"]]], ], ] else: return [ "clamp", ["mload", MemoryPositions.MINNUM], lll_node, ["mload", MemoryPositions.MAXNUM], ]
def attribute(self): # x.balance: balance of address x if self.expr.attr == 'balance': addr = Expr.parse_value_expr(self.expr.value, self.context) if not is_base_type(addr.typ, 'address'): raise TypeMismatch( "Type mismatch: balance keyword expects an address as input", self.expr) 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 not is_base_type(addr.typ, 'address'): raise TypeMismatch( "Type mismatch: codesize keyword expects an address as input", self.expr, ) if self.expr.attr == 'codesize': eval_code = ['extcodesize', addr] output_type = 'int128' 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 is_base_type(addr.typ, 'address'): raise TypeMismatch( "codehash keyword expects an address as input", self.expr, ) if not version_check(begin="constantinople"): raise EvmVersionException( "address.codehash is unavailable prior to constantinople ruleset", self.expr) return LLLnode.from_list(['extcodehash', addr], typ=BaseType('bytes32'), location=None, pos=getpos(self.expr)) # self.x: global attribute elif isinstance(self.expr.value, vy_ast.Name) and self.expr.value.id == "self": if self.expr.attr not in self.context.globals: raise VariableDeclarationException( "Persistent variable undeclared: " + self.expr.attr, self.expr, ) var = self.context.globals[self.expr.attr] return LLLnode.from_list( var.pos, 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 = self.expr.value.id + "." + self.expr.attr if key == "msg.sender": if self.context.is_private: raise StructureException( "msg.sender not allowed in private functions.", self.expr) return LLLnode.from_list(['caller'], typ='address', pos=getpos(self.expr)) elif key == "msg.value": if not self.context.is_payable: raise NonPayableViolation( "Cannot use msg.value in a non-payable function", self.expr, ) 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.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 == "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)) else: raise StructureException("Unsupported keyword: " + key, self.expr) # Other variables else: sub = Expr.parse_variable_location(self.expr.value, self.context) # contract type if isinstance(sub.typ, ContractType): return sub if not isinstance(sub.typ, StructType): raise TypeMismatch( "Type mismatch: member variable access not expected", self.expr.value, ) attrs = list(sub.typ.members.keys()) if self.expr.attr not in attrs: raise TypeMismatch( f"Member {self.expr.attr} not found. Only the following available: " f"{' '.join(attrs)}", self.expr, ) return add_variable_offset(sub, self.expr.attr, pos=getpos(self.expr))
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 pos = getpos(self.expr) types = {left.typ.typ, right.typ.typ} literals = {left.typ.is_literal, right.typ.is_literal} # If one value of the operation is a literal, we recast it to match the non-literal type. # We know this is OK because types were already verified in the actual typechecking pass. # This is a temporary solution to not break parser while we work toward removing types # altogether at this stage of complition. @iamdefinitelyahuman if literals == {True, False } and len(types) > 1 and "decimal" not in types: if left.typ.is_literal and SizeLimits.in_bounds( right.typ.typ, left.value): left = LLLnode.from_list( left.value, typ=BaseType(right.typ.typ, None, is_literal=True), pos=pos, ) elif right.typ.is_literal and SizeLimits.in_bounds( left.typ.typ, right.value): right = LLLnode.from_list( right.value, typ=BaseType(left.typ.typ, None, is_literal=True), pos=pos, ) ltyp, rtyp = left.typ.typ, right.typ.typ if ltyp != rtyp: # Sanity check - ensure that we aren't dealing with different types # This should be unreachable due to the type check pass return arith = None if isinstance(self.expr.op, (vy_ast.Add, vy_ast.Sub)): new_typ = BaseType(ltyp) if ltyp == "uint256": if isinstance(self.expr.op, vy_ast.Add): # safeadd arith = [ "seq", ["assert", ["ge", ["add", "l", "r"], "l"]], ["add", "l", "r"] ] elif isinstance(self.expr.op, vy_ast.Sub): # safesub arith = [ "seq", ["assert", ["ge", "l", "r"]], ["sub", "l", "r"] ] elif ltyp == "int256": if isinstance(self.expr.op, vy_ast.Add): op, comp1, comp2 = "add", "sge", "slt" else: op, comp1, comp2 = "sub", "sle", "sgt" if right.typ.is_literal: if right.value >= 0: arith = [ "seq", ["assert", [comp1, [op, "l", "r"], "l"]], [op, "l", "r"] ] else: arith = [ "seq", ["assert", [comp2, [op, "l", "r"], "l"]], [op, "l", "r"] ] else: arith = [ "with", "ans", [op, "l", "r"], [ "seq", [ "assert", [ "or", [ "and", ["sge", "r", 0], [comp1, "ans", "l"] ], [ "and", ["slt", "r", 0], [comp2, "ans", "l"] ], ], ], "ans", ], ] elif ltyp in ("decimal", "int128"): op = "add" if isinstance(self.expr.op, vy_ast.Add) else "sub" arith = [op, "l", "r"] elif isinstance(self.expr.op, vy_ast.Mult): new_typ = BaseType(ltyp) if ltyp == "uint256": arith = [ "with", "ans", ["mul", "l", "r"], [ "seq", [ "assert", [ "or", ["eq", ["div", "ans", "l"], "r"], ["iszero", "l"] ] ], "ans", ], ] elif ltyp == "int256": if version_check(begin="constantinople"): upper_bound = ["shl", 255, 1] else: upper_bound = -(2**255) if not left.typ.is_literal and not right.typ.is_literal: bounds_check = [ "assert", [ "or", ["ne", "l", ["not", 0]], ["ne", "r", upper_bound] ], ] elif left.typ.is_literal and left.value == -1: bounds_check = ["assert", ["ne", "r", upper_bound]] elif right.typ.is_literal and right.value == -(2**255): bounds_check = ["assert", ["ne", "l", ["not", 0]]] else: bounds_check = "pass" arith = [ "with", "ans", ["mul", "l", "r"], [ "seq", bounds_check, [ "assert", [ "or", ["eq", ["sdiv", "ans", "l"], "r"], ["iszero", "l"] ] ], "ans", ], ] elif ltyp == "int128": arith = ["mul", "l", "r"] elif ltyp == "decimal": arith = [ "with", "ans", ["mul", "l", "r"], [ "seq", [ "assert", [ "or", ["eq", ["sdiv", "ans", "l"], "r"], ["iszero", "l"] ] ], ["sdiv", "ans", DECIMAL_DIVISOR], ], ] elif isinstance(self.expr.op, vy_ast.Div): if right.typ.is_literal and right.value == 0: return new_typ = BaseType(ltyp) if right.typ.is_literal: divisor = "r" else: # only apply the non-zero clamp when r is not a constant divisor = ["clamp_nonzero", "r"] if ltyp == "uint256": arith = ["div", "l", divisor] elif ltyp == "int256": if version_check(begin="constantinople"): upper_bound = ["shl", 255, 1] else: upper_bound = -(2**255) if not left.typ.is_literal and not right.typ.is_literal: bounds_check = [ "assert", [ "or", ["ne", "r", ["not", 0]], ["ne", "l", upper_bound] ], ] elif left.typ.is_literal and left.value == -(2**255): bounds_check = ["assert", ["ne", "r", ["not", 0]]] elif right.typ.is_literal and right.value == -1: bounds_check = ["assert", ["ne", "l", upper_bound]] else: bounds_check = "pass" arith = ["seq", bounds_check, ["sdiv", "l", divisor]] elif ltyp in ("int128", "int256"): arith = ["sdiv", "l", divisor] elif ltyp == "decimal": arith = [ "sdiv", ["mul", "l", DECIMAL_DIVISOR], divisor, ] elif isinstance(self.expr.op, vy_ast.Mod): if right.typ.is_literal and right.value == 0: return new_typ = BaseType(ltyp) if right.typ.is_literal: divisor = "r" else: # only apply the non-zero clamp when r is not a constant divisor = ["clamp_nonzero", "r"] if ltyp == "uint256": arith = ["mod", "l", divisor] else: arith = ["smod", "l", divisor] elif isinstance(self.expr.op, vy_ast.Pow): new_typ = BaseType(ltyp) if self.expr.left.get("value") == 1: return LLLnode.from_list([1], typ=new_typ, pos=pos) if self.expr.left.get("value") == 0: return LLLnode.from_list(["iszero", right], typ=new_typ, pos=pos) if ltyp == "int128": is_signed = True num_bits = 128 elif ltyp == "int256": is_signed = True num_bits = 256 else: is_signed = False num_bits = 256 if isinstance(self.expr.left, vy_ast.Int): value = self.expr.left.value upper_bound = calculate_largest_power(value, num_bits, is_signed) + 1 # for signed integers, this also prevents negative values clamp = ["lt", right, upper_bound] return LLLnode.from_list( ["seq", ["assert", clamp], ["exp", left, right]], typ=new_typ, pos=pos, ) elif isinstance(self.expr.right, vy_ast.Int): value = self.expr.right.value upper_bound = calculate_largest_base(value, num_bits, is_signed) + 1 if is_signed: clamp = [ "and", ["slt", left, upper_bound], ["sgt", left, -upper_bound] ] else: clamp = ["lt", left, upper_bound] return LLLnode.from_list( ["seq", ["assert", clamp], ["exp", left, right]], typ=new_typ, pos=pos, ) 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 if arith is None: return p = ["seq"] if new_typ.typ == "int128": p.append(int128_clamp(arith)) elif new_typ.typ == "decimal": p.append([ "clamp", ["mload", MemoryPositions.MINDECIMAL], arith, ["mload", MemoryPositions.MAXDECIMAL], ]) elif new_typ.typ in ("uint256", "int256"): p.append(arith) else: return p = ["with", "l", left, ["with", "r", right, p]] return LLLnode.from_list(p, typ=new_typ, pos=pos)
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), ) # 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" and not self.context.is_internal: return LLLnode.from_list(["caller"], typ="address", pos=getpos(self.expr)) elif key == "msg.data" and not self.context.is_internal: is_len = self.expr._metadata.get("is_len") if is_len is True: typ = ByteArrayType(32) pos = self.context.new_internal_variable(typ) node = ["seq", ["mstore", pos, "calldatasize"], pos] return LLLnode.from_list(node, typ=typ, pos=getpos(self.expr), location="memory") size = self.expr._metadata.get("size") typ = ByteArrayType(size + 32) pos = self.context.new_internal_variable(typ) node = [ "seq", ["assert", ["le", size, "calldatasize"]], ["mstore", pos, size], ["calldatacopy", pos + 32, 0, size], pos, ] return LLLnode.from_list(node, typ=typ, pos=getpos(self.expr), location="memory") 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.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 == "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.parse_variable_location(self.expr.value, self.context) # contract type if isinstance(sub.typ, InterfaceType): return sub if isinstance(sub.typ, StructType) and self.expr.attr in sub.typ.members: return add_variable_offset(sub, self.expr.attr, pos=getpos(self.expr))
def test_version_check_no_begin_or_end(): with pytest.raises(CompilerPanic): opcodes.version_check()
def make_arg_clamper(datapos, mempos, typ, is_init=False): """ Clamps argument to type limits. Arguments --------- datapos : int | LLLnode Calldata offset of the value being clamped mempos : int | LLLnode Memory offset that the value is stored at during clamping typ : vyper.types.types.BaseType Type of the value is_init : bool, optional Boolean indicating if we are generating init bytecode Returns ------- LLLnode Arg clamper LLL """ if not is_init: data_decl = ["calldataload", ["add", 4, datapos]] copier = functools.partial(_mk_calldatacopy_copier, mempos=mempos) else: data_decl = ["codeload", ["add", "~codelen", datapos]] copier = functools.partial(_mk_codecopy_copier, mempos=mempos) # Numbers: make sure they're in range if is_base_type(typ, "int128"): return LLLnode.from_list(int128_clamp(data_decl), typ=typ, annotation="checking int128 input") # Booleans: make sure they're zero or one elif is_base_type(typ, "bool"): if version_check(begin="constantinople"): lll = ["assert", ["iszero", ["shr", 1, data_decl]]] else: lll = ["uclamplt", data_decl, 2] return LLLnode.from_list(lll, typ=typ, annotation="checking bool input") # Addresses: make sure they're in range elif is_base_type(typ, "address"): if version_check(begin="constantinople"): lll = ["assert", ["iszero", ["shr", 160, data_decl]]] else: lll = ["uclamplt", data_decl, ["mload", MemoryPositions.ADDRSIZE]] return LLLnode.from_list(lll, typ=typ, annotation="checking address input") # Bytes: make sure they have the right size elif isinstance(typ, ByteArrayLike): return LLLnode.from_list( [ "seq", copier(data_decl, 32 + typ.maxlen), [ "assert", [ "le", ["calldataload", ["add", 4, data_decl]], typ.maxlen ] ], ], typ=None, annotation="checking bytearray input", ) # Lists: recurse elif isinstance(typ, ListType): if typ.count > 5 or (type(datapos) is list and type(mempos) is list): # find ultimate base type subtype = typ.subtype while hasattr(subtype, "subtype"): subtype = subtype.subtype # make arg clamper for the base type offset = MemoryPositions.FREE_LOOP_INDEX clamper = make_arg_clamper( ["add", datapos, ["mload", offset]], ["add", mempos, ["mload", offset]], subtype, is_init, ) if clamper.value == "pass": # no point looping if the base type doesn't require clamping return clamper # loop the entire array at once, even if it's multidimensional type_size = get_size_of_type(typ) i_incr = get_size_of_type(subtype) * 32 mem_to = type_size * 32 loop_label = f"_check_list_loop_{str(uuid.uuid4())}" lll_node = [ ["mstore", offset, 0], # init loop ["label", loop_label], clamper, ["mstore", offset, ["add", ["mload", offset], i_incr]], [ "if", ["lt", ["mload", offset], mem_to], ["goto", loop_label] ], ] else: lll_node = [] for i in range(typ.count): offset = get_size_of_type(typ.subtype) * 32 * i lll_node.append( make_arg_clamper(datapos + offset, mempos + offset, typ.subtype, is_init)) return LLLnode.from_list(["seq"] + lll_node, typ=None, annotation="checking list input") # Otherwise don't make any checks else: return LLLnode.from_list("pass")
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 == "bool": 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, expr, "int256") else: raise InvalidLiteral(f"Invalid input for int256: {in_arg}", expr)
def address_clamp(lll_node): if version_check(begin="constantinople"): return ["assert", ["iszero", ["shr", 160, lll_node]]] else: return ["uclamplt", lll_node, ["mload", MemoryPositions.ADDRSIZE]]