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" or sub.args[0].args[0].value == "staticcall"): # self-call to external. mem_pos = sub 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] ]) 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, ) 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.increase_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)) 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)
def pack_args_by_32(holder, maxlen, arg, typ, context, placeholder, dynamic_offset_counter=None, datamem_start=None, zero_pad_i=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, maxlen, zero_pad_i=zero_pad_i)) # 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( "Log list type '%s' does not match provided, expected '%s'" % (provided, 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, 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, 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 parse_return(self): if self.context.return_type is None: if self.stmt.value: raise TypeMismatchException("Not expecting to return a value", self.stmt) return LLLnode.from_list( make_return_stmt(self.stmt, self.context, 0, 0), typ=None, pos=getpos(self.stmt), valency=0, ) if not self.stmt.value: raise TypeMismatchException("Expecting to return a value", self.stmt) sub = Expr(self.stmt.value, self.context).lll_node # Returning a value (most common case) if isinstance(sub.typ, BaseType): sub = unwrap_location(sub) if not isinstance(self.context.return_type, BaseType): raise TypeMismatchException( f"Return type units mismatch {sub.typ} {self.context.return_type}", self.stmt.value) elif self.context.return_type != sub.typ and not sub.typ.is_literal: raise TypeMismatchException( f"Trying to return base type {sub.typ}, output expecting " f"{self.context.return_type}", self.stmt.value, ) elif sub.typ.is_literal and ( self.context.return_type.typ == sub.typ or 'int' in self.context.return_type.typ and 'int' in sub.typ.typ): # noqa: E501 if not SizeLimits.in_bounds(self.context.return_type.typ, sub.value): raise InvalidLiteralException( "Number out of range: " + str(sub.value), self.stmt) else: return LLLnode.from_list( [ 'seq', ['mstore', 0, sub], make_return_stmt(self.stmt, self.context, 0, 32) ], typ=None, pos=getpos(self.stmt), valency=0, ) elif is_base_type(sub.typ, self.context.return_type.typ) or ( is_base_type(sub.typ, 'int128') and is_base_type( self.context.return_type, 'int256')): # noqa: E501 return LLLnode.from_list( [ 'seq', ['mstore', 0, sub], make_return_stmt(self.stmt, self.context, 0, 32) ], typ=None, pos=getpos(self.stmt), valency=0, ) else: raise TypeMismatchException( f"Unsupported type conversion: {sub.typ} to {self.context.return_type}", self.stmt.value, ) # Returning a byte array elif isinstance(sub.typ, ByteArrayLike): if not sub.typ.eq_base(self.context.return_type): raise TypeMismatchException( f"Trying to return base type {sub.typ}, output expecting " f"{self.context.return_type}", self.stmt.value, ) if sub.typ.maxlen > self.context.return_type.maxlen: raise TypeMismatchException( f"Cannot cast from greater max-length {sub.typ.maxlen} to shorter " f"max-length {self.context.return_type.maxlen}", self.stmt.value, ) # loop memory has to be allocated first. loop_memory_position = self.context.new_placeholder( typ=BaseType('uint256')) # len & bytez placeholder have to be declared after each other at all times. len_placeholder = self.context.new_placeholder( typ=BaseType('uint256')) bytez_placeholder = self.context.new_placeholder(typ=sub.typ) if sub.location in ('storage', 'memory'): return LLLnode.from_list([ 'seq', make_byte_array_copier(LLLnode( bytez_placeholder, location='memory', typ=sub.typ), sub, pos=getpos(self.stmt)), zero_pad(bytez_placeholder), ['mstore', len_placeholder, 32], make_return_stmt( self.stmt, self.context, len_placeholder, ['ceil32', ['add', ['mload', bytez_placeholder], 64]], loop_memory_position=loop_memory_position, ) ], typ=None, pos=getpos(self.stmt), valency=0) else: raise Exception(f"Invalid location: {sub.location}") elif isinstance(sub.typ, ListType): sub_base_type = re.split(r'\(|\[', str(sub.typ.subtype))[0] ret_base_type = re.split(r'\(|\[', str(self.context.return_type.subtype))[0] loop_memory_position = self.context.new_placeholder( typ=BaseType('uint256')) if sub_base_type != ret_base_type: raise TypeMismatchException( f"List return type {sub_base_type} does not match specified " f"return type, expecting {ret_base_type}", self.stmt) elif sub.location == "memory" and sub.value != "multi": return LLLnode.from_list( make_return_stmt( self.stmt, self.context, sub, get_size_of_type(self.context.return_type) * 32, loop_memory_position=loop_memory_position, ), typ=None, pos=getpos(self.stmt), valency=0, ) else: new_sub = LLLnode.from_list( self.context.new_placeholder(self.context.return_type), typ=self.context.return_type, location='memory', ) setter = make_setter(new_sub, sub, 'memory', pos=getpos(self.stmt)) return LLLnode.from_list([ 'seq', setter, make_return_stmt( self.stmt, self.context, new_sub, get_size_of_type(self.context.return_type) * 32, loop_memory_position=loop_memory_position, ) ], typ=None, pos=getpos(self.stmt)) # Returning a struct elif isinstance(sub.typ, StructType): retty = self.context.return_type if not isinstance(retty, StructType) or retty.name != sub.typ.name: raise TypeMismatchException( f"Trying to return {sub.typ}, output expecting {self.context.return_type}", self.stmt.value, ) return gen_tuple_return(self.stmt, self.context, sub) # Returning a tuple. elif isinstance(sub.typ, TupleType): if not isinstance(self.context.return_type, TupleType): raise TypeMismatchException( f"Trying to return tuple type {sub.typ}, output expecting " f"{self.context.return_type}", self.stmt.value, ) if len(self.context.return_type.members) != len(sub.typ.members): raise StructureException("Tuple lengths don't match!", self.stmt) # check return type matches, sub type. for i, ret_x in enumerate(self.context.return_type.members): s_member = sub.typ.members[i] sub_type = s_member if isinstance(s_member, NodeType) else s_member.typ if type(sub_type) is not type(ret_x): raise StructureException( "Tuple return type does not match annotated return. " f"{type(sub_type)} != {type(ret_x)}", self.stmt) return gen_tuple_return(self.stmt, self.context, sub) else: raise TypeMismatchException(f"Can't return type {sub.typ}", self.stmt)
def parse_Return(self): if self.context.return_type is None: if self.stmt.value: return return LLLnode.from_list( make_return_stmt(self.stmt, self.context, 0, 0), typ=None, pos=getpos(self.stmt), valency=0, ) sub = Expr(self.stmt.value, self.context).lll_node # Returning a value (most common case) if isinstance(sub.typ, BaseType): sub = unwrap_location(sub) if self.context.return_type != sub.typ and not sub.typ.is_literal: return elif sub.typ.is_literal and ( self.context.return_type.typ == sub.typ or "int" in self.context.return_type.typ and "int" in sub.typ.typ): # noqa: E501 if SizeLimits.in_bounds(self.context.return_type.typ, sub.value): return LLLnode.from_list( [ "seq", ["mstore", 0, sub], make_return_stmt(self.stmt, self.context, 0, 32), ], typ=None, pos=getpos(self.stmt), valency=0, ) elif is_base_type(sub.typ, self.context.return_type.typ) or ( is_base_type(sub.typ, "int128") and is_base_type( self.context.return_type, "int256")): # noqa: E501 return LLLnode.from_list( [ "seq", ["mstore", 0, sub], make_return_stmt(self.stmt, self.context, 0, 32) ], typ=None, pos=getpos(self.stmt), valency=0, ) return # Returning a byte array elif isinstance(sub.typ, ByteArrayLike): if not sub.typ.eq_base(self.context.return_type): return if sub.typ.maxlen > self.context.return_type.maxlen: return # loop memory has to be allocated first. loop_memory_position = self.context.new_placeholder( typ=BaseType("uint256")) # len & bytez placeholder have to be declared after each other at all times. len_placeholder = self.context.new_placeholder( typ=BaseType("uint256")) bytez_placeholder = self.context.new_placeholder(typ=sub.typ) if sub.location in ("storage", "memory"): return LLLnode.from_list( [ "seq", make_byte_array_copier( LLLnode(bytez_placeholder, location="memory", typ=sub.typ), sub, pos=getpos(self.stmt), ), zero_pad(bytez_placeholder), ["mstore", len_placeholder, 32], make_return_stmt( self.stmt, self.context, len_placeholder, [ "ceil32", ["add", ["mload", bytez_placeholder], 64] ], loop_memory_position=loop_memory_position, ), ], typ=None, pos=getpos(self.stmt), valency=0, ) return elif isinstance(sub.typ, ListType): loop_memory_position = self.context.new_placeholder( typ=BaseType("uint256")) if sub.typ != self.context.return_type: return elif sub.location == "memory" and sub.value != "multi": return LLLnode.from_list( make_return_stmt( self.stmt, self.context, sub, get_size_of_type(self.context.return_type) * 32, loop_memory_position=loop_memory_position, ), typ=None, pos=getpos(self.stmt), valency=0, ) else: new_sub = LLLnode.from_list( self.context.new_placeholder(self.context.return_type), typ=self.context.return_type, location="memory", ) setter = make_setter(new_sub, sub, "memory", pos=getpos(self.stmt)) return LLLnode.from_list( [ "seq", setter, make_return_stmt( self.stmt, self.context, new_sub, get_size_of_type(self.context.return_type) * 32, loop_memory_position=loop_memory_position, ), ], typ=None, pos=getpos(self.stmt), ) # Returning a struct elif isinstance(sub.typ, StructType): retty = self.context.return_type if isinstance(retty, StructType) and retty.name == sub.typ.name: return gen_tuple_return(self.stmt, self.context, sub) # Returning a tuple. elif isinstance(sub.typ, TupleType): if not isinstance(self.context.return_type, TupleType): return if len(self.context.return_type.members) != len(sub.typ.members): return # check return type matches, sub type. for i, ret_x in enumerate(self.context.return_type.members): s_member = sub.typ.members[i] sub_type = s_member if isinstance(s_member, NodeType) else s_member.typ if type(sub_type) is not type(ret_x): return return gen_tuple_return(self.stmt, self.context, sub)
def abi_encode(dst, lll_node, pos=None, bufsz=None, returns=False): parent_abi_t = abi_type_of(lll_node.typ) size_bound = parent_abi_t.static_size() + parent_abi_t.dynamic_size_bound() if bufsz is not None and bufsz < 32 * size_bound: raise CompilerPanic("buffer provided to abi_encode not large enough") lll_ret = ["seq"] dyn_ofst = "dyn_ofst" # current offset in the dynamic section dst_begin = "dst" # pointer to beginning of buffer dst_loc = "dst_loc" # pointer to write location in static section os = o_list(lll_node, pos=pos) for i, o in enumerate(os): abi_t = abi_type_of(o.typ) if parent_abi_t.is_tuple(): if abi_t.is_dynamic(): lll_ret.append(["mstore", dst_loc, dyn_ofst]) # recurse child_dst = ["add", dst_begin, dyn_ofst] child = abi_encode(child_dst, o, pos=pos, returns=True) # increment dyn ofst for the return # (optimization note: # if non-returning and this is the last dyn member in # the tuple, this set can be elided.) lll_ret.append(["set", dyn_ofst, ["add", dyn_ofst, child]]) else: # recurse lll_ret.append(abi_encode(dst_loc, o, pos=pos, returns=False)) elif isinstance(o.typ, BaseType): d = LLLnode(dst_loc, typ=o.typ, location="memory") lll_ret.append(make_setter(d, o, location=d.location, pos=pos)) elif isinstance(o.typ, ByteArrayLike): d = LLLnode.from_list(dst_loc, typ=o.typ, location="memory") lll_ret.append([ "seq", make_setter(d, o, location=d.location, pos=pos), zero_pad(d) ]) else: raise CompilerPanic(f"unreachable type: {o.typ}") if i + 1 == len(os): pass # optimize out the last increment to dst_loc else: # note: always false for non-tuple types sz = abi_t.embedded_static_size() lll_ret.append(["set", dst_loc, ["add", dst_loc, sz]]) # declare LLL variables. if returns: if not parent_abi_t.is_dynamic(): lll_ret.append(parent_abi_t.embedded_static_size()) elif parent_abi_t.is_tuple(): lll_ret.append("dyn_ofst") elif isinstance(lll_node.typ, ByteArrayLike): # for abi purposes, return zero-padded length calc_len = ["ceil32", ["add", 32, ["mload", dst_loc]]] lll_ret.append(calc_len) else: raise CompilerPanic("unknown type {lll_node.typ}") if not (parent_abi_t.is_dynamic() and parent_abi_t.is_tuple()): pass # optimize out dyn_ofst allocation if we don't need it else: dyn_section_start = parent_abi_t.static_size() lll_ret = ["with", "dyn_ofst", dyn_section_start, lll_ret] lll_ret = ["with", dst_begin, dst, ["with", dst_loc, dst_begin, lll_ret]] return LLLnode.from_list(lll_ret)
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 typ.size_in_bytes == 32: if isinstance(arg, LLLnode): value = unwrap_location(arg) else: value = Expr(arg, context).lll_node value = base_type_conversion(value, value.typ, value.typ, pos) holder.append( LLLnode.from_list(["mstore", placeholder, value], location="memory")) elif isinstance(typ, ArrayValueAbstractType): 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 beginning to write the ByteArray to. # TODO refactor out the use of `ByteArrayLike` once old types are removed from parser dest_placeholder = LLLnode.from_list( ["add", datamem_start, ["mload", dynamic_offset_counter]], typ=ByteArrayLike(typ.length), 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, ArrayDefinition): if isinstance(arg, vy_ast.Call) and arg.func.get("id") == "empty": # special case for `empty()` with a static-sized array holder.append(mzero(placeholder, typ.size_in_bytes)) maxlen += typ.size_in_bytes - 32 return holder, maxlen maxlen += (typ.length - 1) * 32 typ = typ.value_type # 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] 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 ] ], ) holder, maxlen = pack_args_by_32( holder, maxlen, arg2, typ, context, placeholder + mem_offset, pos=pos, ) mem_offset += typ.size_in_bytes # List from variable. elif isinstance(arg, vy_ast.Name): size = context.vars[arg.id].size pos = context.vars[arg.id].pos mem_offset = 0 for _ in range(0, size): arg2 = LLLnode.from_list( pos + mem_offset, location=context.vars[arg.id].location) holder, maxlen = pack_args_by_32( holder, maxlen, arg2, typ, context, placeholder + mem_offset, pos=pos, ) mem_offset += typ.size_in_bytes # List from list literal. else: mem_offset = 0 for arg2 in arg.elements: holder, maxlen = pack_args_by_32( holder, maxlen, arg2, typ, context, placeholder + mem_offset, pos=pos, ) mem_offset += typ.size_in_bytes return holder, maxlen