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 _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 = 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 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, 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_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. 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_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=f"Internal Call: {method_name}", add_gas_estimate=sig.gas, ) o.gas += sig.gas return o
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 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. if context.vars: var_slots = [(v.pos, v.size) for name, v in context.vars.items()] 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 push_local_vars = [['mload', pos] for pos in range(mem_from, mem_to, 32)] pop_local_vars = [['mstore', pos, 'pass'] for pos in reversed(range(mem_from, mem_to, 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 total_arg_size = ceil32(inargsize - 4) if static_arg_size != total_arg_size: # requires dynamic 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')) push_args += [ ['mstore', i_placeholder, arg_pos + total_arg_size], ['label', start_label], [ 'if', ['lt', ['mload', i_placeholder], static_pos], ['goto', end_label] ], [ 'if_unchecked', ['ne', ['mload', ['mload', i_placeholder]], 0], ['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