def make_byte_array_copier(destination, source, pos=None): if not isinstance(source.typ, (ByteArrayType, NullType)): raise TypeMismatchException( "Can only set a byte array to another byte array", pos) if isinstance( source.typ, ByteArrayType) and source.typ.maxlen > destination.typ.maxlen: raise TypeMismatchException( "Cannot cast from greater max-length %d to shorter max-length %d" % (source.typ.maxlen, destination.typ.maxlen)) # Special case: memory to memory if source.location == "memory" and destination.location == "memory": gas_calculation = GAS_IDENTITY + GAS_IDENTITYWORD * ( ceil32(source.typ.maxlen) // 32) o = LLLnode.from_list([ 'with', '_source', source, [ 'with', '_sz', ['add', 32, ['mload', '_source']], [ 'assert', [ 'call', ['add', 18, ['div', '_sz', 10]], 4, 0, '_source', '_sz', destination, '_sz' ] ] ] ], typ=None, add_gas_estimate=gas_calculation, annotation='Memory copy') return o pos_node = LLLnode.from_list('_pos', typ=source.typ, location=source.location) # Get the length if isinstance(source.typ, NullType): length = 1 elif source.location == "memory": length = ['add', ['mload', '_pos'], 32] elif source.location == "storage": length = ['add', ['sload', '_pos'], 32] pos_node = LLLnode.from_list(['sha3_32', pos_node], typ=source.typ, location=source.location) else: raise Exception("Unsupported location:" + source.location) if destination.location == "storage": destination = LLLnode.from_list(['sha3_32', destination], typ=destination.typ, location=destination.location) # Maximum theoretical length max_length = 32 if isinstance(source.typ, NullType) else source.typ.maxlen + 32 return LLLnode.from_list([ 'with', '_pos', 0 if isinstance(source.typ, NullType) else source, make_byte_slice_copier( destination, pos_node, length, max_length, pos=pos) ], typ=None)
def __init__(self, value, args=None, typ=None, location=None, pos=None, annotation='', mutable=True, add_gas_estimate=0, valency=0): if args is None: args = [] self.value = value self.args = args self.typ = typ assert isinstance(self.typ, NodeType) or self.typ is None, repr(self.typ) self.location = location self.pos = pos self.annotation = annotation self.mutable = mutable self.add_gas_estimate = add_gas_estimate self.as_hex = AS_HEX_DEFAULT self.valency = valency # Determine this node's valency (1 if it pushes a value on the stack, # 0 otherwise) and checks to make sure the number and valencies of # children are correct. Also, find an upper bound on gas consumption # Numbers if isinstance(self.value, int): self.valency = 1 self.gas = 5 elif isinstance(self.value, str): # Opcodes and pseudo-opcodes (e.g. clamp) if self.value.upper() in comb_opcodes: _, ins, outs, gas = comb_opcodes[self.value.upper()] self.valency = outs if len(self.args) != ins: raise Exception("Number of arguments mismatched: %r %r" % (self.value, self.args)) # We add 2 per stack height at push time and take it back # at pop time; this makes `break` easier to handle self.gas = gas + 2 * (outs - ins) for arg in self.args: # if arg.valency == 0: # raise Exception("Can't have a zerovalent argument to an opcode or a pseudo-opcode! %r: %r" % (arg.value, arg)) self.gas += arg.gas # Dynamic gas cost: 8 gas for each byte of logging data if self.value.upper()[0:3] == 'LOG' and isinstance(self.args[1].value, int): self.gas += self.args[1].value * 8 # Dynamic gas cost: non-zero-valued call if self.value.upper() == 'CALL' and self.args[2].value != 0: self.gas += 34000 # Dynamic gas cost: filling sstore (ie. not clearing) elif self.value.upper() == 'SSTORE' and self.args[1].value != 0: self.gas += 15000 # Dynamic gas cost: calldatacopy elif self.value.upper() in ('CALLDATACOPY', 'CODECOPY'): size = 34000 if isinstance(self.args[2].value, int): size = self.args[2].value elif isinstance(self.args[2], LLLnode) and len(self.args[2].args) > 0: size = self.args[2].args / [-1].value self.gas += ceil32(size) // 32 * 3 # Gas limits in call if self.value.upper() == 'CALL' and isinstance(self.args[0].value, int): self.gas += self.args[0].value # If statements elif self.value == 'if': if len(self.args) == 3: self.gas = self.args[0].gas + max(self.args[1].gas, self.args[2].gas) + 3 if self.args[1].valency != self.args[2].valency: raise Exception("Valency mismatch between then and else clause: %r %r" % (self.args[1], self.args[2])) if len(self.args) == 2: self.gas = self.args[0].gas + self.args[1].gas + 17 if self.args[1].valency: raise Exception("2-clause if statement must have a zerovalent body: %r" % self.args[1]) if not self.args[0].valency: raise Exception("Can't have a zerovalent argument as a test to an if statement! %r" % self.args[0]) if len(self.args) not in (2, 3): raise Exception("If can only have 2 or 3 arguments") self.valency = self.args[1].valency # With statements: with <var> <initial> <statement> elif self.value == 'with': if len(self.args) != 3: raise Exception("With statement must have 3 arguments") if len(self.args[0].args) or not isinstance(self.args[0].value, str): raise Exception("First argument to with statement must be a variable") if not self.args[1].valency: raise Exception("Second argument to with statement (initial value) cannot be zerovalent: %r" % self.args[1]) self.valency = self.args[2].valency self.gas = sum([arg.gas for arg in self.args]) + 5 # Repeat statements: repeat <index_memloc> <startval> <rounds> <body> elif self.value == 'repeat': if len(self.args[2].args) or not isinstance(self.args[2].value, int) or self.args[2].value <= 0: raise Exception("Number of times repeated must be a constant nonzero positive integer: %r" % self.args[2]) if not self.args[0].valency: raise Exception("First argument to repeat (memory location) cannot be zerovalent: %r" % self.args[0]) if not self.args[1].valency: raise Exception("Second argument to repeat (start value) cannot be zerovalent: %r" % self.args[1]) if self.args[3].valency: raise Exception("Third argument to repeat (clause to be repeated) must be zerovalent: %r" % self.args[3]) self.valency = 0 if self.args[1].value == 'mload' or self.args[1].value == 'sload': rounds = self.args[2].value else: rounds = abs(self.args[2].value - self.args[1].value) self.gas = rounds * (self.args[3].gas + 50) + 30 # Seq statements: seq <statement> <statement> ... elif self.value == 'seq': self.valency = self.args[-1].valency if self.args else 0 self.gas = sum([arg.gas for arg in self.args]) + 30 # Multi statements: multi <expr> <expr> ... elif self.value == 'multi': for arg in self.args: if not arg.valency: raise Exception("Multi expects all children to not be zerovalent: %r" % arg) self.valency = sum([arg.valency for arg in self.args]) self.gas = sum([arg.gas for arg in self.args]) # LLL brackets (don't bother gas counting) elif self.value == 'lll': self.valency = 1 self.gas = NullAttractor() # Stack variables else: self.valency = 1 self.gas = 5 if self.value == 'seq_unchecked': self.gas = sum([arg.gas for arg in self.args]) + 30 if self.value == 'if_unchecked': self.gas = self.args[0].gas + self.args[1].gas + 17 elif self.value is None and isinstance(self.typ, NullType): self.valency = 1 self.gas = 5 else: raise Exception("Invalid value for LLL AST node: %r" % self.value) assert isinstance(self.args, list) self.gas += self.add_gas_estimate
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) requires_dynamic_offset = any( [isinstance(data.typ, ByteArrayType) for data in expected_data]) if requires_dynamic_offset: zero_pad_i = context.new_placeholder( BaseType('uint256')) # Iterator used to zero pad memory. dynamic_offset_counter = context.new_placeholder(BaseType(32)) dynamic_placeholder = context.new_placeholder(BaseType(32)) else: dynamic_offset_counter = None zero_pad_i = None # Populate static placeholders. placeholder_map = {} for i, (arg, data) in enumerate(zip(args, expected_data)): typ = data.typ placeholder = context.new_placeholder(BaseType(32)) placeholder_map[i] = placeholder if not isinstance(typ, ByteArrayType): holder, maxlen = pack_args_by_32(holder, maxlen, 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 i, (arg, data) in enumerate(zip(args, expected_data)): typ = data.typ if isinstance(typ, ByteArrayType): 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, ByteArrayType): pack_args_by_32(holder=holder, maxlen=maxlen, arg=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 pack_args_by_32(holder, maxlen, arg, typ, context, placeholder, dynamic_offset_counter=None, datamem_start=None, zero_pad_i=None, pos=None): """ Copy necessary variables to pre-allocated memory section. :param holder: Complete holder for all args :param maxlen: Total length in bytes of the full arg section (static + dynamic). :param arg: Current arg to pack :param context: Context of arg :param placeholder: Static placeholder for static argument part. :param dynamic_offset_counter: position counter stored in static args. :param dynamic_placeholder: pointer to current position in memory to write dynamic values to. :param datamem_start: position where the whole datemem section starts. """ if isinstance(typ, BaseType): value = parse_expr(arg, context) value = base_type_conversion(value, value.typ, typ, pos) holder.append( LLLnode.from_list(['mstore', placeholder, value], typ=typ, location='memory')) elif isinstance(typ, ByteArrayType): bytez = b'' source_expr = Expr(arg, context) 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) for c in arg.s: if ord(c) >= 256: raise InvalidLiteralException( "Cannot insert special character %r into byte array" % c, pos) bytez += bytes([ord(c)]) holder.append(source_expr.lll_node) # Set static offset, in arg slot. holder.append( LLLnode.from_list( ['mstore', placeholder, ['mload', dynamic_offset_counter]])) # Get the biginning to write the ByteArray to. dest_placeholder = LLLnode.from_list( ['add', datamem_start, ['mload', dynamic_offset_counter]], typ=typ, location='memory', annotation="pack_args_by_32:dest_placeholder") copier = make_byte_array_copier(dest_placeholder, source_expr.lll_node, pos=pos) holder.append(copier) # Add zero padding. new_maxlen = ceil32(source_expr.lll_node.typ.maxlen) holder.append([ 'with', '_bytearray_loc', dest_placeholder, [ 'seq', [ 'repeat', zero_pad_i, ['mload', '_bytearray_loc'], new_maxlen, [ 'seq', [ 'if', ['ge', ['mload', zero_pad_i], new_maxlen], 'break' ], # stay within allocated bounds [ 'mstore8', [ 'add', ['add', '_bytearray_loc', 32], ['mload', zero_pad_i] ], 0 ] ] ] ] ]) # Increment offset counter. increment_counter = LLLnode.from_list([ 'mstore', dynamic_offset_counter, [ 'add', [ 'add', ['mload', dynamic_offset_counter], ['ceil32', ['mload', dest_placeholder]] ], 32 ] ]) holder.append(increment_counter) elif isinstance(typ, ListType): maxlen += (typ.count - 1) * 32 typ = typ.subtype def check_list_type_match(provided): # Check list types match. if provided != typ: raise TypeMismatchException( "Log list type '%s' does not match provided, expected '%s'" % (provided, typ)) # List from storage if isinstance(arg, ast.Attribute) and arg.value.id == 'self': stor_list = context.globals[arg.attr] check_list_type_match(stor_list.typ.subtype) size = stor_list.typ.count for offset in range(0, size): arg2 = LLLnode.from_list([ 'sload', ['add', ['sha3_32', Expr(arg, context).lll_node], offset] ], typ=typ) p_holder = context.new_placeholder( BaseType(32)) if offset > 0 else placeholder holder, maxlen = pack_args_by_32(holder, maxlen, arg2, typ, context, p_holder, pos=pos) # List from variable. elif isinstance(arg, ast.Name): size = context.vars[arg.id].size pos = context.vars[arg.id].pos check_list_type_match(context.vars[arg.id].typ.subtype) for i in range(0, size): offset = 32 * i arg2 = LLLnode.from_list(pos + offset, typ=typ, location='memory') p_holder = context.new_placeholder( BaseType(32)) if i > 0 else placeholder holder, maxlen = pack_args_by_32(holder, maxlen, arg2, typ, context, p_holder, pos=pos) # is list literal. else: holder, maxlen = pack_args_by_32(holder, maxlen, arg.elts[0], typ, context, placeholder, pos=pos) for j, arg2 in enumerate(arg.elts[1:]): holder, maxlen = pack_args_by_32(holder, maxlen, arg2, typ, context, context.new_placeholder( BaseType(32)), pos=pos) return holder, maxlen
def call_self_private(stmt_expr, context, sig): # ** Private Call ** # Steps: # (x) push current local variables # (x) push arguments # (x) push jumpdest (callback ptr) # (x) jump to label # (x) pop return values # (x) pop local variables method_name, expr_args, sig = call_lookup_specs(stmt_expr, context) pre_init = [] pop_local_vars = [] push_local_vars = [] pop_return_values = [] push_args = [] # Push local variables. if context.vars: var_slots = [(v.pos, v.size) for name, v in context.vars.items()] var_slots.sort(key=lambda x: x[0]) mem_from, mem_to = var_slots[0][ 0], var_slots[-1][0] + var_slots[-1][1] * 32 push_local_vars = [['mload', pos] for pos in range(mem_from, mem_to, 32)] pop_local_vars = [['mstore', pos, 'pass'] for pos in reversed(range(mem_from, mem_to, 32))] # Push Arguments if expr_args: inargs, inargsize, arg_pos = pack_arguments(sig, expr_args, context, return_placeholder=False, pos=getpos(stmt_expr)) push_args += [ inargs ] # copy arguments first, to not mess up the push/pop sequencing. static_arg_count = len(expr_args) * 32 static_pos = arg_pos + static_arg_count total_arg_size = ceil32(inargsize - 4) if len(expr_args) * 32 != total_arg_size: # requires dynamic section. ident = 'push_args_%d_%d_%d' % (sig.method_id, stmt_expr.lineno, stmt_expr.col_offset) start_label = ident + '_start' end_label = ident + '_end' i_placeholder = context.new_placeholder(BaseType('uint256')) push_args += [ ['mstore', i_placeholder, arg_pos + total_arg_size], ['label', start_label], [ 'if', ['lt', ['mload', i_placeholder], static_pos], ['goto', end_label] ], [ 'if_unchecked', ['ne', ['mload', ['mload', i_placeholder]], 0], ['mload', ['mload', i_placeholder]] ], [ 'mstore', i_placeholder, ['sub', ['mload', i_placeholder], 32] ], # decrease i ['goto', start_label], ['label', end_label] ] # push static section push_args += [['mload', pos] for pos in reversed(range(arg_pos, static_pos, 32))] # Jump to function label. jump_to_func = [ ['add', ['pc'], 6], # set callback pointer. ['goto', 'priv_{}'.format(sig.method_id)], ['jumpdest'], ] # Pop return values. returner = [0] if sig.output_type: output_placeholder, returner, output_size = call_make_placeholder( stmt_expr, context, sig) if output_size > 0: dynamic_offsets = [] if isinstance(sig.output_type, (BaseType, ListType)): pop_return_values = [[ 'mstore', ['add', output_placeholder, pos], 'pass' ] for pos in range(0, output_size, 32)] elif isinstance(sig.output_type, ByteArrayType): dynamic_offsets = [(0, sig.output_type)] pop_return_values = [ ['pop', 'pass'], ] elif isinstance(sig.output_type, TupleType): static_offset = 0 pop_return_values = [] for out_type in sig.output_type.members: if isinstance(out_type, ByteArrayType): pop_return_values.append([ 'mstore', ['add', output_placeholder, static_offset], 'pass' ]) dynamic_offsets.append(([ 'mload', ['add', output_placeholder, static_offset] ], out_type)) else: pop_return_values.append([ 'mstore', ['add', output_placeholder, static_offset], 'pass' ]) static_offset += 32 # append dynamic unpacker. dyn_idx = 0 for in_memory_offset, out_type in dynamic_offsets: ident = "%d_%d_arg_%d" % (stmt_expr.lineno, stmt_expr.col_offset, dyn_idx) dyn_idx += 1 start_label = 'dyn_unpack_start_' + ident end_label = 'dyn_unpack_end_' + ident i_placeholder = context.new_placeholder( typ=BaseType('uint256')) begin_pos = ['add', output_placeholder, in_memory_offset] # loop until length. o = LLLnode.from_list( [ 'seq_unchecked', ['mstore', begin_pos, 'pass'], # get len ['mstore', i_placeholder, 0], ['label', start_label], [ 'if', [ 'ge', ['mload', i_placeholder], ['ceil32', ['mload', begin_pos]] ], ['goto', end_label] ], # break [ 'mstore', [ 'add', ['add', begin_pos, 32], ['mload', i_placeholder] ], 'pass' ], # pop into correct memory slot. [ 'mstore', i_placeholder, ['add', 32, ['mload', i_placeholder]] ], # increment i ['goto', start_label], ['label', end_label] ], typ=None, annotation='dynamic unpacker', pos=getpos(stmt_expr)) pop_return_values.append(o) o = LLLnode.from_list(['seq_unchecked'] + pre_init + push_local_vars + push_args + jump_to_func + pop_return_values + pop_local_vars + [returner], typ=sig.output_type, location='memory', pos=getpos(stmt_expr), annotation='Internal Call: %s' % method_name, add_gas_estimate=sig.gas) o.gas += sig.gas return o