def calculate_arg_totals(self): """ Calculate base arguments, and totals. """ code = self.func_ast_code self.base_args = [] self.total_default_args = 0 if hasattr(code.args, "defaults"): self.total_default_args = len(code.args.defaults) if self.total_default_args > 0: # all argument w/o defaults self.base_args = self.args[:-self.total_default_args] else: # No default args, so base_args = args. self.base_args = self.args # All default argument name/type definitions. self.default_args = code.args.args[ -self.total_default_args:] # noqa: E203 # Keep all the value to assign to default parameters. self.default_values = dict( zip([arg.arg for arg in self.default_args], code.args.defaults)) # Calculate the total sizes in memory the function arguments will take use. # Total memory size of all arguments (base + default together). self.max_copy_size = sum([ 32 if isinstance(arg.typ, ByteArrayLike) else get_size_of_type(arg.typ) * 32 for arg in self.args ]) # Total memory size of base arguments (arguments exclude default parameters). self.base_copy_size = sum([ 32 if isinstance(arg.typ, ByteArrayLike) else get_size_of_type(arg.typ) * 32 for arg in self.base_args ])
def _call_make_placeholder(stmt_expr, context, sig): if sig.output_type is None: return 0, 0, 0 output_placeholder = context.new_internal_variable(typ=sig.output_type) output_size = get_size_of_type(sig.output_type) * 32 if isinstance(sig.output_type, BaseType): returner = output_placeholder elif isinstance(sig.output_type, ByteArrayLike): returner = output_placeholder elif isinstance(sig.output_type, TupleLike): # incase of struct we need to decode the output and then return it returner = ["seq"] decoded_placeholder = context.new_internal_variable( typ=sig.output_type) decoded_node = LLLnode(decoded_placeholder, typ=sig.output_type, location="memory") output_node = LLLnode(output_placeholder, typ=sig.output_type, location="memory") returner.append(abi_decode(decoded_node, output_node)) returner.extend([decoded_placeholder]) elif isinstance(sig.output_type, ListType): returner = output_placeholder else: raise TypeCheckFailure(f"Invalid output type: {sig.output_type}") return output_placeholder, returner, output_size
def size(self): if hasattr(self.typ, "size_in_bytes"): # temporary requirement to support both new and old type objects # we divide by 32 here because the returned value is denominated # in "slots" of 32 bytes each return math.ceil(self.typ.size_in_bytes / 32) return get_size_of_type(self.typ)
def new_internal_variable(self, typ: NodeType) -> int: """ Allocate memory for an internal variable. Arguments --------- typ : NodeType Variable type, used to determine the size of memory allocation Returns ------- int Memory offset for the variable """ # internal variable names begin with a number sign so there is no chance for collision var_id = self._internal_var_iter self._internal_var_iter += 1 name = f"#internal_{var_id}" if hasattr(typ, "size_in_bytes"): # temporary requirement to support both new and old type objects var_size = typ.size_in_bytes # type: ignore else: var_size = 32 * get_size_of_type(typ) return self._new_variable(name, typ, var_size, True)
def new_variable(self, name: str, typ: NodeType, pos: VyperNode = None) -> int: """ Allocate memory for a user-defined variable. Arguments --------- name : str Name of the variable typ : NodeType Variable type, used to determine the size of memory allocation pos : VyperNode AST node corresponding to the location where the variable was created, used for annotating exceptions Returns ------- int Memory offset for the variable """ if hasattr(typ, "size_in_bytes"): # temporary requirement to support both new and old type objects var_size = typ.size_in_bytes # type: ignore else: var_size = 32 * get_size_of_type(typ) return self._new_variable(name, typ, var_size, False)
def test_get_size_of_type(): assert get_size_of_type(BaseType("int128")) == 1 assert get_size_of_type(ByteArrayType(12)) == 3 assert get_size_of_type(ByteArrayType(33)) == 4 assert get_size_of_type(ListType(BaseType("int128"), 10)) == 10 _tuple = TupleType([BaseType("int128"), BaseType("decimal")]) assert get_size_of_type(_tuple) == 2 _struct = StructType({ "a": BaseType("int128"), "b": BaseType("decimal") }, "Foo") assert get_size_of_type(_struct) == 2 # Don't allow unknown types. with raises(Exception): get_size_of_type(int) # Maps are not supported for function arguments or outputs with raises(Exception): get_size_of_type(MappingType(BaseType("int128"), BaseType("int128")))
def _make_array_index_setter(target, target_token, pos, location, offset): if location == "memory" and isinstance(target.value, int): offset = target.value + 32 * get_size_of_type( target.typ.subtype) * offset return LLLnode.from_list([offset], typ=target.typ.subtype, location=location, pos=pos) else: return add_variable_offset( target_token, LLLnode.from_list(offset, typ="int256"), pos=pos, array_bounds_check=False, )
def make_setter(left, right, location, pos, in_function_call=False): # Basic types if isinstance(left.typ, BaseType): right = unwrap_location(right) 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 the right side is a literal if right.value in ["multi", "seq_unchecked"] and right.typ.is_literal: if right.value == "seq_unchecked": # when the LLL is `seq_unchecked`, this is a literal where one or # more values must be pre-processed to avoid memory corruption subs = right.args[:-1] right = right.args[-1] else: 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="int256"), 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) 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 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 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": # for arrays and structs, calculate the storage slot by adding an offset # of [index value being accessed] * [size of each item within the sequence] offset = 0 for i in range(index): offset += get_size_of_type(typ.members[attrs[i]]) return LLLnode.from_list( ["add", parent, offset], 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 = unwrap_location(key) 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", "int256", "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": # storage slot determined as [initial storage slot] + [index] * [size of base type] offset = get_size_of_type(subtype) return LLLnode.from_list(["add", parent, ["mul", sub, offset]], 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_call(stmt_expr, context): # ** Internal 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 pop_local_vars = [] push_local_vars = [] pop_return_values = [] push_args = [] method_name = stmt_expr.func.attr # TODO check this out from vyper.old_codegen.expr import parse_sequence pre_init, expr_args = parse_sequence(stmt_expr, stmt_expr.args, context) sig = FunctionSignature.lookup_sig( context.sigs, method_name, expr_args, stmt_expr, context, ) if context.is_constant() and sig.mutability not in ("view", "pure"): raise StateAccessViolation( f"May not call state modifying function " f"'{method_name}' within {context.pp_constancy()}.", getpos(stmt_expr), ) if not sig.internal: raise StructureException("Cannot call external functions via 'self'", stmt_expr) # 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]) if len(var_slots) > 10: # if memory is large enough, push and pop it via iteration mem_from, mem_to = var_slots[0][ 0], var_slots[-1][0] + var_slots[-1][1] * 32 i_placeholder = context.new_internal_variable(BaseType("uint256")) local_save_ident = f"_{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 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: # for smaller memory, hardcode the mload/mstore locations push_mem_slots = [] for pos, size in var_slots: push_mem_slots.extend([pos + i * 32 for i in range(size)]) push_local_vars = [["mload", pos] for pos in push_mem_slots] pop_local_vars = [["mstore", pos, "pass"] for pos in push_mem_slots[::-1]] # Push Arguments if expr_args: inargs, inargsize, arg_pos = pack_arguments(sig, expr_args, context, stmt_expr, is_external_call=False) 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 = int(arg_pos + static_arg_size) needs_dyn_section = any( [has_dynamic_data(arg.typ) for arg in expr_args]) if needs_dyn_section: ident = f"push_args_{sig.method_id}_{stmt_expr.lineno}_{stmt_expr.col_offset}" start_label = ident + "_start" end_label = ident + "_end" i_placeholder = context.new_internal_variable(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. idx = 0 for arg in expr_args: if isinstance(arg.typ, ByteArrayLike): last_idx = idx idx += get_static_size_of_type(arg.typ) 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))] elif sig.args: raise StructureException( f"Wrong number of args for: {sig.name} (0 args given, expected {len(sig.args)})", stmt_expr, ) # Jump to function label. jump_to_func = [ ["add", ["pc"], 6], # set callback pointer. ["goto", f"priv_{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 name, typ in sig.output_type.tuple_items(): if isinstance(typ, ByteArrayLike): pop_return_values.append([ "mstore", ["add", output_placeholder, static_offset], "pass" ]) dynamic_offsets.append(([ "mload", ["add", output_placeholder, static_offset] ], name)) static_offset += 32 else: member_output_size = get_size_of_type(typ) * 32 pop_return_values.extend([[ "mstore", ["add", output_placeholder, pos], "pass" ] for pos in range(static_offset, static_offset + member_output_size, 32)]) static_offset += member_output_size # append dynamic unpacker. dyn_idx = 0 for in_memory_offset, _out_type in dynamic_offsets: ident = f"{stmt_expr.lineno}_{stmt_expr.col_offset}_arg_{dyn_idx}" dyn_idx += 1 start_label = "dyn_unpack_start_" + ident end_label = "dyn_unpack_end_" + ident i_placeholder = context.new_internal_variable( 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=f"Internal Call: {method_name}", add_gas_estimate=sig.gas, ) o.gas += sig.gas return o
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 using make_setter, since # our memory encoding happens to be identical to the ABI # encoding. 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 from_definition( cls, code, sigs=None, custom_structs=None, interface_def=False, constant_override=False, is_from_json=False, ): if not custom_structs: custom_structs = {} name = code.name mem_pos = 0 # Determine the arguments, expects something of the form def foo(arg1: # int128, arg2: int128 ... args = [] for arg in code.args.args: # Each arg needs a type specified. typ = arg.annotation parsed_type = parse_type( typ, None, sigs, custom_structs=custom_structs, ) args.append( VariableRecord( arg.arg, mem_pos, parsed_type, False, defined_at=getpos(arg), )) if isinstance(parsed_type, ByteArrayLike): mem_pos += 32 else: mem_pos += get_size_of_type(parsed_type) * 32 mutability = "nonpayable" # Assume nonpayable by default nonreentrant_key = "" is_internal = False # Update function properties from decorators # NOTE: Can't import enums here because of circular import for dec in code.decorator_list: if isinstance(dec, vy_ast.Name) and dec.id in ("payable", "view", "pure"): mutability = dec.id elif isinstance(dec, vy_ast.Name) and dec.id == "internal": is_internal = True elif isinstance(dec, vy_ast.Name) and dec.id == "external": is_internal = False elif isinstance(dec, vy_ast.Call) and dec.func.id == "nonreentrant": nonreentrant_key = dec.args[0].s if constant_override: # In case this override is abused, match previous behavior if mutability == "payable": raise StructureException( f"Function {name} cannot be both constant and payable.") mutability = "view" # Determine the return type and whether or not it's constant. Expects something # of the form: # def foo(): ... # def foo() -> int128: ... # If there is no return type, ie. it's of the form def foo(): ... # and NOT def foo() -> type: ..., then it's null output_type = None if code.returns: output_type = parse_type( code.returns, None, sigs, custom_structs=custom_structs, ) # Output type must be canonicalizable assert isinstance(output_type, TupleType) or canonicalize_type(output_type) # Get the canonical function signature sig = cls.get_full_sig(name, code.args.args, sigs, custom_structs) # Take the first 4 bytes of the hash of the sig to get the method ID method_id = fourbytes_to_int(keccak256(bytes(sig, "utf-8"))[:4]) return cls( name, args, output_type, mutability, is_internal, nonreentrant_key, sig, method_id, code, is_from_json, )