def new_variable(self, name, typ, pos=None): if self.is_valid_varname(name, pos): self.vars[name] = VariableRecord(name, self.next_mem, typ, True, self.blockscopes.copy()) pos = self.next_mem self.next_mem += 32 * get_size_of_type(typ) return pos
def new_variable(self, name, typ): if not is_varname_valid(name, custom_units=self.custom_units): raise VariableDeclarationException("Variable name invalid or reserved: " + name) if name in self.vars or name in self.globals: raise VariableDeclarationException("Duplicate variable name: %s" % name) self.vars[name] = VariableRecord(name, self.next_mem, typ, True, self.blockscopes.copy()) pos = self.next_mem self.next_mem += 32 * get_size_of_type(typ) return pos
def from_declaration(cls, code, custom_units=None): name = code.target.id pos = 0 if not is_varname_valid(name, custom_units=custom_units): raise EventDeclarationException("Event name invalid: " + name) # Determine the arguments, expects something of the form def foo(arg1: num, arg2: num ... args = [] indexed_list = [] topics_count = 1 if code.annotation.args: keys = code.annotation.args[0].keys values = code.annotation.args[0].values for i in range(len(keys)): typ = values[i] arg = keys[i].id is_indexed = False # Check to see if argument is a topic if isinstance(typ, ast.Call) and typ.func.id == 'indexed': typ = values[i].args[0] indexed_list.append(True) topics_count += 1 is_indexed = True else: indexed_list.append(False) if isinstance(typ, ast.Subscript) and getattr( typ.value, 'id', None ) == 'bytes' and typ.slice.value.n > 32 and is_indexed: raise EventDeclarationException( "Indexed arguments are limited to 32 bytes") if topics_count > 4: raise EventDeclarationException( "Maximum of 3 topics {} given".format(topics_count - 1), arg) if not isinstance(arg, str): raise VariableDeclarationException("Argument name invalid", arg) if not typ: raise InvalidTypeException("Argument must have type", arg) if not is_varname_valid(arg, custom_units): raise VariableDeclarationException( "Argument name invalid or reserved: " + arg, arg) if arg in (x.name for x in args): raise VariableDeclarationException( "Duplicate function argument name: " + arg, arg) parsed_type = parse_type(typ, None, custom_units=custom_units) args.append(VariableRecord(arg, pos, parsed_type, False)) if isinstance(parsed_type, ByteArrayType): pos += ceil32(typ.slice.value.n) else: pos += get_size_of_type(parsed_type) * 32 sig = name + '(' + ','.join([ canonicalize_type(arg.typ, indexed_list[pos]) for pos, arg in enumerate(args) ]) + ')' # noqa F812 event_id = bytes_to_int(sha3(bytes(sig, 'utf-8'))) return cls(name, args, indexed_list, event_id, sig)
def add_globals_and_events(self, item): item_attributes = {"public": False} if self._nonrentrant_counter: raise CompilerPanic("Re-entrancy lock was set before all storage slots were defined") # Make sure we have a valid variable name. if not isinstance(item.target, vy_ast.Name): raise StructureException("Invalid global variable name", item.target) # Handle constants. if self.get_call_func_name(item) == "constant": return item_name, item_attributes = self.get_item_name_and_attributes(item, item_attributes) if item_name in self._contracts or item_name in self._interfaces: if self.get_call_func_name(item) == "address": raise StructureException( f"Persistent address({item_name}) style contract declarations " "are not support anymore." f" Use {item.target.id}: {item_name} instead" ) self._globals[item.target.id] = ContractRecord( item.target.id, len(self._globals), InterfaceType(item_name), True, ) elif self.get_call_func_name(item) == "public": if isinstance(item.annotation.args[0], vy_ast.Name) and item_name in self._contracts: typ = InterfaceType(item_name) else: typ = parse_type(item.annotation.args[0], "storage", custom_structs=self._structs,) self._globals[item.target.id] = VariableRecord( item.target.id, len(self._globals), typ, True, ) elif isinstance(item.annotation, (vy_ast.Name, vy_ast.Call, vy_ast.Subscript)): self._globals[item.target.id] = VariableRecord( item.target.id, len(self._globals), parse_type(item.annotation, "storage", custom_structs=self._structs,), True, ) else: raise InvalidType("Invalid global type specified", item)
def sqrt(expr, args, kwargs, context): from vyper.functions.utils import ( generate_inline_function, ) arg = args[0] sqrt_code = """ assert x >= 0.0 z: decimal if x == 0.0: z = 0.0 else: z = (x + 1.0) / 2.0 y: decimal = x for i in range(256): if z == y: break y = z z = (x / z + z) / 2.0 """ x_type = BaseType('decimal') placeholder_copy = ['pass'] # Steal current position if variable is already allocated. if arg.value == 'mload': new_var_pos = arg.args[0] # Other locations need to be copied. else: new_var_pos = context.new_placeholder(x_type) placeholder_copy = ['mstore', new_var_pos, arg] # Create input variables. variables = { 'x': VariableRecord( name='x', pos=new_var_pos, typ=x_type, mutable=False ) } # Generate inline LLL. new_ctx, sqrt_lll = generate_inline_function( code=sqrt_code, variables=variables, memory_allocator=context.memory_allocator ) return LLLnode.from_list( [ 'seq_unchecked', placeholder_copy, # load x variable sqrt_lll, ['mload', new_ctx.vars['z'].pos] # unload z variable into the stack, ], typ=BaseType('decimal'), pos=getpos(expr), )
def new_variable(self, name, typ, internal_var=False, pos=None): # mangle internally generated variables so they cannot collide # with user variables. if internal_var: name = self._mangle(name) if internal_var or self.is_valid_varname(name, pos): var_size = 32 * get_size_of_type(typ) var_pos, _ = self.memory_allocator.increase_memory(var_size) self.vars[name] = VariableRecord( name=name, pos=var_pos, typ=typ, mutable=True, blockscopes=self.blockscopes.copy(), ) return var_pos
def new_variable(self, name, typ, pos=None): if self.is_valid_varname(name, pos): var_size = 32 * get_size_of_type(typ) var_pos, _ = self.memory_allocator.increase_memory(var_size) self.vars[name] = VariableRecord( name, var_pos, typ, True, self.blockscopes.copy(), ) return var_pos
def from_declaration(cls, class_node, global_ctx): name = class_node.name pos = 0 check_valid_varname( name, global_ctx._structs, global_ctx._constants, pos=class_node, error_prefix="Event name invalid. ", exc=EventDeclarationException, ) args = [] indexed_list = [] if len(class_node.body) != 1 or not isinstance(class_node.body[0], vy_ast.Pass): for node in class_node.body: arg_item = node.target arg = node.target.id typ = node.annotation if isinstance(typ, vy_ast.Call) and typ.get("func.id") == "indexed": indexed_list.append(True) typ = typ.args[0] else: indexed_list.append(False) check_valid_varname( arg, global_ctx._structs, global_ctx._constants, pos=arg_item, error_prefix="Event argument name invalid or reserved.", ) if arg in (x.name for x in args): raise TypeCheckFailure( f"Duplicate function argument name: {arg}") # Can struct be logged? parsed_type = global_ctx.parse_type(typ, None) args.append(VariableRecord(arg, pos, parsed_type, False)) if isinstance(parsed_type, ByteArrayType): pos += ceil32(typ.slice.value.n) else: pos += get_size_of_type(parsed_type) * 32 sig = (name + "(" + ",".join([ canonicalize_type(arg.typ, indexed_list[pos]) for pos, arg in enumerate(args) ]) + ")") # noqa F812 event_id = bytes_to_int(keccak256(bytes(sig, "utf-8"))) return cls(name, args, indexed_list, event_id, sig)
def _new_variable(self, name: str, typ: NodeType, var_size: int, is_internal: bool) -> int: if is_internal: var_pos = self.memory_allocator.expand_memory(var_size) else: var_pos = self.memory_allocator.allocate_memory(var_size) self.vars[name] = VariableRecord( name=name, pos=var_pos, typ=typ, mutable=True, blockscopes=self._scopes.copy(), is_internal=is_internal, ) return var_pos
def from_declaration(cls, code, global_ctx): name = code.target.id pos = 0 check_valid_varname(name, global_ctx._custom_units, global_ctx._structs, global_ctx._constants, pos=code, error_prefix="Event name invalid. ", exc=EventDeclarationException) # Determine the arguments, expects something of the form def foo(arg1: num, arg2: num ... args = [] indexed_list = [] topics_count = 1 if code.annotation.args: keys = code.annotation.args[0].keys values = code.annotation.args[0].values for i in range(len(keys)): typ = values[i] if not isinstance(keys[i], ast.Name): raise EventDeclarationException( 'Invalid key type, expected a valid name.', keys[i], ) if not isinstance(typ, (ast.Name, ast.Call, ast.Subscript)): raise EventDeclarationException( 'Invalid event argument type.', typ) if isinstance(typ, ast.Call) and not isinstance(typ.func, ast.Name): raise EventDeclarationException( 'Invalid event argument type', typ) arg = keys[i].id arg_item = keys[i] is_indexed = False # Check to see if argument is a topic if isinstance(typ, ast.Call) and typ.func.id == 'indexed': typ = values[i].args[0] indexed_list.append(True) topics_count += 1 is_indexed = True else: indexed_list.append(False) if isinstance(typ, ast.Subscript) and getattr( typ.value, 'id', None ) == 'bytes' and typ.slice.value.n > 32 and is_indexed: # noqa: E501 raise EventDeclarationException( "Indexed arguments are limited to 32 bytes") if topics_count > 4: raise EventDeclarationException( f"Maximum of 3 topics {topics_count - 1} given", arg, ) if not isinstance(arg, str): raise VariableDeclarationException("Argument name invalid", arg) if not typ: raise InvalidTypeException("Argument must have type", arg) check_valid_varname( arg, global_ctx._custom_units, global_ctx._structs, global_ctx._constants, pos=arg_item, error_prefix="Event argument name invalid or reserved.", ) if arg in (x.name for x in args): raise VariableDeclarationException( "Duplicate function argument name: " + arg, arg_item, ) # Can struct be logged? parsed_type = global_ctx.parse_type(typ, None) args.append(VariableRecord(arg, pos, parsed_type, False)) if isinstance(parsed_type, ByteArrayType): pos += ceil32(typ.slice.value.n) else: pos += get_size_of_type(parsed_type) * 32 sig = name + '(' + ','.join([ canonicalize_type(arg.typ, indexed_list[pos]) for pos, arg in enumerate(args) ]) + ')' # noqa F812 event_id = bytes_to_int(keccak256(bytes(sig, 'utf-8'))) return cls(name, args, indexed_list, event_id, sig)
def add_globals_and_events(self, item): item_attributes = {"public": False} # Make sure we have a valid variable name. if not isinstance(item.target, ast.Name): raise StructureException('Invalid global variable name', item.target) # Handle constants. if self.get_call_func_name(item) == "constant": self._constants.add_constant(item, global_ctx=self) return # Handle events. if not (self.get_call_func_name(item) == "event"): item_name, item_attributes = self.get_item_name_and_attributes( item, item_attributes) if not all([ attr in valid_global_keywords for attr in item_attributes.keys() ]): raise StructureException( 'Invalid global keyword used: %s' % item_attributes, item) if item.value is not None: raise StructureException( 'May not assign value whilst defining type', item) elif self.get_call_func_name(item) == "event": if self._globals or len(self._defs): raise EventDeclarationException( "Events must all come before global declarations and function definitions", item) self._events.append(item) elif not isinstance(item.target, ast.Name): raise StructureException( "Can only assign type to variable in top-level statement", item) # Is this a custom unit definition. elif item.target.id == 'units': if not self._custom_units: if not isinstance(item.annotation, ast.Dict): raise VariableDeclarationException( "Define custom units using units: { }.", item.target) for key, value in zip(item.annotation.keys, item.annotation.values): if not isinstance(value, ast.Str): raise VariableDeclarationException( "Custom unit description must be a valid string", value) if not isinstance(key, ast.Name): raise VariableDeclarationException( "Custom unit name must be a valid string", key) check_valid_varname(key.id, self._custom_units, self._structs, self._constants, key, "Custom unit invalid.") self._custom_units.add(key.id) self._custom_units_descriptions[key.id] = value.s else: raise VariableDeclarationException( "Custom units can only be defined once", item.target) # Check if variable name is valid. # Don't move this check higher, as unit parsing has to happen first. elif not self.is_valid_varname(item.target.id, item): pass elif len(self._defs): raise StructureException( "Global variables must all come before function definitions", item) # If the type declaration is of the form public(<type here>), then proceed with # the underlying type but also add getters elif self.get_call_func_name(item) == "address": if item.annotation.args[0].id not in premade_contracts: raise VariableDeclarationException( "Unsupported premade contract declaration", item.annotation.args[0]) premade_contract = premade_contracts[item.annotation.args[0].id] self._contracts[item.target.id] = self.make_contract( premade_contract.body) self._globals[item.target.id] = VariableRecord( item.target.id, len(self._globals), BaseType('address'), True) elif item_name in self._contracts: self._globals[item.target.id] = ContractRecord( item.target.id, len(self._globals), ContractType(item_name), True) if item_attributes["public"]: typ = ContractType(item_name) for getter in self.mk_getter(item.target.id, typ): self._getters.append( self.parse_line('\n' * (item.lineno - 1) + getter)) self._getters[-1].pos = getpos(item) elif self.get_call_func_name(item) == "public": if isinstance(item.annotation.args[0], ast.Name) and item_name in self._contracts: typ = ContractType(item_name) else: typ = parse_type(item.annotation.args[0], 'storage', custom_units=self._custom_units, custom_structs=self._structs, constants=self._constants) self._globals[item.target.id] = VariableRecord( item.target.id, len(self._globals), typ, True) # Adding getters here for getter in self.mk_getter(item.target.id, typ): self._getters.append( self.parse_line('\n' * (item.lineno - 1) + getter)) self._getters[-1].pos = getpos(item) elif isinstance(item.annotation, (ast.Name, ast.Call, ast.Subscript)): self._globals[item.target.id] = VariableRecord( item.target.id, len(self._globals), parse_type(item.annotation, 'storage', custom_units=self._custom_units, custom_structs=self._structs, constants=self._constants), True) else: raise InvalidTypeException('Invalid global type specified', item)
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 add_globals_and_events(_custom_units, _contracts, _defs, _events, _getters, _globals, item): item_attributes = {"public": False} if not (isinstance(item.annotation, ast.Call) and item.annotation.func.id == "event"): item_name, item_attributes = get_item_name_and_attributes(item, item_attributes) if not all([attr in valid_global_keywords for attr in item_attributes.keys()]): raise StructureException('Invalid global keyword used: %s' % item_attributes, item) if item.value is not None: raise StructureException('May not assign value whilst defining type', item) elif isinstance(item.annotation, ast.Call) and item.annotation.func.id == "event": if _globals or len(_defs): raise EventDeclarationException("Events must all come before global declarations and function definitions", item) _events.append(item) elif not isinstance(item.target, ast.Name): raise StructureException("Can only assign type to variable in top-level statement", item) # Is this a custom unit definition. elif item.target.id == 'units': if not _custom_units: if not isinstance(item.annotation, ast.Dict): raise VariableDeclarationException("Define custom units using units: { }.", item.target) for key, value in zip(item.annotation.keys, item.annotation.values): if not isinstance(value, ast.Str): raise VariableDeclarationException("Custom unit description must be a valid string.", value) if not isinstance(key, ast.Name): raise VariableDeclarationException("Custom unit name must be a valid string unquoted string.", key) if key.id in _custom_units: raise VariableDeclarationException("Custom unit may only be defined once", key) if not is_varname_valid(key.id, custom_units=_custom_units): raise VariableDeclarationException("Custom unit may not be a reserved keyword", key) _custom_units.append(key.id) else: raise VariableDeclarationException("Can units can only defined once.", item.target) # Check if variable name is reserved or invalid elif not is_varname_valid(item.target.id, custom_units=_custom_units): raise VariableDeclarationException("Variable name invalid or reserved: ", item.target) # Check if global already exists, if so error elif item.target.id in _globals: raise VariableDeclarationException("Cannot declare a persistent variable twice!", item.target) elif len(_defs): raise StructureException("Global variables must all come before function definitions", item) # If the type declaration is of the form public(<type here>), then proceed with # the underlying type but also add getters elif isinstance(item.annotation, ast.Call) and item.annotation.func.id == "address": if item.annotation.args[0].id not in premade_contracts: raise VariableDeclarationException("Unsupported premade contract declaration", item.annotation.args[0]) premade_contract = premade_contracts[item.annotation.args[0].id] _contracts[item.target.id] = add_contract(premade_contract.body) _globals[item.target.id] = VariableRecord(item.target.id, len(_globals), BaseType('address'), True) elif item_name in _contracts: _globals[item.target.id] = ContractRecord(item.target.id, len(_globals), ContractType(item_name), True) if item_attributes["public"]: typ = ContractType(item_name) for getter in mk_getter(item.target.id, typ): _getters.append(parse_line('\n' * (item.lineno - 1) + getter)) _getters[-1].pos = getpos(item) elif isinstance(item.annotation, ast.Call) and item.annotation.func.id == "public": if isinstance(item.annotation.args[0], ast.Name) and item_name in _contracts: typ = ContractType(item_name) else: typ = parse_type(item.annotation.args[0], 'storage', custom_units=_custom_units) _globals[item.target.id] = VariableRecord(item.target.id, len(_globals), typ, True) # Adding getters here for getter in mk_getter(item.target.id, typ): _getters.append(parse_line('\n' * (item.lineno - 1) + getter)) _getters[-1].pos = getpos(item) else: _globals[item.target.id] = VariableRecord( item.target.id, len(_globals), parse_type(item.annotation, 'storage', custom_units=_custom_units), True ) return _custom_units, _contracts, _events, _globals, _getters
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 add_globals_and_events(self, item): item_attributes = {"public": False} if len(self._globals) > NONRENTRANT_STORAGE_OFFSET: raise ParserException( f"Too many globals defined, only {NONRENTRANT_STORAGE_OFFSET} globals are allowed", item, ) # Make sure we have a valid variable name. if not isinstance(item.target, ast.Name): raise StructureException('Invalid global variable name', item.target) # Handle constants. if self.get_call_func_name(item) == "constant": self._constants.add_constant(item, global_ctx=self) return # Handle events. if not (self.get_call_func_name(item) == "event"): item_name, item_attributes = self.get_item_name_and_attributes( item, item_attributes) if not all([ attr in VALID_GLOBAL_KEYWORDS for attr in item_attributes.keys() ]): raise StructureException( f'Invalid global keyword used: {item_attributes}', item) if item.value is not None: raise StructureException( 'May not assign value whilst defining type', item) elif self.get_call_func_name(item) == "event": if self._globals or len(self._defs): raise EventDeclarationException( "Events must all come before global declarations and function definitions", item) self._events.append(item) elif not isinstance(item.target, ast.Name): raise StructureException( "Can only assign type to variable in top-level statement", item) # Is this a custom unit definition. elif item.target.id == 'units': if not self._custom_units: if not isinstance(item.annotation, ast.Dict): raise VariableDeclarationException( "Define custom units using units: { }.", item.target) for key, value in zip(item.annotation.keys, item.annotation.values): if not isinstance(value, ast.Str): raise VariableDeclarationException( "Custom unit description must be a valid string", value) if not isinstance(key, ast.Name): raise VariableDeclarationException( "Custom unit name must be a valid string", key) check_valid_varname(key.id, self._custom_units, self._structs, self._constants, key, "Custom unit invalid.") self._custom_units.add(key.id) self._custom_units_descriptions[key.id] = value.s else: raise VariableDeclarationException( "Custom units can only be defined once", item.target) # Check if variable name is valid. # Don't move this check higher, as unit parsing has to happen first. elif not self.is_valid_varname(item.target.id, item): pass elif len(self._defs): raise StructureException( "Global variables must all come before function definitions", item, ) elif item_name in self._contracts or item_name in self._interfaces: if self.get_call_func_name(item) == "address": raise StructureException( f"Persistent address({item_name}) style contract declarations " "are not support anymore." f" Use {item.target.id}: {item_name} instead") self._globals[item.target.id] = ContractRecord( item.target.id, len(self._globals), ContractType(item_name), True, ) if item_attributes["public"]: typ = ContractType(item_name) for getter in self.mk_getter(item.target.id, typ): self._getters.append( self.parse_line('\n' * (item.lineno - 1) + getter)) self._getters[-1].pos = getpos(item) elif self.get_call_func_name(item) == "public": if isinstance(item.annotation.args[0], ast.Name) and item_name in self._contracts: typ = ContractType(item_name) else: typ = parse_type( item.annotation.args[0], 'storage', custom_units=self._custom_units, custom_structs=self._structs, constants=self._constants, ) self._globals[item.target.id] = VariableRecord( item.target.id, len(self._globals), typ, True, ) # Adding getters here for getter in self.mk_getter(item.target.id, typ): self._getters.append( self.parse_line('\n' * (item.lineno - 1) + getter)) self._getters[-1].pos = getpos(item) elif isinstance(item.annotation, (ast.Name, ast.Call, ast.Subscript)): self._globals[item.target.id] = VariableRecord( item.target.id, len(self._globals), parse_type(item.annotation, 'storage', custom_units=self._custom_units, custom_structs=self._structs, constants=self._constants), True) else: raise InvalidTypeException('Invalid global type specified', item)
def parse_private_function(code: ast.FunctionDef, sig: FunctionSignature, context: Context) -> LLLnode: """ Parse a private function (FuncDef), and produce full function body. :param sig: the FuntionSignature :param code: ast of function :return: full sig compare & function body """ validate_private_function(code, sig) # Get nonreentrant lock nonreentrant_pre, nonreentrant_post = get_nonreentrant_lock( sig, context.global_ctx) # Create callback_ptr, this stores a destination in the bytecode for a private # function to jump to after a function has executed. clampers: List[LLLnode] = [] # Allocate variable space. context.memory_allocator.increase_memory(sig.max_copy_size) _post_callback_ptr = f"{sig.name}_{sig.method_id}_post_callback_ptr" context.callback_ptr = context.new_placeholder(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])) # private 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_private_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.increase_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, ) # Private function copiers. No clamping for private 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_placeholder(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 private_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, private_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 private, # 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_private_arg_copier( memory_dest=var.pos, total_size=_size, )) # Unpack byte array if necessary. if dynamics: i_placeholder = context.new_placeholder( 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', private_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 private functions all variable loading occurs in the default # function sub routine. _clampers = [['label', _post_callback_ptr]] # Function with default parameters. o = LLLnode.from_list( [ 'seq', sig_chain, [ 'if', 0, # can only be jumped into [ 'seq', ['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)) return o return o
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) # 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) # Create a context context = Context(vars=_vars, globals=global_ctx._globals, sigs=sigs, return_type=sig.output_type, is_constant=sig.const, is_payable=sig.payable, origcode=origcode, custom_units=global_ctx._custom_units) # Copy calldata to memory for fixed-size arguments max_copy_size = sum([ 32 if isinstance(arg.typ, ByteArrayType) else get_size_of_type(arg.typ) * 32 for arg in sig.args ]) base_copy_size = sum([ 32 if isinstance(arg.typ, ByteArrayType) else get_size_of_type(arg.typ) * 32 for arg in base_args ]) context.next_mem += max_copy_size if not len(base_args): copier = 'pass' elif sig.name == '__init__': copier = [ 'codecopy', MemoryPositions.RESERVED_MEMORY, '~codelen', base_copy_size ] else: copier = [ 'calldatacopy', MemoryPositions.RESERVED_MEMORY, 4, base_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 variable positions for i, arg in enumerate(sig.args): if i < len(base_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: # Handle default args if present. function_routine = "{}_{}".format(sig.name, sig.method_id) if total_default_args > 0: default_sigs = generate_default_arg_sigs(code, sigs, global_ctx._custom_units) sig_chain = ['seq'] for default_sig_idx, default_sig in enumerate(default_sigs): method_id_node = LLLnode.from_list(default_sig.method_id, pos=getpos(code), annotation='%s' % default_sig.sig) # 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))) # Variables to be populated from calldata copier_arg_count = len(default_sig.args) - len(base_args) default_copiers = [] if copier_arg_count > 0: current_sig_arg_names = {x.name for x in default_sig.args} base_arg_names = {arg.name for arg in base_args} copier_arg_names = current_sig_arg_names - base_arg_names # 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, ByteArrayType) else get_size_of_type(arg.typ) * 32 # Copy set 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_clamper(calldata_offset - 4, var.pos, var.typ)) # Add copying code. if isinstance(var.typ, ByteArrayType): default_copiers.append([ 'calldatacopy', var.pos, ['add', 4, ['calldataload', calldata_offset]], var.size * 32 ]) else: default_copiers.append([ 'calldatacopy', var.pos, calldata_offset, var.size * 32 ]) sig_chain.append([ 'if', ['eq', ['mload', 0], method_id_node], [ 'seq', ['seq'] + set_defaults if set_defaults else ['pass'], ['seq'] + default_copiers if default_copiers else ['pass'], ['goto', function_routine] ] ]) o = LLLnode.from_list( [ 'seq', sig_chain, [ 'if', 0, # can only be jumped into [ 'seq', ['label', function_routine ], ['seq'] + clampers + [parse_body(c, context) for c in code.body] + ['stop'] ] ] ], typ=None, pos=getpos(code)) else: # Function without default parameters. method_id_node = LLLnode.from_list(sig.method_id, pos=getpos(code), annotation='%s' % sig.sig) 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 add_globals_and_events(self, item): item_attributes = {"public": False} if len(self._globals) > NONRENTRANT_STORAGE_OFFSET: raise StructureException( f"Too many globals defined, only {NONRENTRANT_STORAGE_OFFSET} globals are allowed", item, ) # Make sure we have a valid variable name. if not isinstance(item.target, vy_ast.Name): raise StructureException("Invalid global variable name", item.target) # Handle constants. if self.get_call_func_name(item) == "constant": self._constants.add_constant(item, global_ctx=self) return item_name, item_attributes = self.get_item_name_and_attributes( item, item_attributes) if not all( [attr in VALID_GLOBAL_KEYWORDS for attr in item_attributes.keys()]): raise StructureException( f"Invalid global keyword used: {item_attributes}", item) self.is_valid_varname(item.target.id, item) if item_name in self._contracts or item_name in self._interfaces: if self.get_call_func_name(item) == "address": raise StructureException( f"Persistent address({item_name}) style contract declarations " "are not support anymore." f" Use {item.target.id}: {item_name} instead") self._globals[item.target.id] = ContractRecord( item.target.id, len(self._globals), InterfaceType(item_name), True, ) if item_attributes["public"]: typ = InterfaceType(item_name) for getter in self.mk_getter(item.target.id, typ): self._getters.append( self.parse_line("\n" * (item.lineno - 1) + getter)) self._getters[-1].pos = getpos(item) set_offsets(self._getters[-1], self._getters[-1].pos) elif self.get_call_func_name(item) == "public": if isinstance(item.annotation.args[0], vy_ast.Name) and item_name in self._contracts: typ = InterfaceType(item_name) else: typ = parse_type( item.annotation.args[0], "storage", custom_structs=self._structs, constants=self._constants, ) self._globals[item.target.id] = VariableRecord( item.target.id, len(self._globals), typ, True, ) # Adding getters here for getter in self.mk_getter(item.target.id, typ): self._getters.append( self.parse_line("\n" * (item.lineno - 1) + getter)) self._getters[-1].pos = getpos(item) set_offsets(self._getters[-1], self._getters[-1].pos) elif isinstance(item.annotation, (vy_ast.Name, vy_ast.Call, vy_ast.Subscript)): self._globals[item.target.id] = VariableRecord( item.target.id, len(self._globals), parse_type( item.annotation, "storage", custom_structs=self._structs, constants=self._constants, ), True, ) else: raise InvalidType("Invalid global type specified", item)