def validate_default_values(node): if isinstance(node, vy_ast.Name) and node.id in parser.expr.BUILTIN_CONSTANTS: return if isinstance(node, vy_ast.Attribute ) and node.value.id in parser.expr.ENVIRONMENT_VARIABLES: return if not isinstance(node, (vy_ast.Constant, vy_ast.List)): raise FunctionDeclarationException( "Default value must be a literal, built-in constant, or environment variable.", node) if isinstance(node, vy_ast.List): for n in node.elts: validate_default_values(n)
def lookup_sig(cls, sigs, method_name, expr_args, stmt_or_expr, context): """ Using a list of args, determine the most accurate signature to use from the given context """ def synonymise(s): return s.replace('int128', 'num').replace('uint256', 'num') # for sig in sigs['self'] full_sig = cls.get_full_sig(stmt_or_expr.func.attr, expr_args, None, context.custom_units) method_names_dict = dict( Counter([x.split('(')[0] for x in context.sigs['self']])) if method_name not in method_names_dict: raise FunctionDeclarationException( "Function not declared yet (reminder: functions cannot " "call functions later in code than themselves): %s" % method_name) if method_names_dict[method_name] == 1: return next(sig for name, sig in context.sigs['self'].items() if name.split('(')[0] == method_name) if full_sig in context.sigs['self']: return context.sigs['self'][full_sig] else: synonym_sig = synonymise(full_sig) syn_sigs_test = [synonymise(k) for k in context.sigs.keys()] if len(syn_sigs_test) != len(set(syn_sigs_test)): raise Exception( 'Incompatible default parameter signature,' 'can not tell the number type of literal', stmt_or_expr) synonym_sigs = [(synonymise(k), v) for k, v in context.sigs['self'].items()] ssig = [s[1] for s in synonym_sigs if s[0] == synonym_sig] if len(ssig) == 0: raise FunctionDeclarationException( "Function not declared yet (reminder: functions cannot " "call functions later in code than themselves): %s" % method_name) return ssig[0]
def __init__(self, vyper_module: vy_ast.Module, fn_node: vy_ast.FunctionDef, namespace: dict) -> None: self.vyper_module = vyper_module self.fn_node = fn_node self.namespace = namespace self.func = namespace["self"].get_member(fn_node.name, fn_node) namespace.update(self.func.arguments) if self.func.visibility is FunctionVisibility.INTERNAL: node_list = fn_node.get_descendants(vy_ast.Attribute, { "value.id": "msg", "attr": "sender" }) if node_list: raise StateAccessViolation( "msg.sender is not allowed in internal functions", node_list[0]) if self.func.mutability == StateMutability.PURE: node_list = fn_node.get_descendants( vy_ast.Attribute, { "value.id": set(CONSTANT_ENVIRONMENT_VARS.keys()).union( set(MUTABLE_ENVIRONMENT_VARS.keys())) }, ) if node_list: raise StateAccessViolation( "not allowed to query contract or environment variables in pure functions", node_list[0], ) if self.func.mutability is not StateMutability.PAYABLE: node_list = fn_node.get_descendants(vy_ast.Attribute, { "value.id": "msg", "attr": "value" }) if node_list: raise NonPayableViolation( "msg.value is not allowed in non-payable functions", node_list[0]) for node in fn_node.body: self.visit(node) if self.func.return_type: if not check_for_terminus(fn_node.body): raise FunctionDeclarationException( f"Missing or unmatched return statements in function '{fn_node.name}'", fn_node, )
def get_global_context(cls, code): global_ctx = cls() for item in code: # Contract references if isinstance(item, ast.ClassDef): if global_ctx._events or global_ctx._globals or global_ctx._defs: raise StructureException( "External contract and struct declarations must come before event declarations, global declarations, and function definitions", item) base_classes = [x.id for x in item.bases] if base_classes == ['__VYPER_ANNOT_STRUCT__']: if global_ctx._contracts: raise StructureException( "Structs must come before external contract definitions", item) global_ctx._structs[item.name] = global_ctx.make_struct( item.name, item.body) elif base_classes == ['__VYPER_ANNOT_CONTRACT__']: global_ctx._contracts[ item.name] = GlobalContext.make_contract(item.body) elif base_classes == []: raise StructureException( "No base classes for class. This is likely a compiler bug, please report at https://github.com/ethereum/vyper/issues", item) else: raise StructureException( "Multiple base classes for class. This is likely a compiler bug, please report at https://github.com/ethereum/vyper/issues", item) # Statements of the form: # variable_name: type elif isinstance(item, ast.AnnAssign): global_ctx.add_globals_and_events(item) # Function definitions elif isinstance(item, ast.FunctionDef): if item.name in global_ctx._globals: raise FunctionDeclarationException( "Function name shadowing a variable name: %s" % item.name) global_ctx._defs.append(item) else: raise StructureException("Invalid top-level statement", item) # Add getters to _defs global_ctx._defs += global_ctx._getters return global_ctx
def parse_tree_to_lll(code, origcode, runtime_only=False): global_ctx = GlobalContext.get_global_context(code) _names_def = [_def.name for _def in global_ctx._defs] # Checks for duplicate function names if len(set(_names_def)) < len(_names_def): raise FunctionDeclarationException( "Duplicate function name: %s" % [name for name in _names_def if _names_def.count(name) > 1][0]) _names_events = [_event.target.id for _event in global_ctx._events] # Checks for duplicate event names if len(set(_names_events)) < len(_names_events): raise EventDeclarationException( "Duplicate event name: %s" % [name for name in _names_events if _names_events.count(name) > 1][0]) # Initialization function initfunc = [_def for _def in global_ctx._defs if is_initializer(_def)] # Default function defaultfunc = [_def for _def in global_ctx._defs if is_default_func(_def)] # Regular functions otherfuncs = [ _def for _def in global_ctx._defs if not is_initializer(_def) and not is_default_func(_def) ] sigs = {} external_contracts = {} # Create the main statement o = ['seq'] if global_ctx._events: sigs = parse_events(sigs, global_ctx._events, global_ctx._custom_units) if global_ctx._contracts: external_contracts = parse_external_contracts(external_contracts, global_ctx._contracts) # If there is an init func... if initfunc: o.append(['seq', initializer_lll]) o.append( parse_func(initfunc[0], { **{ 'self': sigs }, **external_contracts }, origcode, global_ctx)) # If there are regular functions... if otherfuncs or defaultfunc: o = parse_other_functions(o, otherfuncs, sigs, external_contracts, origcode, global_ctx, defaultfunc, runtime_only) return LLLnode.from_list(o, typ=None)
def __init__(self, vyper_module: vy_ast.Module, fn_node: vy_ast.FunctionDef, namespace: dict) -> None: self.vyper_module = vyper_module self.fn_node = fn_node self.namespace = namespace self.func = fn_node._metadata["type"] self.annotation_visitor = StatementAnnotationVisitor( fn_node, namespace) self.expr_visitor = _LocalExpressionVisitor() namespace.update(self.func.arguments) for node in fn_node.body: self.visit(node) if self.func.return_type: if not check_for_terminus(fn_node.body): raise FunctionDeclarationException( f"Missing or unmatched return statements in function '{fn_node.name}'", fn_node) if self.func.mutability == StateMutability.PURE: node_list = fn_node.get_descendants( vy_ast.Attribute, { "value.id": set(CONSTANT_ENVIRONMENT_VARS.keys()).union( set(MUTABLE_ENVIRONMENT_VARS.keys())) }, ) for node in node_list: t = node._metadata.get("type") if isinstance(t, ContractFunction ) and t.mutability == StateMutability.PURE: # allowed continue raise StateAccessViolation( "not allowed to query contract or environment variables in pure functions", node_list[0], ) if self.func.mutability is not StateMutability.PAYABLE: node_list = fn_node.get_descendants(vy_ast.Attribute, { "value.id": "msg", "attr": "value" }) if node_list: raise NonPayableViolation( "msg.value is not allowed in non-payable functions", node_list[0])
def parse_external_interfaces(external_interfaces, global_ctx): for _interfacename in global_ctx._contracts: # TODO factor me into helper function _interface_defs = global_ctx._contracts[_interfacename] _defnames = [_def.name for _def in _interface_defs] interface = {} if len(set(_defnames)) < len(_interface_defs): raise FunctionDeclarationException( "Duplicate function name: " f"{[name for name in _defnames if _defnames.count(name) > 1][0]}" ) for _def in _interface_defs: constant = False # test for valid call type keyword. if (len(_def.body) == 1 and isinstance(_def.body[0], vy_ast.Expr) and isinstance(_def.body[0].value, vy_ast.Name) # NOTE: Can't import enums here because of circular import and _def.body[0].value.id in ("pure", "view", "nonpayable", "payable")): constant = True if _def.body[0].value.id in ("view", "pure") else False else: raise StructureException( "state mutability of call type must be specified", _def) # Recognizes already-defined structs sig = FunctionSignature.from_definition( _def, sigs=global_ctx.interface_names, interface_def=True, constant_override=constant, custom_structs=global_ctx._structs, ) interface[sig.name] = sig external_interfaces[_interfacename] = interface for interface_name, interface in global_ctx._interfaces.items(): external_interfaces[interface_name] = { sig.name: sig for sig in interface if isinstance(sig, FunctionSignature) } return external_interfaces
def get_global_context(cls, code): global_ctx = cls() for item in code: # Contract references if isinstance(item, ast.ClassDef): if global_ctx._events or global_ctx._globals or global_ctx._defs: raise StructureException( "External contract and struct declarations must come before event declarations, global declarations, and function definitions", item) if item.class_type == 'struct': if global_ctx._contracts: raise StructureException( "Structs must come before external contract definitions", item) global_ctx._structs[item.name] = global_ctx.make_struct( item.name, item.body) elif item.class_type == 'contract': global_ctx._contracts[ item.name] = GlobalContext.make_contract(item.body) else: raise StructureException( "Unknown class_type. This is likely a compiler bug, please report", item) # Statements of the form: # variable_name: type elif isinstance(item, ast.AnnAssign): global_ctx.add_globals_and_events(item) # Function definitions elif isinstance(item, ast.FunctionDef): if item.name in global_ctx._globals: raise FunctionDeclarationException( "Function name shadowing a variable name: %s" % item.name) global_ctx._defs.append(item) else: raise StructureException("Invalid top-level statement", item) # Add getters to _defs global_ctx._defs += global_ctx._getters return global_ctx
def parse_external_contracts(external_contracts, global_ctx): for _contractname in global_ctx._contracts: _contract_defs = global_ctx._contracts[_contractname] _defnames = [_def.name for _def in _contract_defs] contract = {} if len(set(_defnames)) < len(_contract_defs): raise FunctionDeclarationException( "Duplicate function name: %s" % ( [name for name in _defnames if _defnames.count(name) > 1][0] ) ) for _def in _contract_defs: constant = False # test for valid call type keyword. if len(_def.body) == 1 and \ isinstance(_def.body[0], ast.Expr) and \ isinstance(_def.body[0].value, ast.Name) and \ _def.body[0].value.id in ('modifying', 'constant'): constant = True if _def.body[0].value.id == 'constant' else False else: raise StructureException('constant or modifying call type must be specified', _def) # Recognizes already-defined structs sig = FunctionSignature.from_definition( _def, contract_def=True, constant=constant, custom_structs=global_ctx._structs, constants=global_ctx._constants ) contract[sig.name] = sig external_contracts[_contractname] = contract for interface_name, interface in global_ctx._interfaces.items(): external_contracts[interface_name] = { sig.name: sig for sig in interface if isinstance(sig, FunctionSignature) } return external_contracts
def parse_external_contracts(external_contracts, _contracts): for _contractname in _contracts: _contract_defs = _contracts[_contractname] _defnames = [_def.name for _def in _contract_defs] contract = {} if len(set(_defnames)) < len(_contract_defs): raise FunctionDeclarationException("Duplicate function name: %s" % [name for name in _defnames if _defnames.count(name) > 1][0]) for _def in _contract_defs: constant = False # test for valid call type keyword. if len(_def.body) == 1 and \ isinstance(_def.body[0], ast.Expr) and \ isinstance(_def.body[0].value, ast.Name) and \ _def.body[0].value.id in ('modifying', 'constant'): constant = True if _def.body[0].value.id == 'constant' else False else: raise StructureException('constant or modifying call type must be specified', _def) sig = FunctionSignature.from_definition(_def, contract_def=True, constant=constant) contract[sig.name] = sig external_contracts[_contractname] = contract return external_contracts
def external_contract_call(node, context, contract_name, contract_address, pos, value=None, gas=None): if value is None: value = 0 if gas is None: gas = 'gas' if contract_name not in context.sigs: raise VariableDeclarationException("Contract not declared yet: %s" % contract_name) method_name = node.func.attr if method_name not in context.sigs[contract_name]: raise FunctionDeclarationException("Function not declared yet: %s (reminder: " "function must be declared in the correct contract)" % method_name, pos) sig = context.sigs[contract_name][method_name] inargs, inargsize = pack_arguments(sig, [parse_expr(arg, context) for arg in node.args], context, pos=pos) output_placeholder, output_size, returner = get_external_contract_call_output(sig, context) sub = ['seq', ['assert', ['extcodesize', contract_address]], ['assert', ['ne', 'address', contract_address]]] if context.is_constant or sig.const: 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]]) sub.extend(returner) o = LLLnode.from_list(sub, typ=sig.output_type, location='memory', pos=getpos(node)) return o
def parse_tree_to_ir( global_ctx: GlobalContext ) -> Tuple[IRnode, IRnode, FunctionSignatures]: _names_def = [_def.name for _def in global_ctx._defs] # Checks for duplicate function names if len(set(_names_def)) < len(_names_def): raise FunctionDeclarationException( "Duplicate function name: " f"{[name for name in _names_def if _names_def.count(name) > 1][0]}" ) _names_events = [_event.name for _event in global_ctx._events] # Checks for duplicate event names if len(set(_names_events)) < len(_names_events): raise EventDeclarationException(f"""Duplicate event name: {[name for name in _names_events if _names_events.count(name) > 1][0]}""" ) # Initialization function init_function = next( (_def for _def in global_ctx._defs if is_initializer(_def)), None) # Default function default_function = next( (i for i in global_ctx._defs if is_default_func(i)), None) regular_functions = [ _def for _def in global_ctx._defs if not is_initializer(_def) and not is_default_func(_def) ] sigs: dict = {} external_interfaces: dict = {} # Create the main statement o: List[Union[str, IRnode]] = ["seq"] if global_ctx._contracts or global_ctx._interfaces: external_interfaces = parse_external_interfaces( external_interfaces, global_ctx) init_func_ir = None if init_function: init_func_ir, _frame_start, init_frame_size = generate_ir_for_function( init_function, { **{ "self": sigs }, **external_interfaces }, global_ctx, False, ) o.append(init_func_ir) if regular_functions or default_function: runtime = parse_regular_functions( regular_functions, sigs, external_interfaces, global_ctx, default_function, init_func_ir, ) else: # for some reason, somebody may want to deploy a contract with no code, # or more likely, a "pure data" contract which contains immutables runtime = IRnode.from_list(["seq"]) immutables_len = global_ctx.immutable_section_bytes if init_function: memsize = init_func_ir.context.memory_allocator.size_of_mem # type: ignore else: memsize = 0 # note: (deploy mem_ofst, code, extra_padding) o.append(["deploy", memsize, runtime, immutables_len]) # type: ignore return IRnode.from_list(o), IRnode.from_list(runtime), sigs
def external_contract_call(node, context, contract_name, contract_address, pos, value=None, gas=None): from vyper.parser.parser import parse_expr if value is None: value = 0 if gas is None: gas = 'gas' if not contract_name: raise StructureException( f'Invalid external contract call "{node.func.attr}".', node ) if contract_name not in context.sigs: raise VariableDeclarationException( f'Contract "{contract_name}" not declared yet', node ) method_name = node.func.attr if method_name not in context.sigs[contract_name]: raise FunctionDeclarationException( ( "Function not declared yet: %s (reminder: " "function must be declared in the correct contract)" " The available methods are: %s" ) % (method_name, ",".join(context.sigs[contract_name].keys())), node.func ) sig = context.sigs[contract_name][method_name] inargs, inargsize, _ = pack_arguments( sig, [parse_expr(arg, context) for arg in node.args], context, pos=pos, ) output_placeholder, output_size, returner = get_external_contract_call_output(sig, context) sub = [ 'seq', ['assert', ['extcodesize', contract_address]], ['assert', ['ne', 'address', contract_address]], ] if context.is_constant() or sig.const: 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, ] ]) sub.extend(returner) o = LLLnode.from_list(sub, typ=sig.output_type, location='memory', pos=getpos(node)) return o
def from_definition( cls, code, sigs=None, custom_structs=None, interface_def=False, constants=None, constant_override=False, is_from_json=False, ): if not custom_structs: custom_structs = {} name = code.name mem_pos = 0 valid_name, msg = is_varname_valid(name, custom_structs, constants) if not valid_name and (not name.lower() in FUNCTION_WHITELIST): raise FunctionDeclarationException("Function name invalid. " + msg, code) # Validate default values. for default_value in getattr(code.args, "defaults", []): validate_default_values(default_value) # 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 if not typ: raise InvalidType("Argument must have type", arg) # Validate arg name. check_valid_varname( arg.arg, custom_structs, constants, arg, "Argument name invalid or reserved. ", FunctionDeclarationException, ) # Check for duplicate arg name. if arg.arg in (x.name for x in args): raise FunctionDeclarationException( "Duplicate function argument name: " + arg.arg, arg, ) parsed_type = parse_type( typ, None, sigs, custom_structs=custom_structs, constants=constants, ) 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 internal = False external = False nonreentrant_key = "" # 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": internal = True elif isinstance(dec, vy_ast.Name) and dec.id == "external": external = True elif isinstance(dec, vy_ast.Call) and dec.func.id == "nonreentrant": if nonreentrant_key: raise StructureException( "Only one @nonreentrant decorator allowed per function", dec ) if ( dec.args and len(dec.args) == 1 and isinstance(dec.args[0], vy_ast.Str) and dec.args[0].s ): # noqa: E501 nonreentrant_key = dec.args[0].s else: raise StructureException( "@nonreentrant decorator requires a non-empty string to use as a key.", dec ) else: raise StructureException("Bad decorator", dec) 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" if external and internal: raise StructureException( f"Cannot use external and internal decorators on the same function: {name}" ) if mutability == "payable" and internal: raise StructureException(f"Function {name} cannot be both internal and payable.") if (not external and not internal) and not interface_def: raise StructureException( "Function visibility must be declared (@external or @internal)", code, ) if mutability in ("view", "pure") and nonreentrant_key: raise StructureException( f"@nonreentrant makes no sense on a @{mutability} function.", code ) # 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 if not code.returns: output_type = None elif isinstance( code.returns, (vy_ast.Name, vy_ast.Compare, vy_ast.Subscript, vy_ast.Call, vy_ast.Tuple) ): output_type = parse_type( code.returns, None, sigs, custom_structs=custom_structs, constants=constants, ) else: raise InvalidType( f"Output type invalid or unsupported: {parse_type(code.returns, None)}", code.returns, ) # Output type must be canonicalizable if output_type is not None: 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, constants) # 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, internal, nonreentrant_key, sig, method_id, code, is_from_json, )
def parse_tree_to_lll(global_ctx: GlobalContext) -> Tuple[LLLnode, LLLnode]: _names_def = [_def.name for _def in global_ctx._defs] # Checks for duplicate function names if len(set(_names_def)) < len(_names_def): raise FunctionDeclarationException( "Duplicate function name: " f"{[name for name in _names_def if _names_def.count(name) > 1][0]}" ) _names_events = [_event.name for _event in global_ctx._events] # Checks for duplicate event names if len(set(_names_events)) < len(_names_events): raise EventDeclarationException(f"""Duplicate event name: {[name for name in _names_events if _names_events.count(name) > 1][0]}""" ) # Initialization function initfunc = [_def for _def in global_ctx._defs if is_initializer(_def)] # Default function defaultfunc = next((i for i in global_ctx._defs if is_default_func(i)), None) # Regular functions otherfuncs = [ _def for _def in global_ctx._defs if not is_initializer(_def) and not is_default_func(_def) ] sigs: dict = {} external_interfaces: dict = {} # Create the main statement o = ["seq"] if global_ctx._contracts or global_ctx._interfaces: external_interfaces = parse_external_interfaces( external_interfaces, global_ctx) # If there is an init func... if initfunc: o.append(init_func_init_lll()) o.append( parse_function( initfunc[0], { **{ "self": sigs }, **external_interfaces }, global_ctx, False, )) # If there are regular functions... if otherfuncs or defaultfunc: o, runtime = parse_other_functions( o, otherfuncs, sigs, external_interfaces, global_ctx, defaultfunc, ) else: runtime = o.copy() return LLLnode.from_list(o, typ=None), LLLnode.from_list(runtime, typ=None)
def from_definition(cls, code, sigs=None, custom_units=None, custom_structs=None, contract_def=False, constants=None, constant=False): if not custom_structs: custom_structs = {} name = code.name mem_pos = 0 valid_name, msg = is_varname_valid(name, custom_units, custom_structs, constants) if not valid_name and (not name.lower() in function_whitelist): raise FunctionDeclarationException("Function name invalid. " + msg, code) # Validate default values. for default_value in getattr(code.args, 'defaults', []): allowed_types = (ast.Num, ast.Str, ast.Bytes, ast.List, ast.NameConstant) if not isinstance(default_value, allowed_types): raise FunctionDeclarationException( "Default parameter values have to be literals.") # 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 if not typ: raise InvalidTypeException("Argument must have type", arg) # Validate arg name. check_valid_varname( arg.arg, custom_units, custom_structs, constants, arg, "Argument name invalid or reserved. ", FunctionDeclarationException, ) # Check for duplicate arg name. if arg.arg in (x.name for x in args): raise FunctionDeclarationException( "Duplicate function argument name: " + arg.arg, arg, ) parsed_type = parse_type( typ, None, sigs, custom_units=custom_units, custom_structs=custom_structs, constants=constants, ) 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 # Apply decorators const, payable, private, public, nonreentrant_key = False, False, False, False, '' for dec in code.decorator_list: if isinstance(dec, ast.Name) and dec.id == "constant": const = True elif isinstance(dec, ast.Name) and dec.id == "payable": payable = True elif isinstance(dec, ast.Name) and dec.id == "private": private = True elif isinstance(dec, ast.Name) and dec.id == "public": public = True elif isinstance(dec, ast.Call) and dec.func.id == "nonreentrant": if dec.args and len(dec.args) == 1 and isinstance( dec.args[0], ast.Str) and dec.args[0].s: # noqa: E501 nonreentrant_key = dec.args[0].s else: raise StructureException( "@nonreentrant decorator requires a non-empty string to use as a key.", dec) else: raise StructureException("Bad decorator", dec) if public and private: raise StructureException( "Cannot use public and private decorators on the same function: {}" .format(name)) if payable and const: raise StructureException( "Function {} cannot be both constant and payable.".format( name)) if payable and private: raise StructureException( "Function {} cannot be both private and payable.".format(name)) if (not public and not private) and not contract_def: raise StructureException( "Function visibility must be declared (@public or @private)", code, ) if constant and nonreentrant_key: raise StructureException( "@nonreentrant makes no sense on a @constant function.", code) if constant: const = True # 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 if not code.returns: output_type = None elif isinstance( code.returns, (ast.Name, ast.Compare, ast.Subscript, ast.Call, ast.Tuple)): output_type = parse_type( code.returns, None, sigs, custom_units=custom_units, custom_structs=custom_structs, constants=constants, ) else: raise InvalidTypeException( "Output type invalid or unsupported: %r" % parse_type(code.returns, None), code.returns, ) # Output type must be canonicalizable if output_type is not None: 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_units, custom_structs, constants) # 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, const, payable, private, nonreentrant_key, sig, method_id, custom_units, code)
def parse_func(code, sigs, origcode, global_ctx, _vars=None): if _vars is None: _vars = {} sig = FunctionSignature.from_definition( code, sigs=sigs, custom_units=global_ctx._custom_units, custom_structs=global_ctx._structs, constants=global_ctx._constants) # Get base args for function. total_default_args = len(code.args.defaults) base_args = sig.args[: -total_default_args] if total_default_args > 0 else sig.args default_args = code.args.args[-total_default_args:] default_values = dict( zip([arg.arg for arg in default_args], code.args.defaults)) # __init__ function may not have defaults. if sig.name == '__init__' and total_default_args > 0: raise FunctionDeclarationException( "__init__ function may not have default parameters.") # Check for duplicate variables with globals for arg in sig.args: if arg.name in global_ctx._globals: raise FunctionDeclarationException( "Variable name duplicated between function arguments and globals: " + arg.name) nonreentrant_pre = [['pass']] nonreentrant_post = [['pass']] if sig.nonreentrant_key: nkey = global_ctx.get_nonrentrant_counter(sig.nonreentrant_key) nonreentrant_pre = [[ 'seq', ['assert', ['iszero', ['sload', nkey]]], ['sstore', nkey, 1] ]] nonreentrant_post = [['sstore', nkey, 0]] # Create a local (per function) context. context = Context( vars=_vars, global_ctx=global_ctx, sigs=sigs, return_type=sig.output_type, constancy=Constancy.Constant if sig.const else Constancy.Mutable, is_payable=sig.payable, origcode=origcode, is_private=sig.private, method_id=sig.method_id) # Copy calldata to memory for fixed-size arguments max_copy_size = sum([ 32 if isinstance(arg.typ, ByteArrayLike) else get_size_of_type(arg.typ) * 32 for arg in sig.args ]) base_copy_size = sum([ 32 if isinstance(arg.typ, ByteArrayLike) else get_size_of_type(arg.typ) * 32 for arg in base_args ]) context.next_mem += max_copy_size clampers = [] # Create callback_ptr, this stores a destination in the bytecode for a private # function to jump to after a function has executed. _post_callback_ptr = "{}_{}_post_callback_ptr".format( sig.name, sig.method_id) if sig.private: context.callback_ptr = context.new_placeholder(typ=BaseType('uint256')) clampers.append( LLLnode.from_list( ['mstore', context.callback_ptr, 'pass'], annotation='pop callback pointer', )) if total_default_args > 0: clampers.append(['label', _post_callback_ptr]) # private functions without return types need to jump back to # the calling function, as there is no return statement to handle the # jump. stop_func = [['stop']] if sig.output_type is None and sig.private: stop_func = [['jump', ['mload', context.callback_ptr]]] if not len(base_args): copier = 'pass' elif sig.name == '__init__': copier = [ 'codecopy', MemoryPositions.RESERVED_MEMORY, '~codelen', base_copy_size ] else: copier = get_arg_copier(sig=sig, total_size=base_copy_size, memory_dest=MemoryPositions.RESERVED_MEMORY) clampers.append(copier) # Add asserts for payable and internal # private never gets payable check. if not sig.payable and not sig.private: clampers.append(['assert', ['iszero', 'callvalue']]) # Fill variable positions for i, arg in enumerate(sig.args): if i < len(base_args) and not sig.private: clampers.append( make_clamper( arg.pos, context.next_mem, arg.typ, sig.name == '__init__', )) if isinstance(arg.typ, ByteArrayLike): context.vars[arg.name] = VariableRecord(arg.name, context.next_mem, arg.typ, False) context.next_mem += 32 * get_size_of_type(arg.typ) else: context.vars[arg.name] = VariableRecord( arg.name, MemoryPositions.RESERVED_MEMORY + arg.pos, arg.typ, False, ) # Private function copiers. No clamping for private functions. dyn_variable_names = [ a.name for a in base_args if isinstance(a.typ, ByteArrayLike) ] if sig.private and dyn_variable_names: i_placeholder = context.new_placeholder(typ=BaseType('uint256')) unpackers = [] for idx, var_name in enumerate(dyn_variable_names): var = context.vars[var_name] ident = "_load_args_%d_dynarg%d" % (sig.method_id, idx) o = make_unpacker(ident=ident, i_placeholder=i_placeholder, begin_pos=var.pos) unpackers.append(o) if not unpackers: unpackers = ['pass'] clampers.append( LLLnode.from_list( # [0] to complete full overarching 'seq' statement, see private_label. ['seq_unchecked'] + unpackers + [0], typ=None, annotation='dynamic unpacker', pos=getpos(code), )) # Create "clampers" (input well-formedness checkers) # Return function body if sig.name == '__init__': o = LLLnode.from_list( ['seq'] + clampers + [parse_body(code.body, context)], pos=getpos(code), ) elif is_default_func(sig): if len(sig.args) > 0: raise FunctionDeclarationException( 'Default function may not receive any arguments.', code) if sig.private: raise FunctionDeclarationException( 'Default function may only be public.', code, ) o = LLLnode.from_list( ['seq'] + clampers + [parse_body(code.body, context)], pos=getpos(code), ) else: if total_default_args > 0: # Function with default parameters. function_routine = "{}_{}".format(sig.name, sig.method_id) default_sigs = generate_default_arg_sigs(code, sigs, global_ctx) sig_chain = ['seq'] for default_sig in default_sigs: sig_compare, private_label = get_sig_statements( default_sig, getpos(code)) # Populate unset default variables populate_arg_count = len(sig.args) - len(default_sig.args) set_defaults = [] if populate_arg_count > 0: current_sig_arg_names = {x.name for x in default_sig.args} missing_arg_names = [ arg.arg for arg in default_args if arg.arg not in current_sig_arg_names ] for arg_name in missing_arg_names: value = Expr(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 base_args} if sig.private: # Load all variables in default section, if private, # because the stack is a linear pipe. copier_arg_count = len(default_sig.args) copier_arg_names = current_sig_arg_names else: copier_arg_count = len(default_sig.args) - len(base_args) copier_arg_names = 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 = [] 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] calldata_offset = calldata_offset_map[arg_name] if sig.private: _offset = calldata_offset if isinstance(var.typ, ByteArrayLike): _size = 32 dynamics.append(var.pos) else: _size = var.size * 32 default_copiers.append( get_arg_copier( sig=sig, memory_dest=var.pos, total_size=_size, offset=_offset, )) else: # Add clampers. default_copiers.append( make_clamper( calldata_offset - 4, var.pos, var.typ, )) # Add copying code. if isinstance(var.typ, ByteArrayLike): _offset = [ 'add', 4, ['calldataload', calldata_offset] ] else: _offset = calldata_offset default_copiers.append( get_arg_copier( sig=sig, memory_dest=var.pos, total_size=var.size * 32, offset=_offset, )) # Unpack byte array if necessary. if dynamics: i_placeholder = context.new_placeholder( typ=BaseType('uint256')) for idx, var_pos in enumerate(dynamics): ident = 'unpack_default_sig_dyn_%d_arg%d' % ( default_sig.method_id, 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', private_label, ['pass'] if not sig.private else 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 if sig.private else function_routine ] ] ]) # With private functions all variable loading occurs in the default # function sub routine. if sig.private: _clampers = [['label', _post_callback_ptr]] else: _clampers = clampers # Function with default parameters. o = LLLnode.from_list( [ 'seq', sig_chain, [ 'if', 0, # can only be jumped into [ 'seq', ['label', function_routine] if not sig.private else ['pass'], ['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, private_label = get_sig_statements(sig, getpos(code)) o = LLLnode.from_list([ 'if', sig_compare, ['seq'] + [private_label] + nonreentrant_pre + clampers + [parse_body(c, context) for c in code.body] + nonreentrant_post + stop_func ], typ=None, pos=getpos(code)) # Check for at leasts one return statement if necessary. if context.return_type and context.function_return_count == 0: raise FunctionDeclarationException( "Missing return statement in function '%s' " % sig.name, code) o.context = context o.total_gas = o.gas + calc_mem_gas(o.context.next_mem) o.func_name = sig.name return o
def from_definition(cls, code, sigs=None, custom_units=None, contract_def=False, constant=False): name = code.name pos = 0 if not is_varname_valid(name, custom_units=custom_units): raise FunctionDeclarationException("Function name invalid: " + name) # Determine the arguments, expects something of the form def foo(arg1: int128, arg2: int128 ... args = [] for arg in code.args.args: typ = arg.annotation if not typ: raise InvalidTypeException("Argument must have type", arg) if not is_varname_valid(arg.arg, custom_units=custom_units): raise FunctionDeclarationException( "Argument name invalid or reserved: " + arg.arg, arg) if arg.arg in (x.name for x in args): raise FunctionDeclarationException( "Duplicate function argument name: " + arg.arg, arg) parsed_type = parse_type(typ, None, sigs, custom_units=custom_units) args.append(VariableRecord(arg.arg, pos, parsed_type, False)) if isinstance(parsed_type, ByteArrayType): pos += 32 else: pos += get_size_of_type(parsed_type) * 32 # Apply decorators const, payable, private, public = False, False, False, False for dec in code.decorator_list: if isinstance(dec, ast.Name) and dec.id == "constant": const = True elif isinstance(dec, ast.Name) and dec.id == "payable": payable = True elif isinstance(dec, ast.Name) and dec.id == "private": private = True elif isinstance(dec, ast.Name) and dec.id == "public": public = True else: raise StructureException("Bad decorator", dec) if public and private: raise StructureException( "Cannot use public and private decorators on the same function: {}" .format(name)) if payable and const: raise StructureException( "Function {} cannot be both constant and payable.".format( name)) if payable and private: raise StructureException( "Function {} cannot be both private and payable.".format(name)) if (not public and not private) and not contract_def: raise StructureException( "Function visibility must be declared (@public or @private)", code) if constant: const = True # 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 if not code.returns: output_type = None elif isinstance( code.returns, (ast.Name, ast.Compare, ast.Subscript, ast.Call, ast.Tuple)): output_type = parse_type(code.returns, None, sigs, custom_units=custom_units) else: raise InvalidTypeException( "Output type invalid or unsupported: %r" % parse_type(code.returns, None), code.returns, ) # Output type must be canonicalizable if output_type is not None: 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_units) # Take the first 4 bytes of the hash of the sig to get the method ID method_id = fourbytes_to_int(sha3(bytes(sig, 'utf-8'))[:4]) return cls(name, args, output_type, const, payable, private, sig, method_id, custom_units)
def parse_tree_to_lll( code, origcode, runtime_only=False, interface_codes=None, ): global_ctx = GlobalContext.get_global_context(code, interface_codes) _names_def = [_def.name for _def in global_ctx._defs] # Checks for duplicate function names if len(set(_names_def)) < len(_names_def): raise FunctionDeclarationException( "Duplicate function name: %s" % [name for name in _names_def if _names_def.count(name) > 1][0]) _names_events = [_event.target.id for _event in global_ctx._events] # Checks for duplicate event names if len(set(_names_events)) < len(_names_events): raise EventDeclarationException( "Duplicate event name: %s" % [name for name in _names_events if _names_events.count(name) > 1][0]) # Initialization function initfunc = [_def for _def in global_ctx._defs if is_initializer(_def)] # Default function defaultfunc = [_def for _def in global_ctx._defs if is_default_func(_def)] # Regular functions otherfuncs = [ _def for _def in global_ctx._defs if not is_initializer(_def) and not is_default_func(_def) ] sigs = {} external_contracts = {} # Create the main statement o = ['seq'] if global_ctx._events: sigs = parse_events(sigs, global_ctx) if global_ctx._contracts: external_contracts = parse_external_contracts(external_contracts, global_ctx._contracts, global_ctx._structs, global_ctx._constants) # If there is an init func... if initfunc: o.append(['seq', initializer_lll]) o.append( parse_func(initfunc[0], { **{ 'self': sigs }, **external_contracts }, origcode, global_ctx)) # If there are regular functions... if otherfuncs or defaultfunc: o = parse_other_functions(o, otherfuncs, sigs, external_contracts, origcode, global_ctx, defaultfunc, runtime_only) # Check interface. if global_ctx._interface: funcs_left = global_ctx._interface.copy() for sig, func_sig in sigs.items(): if isinstance(func_sig, FunctionSignature): if sig in funcs_left and not func_sig.private: del funcs_left[sig] if isinstance(func_sig, EventSignature) and func_sig.sig in funcs_left: del funcs_left[func_sig.sig] if funcs_left: error_message = 'Contract does not comply to supplied Interface(s).\n' missing_functions = [ sig_name for sig_name, func_sig in funcs_left.items() if isinstance(func_sig, FunctionSignature) ] missing_events = [ sig_name for sig_name, func_sig in funcs_left.items() if isinstance(func_sig, EventSignature) ] if missing_functions: error_message += 'Missing interface functions:\n\t{}'.format( '\n\t'.join(missing_functions)) if missing_events: error_message += 'Missing interface events:\n\t{}'.format( '\n\t'.join(missing_events)) raise StructureException(error_message) return LLLnode.from_list(o, typ=None)
def parse_func(code, _globals, sigs, origcode, _custom_units, _vars=None): if _vars is None: _vars = {} sig = FunctionSignature.from_definition(code, sigs=sigs, custom_units=_custom_units) # Check for duplicate variables with globals for arg in sig.args: if arg.name in _globals: raise FunctionDeclarationException("Variable name duplicated between function arguments and globals: " + arg.name) # Create a context context = Context(vars=_vars, globals=_globals, sigs=sigs, return_type=sig.output_type, is_constant=sig.const, is_payable=sig.payable, origcode=origcode, custom_units=_custom_units) # Copy calldata to memory for fixed-size arguments copy_size = sum([32 if isinstance(arg.typ, ByteArrayType) else get_size_of_type(arg.typ) * 32 for arg in sig.args]) context.next_mem += copy_size if not len(sig.args): copier = 'pass' elif sig.name == '__init__': copier = ['codecopy', MemoryPositions.RESERVED_MEMORY, '~codelen', copy_size] else: copier = ['calldatacopy', MemoryPositions.RESERVED_MEMORY, 4, copy_size] clampers = [copier] # Add asserts for payable and internal if not sig.payable: clampers.append(['assert', ['iszero', 'callvalue']]) if sig.private: clampers.append(['assert', ['eq', 'caller', 'address']]) # Fill in variable positions for arg in sig.args: clampers.append(make_clamper(arg.pos, context.next_mem, arg.typ, sig.name == '__init__')) if isinstance(arg.typ, ByteArrayType): context.vars[arg.name] = VariableRecord(arg.name, context.next_mem, arg.typ, False) context.next_mem += 32 * get_size_of_type(arg.typ) else: context.vars[arg.name] = VariableRecord(arg.name, MemoryPositions.RESERVED_MEMORY + arg.pos, arg.typ, False) # Create "clampers" (input well-formedness checkers) # Return function body if sig.name == '__init__': o = LLLnode.from_list(['seq'] + clampers + [parse_body(code.body, context)], pos=getpos(code)) elif is_default_func(sig): if len(sig.args) > 0: raise FunctionDeclarationException('Default function may not receive any arguments.', code) if sig.private: raise FunctionDeclarationException('Default function may only be public.', code) o = LLLnode.from_list(['seq'] + clampers + [parse_body(code.body, context)], pos=getpos(code)) else: method_id_node = LLLnode.from_list(sig.method_id, pos=getpos(code), annotation='%s' % sig.name) o = LLLnode.from_list(['if', ['eq', ['mload', 0], method_id_node], ['seq'] + clampers + [parse_body(c, context) for c in code.body] + ['stop'] ], typ=None, pos=getpos(code)) # Check for at leasts one return statement if necessary. if context.return_type and context.function_return_count == 0: raise FunctionDeclarationException( "Missing return statement in function '%s' " % sig.name, code ) o.context = context o.total_gas = o.gas + calc_mem_gas(o.context.next_mem) o.func_name = sig.name return o
def call(self): from .parser import ( pack_arguments, pack_logging_data, pack_logging_topics, external_contract_call, ) if isinstance(self.stmt.func, ast.Name): if self.stmt.func.id in stmt_dispatch_table: return stmt_dispatch_table[self.stmt.func.id](self.stmt, self.context) elif self.stmt.func.id in dispatch_table: raise StructureException( "Function {} can not be called without being used.".format( self.stmt.func.id), self.stmt) else: raise StructureException( "Unknown function: '{}'.".format(self.stmt.func.id), self.stmt) elif isinstance(self.stmt.func, ast.Attribute) and isinstance( self.stmt.func.value, ast.Name) and self.stmt.func.value.id == "self": method_name = self.stmt.func.attr if method_name not in self.context.sigs['self']: raise FunctionDeclarationException( "Function not declared yet (reminder: functions cannot " "call functions later in code than themselves): %s" % self.stmt.func.attr) sig = self.context.sigs['self'][method_name] if self.context.is_constant and not sig.const: raise ConstancyViolationException( "May not call non-constant function '%s' within a constant function." % (method_name)) add_gas = self.context.sigs['self'][method_name].gas inargs, inargsize = pack_arguments( sig, [Expr(arg, self.context).lll_node for arg in self.stmt.args], self.context, pos=getpos(self.stmt)) return LLLnode.from_list([ 'assert', ['call', ['gas'], ['address'], 0, inargs, inargsize, 0, 0] ], typ=None, pos=getpos(self.stmt), add_gas_estimate=add_gas, annotation='Internal Call: %s' % method_name) elif isinstance(self.stmt.func, ast.Attribute) and isinstance( self.stmt.func.value, ast.Call): contract_name = self.stmt.func.value.func.id contract_address = Expr.parse_value_expr( self.stmt.func.value.args[0], self.context) return external_contract_call(self.stmt, self.context, contract_name, contract_address, pos=getpos(self.stmt)) elif isinstance(self.stmt.func.value, ast.Attribute ) and self.stmt.func.value.attr in self.context.sigs: contract_name = self.stmt.func.value.attr var = self.context.globals[self.stmt.func.value.attr] contract_address = unwrap_location( LLLnode.from_list(var.pos, typ=var.typ, location='storage', pos=getpos(self.stmt), annotation='self.' + self.stmt.func.value.attr)) return external_contract_call(self.stmt, self.context, contract_name, contract_address, pos=getpos(self.stmt)) elif isinstance( self.stmt.func.value, ast.Attribute ) and self.stmt.func.value.attr in self.context.globals: contract_name = self.context.globals[ self.stmt.func.value.attr].typ.unit var = self.context.globals[self.stmt.func.value.attr] contract_address = unwrap_location( LLLnode.from_list(var.pos, typ=var.typ, location='storage', pos=getpos(self.stmt), annotation='self.' + self.stmt.func.value.attr)) return external_contract_call(self.stmt, self.context, contract_name, contract_address, pos=getpos(self.stmt)) elif isinstance(self.stmt.func, ast.Attribute) and self.stmt.func.value.id == 'log': if self.stmt.func.attr not in self.context.sigs['self']: raise EventDeclarationException("Event not declared yet: %s" % self.stmt.func.attr) event = self.context.sigs['self'][self.stmt.func.attr] if len(event.indexed_list) != len(self.stmt.args): raise EventDeclarationException( "%s received %s arguments but expected %s" % (event.name, len(self.stmt.args), len(event.indexed_list))) expected_topics, topics = [], [] expected_data, data = [], [] for pos, is_indexed in enumerate(event.indexed_list): if is_indexed: expected_topics.append(event.args[pos]) topics.append(self.stmt.args[pos]) else: expected_data.append(event.args[pos]) data.append(self.stmt.args[pos]) topics = pack_logging_topics(event.event_id, topics, expected_topics, self.context, pos=getpos(self.stmt)) inargs, inargsize, inargsize_node, inarg_start = pack_logging_data( expected_data, data, self.context, pos=getpos(self.stmt)) if inargsize_node is None: sz = inargsize else: sz = ['mload', inargsize_node] return LLLnode.from_list([ 'seq', inargs, LLLnode.from_list( ["log" + str(len(topics)), inarg_start, sz] + topics, add_gas_estimate=inargsize * 10) ], typ=None, pos=getpos(self.stmt)) else: raise StructureException( "Unsupported operator: %r" % ast.dump(self.stmt), self.stmt)
def validate_private_function(code: ast.FunctionDef, sig: FunctionSignature) -> None: """ Validate private function defintion """ if sig.is_default_func(): raise FunctionDeclarationException( 'Default function may only be public.', code)
def get_global_context(cls, code, interface_codes=None): from vyper.signatures.interface import ( extract_sigs, get_builtin_interfaces, ) interface_codes = {} if interface_codes is None else interface_codes global_ctx = cls() for item in code: # Contract references if isinstance(item, ast.ClassDef): if global_ctx._events or global_ctx._globals or global_ctx._defs: raise StructureException( ("External contract and struct declarations must come " "before event declarations, global declarations, and " "function definitions"), item) if item.class_type == 'struct': if global_ctx._contracts: raise StructureException( "Structs must come before external contract definitions", item) global_ctx._structs[item.name] = global_ctx.make_struct( item.name, item.body) elif item.class_type == 'contract': if item.name in global_ctx._contracts or item.name in global_ctx._interfaces: raise StructureException( f"Contract '{item.name}' is already defined", item, ) global_ctx._contracts[ item.name] = GlobalContext.make_contract(item.body) else: raise StructureException( "Unknown class_type. This is likely a compiler bug, please report", item) # Statements of the form: # variable_name: type elif isinstance(item, ast.AnnAssign): is_implements_statement = (isinstance(item.target, ast.Name) and item.target.id == 'implements') and item.annotation # implements statement. if is_implements_statement: interface_name = item.annotation.id if interface_name not in global_ctx._interfaces: raise StructureException( f'Unknown interface specified: {interface_name}', item) global_ctx._implemented_interfaces.add(interface_name) else: global_ctx.add_globals_and_events(item) # Function definitions elif isinstance(item, ast.FunctionDef): if item.name in global_ctx._globals: raise FunctionDeclarationException( f"Function name shadowing a variable name: {item.name}" ) global_ctx._defs.append(item) elif isinstance(item, ast.ImportFrom): if not item.level and item.module == 'vyper.interfaces': built_in_interfaces = get_builtin_interfaces() for item_alias in item.names: interface_name = item_alias.name if interface_name in global_ctx._interfaces: raise StructureException( f'Duplicate import of {interface_name}', item) if interface_name not in built_in_interfaces: raise StructureException( f'Built-In interface {interface_name} does not exist.', item) global_ctx._interfaces[ interface_name] = built_in_interfaces[ interface_name].copy() # noqa: E501 else: for item_alias in item.names: interface_name = item_alias.name if interface_name in global_ctx._interfaces: raise StructureException( f'Duplicate import of {interface_name}', item) if interface_name not in interface_codes: raise StructureException( f'Unknown interface {interface_name}', item) global_ctx._interfaces[interface_name] = extract_sigs( interface_codes[interface_name]) # noqa: E501 elif isinstance(item, ast.Import): for item_alias in item.names: if not item_alias.asname: raise StructureException( 'External interface import expects an alias using `as` statement', item) interface_name = item_alias.asname if interface_name in global_ctx._interfaces: raise StructureException( f'Duplicate import of {interface_name}', item) if interface_name not in interface_codes: raise StructureException( f'Unknown interface {interface_name}', item) global_ctx._interfaces[interface_name] = extract_sigs( interface_codes[interface_name]) # noqa: E501 else: raise StructureException("Invalid top-level statement", item) # Merge intefaces. if global_ctx._interfaces: for interface_name, sigs in global_ctx._interfaces.items(): if interface_name in global_ctx._implemented_interfaces: for func_sig in sigs: func_sig.defined_in_interface = interface_name global_ctx._interface[func_sig.sig] = func_sig # Add getters to _defs global_ctx._defs += global_ctx._getters return global_ctx
def get_global_context(cls, code, interface_codes=None): interface_codes = {} if interface_codes is None else interface_codes global_ctx = cls() for item in code: # Contract references if isinstance(item, ast.ClassDef): if global_ctx._events or global_ctx._globals or global_ctx._defs: raise StructureException( "External contract and struct declarations must come before event declarations, global declarations, and function definitions", item) if item.class_type == 'struct': if global_ctx._contracts: raise StructureException( "Structs must come before external contract definitions", item) global_ctx._structs[item.name] = global_ctx.make_struct( item.name, item.body) elif item.class_type == 'contract': global_ctx._contracts[ item.name] = GlobalContext.make_contract(item.body) else: raise StructureException( "Unknown class_type. This is likely a compiler bug, please report", item) # Statements of the form: # variable_name: type elif isinstance(item, ast.AnnAssign): # implements statement. if isinstance( item.target, ast.Name ) and item.target.id == 'implements' and item.annotation: interface_name = item.annotation.id if interface_name not in global_ctx._interfaces: raise StructureException( 'Unknown interface specified: {}'.format( interface_name), item) global_ctx._implemented_interfaces.add(interface_name) else: global_ctx.add_globals_and_events(item) # Function definitions elif isinstance(item, ast.FunctionDef): if item.name in global_ctx._globals: raise FunctionDeclarationException( "Function name shadowing a variable name: %s" % item.name) global_ctx._defs.append(item) elif isinstance(item, ast.ImportFrom): if item.module == 'vyper.interfaces': built_in_interfaces = get_builtin_interfaces() for item_alias in item.names: interface_name = item_alias.name if interface_name in global_ctx._interfaces: raise StructureException( 'Duplicate import of {}'.format( interface_name), item) if interface_name not in built_in_interfaces: raise StructureException( 'Built-In interface {} does not exist.'.format( interface_name), item) global_ctx._interfaces[ interface_name] = built_in_interfaces[ interface_name].copy() else: raise StructureException( 'Only built-in vyper.interfaces package supported for `from` statement.', item) elif isinstance(item, ast.Import): for item_alias in item.names: if not item_alias.asname: raise StructureException( 'External interface import expects and alias using `as` statement', item) interface_name = item_alias.asname if interface_name in global_ctx._interfaces: raise StructureException( 'Duplicate import of {}'.format(interface_name), item) if interface_name not in interface_codes: raise StructureException( 'Unknown interface {}'.format(interface_name), item) global_ctx._interfaces[interface_name] = extract_sigs( interface_codes[interface_name]) else: raise StructureException("Invalid top-level statement", item) if set(global_ctx._interfaces.keys() ) != global_ctx._implemented_interfaces: raise StructureException( 'All interfaces that are imported have to be implemented using the "implements" statement.' ) # Merge intefaces. if global_ctx._interfaces: for interface_name, sigs in global_ctx._interfaces.items(): for func_sig in sigs: setattr(func_sig, 'defined_in_interface', interface_name) global_ctx._interface[func_sig.sig] = func_sig # Add getters to _defs global_ctx._defs += global_ctx._getters return global_ctx
def parse_tree_to_lll(code, origcode, runtime_only=False, interface_codes=None): global_ctx = GlobalContext.get_global_context( code, interface_codes=interface_codes) _names_def = [_def.name for _def in global_ctx._defs] # Checks for duplicate function names if len(set(_names_def)) < len(_names_def): raise FunctionDeclarationException( "Duplicate function name: " f"{[name for name in _names_def if _names_def.count(name) > 1][0]}" ) _names_events = [_event.target.id for _event in global_ctx._events] # Checks for duplicate event names if len(set(_names_events)) < len(_names_events): raise EventDeclarationException(f"""Duplicate event name: {[name for name in _names_events if _names_events.count(name) > 1][0]}""" ) # Initialization function initfunc = [_def for _def in global_ctx._defs if is_initializer(_def)] # Default function defaultfunc = [_def for _def in global_ctx._defs if is_default_func(_def)] # Regular functions otherfuncs = [ _def for _def in global_ctx._defs if not is_initializer(_def) and not is_default_func(_def) ] sigs = {} external_contracts = {} # Create the main statement o = ['seq'] if global_ctx._events: sigs = parse_events(sigs, global_ctx) if global_ctx._contracts or global_ctx._interfaces: external_contracts = parse_external_contracts(external_contracts, global_ctx) # If there is an init func... if initfunc: o.append(INIT_FUNC_INIT_LLL) o.append( parse_function( initfunc[0], { **{ 'self': sigs }, **external_contracts }, origcode, global_ctx, )) # If there are regular functions... if otherfuncs or defaultfunc: o = parse_other_functions(o, otherfuncs, sigs, external_contracts, origcode, global_ctx, defaultfunc, runtime_only) # Check if interface of contract is correct. check_valid_contract_interface(global_ctx, sigs) return LLLnode.from_list(o, typ=None)
def from_FunctionDef( cls, node: vy_ast.FunctionDef, is_interface: Optional[bool] = False) -> "ContractFunction": """ Generate a `ContractFunction` object from a `FunctionDef` node. Arguments --------- node : FunctionDef Vyper ast node to generate the function definition from. is_interface: bool, optional Boolean indicating if the function definition is part of an interface. Returns ------- ContractFunction """ kwargs: Dict[str, Any] = {} if is_interface: # FunctionDef with stateMutability in body (Interface defintions) if (len(node.body) == 1 and isinstance(node.body[0], vy_ast.Expr) and isinstance(node.body[0].value, vy_ast.Name) and StateMutability.is_valid_value(node.body[0].value.id)): # Interfaces are always public kwargs["function_visibility"] = FunctionVisibility.EXTERNAL kwargs["state_mutability"] = StateMutability( node.body[0].value.id) elif len(node.body) == 1 and node.body[0].get("value.id") in ( "constant", "modifying"): if node.body[0].value.id == "constant": expected = "view or pure" else: expected = "payable or nonpayable" raise StructureException( f"State mutability should be set to {expected}", node.body[0]) else: raise StructureException( "Body must only contain state mutability label", node.body[0]) else: # FunctionDef with decorators (normal functions) for decorator in node.decorator_list: if isinstance(decorator, vy_ast.Call): if "nonreentrant" in kwargs: raise StructureException( "nonreentrant decorator is already set with key: " f"{kwargs['nonreentrant']}", node, ) if decorator.get("func.id") != "nonreentrant": raise StructureException("Decorator is not callable", decorator) if len(decorator.args) != 1 or not isinstance( decorator.args[0], vy_ast.Str): raise StructureException( "@nonreentrant name must be given as a single string literal", decorator) if node.name == "__init__": msg = "Nonreentrant decorator disallowed on `__init__`" raise FunctionDeclarationException(msg, decorator) kwargs["nonreentrant"] = decorator.args[0].value elif isinstance(decorator, vy_ast.Name): if FunctionVisibility.is_valid_value(decorator.id): if "function_visibility" in kwargs: raise FunctionDeclarationException( f"Visibility is already set to: {kwargs['function_visibility']}", node, ) kwargs["function_visibility"] = FunctionVisibility( decorator.id) elif StateMutability.is_valid_value(decorator.id): if "state_mutability" in kwargs: raise FunctionDeclarationException( f"Mutability is already set to: {kwargs['state_mutability']}", node) kwargs["state_mutability"] = StateMutability( decorator.id) else: if decorator.id == "constant": warnings.warn( "'@constant' decorator has been removed (see VIP2040). " "Use `@view` instead.", DeprecationWarning, ) raise FunctionDeclarationException( f"Unknown decorator: {decorator.id}", decorator) else: raise StructureException("Bad decorator syntax", decorator) if "function_visibility" not in kwargs: raise FunctionDeclarationException( f"Visibility must be set to one of: {', '.join(FunctionVisibility.values())}", node) if node.name == "__default__": if kwargs["function_visibility"] != FunctionVisibility.EXTERNAL: raise FunctionDeclarationException( "Default function must be marked as `@external`", node) if node.args.args: raise FunctionDeclarationException( "Default function may not receive any arguments", node.args.args[0]) if "state_mutability" not in kwargs: # Assume nonpayable if not set at all (cannot accept Ether, but can modify state) kwargs["state_mutability"] = StateMutability.NONPAYABLE if kwargs[ "state_mutability"] == StateMutability.PURE and "nonreentrant" in kwargs: raise StructureException( "Cannot use reentrancy guard on pure functions", node) # call arguments if node.args.defaults and node.name == "__init__": raise FunctionDeclarationException( "Constructor may not use default arguments", node.args.defaults[0]) arguments = OrderedDict() max_arg_count = len(node.args.args) min_arg_count = max_arg_count - len(node.args.defaults) defaults = [None] * min_arg_count + node.args.defaults namespace = get_namespace() for arg, value in zip(node.args.args, defaults): if arg.arg in ("gas", "value", "skip_contract_check", "default_return_value"): raise ArgumentException( f"Cannot use '{arg.arg}' as a variable name in a function input", arg) if arg.arg in arguments: raise ArgumentException( f"Function contains multiple inputs named {arg.arg}", arg) if arg.arg in namespace: raise NamespaceCollision(arg.arg, arg) if arg.annotation is None: raise ArgumentException( f"Function argument '{arg.arg}' is missing a type", arg) type_definition = get_type_from_annotation( arg.annotation, location=DataLocation.CALLDATA, is_constant=True) if value is not None: if not check_kwargable(value): raise StateAccessViolation( "Value must be literal or environment variable", value) validate_expected_type(value, type_definition) arguments[arg.arg] = type_definition # return types if node.returns is None: return_type = None elif node.name == "__init__": raise FunctionDeclarationException( "Constructor may not have a return type", node.returns) elif isinstance(node.returns, (vy_ast.Name, vy_ast.Call, vy_ast.Subscript)): return_type = get_type_from_annotation( node.returns, location=DataLocation.MEMORY) elif isinstance(node.returns, vy_ast.Tuple): tuple_types: Tuple = () for n in node.returns.elements: tuple_types += (get_type_from_annotation( n, location=DataLocation.MEMORY), ) return_type = TupleDefinition(tuple_types) else: raise InvalidType( "Function return value must be a type name or tuple", node.returns) return cls(node.name, arguments, min_arg_count, max_arg_count, return_type, **kwargs)
def parse_tree_to_lll(global_ctx: GlobalContext) -> Tuple[LLLnode, LLLnode]: _names_def = [_def.name for _def in global_ctx._defs] # Checks for duplicate function names if len(set(_names_def)) < len(_names_def): raise FunctionDeclarationException( "Duplicate function name: " f"{[name for name in _names_def if _names_def.count(name) > 1][0]}" ) _names_events = [_event.name for _event in global_ctx._events] # Checks for duplicate event names if len(set(_names_events)) < len(_names_events): raise EventDeclarationException(f"""Duplicate event name: {[name for name in _names_events if _names_events.count(name) > 1][0]}""" ) # Initialization function initfunc = [_def for _def in global_ctx._defs if is_initializer(_def)] # Default function defaultfunc = [_def for _def in global_ctx._defs if is_default_func(_def)] # Regular functions otherfuncs = [ _def for _def in global_ctx._defs if not is_initializer(_def) and not is_default_func(_def) ] # check if any functions in the contract are payable - if not, we do a single # ASSERT CALLVALUE ISZERO at the start of the bytecode rather than at the start # of each function is_contract_payable = next( (True for i in global_ctx._defs if FunctionSignature.from_definition( i, custom_structs=global_ctx._structs).mutability == "payable"), False, ) sigs: dict = {} external_interfaces: dict = {} # Create the main statement o = ["seq"] if global_ctx._contracts or global_ctx._interfaces: external_interfaces = parse_external_interfaces( external_interfaces, global_ctx) # If there is an init func... if initfunc: o.append(init_func_init_lll()) o.append( parse_function( initfunc[0], { **{ "self": sigs }, **external_interfaces }, global_ctx, False, )) # If there are regular functions... if otherfuncs or defaultfunc: o, runtime = parse_other_functions( o, otherfuncs, sigs, external_interfaces, global_ctx, defaultfunc, is_contract_payable, ) else: runtime = o.copy() if not is_contract_payable: # if no functions in the contract are payable, assert that callvalue is # zero at the beginning of the bytecode runtime.insert(1, ["assert", ["iszero", "callvalue"]]) return LLLnode.from_list(o, typ=None), LLLnode.from_list(runtime, typ=None)
def external_contract_call(node, context, contract_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' if not contract_name: raise StructureException( f'Invalid external contract call "{node.func.attr}".', node) if contract_name not in context.sigs: raise VariableDeclarationException( f'Contract "{contract_name}" not declared yet', node) if contract_address.value == "address": raise StructureException(f"External calls to self are not permitted.", node) method_name = node.func.attr if method_name not in context.sigs[contract_name]: raise FunctionDeclarationException(( f"Function not declared yet: {method_name} (reminder: " "function must be declared in the correct contract)" f"The available methods are: {','.join(context.sigs[contract_name].keys())}" ), node.func) sig = context.sigs[contract_name][method_name] inargs, inargsize, _ = pack_arguments( sig, [Expr(arg, context).lll_node for arg in node.args], context, node.func, ) output_placeholder, output_size, returner = get_external_contract_call_output( sig, context) sub = [ 'seq', ['assert', ['extcodesize', contract_address]], ['assert', ['ne', 'address', contract_address]], ] if context.is_constant() and not sig.const: raise ConstancyViolationException( f"May not call non-constant function '{method_name}' within {context.pp_constancy()}." " For asserting the result of modifiable contract calls, try assert_modifiable.", node) if context.is_constant() or sig.const: 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, ] ]) sub.extend(returner) o = LLLnode.from_list(sub, typ=sig.output_type, location='memory', pos=getpos(node)) return o
def from_FunctionDef( cls, node: vy_ast.FunctionDef, is_interface: Optional[bool] = False, ) -> "ContractFunction": """ Generate a `ContractFunction` object from a `FunctionDef` node. Arguments --------- node : FunctionDef Vyper ast node to generate the function definition from. is_interface: bool, optional Boolean indicating if the function definition is part of an interface. Returns ------- ContractFunction """ kwargs: Dict[str, Any] = {} if is_interface: # FunctionDef with stateMutability in body (Interface defintions) if (len(node.body) == 1 and isinstance(node.body[0], vy_ast.Expr) and isinstance(node.body[0].value, vy_ast.Name) and StateMutability.is_valid_value(node.body[0].value.id)): # Interfaces are always public kwargs["function_visibility"] = FunctionVisibility.EXTERNAL kwargs["state_mutability"] = StateMutability( node.body[0].value.id) elif len(node.body) == 1 and node.body[0].get("value.id") in ( "constant", "modifying"): if node.body[0].value.id == "constant": expected = "view or pure" else: expected = "payable or nonpayable" raise StructureException( f"State mutability should be set to {expected}", node.body[0]) else: raise StructureException( "Body must only contain state mutability label", node.body[0]) else: # FunctionDef with decorators (normal functions) for decorator in node.decorator_list: if isinstance(decorator, vy_ast.Call): if "nonreentrant" in kwargs: raise StructureException( "nonreentrant decorator is already set with key: " f"{kwargs['nonreentrant']}", node, ) if decorator.get("func.id") != "nonreentrant": raise StructureException("Decorator is not callable", decorator) if len(decorator.args) != 1 or not isinstance( decorator.args[0], vy_ast.Str): raise StructureException( "@nonreentrant name must be given as a single string literal", decorator, ) kwargs["nonreentrant"] = decorator.args[0].value elif isinstance(decorator, vy_ast.Name): if FunctionVisibility.is_valid_value(decorator.id): if "function_visibility" in kwargs: raise FunctionDeclarationException( f"Visibility is already set to: {kwargs['function_visibility']}", node, ) kwargs["function_visibility"] = FunctionVisibility( decorator.id) elif StateMutability.is_valid_value(decorator.id): if "state_mutability" in kwargs: raise FunctionDeclarationException( f"Mutability is already set to: {kwargs['state_mutability']}", node) kwargs["state_mutability"] = StateMutability( decorator.id) else: if decorator.id == "constant": warnings.warn( "'@constant' decorator has been removed (see VIP2040). " "Use `@view` instead.", DeprecationWarning, ) raise FunctionDeclarationException( f"Unknown decorator: {decorator.id}", decorator) else: raise StructureException("Bad decorator syntax", decorator) if "function_visibility" not in kwargs: raise FunctionDeclarationException( f"Visibility must be set to one of: {', '.join(FunctionVisibility.values())}", node) if "state_mutability" not in kwargs: # Assume nonpayable if not set at all (cannot accept Ether, but can modify state) kwargs["state_mutability"] = StateMutability.NONPAYABLE # call arguments arg_count: Union[Tuple[int, int], int] = len(node.args.args) if node.args.defaults: arg_count = ( len(node.args.args) - len(node.args.defaults), len(node.args.args), ) arguments = OrderedDict() defaults = [None] * (len(node.args.args) - len(node.args.defaults)) + node.args.defaults namespace = get_namespace() for arg, value in zip(node.args.args, defaults): if arg.arg in ("gas", "value"): raise ArgumentException( f"Cannot use '{arg.arg}' as a variable name in a function input", arg, ) if arg.arg in arguments: raise ArgumentException( f"Function contains multiple inputs named {arg.arg}", arg) if arg.arg in namespace["self"].members: raise NamespaceCollision( "Name shadows an existing storage-scoped value", arg) if arg.arg in namespace: raise NamespaceCollision(arg.arg, arg) if arg.annotation is None: raise ArgumentException( f"Function argument '{arg.arg}' is missing a type", arg) type_definition = get_type_from_annotation( arg.annotation, location=DataLocation.CALLDATA, is_immutable=True) if isinstance( type_definition, StructDefinition) and type_definition.is_dynamic_size: # this is a temporary restriction and should be removed once support for dynamically # sized structs is implemented - https://github.com/vyperlang/vyper/issues/2190 raise ArgumentException( "Struct with dynamically sized data cannot be used as a function input", arg) if value is not None: if not check_constant(value): raise StateAccessViolation( "Value must be literal or environment variable", value) validate_expected_type(value, type_definition) arguments[arg.arg] = type_definition # return types if node.returns is None: return_type = None elif isinstance(node.returns, (vy_ast.Name, vy_ast.Call, vy_ast.Subscript)): return_type = get_type_from_annotation( node.returns, location=DataLocation.MEMORY) elif isinstance(node.returns, vy_ast.Tuple): tuple_types: Tuple = () for n in node.returns.elements: tuple_types += (get_type_from_annotation( n, location=DataLocation.MEMORY), ) return_type = TupleDefinition(tuple_types) else: raise InvalidType( "Function return value must be a type name or tuple", node.returns) return cls(node.name, arguments, arg_count, return_type, **kwargs)
def parse_public_function(code: ast.FunctionDef, sig: FunctionSignature, context: Context) -> LLLnode: """ Parse a public function (FuncDef), and produce full function body. :param sig: the FuntionSignature :param code: ast of function :return: full sig compare & function body """ validate_public_function(code, sig, context.global_ctx) # Get nonreentrant lock nonreentrant_pre, nonreentrant_post = get_nonreentrant_lock( sig, context.global_ctx) 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.increase_memory(sig.max_copy_size) clampers.append(copier) # Add asserts for payable and internal if not sig.payable: 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.increase_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. default_arg_pos, _ = context.memory_allocator.increase_memory( 32) 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(): if len(sig.args) > 0: raise FunctionDeclarationException( 'Default function may not receive any arguments.', code) o = LLLnode.from_list( ['seq'] + clampers + [parse_body(code.body, context)], # 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_public_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. o = LLLnode.from_list( [ 'seq', sig_chain, [ 'if', 0, # can only be jumped into [ 'seq', ['label', function_routine ], ['seq'] + nonreentrant_pre + clampers + [parse_body(c, context) for c in code.body] + nonreentrant_post + [['stop']] ], ], ], 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