def assign(self): # Assignment (e.g. x[4] = y) if len(self.stmt.targets) != 1: raise StructureException("Assignment statement must have one target", self.stmt) self.context.set_in_assignment(True) sub = Expr(self.stmt.value, self.context).lll_node # Disallow assignment to None if isinstance(sub.typ, NullType): raise InvalidLiteralException('Assignment to None is not allowed, use a default value or built-in `clear()`.', self.stmt) # Determine if it's an RLPList assignment. if isinstance(self.stmt.value, ast.Call) and getattr(self.stmt.value.func, 'id', '') is 'RLPList': pos = self.context.new_variable(self.stmt.targets[0].id, sub.typ) variable_loc = LLLnode.from_list(pos, typ=sub.typ, location='memory', pos=getpos(self.stmt), annotation=self.stmt.targets[0].id) o = make_setter(variable_loc, sub, 'memory', pos=getpos(self.stmt)) # All other assignments are forbidden. elif isinstance(self.stmt.targets[0], ast.Name) and self.stmt.targets[0].id not in self.context.vars: raise VariableDeclarationException("Variable type not defined", self.stmt) elif isinstance(self.stmt.targets[0], ast.Tuple) and isinstance(self.stmt.value, ast.Tuple): raise VariableDeclarationException("Tuple to tuple assignment not supported", self.stmt) else: # Checks to see if assignment is valid target = self.get_target(self.stmt.targets[0]) o = make_setter(target, sub, target.location, pos=getpos(self.stmt)) o.pos = getpos(self.stmt) self.context.set_in_assignment(False) return o
def assign(self): # Assignment (e.g. x[4] = y) if len(self.stmt.targets) != 1: raise StructureException( "Assignment statement must have one target", self.stmt) with self.context.assignment_scope(): sub = Expr(self.stmt.value, self.context).lll_node # Disallow assignment to None if isinstance(sub.typ, NullType): raise InvalidLiteralException( 'Assignment to None is not allowed, use a default value or built-in `clear()`.', self.stmt) # Determine if it's an RLPList assignment. if isinstance(self.stmt.value, ast.Call) and getattr( self.stmt.value.func, 'id', '') == 'RLPList': pos = self.context.new_variable(self.stmt.targets[0].id, sub.typ) variable_loc = LLLnode.from_list( pos, typ=sub.typ, location='memory', pos=getpos(self.stmt), annotation=self.stmt.targets[0].id) o = make_setter(variable_loc, sub, 'memory', pos=getpos(self.stmt)) else: # Error check when assigning to declared variable if isinstance(self.stmt.targets[0], ast.Name): # Do not allow assignment to undefined variables without annotation if self.stmt.targets[0].id not in self.context.vars: raise VariableDeclarationException( "Variable type not defined", self.stmt) # Check against implicit conversion self._check_implicit_conversion(self.stmt.targets[0].id, sub) # Do no allow tuple-to-tuple assignment if isinstance(self.stmt.targets[0], ast.Tuple) and isinstance( self.stmt.value, ast.Tuple): raise VariableDeclarationException( "Tuple to tuple assignment not supported", self.stmt) # Checks to see if assignment is valid target = self.get_target(self.stmt.targets[0]) o = make_setter(target, sub, target.location, pos=getpos(self.stmt)) o.pos = getpos(self.stmt) return o
def assign(self): # Assignment (e.g. x[4] = y) with self.context.assignment_scope(): sub = Expr(self.stmt.value, self.context).lll_node is_valid_rlp_list_assign = ( isinstance(self.stmt.value, vy_ast.Call) ) and getattr(self.stmt.value.func, 'id', '') == 'RLPList' # Determine if it's an RLPList assignment. if is_valid_rlp_list_assign: pos = self.context.new_variable(self.stmt.target.id, sub.typ) variable_loc = LLLnode.from_list( pos, typ=sub.typ, location='memory', pos=getpos(self.stmt), annotation=self.stmt.target.id, ) o = make_setter(variable_loc, sub, 'memory', pos=getpos(self.stmt)) else: # Error check when assigning to declared variable if isinstance(self.stmt.target, vy_ast.Name): # Do not allow assignment to undefined variables without annotation if self.stmt.target.id not in self.context.vars: raise VariableDeclarationException("Variable type not defined", self.stmt) # Check against implicit conversion self._check_implicit_conversion(self.stmt.target.id, sub) is_valid_tuple_assign = ( isinstance(self.stmt.target, vy_ast.Tuple) ) and isinstance(self.stmt.value, vy_ast.Tuple) # Do no allow tuple-to-tuple assignment if is_valid_tuple_assign: raise VariableDeclarationException( "Tuple to tuple assignment not supported", self.stmt, ) # Checks to see if assignment is valid target = self.get_target(self.stmt.target) if isinstance(target.typ, ContractType) and not isinstance(sub.typ, ContractType): raise TypeMismatch( 'Contract assignment expects casted address: ' f'{target.typ}(<address_var>)', self.stmt ) o = make_setter(target, sub, target.location, pos=getpos(self.stmt)) o.pos = getpos(self.stmt) return o
def abi_decode(lll_node, src, pos=None): os = o_list(lll_node, pos=pos) lll_ret = ["seq"] parent_abi_t = abi_type_of(lll_node.typ) for i, o in enumerate(os): abi_t = abi_type_of(o.typ) src_loc = LLLnode("src_loc", typ=o.typ, location=src.location) if parent_abi_t.is_tuple(): if abi_t.is_dynamic(): child_loc = ["add", "src", unwrap_location(src_loc)] child_loc = LLLnode.from_list(child_loc, typ=o.typ, location=src.location) else: child_loc = src_loc # descend into the child tuple lll_ret.append(abi_decode(o, child_loc, pos=pos)) else: lll_ret.append( make_setter(o, src_loc, location=o.location, pos=pos)) if i + 1 == len(os): pass # optimize out the last pointer increment else: sz = abi_t.embedded_static_size() lll_ret.append(["set", "src_loc", ["add", "src_loc", sz]]) lll_ret = ["with", "src", src, ["with", "src_loc", "src", lll_ret]] return lll_ret
def parse_Assign(self): # Assignment (e.g. x[4] = y) sub = Expr(self.stmt.value, self.context).lll_node target = self._get_target(self.stmt.target) lll_node = make_setter(target, sub, target.location, pos=getpos(self.stmt)) lll_node.pos = getpos(self.stmt) return lll_node
def parse_AnnAssign(self): typ = parse_type( self.stmt.annotation, location="memory", custom_structs=self.context.structs, ) varname = self.stmt.target.id pos = self.context.new_variable(varname, typ, pos=self.stmt) if self.stmt.value is None: return sub = Expr(self.stmt.value, self.context).lll_node is_literal_bytes32_assign = ( isinstance(sub.typ, ByteArrayType) and sub.typ.maxlen == 32 and isinstance(typ, BaseType) and typ.typ == "bytes32" and sub.typ.is_literal ) # If bytes[32] to bytes32 assignment rewrite sub as bytes32. if is_literal_bytes32_assign: sub = LLLnode( bytes_to_int(self.stmt.value.s), typ=BaseType("bytes32"), pos=getpos(self.stmt), ) variable_loc = LLLnode.from_list(pos, typ=typ, location="memory", pos=getpos(self.stmt),) lll_node = make_setter(variable_loc, sub, "memory", pos=getpos(self.stmt)) return lll_node
def abi_decode(lll_node, src, pos=None): os = o_list(lll_node, pos=pos) lll_ret = [] src_ptr = 'src' # pointer to beginning of buffer src_loc = 'src_loc' # pointer to read location in static section parent_abi_t = abi_type_of(src.typ) for i, o in enumerate(os): abi_t = abi_type_of(o.typ) src_loc = LLLnode('src_loc', typ=o.typ, location=src.location) if parent_abi_t.is_tuple(): if abi_t.is_dynamic(): child_loc = ['add', src_ptr, unwrap_location(src_loc)] else: child_loc = src_loc # descend into the child tuple lll_ret.append(abi_decode(o, child_loc, pos=pos)) else: lll_ret.append(make_setter(o, src_loc)) if i + 1 == len(os): pass # optimize out the last pointer increment else: sz = abi_t.embedded_static_size() lll_ret.append(['set', src_loc, ['add', src_loc, sz]]) lll_ret = [ 'with', 'src', src, ['with', 'src_loc', 'src', ['seq', lll_ret]] ] return lll_ret
def parse_Tuple(self): if not len(self.expr.elements): return call_lll = [] multi_lll = [] for node in self.expr.elements: if isinstance(node, vy_ast.Call): # for calls inside the tuple, we perform the call prior to building the tuple and # assign it's result to memory - otherwise there is potential for memory corruption lll_node = Expr(node, self.context).lll_node target = LLLnode.from_list( self.context.new_internal_variable(lll_node.typ), typ=lll_node.typ, location="memory", pos=getpos(self.expr), ) call_lll.append(make_setter(target, lll_node, "memory", pos=getpos(self.expr))) multi_lll.append( LLLnode.from_list( target, typ=lll_node.typ, pos=getpos(self.expr), location="memory" ), ) else: multi_lll.append(Expr(node, self.context).lll_node) typ = TupleType([x.typ for x in multi_lll], is_literal=True) multi_lll = LLLnode.from_list(["multi"] + multi_lll, typ=typ, pos=getpos(self.expr)) if not call_lll: return multi_lll lll_node = ["seq_unchecked"] + call_lll + [multi_lll] return LLLnode.from_list(lll_node, typ=typ, pos=getpos(self.expr))
def ann_assign(self): self.context.set_in_assignment(True) typ = parse_type(self.stmt.annotation, location='memory', custom_units=self.context.custom_units) if isinstance(self.stmt.target, ast.Attribute) and self.stmt.target.value.id == 'self': raise TypeMismatchException('May not redefine storage variables.', self.stmt) varname = self.stmt.target.id pos = self.context.new_variable(varname, typ) o = LLLnode.from_list('pass', typ=None, pos=pos) if self.stmt.value is not None: sub = Expr(self.stmt.value, self.context).lll_node # If bytes[32] to bytes32 assignment rewrite sub as bytes32. if isinstance( sub.typ, ByteArrayType) and sub.typ.maxlen == 32 and isinstance( typ, BaseType) and typ.typ == 'bytes32': bytez, bytez_length = string_to_bytes(self.stmt.value.s) sub = LLLnode(bytes_to_int(bytez), typ=BaseType('bytes32'), pos=getpos(self.stmt)) self._check_valid_assign(sub) self._check_same_variable_assign(sub) variable_loc = LLLnode.from_list(pos, typ=typ, location='memory', pos=getpos(self.stmt)) o = make_setter(variable_loc, sub, 'memory', pos=getpos(self.stmt)) self.context.set_in_assignment(False) return o
def ann_assign(self): self.context.set_in_assignment(True) typ = parse_type(self.stmt.annotation, location='memory', custom_units=self.context.custom_units, custom_structs=self.context.structs) if isinstance(self.stmt.target, ast.Attribute): raise TypeMismatchException('May not set type for field %r' % self.stmt.target.attr, self.stmt) varname = self.stmt.target.id pos = self.context.new_variable(varname, typ) o = LLLnode.from_list('pass', typ=None, pos=pos) if self.stmt.value is not None: sub = Expr(self.stmt.value, self.context).lll_node # Disallow assignment to None if isinstance(sub.typ, NullType): raise InvalidLiteralException('Assignment to None is not allowed, use a default value or built-in `clear()`.', self.stmt) # If bytes[32] to bytes32 assignment rewrite sub as bytes32. if isinstance(sub.typ, ByteArrayType) and sub.typ.maxlen == 32 and isinstance(typ, BaseType) and typ.typ == 'bytes32': bytez, bytez_length = string_to_bytes(self.stmt.value.s) sub = LLLnode(bytes_to_int(bytez), typ=BaseType('bytes32'), pos=getpos(self.stmt)) self._check_valid_assign(sub) self._check_same_variable_assign(sub) variable_loc = LLLnode.from_list(pos, typ=typ, location='memory', pos=getpos(self.stmt)) o = make_setter(variable_loc, sub, 'memory', pos=getpos(self.stmt)) # o.pos = getpos(self.stmt) # TODO: Should this be here like in assign()? self.context.set_in_assignment(False) return o
def ann_assign(self): with self.context.assignment_scope(): typ = parse_type( self.stmt.annotation, location='memory', custom_units=self.context.custom_units, custom_structs=self.context.structs, constants=self.context.constants, ) if isinstance(self.stmt.target, ast.Attribute): raise TypeMismatchException( f'May not set type for field {self.stmt.target.attr}', self.stmt, ) varname = self.stmt.target.id pos = self.context.new_variable(varname, typ) o = LLLnode.from_list('pass', typ=None, pos=pos) if self.stmt.value is None: raise StructureException( 'New variables must be initialized explicitly', self.stmt) sub = Expr(self.stmt.value, self.context).lll_node # Disallow assignment to None if isinstance(sub.typ, NullType): raise InvalidLiteralException( ( 'Assignment to None is not allowed, use a default ' 'value or built-in `clear()`.' ), self.stmt ) is_valid_bytes32_assign = ( isinstance(sub.typ, ByteArrayType) and sub.typ.maxlen == 32 ) and isinstance(typ, BaseType) and typ.typ == 'bytes32' # If bytes[32] to bytes32 assignment rewrite sub as bytes32. if is_valid_bytes32_assign: sub = LLLnode( bytes_to_int(self.stmt.value.s), typ=BaseType('bytes32'), pos=getpos(self.stmt), ) self._check_valid_assign(sub) self._check_same_variable_assign(sub) variable_loc = LLLnode.from_list( pos, typ=typ, location='memory', pos=getpos(self.stmt), ) o = make_setter(variable_loc, sub, 'memory', pos=getpos(self.stmt)) # o.pos = getpos(self.stmt) # TODO: Should this be here like in assign()? return o
def parse_delete(self): from .parser import ( make_setter, ) if len(self.stmt.targets) != 1: raise StructureException("Can delete one variable at a time", self.stmt) target = self.stmt.targets[0] target_lll = Expr(self.stmt.targets[0], self.context).lll_node if isinstance(target, ast.Subscript): if target_lll.location == "storage": return make_setter(target_lll, LLLnode.from_list(None, typ=NullType()), "storage", pos=getpos(self.stmt)) raise StructureException("Deleting type not supported.", self.stmt)
def pack_logging_topics(event_id, arg_nodes, arg_types, context): topics = [event_id] for node, typ in zip(arg_nodes, arg_types): value = Expr(node, context).lll_node if isinstance(typ, ArrayValueAbstractType): if isinstance(node, (vy_ast.Str, vy_ast.Bytes)): # for literals, generate the topic at compile time value = node.value if isinstance(value, str): value = value.encode() topics.append(bytes_to_int(keccak256(value))) elif value.location == "memory": topics.append(["sha3", ["add", value, 32], ["mload", value]]) else: # storage or calldata placeholder = context.new_internal_variable(value.typ) placeholder_node = LLLnode.from_list(placeholder, typ=value.typ, location="memory") copier = make_byte_array_copier( placeholder_node, LLLnode.from_list("_sub", typ=value.typ, location=value.location), ) lll_node = [ "with", "_sub", value, ["seq", copier, ["sha3", ["add", placeholder, 32], ["mload", placeholder]]], ] topics.append(lll_node) elif isinstance(typ, ArrayDefinition): size = typ.size_in_bytes if value.location == "memory": topics.append(["sha3", value, size]) else: # storage or calldata placeholder = context.new_internal_variable(value.typ) placeholder_node = LLLnode.from_list(placeholder, typ=value.typ, location="memory") setter = make_setter(placeholder_node, value, "memory", value.pos) lll_node = ["seq", setter, ["sha3", placeholder, size]] topics.append(lll_node) else: value = unwrap_location(value) topics.append(value) return topics
def ann_assign(self): with self.context.assignment_scope(): typ = parse_type( self.stmt.annotation, location='memory', custom_structs=self.context.structs, constants=self.context.constants, ) if isinstance(self.stmt.target, vy_ast.Attribute): raise TypeMismatch( f'May not set type for field {self.stmt.target.attr}', self.stmt, ) varname = self.stmt.target.id pos = self.context.new_variable(varname, typ) if self.stmt.value is None: raise StructureException( 'New variables must be initialized explicitly', self.stmt) sub = Expr(self.stmt.value, self.context).lll_node is_literal_bytes32_assign = ( isinstance(sub.typ, ByteArrayType) and sub.typ.maxlen == 32 and isinstance(typ, BaseType) and typ.typ == 'bytes32' and sub.typ.is_literal ) # If bytes[32] to bytes32 assignment rewrite sub as bytes32. if is_literal_bytes32_assign: sub = LLLnode( bytes_to_int(self.stmt.value.s), typ=BaseType('bytes32'), pos=getpos(self.stmt), ) self._check_valid_assign(sub) self._check_same_variable_assign(sub) variable_loc = LLLnode.from_list( pos, typ=typ, location='memory', pos=getpos(self.stmt), ) o = make_setter(variable_loc, sub, 'memory', pos=getpos(self.stmt)) return o
def ann_assign(self): self.context.set_in_assignment(True) typ = parse_type(self.stmt.annotation, location='memory', custom_units=self.context.custom_units) if isinstance(self.stmt.target, ast.Attribute) and self.stmt.target.value.id == 'self': raise TypeMismatchException('May not redefine storage variables.', self.stmt) varname = self.stmt.target.id pos = self.context.new_variable(varname, typ) o = LLLnode.from_list('pass', typ=None, pos=pos) if self.stmt.value is not None: sub = Expr(self.stmt.value, self.context).lll_node self._check_valid_assign(sub) variable_loc = LLLnode.from_list(pos, typ=typ, location='memory', pos=getpos(self.stmt)) o = make_setter(variable_loc, sub, 'memory', pos=getpos(self.stmt)) self.context.set_in_assignment(False) return o
def _clear(self): # Create zero node none = ast.NameConstant(None) none.lineno = self.stmt.lineno none.col_offset = self.stmt.col_offset zero = Expr(none, self.context).lll_node # Get target variable target = self.get_target(self.stmt.args[0]) # Generate LLL node to set to zero o = make_setter(target, zero, target.location, pos=getpos(self.stmt)) o.pos = getpos(self.stmt) return o
def assign(self): # Assignment (e.g. x[4] = y) with self.context.assignment_scope(): sub = Expr(self.stmt.value, self.context).lll_node # Error check when assigning to declared variable if isinstance(self.stmt.target, vy_ast.Name): # Do not allow assignment to undefined variables without annotation if self.stmt.target.id not in self.context.vars: raise VariableDeclarationException( "Variable type not defined", self.stmt) # Check against implicit conversion self._check_implicit_conversion(self.stmt.target.id, sub) is_valid_tuple_assign = (isinstance( self.stmt.target, vy_ast.Tuple)) and isinstance( self.stmt.value, vy_ast.Tuple) # Do no allow tuple-to-tuple assignment if is_valid_tuple_assign: raise VariableDeclarationException( "Tuple to tuple assignment not supported", self.stmt, ) # Checks to see if assignment is valid target = self.get_target(self.stmt.target) if isinstance(target.typ, ContractType) and not isinstance( sub.typ, ContractType): raise TypeMismatch( 'Contract assignment expects casted address: ' f'{target.typ}(<address_var>)', self.stmt) o = make_setter(target, sub, target.location, pos=getpos(self.stmt)) o.pos = getpos(self.stmt) 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, 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 parse_return(self): if self.context.return_type is None: if self.stmt.value: raise TypeMismatchException("Not expecting to return a value", self.stmt) return LLLnode.from_list(make_return_stmt(self.stmt, self.context, 0, 0), typ=None, pos=getpos(self.stmt), valency=0) if not self.stmt.value: raise TypeMismatchException("Expecting to return a value", self.stmt) def zero_pad(bytez_placeholder, maxlen): zero_padder = LLLnode.from_list(['pass']) if maxlen > 0: zero_pad_i = self.context.new_placeholder( BaseType('uint256')) # Iterator used to zero pad memory. zero_padder = LLLnode.from_list( [ 'with', '_ceil32_end', ['ceil32', ['mload', bytez_placeholder]], [ 'repeat', zero_pad_i, ['mload', bytez_placeholder], maxlen, [ 'seq', [ 'if', [ 'gt', ['mload', zero_pad_i], '_ceil32_end' ], 'break' ], # stay within allocated bounds [ 'mstore8', [ 'add', ['add', 32, bytez_placeholder], ['mload', zero_pad_i] ], 0 ] ] ] ], annotation="Zero pad") return zero_padder sub = Expr(self.stmt.value, self.context).lll_node self.context.increment_return_counter() # Returning a value (most common case) if isinstance(sub.typ, BaseType): sub = unwrap_location(sub) if not isinstance(self.context.return_type, BaseType): raise TypeMismatchException( "Return type units mismatch %r %r" % (sub.typ, self.context.return_type), self.stmt.value) elif self.context.return_type != sub.typ and not sub.typ.is_literal: raise TypeMismatchException( "Trying to return base type %r, output expecting %r" % (sub.typ, self.context.return_type), self.stmt.value) elif sub.typ.is_literal and ( self.context.return_type.typ == sub.typ or 'int' in self.context.return_type.typ and 'int' in sub.typ.typ): if not SizeLimits.in_bounds(self.context.return_type.typ, sub.value): raise InvalidLiteralException( "Number out of range: " + str(sub.value), self.stmt) else: return LLLnode.from_list([ 'seq', ['mstore', 0, sub], make_return_stmt(self.stmt, self.context, 0, 32) ], typ=None, pos=getpos(self.stmt), valency=0) elif is_base_type(sub.typ, self.context.return_type.typ) or \ (is_base_type(sub.typ, 'int128') and is_base_type(self.context.return_type, 'int256')): return LLLnode.from_list([ 'seq', ['mstore', 0, sub], make_return_stmt(self.stmt, self.context, 0, 32) ], typ=None, pos=getpos(self.stmt), valency=0) else: raise TypeMismatchException( "Unsupported type conversion: %r to %r" % (sub.typ, self.context.return_type), self.stmt.value) # Returning a byte array elif isinstance(sub.typ, ByteArrayLike): if not sub.typ.eq_base(self.context.return_type): raise TypeMismatchException( "Trying to return base type %r, output expecting %r" % (sub.typ, self.context.return_type), self.stmt.value) if sub.typ.maxlen > self.context.return_type.maxlen: raise TypeMismatchException( "Cannot cast from greater max-length %d to shorter max-length %d" % (sub.typ.maxlen, self.context.return_type.maxlen), self.stmt.value) loop_memory_position = self.context.new_placeholder( typ=BaseType( 'uint256')) # loop memory has to be allocated first. len_placeholder = self.context.new_placeholder( typ=BaseType('uint256') ) # len & bytez placeholder have to be declared after each other at all times. bytez_placeholder = self.context.new_placeholder(typ=sub.typ) if sub.location in ('storage', 'memory'): return LLLnode.from_list([ 'seq', make_byte_array_copier(LLLnode( bytez_placeholder, location='memory', typ=sub.typ), sub, pos=getpos(self.stmt)), zero_pad(bytez_placeholder, sub.typ.maxlen), ['mstore', len_placeholder, 32], make_return_stmt( self.stmt, self.context, len_placeholder, ['ceil32', ['add', ['mload', bytez_placeholder], 64]], loop_memory_position=loop_memory_position) ], typ=None, pos=getpos(self.stmt), valency=0) else: raise Exception("Invalid location: %s" % sub.location) elif isinstance(sub.typ, ListType): sub_base_type = re.split(r'\(|\[', str(sub.typ.subtype))[0] ret_base_type = re.split(r'\(|\[', str(self.context.return_type.subtype))[0] loop_memory_position = self.context.new_placeholder( typ=BaseType('uint256')) if sub_base_type != ret_base_type: raise TypeMismatchException( "List return type %r does not match specified return type, expecting %r" % (sub_base_type, ret_base_type), self.stmt) elif sub.location == "memory" and sub.value != "multi": return LLLnode.from_list(make_return_stmt( self.stmt, self.context, sub, get_size_of_type(self.context.return_type) * 32, loop_memory_position=loop_memory_position), typ=None, pos=getpos(self.stmt), valency=0) else: new_sub = LLLnode.from_list(self.context.new_placeholder( self.context.return_type), typ=self.context.return_type, location='memory') setter = make_setter(new_sub, sub, 'memory', pos=getpos(self.stmt)) return LLLnode.from_list([ 'seq', setter, make_return_stmt( self.stmt, self.context, new_sub, get_size_of_type(self.context.return_type) * 32, loop_memory_position=loop_memory_position) ], typ=None, pos=getpos(self.stmt)) # Returning a struct elif isinstance(sub.typ, StructType): retty = self.context.return_type if not isinstance(retty, StructType) or retty.name != sub.typ.name: raise TypeMismatchException( "Trying to return %r, output expecting %r" % (sub.typ, self.context.return_type), self.stmt.value) return gen_tuple_return(self.stmt, self.context, sub) # Returning a tuple. elif isinstance(sub.typ, TupleType): if not isinstance(self.context.return_type, TupleType): raise TypeMismatchException( "Trying to return tuple type %r, output expecting %r" % (sub.typ, self.context.return_type), self.stmt.value) if len(self.context.return_type.members) != len(sub.typ.members): raise StructureException("Tuple lengths don't match!", self.stmt) # check return type matches, sub type. for i, ret_x in enumerate(self.context.return_type.members): s_member = sub.typ.members[i] sub_type = s_member if isinstance(s_member, NodeType) else s_member.typ if type(sub_type) is not type(ret_x): raise StructureException( "Tuple return type does not match annotated return. {} != {}" .format(type(sub_type), type(ret_x)), self.stmt) return gen_tuple_return(self.stmt, self.context, sub) else: raise TypeMismatchException("Can't return type %r" % sub.typ, self.stmt)
def parse_for_list(self): from .parser import (parse_body, make_setter) iter_list_node = Expr(self.stmt.iter, self.context).lll_node if not isinstance(iter_list_node.typ.subtype, BaseType): # Sanity check on list subtype. raise StructureException( 'For loops allowed only on basetype lists.', self.stmt.iter) iter_var_type = self.context.vars.get( self.stmt.iter.id).typ if isinstance(self.stmt.iter, ast.Name) else None subtype = iter_list_node.typ.subtype.typ varname = self.stmt.target.id value_pos = self.context.new_variable( varname, BaseType(subtype, unit=iter_list_node.typ.subtype.unit)) i_pos = self.context.new_variable('_index_for_' + varname, BaseType(subtype)) self.context.forvars[varname] = True # Is a list that is already allocated to memory. if iter_var_type: list_name = self.stmt.iter.id with self.context.in_for_loop_scope( list_name ): # make sure list cannot be altered whilst iterating. iter_var = self.context.vars.get(self.stmt.iter.id) body = [ 'seq', [ 'mstore', value_pos, [ 'mload', [ 'add', iter_var.pos, ['mul', ['mload', i_pos], 32] ] ] ], parse_body(self.stmt.body, self.context) ] o = LLLnode.from_list( ['repeat', i_pos, 0, iter_var.size, body], typ=None, pos=getpos(self.stmt)) # List gets defined in the for statement. elif isinstance(self.stmt.iter, ast.List): # Allocate list to memory. count = iter_list_node.typ.count tmp_list = LLLnode.from_list(obj=self.context.new_placeholder( ListType(iter_list_node.typ.subtype, count)), typ=ListType( iter_list_node.typ.subtype, count), location='memory') setter = make_setter(tmp_list, iter_list_node, 'memory', pos=getpos(self.stmt)) body = [ 'seq', [ 'mstore', value_pos, [ 'mload', ['add', tmp_list, ['mul', ['mload', i_pos], 32]] ] ], parse_body(self.stmt.body, self.context) ] o = LLLnode.from_list( ['seq', setter, ['repeat', i_pos, 0, count, body]], typ=None, pos=getpos(self.stmt)) # List contained in storage. elif isinstance(self.stmt.iter, ast.Attribute): count = iter_list_node.typ.count list_name = iter_list_node.annotation with self.context.in_for_loop_scope( list_name ): # make sure list cannot be altered whilst iterating. body = [ 'seq', [ 'mstore', value_pos, [ 'sload', [ 'add', ['sha3_32', iter_list_node], ['mload', i_pos] ] ] ], parse_body(self.stmt.body, self.context), ] o = LLLnode.from_list( ['seq', ['repeat', i_pos, 0, count, body]], typ=None, pos=getpos(self.stmt)) del self.context.vars[varname] del self.context.vars['_index_for_' + varname] del self.context.forvars[varname] return o
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 build_in_comparator(self): left = Expr(self.expr.left, self.context).lll_node right = Expr(self.expr.right, self.context).lll_node result_placeholder = self.context.new_internal_variable( BaseType("bool")) setter = [] # Load nth item from list in memory. if right.value == "multi": # Copy literal to memory to be compared. tmp_list = LLLnode.from_list( obj=self.context.new_internal_variable( ListType(right.typ.subtype, right.typ.count)), typ=ListType(right.typ.subtype, right.typ.count), location="memory", ) setter = make_setter(tmp_list, right, "memory", pos=getpos(self.expr)) load_i_from_list = [ "mload", [ "add", tmp_list, ["mul", 32, ["mload", MemoryPositions.FREE_LOOP_INDEX]] ], ] elif right.location == "storage": load_i_from_list = [ "sload", ["add", right, ["mload", MemoryPositions.FREE_LOOP_INDEX]], ] else: load_operation = "mload" if right.location == "memory" else "calldataload" load_i_from_list = [ load_operation, [ "add", right, ["mul", 32, ["mload", MemoryPositions.FREE_LOOP_INDEX]] ], ] # Condition repeat loop has to break on. break_loop_condition = [ "if", ["eq", unwrap_location(left), load_i_from_list], ["seq", ["mstore", "_result", 1], "break"], # store true. ] # Repeat loop to loop-compare each item in the list. for_loop_sequence = [ ["mstore", result_placeholder, 0], [ "with", "_result", result_placeholder, [ "repeat", MemoryPositions.FREE_LOOP_INDEX, 0, right.typ.count, break_loop_condition, ], ], ["mload", result_placeholder], ] # Save list to memory, so one can iterate over it, # used when literal was created with tmp_list. if setter: compare_sequence = ["seq", setter] + for_loop_sequence else: compare_sequence = ["seq"] + for_loop_sequence if isinstance(self.expr.op, vy_ast.NotIn): # for `not in`, invert the result compare_sequence = ["iszero", compare_sequence] return LLLnode.from_list(compare_sequence, typ="bool", annotation="in comparator")
def parse_sequence(base_node, elements, context): """ Generate an LLL node from a sequence of Vyper AST nodes, such as values inside a list/tuple or arguments inside a call. Arguments --------- base_node : VyperNode Parent node which contains the sequence being parsed. elements : List[VyperNode] A list of nodes within the sequence. context : Context Currently active local context. Returns ------- List[LLLNode] LLL nodes that must execute prior to generating the actual sequence in order to avoid memory corruption issues. This list may be empty, depending on the values within `elements`. List[LLLNode] LLL nodes which collectively represent `elements`. """ init_lll = [] sequence_lll = [] for node in elements: if isinstance(node, vy_ast.List): # for nested lists, ensure the init LLL is also processed before the values init, seq = parse_sequence(node, node.elements, context) init_lll.extend(init) out_type = next((i.typ for i in seq if not i.typ.is_literal), seq[0].typ) typ = ListType(out_type, len(node.elements), is_literal=True) multi_lll = LLLnode.from_list(["multi"] + seq, typ=typ, pos=getpos(node)) sequence_lll.append(multi_lll) continue lll_node = Expr(node, context).lll_node if isinstance( node, vy_ast.Call) or (isinstance(node, vy_ast.Subscript) and isinstance(node.value, vy_ast.Call)): # nodes which potentially create their own internal memory variables, and so must # be parsed prior to generating the final sequence to avoid memory corruption target = LLLnode.from_list( context.new_internal_variable(lll_node.typ), typ=lll_node.typ, location="memory", pos=getpos(base_node), ) init_lll.append( make_setter(target, lll_node, "memory", pos=getpos(base_node))) sequence_lll.append( LLLnode.from_list(target, typ=lll_node.typ, pos=getpos(base_node), location="memory"), ) else: sequence_lll.append(lll_node) return init_lll, sequence_lll
def parse_return(self): if self.context.return_type is None: if self.stmt.value: raise TypeMismatchException("Not expecting to return a value", self.stmt) return LLLnode.from_list(self.make_return_stmt(0, 0), typ=None, pos=getpos(self.stmt), valency=0) if not self.stmt.value: raise TypeMismatchException("Expecting to return a value", self.stmt) def zero_pad(bytez_placeholder, maxlen): zero_padder = LLLnode.from_list(['pass']) if maxlen > 0: zero_pad_i = self.context.new_placeholder( BaseType('uint256')) # Iterator used to zero pad memory. zero_padder = LLLnode.from_list( [ 'repeat', zero_pad_i, ['mload', bytez_placeholder], maxlen, [ 'seq', [ 'if', ['gt', ['mload', zero_pad_i], maxlen], 'break' ], # stay within allocated bounds [ 'mstore8', [ 'add', ['add', 32, bytez_placeholder], ['mload', zero_pad_i] ], 0 ] ] ], annotation="Zero pad") return zero_padder sub = Expr(self.stmt.value, self.context).lll_node self.context.increment_return_counter() # Returning a value (most common case) if isinstance(sub.typ, BaseType): if not isinstance(self.context.return_type, BaseType): raise TypeMismatchException( "Trying to return base type %r, output expecting %r" % (sub.typ, self.context.return_type), self.stmt.value) sub = unwrap_location(sub) if not are_units_compatible(sub.typ, self.context.return_type): raise TypeMismatchException( "Return type units mismatch %r %r" % (sub.typ, self.context.return_type), self.stmt.value) elif sub.typ.is_literal and ( self.context.return_type.typ == sub.typ or 'int' in self.context.return_type.typ and 'int' in sub.typ.typ): if not SizeLimits.in_bounds(self.context.return_type.typ, sub.value): raise InvalidLiteralException( "Number out of range: " + str(sub.value), self.stmt) else: return LLLnode.from_list([ 'seq', ['mstore', 0, sub], self.make_return_stmt(0, 32) ], typ=None, pos=getpos(self.stmt), valency=0) elif is_base_type(sub.typ, self.context.return_type.typ) or \ (is_base_type(sub.typ, 'int128') and is_base_type(self.context.return_type, 'int256')): return LLLnode.from_list( ['seq', ['mstore', 0, sub], self.make_return_stmt(0, 32)], typ=None, pos=getpos(self.stmt), valency=0) else: raise TypeMismatchException( "Unsupported type conversion: %r to %r" % (sub.typ, self.context.return_type), self.stmt.value) # Returning a byte array elif isinstance(sub.typ, ByteArrayType): if not isinstance(self.context.return_type, ByteArrayType): raise TypeMismatchException( "Trying to return base type %r, output expecting %r" % (sub.typ, self.context.return_type), self.stmt.value) if sub.typ.maxlen > self.context.return_type.maxlen: raise TypeMismatchException( "Cannot cast from greater max-length %d to shorter max-length %d" % (sub.typ.maxlen, self.context.return_type.maxlen), self.stmt.value) loop_memory_position = self.context.new_placeholder( typ=BaseType( 'uint256')) # loop memory has to be allocated first. len_placeholder = self.context.new_placeholder( typ=BaseType('uint256') ) # len & bytez placeholder have to be declared after each other at all times. bytez_placeholder = self.context.new_placeholder(typ=sub.typ) if sub.location in ('storage', 'memory'): return LLLnode.from_list([ 'seq', make_byte_array_copier(LLLnode( bytez_placeholder, location='memory', typ=sub.typ), sub, pos=getpos(self.stmt)), zero_pad(bytez_placeholder, sub.typ.maxlen), ['mstore', len_placeholder, 32], self.make_return_stmt( len_placeholder, ['ceil32', ['add', ['mload', bytez_placeholder], 64]], loop_memory_position=loop_memory_position) ], typ=None, pos=getpos(self.stmt), valency=0) else: raise Exception("Invalid location: %s" % sub.location) elif isinstance(sub.typ, ListType): sub_base_type = re.split(r'\(|\[', str(sub.typ.subtype))[0] ret_base_type = re.split(r'\(|\[', str(self.context.return_type.subtype))[0] loop_memory_position = self.context.new_placeholder( typ=BaseType('uint256')) if sub_base_type != ret_base_type: raise TypeMismatchException( "List return type %r does not match specified return type, expecting %r" % (sub_base_type, ret_base_type), self.stmt) elif sub.location == "memory" and sub.value != "multi": return LLLnode.from_list(self.make_return_stmt( sub, get_size_of_type(self.context.return_type) * 32, loop_memory_position=loop_memory_position), typ=None, pos=getpos(self.stmt), valency=0) else: new_sub = LLLnode.from_list(self.context.new_placeholder( self.context.return_type), typ=self.context.return_type, location='memory') setter = make_setter(new_sub, sub, 'memory', pos=getpos(self.stmt)) return LLLnode.from_list([ 'seq', setter, self.make_return_stmt( new_sub, get_size_of_type(self.context.return_type) * 32, loop_memory_position=loop_memory_position) ], typ=None, pos=getpos(self.stmt)) # Returning a tuple. elif isinstance(sub.typ, TupleType): if not isinstance(self.context.return_type, TupleType): raise TypeMismatchException( "Trying to return tuple type %r, output expecting %r" % (sub.typ, self.context.return_type), self.stmt.value) if len(self.context.return_type.members) != len(sub.typ.members): raise StructureException("Tuple lengths don't match!", self.stmt) # check return type matches, sub type. for i, ret_x in enumerate(self.context.return_type.members): s_member = sub.typ.members[i] sub_type = s_member if isinstance(s_member, NodeType) else s_member.typ if type(sub_type) is not type(ret_x): raise StructureException( "Tuple return type does not match annotated return. {} != {}" .format(type(sub_type), type(ret_x)), self.stmt) # Is from a call expression. if len(sub.args[0].args) > 0 and sub.args[0].args[ 0].value == 'call': # self-call to public. mem_pos = sub.args[0].args[-1] mem_size = get_size_of_type(sub.typ) * 32 return LLLnode.from_list(['return', mem_pos, mem_size], typ=sub.typ) elif (sub.annotation and 'Internal Call' in sub.annotation): mem_pos = sub.args[ -1].value if sub.value == 'seq_unchecked' else sub.args[ 0].args[-1] mem_size = get_size_of_type(sub.typ) * 32 # Add zero padder if bytes are present in output. zero_padder = ['pass'] byte_arrays = [(i, x) for i, x in enumerate(sub.typ.members) if isinstance(x, ByteArrayType)] if byte_arrays: i, x = byte_arrays[-1] zero_padder = zero_pad(bytez_placeholder=[ 'add', mem_pos, ['mload', mem_pos + i * 32] ], maxlen=x.maxlen) return LLLnode.from_list( ['seq'] + [sub] + [zero_padder] + [self.make_return_stmt(mem_pos, mem_size)], typ=sub.typ, pos=getpos(self.stmt), valency=0) subs = [] # Pre-allocate loop_memory_position if required for private function returning. loop_memory_position = self.context.new_placeholder( typ=BaseType('uint256')) if self.context.is_private else None # Allocate dynamic off set counter, to keep track of the total packed dynamic data size. dynamic_offset_counter_placeholder = self.context.new_placeholder( typ=BaseType('uint256')) dynamic_offset_counter = LLLnode( dynamic_offset_counter_placeholder, typ=None, annotation= "dynamic_offset_counter" # dynamic offset position counter. ) new_sub = LLLnode.from_list( self.context.new_placeholder(typ=BaseType('uint256')), typ=self.context.return_type, location='memory', annotation='new_sub') keyz = list(range(len(sub.typ.members))) dynamic_offset_start = 32 * len( sub.args) # The static list of args end. left_token = LLLnode.from_list('_loc', typ=new_sub.typ, location="memory") def get_dynamic_offset_value(): # Get value of dynamic offset counter. return ['mload', dynamic_offset_counter] def increment_dynamic_offset(dynamic_spot): # Increment dyanmic offset counter in memory. return [ 'mstore', dynamic_offset_counter, [ 'add', ['add', ['ceil32', ['mload', dynamic_spot]], 32], ['mload', dynamic_offset_counter] ] ] for i, typ in enumerate(keyz): arg = sub.args[i] variable_offset = LLLnode.from_list( ['add', 32 * i, left_token], typ=arg.typ, annotation='variable_offset') if isinstance(arg.typ, ByteArrayType): # Store offset pointer value. subs.append([ 'mstore', variable_offset, get_dynamic_offset_value() ]) # Store dynamic data, from offset pointer onwards. dynamic_spot = LLLnode.from_list( ['add', left_token, get_dynamic_offset_value()], location="memory", typ=arg.typ, annotation='dynamic_spot') subs.append( make_setter(dynamic_spot, arg, location="memory", pos=getpos(self.stmt))) subs.append(increment_dynamic_offset(dynamic_spot)) elif isinstance(arg.typ, BaseType): subs.append( make_setter(variable_offset, arg, "memory", pos=getpos(self.stmt))) else: raise Exception("Can't return type %s as part of tuple", type(arg.typ)) setter = LLLnode.from_list([ 'seq', [ 'mstore', dynamic_offset_counter, dynamic_offset_start ], ['with', '_loc', new_sub, ['seq'] + subs] ], typ=None) return LLLnode.from_list([ 'seq', setter, self.make_return_stmt(new_sub, get_dynamic_offset_value(), loop_memory_position) ], typ=None, pos=getpos(self.stmt), valency=0) else: raise TypeMismatchException("Can only return base type!", self.stmt)
def build_in_comparator(self): left = Expr(self.expr.left, self.context).lll_node right = Expr(self.expr.right, self.context).lll_node if left.typ != right.typ.subtype: raise TypeMismatch( f"{left.typ} cannot be in a list of {right.typ.subtype}", self.expr, ) result_placeholder = self.context.new_placeholder(BaseType('bool')) setter = [] # Load nth item from list in memory. if right.value == 'multi': # Copy literal to memory to be compared. tmp_list = LLLnode.from_list(obj=self.context.new_placeholder( ListType(right.typ.subtype, right.typ.count)), typ=ListType(right.typ.subtype, right.typ.count), location='memory') setter = make_setter(tmp_list, right, 'memory', pos=getpos(self.expr)) load_i_from_list = [ 'mload', [ 'add', tmp_list, ['mul', 32, ['mload', MemoryPositions.FREE_LOOP_INDEX]] ], ] elif right.location == "storage": load_i_from_list = [ 'sload', [ 'add', ['sha3_32', right], ['mload', MemoryPositions.FREE_LOOP_INDEX] ], ] else: load_i_from_list = [ 'mload', [ 'add', right, ['mul', 32, ['mload', MemoryPositions.FREE_LOOP_INDEX]] ], ] # Condition repeat loop has to break on. break_loop_condition = [ 'if', ['eq', unwrap_location(left), load_i_from_list], [ 'seq', ['mstore', '_result', 1], # store true. 'break' ] ] # Repeat loop to loop-compare each item in the list. for_loop_sequence = [['mstore', result_placeholder, 0], [ 'with', '_result', result_placeholder, [ 'repeat', MemoryPositions.FREE_LOOP_INDEX, 0, right.typ.count, break_loop_condition, ] ], ['mload', result_placeholder]] # Save list to memory, so one can iterate over it, # used when literal was created with tmp_list. if setter: compare_sequence = ['seq', setter] + for_loop_sequence else: compare_sequence = ['seq'] + for_loop_sequence # Compare the result of the repeat loop to 1, to know if a match was found. o = LLLnode.from_list(['eq', 1, compare_sequence], typ='bool', annotation="in comporator") return o
def parse_return(self): if self.context.return_type is None: if self.stmt.value: raise TypeMismatchException("Not expecting to return a value", self.stmt) return LLLnode.from_list( make_return_stmt(self.stmt, self.context, 0, 0), typ=None, pos=getpos(self.stmt), valency=0, ) if not self.stmt.value: raise TypeMismatchException("Expecting to return a value", self.stmt) sub = Expr(self.stmt.value, self.context).lll_node # Returning a value (most common case) if isinstance(sub.typ, BaseType): sub = unwrap_location(sub) if not isinstance(self.context.return_type, BaseType): raise TypeMismatchException( f"Return type units mismatch {sub.typ} {self.context.return_type}", self.stmt.value) elif self.context.return_type != sub.typ and not sub.typ.is_literal: raise TypeMismatchException( f"Trying to return base type {sub.typ}, output expecting " f"{self.context.return_type}", self.stmt.value, ) elif sub.typ.is_literal and ( self.context.return_type.typ == sub.typ or 'int' in self.context.return_type.typ and 'int' in sub.typ.typ): # noqa: E501 if not SizeLimits.in_bounds(self.context.return_type.typ, sub.value): raise InvalidLiteralException( "Number out of range: " + str(sub.value), self.stmt) else: return LLLnode.from_list( [ 'seq', ['mstore', 0, sub], make_return_stmt(self.stmt, self.context, 0, 32) ], typ=None, pos=getpos(self.stmt), valency=0, ) elif is_base_type(sub.typ, self.context.return_type.typ) or ( is_base_type(sub.typ, 'int128') and is_base_type( self.context.return_type, 'int256')): # noqa: E501 return LLLnode.from_list( [ 'seq', ['mstore', 0, sub], make_return_stmt(self.stmt, self.context, 0, 32) ], typ=None, pos=getpos(self.stmt), valency=0, ) else: raise TypeMismatchException( f"Unsupported type conversion: {sub.typ} to {self.context.return_type}", self.stmt.value, ) # Returning a byte array elif isinstance(sub.typ, ByteArrayLike): if not sub.typ.eq_base(self.context.return_type): raise TypeMismatchException( f"Trying to return base type {sub.typ}, output expecting " f"{self.context.return_type}", self.stmt.value, ) if sub.typ.maxlen > self.context.return_type.maxlen: raise TypeMismatchException( f"Cannot cast from greater max-length {sub.typ.maxlen} to shorter " f"max-length {self.context.return_type.maxlen}", self.stmt.value, ) # loop memory has to be allocated first. loop_memory_position = self.context.new_placeholder( typ=BaseType('uint256')) # len & bytez placeholder have to be declared after each other at all times. len_placeholder = self.context.new_placeholder( typ=BaseType('uint256')) bytez_placeholder = self.context.new_placeholder(typ=sub.typ) if sub.location in ('storage', 'memory'): return LLLnode.from_list([ 'seq', make_byte_array_copier(LLLnode( bytez_placeholder, location='memory', typ=sub.typ), sub, pos=getpos(self.stmt)), zero_pad(bytez_placeholder), ['mstore', len_placeholder, 32], make_return_stmt( self.stmt, self.context, len_placeholder, ['ceil32', ['add', ['mload', bytez_placeholder], 64]], loop_memory_position=loop_memory_position, ) ], typ=None, pos=getpos(self.stmt), valency=0) else: raise Exception(f"Invalid location: {sub.location}") elif isinstance(sub.typ, ListType): sub_base_type = re.split(r'\(|\[', str(sub.typ.subtype))[0] ret_base_type = re.split(r'\(|\[', str(self.context.return_type.subtype))[0] loop_memory_position = self.context.new_placeholder( typ=BaseType('uint256')) if sub_base_type != ret_base_type: raise TypeMismatchException( f"List return type {sub_base_type} does not match specified " f"return type, expecting {ret_base_type}", self.stmt) elif sub.location == "memory" and sub.value != "multi": return LLLnode.from_list( make_return_stmt( self.stmt, self.context, sub, get_size_of_type(self.context.return_type) * 32, loop_memory_position=loop_memory_position, ), typ=None, pos=getpos(self.stmt), valency=0, ) else: new_sub = LLLnode.from_list( self.context.new_placeholder(self.context.return_type), typ=self.context.return_type, location='memory', ) setter = make_setter(new_sub, sub, 'memory', pos=getpos(self.stmt)) return LLLnode.from_list([ 'seq', setter, make_return_stmt( self.stmt, self.context, new_sub, get_size_of_type(self.context.return_type) * 32, loop_memory_position=loop_memory_position, ) ], typ=None, pos=getpos(self.stmt)) # Returning a struct elif isinstance(sub.typ, StructType): retty = self.context.return_type if not isinstance(retty, StructType) or retty.name != sub.typ.name: raise TypeMismatchException( f"Trying to return {sub.typ}, output expecting {self.context.return_type}", self.stmt.value, ) return gen_tuple_return(self.stmt, self.context, sub) # Returning a tuple. elif isinstance(sub.typ, TupleType): if not isinstance(self.context.return_type, TupleType): raise TypeMismatchException( f"Trying to return tuple type {sub.typ}, output expecting " f"{self.context.return_type}", self.stmt.value, ) if len(self.context.return_type.members) != len(sub.typ.members): raise StructureException("Tuple lengths don't match!", self.stmt) # check return type matches, sub type. for i, ret_x in enumerate(self.context.return_type.members): s_member = sub.typ.members[i] sub_type = s_member if isinstance(s_member, NodeType) else s_member.typ if type(sub_type) is not type(ret_x): raise StructureException( "Tuple return type does not match annotated return. " f"{type(sub_type)} != {type(ret_x)}", self.stmt) return gen_tuple_return(self.stmt, self.context, sub) else: raise TypeMismatchException(f"Can't return type {sub.typ}", self.stmt)
def parse_for_list(self): with self.context.range_scope(): iter_list_node = Expr(self.stmt.iter, self.context).lll_node if not isinstance(iter_list_node.typ.subtype, BaseType): # Sanity check on list subtype. raise StructureException( 'For loops allowed only on basetype lists.', self.stmt.iter) iter_var_type = (self.context.vars.get(self.stmt.iter.id).typ if isinstance(self.stmt.iter, ast.Name) else None) subtype = iter_list_node.typ.subtype.typ varname = self.stmt.target.id value_pos = self.context.new_variable( varname, BaseType(subtype, unit=iter_list_node.typ.subtype.unit), ) i_pos_raw_name = '_index_for_' + varname i_pos = self.context.new_internal_variable( i_pos_raw_name, BaseType(subtype), ) self.context.forvars[varname] = True # Is a list that is already allocated to memory. if iter_var_type: list_name = self.stmt.iter.id # make sure list cannot be altered whilst iterating. with self.context.in_for_loop_scope(list_name): iter_var = self.context.vars.get(self.stmt.iter.id) if iter_var.location == 'calldata': fetcher = 'calldataload' elif iter_var.location == 'memory': fetcher = 'mload' else: raise CompilerPanic( 'List iteration only supported on in-memory types', self.expr) body = [ 'seq', [ 'mstore', value_pos, [ fetcher, [ 'add', iter_var.pos, ['mul', ['mload', i_pos], 32] ] ], ], parse_body(self.stmt.body, self.context) ] o = LLLnode.from_list( ['repeat', i_pos, 0, iter_var.size, body], typ=None, pos=getpos(self.stmt)) # List gets defined in the for statement. elif isinstance(self.stmt.iter, ast.List): # Allocate list to memory. count = iter_list_node.typ.count tmp_list = LLLnode.from_list(obj=self.context.new_placeholder( ListType(iter_list_node.typ.subtype, count)), typ=ListType( iter_list_node.typ.subtype, count), location='memory') setter = make_setter(tmp_list, iter_list_node, 'memory', pos=getpos(self.stmt)) body = [ 'seq', [ 'mstore', value_pos, [ 'mload', ['add', tmp_list, ['mul', ['mload', i_pos], 32]] ] ], parse_body(self.stmt.body, self.context) ] o = LLLnode.from_list( ['seq', setter, ['repeat', i_pos, 0, count, body]], typ=None, pos=getpos(self.stmt)) # List contained in storage. elif isinstance(self.stmt.iter, ast.Attribute): count = iter_list_node.typ.count list_name = iter_list_node.annotation # make sure list cannot be altered whilst iterating. with self.context.in_for_loop_scope(list_name): body = [ 'seq', [ 'mstore', value_pos, [ 'sload', [ 'add', ['sha3_32', iter_list_node], ['mload', i_pos] ] ] ], parse_body(self.stmt.body, self.context), ] o = LLLnode.from_list( ['seq', ['repeat', i_pos, 0, count, body]], typ=None, pos=getpos(self.stmt)) del self.context.vars[varname] # this kind of open access to the vars dict should be disallowed. # we should use member functions to provide an API for these kinds # of operations. del self.context.vars[self.context._mangle(i_pos_raw_name)] del self.context.forvars[varname] return o
def parse_Return(self): if self.context.return_type is None: if self.stmt.value: return return LLLnode.from_list( make_return_stmt(self.stmt, self.context, 0, 0), typ=None, pos=getpos(self.stmt), valency=0, ) sub = Expr(self.stmt.value, self.context).lll_node # Returning a value (most common case) if isinstance(sub.typ, BaseType): sub = unwrap_location(sub) if self.context.return_type != sub.typ and not sub.typ.is_literal: return elif sub.typ.is_literal and ( self.context.return_type.typ == sub.typ or "int" in self.context.return_type.typ and "int" in sub.typ.typ): # noqa: E501 if SizeLimits.in_bounds(self.context.return_type.typ, sub.value): return LLLnode.from_list( [ "seq", ["mstore", 0, sub], make_return_stmt(self.stmt, self.context, 0, 32), ], typ=None, pos=getpos(self.stmt), valency=0, ) elif is_base_type(sub.typ, self.context.return_type.typ) or ( is_base_type(sub.typ, "int128") and is_base_type( self.context.return_type, "int256")): # noqa: E501 return LLLnode.from_list( [ "seq", ["mstore", 0, sub], make_return_stmt(self.stmt, self.context, 0, 32) ], typ=None, pos=getpos(self.stmt), valency=0, ) return # Returning a byte array elif isinstance(sub.typ, ByteArrayLike): if not sub.typ.eq_base(self.context.return_type): return if sub.typ.maxlen > self.context.return_type.maxlen: return # loop memory has to be allocated first. loop_memory_position = self.context.new_placeholder( typ=BaseType("uint256")) # len & bytez placeholder have to be declared after each other at all times. len_placeholder = self.context.new_placeholder( typ=BaseType("uint256")) bytez_placeholder = self.context.new_placeholder(typ=sub.typ) if sub.location in ("storage", "memory"): return LLLnode.from_list( [ "seq", make_byte_array_copier( LLLnode(bytez_placeholder, location="memory", typ=sub.typ), sub, pos=getpos(self.stmt), ), zero_pad(bytez_placeholder), ["mstore", len_placeholder, 32], make_return_stmt( self.stmt, self.context, len_placeholder, [ "ceil32", ["add", ["mload", bytez_placeholder], 64] ], loop_memory_position=loop_memory_position, ), ], typ=None, pos=getpos(self.stmt), valency=0, ) return elif isinstance(sub.typ, ListType): loop_memory_position = self.context.new_placeholder( typ=BaseType("uint256")) if sub.typ != self.context.return_type: return elif sub.location == "memory" and sub.value != "multi": return LLLnode.from_list( make_return_stmt( self.stmt, self.context, sub, get_size_of_type(self.context.return_type) * 32, loop_memory_position=loop_memory_position, ), typ=None, pos=getpos(self.stmt), valency=0, ) else: new_sub = LLLnode.from_list( self.context.new_placeholder(self.context.return_type), typ=self.context.return_type, location="memory", ) setter = make_setter(new_sub, sub, "memory", pos=getpos(self.stmt)) return LLLnode.from_list( [ "seq", setter, make_return_stmt( self.stmt, self.context, new_sub, get_size_of_type(self.context.return_type) * 32, loop_memory_position=loop_memory_position, ), ], typ=None, pos=getpos(self.stmt), ) # Returning a struct elif isinstance(sub.typ, StructType): retty = self.context.return_type if isinstance(retty, StructType) and retty.name == sub.typ.name: return gen_tuple_return(self.stmt, self.context, sub) # Returning a tuple. elif isinstance(sub.typ, TupleType): if not isinstance(self.context.return_type, TupleType): return if len(self.context.return_type.members) != len(sub.typ.members): return # check return type matches, sub type. for i, ret_x in enumerate(self.context.return_type.members): s_member = sub.typ.members[i] sub_type = s_member if isinstance(s_member, NodeType) else s_member.typ if type(sub_type) is not type(ret_x): return return gen_tuple_return(self.stmt, self.context, sub)
def pack_logging_data(expected_data, args, context, pos): # Checks to see if there's any data if not args: return ['seq'], 0, None, 0 holder = ['seq'] maxlen = len(args) * 32 # total size of all packed args (upper limit) # Unroll any function calls, to temp variables. prealloacted = {} for idx, (arg, _expected_arg) in enumerate(zip(args, expected_data)): if isinstance(arg, (ast.Str, ast.Call)): expr = Expr(arg, context) source_lll = expr.lll_node typ = source_lll.typ if isinstance(arg, ast.Str): if len(arg.s) > typ.maxlen: raise TypeMismatchException( "Data input bytes are to big: %r %r" % (len(arg.s), typ), pos) tmp_variable = context.new_variable( '_log_pack_var_%i_%i' % (arg.lineno, arg.col_offset), source_lll.typ, ) tmp_variable_node = LLLnode.from_list( tmp_variable, typ=source_lll.typ, pos=getpos(arg), location="memory", annotation='log_prealloacted %r' % source_lll.typ, ) # Store len. # holder.append(['mstore', len_placeholder, ['mload', unwrap_location(source_lll)]]) # Copy bytes. holder.append( make_setter(tmp_variable_node, source_lll, pos=getpos(arg), location='memory')) prealloacted[idx] = tmp_variable_node requires_dynamic_offset = any( [isinstance(data.typ, ByteArrayLike) for data in expected_data]) if requires_dynamic_offset: # Iterator used to zero pad memory. zero_pad_i = context.new_placeholder(BaseType('uint256')) dynamic_offset_counter = context.new_placeholder(BaseType(32)) dynamic_placeholder = context.new_placeholder(BaseType(32)) else: dynamic_offset_counter = None zero_pad_i = None # Create placeholder for static args. Note: order of new_*() is important. placeholder_map = {} for i, (_arg, data) in enumerate(zip(args, expected_data)): typ = data.typ if not isinstance(typ, ByteArrayLike): placeholder = context.new_placeholder(typ) else: placeholder = context.new_placeholder(BaseType(32)) placeholder_map[i] = placeholder # Populate static placeholders. for i, (arg, data) in enumerate(zip(args, expected_data)): typ = data.typ placeholder = placeholder_map[i] if not isinstance(typ, ByteArrayLike): holder, maxlen = pack_args_by_32( holder, maxlen, prealloacted.get(i, arg), typ, context, placeholder, zero_pad_i=zero_pad_i, pos=pos, ) # Dynamic position starts right after the static args. if requires_dynamic_offset: holder.append( LLLnode.from_list(['mstore', dynamic_offset_counter, maxlen])) # Calculate maximum dynamic offset placeholders, used for gas estimation. for _arg, data in zip(args, expected_data): typ = data.typ if isinstance(typ, ByteArrayLike): maxlen += 32 + ceil32(typ.maxlen) if requires_dynamic_offset: datamem_start = dynamic_placeholder + 32 else: datamem_start = placeholder_map[0] # Copy necessary data into allocated dynamic section. for i, (arg, data) in enumerate(zip(args, expected_data)): typ = data.typ if isinstance(typ, ByteArrayLike): pack_args_by_32(holder=holder, maxlen=maxlen, arg=prealloacted.get(i, arg), typ=typ, context=context, placeholder=placeholder_map[i], datamem_start=datamem_start, dynamic_offset_counter=dynamic_offset_counter, zero_pad_i=zero_pad_i, pos=pos) return holder, maxlen, dynamic_offset_counter, datamem_start
def gen_tuple_return(stmt, context, sub): abi_typ = abi_type_of(context.return_type) # according to the ABI, return types are ALWAYS tuples even if # only one element is being returned. # https://solidity.readthedocs.io/en/latest/abi-spec.html#function-selector-and-argument-encoding # "and the return values v_1, ..., v_k of f are encoded as # # enc((v_1, ..., v_k)) # i.e. the values are combined into a tuple and encoded. # " # therefore, wrap it in a tuple if it's not already a tuple. # (big difference between returning `(bytes,)` and `bytes`. abi_typ = ensure_tuple(abi_typ) abi_bytes_needed = abi_typ.static_size() + abi_typ.dynamic_size_bound() dst = context.memory_allocator.expand_memory(abi_bytes_needed) return_buffer = LLLnode(dst, location="memory", annotation="return_buffer", typ=context.return_type) check_assign(return_buffer, sub, pos=getpos(stmt)) if sub.value == "multi": if isinstance(context.return_type, TupleType) and not abi_typ.dynamic_size_bound(): # for tuples where every value is of the same type and a fixed length, # we can simplify the encoding by treating it as though it were an array base_types = set() for typ in context.return_type.members: while isinstance(typ, ListType): typ = typ.subtype base_types.add(typ.typ) if len(base_types) == 1: new_sub = LLLnode.from_list( context.new_internal_variable(context.return_type), typ=context.return_type, location="memory", ) setter = make_setter(new_sub, sub, "memory", pos=getpos(stmt)) return LLLnode.from_list( [ "seq", setter, make_return_stmt( stmt, context, new_sub, get_size_of_type(context.return_type) * 32, ), ], typ=None, pos=getpos(stmt), ) # in case of multi we can't create a variable to store location of the return expression # as multi can have data from multiple location like store, calldata etc encode_out = abi_encode(return_buffer, sub, pos=getpos(stmt), returns=True) load_return_len = ["mload", MemoryPositions.FREE_VAR_SPACE] os = [ "seq", ["mstore", MemoryPositions.FREE_VAR_SPACE, encode_out], make_return_stmt(stmt, context, return_buffer, load_return_len), ] return LLLnode.from_list(os, typ=None, pos=getpos(stmt), valency=0) # for tuple return types where a function is called inside the tuple, we # process the calls prior to encoding the return data if sub.value == "seq_unchecked" and sub.args[-1].value == "multi": encode_out = abi_encode(return_buffer, sub.args[-1], pos=getpos(stmt), returns=True) load_return_len = ["mload", MemoryPositions.FREE_VAR_SPACE] os = (["seq"] + sub.args[:-1] + [ ["mstore", MemoryPositions.FREE_VAR_SPACE, encode_out], make_return_stmt(stmt, context, return_buffer, load_return_len), ]) return LLLnode.from_list(os, typ=None, pos=getpos(stmt), valency=0) # for all othe cases we are creating a stack variable named sub_loc to store the location # of the return expression. This is done so that the return expression does not get evaluated # abi-encode uses a function named o_list which evaluate the expression multiple times sub_loc = LLLnode("sub_loc", typ=sub.typ, location=sub.location) encode_out = abi_encode(return_buffer, sub_loc, pos=getpos(stmt), returns=True) load_return_len = ["mload", MemoryPositions.FREE_VAR_SPACE] os = [ "with", "sub_loc", sub, [ "seq", ["mstore", MemoryPositions.FREE_VAR_SPACE, encode_out], make_return_stmt(stmt, context, return_buffer, load_return_len), ], ] return LLLnode.from_list(os, typ=None, pos=getpos(stmt), valency=0)