def make_byte_array_copier(destination, source): if not isinstance(source.typ, (ByteArrayType, NullType)): raise TypeMismatchException("Can only set a byte array to another byte array") 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 = opcodes.GIDENTITYBASE + \ opcodes.GIDENTITYWORD * (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) 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)], typ=None)
def pack_logging_data(expected_data, args, context): # 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) dynamic_offset_counter = context.new_placeholder(BaseType(32)) dynamic_placeholder = context.new_placeholder(BaseType(32)) # 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) # Dynamic position starts right after the static args. 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) # Obtain the start of the arg section. if isinstance(expected_data[0].typ, ListType): datamem_start = holder[1].to_list()[1][0] else: datamem_start = dynamic_placeholder + 32 # Copy necessary data into allocated dynamic section. for i, (arg, data) in enumerate(zip(args, expected_data)): typ = data.typ 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) return holder, maxlen, dynamic_offset_counter, datamem_start
def __init__(self, value, args=None, typ=None, location=None, pos=None, annotation='', mutable=True, add_gas_estimate=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 # 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 (eg. 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" % 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 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 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)