def zero_pad(bytez_placeholder, maxlen, context=None, zero_pad_i=None): zero_padder = LLLnode.from_list(['pass']) if maxlen > 0: # Iterator used to zero pad memory. if zero_pad_i is None: zero_pad_i = context.new_placeholder(BaseType('uint256')) zero_padder = LLLnode.from_list( [ 'repeat', zero_pad_i, ['mload', bytez_placeholder], ceil32(maxlen), [ 'seq', # stay within allocated bounds [ 'if', ['gt', ['mload', zero_pad_i], ceil32(maxlen)], 'break' ], [ 'mstore8', [ 'add', ['add', 32, bytez_placeholder], ['mload', zero_pad_i] ], 0 ] ] ], annotation="Zero pad", ) return zero_padder
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 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: dynamic_offset_counter = context.new_placeholder(BaseType(32)) dynamic_placeholder = context.new_placeholder(BaseType(32)) else: dynamic_offset_counter = 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, 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, pos=pos) return holder, maxlen, dynamic_offset_counter, datamem_start
def zero_pad(bytez_placeholder, maxlen, context=None, zero_pad_i=None): zero_padder = LLLnode.from_list(['pass']) if maxlen > 0: # Iterator used to zero pad memory. if zero_pad_i is None: zero_pad_i = context.new_placeholder(BaseType('uint256')) zero_padder = LLLnode.from_list( [ # the runtime length of the data rounded up to nearest 32 # from spec: # the actual value of X as a byte sequence, # followed by the *minimum* number of zero-bytes # such that len(enc(X)) is a multiple of 32. 'with', '_ceil32_end', ['ceil32', ['mload', bytez_placeholder]], [ 'repeat', zero_pad_i, ['mload', bytez_placeholder], ceil32(maxlen), [ 'seq', # stay within allocated bounds [ 'if', ['ge', ['mload', zero_pad_i], '_ceil32_end'], 'break' ], [ 'mstore8', [ 'add', ['add', 32, bytez_placeholder], ['mload', zero_pad_i] ], 0 ] ] ] ], annotation="Zero pad", ) return zero_padder
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): if isinstance(arg, LLLnode): value = unwrap_location(arg) else: 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, ByteArrayLike): if isinstance(arg, LLLnode): # Is prealloacted variable. source_lll = arg else: source_lll = parse_expr(arg, context) # 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_lll, pos=pos) holder.append(copier) # Add zero padding. new_maxlen = ceil32(source_lll.typ.maxlen) holder.append([ 'with', '_ceil32_end', ['ceil32', ['mload', dest_placeholder]], [ 'seq', [ 'with', '_bytearray_loc', dest_placeholder, [ 'seq', [ 'repeat', zero_pad_i, ['mload', '_bytearray_loc'], new_maxlen, [ 'seq', # stay within allocated bounds [ 'if', [ 'ge', ['mload', zero_pad_i], '_ceil32_end' ], 'break' ], [ '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, ], ], annotation='Increment dynamic offset counter') 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 mem_offset = 0 for i in range(0, size): storage_offset = i arg2 = LLLnode.from_list( [ 'sload', [ 'add', ['sha3_32', Expr(arg, context).lll_node], storage_offset ] ], typ=typ, ) holder, maxlen = pack_args_by_32( holder, maxlen, arg2, typ, context, placeholder + mem_offset, pos=pos, ) mem_offset += get_size_of_type(typ) * 32 # 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) mem_offset = 0 for _ in range(0, size): arg2 = LLLnode.from_list(pos + mem_offset, typ=typ, location='memory') holder, maxlen = pack_args_by_32( holder, maxlen, arg2, typ, context, placeholder + mem_offset, pos=pos, ) mem_offset += get_size_of_type(typ) * 32 # List from list literal. else: mem_offset = 0 for arg2 in arg.elts: holder, maxlen = pack_args_by_32( holder, maxlen, arg2, typ, context, placeholder + mem_offset, pos=pos, ) mem_offset += get_size_of_type(typ) * 32 return holder, maxlen
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 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, ByteArrayLike): dynamic_offsets = [(0, sig.output_type)] pop_return_values = [ ['pop', 'pass'], ] elif isinstance(sig.output_type, TupleLike): static_offset = 0 pop_return_values = [] for out_type in sig.output_type.members: if isinstance(out_type, ByteArrayLike): 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) call_body = (['seq_unchecked'] + pre_init + push_local_vars + push_args + jump_to_func + pop_return_values + pop_local_vars + [returner]) # If we have no return, we need to pop off pop_returner_call_body = ['pop', call_body ] if sig.output_type is None else call_body o = LLLnode.from_list(pop_returner_call_body, typ=sig.output_type, location='memory', pos=getpos(stmt_expr), annotation='Internal Call: %s' % method_name, add_gas_estimate=sig.gas) o.gas += sig.gas return o
def make_byte_array_copier(destination, source, pos=None): if not isinstance(source.typ, ByteArrayLike): btype = "byte array" if isinstance(destination.typ, ByteArrayType) else "string" raise TypeMismatch(f"Can only set a {btype} to another {btype}", pos) if isinstance( source.typ, ByteArrayLike) and source.typ.maxlen > destination.typ.maxlen: raise TypeMismatch( f"Cannot cast from greater max-length {source.typ.maxlen} to shorter " f"max-length {destination.typ.maxlen}") # stricter check for zeroing a byte array. if isinstance(source.typ, ByteArrayLike): if source.value is None and source.typ.maxlen != destination.typ.maxlen: raise TypeMismatch( f"Bad type for clearing bytes: expected {destination.typ}" f" but got {source.typ}") # 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", ["gas"], 4, 0, "_source", "_sz", destination, "_sz" ] ], ], ], # noqa: E501 typ=None, add_gas_estimate=gas_calculation, annotation="Memory copy", ) return o if source.value is None: pos_node = source else: pos_node = LLLnode.from_list("_pos", typ=source.typ, location=source.location) # Get the length if source.value is None: 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 CompilerPanic(f"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 source.value is None else source.typ.maxlen + 32 return LLLnode.from_list( [ "with", "_pos", 0 if source.value is None 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=None): 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 # Optional annotation properties for gas estimation self.total_gas = None self.func_name = None # 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 CompilerPanic( "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: # pop and pass are used to push/pop values on the stack to be # consumed for private functions, therefore we whitelist this as a zero valency # allowed argument. zero_valency_whitelist = {'pass', 'pop'} if arg.valency == 0 and arg.value not in zero_valency_whitelist: raise CompilerPanic( "Can't have a zerovalent argument to an opcode or a pseudo-opcode! " "%r: %r. Please file a bug report." % (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 len(self.args) == 2: self.gas = self.args[0].gas + self.args[1].gas + 17 if not self.args[0].valency: raise CompilerPanic( ("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 CompilerPanic("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 CompilerPanic("With statement must have 3 arguments") if len(self.args[0].args) or not isinstance( self.args[0].value, str): raise CompilerPanic( "First argument to with statement must be a variable") if not self.args[1].valency: raise CompilerPanic( ("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': is_invalid_repeat_count = any(( len(self.args[2].args), not isinstance(self.args[2].value, int), self.args[2].value <= 0, )) if is_invalid_repeat_count: raise CompilerPanic( ("Number of times repeated must be a constant nonzero " "positive integer: %r") % self.args[2]) if not self.args[0].valency: raise CompilerPanic(( "First argument to repeat (memory location) cannot be " "zerovalent: %r") % self.args[0]) if not self.args[1].valency: raise CompilerPanic( ("Second argument to repeat (start value) cannot be " "zerovalent: %r") % self.args[1]) if self.args[3].valency: raise CompilerPanic(( "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 CompilerPanic( "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 CompilerPanic("Invalid value for LLL AST node: %r" % self.value) assert isinstance(self.args, list) if valency is not None: self.valency = valency self.gas += self.add_gas_estimate
def add_variable_offset(parent, key): typ, location = parent.typ, parent.location if isinstance(typ, (StructType, TupleType)): if isinstance(typ, StructType): if not isinstance(key, str): raise TypeMismatchException( "Expecting a member variable access; cannot access element %r" % key) if key not in typ.members: raise TypeMismatchException( "Object does not have member variable %s" % key) subtype = typ.members[key] attrs = sorted(typ.members.keys()) if key not in attrs: raise TypeMismatchException( "Member %s not found. Only the following available: %s" % (key, " ".join(attrs))) index = attrs.index(key) annotation = key else: if not isinstance(key, int): raise TypeMismatchException( "Expecting a static index; cannot access element %r" % key) attrs = list(range(len(typ.members))) index = key annotation = None if location == 'storage': return LLLnode.from_list([ 'add', ['sha3_32', parent], LLLnode.from_list(index, annotation=annotation) ], typ=subtype, location='storage') elif location == 'storage_prehashed': return LLLnode.from_list([ 'add', parent, LLLnode.from_list(index, annotation=annotation) ], typ=subtype, location='storage') elif location == 'memory': offset = 0 for i in range(index): offset += 32 * get_size_of_type(typ.members[attrs[i]]) return LLLnode.from_list(['add', offset, parent], typ=typ.members[key], location='memory', annotation=annotation) else: raise TypeMismatchException( "Not expecting a member variable access") elif isinstance(typ, (ListType, MappingType)): if isinstance(typ, ListType): subtype = typ.subtype sub = [ 'uclamplt', base_type_conversion(key, key.typ, BaseType('num')), typ.count ] elif isinstance(typ, MappingType) and isinstance( key.typ, ByteArrayType): if not isinstance(typ.keytype, ByteArrayType) or ( typ.keytype.maxlen < key.typ.maxlen): raise TypeMismatchException( 'Mapping keys of bytes cannot be cast, use exact same bytes type of: %s', str(typ.keytype)) subtype = typ.valuetype if len(key.args[0].args) == 3: # handle bytes literal. sub = LLLnode.from_list([ 'seq', key, [ 'sha3', ['add', key.args[0].args[-1], 32], ceil32(key.typ.maxlen) ] ]) else: sub = LLLnode.from_list([ 'sha3', ['add', key.args[0].value, 32], ceil32(key.typ.maxlen) ]) else: subtype = typ.valuetype sub = base_type_conversion(key, key.typ, typ.keytype) if location == 'storage': return LLLnode.from_list(['add', ['sha3_32', parent], sub], typ=subtype, location='storage') elif location == 'storage_prehashed': return LLLnode.from_list(['add', parent, sub], typ=subtype, location='storage') elif location == 'memory': if isinstance(typ, MappingType): raise TypeMismatchException( "Can only have fixed-side arrays in memory, not mappings") offset = 32 * get_size_of_type(subtype) return LLLnode.from_list(['add', ['mul', offset, sub], parent], typ=subtype, location='memory') else: raise TypeMismatchException("Not expecting an array access ") else: raise TypeMismatchException( "Cannot access the child of a constant variable! %r" % typ)
def __init__( self, value: Union[str, int], args: List["LLLnode"] = None, typ: "BaseType" = None, location: str = None, pos: Optional[Tuple[int, int]] = None, annotation: Optional[str] = None, mutable: bool = True, add_gas_estimate: int = 0, valency: Optional[int] = None, ): 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 # Optional annotation properties for gas estimation self.total_gas = None self.func_name = None # 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 get_comb_opcodes(): _, ins, outs, gas = get_comb_opcodes()[self.value.upper()] self.valency = outs if len(self.args) != ins: raise CompilerPanic( f"Number of arguments mismatched: {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: # pop and pass are used to push/pop values on the stack to be # consumed for private functions, therefore we whitelist this as a zero valency # allowed argument. zero_valency_whitelist = {"pass", "pop"} if arg.valency == 0 and arg.value not in zero_valency_whitelist: raise CompilerPanic( "Can't have a zerovalent argument to an opcode or a pseudo-opcode! " f"{arg.value}: {arg}. Please file a bug report.") 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 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 len(self.args) == 2: self.gas = self.args[0].gas + self.args[1].gas + 17 if not self.args[0].valency: raise CompilerPanic( "Can't have a zerovalent argument as a test to an if " f"statement! {self.args[0]}") if len(self.args) not in (2, 3): raise CompilerPanic("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 CompilerPanic("With statement must have 3 arguments") if len(self.args[0].args) or not isinstance( self.args[0].value, str): raise CompilerPanic( "First argument to with statement must be a variable") if not self.args[1].valency: raise CompilerPanic( ("Second argument to with statement (initial value) " f"cannot be zerovalent: {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": is_invalid_repeat_count = any(( len(self.args[2].args), not isinstance(self.args[2].value, int), isinstance(self.args[2].value, int) and self.args[2].value <= 0, )) if is_invalid_repeat_count: raise CompilerPanic( ("Number of times repeated must be a constant nonzero " f"positive integer: {self.args[2]}")) if not self.args[0].valency: raise CompilerPanic(( "First argument to repeat (memory location) cannot be " f"zerovalent: {self.args[0]}")) if not self.args[1].valency: raise CompilerPanic( ("Second argument to repeat (start value) cannot be " f"zerovalent: {self.args[1]}")) if self.args[3].valency: raise CompilerPanic(( "Third argument to repeat (clause to be repeated) must " f"be zerovalent: {self.args[3]}")) self.valency = 0 rounds: int if self.args[1].value in ("calldataload", "mload" ) or self.args[1].value == "sload": if isinstance(self.args[2].value, int): rounds = self.args[2].value else: raise CompilerPanic( f"Unsupported rounds argument type. {self.args[2]}" ) else: if isinstance(self.args[2].value, int) and isinstance( self.args[1].value, int): rounds = abs(self.args[2].value - self.args[1].value) else: raise CompilerPanic( f"Unsupported second argument types. {self.args}") 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 CompilerPanic( f"Multi expects all children to not be zerovalent: {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: self.valency = 1 # None LLLnodes always get compiled into something else, e.g. # mzero or PUSH1 0, and the gas will get re-estimated then. self.gas = 3 else: raise CompilerPanic( f"Invalid value for LLL AST node: {self.value}") assert isinstance(self.args, list) if valency is not None: self.valency = valency self.gas += self.add_gas_estimate