def init_func_init_lll(): return LLLnode.from_list(["seq"] + LIMIT_MEMORY_SET, typ=None)
def add_variable_offset(parent, key, pos, array_bounds_check=True): typ, location = parent.typ, parent.location if isinstance(typ, TupleLike): if isinstance(typ, StructType): if not isinstance(key, str): raise TypeMismatch( f"Expecting a member variable access; cannot access element {key}", pos) if key not in typ.members: raise TypeMismatch( f"Object does not have member variable {key}", pos) subtype = typ.members[key] attrs = list(typ.tuple_keys()) if key not in attrs: raise TypeMismatch( f"Member {key} not found. Only the following available: " + " ".join(attrs), pos) index = attrs.index(key) annotation = key else: if not isinstance(key, int): raise TypeMismatch( f"Expecting a static index; cannot access element {key}", pos) attrs = list(range(len(typ.members))) index = key annotation = None if location == 'storage': return LLLnode.from_list( [ 'add', ['sha3_32', parent], LLLnode.from_list(index, annotation=annotation) ], typ=subtype, location='storage', ) elif location == 'storage_prehashed': return LLLnode.from_list( [ 'add', parent, LLLnode.from_list(index, annotation=annotation) ], typ=subtype, location='storage', ) elif location in ('calldata', 'memory'): offset = 0 for i in range(index): offset += 32 * get_size_of_type(typ.members[attrs[i]]) return LLLnode.from_list(['add', offset, parent], typ=typ.members[key], location=location, annotation=annotation) else: raise TypeMismatch("Not expecting a member variable access", pos) elif isinstance(typ, MappingType): if isinstance(key.typ, ByteArrayLike): if not isinstance(typ.keytype, ByteArrayLike) or ( typ.keytype.maxlen < key.typ.maxlen): raise TypeMismatch( "Mapping keys of bytes cannot be cast, use exact same bytes type of: " f"{str(typ.keytype)}", pos, ) subtype = typ.valuetype if len(key.args[0].args) >= 3: # handle bytes literal. sub = LLLnode.from_list([ 'seq', key, [ 'sha3', ['add', key.args[0].args[-1], 32], ['mload', key.args[0].args[-1]] ] ]) else: sub = LLLnode.from_list([ 'sha3', ['add', key.args[0].value, 32], ['mload', key.args[0].value] ]) else: subtype = typ.valuetype sub = base_type_conversion(key, key.typ, typ.keytype, pos=pos) if location == 'storage': return LLLnode.from_list(['sha3_64', parent, sub], typ=subtype, location='storage') elif location in ('memory', 'calldata'): raise TypeMismatch( "Can only have fixed-side arrays in memory, not mappings", pos) elif isinstance(typ, ListType): subtype = typ.subtype k = unwrap_location(key) if not is_base_type(key.typ, ('int128', 'uint256')): raise TypeMismatch(f'Invalid type for array index: {key.typ}', pos) if not array_bounds_check: sub = k elif key.typ.is_literal: # note: BaseType always has is_literal attr # perform the check at compile time and elide the runtime check. if key.value < 0 or key.value >= typ.count: raise ArrayIndexException( 'Array index determined to be out of bounds. ' f'Index is {key.value} but array size is {typ.count}', pos) sub = k else: # this works, even for int128. for int128, since two's-complement # is used, if the index is negative, (unsigned) LT will interpret # it as a very large number, larger than any practical value for # an array index, and the clamp will throw an error. sub = ['uclamplt', k, typ.count] if location == 'storage': return LLLnode.from_list(['add', ['sha3_32', parent], sub], typ=subtype, location='storage', pos=pos) elif location == 'storage_prehashed': return LLLnode.from_list(['add', parent, sub], typ=subtype, location='storage', pos=pos) elif location in ('calldata', 'memory'): offset = 32 * get_size_of_type(subtype) return LLLnode.from_list(['add', ['mul', offset, sub], parent], typ=subtype, location=location, pos=pos) else: raise TypeMismatch("Not expecting an array access ", pos) else: raise TypeMismatch( f"Cannot access the child of a constant variable! {typ}", pos)
def make_setter(left, right, location, pos, in_function_call=False): # Basic types if isinstance(left.typ, BaseType): right = base_type_conversion( right, right.typ, left.typ, pos, in_function_call=in_function_call, ) if location == 'storage': return LLLnode.from_list(['sstore', left, right], typ=None) elif location == 'memory': return LLLnode.from_list(['mstore', left, right], typ=None) # Byte arrays elif isinstance(left.typ, ByteArrayLike): return make_byte_array_copier(left, right, pos) # Can't copy mappings elif isinstance(left.typ, MappingType): raise TypeMismatch( "Cannot copy mappings; can only copy individual elements", pos) # Arrays elif isinstance(left.typ, ListType): # Cannot do something like [a, b, c] = [1, 2, 3] if left.value == "multi": raise Exception("Target of set statement must be a single item") if not isinstance(right.typ, (ListType, NullType)): raise TypeMismatch( f"Setter type mismatch: left side is array, right side is {right.typ}", pos) left_token = LLLnode.from_list('_L', typ=left.typ, location=left.location) if left.location == "storage": left = LLLnode.from_list(['sha3_32', left], typ=left.typ, location="storage_prehashed") left_token.location = "storage_prehashed" # Type checks if not isinstance(right.typ, NullType): if not isinstance(right.typ, ListType): raise TypeMismatch("Left side is array, right side is not", pos) if left.typ.count != right.typ.count: raise TypeMismatch("Mismatched number of elements", pos) # If the right side is a literal if right.value == "multi": if len(right.args) != left.typ.count: raise TypeMismatch("Mismatched number of elements", pos) subs = [] for i in range(left.typ.count): subs.append( make_setter(add_variable_offset( left_token, LLLnode.from_list(i, typ='int128'), pos=pos, array_bounds_check=False, ), right.args[i], location, pos=pos)) return LLLnode.from_list(['with', '_L', left, ['seq'] + subs], typ=None) # If the right side is a null # CC 20190619 probably not needed as of #1106 elif isinstance(right.typ, NullType): subs = [] for i in range(left.typ.count): subs.append( make_setter(add_variable_offset( left_token, LLLnode.from_list(i, typ='int128'), pos=pos, array_bounds_check=False, ), LLLnode.from_list(None, typ=NullType()), location, pos=pos)) return LLLnode.from_list(['with', '_L', left, ['seq'] + subs], typ=None) # If the right side is a variable else: right_token = LLLnode.from_list('_R', typ=right.typ, location=right.location) subs = [] for i in range(left.typ.count): subs.append( make_setter(add_variable_offset( left_token, LLLnode.from_list(i, typ='int128'), pos=pos, array_bounds_check=False, ), add_variable_offset( right_token, LLLnode.from_list(i, typ='int128'), pos=pos, array_bounds_check=False, ), location, pos=pos)) return LLLnode.from_list( ['with', '_L', left, ['with', '_R', right, ['seq'] + subs]], typ=None) # Structs elif isinstance(left.typ, TupleLike): if left.value == "multi" and isinstance(left.typ, StructType): raise Exception("Target of set statement must be a single item") if not isinstance(right.typ, NullType): if not isinstance(right.typ, left.typ.__class__): raise TypeMismatch( f"Setter type mismatch: left side is {left.typ}, right side is {right.typ}", pos, ) if isinstance(left.typ, StructType): for k in right.args: if k.value is None: raise InvalidLiteral( 'Setting struct value to None is not allowed, use a default value.', pos, ) for k in left.typ.members: if k not in right.typ.members: raise TypeMismatch( f"Keys don't match for structs, missing {k}", pos, ) for k in right.typ.members: if k not in left.typ.members: raise TypeMismatch( f"Keys don't match for structs, extra {k}", pos, ) if left.typ.name != right.typ.name: raise TypeMismatch(f"Expected {left.typ}, got {right.typ}", pos) else: if len(left.typ.members) != len(right.typ.members): raise TypeMismatch( "Tuple lengths don't match, " f"{len(left.typ.members)} vs {len(right.typ.members)}", pos, ) left_token = LLLnode.from_list('_L', typ=left.typ, location=left.location) if left.location == "storage": left = LLLnode.from_list(['sha3_32', left], typ=left.typ, location="storage_prehashed") left_token.location = "storage_prehashed" keyz = left.typ.tuple_keys() # If the left side is a literal if left.value == 'multi': locations = [arg.location for arg in left.args] else: locations = [location for _ in keyz] # If the right side is a literal if right.value == "multi": if len(right.args) != len(keyz): raise TypeMismatch("Mismatched number of elements", pos) # get the RHS arguments into a dict because # they are not guaranteed to be in the same order # the LHS keys. right_args = dict(zip(right.typ.tuple_keys(), right.args)) subs = [] for (key, loc) in zip(keyz, locations): subs.append( make_setter( add_variable_offset(left_token, key, pos=pos), right_args[key], loc, pos=pos, )) return LLLnode.from_list(['with', '_L', left, ['seq'] + subs], typ=None) # If the right side is a null elif isinstance(right.typ, NullType): subs = [] for typ, loc in zip(keyz, locations): subs.append( make_setter( add_variable_offset(left_token, typ, pos=pos), LLLnode.from_list(None, typ=NullType()), loc, pos=pos, )) return LLLnode.from_list(['with', '_L', left, ['seq'] + subs], typ=None) # If tuple assign. elif isinstance(left.typ, TupleType) and isinstance( right.typ, TupleType): subs = [] static_offset_counter = 0 zipped_components = zip(left.args, right.typ.members, locations) for var_arg in left.args: if var_arg.location == 'calldata': raise ConstancyViolation( f"Cannot modify function argument: {var_arg.annotation}", pos) for left_arg, right_arg, loc in zipped_components: if isinstance(right_arg, ByteArrayLike): RType = ByteArrayType if isinstance( right_arg, ByteArrayType) else StringType offset = LLLnode.from_list([ 'add', '_R', ['mload', ['add', '_R', static_offset_counter]] ], typ=RType(right_arg.maxlen), location='memory', pos=pos) static_offset_counter += 32 else: offset = LLLnode.from_list( ['mload', ['add', '_R', static_offset_counter]], typ=right_arg.typ, pos=pos, ) static_offset_counter += get_size_of_type(right_arg) * 32 subs.append(make_setter(left_arg, offset, loc, pos=pos)) return LLLnode.from_list( ['with', '_R', right, ['seq'] + subs], typ=None, annotation='Tuple assignment', ) # If the right side is a variable else: subs = [] right_token = LLLnode.from_list('_R', typ=right.typ, location=right.location) for typ, loc in zip(keyz, locations): subs.append( make_setter(add_variable_offset(left_token, typ, pos=pos), add_variable_offset(right_token, typ, pos=pos), loc, pos=pos)) return LLLnode.from_list( ['with', '_L', left, ['with', '_R', right, ['seq'] + subs]], typ=None, ) else: raise Exception("Invalid type for setters")
def gen_tuple_return(stmt, context, sub): abi_typ = abi_type_of(context.return_type) # according to the ABI, return types are ALWAYS tuples even if # only one element is being returned. # https://solidity.readthedocs.io/en/latest/abi-spec.html#function-selector-and-argument-encoding # "and the return values v_1, ..., v_k of f are encoded as # # enc((v_1, ..., v_k)) # i.e. the values are combined into a tuple and encoded. # " # therefore, wrap it in a tuple if it's not already a tuple. # (big difference between returning `(bytes,)` and `bytes`. abi_typ = ensure_tuple(abi_typ) abi_bytes_needed = abi_typ.static_size() + abi_typ.dynamic_size_bound() dst = context.memory_allocator.expand_memory(abi_bytes_needed) return_buffer = LLLnode(dst, location="memory", annotation="return_buffer", typ=context.return_type) check_assign(return_buffer, sub, pos=getpos(stmt)) if sub.value == "multi": if isinstance(context.return_type, TupleType) and not abi_typ.dynamic_size_bound(): # for tuples where every value is of the same type and a fixed length, # we can simplify the encoding by treating it as though it were an array base_types = set() for typ in context.return_type.members: while isinstance(typ, ListType): typ = typ.subtype base_types.add(typ.typ) if len(base_types) == 1: new_sub = LLLnode.from_list( context.new_internal_variable(context.return_type), typ=context.return_type, location="memory", ) setter = make_setter(new_sub, sub, "memory", pos=getpos(stmt)) return LLLnode.from_list( [ "seq", setter, make_return_stmt( stmt, context, new_sub, get_size_of_type(context.return_type) * 32, ), ], typ=None, pos=getpos(stmt), ) # in case of multi we can't create a variable to store location of the return expression # as multi can have data from multiple location like store, calldata etc encode_out = abi_encode(return_buffer, sub, pos=getpos(stmt), returns=True) load_return_len = ["mload", MemoryPositions.FREE_VAR_SPACE] os = [ "seq", ["mstore", MemoryPositions.FREE_VAR_SPACE, encode_out], make_return_stmt(stmt, context, return_buffer, load_return_len), ] return LLLnode.from_list(os, typ=None, pos=getpos(stmt), valency=0) # for tuple return types where a function is called inside the tuple, we # process the calls prior to encoding the return data if sub.value == "seq_unchecked" and sub.args[-1].value == "multi": encode_out = abi_encode(return_buffer, sub.args[-1], pos=getpos(stmt), returns=True) load_return_len = ["mload", MemoryPositions.FREE_VAR_SPACE] os = (["seq"] + sub.args[:-1] + [ ["mstore", MemoryPositions.FREE_VAR_SPACE, encode_out], make_return_stmt(stmt, context, return_buffer, load_return_len), ]) return LLLnode.from_list(os, typ=None, pos=getpos(stmt), valency=0) # for all othe cases we are creating a stack variable named sub_loc to store the location # of the return expression. This is done so that the return expression does not get evaluated # abi-encode uses a function named o_list which evaluate the expression multiple times sub_loc = LLLnode("sub_loc", typ=sub.typ, location=sub.location) encode_out = abi_encode(return_buffer, sub_loc, pos=getpos(stmt), returns=True) load_return_len = ["mload", MemoryPositions.FREE_VAR_SPACE] os = [ "with", "sub_loc", sub, [ "seq", ["mstore", MemoryPositions.FREE_VAR_SPACE, encode_out], make_return_stmt(stmt, context, return_buffer, load_return_len), ], ] return LLLnode.from_list(os, typ=None, pos=getpos(stmt), valency=0)
def make_byte_slice_copier(destination, source, length, max_length, pos=None): # Special case: memory to memory if source.location == "memory" and destination.location == "memory": return LLLnode.from_list( [ 'with', '_l', max_length, [ 'pop', ['call', ['gas'], 4, 0, source, '_l', destination, '_l'] ] ], typ=None, annotation=f'copy byte slice dest: {str(destination)}') # Copy over data if isinstance(source.typ, NullType): loader = 0 elif source.location == "memory": loader = [ 'mload', [ 'add', '_pos', ['mul', 32, ['mload', MemoryPositions.FREE_LOOP_INDEX]] ] ] elif source.location == "storage": loader = [ 'sload', ['add', '_pos', ['mload', MemoryPositions.FREE_LOOP_INDEX]] ] else: raise Exception("Unsupported location:" + source.location) # Where to paste it? if destination.location == "memory": setter = [ 'mstore', [ 'add', '_opos', ['mul', 32, ['mload', MemoryPositions.FREE_LOOP_INDEX]] ], loader ] elif destination.location == "storage": setter = [ 'sstore', ['add', '_opos', ['mload', MemoryPositions.FREE_LOOP_INDEX]], loader ] else: raise Exception("Unsupported location:" + destination.location) # Check to see if we hit the length checker = [ 'if', [ 'gt', ['mul', 32, ['mload', MemoryPositions.FREE_LOOP_INDEX]], '_actual_len' ], 'break' ] # Make a loop to do the copying o = [ 'with', '_pos', source, [ 'with', '_opos', destination, [ 'with', '_actual_len', length, [ 'repeat', MemoryPositions.FREE_LOOP_INDEX, 0, (max_length + 31) // 32, ['seq', checker, setter] ] ] ] ] return LLLnode.from_list( o, typ=None, annotation=f'copy byte slice src: {source} dst: {destination}', pos=pos, )
def add_variable_offset(parent, key, pos, array_bounds_check=True): typ, location = parent.typ, parent.location if isinstance(typ, TupleLike): if isinstance(typ, StructType): subtype = typ.members[key] attrs = list(typ.tuple_keys()) index = attrs.index(key) annotation = key else: attrs = list(range(len(typ.members))) index = key annotation = None if location == "storage": return LLLnode.from_list( [ "add", ["sha3_32", parent], LLLnode.from_list(index, annotation=annotation) ], typ=subtype, location="storage", ) elif location == "storage_prehashed": return LLLnode.from_list( [ "add", parent, LLLnode.from_list(index, annotation=annotation) ], typ=subtype, location="storage", ) elif location in ("calldata", "memory"): offset = 0 for i in range(index): offset += 32 * get_size_of_type(typ.members[attrs[i]]) return LLLnode.from_list( ["add", offset, parent], typ=typ.members[key], location=location, annotation=annotation, ) elif isinstance(typ, MappingType): sub = None if isinstance(key.typ, ByteArrayLike): if isinstance( typ.keytype, ByteArrayLike) and (typ.keytype.maxlen >= key.typ.maxlen): subtype = typ.valuetype if len(key.args[0].args) >= 3: # handle bytes literal. sub = LLLnode.from_list([ "seq", key, [ "sha3", ["add", key.args[0].args[-1], 32], ["mload", key.args[0].args[-1]], ], ]) else: value = key.args[0].value if value == "add": # special case, key is a bytes array within a tuple/struct value = key.args[0] sub = LLLnode.from_list(["sha3", ["add", value, 32], key]) else: subtype = typ.valuetype sub = base_type_conversion(key, key.typ, typ.keytype, pos=pos) if sub is not None and location == "storage": return LLLnode.from_list(["sha3_64", parent, sub], typ=subtype, location="storage") elif isinstance(typ, ListType) and is_base_type(key.typ, ("int128", "uint256")): subtype = typ.subtype k = unwrap_location(key) if not array_bounds_check: sub = k elif key.typ.is_literal: # note: BaseType always has is_literal attr # perform the check at compile time and elide the runtime check. if key.value < 0 or key.value >= typ.count: return sub = k else: # this works, even for int128. for int128, since two's-complement # is used, if the index is negative, (unsigned) LT will interpret # it as a very large number, larger than any practical value for # an array index, and the clamp will throw an error. sub = ["uclamplt", k, typ.count] if location == "storage": return LLLnode.from_list(["add", ["sha3_32", parent], sub], typ=subtype, location="storage", pos=pos) elif location == "storage_prehashed": return LLLnode.from_list(["add", parent, sub], typ=subtype, location="storage", pos=pos) elif location in ("calldata", "memory"): offset = 32 * get_size_of_type(subtype) return LLLnode.from_list(["add", ["mul", offset, sub], parent], typ=subtype, location=location, pos=pos)
def make_setter(left, right, location, pos, in_function_call=False): # Basic types if isinstance(left.typ, BaseType): right = base_type_conversion( right, right.typ, left.typ, pos, in_function_call=in_function_call, ) if location == "storage": return LLLnode.from_list(["sstore", left, right], typ=None) elif location == "memory": return LLLnode.from_list(["mstore", left, right], typ=None) # Byte arrays elif isinstance(left.typ, ByteArrayLike): return make_byte_array_copier(left, right, pos) # Can't copy mappings elif isinstance(left.typ, MappingType): raise TypeMismatch( "Cannot copy mappings; can only copy individual elements", pos) # Arrays elif isinstance(left.typ, ListType): # Cannot do something like [a, b, c] = [1, 2, 3] if left.value == "multi": return if not isinstance(right.typ, ListType): return if right.typ.count != left.typ.count: return left_token = LLLnode.from_list("_L", typ=left.typ, location=left.location) if left.location == "storage": left = LLLnode.from_list(["sha3_32", left], typ=left.typ, location="storage_prehashed") left_token.location = "storage_prehashed" # If the right side is a literal if right.value == "multi": subs = [] for i in range(left.typ.count): lhs_setter = _make_array_index_setter(left, left_token, pos, location, i) subs.append( make_setter( lhs_setter, right.args[i], location, pos=pos, )) if left.location == "memory" and isinstance(left.value, int): return LLLnode.from_list(["seq"] + subs, typ=None) else: return LLLnode.from_list(["with", "_L", left, ["seq"] + subs], typ=None) elif right.value is None: if right.typ != left.typ: return if left.location == "memory": return mzero(left, 32 * get_size_of_type(left.typ)) subs = [] for i in range(left.typ.count): subs.append( make_setter( add_variable_offset( left_token, LLLnode.from_list(i, typ="int128"), pos=pos, array_bounds_check=False, ), LLLnode.from_list(None, typ=right.typ.subtype), location, pos=pos, )) return LLLnode.from_list(["with", "_L", left, ["seq"] + subs], typ=None) # If the right side is a variable else: right_token = LLLnode.from_list("_R", typ=right.typ, location=right.location) subs = [] for i in range(left.typ.count): lhs_setter = _make_array_index_setter(left, left_token, pos, left.location, i) rhs_setter = _make_array_index_setter(right, right_token, pos, right.location, i) subs.append( make_setter( lhs_setter, rhs_setter, location, pos=pos, )) lll_node = ["seq"] + subs if right.location != "memory" or not isinstance(right.value, int): lll_node = ["with", "_R", right, lll_node] if left.location != "memory" or not isinstance(left.value, int): lll_node = ["with", "_L", left, lll_node] return LLLnode.from_list(lll_node, typ=None) # Structs elif isinstance(left.typ, TupleLike): if left.value == "multi" and isinstance(left.typ, StructType): return if right.value is not None: if not isinstance(right.typ, left.typ.__class__): return if isinstance(left.typ, StructType): for k in left.typ.members: if k not in right.typ.members: return for k in right.typ.members: if k not in left.typ.members: return if left.typ.name != right.typ.name: return else: if len(left.typ.members) != len(right.typ.members): return left_token = LLLnode.from_list("_L", typ=left.typ, location=left.location) if left.location == "storage": left = LLLnode.from_list(["sha3_32", left], typ=left.typ, location="storage_prehashed") left_token.location = "storage_prehashed" keyz = left.typ.tuple_keys() # If the left side is a literal if left.value == "multi": locations = [arg.location for arg in left.args] else: locations = [location for _ in keyz] # If the right side is a literal if right.value == "multi": if len(right.args) != len(keyz): return # get the RHS arguments into a dict because # they are not guaranteed to be in the same order # the LHS keys. right_args = dict(zip(right.typ.tuple_keys(), right.args)) subs = [] for (key, loc) in zip(keyz, locations): subs.append( make_setter( add_variable_offset(left_token, key, pos=pos), right_args[key], loc, pos=pos, )) return LLLnode.from_list(["with", "_L", left, ["seq"] + subs], typ=None) # If the right side is a null elif right.value is None: if left.typ != right.typ: return if left.location == "memory": return mzero(left, 32 * get_size_of_type(left.typ)) subs = [] for key, loc in zip(keyz, locations): subs.append( make_setter( add_variable_offset(left_token, key, pos=pos), LLLnode.from_list(None, typ=right.typ.members[key]), loc, pos=pos, )) return LLLnode.from_list(["with", "_L", left, ["seq"] + subs], typ=None) # If tuple assign. elif isinstance(left.typ, TupleType) and isinstance( right.typ, TupleType): subs = [] for var_arg in left.args: if var_arg.location == "calldata": return right_token = LLLnode.from_list("_R", typ=right.typ, location=right.location) for left_arg, key, loc in zip(left.args, keyz, locations): subs.append( make_setter(left_arg, add_variable_offset(right_token, key, pos=pos), loc, pos=pos)) return LLLnode.from_list( ["with", "_R", right, ["seq"] + subs], typ=None, annotation="Tuple assignment", ) # If the left side is a variable i.e struct type else: subs = [] right_token = LLLnode.from_list("_R", typ=right.typ, location=right.location) for typ, loc in zip(keyz, locations): subs.append( make_setter( add_variable_offset(left_token, typ, pos=pos), add_variable_offset(right_token, typ, pos=pos), loc, pos=pos, )) return LLLnode.from_list( ["with", "_L", left, ["with", "_R", right, ["seq"] + subs]], typ=None, )
def compare(self): left = Expr.parse_value_expr(self.expr.left, self.context) right = Expr.parse_value_expr(self.expr.comparators[0], self.context) if isinstance(right.typ, NullType): raise InvalidLiteralException( 'Comparison to None is not allowed, compare against a default value.', self.expr, ) if isinstance(left.typ, ByteArrayType) and isinstance( right.typ, ByteArrayType): if left.typ.maxlen != right.typ.maxlen: raise TypeMismatchException( 'Can only compare bytes of the same length', self.expr) if left.typ.maxlen > 32 or right.typ.maxlen > 32: raise ParserException( 'Can only compare bytes of length shorter than 32 bytes', self.expr, ) elif isinstance(self.expr.ops[0], ast.In) and isinstance( right.typ, ListType): if left.typ != right.typ.subtype: raise TypeMismatchException( "Can't use IN comparison with different types!", self.expr, ) return self.build_in_comparator() else: if not are_units_compatible( left.typ, right.typ) and not are_units_compatible( right.typ, left.typ): # noqa: E501 raise TypeMismatchException( "Can't compare values with different units!", self.expr) if len(self.expr.ops) != 1: raise StructureException( "Cannot have a comparison with more than two elements", self.expr, ) if isinstance(self.expr.ops[0], ast.Gt): op = 'sgt' elif isinstance(self.expr.ops[0], ast.GtE): op = 'sge' elif isinstance(self.expr.ops[0], ast.LtE): op = 'sle' elif isinstance(self.expr.ops[0], ast.Lt): op = 'slt' elif isinstance(self.expr.ops[0], ast.Eq): op = 'eq' elif isinstance(self.expr.ops[0], ast.NotEq): op = 'ne' else: raise Exception("Unsupported comparison operator") # Compare (limited to 32) byte arrays. if isinstance(left.typ, ByteArrayType) and isinstance( left.typ, ByteArrayType): left = Expr(self.expr.left, self.context).lll_node right = Expr(self.expr.comparators[0], self.context).lll_node def load_bytearray(side): if side.location == 'memory': return ['mload', ['add', 32, side]] elif side.location == 'storage': return ['sload', ['add', 1, ['sha3_32', side]]] return LLLnode.from_list( [op, load_bytearray(left), load_bytearray(right)], typ='bool', pos=getpos(self.expr), ) # Compare other types. if not is_numeric_type(left.typ) or not is_numeric_type(right.typ): if op not in ('eq', 'ne'): raise TypeMismatchException("Invalid type for comparison op", self.expr) left_type, right_type = left.typ.typ, right.typ.typ # Special Case: comparison of a literal integer. If in valid range allow it to be compared. if {left_type, right_type} == {'int128', 'uint256'} and { left.typ.is_literal, right.typ.is_literal } == {True, False}: # noqa: E501 comparison_allowed = False if left.typ.is_literal and SizeLimits.in_bounds( right_type, left.value): comparison_allowed = True elif right.typ.is_literal and SizeLimits.in_bounds( left_type, right.value): comparison_allowed = True op = self._signed_to_unsigned_comparision_op(op) if comparison_allowed: return LLLnode.from_list([op, left, right], typ='bool', pos=getpos(self.expr)) elif {left_type, right_type} == {'uint256', 'uint256'}: op = self._signed_to_unsigned_comparision_op(op) elif (left_type in ('decimal', 'int128') or right_type in ('decimal', 'int128')) and left_type != right_type: # noqa: E501 raise TypeMismatchException( 'Implicit conversion from {} to {} disallowed, please convert.' .format( left_type, right_type, ), self.expr, ) if left_type == right_type: return LLLnode.from_list([op, left, right], typ='bool', pos=getpos(self.expr)) else: raise TypeMismatchException( "Unsupported types for comparison: %r %r" % (left_type, right_type), self.expr, )
def make_external_call(stmt_expr, context): from vyper.parser.expr import Expr value, gas = get_external_interface_keywords(stmt_expr, context) if isinstance(stmt_expr.func, vy_ast.Attribute) and isinstance( stmt_expr.func.value, vy_ast.Call): contract_name = stmt_expr.func.value.func.id contract_address = Expr.parse_value_expr(stmt_expr.func.value.args[0], context) return external_call( stmt_expr, context, contract_name, contract_address, pos=getpos(stmt_expr), value=value, gas=gas, ) elif (isinstance(stmt_expr.func.value, vy_ast.Attribute) and stmt_expr.func.value.attr in context.sigs): # noqa: E501 contract_name = stmt_expr.func.value.attr var = context.globals[stmt_expr.func.value.attr] contract_address = unwrap_location( LLLnode.from_list( var.pos, typ=var.typ, location="storage", pos=getpos(stmt_expr), annotation="self." + stmt_expr.func.value.attr, )) return external_call( stmt_expr, context, contract_name, contract_address, pos=getpos(stmt_expr), value=value, gas=gas, ) elif (isinstance(stmt_expr.func.value, vy_ast.Attribute) and stmt_expr.func.value.attr in context.globals and hasattr(context.globals[stmt_expr.func.value.attr].typ, "name")): contract_name = context.globals[stmt_expr.func.value.attr].typ.name var = context.globals[stmt_expr.func.value.attr] contract_address = unwrap_location( LLLnode.from_list( var.pos, typ=var.typ, location="storage", pos=getpos(stmt_expr), annotation="self." + stmt_expr.func.value.attr, )) return external_call( stmt_expr, context, contract_name, contract_address, pos=getpos(stmt_expr), value=value, gas=gas, ) else: raise StructureException("Unsupported operator.", stmt_expr)
def number(self): orignum = get_original_if_0_prefixed(self.expr, self.context) if orignum is None and isinstance(self.expr.n, int): # Literal (mostly likely) becomes int128 if SizeLimits.in_bounds('int128', self.expr.n) or self.expr.n < 0: return LLLnode.from_list( self.expr.n, typ=BaseType('int128', unit={}, is_literal=True), pos=getpos(self.expr), ) # Literal is large enough (mostly likely) becomes uint256. else: return LLLnode.from_list( self.expr.n, typ=BaseType('uint256', unit={}, is_literal=True), pos=getpos(self.expr), ) elif isinstance(self.expr.n, float): numstring, num, den = get_number_as_fraction( self.expr, self.context) # if not SizeLimits.in_bounds('decimal', num // den): # if not SizeLimits.MINDECIMAL * den <= num <= SizeLimits.MAXDECIMAL * den: if not (SizeLimits.MINNUM * den < num < SizeLimits.MAXNUM * den): raise InvalidLiteralException( "Number out of range: " + numstring, self.expr) if DECIMAL_DIVISOR % den: raise InvalidLiteralException( "Too many decimal places: " + numstring, self.expr) return LLLnode.from_list( num * DECIMAL_DIVISOR // den, typ=BaseType('decimal', unit=None), pos=getpos(self.expr), ) # Binary literal. elif orignum[:2] == '0b': str_val = orignum[2:] total_bits = len(orignum[2:]) total_bits = ( total_bits if total_bits % 8 == 0 else total_bits + 8 - (total_bits % 8) # ceil8 to get byte length. ) if len( orignum[2:] ) != total_bits: # Support only full formed bit definitions. raise InvalidLiteralException( ("Bit notation requires a multiple of 8 bits / 1 byte. {} " "bit(s) are missing.").format(total_bits - len(orignum[2:])), self.expr) byte_len = int(total_bits / 8) placeholder = self.context.new_placeholder(ByteArrayType(byte_len)) seq = [] seq.append(['mstore', placeholder, byte_len]) for i in range(0, total_bits, 256): section = str_val[i:i + 256] int_val = int(section, 2) << (256 - len(section) ) # bytes are right padded. seq.append(['mstore', ['add', placeholder, i + 32], int_val]) return LLLnode.from_list( ['seq'] + seq + [placeholder], typ=ByteArrayType(byte_len), location='memory', pos=getpos(self.expr), annotation='Create ByteArray (Binary literal): %s' % str_val, ) elif len(orignum) == 42: if checksum_encode(orignum) != orignum: raise InvalidLiteralException( """Address checksum mismatch. If you are sure this is the right address, the correct checksummed form is: %s""" % checksum_encode(orignum), self.expr) return LLLnode.from_list( self.expr.n, typ=BaseType('address', is_literal=True), pos=getpos(self.expr), ) elif len(orignum) == 66: return LLLnode.from_list( self.expr.n, typ=BaseType('bytes32', is_literal=True), pos=getpos(self.expr), ) else: raise InvalidLiteralException( ("Cannot read 0x value with length %d. Expecting 42 (address " "incl 0x) or 66 (bytes32 incl 0x)") % len(orignum), self.expr)
def build_in_comparator(self): from vyper.parser.parser import make_setter left = Expr(self.expr.left, self.context).lll_node right = Expr(self.expr.comparators[0], self.context).lll_node if left.typ != right.typ.subtype: raise TypeMismatchException( "%s cannot be in a list of %s" % (left.typ, right.typ.subtype), ) result_placeholder = self.context.new_placeholder(BaseType('bool')) setter = [] # Load nth item from list in memory. if right.value == 'multi': # Copy literal to memory to be compared. tmp_list = LLLnode.from_list(obj=self.context.new_placeholder( ListType(right.typ.subtype, right.typ.count)), typ=ListType(right.typ.subtype, right.typ.count), location='memory') setter = make_setter(tmp_list, right, 'memory', pos=getpos(self.expr)) load_i_from_list = [ 'mload', [ 'add', tmp_list, ['mul', 32, ['mload', MemoryPositions.FREE_LOOP_INDEX]] ], ] elif right.location == "storage": load_i_from_list = [ 'sload', [ 'add', ['sha3_32', right], ['mload', MemoryPositions.FREE_LOOP_INDEX] ], ] else: load_i_from_list = [ 'mload', [ 'add', right, ['mul', 32, ['mload', MemoryPositions.FREE_LOOP_INDEX]] ], ] # Condition repeat loop has to break on. break_loop_condition = [ 'if', ['eq', unwrap_location(left), load_i_from_list], [ 'seq', ['mstore', '_result', 1], # store true. 'break' ] ] # Repeat loop to loop-compare each item in the list. for_loop_sequence = [['mstore', result_placeholder, 0], [ 'with', '_result', result_placeholder, [ 'repeat', MemoryPositions.FREE_LOOP_INDEX, 0, right.typ.count, break_loop_condition, ] ], ['mload', result_placeholder]] # Save list to memory, so one can iterate over it, # used when literal was created with tmp_list. if setter: compare_sequence = ['seq', setter] + for_loop_sequence else: compare_sequence = ['seq'] + for_loop_sequence # Compare the result of the repeat loop to 1, to know if a match was found. o = LLLnode.from_list(['eq', 1, compare_sequence], typ='bool', annotation="in comporator") return o
def arithmetic(self): pre_alloc_left, left = self.arithmetic_get_reference(self.expr.left) pre_alloc_right, right = self.arithmetic_get_reference(self.expr.right) if not is_numeric_type(left.typ) or not is_numeric_type(right.typ): raise TypeMismatchException( "Unsupported types for arithmetic op: %r %r" % (left.typ, right.typ), self.expr, ) arithmetic_pair = {left.typ.typ, right.typ.typ} # Special Case: Simplify any literal to literal arithmetic at compile time. if left.typ.is_literal and right.typ.is_literal and \ isinstance(right.value, int) and isinstance(left.value, int): if isinstance(self.expr.op, ast.Add): val = left.value + right.value elif isinstance(self.expr.op, ast.Sub): val = left.value - right.value elif isinstance(self.expr.op, ast.Mult): val = left.value * right.value elif isinstance(self.expr.op, ast.Div): val = left.value // right.value elif isinstance(self.expr.op, ast.Mod): val = left.value % right.value elif isinstance(self.expr.op, ast.Pow): val = left.value**right.value else: raise ParserException( 'Unsupported literal operator: %s' % str(type(self.expr.op)), self.expr, ) num = ast.Num(val) num.source_code = self.expr.source_code num.lineno = self.expr.lineno num.col_offset = self.expr.col_offset return Expr.parse_value_expr(num, self.context) # Special case with uint256 were int literal may be casted. if arithmetic_pair == {'uint256', 'int128'}: # Check right side literal. if right.typ.is_literal and SizeLimits.in_bounds( 'uint256', right.value): right = LLLnode.from_list( right.value, typ=BaseType('uint256', None, is_literal=True), pos=getpos(self.expr), ) arithmetic_pair = {left.typ.typ, right.typ.typ} # Check left side literal. elif left.typ.is_literal and SizeLimits.in_bounds( 'uint256', left.value): left = LLLnode.from_list( left.value, typ=BaseType('uint256', None, is_literal=True), pos=getpos(self.expr), ) arithmetic_pair = {left.typ.typ, right.typ.typ} # Only allow explicit conversions to occur. if left.typ.typ != right.typ.typ: raise TypeMismatchException( "Cannot implicitly convert {} to {}.".format( left.typ.typ, right.typ.typ), self.expr, ) ltyp, rtyp = left.typ.typ, right.typ.typ if isinstance(self.expr.op, (ast.Add, ast.Sub)): if left.typ.unit != right.typ.unit and left.typ.unit != {} and right.typ.unit != {}: raise TypeMismatchException( "Unit mismatch: %r %r" % (left.typ.unit, right.typ.unit), self.expr, ) if left.typ.positional and right.typ.positional and isinstance( self.expr.op, ast.Add): raise TypeMismatchException( "Cannot add two positional units!", self.expr, ) new_unit = left.typ.unit or right.typ.unit # xor, as subtracting two positionals gives a delta new_positional = left.typ.positional ^ right.typ.positional op = 'add' if isinstance(self.expr.op, ast.Add) else 'sub' if ltyp == 'uint256' and isinstance(self.expr.op, ast.Add): o = LLLnode.from_list( [ 'seq', # Checks that: a + b >= a ['assert', ['ge', ['add', left, right], left]], ['add', left, right], ], typ=BaseType('uint256', new_unit, new_positional), pos=getpos(self.expr)) elif ltyp == 'uint256' and isinstance(self.expr.op, ast.Sub): o = LLLnode.from_list( [ 'seq', # Checks that: a >= b ['assert', ['ge', left, right]], ['sub', left, right] ], typ=BaseType('uint256', new_unit, new_positional), pos=getpos(self.expr)) elif ltyp == rtyp: o = LLLnode.from_list( [op, left, right], typ=BaseType(ltyp, new_unit, new_positional), pos=getpos(self.expr), ) else: raise Exception("Unsupported Operation '%r(%r, %r)'" % (op, ltyp, rtyp)) elif isinstance(self.expr.op, ast.Mult): if left.typ.positional or right.typ.positional: raise TypeMismatchException( "Cannot multiply positional values!", self.expr) new_unit = combine_units(left.typ.unit, right.typ.unit) if ltyp == rtyp == 'uint256': o = LLLnode.from_list([ 'if', ['eq', left, 0], [0], [ 'seq', [ 'assert', ['eq', ['div', ['mul', left, right], left], right] ], ['mul', left, right] ], ], typ=BaseType('uint256', new_unit), pos=getpos(self.expr)) elif ltyp == rtyp == 'int128': o = LLLnode.from_list( ['mul', left, right], typ=BaseType('int128', new_unit), pos=getpos(self.expr), ) elif ltyp == rtyp == 'decimal': o = LLLnode.from_list([ 'with', 'r', right, [ 'with', 'l', left, [ 'with', 'ans', ['mul', 'l', 'r'], [ 'seq', [ 'assert', [ 'or', [ 'eq', ['sdiv', 'ans', 'l'], 'r' ], ['iszero', 'l'] ] ], ['sdiv', 'ans', DECIMAL_DIVISOR], ], ], ], ], typ=BaseType('decimal', new_unit), pos=getpos(self.expr)) else: raise Exception("Unsupported Operation 'mul(%r, %r)'" % (ltyp, rtyp)) elif isinstance(self.expr.op, ast.Div): if left.typ.positional or right.typ.positional: raise TypeMismatchException("Cannot divide positional values!", self.expr) new_unit = combine_units(left.typ.unit, right.typ.unit, div=True) if ltyp == rtyp == 'uint256': o = LLLnode.from_list( [ 'seq', # Checks that: b != 0 ['assert', right], ['div', left, right], ], typ=BaseType('uint256', new_unit), pos=getpos(self.expr)) elif ltyp == rtyp == 'int128': o = LLLnode.from_list( ['sdiv', left, ['clamp_nonzero', right]], typ=BaseType('int128', new_unit), pos=getpos(self.expr), ) elif ltyp == rtyp == 'decimal': o = LLLnode.from_list([ 'with', 'l', left, [ 'with', 'r', ['clamp_nonzero', right], [ 'sdiv', ['mul', 'l', DECIMAL_DIVISOR], 'r', ], ] ], typ=BaseType('decimal', new_unit), pos=getpos(self.expr)) else: raise Exception("Unsupported Operation 'div(%r, %r)'" % (ltyp, rtyp)) elif isinstance(self.expr.op, ast.Mod): if left.typ.positional or right.typ.positional: raise TypeMismatchException( "Cannot use positional values as modulus arguments!", self.expr, ) if not are_units_compatible(left.typ, right.typ) and not ( left.typ.unit or right.typ.unit): # noqa: E501 raise TypeMismatchException( "Modulus arguments must have same unit", self.expr) new_unit = left.typ.unit or right.typ.unit if ltyp == rtyp == 'uint256': o = LLLnode.from_list( ['seq', ['assert', right], ['mod', left, right]], typ=BaseType('uint256', new_unit), pos=getpos(self.expr)) elif ltyp == rtyp: o = LLLnode.from_list( ['smod', left, ['clamp_nonzero', right]], typ=BaseType(ltyp, new_unit), pos=getpos(self.expr), ) else: raise Exception("Unsupported Operation 'mod(%r, %r)'" % (ltyp, rtyp)) elif isinstance(self.expr.op, ast.Pow): if left.typ.positional or right.typ.positional: raise TypeMismatchException( "Cannot use positional values as exponential arguments!", self.expr, ) if right.typ.unit: raise TypeMismatchException( "Cannot use unit values as exponents", self.expr, ) if ltyp != 'int128' and ltyp != 'uint256' and isinstance( self.expr.right, ast.Name): raise TypeMismatchException( "Cannot use dynamic values as exponents, for unit base types", self.expr, ) if ltyp == rtyp == 'uint256': o = LLLnode.from_list([ 'seq', [ 'assert', [ 'or', ['or', ['eq', right, 1], ['iszero', right]], ['lt', left, ['exp', left, right]] ], ], ['exp', left, right], ], typ=BaseType('uint256'), pos=getpos(self.expr)) elif ltyp == rtyp == 'int128': new_unit = left.typ.unit if left.typ.unit and not isinstance(self.expr.right, ast.Name): new_unit = { left.typ.unit.copy().popitem()[0]: self.expr.right.n } o = LLLnode.from_list( ['exp', left, right], typ=BaseType('int128', new_unit), pos=getpos(self.expr), ) else: raise TypeMismatchException( 'Only whole number exponents are supported', self.expr) else: raise ParserException( "Unsupported binary operator: %r" % self.expr.op, self.expr) p = ['seq'] if pre_alloc_left: p.append(pre_alloc_left) if pre_alloc_right: p.append(pre_alloc_right) if o.typ.typ == 'int128': p.append([ 'clamp', ['mload', MemoryPositions.MINNUM], o, ['mload', MemoryPositions.MAXNUM], ]) return LLLnode.from_list(p, typ=o.typ, pos=getpos(self.expr)) elif o.typ.typ == 'decimal': p.append([ 'clamp', ['mload', MemoryPositions.MINDECIMAL], o, ['mload', MemoryPositions.MAXDECIMAL], ]) return LLLnode.from_list(p, typ=o.typ, pos=getpos(self.expr)) if o.typ.typ == 'uint256': p.append(o) return LLLnode.from_list(p, typ=o.typ, pos=getpos(self.expr)) else: raise Exception("%r %r" % (o, o.typ))
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 TypeMismatchException( "Type mismatch: balance keyword expects an address as input", self.expr) return LLLnode.from_list( ['balance', addr], typ=BaseType('uint256', {'wei': 1}), 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 TypeMismatchException( "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), ) # self.x: global attribute elif isinstance(self.expr.value, 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, ast.Name) and self.expr.value.id in ("msg", "block", "tx"): key = self.expr.value.id + "." + self.expr.attr if key == "msg.sender": if self.context.is_private: raise ParserException( "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 NonPayableViolationException( "Cannot use msg.value in a non-payable function", self.expr, ) return LLLnode.from_list( ['callvalue'], typ=BaseType('uint256', {'wei': 1}), 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', {'sec': 1}, True), 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)) else: raise ParserException("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 TypeMismatchException( "Type mismatch: member variable access not expected", self.expr.value, ) attrs = list(sub.typ.members.keys()) if self.expr.attr not in attrs: raise TypeMismatchException( "Member %s not found. Only the following available: %s" % (self.expr.attr, " ".join(attrs)), self.expr, ) return add_variable_offset(sub, self.expr.attr, pos=getpos(self.expr))
def parse_other_functions(o, otherfuncs, sigs, external_interfaces, global_ctx, default_function): # check for payable/nonpayable external functions to optimize nonpayable assertions func_types = [i._metadata["type"] for i in global_ctx._defs] mutabilities = [ i.mutability for i in func_types if i.visibility == FunctionVisibility.EXTERNAL ] has_payable = next( (True for i in mutabilities if i == StateMutability.PAYABLE), False) has_nonpayable = next( (True for i in mutabilities if i != StateMutability.PAYABLE), False) is_default_payable = (default_function is not None and default_function._metadata["type"].mutability == StateMutability.PAYABLE) # when a contract has a payable default function and at least one nonpayable # external function, we must perform the nonpayable check on every function check_per_function = is_default_payable and has_nonpayable # generate LLL for regular functions payable_func_sub = ["seq"] external_func_sub = ["seq"] internal_func_sub = ["seq"] add_gas = func_init_lll().gas for func_node in otherfuncs: func_type = func_node._metadata["type"] func_lll = parse_function(func_node, { **{ "self": sigs }, **external_interfaces }, global_ctx, check_per_function) if func_type.visibility == FunctionVisibility.INTERNAL: internal_func_sub.append(func_lll) elif func_type.mutability == StateMutability.PAYABLE: add_gas += 30 payable_func_sub.append(func_lll) else: external_func_sub.append(func_lll) add_gas += 30 func_lll.total_gas += add_gas for sig in sig_utils.generate_default_arg_sigs(func_node, external_interfaces, global_ctx): sig.gas = func_lll.total_gas sigs[sig.sig] = sig # generate LLL for fallback function if default_function: fallback_lll = parse_function( default_function, { **{ "self": sigs }, **external_interfaces }, global_ctx, # include a nonpayble check here if the contract only has a default function check_per_function or not otherfuncs, ) else: fallback_lll = LLLnode.from_list(["revert", 0, 0], typ=None, annotation="Default function") if check_per_function: external_seq = ["seq", payable_func_sub, external_func_sub] else: # payable functions are placed prior to nonpayable functions # and seperated by a nonpayable assertion external_seq = ["seq"] if has_payable: external_seq.append(payable_func_sub) if has_nonpayable: external_seq.extend([["assert", ["iszero", "callvalue"]], external_func_sub]) # bytecode is organized by: external functions, fallback fn, internal functions # this way we save gas and reduce bytecode by not jumping over internal functions main_seq = [ "seq", func_init_lll(), ["with", "_func_sig", ["mload", 0], external_seq], ["seq_unchecked", ["label", "fallback"], fallback_lll], internal_func_sub, ] o.append(["return", 0, ["lll", main_seq, 0]]) return o, main_seq
def make_byte_slice_copier(destination, source, length, max_length, pos=None): # Special case: memory to memory if source.location == "memory" and destination.location == "memory": return LLLnode.from_list( [ "with", "_l", max_length, [ "pop", ["call", ["gas"], 4, 0, source, "_l", destination, "_l"] ], ], typ=None, annotation=f"copy byte slice dest: {str(destination)}", ) # special case: rhs is zero if source.value is None: if destination.location == "memory": return mzero(destination, max_length) else: loader = 0 # Copy over data elif source.location == "memory": loader = [ "mload", [ "add", "_pos", ["mul", 32, ["mload", MemoryPositions.FREE_LOOP_INDEX]] ] ] elif source.location == "storage": loader = [ "sload", ["add", "_pos", ["mload", MemoryPositions.FREE_LOOP_INDEX]] ] elif source.location == "calldata": loader = [ "calldataload", [ "add", "_pos", ["mul", 32, ["mload", MemoryPositions.FREE_LOOP_INDEX]] ], ] else: raise CompilerPanic(f"Unsupported location: {source.location}") # Where to paste it? if destination.location == "memory": setter = [ "mstore", [ "add", "_opos", ["mul", 32, ["mload", MemoryPositions.FREE_LOOP_INDEX]] ], loader, ] elif destination.location == "storage": setter = [ "sstore", ["add", "_opos", ["mload", MemoryPositions.FREE_LOOP_INDEX]], loader ] else: raise CompilerPanic(f"Unsupported location: {destination.location}") # Check to see if we hit the length checker = [ "if", [ "gt", ["mul", 32, ["mload", MemoryPositions.FREE_LOOP_INDEX]], "_actual_len" ], "break", ] # Make a loop to do the copying ipos = 0 if source.value is None else source o = [ "with", "_pos", ipos, [ "with", "_opos", destination, [ "with", "_actual_len", length, [ "repeat", MemoryPositions.FREE_LOOP_INDEX, 0, (max_length + 31) // 32, ["seq", checker, setter], ], ], ], ] return LLLnode.from_list( o, typ=None, annotation=f"copy byte slice src: {source} dst: {destination}", pos=pos, )
def external_call(node, context, interface_name, contract_address, pos, value=None, gas=None): from vyper.parser.expr import Expr if value is None: value = 0 if gas is None: gas = "gas" method_name = node.func.attr sig = context.sigs[interface_name][method_name] inargs, inargsize, _ = pack_arguments( sig, [Expr(arg, context).lll_node for arg in node.args], context, node.func, is_external_call=True, ) output_placeholder, output_size, returner = get_external_call_output( sig, context) sub = ["seq"] if not output_size: # if we do not expect return data, check that a contract exists at the target address # we can omit this when we _do_ expect return data because we later check `returndatasize` sub.append(["assert", ["extcodesize", contract_address]]) if context.is_constant() and sig.mutability not in ("view", "pure"): # TODO this can probably go raise StateAccessViolation( f"May not call state modifying function '{method_name}' " f"within {context.pp_constancy()}.", node, ) if context.is_constant() or sig.mutability in ("view", "pure"): sub.append([ "assert", [ "staticcall", gas, contract_address, inargs, inargsize, output_placeholder, output_size, ], ]) else: sub.append([ "assert", [ "call", gas, contract_address, value, inargs, inargsize, output_placeholder, output_size, ], ]) if output_size: # when return data is expected, revert when the length of `returndatasize` is insufficient output_type = sig.output_type if not has_dynamic_data(output_type): static_output_size = get_static_size_of_type(output_type) * 32 sub.append( ["assert", ["gt", "returndatasize", static_output_size - 1]]) else: if isinstance(output_type, ByteArrayLike): types_list = (output_type, ) elif isinstance(output_type, TupleLike): types_list = output_type.tuple_members() else: raise dynamic_checks = [] static_offset = output_placeholder static_output_size = 0 for typ in types_list: # ensure length of bytes does not exceed max allowable length for type if isinstance(typ, ByteArrayLike): static_output_size += 32 # do not perform this check on calls to a JSON interface - we don't know # for certain how long the expected data is if not sig.is_from_json: dynamic_checks.append([ "assert", [ "lt", [ "mload", [ "add", ["mload", static_offset], output_placeholder ], ], typ.maxlen + 1, ], ]) static_offset += get_static_size_of_type(typ) * 32 static_output_size += get_static_size_of_type(typ) * 32 sub.append( ["assert", ["gt", "returndatasize", static_output_size - 1]]) sub.extend(dynamic_checks) sub.extend(returner) return LLLnode.from_list(sub, typ=sig.output_type, location="memory", pos=getpos(node))
def get_length(arg): if arg.location == "memory": return LLLnode.from_list(["mload", arg], typ=BaseType("uint256")) elif arg.location == "storage": return LLLnode.from_list(["sload", ["sha3_32", arg]], typ=BaseType("uint256"))
def pack_logging_data(expected_data, args, context, pos): # Checks to see if there's any data if not args: return ['seq'], 0, None, 0 holder = ['seq'] maxlen = len(args) * 32 # total size of all packed args (upper limit) # Unroll any function calls, to temp variables. prealloacted = {} for idx, (arg, _expected_arg) in enumerate(zip(args, expected_data)): if isinstance(arg, (vy_ast.Str, vy_ast.Call)): expr = Expr(arg, context) source_lll = expr.lll_node typ = source_lll.typ if isinstance(arg, vy_ast.Str): if len(arg.s) > typ.maxlen: raise TypeMismatchException( f"Data input bytes are to big: {len(arg.s)} {typ}", pos) tmp_variable = context.new_internal_variable( f'_log_pack_var_{arg.lineno}_{arg.col_offset}', source_lll.typ, ) tmp_variable_node = LLLnode.from_list( tmp_variable, typ=source_lll.typ, pos=getpos(arg), location="memory", annotation=f'log_prealloacted {source_lll.typ}', ) # Store len. # holder.append(['mstore', len_placeholder, ['mload', unwrap_location(source_lll)]]) # Copy bytes. holder.append( make_setter(tmp_variable_node, source_lll, pos=getpos(arg), location='memory')) prealloacted[idx] = tmp_variable_node requires_dynamic_offset = any( [isinstance(data.typ, ByteArrayLike) for data in expected_data]) if requires_dynamic_offset: dynamic_offset_counter = context.new_placeholder(BaseType(32)) dynamic_placeholder = context.new_placeholder(BaseType(32)) else: dynamic_offset_counter = None # Create placeholder for static args. Note: order of new_*() is important. placeholder_map = {} for i, (_arg, data) in enumerate(zip(args, expected_data)): typ = data.typ if not isinstance(typ, ByteArrayLike): placeholder = context.new_placeholder(typ) else: placeholder = context.new_placeholder(BaseType(32)) placeholder_map[i] = placeholder # Populate static placeholders. for i, (arg, data) in enumerate(zip(args, expected_data)): typ = data.typ placeholder = placeholder_map[i] if not isinstance(typ, ByteArrayLike): holder, maxlen = pack_args_by_32( holder, maxlen, prealloacted.get(i, arg), typ, context, placeholder, pos=pos, ) # Dynamic position starts right after the static args. if requires_dynamic_offset: holder.append( LLLnode.from_list(['mstore', dynamic_offset_counter, maxlen])) # Calculate maximum dynamic offset placeholders, used for gas estimation. for _arg, data in zip(args, expected_data): typ = data.typ if isinstance(typ, ByteArrayLike): maxlen += 32 + ceil32(typ.maxlen) if requires_dynamic_offset: datamem_start = dynamic_placeholder + 32 else: datamem_start = placeholder_map[0] # Copy necessary data into allocated dynamic section. for i, (arg, data) in enumerate(zip(args, expected_data)): typ = data.typ if isinstance(typ, ByteArrayLike): pack_args_by_32(holder=holder, maxlen=maxlen, arg=prealloacted.get(i, arg), typ=typ, context=context, placeholder=placeholder_map[i], datamem_start=datamem_start, dynamic_offset_counter=dynamic_offset_counter, pos=pos) return holder, maxlen, dynamic_offset_counter, datamem_start
def pack_arguments(signature, args, context, stmt_expr, is_external_call): pos = getpos(stmt_expr) setters = [] staticarray_offset = 0 maxlen = sum([get_size_of_type(arg.typ) for arg in signature.args]) * 32 if is_external_call: maxlen += 32 placeholder_typ = ByteArrayType(maxlen=maxlen) placeholder = context.new_internal_variable(placeholder_typ) if is_external_call: setters.append(["mstore", placeholder, signature.method_id]) placeholder += 32 if len(signature.args) != len(args): return # check for dynamic-length types dynamic_remaining = len( [i for i in signature.args if isinstance(i.typ, ByteArrayLike)]) needpos = bool(dynamic_remaining) for i, (arg, typ) in enumerate(zip(args, [arg.typ for arg in signature.args])): if isinstance(typ, BaseType): setters.append( make_setter( LLLnode.from_list( placeholder + staticarray_offset + i * 32, typ=typ, ), arg, "memory", pos=pos, in_function_call=True, )) elif isinstance(typ, ByteArrayLike): dynamic_remaining -= 1 setters.append( ["mstore", placeholder + staticarray_offset + i * 32, "_poz"]) arg_copy = LLLnode.from_list("_s", typ=arg.typ, location=arg.location) target = LLLnode.from_list( ["add", placeholder, "_poz"], typ=typ, location="memory", ) pos_setter = "pass" # if `arg.value` is None, this is a call to `empty()` # if `arg.typ.maxlen` is 0, this is a literal "" or b"" if arg.value is None or arg.typ.maxlen == 0: if dynamic_remaining: # only adjust the dynamic pointer if this is not the last dynamic type pos_setter = ["set", "_poz", ["add", "_poz", 64]] setters.append(["seq", mzero(target, 64), pos_setter]) else: if dynamic_remaining: pos_setter = [ "set", "_poz", [ "add", 32, ["ceil32", ["add", "_poz", get_length(arg_copy)]] ], ] setters.append([ "with", "_s", arg, [ "seq", make_byte_array_copier(target, arg_copy, pos), pos_setter ], ]) elif isinstance(typ, (StructType, ListType)): if has_dynamic_data(typ): return target = LLLnode.from_list( [placeholder + staticarray_offset + i * 32], typ=typ, location="memory", ) setters.append(make_setter(target, arg, "memory", pos=pos)) staticarray_offset += 32 * (get_size_of_type(typ) - 1) else: return if is_external_call: returner = [[placeholder - 4]] inargsize = placeholder_typ.maxlen - 28 else: # internal call does not use a returner or adjust max length for signature returner = [] inargsize = placeholder_typ.maxlen if needpos: return ( LLLnode.from_list( [ "with", "_poz", len(args) * 32 + staticarray_offset, ["seq"] + setters + returner ], typ=placeholder_typ, location="memory", ), inargsize, placeholder, ) else: return ( LLLnode.from_list(["seq"] + setters + returner, typ=placeholder_typ, location="memory"), inargsize, placeholder, )
def pack_args_by_32(holder, maxlen, arg, typ, context, placeholder, dynamic_offset_counter=None, datamem_start=None, pos=None): """ Copy necessary variables to pre-allocated memory section. :param holder: Complete holder for all args :param maxlen: Total length in bytes of the full arg section (static + dynamic). :param arg: Current arg to pack :param context: Context of arg :param placeholder: Static placeholder for static argument part. :param dynamic_offset_counter: position counter stored in static args. :param dynamic_placeholder: pointer to current position in memory to write dynamic values to. :param datamem_start: position where the whole datemem section starts. """ if isinstance(typ, BaseType): if isinstance(arg, LLLnode): value = unwrap_location(arg) else: value = Expr(arg, context).lll_node value = base_type_conversion(value, value.typ, typ, pos) holder.append( LLLnode.from_list(['mstore', placeholder, value], typ=typ, location='memory')) elif isinstance(typ, ByteArrayLike): if isinstance(arg, LLLnode): # Is prealloacted variable. source_lll = arg else: source_lll = Expr(arg, context).lll_node # Set static offset, in arg slot. holder.append( LLLnode.from_list( ['mstore', placeholder, ['mload', dynamic_offset_counter]])) # Get the biginning to write the ByteArray to. dest_placeholder = LLLnode.from_list( ['add', datamem_start, ['mload', dynamic_offset_counter]], typ=typ, location='memory', annotation="pack_args_by_32:dest_placeholder") copier = make_byte_array_copier(dest_placeholder, source_lll, pos=pos) holder.append(copier) # Add zero padding. holder.append(zero_pad(dest_placeholder)) # Increment offset counter. increment_counter = LLLnode.from_list( [ 'mstore', dynamic_offset_counter, [ 'add', [ 'add', ['mload', dynamic_offset_counter], ['ceil32', ['mload', dest_placeholder]] ], 32, ], ], annotation='Increment dynamic offset counter') holder.append(increment_counter) elif isinstance(typ, ListType): maxlen += (typ.count - 1) * 32 typ = typ.subtype def check_list_type_match(provided): # Check list types match. if provided != typ: raise TypeMismatchException( f"Log list type '{provided}' does not match provided, expected '{typ}'" ) # NOTE: Below code could be refactored into iterators/getter functions for each type of # repetitive loop. But seeing how each one is a unique for loop, and in which way # the sub value makes the difference in each type of list clearer. # List from storage if isinstance(arg, vy_ast.Attribute) and arg.value.id == 'self': stor_list = context.globals[arg.attr] check_list_type_match(stor_list.typ.subtype) size = stor_list.typ.count mem_offset = 0 for i in range(0, size): storage_offset = i arg2 = LLLnode.from_list( [ 'sload', [ 'add', ['sha3_32', Expr(arg, context).lll_node], storage_offset ] ], typ=typ, ) holder, maxlen = pack_args_by_32( holder, maxlen, arg2, typ, context, placeholder + mem_offset, pos=pos, ) mem_offset += get_size_of_type(typ) * 32 # List from variable. elif isinstance(arg, vy_ast.Name): size = context.vars[arg.id].size pos = context.vars[arg.id].pos check_list_type_match(context.vars[arg.id].typ.subtype) mem_offset = 0 for _ in range(0, size): arg2 = LLLnode.from_list( pos + mem_offset, typ=typ, location=context.vars[arg.id].location) holder, maxlen = pack_args_by_32( holder, maxlen, arg2, typ, context, placeholder + mem_offset, pos=pos, ) mem_offset += get_size_of_type(typ) * 32 # List from list literal. else: mem_offset = 0 for arg2 in arg.elts: holder, maxlen = pack_args_by_32( holder, maxlen, arg2, typ, context, placeholder + mem_offset, pos=pos, ) mem_offset += get_size_of_type(typ) * 32 return holder, maxlen
def make_byte_array_copier(destination, source, pos=None): if not isinstance(source.typ, ByteArrayLike): btype = "byte array" if isinstance(destination.typ, ByteArrayType) else "string" raise TypeMismatch(f"Can only set a {btype} to another {btype}", pos) if isinstance( source.typ, ByteArrayLike) and source.typ.maxlen > destination.typ.maxlen: raise TypeMismatch( f"Cannot cast from greater max-length {source.typ.maxlen} to shorter " f"max-length {destination.typ.maxlen}") # stricter check for zeroing a byte array. if isinstance(source.typ, ByteArrayLike): if source.value is None and source.typ.maxlen != destination.typ.maxlen: raise TypeMismatch( f"Bad type for clearing bytes: expected {destination.typ}" f" but got {source.typ}") # Special case: memory to memory if source.location == "memory" and destination.location == "memory": gas_calculation = GAS_IDENTITY + GAS_IDENTITYWORD * ( ceil32(source.typ.maxlen) // 32) o = LLLnode.from_list( [ "with", "_source", source, [ "with", "_sz", ["add", 32, ["mload", "_source"]], [ "assert", [ "call", ["gas"], 4, 0, "_source", "_sz", destination, "_sz" ] ], ], ], # noqa: E501 typ=None, add_gas_estimate=gas_calculation, annotation="Memory copy", ) return o if source.value is None: pos_node = source else: pos_node = LLLnode.from_list("_pos", typ=source.typ, location=source.location) # Get the length if source.value is None: length = 1 elif source.location == "memory": length = ["add", ["mload", "_pos"], 32] elif source.location == "storage": length = ["add", ["sload", "_pos"], 32] pos_node = LLLnode.from_list( ["sha3_32", pos_node], typ=source.typ, location=source.location, ) else: raise CompilerPanic(f"Unsupported location: {source.location}") if destination.location == "storage": destination = LLLnode.from_list( ["sha3_32", destination], typ=destination.typ, location=destination.location, ) # Maximum theoretical length max_length = 32 if source.value is None else source.typ.maxlen + 32 return LLLnode.from_list( [ "with", "_pos", 0 if source.value is None else source, make_byte_slice_copier( destination, pos_node, length, max_length, pos=pos), ], typ=None, )
def add_variable_offset(parent, key, pos): typ, location = parent.typ, parent.location if isinstance(typ, (StructType, TupleType)): if isinstance(typ, StructType): if not isinstance(key, str): raise TypeMismatchException( "Expecting a member variable access; cannot access element %r" % key, pos) if key not in typ.members: raise TypeMismatchException( "Object does not have member variable %s" % key, pos) subtype = typ.members[key] attrs = list(typ.members.keys()) if key not in attrs: raise TypeMismatchException( "Member %s not found. Only the following available: %s" % (key, " ".join(attrs)), pos) index = attrs.index(key) annotation = key else: if not isinstance(key, int): raise TypeMismatchException( "Expecting a static index; cannot access element %r" % key, pos) attrs = list(range(len(typ.members))) index = key annotation = None if location == 'storage': return LLLnode.from_list( [ 'add', ['sha3_32', parent], LLLnode.from_list(index, annotation=annotation) ], typ=subtype, location='storage', ) elif location == 'storage_prehashed': return LLLnode.from_list( [ 'add', parent, LLLnode.from_list(index, annotation=annotation) ], typ=subtype, location='storage', ) elif location == 'memory': offset = 0 for i in range(index): offset += 32 * get_size_of_type(typ.members[attrs[i]]) return LLLnode.from_list(['add', offset, parent], typ=typ.members[key], location='memory', annotation=annotation) else: raise TypeMismatchException( "Not expecting a member variable access", pos) elif isinstance(typ, MappingType): if isinstance(key.typ, ByteArrayLike): if not isinstance(typ.keytype, ByteArrayLike) or ( typ.keytype.maxlen < key.typ.maxlen): raise TypeMismatchException( 'Mapping keys of bytes cannot be cast, use exact same bytes type of: %s' % (str(typ.keytype), ), pos, ) subtype = typ.valuetype if len(key.args[0].args) >= 3: # handle bytes literal. sub = LLLnode.from_list([ 'seq', key, [ 'sha3', ['add', key.args[0].args[-1], 32], ['mload', key.args[0].args[-1]] ] ]) else: sub = LLLnode.from_list([ 'sha3', ['add', key.args[0].value, 32], ['mload', key.args[0].value] ]) else: subtype = typ.valuetype sub = base_type_conversion(key, key.typ, typ.keytype, pos=pos) if location == 'storage': return LLLnode.from_list(['sha3_64', parent, sub], typ=subtype, location='storage') elif location == 'memory': raise TypeMismatchException( "Can only have fixed-side arrays in memory, not mappings", pos) elif isinstance(typ, ListType): subtype = typ.subtype sub = [ 'uclamplt', base_type_conversion(key, key.typ, BaseType('int128'), pos=pos), typ.count ] if location == 'storage': return LLLnode.from_list(['add', ['sha3_32', parent], sub], typ=subtype, location='storage') elif location == 'storage_prehashed': return LLLnode.from_list(['add', parent, sub], typ=subtype, location='storage') elif location == 'memory': offset = 32 * get_size_of_type(subtype) return LLLnode.from_list( ['add', ['mul', offset, sub], parent], typ=subtype, location='memory', ) else: raise TypeMismatchException("Not expecting an array access ", pos) else: raise TypeMismatchException( "Cannot access the child of a constant variable! %r" % typ, pos)
def make_external_call(stmt_expr, context): from vyper.parser.expr import Expr value, gas = get_external_contract_keywords(stmt_expr, context) if isinstance(stmt_expr.func, ast.Attribute) and isinstance( stmt_expr.func.value, ast.Call): contract_name = stmt_expr.func.value.func.id contract_address = Expr.parse_value_expr(stmt_expr.func.value.args[0], context) return external_contract_call(stmt_expr, context, contract_name, contract_address, pos=getpos(stmt_expr), value=value, gas=gas) elif isinstance( stmt_expr.func.value, ast.Attribute) and stmt_expr.func.value.attr in context.sigs: contract_name = stmt_expr.func.value.attr var = context.globals[stmt_expr.func.value.attr] contract_address = unwrap_location( LLLnode.from_list(var.pos, typ=var.typ, location='storage', pos=getpos(stmt_expr), annotation='self.' + stmt_expr.func.value.attr)) return external_contract_call(stmt_expr, context, contract_name, contract_address, pos=getpos(stmt_expr), value=value, gas=gas) elif isinstance( stmt_expr.func.value, ast.Attribute) and stmt_expr.func.value.attr in context.globals: contract_name = context.globals[stmt_expr.func.value.attr].typ.unit var = context.globals[stmt_expr.func.value.attr] contract_address = unwrap_location( LLLnode.from_list(var.pos, typ=var.typ, location='storage', pos=getpos(stmt_expr), annotation='self.' + stmt_expr.func.value.attr)) return external_contract_call(stmt_expr, context, contract_name, contract_address, pos=getpos(stmt_expr), value=value, gas=gas) else: raise StructureException( "Unsupported operator: %r" % ast.dump(stmt_expr), stmt_expr)
def make_setter(left, right, location, pos, in_function_call=False): # Basic types if isinstance(left.typ, BaseType): right = base_type_conversion( right, right.typ, left.typ, pos, in_function_call=in_function_call, ) if location == 'storage': return LLLnode.from_list(['sstore', left, right], typ=None) elif location == 'memory': return LLLnode.from_list(['mstore', left, right], typ=None) # Byte arrays elif isinstance(left.typ, ByteArrayLike): return make_byte_array_copier(left, right, pos) # Can't copy mappings elif isinstance(left.typ, MappingType): raise TypeMismatchException( "Cannot copy mappings; can only copy individual elements", pos) # Arrays elif isinstance(left.typ, ListType): # Cannot do something like [a, b, c] = [1, 2, 3] if left.value == "multi": raise Exception("Target of set statement must be a single item") if not isinstance(right.typ, (ListType, NullType)): raise TypeMismatchException( "Setter type mismatch: left side is array, right side is %r" % right.typ, pos) left_token = LLLnode.from_list('_L', typ=left.typ, location=left.location) if left.location == "storage": left = LLLnode.from_list(['sha3_32', left], typ=left.typ, location="storage_prehashed") left_token.location = "storage_prehashed" # Type checks if not isinstance(right.typ, NullType): if not isinstance(right.typ, ListType): raise TypeMismatchException( "Left side is array, right side is not", pos) if left.typ.count != right.typ.count: raise TypeMismatchException("Mismatched number of elements", pos) # If the right side is a literal if right.value == "multi": if len(right.args) != left.typ.count: raise TypeMismatchException("Mismatched number of elements", pos) subs = [] for i in range(left.typ.count): subs.append( make_setter(add_variable_offset( left_token, LLLnode.from_list(i, typ='int128'), pos=pos, ), right.args[i], location, pos=pos)) return LLLnode.from_list(['with', '_L', left, ['seq'] + subs], typ=None) # If the right side is a null elif isinstance(right.typ, NullType): subs = [] for i in range(left.typ.count): subs.append( make_setter(add_variable_offset( left_token, LLLnode.from_list(i, typ='int128'), pos=pos, ), LLLnode.from_list(None, typ=NullType()), location, pos=pos)) return LLLnode.from_list(['with', '_L', left, ['seq'] + subs], typ=None) # If the right side is a variable else: right_token = LLLnode.from_list('_R', typ=right.typ, location=right.location) subs = [] for i in range(left.typ.count): subs.append( make_setter(add_variable_offset( left_token, LLLnode.from_list(i, typ='int128'), pos=pos, ), add_variable_offset( right_token, LLLnode.from_list(i, typ='int128'), pos=pos, ), location, pos=pos)) return LLLnode.from_list( ['with', '_L', left, ['with', '_R', right, ['seq'] + subs]], typ=None) # Structs elif isinstance(left.typ, (StructType, TupleType)): if left.value == "multi" and isinstance(left.typ, StructType): raise Exception("Target of set statement must be a single item") if not isinstance(right.typ, NullType): if not isinstance(right.typ, left.typ.__class__): raise TypeMismatchException( "Setter type mismatch: left side is %r, right side is %r" % ( left.typ, right.typ, ), pos, ) if isinstance(left.typ, StructType): for k in right.args: if k.value is None: raise InvalidLiteralException( 'Setting struct value to None is not allowed, use a default value.', pos, ) for k in left.typ.members: if k not in right.typ.members: raise TypeMismatchException( "Keys don't match for structs, missing %s" % k, pos, ) for k in right.typ.members: if k not in left.typ.members: raise TypeMismatchException( "Keys don't match for structs, extra %s" % k, pos, ) if left.typ.name != right.typ.name: raise TypeMismatchException( "Expected %r, got %r" % (left.typ, right.typ), pos) else: if len(left.typ.members) != len(right.typ.members): raise TypeMismatchException( "Tuple lengths don't match, %d vs %d" % ( len(left.typ.members), len(right.typ.members), ), pos, ) left_token = LLLnode.from_list('_L', typ=left.typ, location=left.location) if left.location == "storage": left = LLLnode.from_list(['sha3_32', left], typ=left.typ, location="storage_prehashed") left_token.location = "storage_prehashed" if isinstance(left.typ, StructType): keyz = list(left.typ.members.keys()) else: keyz = list(range(len(left.typ.members))) # If the left side is a literal if left.value == 'multi': locations = [arg.location for arg in left.args] else: locations = [location for _ in keyz] # If the right side is a literal if right.value == "multi": if len(right.args) != len(keyz): raise TypeMismatchException("Mismatched number of elements", pos) subs = [] for i, (typ, loc) in enumerate(zip(keyz, locations)): subs.append( make_setter( add_variable_offset(left_token, typ, pos=pos), right.args[i], loc, pos=pos, )) return LLLnode.from_list(['with', '_L', left, ['seq'] + subs], typ=None) # If the right side is a null elif isinstance(right.typ, NullType): subs = [] for typ, loc in zip(keyz, locations): subs.append( make_setter( add_variable_offset(left_token, typ, pos=pos), LLLnode.from_list(None, typ=NullType()), loc, pos=pos, )) return LLLnode.from_list(['with', '_L', left, ['seq'] + subs], typ=None) # If tuple assign. elif isinstance(left.typ, TupleType) and isinstance( right.typ, TupleType): subs = [] static_offset_counter = 0 zipped_components = zip(left.args, right.typ.members, locations) for left_arg, right_arg, loc in zipped_components: if isinstance(right_arg, ByteArrayLike): RType = ByteArrayType if isinstance( right_arg, ByteArrayType) else StringType offset = LLLnode.from_list([ 'add', '_R', ['mload', ['add', '_R', static_offset_counter]] ], typ=RType(right_arg.maxlen), location='memory', pos=pos) static_offset_counter += 32 else: offset = LLLnode.from_list( ['mload', ['add', '_R', static_offset_counter]], typ=right_arg.typ, pos=pos, ) static_offset_counter += get_size_of_type(right_arg) * 32 subs.append(make_setter(left_arg, offset, loc, pos=pos)) return LLLnode.from_list( ['with', '_R', right, ['seq'] + subs], typ=None, annotation='Tuple assignment', ) # If the right side is a variable else: subs = [] right_token = LLLnode.from_list('_R', typ=right.typ, location=right.location) for typ, loc in zip(keyz, locations): subs.append( make_setter(add_variable_offset(left_token, typ, pos=pos), add_variable_offset(right_token, typ, pos=pos), loc, pos=pos)) return LLLnode.from_list( ['with', '_L', left, ['with', '_R', right, ['seq'] + subs]], typ=None, ) else: raise Exception("Invalid type for setters")
def get_length(arg): if arg.location == "memory": return LLLnode.from_list(['mload', arg], typ=BaseType('int128')) elif arg.location == "storage": return LLLnode.from_list(['sload', ['sha3_32', arg]], typ=BaseType('int128'))
def gen_tuple_return(stmt, context, sub): # Is from a call expression. if sub.args and len( sub.args[0].args) > 0 and sub.args[0].args[0].value == 'call': # self-call to public. mem_pos = sub.args[0].args[-1] mem_size = get_size_of_type(sub.typ) * 32 return LLLnode.from_list(['return', mem_pos, mem_size], typ=sub.typ) elif (sub.annotation and 'Internal Call' in sub.annotation): mem_pos = sub.args[ -1].value if sub.value == 'seq_unchecked' else sub.args[0].args[-1] mem_size = get_size_of_type(sub.typ) * 32 # Add zero padder if bytes are present in output. zero_padder = ['pass'] byte_arrays = [(i, x) for i, x in enumerate(sub.typ.tuple_members()) if isinstance(x, ByteArrayLike)] if byte_arrays: i, x = byte_arrays[-1] zero_padder = zero_pad(bytez_placeholder=[ 'add', mem_pos, ['mload', mem_pos + i * 32] ], maxlen=x.maxlen, context=context) return LLLnode.from_list( ['seq'] + [sub] + [zero_padder] + [make_return_stmt(stmt, context, mem_pos, mem_size)], typ=sub.typ, pos=getpos(stmt), valency=0) subs = [] # Pre-allocate loop_memory_position if required for private function returning. loop_memory_position = (context.new_placeholder( typ=BaseType('uint256')) if context.is_private else None) # Allocate dynamic off set counter, to keep track of the total packed dynamic data size. dynamic_offset_counter_placeholder = context.new_placeholder( typ=BaseType('uint256')) dynamic_offset_counter = LLLnode( dynamic_offset_counter_placeholder, typ=None, annotation="dynamic_offset_counter" # dynamic offset position counter. ) new_sub = LLLnode.from_list( context.new_placeholder(typ=BaseType('uint256')), typ=context.return_type, location='memory', annotation='new_sub', ) left_token = LLLnode.from_list('_loc', typ=new_sub.typ, location="memory") def get_dynamic_offset_value(): # Get value of dynamic offset counter. return ['mload', dynamic_offset_counter] def increment_dynamic_offset(dynamic_spot): # Increment dyanmic offset counter in memory. return [ 'mstore', dynamic_offset_counter, [ 'add', ['add', ['ceil32', ['mload', dynamic_spot]], 32], ['mload', dynamic_offset_counter] ] ] if not isinstance(context.return_type, TupleLike): raise TypeMismatchException( 'Trying to return %r when expecting %r' % (sub.typ, context.return_type), getpos(stmt)) items = context.return_type.tuple_items() dynamic_offset_start = 32 * len(items) # The static list of args end. for i, (key, typ) in enumerate(items): variable_offset = LLLnode.from_list( ['add', 32 * i, left_token], typ=typ, annotation='variable_offset', ) # variable offset of destination if sub.typ.is_literal: arg = sub.args[i] else: arg = add_variable_offset(parent=sub, key=key, pos=getpos(stmt)) if isinstance(typ, ByteArrayLike): # Store offset pointer value. subs.append( ['mstore', variable_offset, get_dynamic_offset_value()]) # Store dynamic data, from offset pointer onwards. dynamic_spot = LLLnode.from_list( ['add', left_token, get_dynamic_offset_value()], location="memory", typ=typ, annotation='dynamic_spot', ) subs.append( make_setter(dynamic_spot, arg, location="memory", pos=getpos(stmt))) subs.append(increment_dynamic_offset(dynamic_spot)) elif isinstance(typ, BaseType): subs.append( make_setter(variable_offset, arg, "memory", pos=getpos(stmt))) elif isinstance(typ, TupleLike): subs.append(gen_tuple_return(stmt, context, arg)) else: # Maybe this should panic because the type error should be # caught at an earlier type-checking stage. raise TypeMismatchException( "Can't return type %s as part of tuple" % arg.typ, stmt) setter = LLLnode.from_list([ 'seq', ['mstore', dynamic_offset_counter, dynamic_offset_start], ['with', '_loc', new_sub, ['seq'] + subs] ], typ=None) return LLLnode.from_list([ 'seq', setter, make_return_stmt(stmt, context, new_sub, get_dynamic_offset_value(), loop_memory_position) ], typ=None, pos=getpos(stmt), valency=0)
def pack_arguments(signature, args, context, stmt_expr, return_placeholder=True): pos = getpos(stmt_expr) placeholder_typ = ByteArrayType( maxlen=sum([get_size_of_type(arg.typ) for arg in signature.args]) * 32 + 32) placeholder = context.new_placeholder(placeholder_typ) setters = [['mstore', placeholder, signature.method_id]] needpos = False staticarray_offset = 0 expected_arg_count = len(signature.args) actual_arg_count = len(args) if actual_arg_count != expected_arg_count: raise StructureException( f"Wrong number of args for: {signature.name} " f"({actual_arg_count} args given, expected {expected_arg_count}", stmt_expr) for i, (arg, typ) in enumerate(zip(args, [arg.typ for arg in signature.args])): if isinstance(typ, BaseType): setters.append( make_setter(LLLnode.from_list( placeholder + staticarray_offset + 32 + i * 32, typ=typ, ), arg, 'memory', pos=pos, in_function_call=True)) elif isinstance(typ, ByteArrayLike): setters.append([ 'mstore', placeholder + staticarray_offset + 32 + i * 32, '_poz' ]) arg_copy = LLLnode.from_list('_s', typ=arg.typ, location=arg.location) target = LLLnode.from_list( ['add', placeholder + 32, '_poz'], typ=typ, location='memory', ) setters.append([ 'with', '_s', arg, [ 'seq', make_byte_array_copier(target, arg_copy, pos), [ 'set', '_poz', [ 'add', 32, ['ceil32', ['add', '_poz', get_length(arg_copy)]] ] ], ], ]) needpos = True elif isinstance(typ, (StructType, ListType)): if has_dynamic_data(typ): raise TypeMismatch("Cannot pack bytearray in struct", stmt_expr) target = LLLnode.from_list( [placeholder + 32 + staticarray_offset + i * 32], typ=typ, location='memory', ) setters.append(make_setter(target, arg, 'memory', pos=pos)) if (isinstance(typ, ListType)): count = typ.count else: count = len(typ.tuple_items()) staticarray_offset += 32 * (count - 1) else: raise TypeMismatch(f"Cannot pack argument of type {typ}", stmt_expr) # For private call usage, doesn't use a returner. returner = [[placeholder + 28]] if return_placeholder else [] if needpos: return (LLLnode.from_list([ 'with', '_poz', len(args) * 32 + staticarray_offset, ['seq'] + setters + returner ], typ=placeholder_typ, location='memory'), placeholder_typ.maxlen - 28, placeholder + 32) else: return (LLLnode.from_list(['seq'] + setters + returner, typ=placeholder_typ, location='memory'), placeholder_typ.maxlen - 28, placeholder + 32)
def call_self_private(stmt_expr, context, sig): # ** Private Call ** # Steps: # (x) push current local variables # (x) push arguments # (x) push jumpdest (callback ptr) # (x) jump to label # (x) pop return values # (x) pop local variables method_name, expr_args, sig = call_lookup_specs(stmt_expr, context) pre_init = [] pop_local_vars = [] push_local_vars = [] pop_return_values = [] push_args = [] # Push local variables. var_slots = [(v.pos, v.size) for name, v in context.vars.items() if v.location == 'memory'] if var_slots: var_slots.sort(key=lambda x: x[0]) mem_from, mem_to = var_slots[0][ 0], var_slots[-1][0] + var_slots[-1][1] * 32 i_placeholder = context.new_placeholder(BaseType('uint256')) local_save_ident = "_%d_%d" % (stmt_expr.lineno, stmt_expr.col_offset) push_loop_label = 'save_locals_start' + local_save_ident pop_loop_label = 'restore_locals_start' + local_save_ident if mem_to - mem_from > 320: push_local_vars = [['mstore', i_placeholder, mem_from], ['label', push_loop_label], ['mload', ['mload', i_placeholder]], [ 'mstore', i_placeholder, ['add', ['mload', i_placeholder], 32] ], [ 'if', ['lt', ['mload', i_placeholder], mem_to], ['goto', push_loop_label] ]] pop_local_vars = [['mstore', i_placeholder, mem_to - 32], ['label', pop_loop_label], ['mstore', ['mload', i_placeholder], 'pass'], [ 'mstore', i_placeholder, ['sub', ['mload', i_placeholder], 32] ], [ 'if', ['ge', ['mload', i_placeholder], mem_from], ['goto', pop_loop_label] ]] else: push_local_vars = [['mload', pos] for pos in range(mem_from, mem_to, 32)] pop_local_vars = [['mstore', pos, 'pass'] for pos in range(mem_to - 32, mem_from - 32, -32) ] # Push Arguments if expr_args: inargs, inargsize, arg_pos = pack_arguments( sig, expr_args, context, return_placeholder=False, pos=getpos(stmt_expr), ) push_args += [ inargs ] # copy arguments first, to not mess up the push/pop sequencing. static_arg_size = 32 * sum( [get_static_size_of_type(arg.typ) for arg in expr_args]) static_pos = arg_pos + static_arg_size needs_dyn_section = any( [has_dynamic_data(arg.typ) for arg in expr_args]) if needs_dyn_section: ident = 'push_args_%d_%d_%d' % (sig.method_id, stmt_expr.lineno, stmt_expr.col_offset) start_label = ident + '_start' end_label = ident + '_end' i_placeholder = context.new_placeholder(BaseType('uint256')) # Calculate copy start position. # Given | static | dynamic | section in memory, # copy backwards so the values are in order on the stack. # We calculate i, the end of the whole encoded part # (i.e. the starting index for copy) # by taking ceil32(len<arg>) + offset<arg> + arg_pos # for the last dynamic argument and arg_pos is the start # the whole argument section. for idx, arg in enumerate(expr_args): if isinstance(arg.typ, ByteArrayLike): last_idx = idx push_args += [[ 'with', 'offset', ['mload', arg_pos + last_idx * 32], [ 'with', 'len_pos', ['add', arg_pos, 'offset'], [ 'with', 'len_value', ['mload', 'len_pos'], [ 'mstore', i_placeholder, ['add', 'len_pos', ['ceil32', 'len_value']] ] ] ] ]] # loop from end of dynamic section to start of dynamic section, # pushing each element onto the stack. push_args += [ ['label', start_label], [ 'if', ['lt', ['mload', i_placeholder], static_pos], ['goto', end_label] ], ['mload', ['mload', i_placeholder]], [ 'mstore', i_placeholder, ['sub', ['mload', i_placeholder], 32] ], # decrease i ['goto', start_label], ['label', end_label] ] # push static section push_args += [['mload', pos] for pos in reversed(range(arg_pos, static_pos, 32))] # Jump to function label. jump_to_func = [ ['add', ['pc'], 6], # set callback pointer. ['goto', 'priv_{}'.format(sig.method_id)], ['jumpdest'], ] # Pop return values. returner = [0] if sig.output_type: output_placeholder, returner, output_size = call_make_placeholder( stmt_expr, context, sig) if output_size > 0: dynamic_offsets = [] if isinstance(sig.output_type, (BaseType, ListType)): pop_return_values = [[ 'mstore', ['add', output_placeholder, pos], 'pass' ] for pos in range(0, output_size, 32)] elif isinstance(sig.output_type, ByteArrayLike): dynamic_offsets = [(0, sig.output_type)] pop_return_values = [ ['pop', 'pass'], ] elif isinstance(sig.output_type, TupleLike): static_offset = 0 pop_return_values = [] for out_type in sig.output_type.members: if isinstance(out_type, ByteArrayLike): pop_return_values.append([ 'mstore', ['add', output_placeholder, static_offset], 'pass' ]) dynamic_offsets.append(([ 'mload', ['add', output_placeholder, static_offset] ], out_type)) else: pop_return_values.append([ 'mstore', ['add', output_placeholder, static_offset], 'pass' ]) static_offset += 32 # append dynamic unpacker. dyn_idx = 0 for in_memory_offset, _out_type in dynamic_offsets: ident = "%d_%d_arg_%d" % (stmt_expr.lineno, stmt_expr.col_offset, dyn_idx) dyn_idx += 1 start_label = 'dyn_unpack_start_' + ident end_label = 'dyn_unpack_end_' + ident i_placeholder = context.new_placeholder( typ=BaseType('uint256')) begin_pos = ['add', output_placeholder, in_memory_offset] # loop until length. o = LLLnode.from_list( [ 'seq_unchecked', ['mstore', begin_pos, 'pass'], # get len ['mstore', i_placeholder, 0], ['label', start_label], [ # break 'if', [ 'ge', ['mload', i_placeholder], ['ceil32', ['mload', begin_pos]] ], ['goto', end_label] ], [ # pop into correct memory slot. 'mstore', [ 'add', ['add', begin_pos, 32], ['mload', i_placeholder] ], 'pass', ], # increment i [ 'mstore', i_placeholder, ['add', 32, ['mload', i_placeholder]] ], ['goto', start_label], ['label', end_label] ], typ=None, annotation='dynamic unpacker', pos=getpos(stmt_expr)) pop_return_values.append(o) call_body = list( itertools.chain( ['seq_unchecked'], pre_init, push_local_vars, push_args, jump_to_func, pop_return_values, pop_local_vars, [returner], )) # If we have no return, we need to pop off pop_returner_call_body = ['pop', call_body ] if sig.output_type is None else call_body o = LLLnode.from_list(pop_returner_call_body, typ=sig.output_type, location='memory', pos=getpos(stmt_expr), annotation='Internal Call: %s' % method_name, add_gas_estimate=sig.gas) o.gas += sig.gas return o
def make_byte_array_copier(destination, source, pos=None): if not isinstance(source.typ, (ByteArrayLike, NullType)): btype = 'byte array' if isinstance(destination.typ, ByteArrayType) else 'string' raise TypeMismatch(f"Can only set a {btype} to another {btype}", pos) if isinstance( source.typ, ByteArrayLike) and source.typ.maxlen > destination.typ.maxlen: raise TypeMismatch( f"Cannot cast from greater max-length {source.typ.maxlen} to shorter " f"max-length {destination.typ.maxlen}") # Special case: memory to memory if source.location == "memory" and destination.location == "memory": gas_calculation = GAS_IDENTITY + GAS_IDENTITYWORD * ( ceil32(source.typ.maxlen) // 32) o = LLLnode.from_list( [ 'with', '_source', source, [ 'with', '_sz', ['add', 32, ['mload', '_source']], [ 'assert', [ 'call', ['gas'], 4, 0, '_source', '_sz', destination, '_sz' ] ] ] ], # noqa: E501 typ=None, add_gas_estimate=gas_calculation, annotation='Memory copy') return o pos_node = LLLnode.from_list('_pos', typ=source.typ, location=source.location) # Get the length if isinstance(source.typ, NullType): length = 1 elif source.location == "memory": length = ['add', ['mload', '_pos'], 32] elif source.location == "storage": length = ['add', ['sload', '_pos'], 32] pos_node = LLLnode.from_list( ['sha3_32', pos_node], typ=source.typ, location=source.location, ) else: raise Exception("Unsupported location:" + source.location) if destination.location == "storage": destination = LLLnode.from_list( ['sha3_32', destination], typ=destination.typ, location=destination.location, ) # Maximum theoretical length max_length = 32 if isinstance(source.typ, NullType) else source.typ.maxlen + 32 return LLLnode.from_list([ 'with', '_pos', 0 if isinstance(source.typ, NullType) else source, make_byte_slice_copier( destination, pos_node, length, max_length, pos=pos) ], typ=None)
def func_init_lll(): return LLLnode.from_list(STORE_CALLDATA + LIMIT_MEMORY_SET, typ=None)