def to_address(expr, args, kwargs, context): lll_node = [ "with", "_in_arg", args[0], ["seq", address_clamp("_in_arg"), "_in_arg"] ] return LLLnode.from_list(lll_node, typ=BaseType("address"), pos=getpos(expr))
def to_bool(expr, args, kwargs, context): in_arg = args[0] input_type, _ = get_type(in_arg) if input_type == "Bytes": if in_arg.typ.maxlen > 32: raise TypeMismatch( f"Cannot convert bytes array of max length {in_arg.typ.maxlen} to bool", expr, ) else: num = byte_array_to_num(in_arg, expr, "uint256") return LLLnode.from_list(["iszero", ["iszero", num]], typ=BaseType("bool"), pos=getpos(expr)) else: return LLLnode.from_list(["iszero", ["iszero", in_arg]], typ=BaseType("bool"), pos=getpos(expr))
def keccak256_helper(expr, args, kwargs, context): sub = args[0] # Can hash literals if isinstance(sub, bytes): return LLLnode.from_list( bytes_to_int(keccak256(sub)), typ=BaseType("bytes32"), pos=getpos(expr) ) # Can hash bytes32 objects if is_base_type(sub.typ, "bytes32"): return LLLnode.from_list( [ "seq", ["mstore", MemoryPositions.FREE_VAR_SPACE, sub], ["sha3", MemoryPositions.FREE_VAR_SPACE, 32], ], typ=BaseType("bytes32"), pos=getpos(expr), ) # Copy the data to an in-memory array if sub.location == "memory": # If we are hashing a value in memory, no need to copy it, just hash in-place return LLLnode.from_list( ["with", "_sub", sub, ["sha3", ["add", "_sub", 32], ["mload", "_sub"]]], typ=BaseType("bytes32"), pos=getpos(expr), ) elif sub.location == "storage": lengetter = LLLnode.from_list(["sload", "_sub"], typ=BaseType("int128")) else: # This should never happen, but just left here for future compiler-writers. raise Exception(f"Unsupported location: {sub.location}") # pragma: no test placeholder = context.new_internal_variable(sub.typ) placeholder_node = LLLnode.from_list(placeholder, typ=sub.typ, location="memory") copier = make_byte_array_copier( placeholder_node, LLLnode.from_list("_sub", typ=sub.typ, location=sub.location), ) return LLLnode.from_list( ["with", "_sub", sub, ["seq", copier, ["sha3", ["add", placeholder, 32], lengetter]]], typ=BaseType("bytes32"), pos=getpos(expr), )
def to_uint256(expr, args, kwargs, context): in_arg = args[0] input_type, _ = get_type(in_arg) if input_type == "num_literal": if isinstance(in_arg, int): if not SizeLimits.in_bounds("uint256", in_arg): raise InvalidLiteral(f"Number out of range: {in_arg}") return LLLnode.from_list(in_arg, typ=BaseType("uint256", ), pos=getpos(expr)) elif isinstance(in_arg, Decimal): if not SizeLimits.in_bounds("uint256", math.trunc(in_arg)): raise InvalidLiteral( f"Number out of range: {math.trunc(in_arg)}") return LLLnode.from_list(math.trunc(in_arg), typ=BaseType("uint256"), pos=getpos(expr)) else: raise InvalidLiteral(f"Unknown numeric literal type: {in_arg}") elif isinstance(in_arg, LLLnode) and input_type in ("int128", "int256"): return LLLnode.from_list(["clampge", in_arg, 0], typ=BaseType("uint256"), pos=getpos(expr)) elif isinstance(in_arg, LLLnode) and input_type == "decimal": return LLLnode.from_list( ["div", ["clampge", in_arg, 0], DECIMAL_DIVISOR], typ=BaseType("uint256"), pos=getpos(expr), ) elif isinstance(in_arg, LLLnode) and input_type == "bool": return LLLnode.from_list(in_arg, typ=BaseType("uint256"), pos=getpos(expr)) elif isinstance(in_arg, LLLnode) and input_type in ("bytes32", "address"): return LLLnode(value=in_arg.value, args=in_arg.args, typ=BaseType("uint256"), pos=getpos(expr)) elif isinstance(in_arg, LLLnode) and input_type == "Bytes": if in_arg.typ.maxlen > 32: raise InvalidLiteral( f"Cannot convert bytes array of max length {in_arg.typ.maxlen} to uint256", expr, ) return byte_array_to_num(in_arg, expr, "uint256") else: raise InvalidLiteral(f"Invalid input for uint256: {in_arg}", expr)
def _to_bytelike(expr, args, kwargs, context, bytetype): if bytetype == "String": ReturnType = StringType elif bytetype == "Bytes": ReturnType = ByteArrayType else: raise TypeMismatch(f"Invalid {bytetype} supplied") in_arg = args[0] if in_arg.typ.maxlen > args[1].slice.value.n: raise TypeMismatch( f"Cannot convert as input {bytetype} are larger than max length", expr, ) return LLLnode( value=in_arg.value, args=in_arg.args, typ=ReturnType(in_arg.typ.maxlen), pos=getpos(expr), location=in_arg.location, )
def to_bytes32(expr, args, kwargs, context): in_arg = args[0] input_type, _len = get_type(in_arg) if input_type == "Bytes": if _len > 32: raise TypeMismatch( f"Unable to convert bytes[{_len}] to bytes32, max length is too " "large.") if in_arg.location == "memory": return LLLnode.from_list(["mload", ["add", in_arg, 32]], typ=BaseType("bytes32")) elif in_arg.location == "storage": return LLLnode.from_list(["sload", ["add", in_arg, 1]], typ=BaseType("bytes32")) else: return LLLnode(value=in_arg.value, args=in_arg.args, typ=BaseType("bytes32"), pos=getpos(expr))
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 to_int128(expr, args, kwargs, context): in_arg = args[0] input_type, _ = get_type(in_arg) if input_type == "num_literal": if isinstance(in_arg, int): if not SizeLimits.in_bounds("int128", in_arg): raise InvalidLiteral(f"Number out of range: {in_arg}") return LLLnode.from_list(in_arg, typ=BaseType("int128"), pos=getpos(expr)) elif isinstance(in_arg, Decimal): if not SizeLimits.in_bounds("int128", math.trunc(in_arg)): raise InvalidLiteral( f"Number out of range: {math.trunc(in_arg)}") return LLLnode.from_list(math.trunc(in_arg), typ=BaseType("int128"), pos=getpos(expr)) else: raise InvalidLiteral(f"Unknown numeric literal type: {in_arg}") elif input_type in ("bytes32", "int256"): if in_arg.typ.is_literal: if not SizeLimits.in_bounds("int128", in_arg.value): raise InvalidLiteral(f"Number out of range: {in_arg.value}", expr) else: return LLLnode.from_list(in_arg, typ=BaseType("int128"), pos=getpos(expr)) else: return LLLnode.from_list( int128_clamp(in_arg), typ=BaseType("int128"), pos=getpos(expr), ) elif input_type == "address": return LLLnode.from_list( ["signextend", 15, ["and", in_arg, (SizeLimits.ADDRSIZE - 1)]], typ=BaseType("int128"), pos=getpos(expr), ) elif input_type in ("String", "Bytes"): if in_arg.typ.maxlen > 32: raise TypeMismatch( f"Cannot convert bytes array of max length {in_arg.typ.maxlen} to int128", expr, ) return byte_array_to_num(in_arg, expr, "int128") elif input_type == "uint256": if in_arg.typ.is_literal: if not SizeLimits.in_bounds("int128", in_arg.value): raise InvalidLiteral(f"Number out of range: {in_arg.value}", expr) else: return LLLnode.from_list(in_arg, typ=BaseType("int128"), pos=getpos(expr)) else: return LLLnode.from_list( ["uclample", in_arg, ["mload", MemoryPositions.MAX_INT128]], typ=BaseType("int128"), pos=getpos(expr), ) elif input_type == "decimal": return LLLnode.from_list( int128_clamp(["sdiv", in_arg, DECIMAL_DIVISOR]), typ=BaseType("int128"), pos=getpos(expr), ) elif input_type == "bool": return LLLnode.from_list(in_arg, typ=BaseType("int128"), pos=getpos(expr)) else: raise InvalidLiteral(f"Invalid input for int128: {in_arg}", expr)
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, )
def to_int256(expr, args, kwargs, context): in_arg = args[0] input_type, _ = get_type(in_arg) if input_type == "num_literal": if isinstance(in_arg, int): if not SizeLimits.in_bounds("int256", in_arg): raise InvalidLiteral(f"Number out of range: {in_arg}") return LLLnode.from_list(in_arg, typ=BaseType("int256", ), pos=getpos(expr)) elif isinstance(in_arg, Decimal): if not SizeLimits.in_bounds("int256", math.trunc(in_arg)): raise InvalidLiteral( f"Number out of range: {math.trunc(in_arg)}") return LLLnode.from_list(math.trunc(in_arg), typ=BaseType("int256"), pos=getpos(expr)) else: raise InvalidLiteral(f"Unknown numeric literal type: {in_arg}") elif isinstance(in_arg, LLLnode) and input_type == "int128": return LLLnode.from_list(in_arg, typ=BaseType("int256"), pos=getpos(expr)) elif isinstance(in_arg, LLLnode) and input_type == "uint256": if version_check(begin="constantinople"): upper_bound = ["shl", 255, 1] else: upper_bound = -(2**255) return LLLnode.from_list(["uclamplt", in_arg, upper_bound], typ=BaseType("int256"), pos=getpos(expr)) elif isinstance(in_arg, LLLnode) and input_type == "decimal": return LLLnode.from_list( ["sdiv", in_arg, DECIMAL_DIVISOR], typ=BaseType("int256"), pos=getpos(expr), ) elif isinstance(in_arg, LLLnode) and input_type == "bool": return LLLnode.from_list(in_arg, typ=BaseType("int256"), pos=getpos(expr)) elif isinstance(in_arg, LLLnode) and input_type in ("bytes32", "address"): return LLLnode(value=in_arg.value, args=in_arg.args, typ=BaseType("int256"), pos=getpos(expr)) elif isinstance(in_arg, LLLnode) and input_type in ("Bytes", "String"): if in_arg.typ.maxlen > 32: raise TypeMismatch( f"Cannot convert bytes array of max length {in_arg.typ.maxlen} to int256", expr, ) return byte_array_to_num(in_arg, expr, "int256") else: raise InvalidLiteral(f"Invalid input for int256: {in_arg}", expr)
def external_call(node, context, interface_name, contract_address, pos, value=None, gas=None): from vyper.old_codegen.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 make_external_call(stmt_expr, context): # circular import! from vyper.old_codegen.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 type_ = stmt_expr.func.value._metadata["type"] var = context.globals[stmt_expr.func.value.attr] contract_address = unwrap_location( LLLnode.from_list( type_.position.position, 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 type_ = stmt_expr.func.value._metadata["type"] var = context.globals[stmt_expr.func.value.attr] contract_address = unwrap_location( LLLnode.from_list( type_.position.position, 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 parse_external_function( code: vy_ast.FunctionDef, sig: FunctionSignature, context: Context, check_nonpayable: bool, ) -> LLLnode: """ Parse a external function (FuncDef), and produce full function body. :param sig: the FuntionSignature :param code: ast of function :param check_nonpayable: if True, include a check that `msg.value == 0` at the beginning of the function :return: full sig compare & function body """ func_type = code._metadata["type"] # Get nonreentrant lock nonreentrant_pre, nonreentrant_post = get_nonreentrant_lock(func_type) clampers = [] # Generate copiers copier: List[Any] = ["pass"] if not len(sig.base_args): copier = ["pass"] elif sig.name == "__init__": copier = [ "codecopy", MemoryPositions.RESERVED_MEMORY, "~codelen", sig.base_copy_size ] context.memory_allocator.expand_memory(sig.max_copy_size) clampers.append(copier) if check_nonpayable and sig.mutability != "payable": # if the contract contains payable functions, but this is not one of them # add an assertion that the value of the call is zero clampers.append(["assert", ["iszero", "callvalue"]]) # Fill variable positions default_args_start_pos = len(sig.base_args) for i, arg in enumerate(sig.args): if i < len(sig.base_args): clampers.append( make_arg_clamper( arg.pos, context.memory_allocator.get_next_memory_position(), arg.typ, sig.name == "__init__", )) if isinstance(arg.typ, ByteArrayLike): mem_pos = context.memory_allocator.expand_memory( 32 * get_size_of_type(arg.typ)) context.vars[arg.name] = VariableRecord(arg.name, mem_pos, arg.typ, False) else: if sig.name == "__init__": context.vars[arg.name] = VariableRecord( arg.name, MemoryPositions.RESERVED_MEMORY + arg.pos, arg.typ, False, ) elif i >= default_args_start_pos: # default args need to be allocated in memory. type_size = get_size_of_type(arg.typ) * 32 default_arg_pos = context.memory_allocator.expand_memory( type_size) context.vars[arg.name] = VariableRecord( name=arg.name, pos=default_arg_pos, typ=arg.typ, mutable=False, ) else: context.vars[arg.name] = VariableRecord(name=arg.name, pos=4 + arg.pos, typ=arg.typ, mutable=False, location="calldata") # Create "clampers" (input well-formedness checkers) # Return function body if sig.name == "__init__": o = LLLnode.from_list( ["seq"] + clampers + [parse_body(code.body, context)], # type: ignore pos=getpos(code), ) # Is default function. elif sig.is_default_func(): o = LLLnode.from_list( ["seq"] + clampers + [parse_body(code.body, context)] + [["stop"]], # type: ignore pos=getpos(code), ) # Is a normal function. else: # Function with default parameters. if sig.total_default_args > 0: function_routine = f"{sig.name}_{sig.method_id}" default_sigs = sig_utils.generate_default_arg_sigs( code, context.sigs, context.global_ctx) sig_chain: List[Any] = ["seq"] for default_sig in default_sigs: sig_compare, _ = get_sig_statements(default_sig, getpos(code)) # Populate unset default variables set_defaults = [] for arg_name in get_default_names_to_set(sig, default_sig): value = Expr(sig.default_values[arg_name], context).lll_node var = context.vars[arg_name] left = LLLnode.from_list( var.pos, typ=var.typ, location="memory", pos=getpos(code), mutable=var.mutable, ) set_defaults.append( make_setter(left, value, "memory", pos=getpos(code))) current_sig_arg_names = {x.name for x in default_sig.args} base_arg_names = {arg.name for arg in sig.base_args} copier_arg_count = len(default_sig.args) - len(sig.base_args) copier_arg_names = list(current_sig_arg_names - base_arg_names) # Order copier_arg_names, this is very important. copier_arg_names = [ x.name for x in default_sig.args if x.name in copier_arg_names ] # Variables to be populated from calldata/stack. default_copiers: List[Any] = [] if copier_arg_count > 0: # Get map of variables in calldata, with thier offsets offset = 4 calldata_offset_map = {} for arg in default_sig.args: calldata_offset_map[arg.name] = offset offset += (32 if isinstance(arg.typ, ByteArrayLike) else get_size_of_type(arg.typ) * 32) # Copy default parameters from calldata. for arg_name in copier_arg_names: var = context.vars[arg_name] calldata_offset = calldata_offset_map[arg_name] # Add clampers. default_copiers.append( make_arg_clamper( calldata_offset - 4, var.pos, var.typ, )) # Add copying code. _offset: Union[int, List[Any]] = calldata_offset if isinstance(var.typ, ByteArrayLike): _offset = [ "add", 4, ["calldataload", calldata_offset] ] default_copiers.append( get_external_arg_copier( memory_dest=var.pos, total_size=var.size * 32, offset=_offset, )) default_copiers.append(0) # for over arching seq, POP sig_chain.append([ "if", sig_compare, [ "seq", ["seq"] + set_defaults if set_defaults else ["pass"], ["seq_unchecked"] + default_copiers if default_copiers else ["pass"], ["goto", function_routine], ], ]) # Function with default parameters. function_jump_label = f"{sig.name}_{sig.method_id}_skip" o = LLLnode.from_list( [ "seq", sig_chain, [ "seq", ["goto", function_jump_label], ["label", function_routine], ["seq"] + nonreentrant_pre + clampers + [parse_body(c, context) for c in code.body] + nonreentrant_post + [["stop"]], ["label", function_jump_label], ], ], typ=None, pos=getpos(code), ) else: # Function without default parameters. sig_compare, _ = get_sig_statements(sig, getpos(code)) o = LLLnode.from_list( [ "if", sig_compare, ["seq"] + nonreentrant_pre + clampers + [parse_body(c, context) for c in code.body] + nonreentrant_post + [["stop"]], ], typ=None, pos=getpos(code), ) return o
def parse_internal_function(code: vy_ast.FunctionDef, sig: FunctionSignature, context: Context) -> LLLnode: """ Parse a internal function (FuncDef), and produce full function body. :param sig: the FuntionSignature :param code: ast of function :return: full sig compare & function body """ func_type = code._metadata["type"] # Get nonreentrant lock nonreentrant_pre, nonreentrant_post = get_nonreentrant_lock(func_type) # Create callback_ptr, this stores a destination in the bytecode for a internal # function to jump to after a function has executed. clampers: List[LLLnode] = [] # Allocate variable space. context.memory_allocator.expand_memory(sig.max_copy_size) _post_callback_ptr = f"{sig.name}_{sig.method_id}_post_callback_ptr" context.callback_ptr = context.new_internal_variable( typ=BaseType("uint256")) clampers.append( LLLnode.from_list( ["mstore", context.callback_ptr, "pass"], annotation="pop callback pointer", )) if sig.total_default_args > 0: clampers.append(LLLnode.from_list(["label", _post_callback_ptr])) # internal functions without return types need to jump back to # the calling function, as there is no return statement to handle the # jump. if sig.output_type is None: stop_func = [["jump", ["mload", context.callback_ptr]]] else: stop_func = [["stop"]] # Generate copiers if len(sig.base_args) == 0: copier = ["pass"] clampers.append(LLLnode.from_list(copier)) elif sig.total_default_args == 0: copier = get_internal_arg_copier( total_size=sig.base_copy_size, memory_dest=MemoryPositions.RESERVED_MEMORY) clampers.append(LLLnode.from_list(copier)) # Fill variable positions for arg in sig.args: if isinstance(arg.typ, ByteArrayLike): mem_pos = context.memory_allocator.expand_memory( 32 * get_size_of_type(arg.typ)) context.vars[arg.name] = VariableRecord(arg.name, mem_pos, arg.typ, False) else: context.vars[arg.name] = VariableRecord( arg.name, MemoryPositions.RESERVED_MEMORY + arg.pos, arg.typ, False, ) # internal function copiers. No clamping for internal functions. dyn_variable_names = [ a.name for a in sig.base_args if isinstance(a.typ, ByteArrayLike) ] if dyn_variable_names: i_placeholder = context.new_internal_variable(typ=BaseType("uint256")) unpackers: List[Any] = [] for idx, var_name in enumerate(dyn_variable_names): var = context.vars[var_name] ident = f"_load_args_{sig.method_id}_dynarg{idx}" o = make_unpacker(ident=ident, i_placeholder=i_placeholder, begin_pos=var.pos) unpackers.append(o) if not unpackers: unpackers = ["pass"] # 0 added to complete full overarching 'seq' statement, see internal_label. unpackers.append(0) clampers.append( LLLnode.from_list( ["seq_unchecked"] + unpackers, typ=None, annotation="dynamic unpacker", pos=getpos(code), )) # Function has default arguments. if sig.total_default_args > 0: # Function with default parameters. default_sigs = sig_utils.generate_default_arg_sigs( code, context.sigs, context.global_ctx) sig_chain: List[Any] = ["seq"] for default_sig in default_sigs: sig_compare, internal_label = get_sig_statements( default_sig, getpos(code)) # Populate unset default variables set_defaults = [] for arg_name in get_default_names_to_set(sig, default_sig): value = Expr(sig.default_values[arg_name], context).lll_node var = context.vars[arg_name] left = LLLnode.from_list(var.pos, typ=var.typ, location="memory", pos=getpos(code), mutable=var.mutable) set_defaults.append( make_setter(left, value, "memory", pos=getpos(code))) current_sig_arg_names = [x.name for x in default_sig.args] # Load all variables in default section, if internal, # because the stack is a linear pipe. copier_arg_count = len(default_sig.args) copier_arg_names = current_sig_arg_names # Order copier_arg_names, this is very important. copier_arg_names = [ x.name for x in default_sig.args if x.name in copier_arg_names ] # Variables to be populated from calldata/stack. default_copiers: List[Any] = [] if copier_arg_count > 0: # Get map of variables in calldata, with thier offsets offset = 4 calldata_offset_map = {} for arg in default_sig.args: calldata_offset_map[arg.name] = offset offset += (32 if isinstance(arg.typ, ByteArrayLike) else get_size_of_type(arg.typ) * 32) # Copy set default parameters from calldata dynamics = [] for arg_name in copier_arg_names: var = context.vars[arg_name] if isinstance(var.typ, ByteArrayLike): _size = 32 dynamics.append(var.pos) else: _size = var.size * 32 default_copiers.append( get_internal_arg_copier( memory_dest=var.pos, total_size=_size, )) # Unpack byte array if necessary. if dynamics: i_placeholder = context.new_internal_variable( typ=BaseType("uint256")) for idx, var_pos in enumerate(dynamics): ident = f"unpack_default_sig_dyn_{default_sig.method_id}_arg{idx}" default_copiers.append( make_unpacker( ident=ident, i_placeholder=i_placeholder, begin_pos=var_pos, )) default_copiers.append(0) # for over arching seq, POP sig_chain.append([ "if", sig_compare, [ "seq", internal_label, LLLnode.from_list( ["mstore", context.callback_ptr, "pass"], annotation="pop callback pointer", pos=getpos(code), ), ["seq"] + set_defaults if set_defaults else ["pass"], ["seq_unchecked"] + default_copiers if default_copiers else ["pass"], ["goto", _post_callback_ptr], ], ]) # With internal functions all variable loading occurs in the default # function sub routine. _clampers = [["label", _post_callback_ptr]] # Function with default parameters. return LLLnode.from_list( [ "seq", sig_chain, ["seq"] + nonreentrant_pre + _clampers + [parse_body(c, context) for c in code.body] + nonreentrant_post + stop_func, ], typ=None, pos=getpos(code), ) else: # Function without default parameters. sig_compare, internal_label = get_sig_statements(sig, getpos(code)) return LLLnode.from_list( ["seq"] + [internal_label] + nonreentrant_pre + clampers + [parse_body(c, context) for c in code.body] + nonreentrant_post + stop_func, typ=None, pos=getpos(code), )
def pack_logging_data(arg_nodes, arg_types, context, pos): # Checks to see if there's any data if not arg_nodes: return ["seq"], 0, None, 0 holder = ["seq"] maxlen = len(arg_nodes) * 32 # total size of all packed args (upper limit) # Unroll any function calls, to temp variables. prealloacted = {} for idx, node in enumerate(arg_nodes): if isinstance( node, (vy_ast.Str, vy_ast.Call)) and node.get("func.id") != "empty": expr = Expr(node, context) source_lll = expr.lll_node tmp_variable = context.new_internal_variable(source_lll.typ) tmp_variable_node = LLLnode.from_list( tmp_variable, typ=source_lll.typ, pos=getpos(node), location="memory", annotation=f"log_prealloacted {source_lll.typ}", ) # Copy bytes. holder.append( make_setter(tmp_variable_node, source_lll, pos=getpos(node), location="memory")) prealloacted[idx] = tmp_variable_node # Create internal variables for for dynamic and static args. static_types = [] for typ in arg_types: static_types.append( typ if not typ.is_dynamic_size else Uint256Definition()) requires_dynamic_offset = any(typ.is_dynamic_size for typ in arg_types) dynamic_offset_counter = None if requires_dynamic_offset: # TODO refactor out old type objects dynamic_offset_counter = context.new_internal_variable(BaseType(32)) dynamic_placeholder = context.new_internal_variable(BaseType(32)) static_vars = [context.new_internal_variable(i) for i in static_types] # Populate static placeholders. for i, (node, typ) in enumerate(zip(arg_nodes, arg_types)): placeholder = static_vars[i] if not isinstance(typ, ArrayValueAbstractType): holder, maxlen = pack_args_by_32( holder, maxlen, prealloacted.get(i, node), 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 typ in arg_types: if typ.is_dynamic_size: maxlen += typ.size_in_bytes if requires_dynamic_offset: datamem_start = dynamic_placeholder + 32 else: datamem_start = static_vars[0] # Copy necessary data into allocated dynamic section. for i, (node, typ) in enumerate(zip(arg_nodes, arg_types)): if isinstance(typ, ArrayValueAbstractType): if isinstance(node, vy_ast.Call) and node.func.get("id") == "empty": # TODO add support for this raise StructureException( "Cannot use `empty` on Bytes or String types within an event log", node) pack_args_by_32( holder=holder, maxlen=maxlen, arg=prealloacted.get(i, node), typ=typ, context=context, placeholder=static_vars[i], datamem_start=datamem_start, dynamic_offset_counter=dynamic_offset_counter, pos=pos, ) return holder, maxlen, dynamic_offset_counter, datamem_start
def to_decimal(expr, args, kwargs, context): in_arg = args[0] input_type, _ = get_type(in_arg) if input_type == "Bytes": if in_arg.typ.maxlen > 32: raise TypeMismatch( f"Cannot convert bytes array of max length {in_arg.typ.maxlen} to decimal", expr, ) num = byte_array_to_num(in_arg, expr, "int128") return LLLnode.from_list(["mul", num, DECIMAL_DIVISOR], typ=BaseType("decimal"), pos=getpos(expr)) else: if input_type == "uint256": if in_arg.typ.is_literal: if not SizeLimits.in_bounds("int128", (in_arg.value * DECIMAL_DIVISOR)): raise InvalidLiteral( f"Number out of range: {in_arg.value}", expr, ) else: return LLLnode.from_list(["mul", in_arg, DECIMAL_DIVISOR], typ=BaseType("decimal"), pos=getpos(expr)) else: return LLLnode.from_list( [ "uclample", ["mul", in_arg, DECIMAL_DIVISOR], ["mload", MemoryPositions.MAXDECIMAL], ], typ=BaseType("decimal"), pos=getpos(expr), ) elif input_type == "address": return LLLnode.from_list( [ "mul", [ "signextend", 15, ["and", in_arg, (SizeLimits.ADDRSIZE - 1)] ], DECIMAL_DIVISOR, ], typ=BaseType("decimal"), pos=getpos(expr), ) elif input_type == "bytes32": if in_arg.typ.is_literal: if not SizeLimits.in_bounds("int128", (in_arg.value * DECIMAL_DIVISOR)): raise InvalidLiteral( f"Number out of range: {in_arg.value}", expr, ) else: return LLLnode.from_list(["mul", in_arg, DECIMAL_DIVISOR], typ=BaseType("decimal"), pos=getpos(expr)) else: return LLLnode.from_list( [ "clamp", ["mload", MemoryPositions.MINDECIMAL], ["mul", in_arg, DECIMAL_DIVISOR], ["mload", MemoryPositions.MAXDECIMAL], ], typ=BaseType("decimal"), pos=getpos(expr), ) elif input_type == "int256": return LLLnode.from_list( [ "seq", int128_clamp(in_arg), ["mul", in_arg, DECIMAL_DIVISOR] ], typ=BaseType("decimal"), pos=getpos(expr), ) elif input_type in ("int128", "bool"): return LLLnode.from_list(["mul", in_arg, DECIMAL_DIVISOR], typ=BaseType("decimal"), pos=getpos(expr)) else: raise InvalidLiteral(f"Invalid input for decimal: {in_arg}", expr)